forked from github-starred/komodo
start permissions UI etc
This commit is contained in:
@@ -27,7 +27,7 @@
|
||||
"cmdk": "^0.2.0",
|
||||
"jotai": "^2.4.1",
|
||||
"lightweight-charts": "^4.0.1",
|
||||
"lucide-react": "^0.274.0",
|
||||
"lucide-react": "^0.285.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-minimal-pie-chart": "^8.4.0",
|
||||
|
||||
@@ -155,10 +155,11 @@ export const ResourceSelector = ({
|
||||
onSelect: (id: string) => void;
|
||||
}) => {
|
||||
const resources = useRead(`List${type}s`, {}).data;
|
||||
const value = resources?.find((r) => r.id === selected)?.name;
|
||||
return (
|
||||
<Select value={selected ?? undefined} onValueChange={onSelect}>
|
||||
<Select value={value ?? undefined} onValueChange={onSelect}>
|
||||
<SelectTrigger className="w-full lg:w-[300px]">
|
||||
<SelectValue placeholder={`Select ${type}`} />
|
||||
{value ?? `Select ${type}`}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
@@ -187,9 +188,7 @@ export const AccountSelector = ({
|
||||
onSelect: (id: string) => void;
|
||||
}) => {
|
||||
const request =
|
||||
type === "Server"
|
||||
? "GetServerAvailableAccounts"
|
||||
: "GetBuilderAvailableAccounts";
|
||||
type === "Server" ? "GetAvailableAccounts" : "GetBuilderAvailableAccounts";
|
||||
const accounts = useRead(request, { id: id! }, { enabled: !!id }).data;
|
||||
return (
|
||||
<ConfigItem label={`${account_type} Account`}>
|
||||
|
||||
@@ -26,8 +26,6 @@ import { ResourceTarget } from "@monitor/client/dist/types";
|
||||
|
||||
const Username = ({ user_id }: { user_id: string }) => {
|
||||
const username = useRead("GetUsername", { user_id }).data?.username;
|
||||
|
||||
console.log(user_id, useRead("GetUsername", { user_id }).data);
|
||||
return <>{username}</>;
|
||||
};
|
||||
|
||||
@@ -138,8 +136,6 @@ export const ResourcePermissions = ({
|
||||
.filter((id) => id != me)
|
||||
.filter((id) => !users?.find((u) => u._id?.$oid === id)?.admin);
|
||||
|
||||
console.log(users, display);
|
||||
|
||||
if (!admin || !display.length) return null;
|
||||
|
||||
return (
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Builder } from "./builder";
|
||||
import { Deployment } from "./deployment";
|
||||
import { Repo } from "./repo";
|
||||
import { Server } from "./server";
|
||||
import { Procedure } from "./procedure";
|
||||
|
||||
export const ResourceComponents: {
|
||||
[key in UsableResource]: RequiredResourceComponents;
|
||||
@@ -15,4 +16,5 @@ export const ResourceComponents: {
|
||||
Deployment,
|
||||
Repo,
|
||||
Server,
|
||||
Procedure,
|
||||
};
|
||||
|
||||
403
frontend/src/components/resources/procedure/index.tsx
Normal file
403
frontend/src/components/resources/procedure/index.tsx
Normal 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 />,
|
||||
};
|
||||
@@ -34,11 +34,11 @@ export const useRead = <
|
||||
(T | P)[]
|
||||
>,
|
||||
"queryFn" | "queryKey"
|
||||
>,
|
||||
>
|
||||
>(
|
||||
type: T,
|
||||
params: P,
|
||||
config?: C,
|
||||
config?: C
|
||||
) => useQuery([type, params], () => client.read({ type, params } as R), config);
|
||||
|
||||
export const useWrite = <
|
||||
@@ -48,10 +48,10 @@ export const useWrite = <
|
||||
C extends Omit<
|
||||
UseMutationOptions<WriteResponses[T], unknown, P, unknown>,
|
||||
"mutationKey" | "mutationFn"
|
||||
>,
|
||||
>
|
||||
>(
|
||||
type: T,
|
||||
config?: C,
|
||||
config?: C
|
||||
) =>
|
||||
useMutation([type], (params: P) => client.write({ type, params } as R), {
|
||||
...config,
|
||||
@@ -67,10 +67,10 @@ export const useExecute = <
|
||||
C extends Omit<
|
||||
UseMutationOptions<ExecuteResponses[T], unknown, P, unknown>,
|
||||
"mutationKey" | "mutationFn"
|
||||
>,
|
||||
>
|
||||
>(
|
||||
type: T,
|
||||
config?: C,
|
||||
config?: C
|
||||
) =>
|
||||
useMutation([type], (params: P) => client.execute({ type, params } as R), {
|
||||
...config,
|
||||
@@ -85,14 +85,10 @@ export const useInvalidate = () => {
|
||||
|
||||
return <
|
||||
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.forEach((k) => {
|
||||
console.log("invalidating", k);
|
||||
qc.invalidateQueries([...k]);
|
||||
});
|
||||
) => keys.forEach((k) => qc.invalidateQueries([...k]));
|
||||
};
|
||||
|
||||
export const usePushRecentlyViewed = ({ type, id }: Types.ResourceTarget) => {
|
||||
|
||||
@@ -17,6 +17,7 @@ export const RESOURCE_TARGETS: UsableResource[] = [
|
||||
"Deployment",
|
||||
"Repo",
|
||||
"Server",
|
||||
"Procedure",
|
||||
];
|
||||
|
||||
export const fmt_update_date = (d: Date) =>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import "globals.css";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { MonitorClient } from "@monitor/client";
|
||||
import { ThemeProvider } from "@ui/theme";
|
||||
@@ -21,7 +22,7 @@ const query_client = new QueryClient({
|
||||
});
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
// <React.StrictMode>
|
||||
<React.StrictMode>
|
||||
<QueryClientProvider client={query_client}>
|
||||
<WebsocketProvider url={UPDATE_WS_URL}>
|
||||
<ThemeProvider>
|
||||
@@ -30,5 +31,5 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
</ThemeProvider>
|
||||
</WebsocketProvider>
|
||||
</QueryClientProvider>
|
||||
// </React.StrictMode>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
13
frontend/src/types.d.ts
vendored
13
frontend/src/types.d.ts
vendored
@@ -2,19 +2,6 @@ import { Types } from "@monitor/client";
|
||||
|
||||
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 OptionalIdComponent = React.FC<{ id?: string }>;
|
||||
|
||||
|
||||
@@ -1968,10 +1968,10 @@ lru-cache@^6.0.0:
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
lucide-react@^0.274.0:
|
||||
version "0.274.0"
|
||||
resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.274.0.tgz#d3b54dcb972b12f1292061448d61d422ef2e269d"
|
||||
integrity sha512-qiWcojRXEwDiSimMX1+arnxha+ROJzZjJaVvCC0rsG6a9pUPjZePXSq7em4ZKMp0NDm1hyzPNkM7UaWC3LU2AA==
|
||||
lucide-react@^0.285.0:
|
||||
version "0.285.0"
|
||||
resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.285.0.tgz#2929460f74aeb6b709ef5c1f9fad7cd208cd6507"
|
||||
integrity sha512-TvWtS0Zc2lT0wTMyD+sEB7x9TM/38MQMJfJbQMMWJOsPx+lEaWBk1aKalqhCZj/Vbl2r00Uqln7xTTY2T7R63g==
|
||||
|
||||
merge2@^1.3.0, merge2@^1.4.1:
|
||||
version "1.4.1"
|
||||
|
||||
Reference in New Issue
Block a user