start permissions UI etc

This commit is contained in:
kv
2024-01-07 18:28:56 -08:00
parent 61f7efaa85
commit 409e064452
10 changed files with 434 additions and 49 deletions

View File

@@ -27,7 +27,7 @@
"cmdk": "^0.2.0", "cmdk": "^0.2.0",
"jotai": "^2.4.1", "jotai": "^2.4.1",
"lightweight-charts": "^4.0.1", "lightweight-charts": "^4.0.1",
"lucide-react": "^0.274.0", "lucide-react": "^0.285.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-minimal-pie-chart": "^8.4.0", "react-minimal-pie-chart": "^8.4.0",

View File

@@ -155,10 +155,11 @@ export const ResourceSelector = ({
onSelect: (id: string) => void; onSelect: (id: string) => void;
}) => { }) => {
const resources = useRead(`List${type}s`, {}).data; const resources = useRead(`List${type}s`, {}).data;
const value = resources?.find((r) => r.id === selected)?.name;
return ( return (
<Select value={selected ?? undefined} onValueChange={onSelect}> <Select value={value ?? undefined} onValueChange={onSelect}>
<SelectTrigger className="w-full lg:w-[300px]"> <SelectTrigger className="w-full lg:w-[300px]">
<SelectValue placeholder={`Select ${type}`} /> {value ?? `Select ${type}`}
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
@@ -187,9 +188,7 @@ export const AccountSelector = ({
onSelect: (id: string) => void; onSelect: (id: string) => void;
}) => { }) => {
const request = const request =
type === "Server" type === "Server" ? "GetAvailableAccounts" : "GetBuilderAvailableAccounts";
? "GetServerAvailableAccounts"
: "GetBuilderAvailableAccounts";
const accounts = useRead(request, { id: id! }, { enabled: !!id }).data; const accounts = useRead(request, { id: id! }, { enabled: !!id }).data;
return ( return (
<ConfigItem label={`${account_type} Account`}> <ConfigItem label={`${account_type} Account`}>

View File

@@ -26,8 +26,6 @@ import { ResourceTarget } from "@monitor/client/dist/types";
const Username = ({ user_id }: { user_id: string }) => { const Username = ({ user_id }: { user_id: string }) => {
const username = useRead("GetUsername", { user_id }).data?.username; const username = useRead("GetUsername", { user_id }).data?.username;
console.log(user_id, useRead("GetUsername", { user_id }).data);
return <>{username}</>; return <>{username}</>;
}; };
@@ -138,8 +136,6 @@ export const ResourcePermissions = ({
.filter((id) => id != me) .filter((id) => id != me)
.filter((id) => !users?.find((u) => u._id?.$oid === id)?.admin); .filter((id) => !users?.find((u) => u._id?.$oid === id)?.admin);
console.log(users, display);
if (!admin || !display.length) return null; if (!admin || !display.length) return null;
return ( return (

View File

@@ -5,6 +5,7 @@ import { Builder } from "./builder";
import { Deployment } from "./deployment"; import { Deployment } from "./deployment";
import { Repo } from "./repo"; import { Repo } from "./repo";
import { Server } from "./server"; import { Server } from "./server";
import { Procedure } from "./procedure";
export const ResourceComponents: { export const ResourceComponents: {
[key in UsableResource]: RequiredResourceComponents; [key in UsableResource]: RequiredResourceComponents;
@@ -15,4 +16,5 @@ export const ResourceComponents: {
Deployment, Deployment,
Repo, Repo,
Server, Server,
Procedure,
}; };

View File

@@ -0,0 +1,403 @@
import { ResourceSelector } from "@components/config/util";
import { NewResource } from "@components/layouts";
import { ConfirmButton } from "@components/util";
import { useExecute, useRead, useWrite } from "@lib/hooks";
import { Types } from "@monitor/client";
import { Execution } from "@monitor/client/dist/types";
import { RequiredResourceComponents, UsableResource } from "@types";
import { Button } from "@ui/button";
import { Input } from "@ui/input";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@ui/select";
import { Loader2, Route, Save } from "lucide-react";
import React, { useState } from "react";
const useProcedure = (id?: string) =>
useRead("ListProcedures", {}).data?.find((d) => d.id === id);
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) return;
if (
parent.config.type === "Sequence" ||
parent.config.type === "Parallel"
) {
update_parent({
id: parent._id?.$oid!,
config: {
...parent.config,
data: [...parent.config.data, _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"];
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 EXECUTION_TYPES: ExecutionType[] = [
// "None",
// "CloneRepo",
// "Deploy",
// "PruneDockerContainers",
// "PruneDockerImages",
// "PruneDockerNetworks",
// "PullRepo",
// "RemoveContainer",
// "RunBuild",
// "RunProcedure",
// "StartContainer",
// "StopAllContainers",
// "StopContainer",
// ];
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 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;
const [type, setType] = useState<ExecutionType>(procedure.config.data.type);
const [params, setParams] = useState(
procedure.config.data ?? EXEC_TYPES[type].params
);
const Component = EXEC_TYPES[type].component;
console.log(params);
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">
<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>
);
};
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 Procedure: 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", { id }).data?.running;
const { mutate, isLoading } = 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: id })}
disabled={running || isLoading}
/>
);
},
New: () => <NewProcedure />,
};

View File

@@ -34,11 +34,11 @@ export const useRead = <
(T | P)[] (T | P)[]
>, >,
"queryFn" | "queryKey" "queryFn" | "queryKey"
>, >
>( >(
type: T, type: T,
params: P, params: P,
config?: C, config?: C
) => useQuery([type, params], () => client.read({ type, params } as R), config); ) => useQuery([type, params], () => client.read({ type, params } as R), config);
export const useWrite = < export const useWrite = <
@@ -48,10 +48,10 @@ export const useWrite = <
C extends Omit< C extends Omit<
UseMutationOptions<WriteResponses[T], unknown, P, unknown>, UseMutationOptions<WriteResponses[T], unknown, P, unknown>,
"mutationKey" | "mutationFn" "mutationKey" | "mutationFn"
>, >
>( >(
type: T, type: T,
config?: C, config?: C
) => ) =>
useMutation([type], (params: P) => client.write({ type, params } as R), { useMutation([type], (params: P) => client.write({ type, params } as R), {
...config, ...config,
@@ -67,10 +67,10 @@ export const useExecute = <
C extends Omit< C extends Omit<
UseMutationOptions<ExecuteResponses[T], unknown, P, unknown>, UseMutationOptions<ExecuteResponses[T], unknown, P, unknown>,
"mutationKey" | "mutationFn" "mutationKey" | "mutationFn"
>, >
>( >(
type: T, type: T,
config?: C, config?: C
) => ) =>
useMutation([type], (params: P) => client.execute({ type, params } as R), { useMutation([type], (params: P) => client.execute({ type, params } as R), {
...config, ...config,
@@ -85,14 +85,10 @@ export const useInvalidate = () => {
return < return <
T extends Types.ReadRequest["type"], T extends Types.ReadRequest["type"],
P = Extract<Types.ReadRequest, { type: T }>["params"], P = Extract<Types.ReadRequest, { type: T }>["params"]
>( >(
...keys: Array<[T] | [T, P]> ...keys: Array<[T] | [T, P]>
) => ) => keys.forEach((k) => qc.invalidateQueries([...k]));
keys.forEach((k) => {
console.log("invalidating", k);
qc.invalidateQueries([...k]);
});
}; };
export const usePushRecentlyViewed = ({ type, id }: Types.ResourceTarget) => { export const usePushRecentlyViewed = ({ type, id }: Types.ResourceTarget) => {

View File

@@ -17,6 +17,7 @@ export const RESOURCE_TARGETS: UsableResource[] = [
"Deployment", "Deployment",
"Repo", "Repo",
"Server", "Server",
"Procedure",
]; ];
export const fmt_update_date = (d: Date) => export const fmt_update_date = (d: Date) =>

View File

@@ -1,4 +1,5 @@
import "globals.css"; import "globals.css";
import React from "react";
import ReactDOM from "react-dom/client"; import ReactDOM from "react-dom/client";
import { MonitorClient } from "@monitor/client"; import { MonitorClient } from "@monitor/client";
import { ThemeProvider } from "@ui/theme"; import { ThemeProvider } from "@ui/theme";
@@ -21,7 +22,7 @@ const query_client = new QueryClient({
}); });
ReactDOM.createRoot(document.getElementById("root")!).render( ReactDOM.createRoot(document.getElementById("root")!).render(
// <React.StrictMode> <React.StrictMode>
<QueryClientProvider client={query_client}> <QueryClientProvider client={query_client}>
<WebsocketProvider url={UPDATE_WS_URL}> <WebsocketProvider url={UPDATE_WS_URL}>
<ThemeProvider> <ThemeProvider>
@@ -30,5 +31,5 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
</ThemeProvider> </ThemeProvider>
</WebsocketProvider> </WebsocketProvider>
</QueryClientProvider> </QueryClientProvider>
// </React.StrictMode> </React.StrictMode>
); );

View File

@@ -2,19 +2,6 @@ import { Types } from "@monitor/client";
export type UsableResource = Exclude<Types.ResourceTarget["type"], "System">; export type UsableResource = Exclude<Types.ResourceTarget["type"], "System">;
// export type RequiredComponents =
// | "Name"
// | "Description"
// | "Icon"
// | "Info"
// | "Actions";
// export type RequiredResourceComponents = {
// [key in RequiredComponents]: React.FC<{ id: string }>;
// } & { Page: { [key: string]: React.FC<{ id: string }> } } & {
// New: () => React.ReactNode;
// };
type IdComponent = React.FC<{ id: string }>; type IdComponent = React.FC<{ id: string }>;
type OptionalIdComponent = React.FC<{ id?: string }>; type OptionalIdComponent = React.FC<{ id?: string }>;

View File

@@ -1968,10 +1968,10 @@ lru-cache@^6.0.0:
dependencies: dependencies:
yallist "^4.0.0" yallist "^4.0.0"
lucide-react@^0.274.0: lucide-react@^0.285.0:
version "0.274.0" version "0.285.0"
resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.274.0.tgz#d3b54dcb972b12f1292061448d61d422ef2e269d" resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.285.0.tgz#2929460f74aeb6b709ef5c1f9fad7cd208cd6507"
integrity sha512-qiWcojRXEwDiSimMX1+arnxha+ROJzZjJaVvCC0rsG6a9pUPjZePXSq7em4ZKMp0NDm1hyzPNkM7UaWC3LU2AA== integrity sha512-TvWtS0Zc2lT0wTMyD+sEB7x9TM/38MQMJfJbQMMWJOsPx+lEaWBk1aKalqhCZj/Vbl2r00Uqln7xTTY2T7R63g==
merge2@^1.3.0, merge2@^1.4.1: merge2@^1.3.0, merge2@^1.4.1:
version "1.4.1" version "1.4.1"