fix the type change

This commit is contained in:
mbecker20
2024-03-27 04:18:57 -07:00
parent 851c1f450c
commit a5e15f9c5e
17 changed files with 710 additions and 615 deletions

View File

@@ -192,7 +192,7 @@ export const AccountSelector = ({
}) => {
const request =
type === "Server" ? "GetAvailableAccounts" : "GetBuilderAvailableAccounts";
const accounts = useRead(request, { id: id! }, { enabled: !!id }).data;
const accounts = useRead(request, { server: id! }, { enabled: !!id }).data;
return (
<ConfigItem label={`${account_type} Account`}>
<Select

View File

@@ -1,189 +1,192 @@
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";
// 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 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");
// 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.Update}>
Update
</SelectItem>
<SelectItem value={Types.PermissionLevel.Execute}>
Execute
</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>
);
};
// 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 admin = useRead("GetUser", {}).data?.admin;
const me = useRead("GetUser", {}).data?._id?.$oid;
const permissions = useRead(`Get${type}`, { id }, { enabled: admin }).data
?.permissions;
// // 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 users = useRead("GetUsers", {}).data?.filter((u) => !u.admin);
const { mutate: update, isPending } = useWrite(
"UpdateUserPermissionsOnTarget"
);
// // 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);
// // 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;
// // 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>
);
};
// // 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>
// // );
// // };

View File

@@ -66,7 +66,7 @@ const NewAlerter = () => {
};
const SlackAlerterConfig = ({ id }: { id: string }) => {
const config = useRead("GetAlerter", { id }).data?.config;
const config = useRead("GetAlerter", { alerter: id }).data?.config;
const [update, set] = useState<Partial<Types.SlackAlerterConfig>>({});
const { mutate } = useWrite("UpdateAlerter");
if (!config) return null;
@@ -89,7 +89,7 @@ const SlackAlerterConfig = ({ id }: { id: string }) => {
};
const CustomAlerterConfig = ({ id }: { id: string }) => {
const config = useRead("GetAlerter", { id }).data?.config;
const config = useRead("GetAlerter", { alerter: id }).data?.config;
const [update, set] = useState<Partial<Types.CustomAlerterConfig>>({});
const { mutate } = useWrite("UpdateAlerter");
if (!config) return null;
@@ -165,7 +165,7 @@ export const AlerterComponents: RequiredResourceComponents = {
Info: ({ id }) => <>{id}</>,
Page: {
Config: ({ id }: { id: string }) => {
const config = useRead("GetAlerter", { id }).data?.config;
const config = useRead("GetAlerter", { alerter: id }).data?.config;
if (config?.type === "Slack") return <SlackAlerterConfig id={id} />;
if (config?.type === "Custom") return <CustomAlerterConfig id={id} />;
},

View File

@@ -75,7 +75,7 @@ export const BuildArgs = ({
};
export const BuildConfig = ({ id }: { id: string }) => {
const config = useRead("GetBuild", { id }).data?.config;
const config = useRead("GetBuild", { build: id }).data?.config;
// const orgs = useRead("GetAccounts")
const [update, set] = useState<Partial<Types.BuildConfig>>({});
const { mutate } = useWrite("UpdateBuild");
@@ -147,7 +147,7 @@ export const BuildConfig = ({ id }: { id: string }) => {
const Name = ({ id }: { id: string }) => <>{useBuild(id)?.name}</>;
const Icon = ({ id }: { id: string }) => {
const building = useRead("GetBuildActionState", { id }).data?.building;
const building = useRead("GetBuildActionState", { build: id }).data?.building;
const className = building
? "w-4 h-4 animate-spin fill-green-500"
: "w-4 h-4";
@@ -244,7 +244,7 @@ export const BuildComponents: RequiredResourceComponents = {
},
Actions: ({ id }) => {
const { toast } = useToast();
const building = useRead("GetBuildActionState", { id }).data?.building;
const building = useRead("GetBuildActionState", { build: id }).data?.building;
const { mutate: run_mutate, isPending: runPending } = useExecute(
"RunBuild",
{
@@ -270,7 +270,7 @@ export const BuildComponents: RequiredResourceComponents = {
title="Cancel Build"
variant="destructive"
icon={<Ban className="h-4 w-4" />}
onClick={() => cancel_mutate({ build_id: id })}
onClick={() => cancel_mutate({ build: id })}
disabled={cancelPending}
/>
);
@@ -285,7 +285,7 @@ export const BuildComponents: RequiredResourceComponents = {
<Hammer className="h-4 w-4" />
)
}
onClick={() => run_mutate({ build_id: id })}
onClick={() => run_mutate({ build: id })}
disabled={runPending}
/>
);

View File

@@ -25,7 +25,7 @@ const useBuilder = (id?: string) =>
useRead("ListBuilders", {}).data?.find((d) => d.id === id);
const AwsBuilderConfig = ({ id }: { id: string }) => {
const config = useRead("GetBuilder", { id }).data?.config;
const config = useRead("GetBuilder", { builder: id }).data?.config;
const [update, set] = useState<Partial<Types.AwsBuilderConfig>>({});
const { mutate } = useWrite("UpdateBuilder");
if (!config) return null;
@@ -72,7 +72,7 @@ const AwsBuilderConfig = ({ id }: { id: string }) => {
};
const ServerBuilderConfig = ({ id }: { id: string }) => {
const config = useRead("GetBuilder", { id }).data?.config;
const config = useRead("GetBuilder", { builder: id }).data?.config;
const [update, set] = useState<Partial<Types.ServerBuilderConfig>>({});
const { mutate } = useWrite("UpdateBuilder");
if (!config) return null;
@@ -224,7 +224,7 @@ export const BuilderComponents: RequiredResourceComponents = {
Icon: () => <Factory className="w-4 h-4" />,
Page: {
Config: ({ id }) => {
const config = useRead("GetBuilder", { id }).data?.config;
const config = useRead("GetBuilder", { builder: id }).data?.config;
if (config?.type === "Aws") return <AwsBuilderConfig id={id} />;
if (config?.type === "Server") return <ServerBuilderConfig id={id} />;
},

View File

@@ -19,14 +19,15 @@ export const RedeployContainer = ({ id }: DeploymentId) => {
const { mutate, isPending } = useExecute("Deploy");
const deployments = useRead("ListDeployments", {}).data;
const deployment = deployments?.find((d) => d.id === id);
const deploying = useRead("GetDeploymentActionState", { id }).data?.deploying;
const deploying = useRead("GetDeploymentActionState", { deployment: id }).data
?.deploying;
return (
<ConfirmButton
title={deployment?.info.status ? "Redeploy" : "Deploy"}
// intent="success"
icon={<Rocket className="h-4 w-4" />}
onClick={() => mutate({ deployment_id: id })}
onClick={() => mutate({ deployment: id })}
disabled={isPending}
loading={deploying}
/>
@@ -34,10 +35,10 @@ export const RedeployContainer = ({ id }: DeploymentId) => {
};
const StartContainer = ({ id }: DeploymentId) => {
const { data: d } = useRead("GetDeployment", { id });
const { data: d } = useRead("GetDeployment", { deployment: id });
const { mutate, isPending } = useExecute("StartContainer");
const starting = useRead("GetDeploymentActionState", {
id,
deployment: id,
}).data?.starting;
if (!d) return null;
@@ -47,7 +48,7 @@ const StartContainer = ({ id }: DeploymentId) => {
title="Start"
// intent="success"
icon={<Play className="h-4 w-4" />}
onClick={() => mutate({ deployment_id: id })}
onClick={() => mutate({ deployment: id })}
disabled={isPending}
loading={starting}
/>
@@ -55,10 +56,10 @@ const StartContainer = ({ id }: DeploymentId) => {
};
const StopContainer = ({ id }: DeploymentId) => {
const { data: d } = useRead("GetDeployment", { id });
const { data: d } = useRead("GetDeployment", { deployment: id });
const { mutate, isPending } = useExecute("StopContainer");
const stopping = useRead("GetDeploymentActionState", {
id,
deployment: id,
}).data?.stopping;
if (!d) return null;
@@ -68,7 +69,7 @@ const StopContainer = ({ id }: DeploymentId) => {
title="Stop"
// intent="warning"
icon={<Pause className="h-4 w-4" />}
onClick={() => mutate({ deployment_id: id })}
onClick={() => mutate({ deployment: id })}
disabled={isPending}
loading={stopping}
/>
@@ -88,14 +89,14 @@ export const StartOrStopContainer = ({ id }: DeploymentId) => {
};
export const RemoveContainer = ({ id }: DeploymentId) => {
const deployment = useRead("GetDeployment", { id }).data;
const deployment = useRead("GetDeployment", { deployment: id }).data;
const { mutate, isPending } = useExecute("RemoveContainer");
const deployments = useRead("ListDeployments", {}).data;
const state = deployments?.find((d) => d.id === id)?.info.state;
const removing = useRead("GetDeploymentActionState", {
id,
deployment: id,
}).data?.removing;
if (!deployment) return null;
@@ -107,7 +108,7 @@ export const RemoveContainer = ({ id }: DeploymentId) => {
title="Remove"
// intent="warning"
icon={<Trash className="h-4 w-4" />}
onClick={() => mutate({ deployment_id: id })}
onClick={() => mutate({ deployment: id })}
disabled={isPending}
loading={removing}
/>
@@ -116,10 +117,10 @@ export const RemoveContainer = ({ id }: DeploymentId) => {
export const DeleteDeployment = ({ id }: { id: string }) => {
const nav = useNavigate();
const { data: d } = useRead("GetDeployment", { id });
const { data: d } = useRead("GetDeployment", { deployment: id });
const { mutateAsync, isPending } = useWrite("DeleteDeployment");
const deleting = useRead("GetDeploymentActionState", { id }).data?.deleting;
const deleting = useRead("GetDeploymentActionState", { deployment: id }).data?.deleting;
if (!d) return null;
return (

View File

@@ -23,7 +23,7 @@ const BuildVersionSelector = ({
}) => {
const versions = useRead(
"GetBuildVersions",
{ id: buildId! },
{ build: buildId! },
{ enabled: !!buildId }
).data;
return (

View File

@@ -19,7 +19,7 @@ export const NetworkModeSelector = ({
}) => {
const networks = useRead(
"GetDockerNetworks",
{ server_id: server_id! },
{ server: server_id! },
{ enabled: !!server_id }
).data;

View File

@@ -53,7 +53,7 @@ export const DeploymentLogs = ({ id }: { id: string }) => {
const { data: logs, refetch } = useRead(
"GetLog",
{ deployment_id: id, tail: Number(tail) },
{ deployment: id, tail: Number(tail) },
{ refetchInterval: 30000 }
);

View File

@@ -5,7 +5,7 @@ import { BuilderComponents } from "./builder";
import { DeploymentComponents } from "./deployment";
import { RepoComponents } from "./repo";
import { ServerComponents } from "./server";
import { ProcedureComponents } from "./procedure";
import { ProcedureComponents } from "./procedure/index";
export const ResourceComponents: {
[key in UsableResource]: RequiredResourceComponents;

View File

@@ -1,413 +1,30 @@
import { ResourceSelector } from "@components/config/util";
import { NewResource } from "@components/layouts";
import { TagsWithBadge } from "@components/tags";
import { ConfirmButton } from "@components/util";
import { useExecute, useRead, useWrite } from "@lib/hooks";
import { useExecute, useRead } 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 { RequiredResourceComponents } from "@types";
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";
import { Link, Loader2, Route } from "lucide-react";
const useProcedure = (id?: string) =>
useRead("ListProcedures", {}).data?.find((d) => d.id === id);
const Name = ({ id }: { id: string }) => <>{useProcedure(id)?.name}</>;
const get_default_data = <T extends Types.ProcedureConfig["type"]>(
type: T
): string[] | Types.Execution => {
if (type === "Execution") return { type: "None", params: {} };
return [] as string[];
};
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} />,
// Config: ({ id }) => <ProcedureConfig id={id} />,
},
Actions: ({ id }) => {
const running = useRead("GetProcedureActionState", { id }).data?.running;
const running = useRead("GetProcedureActionState", { procedure: id }).data
?.running;
const { mutate, isPending } = useExecute("RunProcedure");
return (
<ConfirmButton
title={running ? "Building" : "Run"}
title={running ? "Running" : "Run"}
icon={
running ? (
<Loader2 className="w-4 h-4 animate-spin" />
@@ -415,16 +32,16 @@ export const ProcedureComponents: RequiredResourceComponents = {
<Route className="h-4 w-4" />
)
}
onClick={() => mutate({ procedure_id: id })}
onClick={() => mutate({ procedure: id })}
disabled={running || isPending}
/>
);
},
Table: () => {
const alerters = useRead("ListAlerters", {}).data;
const procedures = useRead("ListProcedures", {}).data;
return (
<DataTable
data={alerters ?? []}
data={procedures ?? []}
columns={[
{
accessorKey: "id",
@@ -436,13 +53,22 @@ export const ProcedureComponents: RequiredResourceComponents = {
to={`/procedures/${id}`}
className="flex items-center gap-2"
>
<Icon id={id} />
<Name id={id} />
<ProcedureComponents.Icon id={id} />
<ProcedureComponents.Name id={id} />
</Link>
);
},
},
{ header: "Tags", accessorFn: ({ tags }) => tags.join(", ") },
{
header: "Tags",
cell: ({ row }) => {
return (
<div className="flex gap-1">
<TagsWithBadge tag_ids={row.original.tags} />
</div>
);
},
},
{
header: "Created",
accessorFn: ({ created_at }) =>
@@ -452,6 +78,21 @@ export const ProcedureComponents: RequiredResourceComponents = {
/>
);
},
New: () => <NewProcedure />,
Dashboard: ProcedureDashboard,
New: () => <></>,
Dashboard: () => {
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>
);
},
};

View File

@@ -0,0 +1,450 @@
// 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,
// };

View File

@@ -39,7 +39,7 @@ export const RepoComponents: RequiredResourceComponents = {
Icon: () => <GitBranch className="w-4 h-4" />,
Page: {
Config: ({ id }) => {
const config = useRead("GetRepo", { id }).data?.config;
const config = useRead("GetRepo", { repo: id }).data?.config;
const [update, set] = useState<Partial<Types.RepoConfig>>({});
const mutate = useWrite("UpdateRepo");
if (!config) return null;

View File

@@ -8,7 +8,7 @@ import { useNavigate } from "react-router-dom";
export const DeleteServer = ({ id }: { id: string }) => {
const nav = useNavigate();
const server = useRead("GetServer", { id }).data;
const server = useRead("GetServer", { server: id }).data;
const { mutateAsync, isPending } = useWrite("DeleteServer");
if (!server) return null;

View File

@@ -37,12 +37,12 @@ export const ServerInfo = ({
const server = useServer(id);
const stats = useRead(
"GetBasicSystemStats",
{ server_id: id },
{ server: id },
{ enabled: server ? server.info.status !== "Disabled" : false }
).data;
const info = useRead(
"GetSystemInformation",
{ server_id: id },
{ server: id },
{ enabled: server ? server.info.status !== "Disabled" : false }
).data;
return (
@@ -84,7 +84,7 @@ export const ServerIconComponent = ({ id }: { id?: string }) => {
const ServerConfig = ({ id }: { id: string }) => {
const invalidate = useInvalidate();
const config = useRead("GetServer", { id }).data?.config;
const config = useRead("GetServer", { server: id }).data?.config;
const [update, set] = useState<Partial<Types.ServerConfig>>({});
const { mutate } = useWrite("UpdateServer", {
onSuccess: () => {

View File

@@ -14,7 +14,7 @@ import { Types } from "@monitor/client";
export const ServerStats = ({ server_id }: { server_id: string }) => {
const stats = useRead(
"GetAllSystemStats",
{ server_id },
{ server: server_id },
{ refetchInterval: 1000 }
).data;

View File

@@ -14,7 +14,7 @@ import { BuildComponents } from "@components/resources/build";
import { RepoComponents } from "@components/resources/repo";
import { BuilderComponents } from "@components/resources/builder";
import { AlerterComponents } from "@components/resources/alerter";
import { ProcedureComponents } from "@components/resources/procedure";
import { ProcedureComponents } from "@components/resources/procedure/index2";
import { TagsSummary } from "@components/dashboard/tags";
import { ApiKeysSummary } from "@components/dashboard/api-keys";