forked from github-starred/komodo
standardize coloring
This commit is contained in:
@@ -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
|
||||
)}
|
||||
>
|
||||
<div className="capitalize"> {snake_case_to_upper_space_case(label)} </div>
|
||||
<div>{snake_case_to_upper_space_case(label)}</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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 (
|
||||
<Link to="/deployments" className="w-full">
|
||||
<Card >
|
||||
<Card>
|
||||
<CardHeader className="justify-between">
|
||||
<div>
|
||||
<CardTitle>Deployments</CardTitle>
|
||||
@@ -26,25 +31,45 @@ export const DeploymentsChart = () => {
|
||||
<CardContent className="flex h-[200px] items-center justify-between">
|
||||
<div className="flex flex-col gap-2 text-muted-foreground w-full">
|
||||
<CardDescription>
|
||||
<span className="text-green-500 font-bold">
|
||||
<span
|
||||
className={cn(
|
||||
text_color_class_by_intention("Good"),
|
||||
"font-bold"
|
||||
)}
|
||||
>
|
||||
{summary?.running}{" "}
|
||||
</span>
|
||||
Running
|
||||
</CardDescription>
|
||||
<CardDescription>
|
||||
<span className="text-red-500 font-bold">
|
||||
<span
|
||||
className={cn(
|
||||
text_color_class_by_intention("Critical"),
|
||||
"font-bold"
|
||||
)}
|
||||
>
|
||||
{summary?.stopped}{" "}
|
||||
</span>
|
||||
Stopped
|
||||
</CardDescription>
|
||||
<CardDescription>
|
||||
<span className="text-blue-500 font-bold">
|
||||
<span
|
||||
className={cn(
|
||||
text_color_class_by_intention("Neutral"),
|
||||
"font-bold"
|
||||
)}
|
||||
>
|
||||
{summary?.not_deployed}{" "}
|
||||
</span>
|
||||
Not Deployed
|
||||
</CardDescription>
|
||||
<CardDescription>
|
||||
<span className="text-purple-500 font-bold">
|
||||
<span
|
||||
className={cn(
|
||||
text_color_class_by_intention("Unknown"),
|
||||
"font-bold"
|
||||
)}
|
||||
>
|
||||
{summary?.unknown}{" "}
|
||||
</span>
|
||||
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",
|
||||
|
||||
@@ -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 = () => {
|
||||
<CardContent className="flex h-[200px] items-center justify-between">
|
||||
<div className="flex flex-col gap-2 text-muted-foreground w-full">
|
||||
<CardDescription>
|
||||
<span className="text-green-500 font-bold">{data?.healthy} </span>
|
||||
<span
|
||||
className={cn(
|
||||
text_color_class_by_intention("Good"),
|
||||
"font-bold"
|
||||
)}
|
||||
>
|
||||
{data?.healthy}{" "}
|
||||
</span>
|
||||
Healthy
|
||||
</CardDescription>
|
||||
<CardDescription>
|
||||
<span className="text-red-500 font-bold">{data?.unhealthy} </span>
|
||||
<span
|
||||
className={cn(
|
||||
text_color_class_by_intention("Critical"),
|
||||
"font-bold"
|
||||
)}
|
||||
>
|
||||
{data?.unhealthy}{" "}
|
||||
</span>
|
||||
Unhealthy
|
||||
</CardDescription>
|
||||
<CardDescription>
|
||||
<span className="text-blue-500 font-bold">{data?.disabled} </span>
|
||||
<span
|
||||
className={cn(
|
||||
text_color_class_by_intention("Neutral"),
|
||||
"font-bold"
|
||||
)}
|
||||
>
|
||||
{data?.disabled}{" "}
|
||||
</span>
|
||||
Disabled
|
||||
</CardDescription>
|
||||
</div>
|
||||
@@ -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",
|
||||
|
||||
@@ -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<string>();
|
||||
// const [permission, setPermission] = useState<Types.PermissionLevel>();
|
||||
// const users = useRead("GetUsers", {}).data?.filter((u) => !u.admin);
|
||||
// const { mutate, isPending } = useWrite("UpdateUserPermissionsOnTarget");
|
||||
|
||||
// return (
|
||||
// <Dialog open={open} onOpenChange={set}>
|
||||
// <DialogTrigger asChild>
|
||||
// <Button className="gap-2">
|
||||
// Add Permission
|
||||
// <PlusCircle className="w-4 h-4" />
|
||||
// </Button>
|
||||
// </DialogTrigger>
|
||||
// <DialogContent>
|
||||
// <DialogHeader>
|
||||
// <DialogTitle>Add User</DialogTitle>
|
||||
// </DialogHeader>
|
||||
// <div className="grid gap-4 my-4">
|
||||
// <div className="flex items-center justify-between">
|
||||
// User
|
||||
// <Select value={user_id} onValueChange={setUserId}>
|
||||
// <SelectTrigger className="w-48">
|
||||
// <SelectValue placeholder="Select User" />
|
||||
// </SelectTrigger>
|
||||
// <SelectContent className="w-48">
|
||||
// <SelectGroup>
|
||||
// {users?.map((user) => (
|
||||
// <SelectItem key={user._id?.$oid} value={user._id!.$oid}>
|
||||
// {user.username}
|
||||
// </SelectItem>
|
||||
// ))}
|
||||
// </SelectGroup>
|
||||
// </SelectContent>
|
||||
// </Select>
|
||||
// </div>
|
||||
// <div className="flex items-center justify-between">
|
||||
// Permissions Level
|
||||
// <Select
|
||||
// value={permission}
|
||||
// onValueChange={(lv: any) =>
|
||||
// setPermission(lv as Types.PermissionLevel)
|
||||
// }
|
||||
// >
|
||||
// <SelectTrigger className="w-48">
|
||||
// <SelectValue placeholder="Select Permission Level" />
|
||||
// </SelectTrigger>
|
||||
// <SelectContent className="w-48">
|
||||
// <SelectGroup>
|
||||
// <SelectItem value={Types.PermissionLevel.Read}>
|
||||
// Read
|
||||
// </SelectItem>
|
||||
// <SelectItem value={Types.PermissionLevel.Execute}>
|
||||
// Execute
|
||||
// </SelectItem>
|
||||
// <SelectItem value={Types.PermissionLevel.Write}>
|
||||
// Write
|
||||
// </SelectItem>
|
||||
// </SelectGroup>
|
||||
// </SelectContent>
|
||||
// </Select>
|
||||
// </div>
|
||||
// </div>
|
||||
// <DialogFooter className="flex justify-end">
|
||||
// <Button
|
||||
// className="gap-2"
|
||||
// disabled={isPending}
|
||||
// onClick={() =>
|
||||
// permission &&
|
||||
// user_id &&
|
||||
// mutate({ permission, user_id, target: { id, type } })
|
||||
// }
|
||||
// >
|
||||
// Add Permissions
|
||||
// {isPending ? (
|
||||
// <Loader2 className="w-4 h-4 animate-spin" />
|
||||
// ) : (
|
||||
// <PlusCircle className="w-4 h-4" />
|
||||
// )}
|
||||
// </Button>
|
||||
// </DialogFooter>
|
||||
// </DialogContent>
|
||||
// </Dialog>
|
||||
// );
|
||||
// };
|
||||
|
||||
// // 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 (
|
||||
// // <Section
|
||||
// // title="Permissions"
|
||||
// // icon={<Lock className="w-4 h-4" />}
|
||||
// // actions={<NewPermission id={id} type={type} />}
|
||||
// // >
|
||||
// // <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
// // {display.map((user_id) => (
|
||||
// // <Card key={user_id}>
|
||||
// // <CardHeader className="justify-between">
|
||||
// // <CardTitle className="flex items-center gap-2">
|
||||
// // <User className="w-4 h-4" />
|
||||
// // <Username user_id={user_id} />
|
||||
// // </CardTitle>
|
||||
// // <Select
|
||||
// // value={permissions?.[user_id]}
|
||||
// // onValueChange={(p: any) =>
|
||||
// // update({
|
||||
// // permission: p as Types.PermissionLevel,
|
||||
// // user_id,
|
||||
// // target: { type, id },
|
||||
// // })
|
||||
// // }
|
||||
// // disabled={isPending}
|
||||
// // >
|
||||
// // <SelectTrigger className="w-32">
|
||||
// // <SelectValue placeholder="Set Permissions" />
|
||||
// // </SelectTrigger>
|
||||
// // <SelectContent className="w-32">
|
||||
// // <SelectGroup>
|
||||
// // <SelectItem value={Types.PermissionLevel.Read}>
|
||||
// // Read
|
||||
// // </SelectItem>
|
||||
// // <SelectItem value={Types.PermissionLevel.Update}>
|
||||
// // Update
|
||||
// // </SelectItem>
|
||||
// // <SelectItem value={Types.PermissionLevel.Execute}>
|
||||
// // Execute
|
||||
// // </SelectItem>
|
||||
// // </SelectGroup>
|
||||
// // </SelectContent>
|
||||
// // </Select>
|
||||
// // </CardHeader>
|
||||
// // </Card>
|
||||
// // ))}
|
||||
// // </div>
|
||||
// // </Section>
|
||||
// // );
|
||||
// // };
|
||||
56
frontend/src/components/resources/alerter/config.tsx
Normal file
56
frontend/src/components/resources/alerter/config.tsx
Normal file
@@ -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 <SlackAlerterConfig id={id} />;
|
||||
if (config?.type === "Custom") return <CustomAlerterConfig id={id} />;
|
||||
};
|
||||
|
||||
const SlackAlerterConfig = ({ id }: { id: string }) => {
|
||||
const config = useRead("GetAlerter", { alerter: id }).data?.config;
|
||||
const [update, set] = useState<Partial<Types.SlackAlerterConfig>>({});
|
||||
const { mutate } = useWrite("UpdateAlerter");
|
||||
if (!config) return null;
|
||||
|
||||
return (
|
||||
<Config
|
||||
config={config.params}
|
||||
update={update}
|
||||
set={set}
|
||||
onSave={() => 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<Partial<Types.CustomAlerterConfig>>({});
|
||||
const { mutate } = useWrite("UpdateAlerter");
|
||||
if (!config) return null;
|
||||
|
||||
return (
|
||||
<Config
|
||||
config={config.params}
|
||||
update={update}
|
||||
set={set}
|
||||
onSave={() => mutate({ id, config: { type: "Custom", params: update } })}
|
||||
components={{
|
||||
general: {
|
||||
general: {
|
||||
url: true,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -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<Partial<Types.SlackAlerterConfig>>({});
|
||||
const { mutate } = useWrite("UpdateAlerter");
|
||||
if (!config) return null;
|
||||
|
||||
return (
|
||||
<Config
|
||||
config={config.params}
|
||||
update={update}
|
||||
set={set}
|
||||
onSave={() => 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<Partial<Types.CustomAlerterConfig>>({});
|
||||
const { mutate } = useWrite("UpdateAlerter");
|
||||
if (!config) return null;
|
||||
|
||||
return (
|
||||
<Config
|
||||
config={config.params}
|
||||
update={update}
|
||||
set={set}
|
||||
onSave={() => 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: () => <AlarmClock className="w-4 h-4" />,
|
||||
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 <SlackAlerterConfig id={id} />;
|
||||
if (config?.type === "Custom") return <CustomAlerterConfig id={id} />;
|
||||
},
|
||||
Config: AlerterConfig,
|
||||
},
|
||||
Actions: () => null,
|
||||
Table: AlerterTable,
|
||||
|
||||
@@ -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 <Hammer className={className} />;
|
||||
};
|
||||
@@ -74,7 +75,7 @@ const BuildTable = () => {
|
||||
},
|
||||
{
|
||||
header: "Repo",
|
||||
accessorKey: "info.repo"
|
||||
accessorKey: "info.repo",
|
||||
},
|
||||
{
|
||||
header: "Version",
|
||||
|
||||
@@ -136,6 +136,7 @@ export const BuilderComponents: RequiredResourceComponents = {
|
||||
</>
|
||||
),
|
||||
Icon: () => <Factory className="w-4 h-4" />,
|
||||
Status: () => <></>,
|
||||
Page: {
|
||||
Config: BuilderConfig,
|
||||
},
|
||||
|
||||
@@ -25,7 +25,6 @@ export const RedeployContainer = ({ id }: DeploymentId) => {
|
||||
return (
|
||||
<ConfirmButton
|
||||
title={deployment?.info.status ? "Redeploy" : "Deploy"}
|
||||
// intent="success"
|
||||
icon={<Rocket className="h-4 w-4" />}
|
||||
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 (
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 = ({
|
||||
<SelectValue placeholder="Select Type" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{keys(Types.RestartMode).map((mode) => (
|
||||
{object_keys(Types.RestartMode).map((mode) => (
|
||||
<SelectItem
|
||||
key={mode}
|
||||
value={Types.RestartMode[mode]}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useRead, useWrite } from "@lib/hooks";
|
||||
import { Types } from "@monitor/client";
|
||||
import { RequiredResourceComponents } from "@types";
|
||||
import { AlertTriangle, HardDrive, Rocket, Server } from "lucide-react";
|
||||
import { cn, snake_case_to_upper_space_case } from "@lib/utils";
|
||||
import { cn } from "@lib/utils";
|
||||
import { useState } from "react";
|
||||
import { NewResource, Section } from "@components/layouts";
|
||||
|
||||
@@ -23,33 +23,27 @@ import { ResourceComponents } from "..";
|
||||
import { TagsWithBadge, useTagsFilter } from "@components/tags";
|
||||
import { DeploymentsChart } from "@components/dashboard/deployments-chart";
|
||||
import { Button } from "@ui/button";
|
||||
import { snake_case_to_upper_space_case } from "@lib/formatting";
|
||||
import {
|
||||
deployment_state_intention,
|
||||
fill_color_class_by_intention,
|
||||
text_color_class_by_intention,
|
||||
} from "@lib/color";
|
||||
|
||||
export const useDeployment = (id?: string) =>
|
||||
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 (
|
||||
<Rocket
|
||||
className={cn("w-4", state && deployment_state_fill_color(state))}
|
||||
className={cn(
|
||||
"w-4",
|
||||
fill_color_class_by_intention(deployment_state_intention(state))
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -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 (
|
||||
<div className={color}>
|
||||
{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 (
|
||||
<div className={deployment_state_text_color(state)}>
|
||||
{snake_case_to_upper_space_case(state)}
|
||||
</div>
|
||||
const color = text_color_class_by_intention(
|
||||
deployment_state_intention(state)
|
||||
);
|
||||
return <div className={color}>{snake_case_to_upper_space_case(state)}</div>;
|
||||
},
|
||||
Actions: ({ id }) => (
|
||||
<div className="flex gap-4">
|
||||
|
||||
@@ -27,6 +27,7 @@ export const ProcedureComponents: RequiredResourceComponents = {
|
||||
Description: ({ id }) => <>{useProcedure(id)?.info.procedure_type}</>,
|
||||
Info: () => <></>,
|
||||
Icon: () => <Route className="w-4" />,
|
||||
Status: () => <></>,
|
||||
Page: {
|
||||
Config: ProcedureConfig,
|
||||
},
|
||||
|
||||
@@ -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<Types.ProcedureConfig["type"]>("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 (
|
||||
// // <NewResource
|
||||
// // type="Procedure"
|
||||
// // onSuccess={() =>
|
||||
// // mutateAsync({
|
||||
// // name,
|
||||
// // config: {
|
||||
// // type,
|
||||
// // data: get_default_data(type),
|
||||
// // } as Types.ProcedureConfig,
|
||||
// // })
|
||||
// // }
|
||||
// // enabled={!!name}
|
||||
// // >
|
||||
// // <div className="grid md:grid-cols-2">
|
||||
// // Procedure Name
|
||||
// // <Input
|
||||
// // placeholder="procedure-name"
|
||||
// // value={name}
|
||||
// // onChange={(e) => setName(e.target.value)}
|
||||
// // />
|
||||
// // </div>
|
||||
// // <div className="grid md:grid-cols-2">
|
||||
// // Procedure Type
|
||||
// // <Select
|
||||
// // value={type}
|
||||
// // onValueChange={(value) => setType(value as typeof type)}
|
||||
// // >
|
||||
// // <SelectTrigger>
|
||||
// // <SelectValue placeholder="Select Type" />
|
||||
// // </SelectTrigger>
|
||||
// // <SelectContent>
|
||||
// // <SelectGroup>
|
||||
// // <SelectItem value="Execution">Execution</SelectItem>
|
||||
// // <SelectItem value="Sequence">Sequence</SelectItem>
|
||||
// // <SelectItem value="Paralell">Paralell</SelectItem>
|
||||
// // </SelectGroup>
|
||||
// // </SelectContent>
|
||||
// // </Select>
|
||||
// // </div>
|
||||
// // </NewResource>
|
||||
// // );
|
||||
// // };
|
||||
|
||||
// // type ExecutionType = Extract<
|
||||
// // Types.ProcedureConfig,
|
||||
// // { type: "Execution" }
|
||||
// // >["data"]["type"];
|
||||
|
||||
// // type ExecutionConfigComponent<
|
||||
// // T extends ExecutionType,
|
||||
// // P = Extract<Execution, { type: T }>["params"]
|
||||
// // > = React.FC<{
|
||||
// // params: P;
|
||||
// // setParams: React.Dispatch<React.SetStateAction<P>>;
|
||||
// // }>;
|
||||
|
||||
// // type ExecutionConfigParams<T extends ExecutionType> = Extract<
|
||||
// // Execution,
|
||||
// // { type: T }
|
||||
// // >["params"];
|
||||
|
||||
// // type ExecutionConfigs = {
|
||||
// // [ExType in ExecutionType]: {
|
||||
// // component: ExecutionConfigComponent<ExType>;
|
||||
// // params: ExecutionConfigParams<ExType>;
|
||||
// // };
|
||||
// // };
|
||||
|
||||
// // const TypeSelector = ({
|
||||
// // type,
|
||||
// // selected,
|
||||
// // onSelect,
|
||||
// // }: {
|
||||
// // type: UsableResource;
|
||||
// // selected: string;
|
||||
// // onSelect: (value: string) => void;
|
||||
// // }) => (
|
||||
// // <div className="flex items-center justify-between">
|
||||
// // {type}
|
||||
// // <ResourceSelector type={type} selected={selected} onSelect={onSelect} />
|
||||
// // </div>
|
||||
// // );
|
||||
|
||||
// // const EXEC_TYPES: ExecutionConfigs = {
|
||||
// // None: {
|
||||
// // params: {},
|
||||
// // component: () => <></>,
|
||||
// // },
|
||||
// // CloneRepo: {
|
||||
// // params: { id: "" },
|
||||
// // component: ({ params, setParams }) => (
|
||||
// // <TypeSelector
|
||||
// // type="Repo"
|
||||
// // selected={params.id}
|
||||
// // onSelect={(id) => setParams((p) => ({ ...p, id }))}
|
||||
// // />
|
||||
// // ),
|
||||
// // },
|
||||
// // Deploy: {
|
||||
// // params: { deployment_id: "" },
|
||||
// // component: ({ params, setParams }) => (
|
||||
// // <TypeSelector
|
||||
// // type="Deployment"
|
||||
// // selected={params.deployment_id}
|
||||
// // onSelect={(id) => setParams((p) => ({ ...p, deployment_id: id }))}
|
||||
// // />
|
||||
// // ),
|
||||
// // },
|
||||
// // PruneDockerContainers: {
|
||||
// // params: { server_id: "" },
|
||||
// // component: ({ params, setParams }) => (
|
||||
// // <TypeSelector
|
||||
// // type="Server"
|
||||
// // selected={params.server_id}
|
||||
// // onSelect={(server_id) => setParams((p) => ({ ...p, server_id }))}
|
||||
// // />
|
||||
// // ),
|
||||
// // },
|
||||
// // PruneDockerImages: {
|
||||
// // params: { server_id: "" },
|
||||
// // component: ({ params, setParams }) => (
|
||||
// // <TypeSelector
|
||||
// // type="Server"
|
||||
// // selected={params.server_id}
|
||||
// // onSelect={(id) => setParams((p) => ({ ...p, id }))}
|
||||
// // />
|
||||
// // ),
|
||||
// // },
|
||||
// // PruneDockerNetworks: {
|
||||
// // params: { server_id: "" },
|
||||
// // component: ({ params, setParams }) => (
|
||||
// // <TypeSelector
|
||||
// // type="Server"
|
||||
// // selected={params.server_id}
|
||||
// // onSelect={(id) => setParams((p) => ({ ...p, id }))}
|
||||
// // />
|
||||
// // ),
|
||||
// // },
|
||||
// // PullRepo: {
|
||||
// // params: { id: "" },
|
||||
// // component: ({ params, setParams }) => (
|
||||
// // <TypeSelector
|
||||
// // type="Repo"
|
||||
// // selected={params.id}
|
||||
// // onSelect={(id) => setParams((p) => ({ ...p, id }))}
|
||||
// // />
|
||||
// // ),
|
||||
// // },
|
||||
// // RemoveContainer: {
|
||||
// // params: { deployment_id: "" },
|
||||
// // component: ({ params, setParams }) => (
|
||||
// // <TypeSelector
|
||||
// // type="Deployment"
|
||||
// // selected={params.deployment_id}
|
||||
// // onSelect={(id) => setParams((p) => ({ ...p, deployment_id: id }))}
|
||||
// // />
|
||||
// // ),
|
||||
// // },
|
||||
// // RunBuild: {
|
||||
// // params: { build_id: "" },
|
||||
// // component: ({ params, setParams }) => (
|
||||
// // <TypeSelector
|
||||
// // type="Build"
|
||||
// // selected={params.build_id}
|
||||
// // onSelect={(build_id) => setParams((p) => ({ ...p, build_id }))}
|
||||
// // />
|
||||
// // ),
|
||||
// // },
|
||||
// // RunProcedure: {
|
||||
// // params: { procedure_id: "" },
|
||||
// // component: ({ params, setParams }) => (
|
||||
// // <TypeSelector
|
||||
// // type="Procedure"
|
||||
// // selected={params.procedure_id}
|
||||
// // onSelect={(id) => setParams((p) => ({ ...p, procedure_id: id }))}
|
||||
// // />
|
||||
// // ),
|
||||
// // },
|
||||
// // StartContainer: {
|
||||
// // params: { deployment_id: "" },
|
||||
// // component: ({ params, setParams }) => (
|
||||
// // <TypeSelector
|
||||
// // type="Deployment"
|
||||
// // selected={params.deployment_id}
|
||||
// // onSelect={(id) => setParams((p) => ({ ...p, deployment_id: id }))}
|
||||
// // />
|
||||
// // ),
|
||||
// // },
|
||||
// // StopAllContainers: {
|
||||
// // params: { server_id: "" },
|
||||
// // component: ({ params, setParams }) => (
|
||||
// // <TypeSelector
|
||||
// // type="Server"
|
||||
// // selected={params.server_id}
|
||||
// // onSelect={(id) => setParams((p) => ({ ...p, server_id: id }))}
|
||||
// // />
|
||||
// // ),
|
||||
// // },
|
||||
// // StopContainer: {
|
||||
// // params: { deployment_id: "" },
|
||||
// // component: ({ params, setParams }) => (
|
||||
// // <TypeSelector
|
||||
// // type="Deployment"
|
||||
// // selected={params.deployment_id}
|
||||
// // onSelect={(id) => setParams((p) => ({ ...p, deployment_id: id }))}
|
||||
// // />
|
||||
// // ),
|
||||
// // },
|
||||
// // };
|
||||
|
||||
// const UpdateProcedure = ({
|
||||
// id,
|
||||
// procedure,
|
||||
// }: {
|
||||
// id: string;
|
||||
// procedure: Types.ProcedureConfig;
|
||||
// }) => {
|
||||
// const { mutate } = useWrite("UpdateProcedure");
|
||||
|
||||
// return (
|
||||
// <Button onClick={() => mutate({ id, config: procedure })}>
|
||||
// <Save className="w-4" />
|
||||
// </Button>
|
||||
// );
|
||||
// };
|
||||
|
||||
// // 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<ExecutionType>(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 (
|
||||
// // <div className="p-4 border rounded-md flex flex-col gap-4">
|
||||
// // <div className="flex items-center justify-between">
|
||||
// // {procedure.name}
|
||||
// // <UpdateProcedure
|
||||
// // id={id}
|
||||
// // procedure={{ type: "Execution", data: { type, params } as Execution }}
|
||||
// // />
|
||||
// // </div>
|
||||
// // <div className="flex items-center justify-between">
|
||||
// // Execution Type
|
||||
// // <Select
|
||||
// // value={type}
|
||||
// // onValueChange={(value) => setType(value as typeof type)}
|
||||
// // >
|
||||
// // <SelectTrigger className="w-72">
|
||||
// // <SelectValue placeholder="Select Type" />
|
||||
// // </SelectTrigger>
|
||||
// // <SelectContent className="w-72">
|
||||
// // <SelectGroup>
|
||||
// // {Object.keys(EXEC_TYPES).map((type) => (
|
||||
// // <SelectItem
|
||||
// // value={type}
|
||||
// // className="whitespace-nowrap"
|
||||
// // key={type}
|
||||
// // >
|
||||
// // {type.match(/[A-Z][a-z]+/g)?.join(" ")}
|
||||
// // </SelectItem>
|
||||
// // ))}
|
||||
// // </SelectGroup>
|
||||
// // </SelectContent>
|
||||
// // </Select>
|
||||
// // </div>
|
||||
// // <div className="pt-2 border-t">
|
||||
// // {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
|
||||
// // <Component params={params as any} setParams={setParams as any} />
|
||||
// // </div>
|
||||
// // <div className="pt-2 border-t">
|
||||
// // <pre>{JSON.stringify(procedure?.config, null, 2)}</pre>
|
||||
// // </div>
|
||||
// // </div>
|
||||
// // );
|
||||
// // };
|
||||
|
||||
// // const SequenceConfig = ({ id }: { id: string }) => {
|
||||
// // const procedure = useRead("GetProcedure", { id }).data;
|
||||
// // if (procedure?.config.type !== "Sequence") return null;
|
||||
|
||||
// // return (
|
||||
// // <div className="p-4 border rounded-md flex flex-col gap-4">
|
||||
// // <div className="flex items-center justify-between">
|
||||
// // {procedure?.name}
|
||||
// // <NewProcedure parent={procedure} />
|
||||
// // </div>
|
||||
// // <pre>{JSON.stringify(procedure?.config, null, 2)}</pre>
|
||||
// // <div>
|
||||
// // {procedure.config.data.map((p) => (
|
||||
// // <ProcedureConfig id={p.id} key={p.id} />
|
||||
// // ))}
|
||||
// // </div>
|
||||
// // </div>
|
||||
// // );
|
||||
// // };
|
||||
|
||||
// // export const ProcedureConfig = ({ id }: { id: string }) => {
|
||||
// // const procedure = useRead("GetProcedure", { id }).data;
|
||||
// // if (procedure?.config.type === "Sequence") return <SequenceConfig id={id} />;
|
||||
// // if (procedure?.config.type === "Execution")
|
||||
// // return <ExecutionConfig id={id} />;
|
||||
// // };
|
||||
|
||||
// export const ProcedureDashboard = () => {
|
||||
// const procedure_count = useRead("ListProcedures", {}).data?.length;
|
||||
// return (
|
||||
// <Link to="/procedures/" className="w-full">
|
||||
// <Card>
|
||||
// <CardHeader className="justify-between">
|
||||
// <div>
|
||||
// <CardTitle>Procedures</CardTitle>
|
||||
// <CardDescription>{procedure_count} Total</CardDescription>
|
||||
// </div>
|
||||
// <Route className="w-4 h-4" />
|
||||
// </CardHeader>
|
||||
// </Card>
|
||||
// </Link>
|
||||
// );
|
||||
// };
|
||||
|
||||
// export const ProcedureComponents: RequiredResourceComponents = {
|
||||
// Name: ({ id }) => <>{useProcedure(id)?.name}</>,
|
||||
// Description: ({ id }) => <>{useProcedure(id)?.info.procedure_type}</>,
|
||||
// Info: ({ id }) => <>{id}</>,
|
||||
// Icon: () => <Route className="w-4" />,
|
||||
// Page: {
|
||||
// // Config: ({ id }) => <ProcedureConfig id={id} />,
|
||||
// },
|
||||
// Actions: ({ id }) => {
|
||||
// const running = useRead("GetProcedureActionState", { procedure: id }).data?.running;
|
||||
// const { mutate, isPending } = useExecute("RunProcedure");
|
||||
// return (
|
||||
// <ConfirmButton
|
||||
// title={running ? "Building" : "Run"}
|
||||
// icon={
|
||||
// running ? (
|
||||
// <Loader2 className="w-4 h-4 animate-spin" />
|
||||
// ) : (
|
||||
// <Route className="h-4 w-4" />
|
||||
// )
|
||||
// }
|
||||
// onClick={() => mutate({ procedure: id })}
|
||||
// disabled={running || isPending}
|
||||
// />
|
||||
// );
|
||||
// },
|
||||
// Table: () => {
|
||||
// const alerters = useRead("ListAlerters", {}).data;
|
||||
// return (
|
||||
// <DataTable
|
||||
// data={alerters ?? []}
|
||||
// columns={[
|
||||
// {
|
||||
// accessorKey: "id",
|
||||
// header: "Name",
|
||||
// cell: ({ row }) => {
|
||||
// const id = row.original.id;
|
||||
// return (
|
||||
// <Link
|
||||
// to={`/procedures/${id}`}
|
||||
// className="flex items-center gap-2"
|
||||
// >
|
||||
// <Icon id={id} />
|
||||
// <Name id={id} />
|
||||
// </Link>
|
||||
// );
|
||||
// },
|
||||
// },
|
||||
// { header: "Tags", accessorFn: ({ tags }) => tags.join(", ") },
|
||||
// {
|
||||
// header: "Created",
|
||||
// accessorFn: ({ created_at }) =>
|
||||
// fmt_date_with_minutes(new Date(created_at)),
|
||||
// },
|
||||
// ]}
|
||||
// />
|
||||
// );
|
||||
// },
|
||||
// New: () => <NewProcedure />,
|
||||
// Dashboard: ProcedureDashboard,
|
||||
// };
|
||||
46
frontend/src/components/resources/repo/config.tsx
Normal file
46
frontend/src/components/resources/repo/config.tsx
Normal file
@@ -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<Partial<Types.RepoConfig>>({});
|
||||
const mutate = useWrite("UpdateRepo");
|
||||
if (!config) return null;
|
||||
return (
|
||||
<Config
|
||||
config={config}
|
||||
update={update}
|
||||
set={set}
|
||||
onSave={() => mutate}
|
||||
components={{
|
||||
general: {
|
||||
general: {
|
||||
server_id: (selected, set) => (
|
||||
<ResourceSelector
|
||||
type="Server"
|
||||
selected={selected}
|
||||
onSelect={(server_id) => set({ server_id })}
|
||||
/>
|
||||
),
|
||||
github_account: (value, set) => (
|
||||
<AccountSelector
|
||||
type="Server"
|
||||
account_type="github"
|
||||
id={update.server_id ?? config.server_id}
|
||||
selected={value}
|
||||
onSelect={(github_account) => set({ github_account })}
|
||||
/>
|
||||
),
|
||||
repo: true,
|
||||
branch: true,
|
||||
on_pull: true,
|
||||
on_clone: true,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -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: () => <GitBranch className="w-4 h-4" />,
|
||||
Status: () => <></>,
|
||||
Page: {
|
||||
Config: ({ id }) => {
|
||||
const config = useRead("GetRepo", { repo: id }).data?.config;
|
||||
const [update, set] = useState<Partial<Types.RepoConfig>>({});
|
||||
const mutate = useWrite("UpdateRepo");
|
||||
if (!config) return null;
|
||||
return (
|
||||
<Config
|
||||
config={config}
|
||||
update={update}
|
||||
set={set}
|
||||
onSave={() => mutate}
|
||||
components={{
|
||||
general: {
|
||||
general: {
|
||||
server_id: (selected, set) => (
|
||||
<ResourceSelector
|
||||
type="Server"
|
||||
selected={selected}
|
||||
onSelect={(server_id) => set({ server_id })}
|
||||
/>
|
||||
),
|
||||
github_account: (value, set) => (
|
||||
<AccountSelector
|
||||
type="Server"
|
||||
account_type="github"
|
||||
id={update.server_id ?? config.server_id}
|
||||
selected={value}
|
||||
onSelect={(github_account) => set({ github_account })}
|
||||
/>
|
||||
),
|
||||
repo: true,
|
||||
branch: true,
|
||||
on_pull: true,
|
||||
on_clone: true,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
Config: RepoConfig,
|
||||
},
|
||||
Table: () => {
|
||||
const alerters = useRead("ListAlerters", {}).data;
|
||||
|
||||
50
frontend/src/components/resources/server/config.tsx
Normal file
50
frontend/src/components/resources/server/config.tsx
Normal file
@@ -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<Partial<Types.ServerConfig>>({});
|
||||
const { mutate } = useWrite("UpdateServer", {
|
||||
onSuccess: () => {
|
||||
// In case of disabling to resolve unreachable alert
|
||||
invalidate(["ListAlerts"]);
|
||||
},
|
||||
});
|
||||
if (!config) return null;
|
||||
|
||||
return (
|
||||
<Config
|
||||
config={config}
|
||||
update={update}
|
||||
set={set}
|
||||
onSave={() => 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,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -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 <ServerIcon className={cn("w-4 h-4", id && color())} />;
|
||||
};
|
||||
|
||||
const ServerConfig = ({ id }: { id: string }) => {
|
||||
const invalidate = useInvalidate();
|
||||
const config = useRead("GetServer", { server: id }).data?.config;
|
||||
const [update, set] = useState<Partial<Types.ServerConfig>>({});
|
||||
const { mutate } = useWrite("UpdateServer", {
|
||||
onSuccess: () => {
|
||||
// In case of disabling to resolve unreachable alert
|
||||
invalidate(["ListAlerts"]);
|
||||
},
|
||||
});
|
||||
if (!config) return null;
|
||||
|
||||
return (
|
||||
<Config
|
||||
config={config}
|
||||
update={update}
|
||||
set={set}
|
||||
onSave={() => 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,
|
||||
},
|
||||
},
|
||||
}}
|
||||
<ServerIcon
|
||||
className={cn(
|
||||
"w-4 h-4",
|
||||
id && fill_color_class_by_intention(server_status_intention(status))
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -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 (
|
||||
<div className={stateClass}>
|
||||
{status === Types.ServerStatus.NotOk ? "Not Ok" : status}
|
||||
@@ -253,7 +210,7 @@ export const ServerComponents: RequiredResourceComponents = {
|
||||
</Section>
|
||||
);
|
||||
},
|
||||
Config: ({ id }) => <ServerConfig id={id} />,
|
||||
Config: ServerConfig,
|
||||
Danger: ({ id }) => (
|
||||
<Section title="Danger Zone" icon={<AlertTriangle className="w-4 h-4" />}>
|
||||
<RenameServer id={id} />
|
||||
|
||||
80
frontend/src/lib/color.ts
Normal file
80
frontend/src/lib/color.ts
Normal file
@@ -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";
|
||||
}
|
||||
};
|
||||
39
frontend/src/lib/formatting.ts
Normal file
39
frontend/src/lib/formatting.ts
Normal file
@@ -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(" ");
|
||||
}
|
||||
@@ -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<rws | null>(null);
|
||||
const rws_atom = atom<Rws | null>(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();
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
export const keys = <T extends object>(o: T): (keyof T)[] =>
|
||||
export const object_keys = <T extends object>(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(" ");
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<div className={enabledClass}>
|
||||
{row.original.enabled ? "Enabled" : "Disabled"}
|
||||
|
||||
Reference in New Issue
Block a user