forked from github-starred/komodo
standardize coloring
This commit is contained in:
@@ -19,7 +19,7 @@ import {
|
|||||||
SearchX,
|
SearchX,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { ReactNode, useState } from "react";
|
import { ReactNode, useState } from "react";
|
||||||
import { cn, snake_case_to_upper_space_case } from "@lib/utils";
|
import { cn } from "@lib/utils";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -36,6 +36,7 @@ import {
|
|||||||
CommandInput,
|
CommandInput,
|
||||||
CommandItem,
|
CommandItem,
|
||||||
} from "@ui/command";
|
} from "@ui/command";
|
||||||
|
import { snake_case_to_upper_space_case } from "@lib/formatting";
|
||||||
|
|
||||||
export const ConfigItem = ({
|
export const ConfigItem = ({
|
||||||
label,
|
label,
|
||||||
@@ -52,7 +53,7 @@ export const ConfigItem = ({
|
|||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="capitalize"> {snake_case_to_upper_space_case(label)} </div>
|
<div>{snake_case_to_upper_space_case(label)}</div>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,13 +9,18 @@ import { PieChart } from "react-minimal-pie-chart";
|
|||||||
import { useRead } from "@lib/hooks";
|
import { useRead } from "@lib/hooks";
|
||||||
import { Rocket } from "lucide-react";
|
import { Rocket } from "lucide-react";
|
||||||
import { Link } from "react-router-dom";
|
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 = () => {
|
export const DeploymentsChart = () => {
|
||||||
const summary = useRead("GetDeploymentsSummary", {}).data;
|
const summary = useRead("GetDeploymentsSummary", {}).data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to="/deployments" className="w-full">
|
<Link to="/deployments" className="w-full">
|
||||||
<Card >
|
<Card>
|
||||||
<CardHeader className="justify-between">
|
<CardHeader className="justify-between">
|
||||||
<div>
|
<div>
|
||||||
<CardTitle>Deployments</CardTitle>
|
<CardTitle>Deployments</CardTitle>
|
||||||
@@ -26,25 +31,45 @@ export const DeploymentsChart = () => {
|
|||||||
<CardContent className="flex h-[200px] items-center justify-between">
|
<CardContent className="flex h-[200px] items-center justify-between">
|
||||||
<div className="flex flex-col gap-2 text-muted-foreground w-full">
|
<div className="flex flex-col gap-2 text-muted-foreground w-full">
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
<span className="text-green-500 font-bold">
|
<span
|
||||||
|
className={cn(
|
||||||
|
text_color_class_by_intention("Good"),
|
||||||
|
"font-bold"
|
||||||
|
)}
|
||||||
|
>
|
||||||
{summary?.running}{" "}
|
{summary?.running}{" "}
|
||||||
</span>
|
</span>
|
||||||
Running
|
Running
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
<span className="text-red-500 font-bold">
|
<span
|
||||||
|
className={cn(
|
||||||
|
text_color_class_by_intention("Critical"),
|
||||||
|
"font-bold"
|
||||||
|
)}
|
||||||
|
>
|
||||||
{summary?.stopped}{" "}
|
{summary?.stopped}{" "}
|
||||||
</span>
|
</span>
|
||||||
Stopped
|
Stopped
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
<span className="text-blue-500 font-bold">
|
<span
|
||||||
|
className={cn(
|
||||||
|
text_color_class_by_intention("Neutral"),
|
||||||
|
"font-bold"
|
||||||
|
)}
|
||||||
|
>
|
||||||
{summary?.not_deployed}{" "}
|
{summary?.not_deployed}{" "}
|
||||||
</span>
|
</span>
|
||||||
Not Deployed
|
Not Deployed
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
<span className="text-purple-500 font-bold">
|
<span
|
||||||
|
className={cn(
|
||||||
|
text_color_class_by_intention("Unknown"),
|
||||||
|
"font-bold"
|
||||||
|
)}
|
||||||
|
>
|
||||||
{summary?.unknown}{" "}
|
{summary?.unknown}{" "}
|
||||||
</span>
|
</span>
|
||||||
Unknown
|
Unknown
|
||||||
@@ -57,25 +82,25 @@ export const DeploymentsChart = () => {
|
|||||||
lineWidth={30}
|
lineWidth={30}
|
||||||
data={[
|
data={[
|
||||||
{
|
{
|
||||||
color: "#22C55E",
|
color: hex_color_by_intention("Good"),
|
||||||
value: summary?.running ?? 0,
|
value: summary?.running ?? 0,
|
||||||
title: "running",
|
title: "running",
|
||||||
key: "running",
|
key: "running",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: "#EF0044",
|
color: hex_color_by_intention("Critical"),
|
||||||
value: summary?.stopped ?? 0,
|
value: summary?.stopped ?? 0,
|
||||||
title: "stopped",
|
title: "stopped",
|
||||||
key: "stopped",
|
key: "stopped",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: "#3B82F6",
|
color: hex_color_by_intention("Neutral"),
|
||||||
value: summary?.not_deployed ?? 0,
|
value: summary?.not_deployed ?? 0,
|
||||||
title: "not-deployed",
|
title: "not-deployed",
|
||||||
key: "not-deployed",
|
key: "not-deployed",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: "purple",
|
color: hex_color_by_intention("Unknown"),
|
||||||
value: summary?.unknown ?? 0,
|
value: summary?.unknown ?? 0,
|
||||||
title: "unknown",
|
title: "unknown",
|
||||||
key: "unknown",
|
key: "unknown",
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ import { PieChart } from "react-minimal-pie-chart";
|
|||||||
import { useRead } from "@lib/hooks";
|
import { useRead } from "@lib/hooks";
|
||||||
import { Server } from "lucide-react";
|
import { Server } from "lucide-react";
|
||||||
import { Link } from "react-router-dom";
|
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 = () => {
|
export const ServersChart = () => {
|
||||||
const { data } = useRead("GetServersSummary", {});
|
const { data } = useRead("GetServersSummary", {});
|
||||||
@@ -25,15 +30,36 @@ export const ServersChart = () => {
|
|||||||
<CardContent className="flex h-[200px] items-center justify-between">
|
<CardContent className="flex h-[200px] items-center justify-between">
|
||||||
<div className="flex flex-col gap-2 text-muted-foreground w-full">
|
<div className="flex flex-col gap-2 text-muted-foreground w-full">
|
||||||
<CardDescription>
|
<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
|
Healthy
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
<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
|
Unhealthy
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
<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
|
Disabled
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
@@ -44,19 +70,19 @@ export const ServersChart = () => {
|
|||||||
lineWidth={30}
|
lineWidth={30}
|
||||||
data={[
|
data={[
|
||||||
{
|
{
|
||||||
color: "#22C55E",
|
color: hex_color_by_intention("Good"),
|
||||||
value: data?.healthy ?? 0,
|
value: data?.healthy ?? 0,
|
||||||
title: "healthy",
|
title: "healthy",
|
||||||
key: "healthy",
|
key: "healthy",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: "#EF0044",
|
color: hex_color_by_intention("Critical"),
|
||||||
value: data?.unhealthy ?? 0,
|
value: data?.unhealthy ?? 0,
|
||||||
title: "unhealthy",
|
title: "unhealthy",
|
||||||
key: "unhealthy",
|
key: "unhealthy",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: "#3B82F6",
|
color: hex_color_by_intention("Neutral"),
|
||||||
value: data?.disabled ?? 0,
|
value: data?.disabled ?? 0,
|
||||||
title: "disabled",
|
title: "disabled",
|
||||||
key: "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 { Input } from "@ui/input";
|
||||||
import { AlarmClock } from "lucide-react";
|
import { AlarmClock } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Config } from "@components/config";
|
|
||||||
import { DataTable } from "@ui/data-table";
|
import { DataTable } from "@ui/data-table";
|
||||||
import { ResourceComponents } from "..";
|
import { ResourceComponents } from "..";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card";
|
import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card";
|
||||||
|
import { AlerterConfig } from "./config";
|
||||||
|
|
||||||
const useAlerter = (id?: string) =>
|
const useAlerter = (id?: string) =>
|
||||||
useRead("ListAlerters", {}).data?.find((d) => d.id === id);
|
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 AlerterTable = () => {
|
||||||
const alerters = useRead("ListAlerters", {}).data;
|
const alerters = useRead("ListAlerters", {}).data;
|
||||||
return (
|
return (
|
||||||
@@ -157,12 +111,9 @@ export const AlerterComponents: RequiredResourceComponents = {
|
|||||||
Icon: () => <AlarmClock className="w-4 h-4" />,
|
Icon: () => <AlarmClock className="w-4 h-4" />,
|
||||||
Description: ({ id }) => <>{useAlerter(id)?.info.alerter_type} alerter</>,
|
Description: ({ id }) => <>{useAlerter(id)?.info.alerter_type} alerter</>,
|
||||||
Info: ({ id }) => <>{id}</>,
|
Info: ({ id }) => <>{id}</>,
|
||||||
|
Status: () => <></>,
|
||||||
Page: {
|
Page: {
|
||||||
Config: ({ id }: { id: string }) => {
|
Config: AlerterConfig,
|
||||||
const config = useRead("GetAlerter", { alerter: id }).data?.config;
|
|
||||||
if (config?.type === "Slack") return <SlackAlerterConfig id={id} />;
|
|
||||||
if (config?.type === "Custom") return <CustomAlerterConfig id={id} />;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Actions: () => null,
|
Actions: () => null,
|
||||||
Table: AlerterTable,
|
Table: AlerterTable,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { NewResource } from "@components/layouts";
|
import { NewResource } from "@components/layouts";
|
||||||
import { ConfirmButton } from "@components/util";
|
import { ConfirmButton } from "@components/util";
|
||||||
import { useExecute, useRead, useWrite } from "@lib/hooks";
|
import { useExecute, useRead, useWrite } from "@lib/hooks";
|
||||||
import { fmt_date_with_minutes, fmt_version } from "@lib/utils";
|
|
||||||
import { RequiredResourceComponents } from "@types";
|
import { RequiredResourceComponents } from "@types";
|
||||||
import { DataTable } from "@ui/data-table";
|
import { DataTable } from "@ui/data-table";
|
||||||
import { Input } from "@ui/input";
|
import { Input } from "@ui/input";
|
||||||
@@ -13,6 +12,8 @@ import { BuildChart } from "@components/dashboard/builds-chart";
|
|||||||
import { TagsWithBadge, useTagsFilter } from "@components/tags";
|
import { TagsWithBadge, useTagsFilter } from "@components/tags";
|
||||||
import { useToast } from "@ui/use-toast";
|
import { useToast } from "@ui/use-toast";
|
||||||
import { BuildConfig } from "./config";
|
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) =>
|
const useBuild = (id?: string) =>
|
||||||
useRead("ListBuilds", {}).data?.find((d) => d.id === id);
|
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 Icon = ({ id }: { id: string }) => {
|
||||||
const building = useRead("GetBuildActionState", { build: id }).data?.building;
|
const building = useRead("GetBuildActionState", { build: id }).data?.building;
|
||||||
const className = 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";
|
: "w-4 h-4";
|
||||||
return <Hammer className={className} />;
|
return <Hammer className={className} />;
|
||||||
};
|
};
|
||||||
@@ -74,7 +75,7 @@ const BuildTable = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: "Repo",
|
header: "Repo",
|
||||||
accessorKey: "info.repo"
|
accessorKey: "info.repo",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: "Version",
|
header: "Version",
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ export const BuilderComponents: RequiredResourceComponents = {
|
|||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
Icon: () => <Factory className="w-4 h-4" />,
|
Icon: () => <Factory className="w-4 h-4" />,
|
||||||
|
Status: () => <></>,
|
||||||
Page: {
|
Page: {
|
||||||
Config: BuilderConfig,
|
Config: BuilderConfig,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ export const RedeployContainer = ({ id }: DeploymentId) => {
|
|||||||
return (
|
return (
|
||||||
<ConfirmButton
|
<ConfirmButton
|
||||||
title={deployment?.info.status ? "Redeploy" : "Deploy"}
|
title={deployment?.info.status ? "Redeploy" : "Deploy"}
|
||||||
// intent="success"
|
|
||||||
icon={<Rocket className="h-4 w-4" />}
|
icon={<Rocket className="h-4 w-4" />}
|
||||||
onClick={() => mutate({ deployment: id })}
|
onClick={() => mutate({ deployment: id })}
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
@@ -120,7 +119,8 @@ export const DeleteDeployment = ({ id }: { id: string }) => {
|
|||||||
const { data: d } = useRead("GetDeployment", { deployment: id });
|
const { data: d } = useRead("GetDeployment", { deployment: id });
|
||||||
const { mutateAsync, isPending } = useWrite("DeleteDeployment");
|
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;
|
if (!d) return null;
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { ConfigItem, ResourceSelector } from "@components/config/util";
|
import { ConfigItem, ResourceSelector } from "@components/config/util";
|
||||||
|
import { fmt_version } from "@lib/formatting";
|
||||||
import { useRead } from "@lib/hooks";
|
import { useRead } from "@lib/hooks";
|
||||||
import { fmt_version } from "@lib/utils";
|
|
||||||
import { Types } from "@monitor/client";
|
import { Types } from "@monitor/client";
|
||||||
import { Input } from "@ui/input";
|
import { Input } from "@ui/input";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@ui/select";
|
} from "@ui/select";
|
||||||
import { keys } from "@lib/utils";
|
import { object_keys } from "@lib/utils";
|
||||||
|
|
||||||
const format_mode = (m: string) => m.split("-").join(" ");
|
const format_mode = (m: string) => m.split("-").join(" ");
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ export const RestartModeSelector = ({
|
|||||||
<SelectValue placeholder="Select Type" />
|
<SelectValue placeholder="Select Type" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{keys(Types.RestartMode).map((mode) => (
|
{object_keys(Types.RestartMode).map((mode) => (
|
||||||
<SelectItem
|
<SelectItem
|
||||||
key={mode}
|
key={mode}
|
||||||
value={Types.RestartMode[mode]}
|
value={Types.RestartMode[mode]}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useRead, useWrite } from "@lib/hooks";
|
|||||||
import { Types } from "@monitor/client";
|
import { Types } from "@monitor/client";
|
||||||
import { RequiredResourceComponents } from "@types";
|
import { RequiredResourceComponents } from "@types";
|
||||||
import { AlertTriangle, HardDrive, Rocket, Server } from "lucide-react";
|
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 { useState } from "react";
|
||||||
import { NewResource, Section } from "@components/layouts";
|
import { NewResource, Section } from "@components/layouts";
|
||||||
|
|
||||||
@@ -23,33 +23,27 @@ import { ResourceComponents } from "..";
|
|||||||
import { TagsWithBadge, useTagsFilter } from "@components/tags";
|
import { TagsWithBadge, useTagsFilter } from "@components/tags";
|
||||||
import { DeploymentsChart } from "@components/dashboard/deployments-chart";
|
import { DeploymentsChart } from "@components/dashboard/deployments-chart";
|
||||||
import { Button } from "@ui/button";
|
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) =>
|
export const useDeployment = (id?: string) =>
|
||||||
useRead("ListDeployments", {}, { refetchInterval: 5000 }).data?.find(
|
useRead("ListDeployments", {}, { refetchInterval: 5000 }).data?.find(
|
||||||
(d) => d.id === id
|
(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 Icon = ({ id }: { id?: string }) => {
|
||||||
const state = useDeployment(id)?.info.state;
|
const state = useDeployment(id)?.info.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Rocket
|
<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",
|
header: "State",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const state = row.original.info.state;
|
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 (
|
return (
|
||||||
<div className={color}>
|
<div className={color}>
|
||||||
{snake_case_to_upper_space_case(state)}
|
{snake_case_to_upper_space_case(state)}
|
||||||
@@ -192,11 +188,10 @@ export const DeploymentComponents: RequiredResourceComponents = {
|
|||||||
Status: ({ id }) => {
|
Status: ({ id }) => {
|
||||||
const state =
|
const state =
|
||||||
useDeployment(id)?.info.state ?? Types.DockerContainerState.Unknown;
|
useDeployment(id)?.info.state ?? Types.DockerContainerState.Unknown;
|
||||||
return (
|
const color = text_color_class_by_intention(
|
||||||
<div className={deployment_state_text_color(state)}>
|
deployment_state_intention(state)
|
||||||
{snake_case_to_upper_space_case(state)}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
return <div className={color}>{snake_case_to_upper_space_case(state)}</div>;
|
||||||
},
|
},
|
||||||
Actions: ({ id }) => (
|
Actions: ({ id }) => (
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export const ProcedureComponents: RequiredResourceComponents = {
|
|||||||
Description: ({ id }) => <>{useProcedure(id)?.info.procedure_type}</>,
|
Description: ({ id }) => <>{useProcedure(id)?.info.procedure_type}</>,
|
||||||
Info: () => <></>,
|
Info: () => <></>,
|
||||||
Icon: () => <Route className="w-4" />,
|
Icon: () => <Route className="w-4" />,
|
||||||
|
Status: () => <></>,
|
||||||
Page: {
|
Page: {
|
||||||
Config: ProcedureConfig,
|
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 { TagsWithBadge } from "@components/tags";
|
||||||
import { useRead, useWrite } from "@lib/hooks";
|
import { useRead } from "@lib/hooks";
|
||||||
import { Types } from "@monitor/client";
|
|
||||||
import { Icon } from "@radix-ui/react-select";
|
import { Icon } from "@radix-ui/react-select";
|
||||||
import { RequiredResourceComponents } from "@types";
|
import { RequiredResourceComponents } from "@types";
|
||||||
import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card";
|
import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card";
|
||||||
import { DataTable } from "@ui/data-table";
|
import { DataTable } from "@ui/data-table";
|
||||||
import { GitBranch } from "lucide-react";
|
import { GitBranch } from "lucide-react";
|
||||||
import { useState } from "react";
|
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { RepoConfig } from "./config";
|
||||||
|
|
||||||
const useRepo = (id?: string) =>
|
const useRepo = (id?: string) =>
|
||||||
useRead("ListRepos", {}).data?.find((d) => d.id === id);
|
useRead("ListRepos", {}).data?.find((d) => d.id === id);
|
||||||
@@ -38,47 +35,9 @@ export const RepoComponents: RequiredResourceComponents = {
|
|||||||
Description: ({ id }) => <>{id}</>,
|
Description: ({ id }) => <>{id}</>,
|
||||||
Info: ({ id }) => <>{id}</>,
|
Info: ({ id }) => <>{id}</>,
|
||||||
Icon: () => <GitBranch className="w-4 h-4" />,
|
Icon: () => <GitBranch className="w-4 h-4" />,
|
||||||
|
Status: () => <></>,
|
||||||
Page: {
|
Page: {
|
||||||
Config: ({ id }) => {
|
Config: RepoConfig,
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Table: () => {
|
Table: () => {
|
||||||
const alerters = useRead("ListAlerters", {}).data;
|
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 { cn } from "@lib/utils";
|
||||||
import { Types } from "@monitor/client";
|
import { Types } from "@monitor/client";
|
||||||
import { RequiredResourceComponents } from "@types";
|
import { RequiredResourceComponents } from "@types";
|
||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
Rocket,
|
Rocket,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { ServerStats } from "./stats";
|
import { ServerStats } from "./stats";
|
||||||
import { Config } from "@components/config";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { NewResource, Section } from "@components/layouts";
|
import { NewResource, Section } from "@components/layouts";
|
||||||
import { Input } from "@ui/input";
|
import { Input } from "@ui/input";
|
||||||
@@ -23,6 +22,12 @@ import { TagsWithBadge, useTagsFilter } from "@components/tags";
|
|||||||
import { DeleteServer, RenameServer } from "./actions";
|
import { DeleteServer, RenameServer } from "./actions";
|
||||||
import { ServersChart } from "@components/dashboard/servers-chart";
|
import { ServersChart } from "@components/dashboard/servers-chart";
|
||||||
import { DeploymentTable } from "../deployment";
|
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) =>
|
export const useServer = (id?: string) =>
|
||||||
useRead("ListServers", {}).data?.find((d) => d.id === id);
|
useRead("ListServers", {}).data?.find((d) => d.id === id);
|
||||||
@@ -76,57 +81,12 @@ export const ServerInfo = ({
|
|||||||
|
|
||||||
export const ServerIconComponent = ({ id }: { id?: string }) => {
|
export const ServerIconComponent = ({ id }: { id?: string }) => {
|
||||||
const status = useServer(id)?.info.status;
|
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 (
|
return (
|
||||||
<Config
|
<ServerIcon
|
||||||
config={config}
|
className={cn(
|
||||||
update={update}
|
"w-4 h-4",
|
||||||
set={set}
|
id && fill_color_class_by_intention(server_status_intention(status))
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -229,12 +189,9 @@ export const ServerComponents: RequiredResourceComponents = {
|
|||||||
Icon: ServerIconComponent,
|
Icon: ServerIconComponent,
|
||||||
Status: ({ id }) => {
|
Status: ({ id }) => {
|
||||||
const status = useServer(id)?.info.status;
|
const status = useServer(id)?.info.status;
|
||||||
const stateClass =
|
const stateClass = text_color_class_by_intention(
|
||||||
status === Types.ServerStatus.Ok
|
server_status_intention(status)
|
||||||
? "text-green-500"
|
);
|
||||||
: status === Types.ServerStatus.NotOk
|
|
||||||
? "text-red-500"
|
|
||||||
: "text-blue-500";
|
|
||||||
return (
|
return (
|
||||||
<div className={stateClass}>
|
<div className={stateClass}>
|
||||||
{status === Types.ServerStatus.NotOk ? "Not Ok" : status}
|
{status === Types.ServerStatus.NotOk ? "Not Ok" : status}
|
||||||
@@ -253,7 +210,7 @@ export const ServerComponents: RequiredResourceComponents = {
|
|||||||
</Section>
|
</Section>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
Config: ({ id }) => <ServerConfig id={id} />,
|
Config: ServerConfig,
|
||||||
Danger: ({ id }) => (
|
Danger: ({ id }) => (
|
||||||
<Section title="Danger Zone" icon={<AlertTriangle className="w-4 h-4" />}>
|
<Section title="Danger Zone" icon={<AlertTriangle className="w-4 h-4" />}>
|
||||||
<RenameServer id={id} />
|
<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 { atom, useAtom } from "jotai";
|
||||||
import { Circle } from "lucide-react";
|
import { Circle } from "lucide-react";
|
||||||
import { ReactNode, useCallback, useEffect } from "react";
|
import { ReactNode, useCallback, useEffect } from "react";
|
||||||
import rws from "reconnecting-websocket";
|
import Rws from "reconnecting-websocket";
|
||||||
import { cn } from "@lib/utils";
|
import { cn } from "@lib/utils";
|
||||||
import { AUTH_TOKEN_STORAGE_KEY } from "@main";
|
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 useWebsocket = () => useAtom(rws_atom);
|
||||||
|
|
||||||
const on_message = (
|
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);
|
const jwt = localStorage.getItem(AUTH_TOKEN_STORAGE_KEY);
|
||||||
if (!ws || !jwt) return;
|
if (!ws || !jwt) return;
|
||||||
const msg: Types.WsLoginMessage = { type: "Jwt", params: { jwt } };
|
const msg: Types.WsLoginMessage = { type: "Jwt", params: { jwt } };
|
||||||
@@ -116,7 +116,7 @@ export const WebsocketProvider = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ws) set(new rws(url));
|
if (!ws) set(new Rws(url));
|
||||||
return () => {
|
return () => {
|
||||||
ws?.close();
|
ws?.close();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export function cn(...inputs: ClassValue[]) {
|
|||||||
return twMerge(clsx(inputs));
|
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)[];
|
Object.keys(o) as (keyof T)[];
|
||||||
|
|
||||||
export const RESOURCE_TARGETS: UsableResource[] = [
|
export const RESOURCE_TARGETS: UsableResource[] = [
|
||||||
@@ -20,36 +20,6 @@ export const RESOURCE_TARGETS: UsableResource[] = [
|
|||||||
"Alerter",
|
"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) {
|
export function env_to_text(envVars: Types.EnvironmentVar[] | undefined) {
|
||||||
return envVars?.reduce(
|
return envVars?.reduce(
|
||||||
(prev, { variable, value }) =>
|
(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) {
|
function keep_line(line: string) {
|
||||||
if (line.length === 0) return false;
|
if (line.length === 0) return false;
|
||||||
let firstIndex = -1;
|
let firstIndex = -1;
|
||||||
@@ -71,22 +52,3 @@ function keep_line(line: string) {
|
|||||||
if (line[firstIndex] === "#") return false;
|
if (line[firstIndex] === "#") return false;
|
||||||
return true;
|
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 { Page, Section } from "@components/layouts";
|
||||||
import { ConfirmButton, ResourceLink } from "@components/util";
|
import { ConfirmButton, ResourceLink } from "@components/util";
|
||||||
|
import { text_color_class_by_intention } from "@lib/color";
|
||||||
import { useInvalidate, useRead, useWrite } from "@lib/hooks";
|
import { useInvalidate, useRead, useWrite } from "@lib/hooks";
|
||||||
import { Types } from "@monitor/client";
|
import { Types } from "@monitor/client";
|
||||||
import { UsableResource } from "@types";
|
import { UsableResource } from "@types";
|
||||||
@@ -37,8 +38,8 @@ export const UsersPage = () => {
|
|||||||
header: "Enabled",
|
header: "Enabled",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const enabledClass = row.original.enabled
|
const enabledClass = row.original.enabled
|
||||||
? "text-green-500"
|
? text_color_class_by_intention("Good")
|
||||||
: "text-red-500";
|
: text_color_class_by_intention("Critical");
|
||||||
return (
|
return (
|
||||||
<div className={enabledClass}>
|
<div className={enabledClass}>
|
||||||
{row.original.enabled ? "Enabled" : "Disabled"}
|
{row.original.enabled ? "Enabled" : "Disabled"}
|
||||||
|
|||||||
Reference in New Issue
Block a user