diff --git a/frontend/src/components/config/util.tsx b/frontend/src/components/config/util.tsx index 2db6346c7..3b6008dee 100644 --- a/frontend/src/components/config/util.tsx +++ b/frontend/src/components/config/util.tsx @@ -19,7 +19,7 @@ import { SearchX, } from "lucide-react"; import { ReactNode, useState } from "react"; -import { cn, snake_case_to_upper_space_case } from "@lib/utils"; +import { cn } from "@lib/utils"; import { Dialog, DialogContent, @@ -36,6 +36,7 @@ import { CommandInput, CommandItem, } from "@ui/command"; +import { snake_case_to_upper_space_case } from "@lib/formatting"; export const ConfigItem = ({ label, @@ -52,7 +53,7 @@ export const ConfigItem = ({ className )} > -
{snake_case_to_upper_space_case(label)}
+
{snake_case_to_upper_space_case(label)}
{children} ); diff --git a/frontend/src/components/dashboard/deployments-chart.tsx b/frontend/src/components/dashboard/deployments-chart.tsx index 0ff1729cd..49e6b1d16 100644 --- a/frontend/src/components/dashboard/deployments-chart.tsx +++ b/frontend/src/components/dashboard/deployments-chart.tsx @@ -9,13 +9,18 @@ import { PieChart } from "react-minimal-pie-chart"; import { useRead } from "@lib/hooks"; import { Rocket } 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 DeploymentsChart = () => { const summary = useRead("GetDeploymentsSummary", {}).data; return ( - +
Deployments @@ -26,25 +31,45 @@ export const DeploymentsChart = () => {
- + {summary?.running}{" "} Running - + {summary?.stopped}{" "} Stopped - + {summary?.not_deployed}{" "} Not Deployed - + {summary?.unknown}{" "} Unknown @@ -57,25 +82,25 @@ export const DeploymentsChart = () => { lineWidth={30} data={[ { - color: "#22C55E", + color: hex_color_by_intention("Good"), value: summary?.running ?? 0, title: "running", key: "running", }, { - color: "#EF0044", + color: hex_color_by_intention("Critical"), value: summary?.stopped ?? 0, title: "stopped", key: "stopped", }, { - color: "#3B82F6", + color: hex_color_by_intention("Neutral"), value: summary?.not_deployed ?? 0, title: "not-deployed", key: "not-deployed", }, { - color: "purple", + color: hex_color_by_intention("Unknown"), value: summary?.unknown ?? 0, title: "unknown", key: "unknown", diff --git a/frontend/src/components/dashboard/servers-chart.tsx b/frontend/src/components/dashboard/servers-chart.tsx index 52965c3cd..2d519a581 100644 --- a/frontend/src/components/dashboard/servers-chart.tsx +++ b/frontend/src/components/dashboard/servers-chart.tsx @@ -9,6 +9,11 @@ import { PieChart } from "react-minimal-pie-chart"; import { useRead } from "@lib/hooks"; import { Server } from "lucide-react"; import { Link } from "react-router-dom"; +import { + hex_color_by_intention, + text_color_class_by_intention, +} from "@lib/color"; +import { cn } from "@lib/utils"; export const ServersChart = () => { const { data } = useRead("GetServersSummary", {}); @@ -25,15 +30,36 @@ export const ServersChart = () => {
- {data?.healthy} + + {data?.healthy}{" "} + Healthy - {data?.unhealthy} + + {data?.unhealthy}{" "} + Unhealthy - {data?.disabled} + + {data?.disabled}{" "} + Disabled
@@ -44,19 +70,19 @@ export const ServersChart = () => { lineWidth={30} data={[ { - color: "#22C55E", + color: hex_color_by_intention("Good"), value: data?.healthy ?? 0, title: "healthy", key: "healthy", }, { - color: "#EF0044", + color: hex_color_by_intention("Critical"), value: data?.unhealthy ?? 0, title: "unhealthy", key: "unhealthy", }, { - color: "#3B82F6", + color: hex_color_by_intention("Neutral"), value: data?.disabled ?? 0, title: "disabled", key: "disabled", diff --git a/frontend/src/components/permissions.tsx b/frontend/src/components/permissions.tsx deleted file mode 100644 index df9d20c5d..000000000 --- a/frontend/src/components/permissions.tsx +++ /dev/null @@ -1,192 +0,0 @@ -// import { Section } from "./layouts"; -// import { Loader2, Lock, PlusCircle, User } from "lucide-react"; -// import { useRead, useWrite } from "@lib/hooks"; -// import { UsableResource } from "@types"; -// import { Card, CardHeader, CardTitle } from "@ui/card"; -// import { -// Select, -// SelectContent, -// SelectGroup, -// SelectItem, -// SelectTrigger, -// SelectValue, -// } from "@ui/select"; -// import { Types } from "@monitor/client"; -// import { -// Dialog, -// DialogContent, -// DialogFooter, -// DialogHeader, -// DialogTitle, -// DialogTrigger, -// } from "@ui/dialog"; -// import { Button } from "@ui/button"; -// import { useState } from "react"; -// import { ResourceTarget } from "@monitor/client/dist/types"; - -// const Username = ({ user_id }: { user_id: string }) => { -// const username = useRead("GetUsername", { user_id }).data?.username; -// return <>{username}; -// }; - -// const NewPermission = ({ id, type }: ResourceTarget) => { -// const [open, set] = useState(false); -// const [user_id, setUserId] = useState(); -// const [permission, setPermission] = useState(); -// const users = useRead("GetUsers", {}).data?.filter((u) => !u.admin); -// const { mutate, isPending } = useWrite("UpdateUserPermissionsOnTarget"); - -// return ( -// -// -// -// -// -// -// Add User -// -//
-//
-// User -// -//
-//
-// Permissions Level -// -//
-//
-// -// -// -//
-//
-// ); -// }; - -// // export const ResourcePermissions = ({ -// // type, -// // id, -// // }: { -// // type: UsableResource; -// // id: string; -// // }) => { -// // const user = useRead("GetUser", {}).data; -// // const admin = user?.admin; -// // const me = user?._id?.$oid; -// // const permissions = useRead(`Get${type}`, { id }, { enabled: admin }).data -// // ?.permissions; - -// // const users = useRead("GetUsers", {}).data?.filter((u) => !u.admin); - -// // const { mutate: update, isPending } = useWrite( -// // "UpdateUserPermissionsOnTarget" -// // ); - -// // const display = Object.keys(permissions ?? {}) -// // .filter((id) => id != me) -// // .filter((id) => !users?.find((u) => u._id?.$oid === id)?.admin); - -// // if (!admin || !display.length) return null; - -// // return ( -// //
} -// // actions={} -// // > -// //
-// // {display.map((user_id) => ( -// // -// // -// // -// // -// // -// // -// // -// // -// // -// // ))} -// //
-// //
-// // ); -// // }; diff --git a/frontend/src/components/resources/alerter/config.tsx b/frontend/src/components/resources/alerter/config.tsx new file mode 100644 index 000000000..f2e35fb3f --- /dev/null +++ b/frontend/src/components/resources/alerter/config.tsx @@ -0,0 +1,56 @@ +import { Config } from "@components/config"; +import { useRead, useWrite } from "@lib/hooks"; +import { Types } from "@monitor/client"; +import { useState } from "react"; + +export const AlerterConfig = ({ id }: { id: string }) => { + const config = useRead("GetAlerter", { alerter: id }).data?.config; + if (config?.type === "Slack") return ; + if (config?.type === "Custom") return ; +}; + +const SlackAlerterConfig = ({ id }: { id: string }) => { + const config = useRead("GetAlerter", { alerter: id }).data?.config; + const [update, set] = useState>({}); + const { mutate } = useWrite("UpdateAlerter"); + if (!config) return null; + + return ( + mutate({ id, config: { type: "Slack", params: update } })} + components={{ + general: { + general: { + url: true, + }, + }, + }} + /> + ); +}; + +const CustomAlerterConfig = ({ id }: { id: string }) => { + const config = useRead("GetAlerter", { alerter: id }).data?.config; + const [update, set] = useState>({}); + const { mutate } = useWrite("UpdateAlerter"); + if (!config) return null; + + return ( + mutate({ id, config: { type: "Custom", params: update } })} + components={{ + general: { + general: { + url: true, + }, + }, + }} + /> + ); +}; diff --git a/frontend/src/components/resources/alerter/index.tsx b/frontend/src/components/resources/alerter/index.tsx index 139ae5453..5db70f554 100644 --- a/frontend/src/components/resources/alerter/index.tsx +++ b/frontend/src/components/resources/alerter/index.tsx @@ -13,11 +13,11 @@ import { RequiredResourceComponents } from "@types"; import { Input } from "@ui/input"; import { AlarmClock } from "lucide-react"; import { useState } from "react"; -import { Config } from "@components/config"; import { DataTable } from "@ui/data-table"; import { ResourceComponents } from ".."; import { Link } from "react-router-dom"; import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card"; +import { AlerterConfig } from "./config"; const useAlerter = (id?: string) => useRead("ListAlerters", {}).data?.find((d) => d.id === id); @@ -64,52 +64,6 @@ const NewAlerter = () => { ); }; -const SlackAlerterConfig = ({ id }: { id: string }) => { - const config = useRead("GetAlerter", { alerter: id }).data?.config; - const [update, set] = useState>({}); - const { mutate } = useWrite("UpdateAlerter"); - if (!config) return null; - - return ( - mutate({ id, config: { type: "Slack", params: update } })} - components={{ - general: { - general: { - url: true, - }, - }, - }} - /> - ); -}; - -const CustomAlerterConfig = ({ id }: { id: string }) => { - const config = useRead("GetAlerter", { alerter: id }).data?.config; - const [update, set] = useState>({}); - const { mutate } = useWrite("UpdateAlerter"); - if (!config) return null; - - return ( - mutate({ id, config: { type: "Custom", params: update } })} - components={{ - general: { - general: { - url: true, - }, - }, - }} - /> - ); -}; - const AlerterTable = () => { const alerters = useRead("ListAlerters", {}).data; return ( @@ -157,12 +111,9 @@ export const AlerterComponents: RequiredResourceComponents = { Icon: () => , Description: ({ id }) => <>{useAlerter(id)?.info.alerter_type} alerter, Info: ({ id }) => <>{id}, + Status: () => <>, Page: { - Config: ({ id }: { id: string }) => { - const config = useRead("GetAlerter", { alerter: id }).data?.config; - if (config?.type === "Slack") return ; - if (config?.type === "Custom") return ; - }, + Config: AlerterConfig, }, Actions: () => null, Table: AlerterTable, diff --git a/frontend/src/components/resources/build/index.tsx b/frontend/src/components/resources/build/index.tsx index 67f8b140c..1cf2f37ba 100644 --- a/frontend/src/components/resources/build/index.tsx +++ b/frontend/src/components/resources/build/index.tsx @@ -1,7 +1,6 @@ import { NewResource } from "@components/layouts"; import { ConfirmButton } from "@components/util"; import { useExecute, useRead, useWrite } from "@lib/hooks"; -import { fmt_date_with_minutes, fmt_version } from "@lib/utils"; import { RequiredResourceComponents } from "@types"; import { DataTable } from "@ui/data-table"; import { Input } from "@ui/input"; @@ -13,6 +12,8 @@ import { BuildChart } from "@components/dashboard/builds-chart"; import { TagsWithBadge, useTagsFilter } from "@components/tags"; import { useToast } from "@ui/use-toast"; import { BuildConfig } from "./config"; +import { fmt_date_with_minutes, fmt_version } from "@lib/formatting"; +import { fill_color_class_by_intention } from "@lib/color"; const useBuild = (id?: string) => useRead("ListBuilds", {}).data?.find((d) => d.id === id); @@ -43,7 +44,7 @@ const Name = ({ id }: { id: string }) => <>{useBuild(id)?.name}; const Icon = ({ id }: { id: string }) => { const building = useRead("GetBuildActionState", { build: id }).data?.building; const className = building - ? "w-4 h-4 animate-spin fill-green-500" + ? "w-4 h-4 animate-spin " + fill_color_class_by_intention("Good") : "w-4 h-4"; return ; }; @@ -74,7 +75,7 @@ const BuildTable = () => { }, { header: "Repo", - accessorKey: "info.repo" + accessorKey: "info.repo", }, { header: "Version", diff --git a/frontend/src/components/resources/builder/index.tsx b/frontend/src/components/resources/builder/index.tsx index 29fab125c..01c56d89e 100644 --- a/frontend/src/components/resources/builder/index.tsx +++ b/frontend/src/components/resources/builder/index.tsx @@ -136,6 +136,7 @@ export const BuilderComponents: RequiredResourceComponents = { ), Icon: () => , + Status: () => <>, Page: { Config: BuilderConfig, }, diff --git a/frontend/src/components/resources/deployment/actions.tsx b/frontend/src/components/resources/deployment/actions.tsx index bb6e3c46c..2e54b1c6a 100644 --- a/frontend/src/components/resources/deployment/actions.tsx +++ b/frontend/src/components/resources/deployment/actions.tsx @@ -25,7 +25,6 @@ export const RedeployContainer = ({ id }: DeploymentId) => { return ( } onClick={() => mutate({ deployment: id })} disabled={isPending} @@ -120,7 +119,8 @@ export const DeleteDeployment = ({ id }: { id: string }) => { const { data: d } = useRead("GetDeployment", { deployment: id }); const { mutateAsync, isPending } = useWrite("DeleteDeployment"); - const deleting = useRead("GetDeploymentActionState", { deployment: id }).data?.deleting; + const deleting = useRead("GetDeploymentActionState", { deployment: id }).data + ?.deleting; if (!d) return null; return ( diff --git a/frontend/src/components/resources/deployment/config/components/image.tsx b/frontend/src/components/resources/deployment/config/components/image.tsx index f2025c394..82446e8b6 100644 --- a/frontend/src/components/resources/deployment/config/components/image.tsx +++ b/frontend/src/components/resources/deployment/config/components/image.tsx @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { ConfigItem, ResourceSelector } from "@components/config/util"; +import { fmt_version } from "@lib/formatting"; import { useRead } from "@lib/hooks"; -import { fmt_version } from "@lib/utils"; import { Types } from "@monitor/client"; import { Input } from "@ui/input"; import { diff --git a/frontend/src/components/resources/deployment/config/components/restart.tsx b/frontend/src/components/resources/deployment/config/components/restart.tsx index 908c77e20..9377c9859 100644 --- a/frontend/src/components/resources/deployment/config/components/restart.tsx +++ b/frontend/src/components/resources/deployment/config/components/restart.tsx @@ -7,7 +7,7 @@ import { SelectTrigger, SelectValue, } from "@ui/select"; -import { keys } from "@lib/utils"; +import { object_keys } from "@lib/utils"; const format_mode = (m: string) => m.split("-").join(" "); @@ -27,7 +27,7 @@ export const RestartModeSelector = ({ - {keys(Types.RestartMode).map((mode) => ( + {object_keys(Types.RestartMode).map((mode) => ( useRead("ListDeployments", {}, { refetchInterval: 5000 }).data?.find( (d) => d.id === id ); -const deployment_state_color = (state: Types.DockerContainerState) => { - if (state === Types.DockerContainerState.Running) return "green-500"; - if (state === Types.DockerContainerState.Paused) return "orange-500"; - if (state === Types.DockerContainerState.NotDeployed) return "blue-500"; - return "red-500"; -}; - -const deployment_state_fill_color = (state: Types.DockerContainerState) => { - return `fill-${deployment_state_color(state)}`; -}; - -const deployment_state_text_color = (state: Types.DockerContainerState) => { - return `text-${deployment_state_color(state)}`; -}; - const Icon = ({ id }: { id?: string }) => { const state = useDeployment(id)?.info.state; return ( ); }; @@ -161,7 +155,9 @@ export const DeploymentTable = ({ header: "State", cell: ({ row }) => { const state = row.original.info.state; - const color = deployment_state_text_color(state); + const color = text_color_class_by_intention( + deployment_state_intention(state) + ); return (
{snake_case_to_upper_space_case(state)} @@ -192,11 +188,10 @@ export const DeploymentComponents: RequiredResourceComponents = { Status: ({ id }) => { const state = useDeployment(id)?.info.state ?? Types.DockerContainerState.Unknown; - return ( -
- {snake_case_to_upper_space_case(state)} -
+ const color = text_color_class_by_intention( + deployment_state_intention(state) ); + return
{snake_case_to_upper_space_case(state)}
; }, Actions: ({ id }) => (
diff --git a/frontend/src/components/resources/procedure/index.tsx b/frontend/src/components/resources/procedure/index.tsx index 7c4bf8671..4dedf6771 100644 --- a/frontend/src/components/resources/procedure/index.tsx +++ b/frontend/src/components/resources/procedure/index.tsx @@ -27,6 +27,7 @@ export const ProcedureComponents: RequiredResourceComponents = { Description: ({ id }) => <>{useProcedure(id)?.info.procedure_type}, Info: () => <>, Icon: () => , + Status: () => <>, Page: { Config: ProcedureConfig, }, diff --git a/frontend/src/components/resources/procedure/index2.md b/frontend/src/components/resources/procedure/index2.md deleted file mode 100644 index 53166e605..000000000 --- a/frontend/src/components/resources/procedure/index2.md +++ /dev/null @@ -1,450 +0,0 @@ -// import { ResourceSelector } from "@components/config/util"; -// import { NewResource } from "@components/layouts"; -// import { ConfirmButton } from "@components/util"; -// import { useExecute, useRead, useWrite } from "@lib/hooks"; -// import { fmt_date_with_minutes } from "@lib/utils"; -// import { Types } from "@monitor/client"; -// import { Execution } from "@monitor/client/dist/types"; -// import { Icon } from "@radix-ui/react-select"; -// import { RequiredResourceComponents, UsableResource } from "@types"; -// import { Button } from "@ui/button"; -// import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card"; -// import { DataTable } from "@ui/data-table"; -// import { Input } from "@ui/input"; -// import { -// Select, -// SelectContent, -// SelectGroup, -// SelectItem, -// SelectTrigger, -// SelectValue, -// } from "@ui/select"; -// import { Loader2, Route, Save } from "lucide-react"; -// import React, { useEffect, useState } from "react"; -// import { Link } from "react-router-dom"; - -// const useProcedure = (id?: string) => -// useRead("ListProcedures", {}).data?.find((d) => d.id === id); - -// const Name = ({ id }: { id: string }) => <>{useProcedure(id)?.name}; - -// // const NewProcedure = ({ parent }: { parent?: Types.Procedure }) => { -// // const [name, setName] = useState(""); -// // const [type, setType] = useState("Execution"); - -// // const update_parent = useWrite("UpdateProcedure").mutate; - -// // const { mutateAsync } = useWrite("CreateProcedure", { -// // onSuccess: ({ _id }) => { -// // if (!parent?._id?.$oid || !_id?.$oid) return; -// // if ( -// // parent.config.type === "Sequence" || -// // parent.config.type === "Parallel" -// // ) { -// // update_parent({ -// // id: parent._id.$oid, -// // config: { -// // ...parent.config, -// // data: [...parent.config.data, { enabled: true, id: _id!.$oid }], -// // }, -// // }); -// // } -// // }, -// // }); - -// // return ( -// // -// // mutateAsync({ -// // name, -// // config: { -// // type, -// // data: get_default_data(type), -// // } as Types.ProcedureConfig, -// // }) -// // } -// // enabled={!!name} -// // > -// //
-// // Procedure Name -// // setName(e.target.value)} -// // /> -// //
-// //
-// // Procedure Type -// // -// //
-// //
-// // ); -// // }; - -// // type ExecutionType = Extract< -// // Types.ProcedureConfig, -// // { type: "Execution" } -// // >["data"]["type"]; - -// // type ExecutionConfigComponent< -// // T extends ExecutionType, -// // P = Extract["params"] -// // > = React.FC<{ -// // params: P; -// // setParams: React.Dispatch>; -// // }>; - -// // type ExecutionConfigParams = Extract< -// // Execution, -// // { type: T } -// // >["params"]; - -// // type ExecutionConfigs = { -// // [ExType in ExecutionType]: { -// // component: ExecutionConfigComponent; -// // params: ExecutionConfigParams; -// // }; -// // }; - -// // const TypeSelector = ({ -// // type, -// // selected, -// // onSelect, -// // }: { -// // type: UsableResource; -// // selected: string; -// // onSelect: (value: string) => void; -// // }) => ( -// //
-// // {type} -// // -// //
-// // ); - -// // const EXEC_TYPES: ExecutionConfigs = { -// // None: { -// // params: {}, -// // component: () => <>, -// // }, -// // CloneRepo: { -// // params: { id: "" }, -// // component: ({ params, setParams }) => ( -// // setParams((p) => ({ ...p, id }))} -// // /> -// // ), -// // }, -// // Deploy: { -// // params: { deployment_id: "" }, -// // component: ({ params, setParams }) => ( -// // setParams((p) => ({ ...p, deployment_id: id }))} -// // /> -// // ), -// // }, -// // PruneDockerContainers: { -// // params: { server_id: "" }, -// // component: ({ params, setParams }) => ( -// // setParams((p) => ({ ...p, server_id }))} -// // /> -// // ), -// // }, -// // PruneDockerImages: { -// // params: { server_id: "" }, -// // component: ({ params, setParams }) => ( -// // setParams((p) => ({ ...p, id }))} -// // /> -// // ), -// // }, -// // PruneDockerNetworks: { -// // params: { server_id: "" }, -// // component: ({ params, setParams }) => ( -// // setParams((p) => ({ ...p, id }))} -// // /> -// // ), -// // }, -// // PullRepo: { -// // params: { id: "" }, -// // component: ({ params, setParams }) => ( -// // setParams((p) => ({ ...p, id }))} -// // /> -// // ), -// // }, -// // RemoveContainer: { -// // params: { deployment_id: "" }, -// // component: ({ params, setParams }) => ( -// // setParams((p) => ({ ...p, deployment_id: id }))} -// // /> -// // ), -// // }, -// // RunBuild: { -// // params: { build_id: "" }, -// // component: ({ params, setParams }) => ( -// // setParams((p) => ({ ...p, build_id }))} -// // /> -// // ), -// // }, -// // RunProcedure: { -// // params: { procedure_id: "" }, -// // component: ({ params, setParams }) => ( -// // setParams((p) => ({ ...p, procedure_id: id }))} -// // /> -// // ), -// // }, -// // StartContainer: { -// // params: { deployment_id: "" }, -// // component: ({ params, setParams }) => ( -// // setParams((p) => ({ ...p, deployment_id: id }))} -// // /> -// // ), -// // }, -// // StopAllContainers: { -// // params: { server_id: "" }, -// // component: ({ params, setParams }) => ( -// // setParams((p) => ({ ...p, server_id: id }))} -// // /> -// // ), -// // }, -// // StopContainer: { -// // params: { deployment_id: "" }, -// // component: ({ params, setParams }) => ( -// // setParams((p) => ({ ...p, deployment_id: id }))} -// // /> -// // ), -// // }, -// // }; - -// const UpdateProcedure = ({ -// id, -// procedure, -// }: { -// id: string; -// procedure: Types.ProcedureConfig; -// }) => { -// const { mutate } = useWrite("UpdateProcedure"); - -// return ( -// -// ); -// }; - -// // const ExecutionConfig = ({ id }: { id: string }) => { -// // const procedure = useRead("GetProcedure", { id }).data; -// // if (procedure?.config.type !== "Execution") return null; - -// // // eslint-disable-next-line react-hooks/rules-of-hooks -// // const [type, setType] = useState(procedure.config.data.type); - -// // // eslint-disable-next-line react-hooks/rules-of-hooks -// // const [params, setParams] = useState(procedure.config.data.params); - -// // // eslint-disable-next-line react-hooks/rules-of-hooks -// // useEffect(() => { -// // if (procedure?.config.type !== "Execution") return; -// // if (type !== procedure.config.data.type) { -// // setParams(EXEC_TYPES[type].params); -// // } -// // }, [procedure, type]); - -// // const Component = EXEC_TYPES[type].component; - -// // return ( -// //
-// //
-// // {procedure.name} -// // -// //
-// //
-// // Execution Type -// // -// //
-// //
-// // {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} -// // -// //
-// //
-// //
{JSON.stringify(procedure?.config, null, 2)}
-// //
-// //
-// // ); -// // }; - -// // const SequenceConfig = ({ id }: { id: string }) => { -// // const procedure = useRead("GetProcedure", { id }).data; -// // if (procedure?.config.type !== "Sequence") return null; - -// // return ( -// //
-// //
-// // {procedure?.name} -// // -// //
-// //
{JSON.stringify(procedure?.config, null, 2)}
-// //
-// // {procedure.config.data.map((p) => ( -// // -// // ))} -// //
-// //
-// // ); -// // }; - -// // export const ProcedureConfig = ({ id }: { id: string }) => { -// // const procedure = useRead("GetProcedure", { id }).data; -// // if (procedure?.config.type === "Sequence") return ; -// // if (procedure?.config.type === "Execution") -// // return ; -// // }; - -// export const ProcedureDashboard = () => { -// const procedure_count = useRead("ListProcedures", {}).data?.length; -// return ( -// -// -// -//
-// Procedures -// {procedure_count} Total -//
-// -//
-//
-// -// ); -// }; - -// export const ProcedureComponents: RequiredResourceComponents = { -// Name: ({ id }) => <>{useProcedure(id)?.name}, -// Description: ({ id }) => <>{useProcedure(id)?.info.procedure_type}, -// Info: ({ id }) => <>{id}, -// Icon: () => , -// Page: { -// // Config: ({ id }) => , -// }, -// Actions: ({ id }) => { -// const running = useRead("GetProcedureActionState", { procedure: id }).data?.running; -// const { mutate, isPending } = useExecute("RunProcedure"); -// return ( -// -// ) : ( -// -// ) -// } -// onClick={() => mutate({ procedure: id })} -// disabled={running || isPending} -// /> -// ); -// }, -// Table: () => { -// const alerters = useRead("ListAlerters", {}).data; -// return ( -// { -// const id = row.original.id; -// return ( -// -// -// -// -// ); -// }, -// }, -// { header: "Tags", accessorFn: ({ tags }) => tags.join(", ") }, -// { -// header: "Created", -// accessorFn: ({ created_at }) => -// fmt_date_with_minutes(new Date(created_at)), -// }, -// ]} -// /> -// ); -// }, -// New: () => , -// Dashboard: ProcedureDashboard, -// }; diff --git a/frontend/src/components/resources/repo/config.tsx b/frontend/src/components/resources/repo/config.tsx new file mode 100644 index 000000000..13fea1016 --- /dev/null +++ b/frontend/src/components/resources/repo/config.tsx @@ -0,0 +1,46 @@ +import { Config } from "@components/config"; +import { AccountSelector, ResourceSelector } from "@components/config/util"; +import { useRead, useWrite } from "@lib/hooks"; +import { Types } from "@monitor/client"; +import { useState } from "react"; + +export const RepoConfig = ({ id }: { id: string }) => { + const config = useRead("GetRepo", { repo: id }).data?.config; + const [update, set] = useState>({}); + const mutate = useWrite("UpdateRepo"); + if (!config) return null; + return ( + mutate} + components={{ + general: { + general: { + server_id: (selected, set) => ( + set({ server_id })} + /> + ), + github_account: (value, set) => ( + set({ github_account })} + /> + ), + repo: true, + branch: true, + on_pull: true, + on_clone: true, + }, + }, + }} + /> + ); +}; diff --git a/frontend/src/components/resources/repo/index.tsx b/frontend/src/components/resources/repo/index.tsx index 2f529daba..21cfeb54d 100644 --- a/frontend/src/components/resources/repo/index.tsx +++ b/frontend/src/components/resources/repo/index.tsx @@ -1,15 +1,12 @@ -import { Config } from "@components/config"; -import { AccountSelector, ResourceSelector } from "@components/config/util"; import { TagsWithBadge } from "@components/tags"; -import { useRead, useWrite } from "@lib/hooks"; -import { Types } from "@monitor/client"; +import { useRead } from "@lib/hooks"; import { Icon } from "@radix-ui/react-select"; import { RequiredResourceComponents } from "@types"; import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card"; import { DataTable } from "@ui/data-table"; import { GitBranch } from "lucide-react"; -import { useState } from "react"; import { Link } from "react-router-dom"; +import { RepoConfig } from "./config"; const useRepo = (id?: string) => useRead("ListRepos", {}).data?.find((d) => d.id === id); @@ -38,47 +35,9 @@ export const RepoComponents: RequiredResourceComponents = { Description: ({ id }) => <>{id}, Info: ({ id }) => <>{id}, Icon: () => , + Status: () => <>, Page: { - Config: ({ id }) => { - const config = useRead("GetRepo", { repo: id }).data?.config; - const [update, set] = useState>({}); - const mutate = useWrite("UpdateRepo"); - if (!config) return null; - return ( - mutate} - components={{ - general: { - general: { - server_id: (selected, set) => ( - set({ server_id })} - /> - ), - github_account: (value, set) => ( - set({ github_account })} - /> - ), - repo: true, - branch: true, - on_pull: true, - on_clone: true, - }, - }, - }} - /> - ); - }, + Config: RepoConfig, }, Table: () => { const alerters = useRead("ListAlerters", {}).data; diff --git a/frontend/src/components/resources/server/config.tsx b/frontend/src/components/resources/server/config.tsx new file mode 100644 index 000000000..272129c32 --- /dev/null +++ b/frontend/src/components/resources/server/config.tsx @@ -0,0 +1,50 @@ +import { Config } from "@components/config"; +import { useInvalidate, useRead, useWrite } from "@lib/hooks"; +import { Types } from "@monitor/client"; +import { useState } from "react"; + +export const ServerConfig = ({ id }: { id: string }) => { + const invalidate = useInvalidate(); + const config = useRead("GetServer", { server: id }).data?.config; + const [update, set] = useState>({}); + const { mutate } = useWrite("UpdateServer", { + onSuccess: () => { + // In case of disabling to resolve unreachable alert + invalidate(["ListAlerts"]); + }, + }); + if (!config) return null; + + return ( + mutate({ id, config: update })} + components={{ + general: { + general: { + address: true, + region: true, + enabled: true, + auto_prune: true, + }, + }, + warnings: { + cpu: { + cpu_warning: true, + cpu_critical: true, + }, + memory: { + mem_warning: true, + mem_critical: true, + }, + disk: { + disk_warning: true, + disk_critical: true, + }, + }, + }} + /> + ); +}; diff --git a/frontend/src/components/resources/server/index.tsx b/frontend/src/components/resources/server/index.tsx index 5e05d0a60..3063ce106 100644 --- a/frontend/src/components/resources/server/index.tsx +++ b/frontend/src/components/resources/server/index.tsx @@ -1,4 +1,4 @@ -import { useInvalidate, useRead, useWrite } from "@lib/hooks"; +import { useRead, useWrite } from "@lib/hooks"; import { cn } from "@lib/utils"; import { Types } from "@monitor/client"; import { RequiredResourceComponents } from "@types"; @@ -12,7 +12,6 @@ import { Rocket, } from "lucide-react"; import { ServerStats } from "./stats"; -import { Config } from "@components/config"; import { useState } from "react"; import { NewResource, Section } from "@components/layouts"; import { Input } from "@ui/input"; @@ -23,6 +22,12 @@ import { TagsWithBadge, useTagsFilter } from "@components/tags"; import { DeleteServer, RenameServer } from "./actions"; import { ServersChart } from "@components/dashboard/servers-chart"; import { DeploymentTable } from "../deployment"; +import { + fill_color_class_by_intention, + server_status_intention, + text_color_class_by_intention, +} from "@lib/color"; +import { ServerConfig } from "./config"; export const useServer = (id?: string) => useRead("ListServers", {}).data?.find((d) => d.id === id); @@ -76,57 +81,12 @@ export const ServerInfo = ({ export const ServerIconComponent = ({ id }: { id?: string }) => { const status = useServer(id)?.info.status; - - const color = () => { - if (status === Types.ServerStatus.Ok) return "fill-green-500"; - if (status === Types.ServerStatus.NotOk) return "fill-red-500"; - if (status === Types.ServerStatus.Disabled) return "fill-blue-500"; - }; - return ; -}; - -const ServerConfig = ({ id }: { id: string }) => { - const invalidate = useInvalidate(); - const config = useRead("GetServer", { server: id }).data?.config; - const [update, set] = useState>({}); - const { mutate } = useWrite("UpdateServer", { - onSuccess: () => { - // In case of disabling to resolve unreachable alert - invalidate(["ListAlerts"]); - }, - }); - if (!config) return null; - return ( - mutate({ id, config: update })} - components={{ - general: { - general: { - address: true, - region: true, - enabled: true, - auto_prune: true, - }, - }, - warnings: { - cpu: { - cpu_warning: true, - cpu_critical: true, - }, - memory: { - mem_warning: true, - mem_critical: true, - }, - disk: { - disk_warning: true, - disk_critical: true, - }, - }, - }} + ); }; @@ -229,12 +189,9 @@ export const ServerComponents: RequiredResourceComponents = { Icon: ServerIconComponent, Status: ({ id }) => { const status = useServer(id)?.info.status; - const stateClass = - status === Types.ServerStatus.Ok - ? "text-green-500" - : status === Types.ServerStatus.NotOk - ? "text-red-500" - : "text-blue-500"; + const stateClass = text_color_class_by_intention( + server_status_intention(status) + ); return (
{status === Types.ServerStatus.NotOk ? "Not Ok" : status} @@ -253,7 +210,7 @@ export const ServerComponents: RequiredResourceComponents = { ); }, - Config: ({ id }) => , + Config: ServerConfig, Danger: ({ id }) => (
}> diff --git a/frontend/src/lib/color.ts b/frontend/src/lib/color.ts new file mode 100644 index 000000000..df38220da --- /dev/null +++ b/frontend/src/lib/color.ts @@ -0,0 +1,80 @@ +import { Types } from "@monitor/client"; + +export type ColorIntention = + | "Good" + | "Neutral" + | "Warning" + | "Critical" + | "Unknown"; + +export const hex_color_by_intention = (intention: ColorIntention) => { + switch (intention) { + case "Good": + return "#22C55E"; + case "Neutral": + return "#3B82F6"; + case "Warning": + return "#F97316"; + case "Critical": + return "#EF0044"; + case "Unknown": + return "#A855F7"; + } +}; + +export const color_class_by_intention = (intention: ColorIntention) => { + switch (intention) { + case "Good": + return "green-500"; + case "Neutral": + return "blue-500"; + case "Warning": + return "orange-500"; + case "Critical": + return "red-500"; + case "Unknown": + return "purple-500"; + } +}; + +export const text_color_class_by_intention = (intention: ColorIntention) => { + return `text-${color_class_by_intention(intention)}`; +}; + +export const fill_color_class_by_intention = (intention: ColorIntention) => { + return `fill-${color_class_by_intention(intention)}`; +}; + +export const stroke_color_class_by_intention = (intention: ColorIntention) => { + return `stroke-${color_class_by_intention(intention)}`; +}; + +export const server_status_intention: ( + status?: Types.ServerStatus +) => ColorIntention = (status) => { + switch (status) { + case Types.ServerStatus.Ok: + return "Good"; + case Types.ServerStatus.NotOk: + return "Critical"; + case Types.ServerStatus.Disabled: + return "Neutral"; + default: + return "Unknown"; + } +}; + +export const deployment_state_intention: ( + state?: Types.DockerContainerState +) => ColorIntention = (state) => { + switch (state) { + case Types.DockerContainerState.Running: + return "Good"; + case Types.DockerContainerState.NotDeployed: + return "Neutral"; + case Types.DockerContainerState.Unknown || undefined: + return "Unknown"; + default: + return "Critical"; + } +}; diff --git a/frontend/src/lib/formatting.ts b/frontend/src/lib/formatting.ts new file mode 100644 index 000000000..cc54e1017 --- /dev/null +++ b/frontend/src/lib/formatting.ts @@ -0,0 +1,39 @@ +import { Types } from "@monitor/client"; + +export const fmt_date = (d: Date) => { + return `${d.getDate()}/${ + d.getMonth() + 1 + } @ ${d.getHours()}:${d.getMinutes()}`; +}; + +export const fmt_date_with_minutes = (d: Date) => { + // return `${d.toLocaleDateString()} ${d.toLocaleTimeString()}`; + return d.toLocaleString(); +}; + +export const fmt_version = (version: Types.Version | undefined) => { + if (!version) return "..."; + const { major, minor, patch } = version; + if (major === 0 && minor === 0 && patch === 0) return "latest"; + return `v${major}.${minor}.${patch}`; +}; + +export const fmt_duration = (start_ts: number, end_ts: number) => { + const start = new Date(start_ts); + const end = new Date(end_ts); + const durr = end.getTime() - start.getTime(); + const seconds = durr / 1000; + const minutes = Math.floor(seconds / 60); + const remaining_seconds = seconds % 60; + return `${ + minutes > 0 ? `${minutes} minute${minutes > 1 ? "s" : ""} ` : "" + }${remaining_seconds.toFixed(minutes > 0 ? 0 : 1)} seconds`; +}; + +/// list_all_items => List All Items +export function snake_case_to_upper_space_case(snake: string) { + return snake + .split("_") + .map((item) => item[0].toUpperCase() + item.slice(1)) + .join(" "); +} \ No newline at end of file diff --git a/frontend/src/lib/socket.tsx b/frontend/src/lib/socket.tsx index 4e4cddc6a..2aaedc53f 100644 --- a/frontend/src/lib/socket.tsx +++ b/frontend/src/lib/socket.tsx @@ -5,11 +5,11 @@ import { toast } from "@ui/use-toast"; import { atom, useAtom } from "jotai"; import { Circle } from "lucide-react"; import { ReactNode, useCallback, useEffect } from "react"; -import rws from "reconnecting-websocket"; +import Rws from "reconnecting-websocket"; import { cn } from "@lib/utils"; import { AUTH_TOKEN_STORAGE_KEY } from "@main"; -const rws_atom = atom(null); +const rws_atom = atom(null); const useWebsocket = () => useAtom(rws_atom); const on_message = ( @@ -92,7 +92,7 @@ const on_message = ( } }; -const on_open = (ws: rws | null) => { +const on_open = (ws: Rws | null) => { const jwt = localStorage.getItem(AUTH_TOKEN_STORAGE_KEY); if (!ws || !jwt) return; const msg: Types.WsLoginMessage = { type: "Jwt", params: { jwt } }; @@ -116,7 +116,7 @@ export const WebsocketProvider = ({ ); useEffect(() => { - if (!ws) set(new rws(url)); + if (!ws) set(new Rws(url)); return () => { ws?.close(); }; diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index 373df58a5..c4250e4ba 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -7,7 +7,7 @@ export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } -export const keys = (o: T): (keyof T)[] => +export const object_keys = (o: T): (keyof T)[] => Object.keys(o) as (keyof T)[]; export const RESOURCE_TARGETS: UsableResource[] = [ @@ -20,36 +20,6 @@ export const RESOURCE_TARGETS: UsableResource[] = [ "Alerter", ]; -export const fmt_date = (d: Date) => { - return `${d.getDate()}/${ - d.getMonth() + 1 - } @ ${d.getHours()}:${d.getMinutes()}`; -}; - -export const fmt_date_with_minutes = (d: Date) => { - // return `${d.toLocaleDateString()} ${d.toLocaleTimeString()}`; - return d.toLocaleString(); -}; - -export const fmt_version = (version: Types.Version | undefined) => { - if (!version) return "..."; - const { major, minor, patch } = version; - if (major === 0 && minor === 0 && patch === 0) return "latest"; - return `v${major}.${minor}.${patch}`; -}; - -export const fmt_duration = (start_ts: number, end_ts: number) => { - const start = new Date(start_ts); - const end = new Date(end_ts); - const durr = end.getTime() - start.getTime(); - const seconds = durr / 1000; - const minutes = Math.floor(seconds / 60); - const remaining_seconds = seconds % 60; - return `${ - minutes > 0 ? `${minutes} minute${minutes > 1 ? "s" : ""} ` : "" - }${remaining_seconds.toFixed(minutes > 0 ? 0 : 1)} seconds`; -}; - export function env_to_text(envVars: Types.EnvironmentVar[] | undefined) { return envVars?.reduce( (prev, { variable, value }) => @@ -58,6 +28,17 @@ export function env_to_text(envVars: Types.EnvironmentVar[] | undefined) { ); } +export function text_to_env(env: string): Types.EnvironmentVar[] { + return env + .split("\n") + .filter((line) => keep_line(line)) + .map((entry) => { + const [first, ...rest] = entry.replaceAll('"', "").split("="); + return [first, rest.join("=")]; + }) + .map(([variable, value]) => ({ variable, value })); +} + function keep_line(line: string) { if (line.length === 0) return false; let firstIndex = -1; @@ -71,22 +52,3 @@ function keep_line(line: string) { if (line[firstIndex] === "#") return false; return true; } - -export function text_to_env(env: string): Types.EnvironmentVar[] { - return env - .split("\n") - .filter((line) => keep_line(line)) - .map((entry) => { - const [first, ...rest] = entry.replaceAll('"', "").split("="); - return [first, rest.join("=")]; - }) - .map(([variable, value]) => ({ variable, value })); -} - -/// list_all_items => List All Items -export function snake_case_to_upper_space_case(snake: string) { - return snake - .split("_") - .map((item) => item[0].toUpperCase() + item.slice(1)) - .join(" "); -} diff --git a/frontend/src/pages/users.tsx b/frontend/src/pages/users.tsx index a4932f835..bcc942bb8 100644 --- a/frontend/src/pages/users.tsx +++ b/frontend/src/pages/users.tsx @@ -1,5 +1,6 @@ import { Page, Section } from "@components/layouts"; import { ConfirmButton, ResourceLink } from "@components/util"; +import { text_color_class_by_intention } from "@lib/color"; import { useInvalidate, useRead, useWrite } from "@lib/hooks"; import { Types } from "@monitor/client"; import { UsableResource } from "@types"; @@ -37,8 +38,8 @@ export const UsersPage = () => { header: "Enabled", cell: ({ row }) => { const enabledClass = row.original.enabled - ? "text-green-500" - : "text-red-500"; + ? text_color_class_by_intention("Good") + : text_color_class_by_intention("Critical"); return (
{row.original.enabled ? "Enabled" : "Disabled"}