From 3745b65148566fa0e00133242f5a359a6f0d572a Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Mon, 22 Apr 2024 16:15:17 +0530 Subject: [PATCH] feat(ui): added dynamic secret ui for cassandra --- frontend/src/hooks/api/dynamicSecret/types.ts | 47 ++- .../CassandraInputForm.tsx | 363 +++++++++++++++++ .../CreateDynamicSecretForm.tsx | 73 +++- .../SqlDatabaseInputForm.tsx | 6 +- .../CreateDynamicSecretLease.tsx | 6 +- .../EditDynamicSecretCassandraForm.tsx | 374 ++++++++++++++++++ .../EditDynamicSecretForm.tsx | 18 + .../EditDynamicSecretSqlProviderForm.tsx | 6 +- 8 files changed, 849 insertions(+), 44 deletions(-) create mode 100644 frontend/src/views/SecretMainPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx create mode 100644 frontend/src/views/SecretMainPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCassandraForm.tsx diff --git a/frontend/src/hooks/api/dynamicSecret/types.ts b/frontend/src/hooks/api/dynamicSecret/types.ts index 4b2a736d3..27c4c5ddf 100644 --- a/frontend/src/hooks/api/dynamicSecret/types.ts +++ b/frontend/src/hooks/api/dynamicSecret/types.ts @@ -16,7 +16,8 @@ export type TDynamicSecret = { }; export enum DynamicSecretProviders { - SqlDatabase = "sql-database" + SqlDatabase = "sql-database", + Cassandra = "cassandra" } export enum SqlProviders { @@ -25,21 +26,37 @@ export enum SqlProviders { Oracle = "oracledb" } -export type TDynamicSecretProvider = { - type: DynamicSecretProviders; - inputs: { - client: SqlProviders; - host: string; - port: number; - database: string; - username: string; - password: string; - creationStatement: string; - revocationStatement: string; - renewStatement?: string; - ca?: string | undefined; +export type TDynamicSecretProvider = + | { + type: DynamicSecretProviders.SqlDatabase; + inputs: { + client: SqlProviders; + host: string; + port: number; + database: string; + username: string; + password: string; + creationStatement: string; + revocationStatement: string; + renewStatement?: string; + ca?: string | undefined; + }; + } + | { + type: DynamicSecretProviders.Cassandra; + inputs: { + host: string; + port: number; + keyspace?: string; + localDataCenter: string; + username: string; + password: string; + creationStatement: string; + revocationStatement: string; + renewStatement?: string; + ca?: string | undefined; + }; }; -}; export type TCreateDynamicSecretDTO = { projectSlug: string; diff --git a/frontend/src/views/SecretMainPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx b/frontend/src/views/SecretMainPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx new file mode 100644 index 000000000..950e7b2aa --- /dev/null +++ b/frontend/src/views/SecretMainPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx @@ -0,0 +1,363 @@ +import { Controller, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import ms from "ms"; +import { z } from "zod"; + +import { TtlFormLabel } from "@app/components/features"; +import { createNotification } from "@app/components/notifications"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, + Button, + FormControl, + Input, + SecretInput, + TextArea +} from "@app/components/v2"; +import { useCreateDynamicSecret } from "@app/hooks/api"; +import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; + +const formSchema = z.object({ + provider: z.object({ + host: z.string().toLowerCase().min(1), + port: z.coerce.number(), + keyspace: z.string().optional(), + localDataCenter: z.string().min(1), + username: z.string().min(1), + password: z.string().min(1), + creationStatement: z.string().min(1), + revocationStatement: z.string().min(1), + renewStatement: z.string().optional(), + ca: z.string().optional() + }), + defaultTTL: z.string().superRefine((val, ctx) => { + const valMs = ms(val); + if (valMs < 60 * 1000) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); + // a day + if (valMs > 24 * 60 * 60 * 1000) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + }), + maxTTL: z + .string() + .optional() + .superRefine((val, ctx) => { + if (!val) return; + const valMs = ms(val); + if (valMs < 60 * 1000) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); + // a day + if (valMs > 24 * 60 * 60 * 1000) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + }), + name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase") +}); +type TForm = z.infer; + +type Props = { + onCompleted: () => void; + onCancel: () => void; + secretPath: string; + projectSlug: string; + environment: string; +}; + +const getSqlStatements = () => { + return { + creationStatement: + "CREATE ROLE '{{username}}' WITH PASSWORD = '{{password}}' AND LOGIN=true;\nGRANT ALL PERMISSIONS ON ALL KEYSPACES TO '{{username}}';", + renewStatement: "", + revocationStatement: 'DROP ROLE "{{username}}";' + }; +}; + +export const CassandraInputForm = ({ + onCompleted, + onCancel, + environment, + secretPath, + projectSlug +}: Props) => { + const { + control, + formState: { isSubmitting }, + handleSubmit + } = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + provider: getSqlStatements() + } + }); + + const createDynamicSecret = useCreateDynamicSecret(); + + const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { + // wait till previous request is finished + if (createDynamicSecret.isLoading) return; + try { + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.Cassandra, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment + }); + onCompleted(); + } catch (err) { + createNotification({ + type: "error", + text: "Failed to create dynamic secret" + }); + } + }; + + return ( +
+
+
+
+
+ ( + + + + )} + /> +
+
+ ( + } + isError={Boolean(error?.message)} + errorText={error?.message} + > + + + )} + /> +
+
+ ( + } + isError={Boolean(error?.message)} + errorText={error?.message} + > + + + )} + /> +
+
+
+
+ Configuration +
+
+
+ ( + + + + )} + /> + ( + + + + )} + /> +
+ ( + + + + )} + /> +
+ ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> +
+
+ ( + + + + )} + /> + + + Modify CQL Statements + + ( + +