From 4c5f3859d655e4412f3c6156d9a6e1a9f2c73dd8 Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Fri, 14 Mar 2025 11:59:13 -0300 Subject: [PATCH 1/5] Add dynamic secrets modal form on secrets overview page --- .../OverviewPage/OverviewPage.tsx | 54 ++++++++- .../components/ActionBar/ActionBar.tsx | 2 +- .../AwsElastiCacheInputForm.tsx | 80 ++++++++++---- .../AwsIamInputForm.tsx | 84 ++++++++++---- .../AzureEntraIdInputForm.tsx | 101 +++++++++++------ .../CassandraInputForm.tsx | 81 ++++++++++---- .../CreateDynamicSecretForm.tsx | 35 +++--- .../ElasticSearchInputForm.tsx | 81 ++++++++++---- .../CreateDynamicSecretForm/LdapInputForm.tsx | 90 +++++++++++---- .../MongoAtlasInputForm.tsx | 81 ++++++++++---- .../MongoDBInputForm.tsx | 104 +++++++++++++----- .../RabbitMqInputForm.tsx | 90 +++++++++++---- .../RedisInputForm.tsx | 81 ++++++++++---- .../SapAseInputForm.tsx | 81 ++++++++++---- .../SapHanaInputForm.tsx | 81 ++++++++++---- .../SnowflakeInputForm.tsx | 81 ++++++++++---- .../SqlDatabaseInputForm.tsx | 81 ++++++++++---- .../CreateDynamicSecretForm/TotpInputForm.tsx | 87 +++++++++++---- 18 files changed, 1002 insertions(+), 373 deletions(-) diff --git a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx index 78a2380d0..a611eb030 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx @@ -19,6 +19,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Link, useNavigate, useRouter, useSearch } from "@tanstack/react-router"; import { twMerge } from "tailwind-merge"; +import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { @@ -49,8 +50,10 @@ import { import { ROUTE_PATHS } from "@app/const/routes"; import { ProjectPermissionActions, + ProjectPermissionDynamicSecretActions, ProjectPermissionSub, useProjectPermission, + useSubscription, useWorkspace } from "@app/context"; import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks"; @@ -71,6 +74,7 @@ import { SecretType, SecretV3RawSanitized, TSecretFolder } from "@app/hooks/api/ import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types"; import { useDynamicSecretOverview, useFolderOverview, useSecretOverview } from "@app/hooks/utils"; +import { CreateDynamicSecretForm } from "../SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm"; import { FolderForm } from "../SecretDashboardPage/components/ActionBar/FolderForm"; import { CreateSecretForm } from "./components/CreateSecretForm"; import { FolderBreadCrumbs } from "./components/FolderBreadCrumbs"; @@ -131,6 +135,7 @@ export const OverviewPage = () => { const [searchFilter, setSearchFilter] = useState(""); const [debouncedSearchFilter, setDebouncedSearchFilter] = useDebounce(searchFilter); const secretPath = (routerSearch?.secretPath as string) || "/"; + const { subscription } = useSubscription(); const [filter, setFilter] = useState(DEFAULT_FILTER_STATE); const [filterHistory, setFilterHistory] = useState< @@ -178,6 +183,17 @@ export const OverviewPage = () => { }, []); const userAvailableEnvs = currentWorkspace?.environments || []; + const userAvailableDynamicSecretEnvs = userAvailableEnvs.filter((env) => + permission.can( + ProjectPermissionDynamicSecretActions.CreateRootCredential, + subject(ProjectPermissionSub.DynamicSecrets, { + environment: env.id, + secretPath, + secretName: "*", + secretTags: ["*"] + }) + ) + ); const [visibleEnvs, setVisibleEnvs] = useState(userAvailableEnvs); @@ -249,7 +265,9 @@ export const OverviewPage = () => { "addSecretsInAllEnvs", "addFolder", "misc", - "updateFolder" + "updateFolder", + "addDynamicSecret", + "upgradePlan" ] as const); const handleFolderCreate = async (folderName: string, description: string | null) => { @@ -845,6 +863,22 @@ export const OverviewPage = () => {
+ { /> + handlePopUpToggle("addDynamicSecret", isOpen)} + projectSlug={projectSlug} + environments={userAvailableDynamicSecretEnvs} + secretPath={secretPath} + /> + {subscription && ( + handlePopUpToggle("upgradePlan", isOpen)} + text={ + subscription.slug === null + ? "You can perform this action under an Enterprise license" + : "You can perform this action if you switch to Infisical's Team plan" + } + /> + )}
); }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx index d545ba748..28c58ec5f 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx @@ -655,7 +655,7 @@ export const ActionBar = ({ isOpen={popUp.addDynamicSecret.isOpen} onToggle={(isOpen) => handlePopUpToggle("addDynamicSecret", isOpen)} projectSlug={projectSlug} - environment={environment} + environments={[{ slug: environment, name: "not-used", id: "not-used" }]} secretPath={secretPath} /> 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") + name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"), + environments: z.object({ name: z.string(), slug: z.string() }).array() }); type TForm = z.infer; @@ -59,13 +62,13 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environment: string; + environments: WorkspaceEnv[]; }; export const AwsElastiCacheInputForm = ({ onCompleted, onCancel, - environment, + environments, secretPath, projectSlug }: Props) => { @@ -87,32 +90,42 @@ export const AwsElastiCacheInputForm = ({ revocationStatement: `{ "UserId": "{{username}}" }` - } + }, + environments: environments.length === 1 ? environments : [] } }); const createDynamicSecret = useCreateDynamicSecret(); - const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { + const handleCreateDynamicSecret = async ({ + name, + maxTTL, + provider, + defaultTTL, + environments: selectedEnvs + }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.AwsElastiCache, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + const promises = selectedEnvs.map(async (env) => { + try { + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.AwsElastiCache, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: env.slug + }); + onCompleted(); + } catch { + createNotification({ + type: "error", + text: "Failed to create dynamic secret" + }); + } + }); + await Promise.all(promises); }; return ( @@ -299,6 +312,29 @@ export const AwsElastiCacheInputForm = ({ + {environments.length > 1 && ( + ( + + option.name} + getOptionValue={(option) => option.slug} + /> + + )} + /> + )} diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx index 1acb24e8f..f0590a4e1 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx @@ -5,9 +5,10 @@ import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; import { createNotification } from "@app/components/notifications"; -import { Button, FormControl, Input, TextArea } from "@app/components/v2"; +import { Button, FilterableSelect, FormControl, Input, TextArea } from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; +import { WorkspaceEnv } from "@app/hooks/api/types"; const formSchema = z.object({ provider: z.object({ @@ -40,7 +41,8 @@ const formSchema = z.object({ 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") + name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"), + environments: z.object({ name: z.string(), slug: z.string() }).array() }); type TForm = z.infer; @@ -49,13 +51,13 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environment: string; + environments: WorkspaceEnv[]; }; export const AwsIamInputForm = ({ onCompleted, onCancel, - environment, + environments, secretPath, projectSlug }: Props) => { @@ -64,31 +66,43 @@ export const AwsIamInputForm = ({ formState: { isSubmitting }, handleSubmit } = useForm({ - resolver: zodResolver(formSchema) + resolver: zodResolver(formSchema), + defaultValues: { + environments: environments.length === 1 ? environments : [] + } }); const createDynamicSecret = useCreateDynamicSecret(); - const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { + const handleCreateDynamicSecret = async ({ + name, + maxTTL, + provider, + defaultTTL, + environments: selectedEnvs + }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.AwsIam, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + const promises = selectedEnvs.map(async (environment) => { + try { + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.AwsIam, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug + }); + onCompleted(); + } catch { + createNotification({ + type: "error", + text: "Failed to create dynamic secret" + }); + } + }); + await Promise.all(promises); }; return ( @@ -286,6 +300,30 @@ export const AwsIamInputForm = ({ )} /> + {environments.length > 1 && ( + ( + + option.name} + getOptionValue={(option) => option.slug} + menuPlacement="top" + /> + + )} + /> + )} diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx index 55cebe173..4ab20c064 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx @@ -12,7 +12,7 @@ import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; import { createNotification } from "@app/components/notifications"; -import { Button, FormControl, Input } from "@app/components/v2"; +import { Button, FilterableSelect, FormControl, Input } from "@app/components/v2"; import { DropdownMenu, DropdownMenuContent, @@ -23,6 +23,7 @@ import { Tooltip } from "@app/components/v2/Tooltip"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { useGetDynamicSecretProviderData } from "@app/hooks/api/dynamicSecret/queries"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; +import { WorkspaceEnv } from "@app/hooks/api/types"; const formSchema = z.object({ selectedUsers: z.array( @@ -60,7 +61,8 @@ const formSchema = z.object({ name: z .string() .min(1) - .refine((val) => val.toLowerCase() === val, "Must be lowercase") + .refine((val) => val.toLowerCase() === val, "Must be lowercase"), + environments: z.object({ name: z.string(), slug: z.string() }).array() }); type TForm = z.infer; @@ -69,13 +71,13 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environment: string; + environments: WorkspaceEnv[]; }; export const AzureEntraIdInputForm = ({ onCompleted, onCancel, - environment, + environments, secretPath, projectSlug }: Props) => { @@ -85,7 +87,10 @@ export const AzureEntraIdInputForm = ({ watch, handleSubmit } = useForm({ - resolver: zodResolver(formSchema) + resolver: zodResolver(formSchema), + defaultValues: { + environments: environments.length === 1 ? environments : [] + } }); const tenantId = watch("provider.tenantId"); const applicationId = watch("provider.applicationId"); @@ -107,38 +112,42 @@ export const AzureEntraIdInputForm = ({ selectedUsers, provider, maxTTL, - defaultTTL + defaultTTL, + environments: selectedEnvs }: TForm) => { // wait till previous request is finished - if (createDynamicSecret.isPending) return; - try { - selectedUsers.map(async (user: { id: string; name: string; email: string }) => { - await createDynamicSecret.mutateAsync({ - provider: { - type: DynamicSecretProviders.AzureEntraId, - inputs: { - userId: user.id, - tenantId: provider.tenantId, - email: user.email, - applicationId: provider.applicationId, - clientSecret: provider.clientSecret - } - }, - maxTTL, - name: `${name}-${user.name}`, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment + const promises = selectedEnvs.map(async (environment) => { + if (createDynamicSecret.isPending) return; + try { + selectedUsers.map(async (user: { id: string; name: string; email: string }) => { + await createDynamicSecret.mutateAsync({ + provider: { + type: DynamicSecretProviders.AzureEntraId, + inputs: { + userId: user.id, + tenantId: provider.tenantId, + email: user.email, + applicationId: provider.applicationId, + clientSecret: provider.clientSecret + } + }, + maxTTL, + name: `${name}-${user.name}`, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug + }); }); - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + onCompleted(); + } catch { + createNotification({ + type: "error", + text: "Failed to create dynamic secret" + }); + } + }); + await Promise.all(promises); }; return ( @@ -373,6 +382,30 @@ export const AzureEntraIdInputForm = ({ + {environments.length > 1 && ( + ( + + option.name} + getOptionValue={(option) => option.slug} + menuPlacement="top" + /> + + )} + /> + )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CreateDynamicSecretForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CreateDynamicSecretForm.tsx index c8389fb38..a49ab620e 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CreateDynamicSecretForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CreateDynamicSecretForm.tsx @@ -17,6 +17,7 @@ import { AnimatePresence, motion } from "framer-motion"; import { Modal, ModalContent } from "@app/components/v2"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; +import { WorkspaceEnv } from "@app/hooks/api/types"; import { AwsElastiCacheInputForm } from "./AwsElastiCacheInputForm"; import { AwsIamInputForm } from "./AwsIamInputForm"; @@ -38,7 +39,7 @@ type Props = { isOpen?: boolean; onToggle: (isOpen: boolean) => void; projectSlug: string; - environment: string; + environments: WorkspaceEnv[]; secretPath: string; }; @@ -129,7 +130,7 @@ export const CreateDynamicSecretForm = ({ isOpen, onToggle, projectSlug, - environment, + environments, secretPath }: Props) => { const [wizardStep, setWizardStep] = useState(WizardSteps.SelectProvider); @@ -197,7 +198,7 @@ export const CreateDynamicSecretForm = ({ onCancel={handleFormReset} projectSlug={projectSlug} secretPath={secretPath} - environment={environment} + environments={environments} /> )} @@ -215,7 +216,7 @@ export const CreateDynamicSecretForm = ({ onCancel={handleFormReset} projectSlug={projectSlug} secretPath={secretPath} - environment={environment} + environments={environments} /> )} @@ -233,7 +234,7 @@ export const CreateDynamicSecretForm = ({ onCancel={handleFormReset} projectSlug={projectSlug} secretPath={secretPath} - environment={environment} + environments={environments} /> )} @@ -251,7 +252,7 @@ export const CreateDynamicSecretForm = ({ onCancel={handleFormReset} projectSlug={projectSlug} secretPath={secretPath} - environment={environment} + environments={environments} /> )} @@ -269,7 +270,7 @@ export const CreateDynamicSecretForm = ({ onCancel={handleFormReset} projectSlug={projectSlug} secretPath={secretPath} - environment={environment} + environments={environments} /> )} @@ -287,7 +288,7 @@ export const CreateDynamicSecretForm = ({ onCancel={handleFormReset} projectSlug={projectSlug} secretPath={secretPath} - environment={environment} + environments={environments} /> )} @@ -305,7 +306,7 @@ export const CreateDynamicSecretForm = ({ onCancel={handleFormReset} projectSlug={projectSlug} secretPath={secretPath} - environment={environment} + environments={environments} /> )} @@ -323,7 +324,7 @@ export const CreateDynamicSecretForm = ({ onCancel={handleFormReset} projectSlug={projectSlug} secretPath={secretPath} - environment={environment} + environments={environments} /> )} @@ -341,7 +342,7 @@ export const CreateDynamicSecretForm = ({ onCancel={handleFormReset} projectSlug={projectSlug} secretPath={secretPath} - environment={environment} + environments={environments} /> )} @@ -359,7 +360,7 @@ export const CreateDynamicSecretForm = ({ onCancel={handleFormReset} projectSlug={projectSlug} secretPath={secretPath} - environment={environment} + environments={environments} /> )} @@ -377,7 +378,7 @@ export const CreateDynamicSecretForm = ({ onCancel={handleFormReset} projectSlug={projectSlug} secretPath={secretPath} - environment={environment} + environments={environments} /> )} @@ -395,7 +396,7 @@ export const CreateDynamicSecretForm = ({ onCancel={handleFormReset} projectSlug={projectSlug} secretPath={secretPath} - environment={environment} + environments={environments} /> )} @@ -413,7 +414,7 @@ export const CreateDynamicSecretForm = ({ onCancel={handleFormReset} projectSlug={projectSlug} secretPath={secretPath} - environment={environment} + environments={environments} /> )} @@ -432,7 +433,7 @@ export const CreateDynamicSecretForm = ({ onCancel={handleFormReset} projectSlug={projectSlug} secretPath={secretPath} - environment={environment} + environments={environments} /> )} @@ -450,7 +451,7 @@ export const CreateDynamicSecretForm = ({ onCancel={handleFormReset} projectSlug={projectSlug} secretPath={secretPath} - environment={environment} + environments={environments} /> )} diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx index eb0a7008f..828f98110 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx @@ -9,6 +9,7 @@ import { TtlFormLabel } from "@app/components/features"; import { createNotification } from "@app/components/notifications"; import { Button, + FilterableSelect, FormControl, FormLabel, IconButton, @@ -19,6 +20,7 @@ import { } from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; +import { WorkspaceEnv } from "@app/hooks/api/types"; const authMethods = [ { @@ -73,7 +75,8 @@ const formSchema = z.object({ 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") + name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"), + environments: z.object({ name: z.string(), slug: z.string() }).array() }); type TForm = z.infer; @@ -82,13 +85,13 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environment: string; + environments: WorkspaceEnv[]; }; export const ElasticSearchInputForm = ({ onCompleted, onCancel, - environment, + environments, secretPath, projectSlug }: Props) => { @@ -107,32 +110,42 @@ export const ElasticSearchInputForm = ({ }, roles: ["superuser"], port: 443 - } + }, + environments: environments.length === 1 ? environments : [] } }); const createDynamicSecret = useCreateDynamicSecret(); - const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { + const handleCreateDynamicSecret = async ({ + name, + maxTTL, + provider, + defaultTTL, + environments: selectedEnvs + }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.ElasticSearch, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + const promises = selectedEnvs.map(async (environment) => { + try { + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.ElasticSearch, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug + }); + onCompleted(); + } catch { + createNotification({ + type: "error", + text: "Failed to create dynamic secret" + }); + } + }); + await Promise.all(promises); }; const selectedAuthType = watch("provider.auth.type"); @@ -406,6 +419,30 @@ export const ElasticSearchInputForm = ({ )} /> + {environments.length > 1 && ( + ( + + option.name} + getOptionValue={(option) => option.slug} + menuPlacement="top" + /> + + )} + /> + )} diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx index ef74db5b8..361067d1b 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx @@ -7,9 +7,18 @@ import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; import { createNotification } from "@app/components/notifications"; -import { Button, FormControl, Input, Select, SelectItem, TextArea } from "@app/components/v2"; +import { + Button, + FilterableSelect, + FormControl, + Input, + Select, + SelectItem, + TextArea +} from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; +import { WorkspaceEnv } from "@app/hooks/api/types"; enum CredentialType { Dynamic = "dynamic", @@ -69,7 +78,8 @@ const formSchema = z.object({ 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") + name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"), + environments: z.object({ name: z.string(), slug: z.string() }).array() }); type TForm = z.infer; @@ -79,7 +89,7 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environment: string; + environments: WorkspaceEnv[]; }; export const LdapInputForm = ({ @@ -87,7 +97,7 @@ export const LdapInputForm = ({ onCancel, secretPath, projectSlug, - environment + environments }: Props) => { const { control, @@ -107,7 +117,8 @@ export const LdapInputForm = ({ revocationLdif: "", rollbackLdif: "", credentialType: CredentialType.Dynamic - } + }, + environments: environments.length === 1 ? environments : [] } }); @@ -115,26 +126,35 @@ export const LdapInputForm = ({ const createDynamicSecret = useCreateDynamicSecret(); - const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { + const handleCreateDynamicSecret = async ({ + name, + maxTTL, + provider, + defaultTTL, + environments: selectedEnvs + }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.Ldap, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + const promises = selectedEnvs.map(async (environment) => { + try { + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.Ldap, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug + }); + onCompleted(); + } catch { + createNotification({ + type: "error", + text: "Failed to create dynamic secret" + }); + } + }); + await Promise.all(promises); }; return ( @@ -369,6 +389,30 @@ export const LdapInputForm = ({ )} /> )} + {environments.length > 1 && ( + ( + + option.name} + getOptionValue={(option) => option.slug} + menuPlacement="top" + /> + + )} + /> + )} diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx index 4de9b8acc..a6725b652 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx @@ -13,6 +13,7 @@ import { AccordionItem, AccordionTrigger, Button, + FilterableSelect, FormControl, FormLabel, IconButton, @@ -22,6 +23,7 @@ import { } from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; +import { WorkspaceEnv } from "@app/hooks/api/types"; const formSchema = z.object({ provider: z.object({ @@ -63,7 +65,8 @@ const formSchema = z.object({ 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") + name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"), + environments: z.object({ name: z.string(), slug: z.string() }).array() }); type TForm = z.infer; @@ -72,7 +75,7 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environment: string; + environments: WorkspaceEnv[]; }; const ATLAS_SCOPE_TYPES = [ @@ -93,7 +96,7 @@ const ATLAS_SCOPE_TYPES = [ export const MongoAtlasInputForm = ({ onCompleted, onCancel, - environment, + environments, secretPath, projectSlug }: Props) => { @@ -108,7 +111,8 @@ export const MongoAtlasInputForm = ({ defaultValues: { provider: { roles: [{ databaseName: "", roleName: "" }] - } + }, + environments: environments.length === 1 ? environments : [] } }); @@ -124,26 +128,35 @@ export const MongoAtlasInputForm = ({ const createDynamicSecret = useCreateDynamicSecret(); - const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { + const handleCreateDynamicSecret = async ({ + name, + maxTTL, + provider, + defaultTTL, + environments: selectedEnvs + }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.MongoAtlas, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + const promises = selectedEnvs.map(async (environment) => { + try { + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.MongoAtlas, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug + }); + onCompleted(); + } catch { + createNotification({ + type: "error", + text: "Failed to create dynamic secret" + }); + } + }); + await Promise.all(promises); }; return ( @@ -438,6 +451,30 @@ export const MongoAtlasInputForm = ({ + {environments.length > 1 && ( + ( + + option.name} + getOptionValue={(option) => option.slug} + menuPlacement="top" + /> + + )} + /> + )} diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx index b2418f629..0a28a6913 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx @@ -7,9 +7,18 @@ import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; import { createNotification } from "@app/components/notifications"; -import { Button, FormControl, FormLabel, IconButton, Input, SecretInput } from "@app/components/v2"; +import { + Button, + FilterableSelect, + FormControl, + FormLabel, + IconButton, + Input, + SecretInput +} from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; +import { WorkspaceEnv } from "@app/hooks/api/types"; const formSchema = z.object({ provider: z.object({ @@ -46,7 +55,8 @@ const formSchema = z.object({ 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") + name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"), + environments: z.object({ name: z.string(), slug: z.string() }).array() }); type TForm = z.infer; @@ -55,13 +65,13 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environment: string; + environments: WorkspaceEnv[]; }; export const MongoDBDatabaseInputForm = ({ onCompleted, onCancel, - environment, + environments, secretPath, projectSlug }: Props) => { @@ -76,7 +86,8 @@ export const MongoDBDatabaseInputForm = ({ defaultValues: { provider: { roles: [{ roleName: "readWrite" }] - } + }, + environments: environments.length === 1 ? environments : [] } }); @@ -87,33 +98,42 @@ export const MongoDBDatabaseInputForm = ({ const createDynamicSecret = useCreateDynamicSecret(); - const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { + const handleCreateDynamicSecret = async ({ + name, + maxTTL, + provider, + defaultTTL, + environments: selectedEnvs + }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - await createDynamicSecret.mutateAsync({ - provider: { - type: DynamicSecretProviders.MongoDB, - inputs: { - ...provider, - port: provider?.port ? provider.port : undefined, - roles: provider.roles.map((el) => el.roleName) - } - }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + const promises = selectedEnvs.map(async (environment) => { + try { + await createDynamicSecret.mutateAsync({ + provider: { + type: DynamicSecretProviders.MongoDB, + inputs: { + ...provider, + port: provider?.port ? provider.port : undefined, + roles: provider.roles.map((el) => el.roleName) + } + }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug + }); + onCompleted(); + } catch { + createNotification({ + type: "error", + text: "Failed to create dynamic secret" + }); + } + }); + await Promise.all(promises); }; return ( @@ -321,6 +341,30 @@ export const MongoDBDatabaseInputForm = ({ )} /> + {environments.length > 1 && ( + ( + + option.name} + getOptionValue={(option) => option.slug} + menuPlacement="top" + /> + + )} + /> + )} diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx index 21f8b40de..0a6b84d41 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx @@ -7,9 +7,18 @@ import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; import { createNotification } from "@app/components/notifications"; -import { Button, FormControl, FormLabel, IconButton, Input, SecretInput } from "@app/components/v2"; +import { + Button, + FilterableSelect, + FormControl, + FormLabel, + IconButton, + Input, + SecretInput +} from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; +import { WorkspaceEnv } from "@app/hooks/api/types"; const formSchema = z.object({ provider: z.object({ @@ -50,7 +59,8 @@ const formSchema = z.object({ 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") + name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"), + environments: z.object({ name: z.string(), slug: z.string() }).array() }); type TForm = z.infer; @@ -59,13 +69,13 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environment: string; + environments: WorkspaceEnv[]; }; export const RabbitMqInputForm = ({ onCompleted, onCancel, - environment, + environments, secretPath, projectSlug }: Props) => { @@ -89,32 +99,42 @@ export const RabbitMqInputForm = ({ } }, tags: [] - } + }, + environments: environments.length === 1 ? environments : [] } }); const createDynamicSecret = useCreateDynamicSecret(); - const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { + const handleCreateDynamicSecret = async ({ + name, + maxTTL, + provider, + defaultTTL, + environments: selectedEnvs + }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.RabbitMq, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + const promises = selectedEnvs.map(async (environment) => { + try { + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.RabbitMq, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug + }); + onCompleted(); + } catch { + createNotification({ + type: "error", + text: "Failed to create dynamic secret" + }); + } + }); + await Promise.all(promises); }; const selectedTags = watch("provider.tags"); @@ -405,6 +425,30 @@ export const RabbitMqInputForm = ({ )} /> + {environments.length > 1 && ( + ( + + option.name} + getOptionValue={(option) => option.slug} + menuPlacement="top" + /> + + )} + /> + )} diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx index 9d9933b44..35f165056 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx @@ -11,6 +11,7 @@ import { AccordionItem, AccordionTrigger, Button, + FilterableSelect, FormControl, Input, SecretInput, @@ -18,6 +19,7 @@ import { } from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; +import { WorkspaceEnv } from "@app/hooks/api/types"; const formSchema = z.object({ provider: z.object({ @@ -50,7 +52,8 @@ const formSchema = z.object({ 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") + name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"), + environments: z.object({ name: z.string(), slug: z.string() }).array() }); type TForm = z.infer; @@ -59,13 +62,13 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environment: string; + environments: WorkspaceEnv[]; }; export const RedisInputForm = ({ onCompleted, onCancel, - environment, + environments, secretPath, projectSlug }: Props) => { @@ -81,32 +84,42 @@ export const RedisInputForm = ({ port: 6379, creationStatement: "ACL SETUSER {{username}} on >{{password}} ~* &* +@all", revocationStatement: "ACL DELUSER {{username}}" - } + }, + environments: environments.length === 1 ? environments : [] } }); const createDynamicSecret = useCreateDynamicSecret(); - const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { + const handleCreateDynamicSecret = async ({ + name, + maxTTL, + provider, + defaultTTL, + environments: selectedEnvs + }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.Redis, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + const promises = selectedEnvs.map(async (environment) => { + try { + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.Redis, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug + }); + onCompleted(); + } catch { + createNotification({ + type: "error", + text: "Failed to create dynamic secret" + }); + } + }); + await Promise.all(promises); }; return ( @@ -313,6 +326,30 @@ export const RedisInputForm = ({ + {environments.length > 1 && ( + ( + + option.name} + getOptionValue={(option) => option.slug} + menuPlacement="top" + /> + + )} + /> + )} diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx index efe6e0269..de32565da 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx @@ -11,12 +11,14 @@ import { AccordionItem, AccordionTrigger, Button, + FilterableSelect, FormControl, Input, TextArea } from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; +import { WorkspaceEnv } from "@app/hooks/api/types"; const formSchema = z.object({ provider: z.object({ @@ -48,7 +50,8 @@ const formSchema = z.object({ 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") + name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"), + environments: z.object({ name: z.string(), slug: z.string() }).array() }); type TForm = z.infer; @@ -57,13 +60,13 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environment: string; + environments: WorkspaceEnv[]; }; export const SapAseInputForm = ({ onCompleted, onCancel, - environment, + environments, secretPath, projectSlug }: Props) => { @@ -82,32 +85,42 @@ sp_adduser '{{username}}', '{{username}}', null; sp_role 'grant', 'mon_role', '{{username}}';`, revocationStatement: `sp_dropuser '{{username}}'; sp_droplogin '{{username}}';` - } + }, + environments: environments.length === 1 ? environments : [] } }); const createDynamicSecret = useCreateDynamicSecret(); - const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { + const handleCreateDynamicSecret = async ({ + name, + maxTTL, + provider, + defaultTTL, + environments: selectedEnvs + }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.SapAse, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + const promises = selectedEnvs.map(async (environment) => { + try { + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.SapAse, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug + }); + onCompleted(); + } catch { + createNotification({ + type: "error", + text: "Failed to create dynamic secret" + }); + } + }); + await Promise.all(promises); }; return ( @@ -291,6 +304,30 @@ sp_droplogin '{{username}}';` + {environments.length > 1 && ( + ( + + option.name} + getOptionValue={(option) => option.slug} + menuPlacement="top" + /> + + )} + /> + )} diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx index 1b0bd64b8..d31464665 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx @@ -11,6 +11,7 @@ import { AccordionItem, AccordionTrigger, Button, + FilterableSelect, FormControl, Input, SecretInput, @@ -18,6 +19,7 @@ import { } from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; +import { WorkspaceEnv } from "@app/hooks/api/types"; const formSchema = z.object({ provider: z.object({ @@ -50,7 +52,8 @@ const formSchema = z.object({ 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") + name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"), + environments: z.object({ name: z.string(), slug: z.string() }).array() }); type TForm = z.infer; @@ -59,13 +62,13 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environment: string; + environments: WorkspaceEnv[]; }; export const SapHanaInputForm = ({ onCompleted, onCancel, - environment, + environments, secretPath, projectSlug }: Props) => { @@ -82,32 +85,42 @@ GRANT "MONITORING" TO {{username}};`, revocationStatement: `REVOKE "MONITORING" FROM {{username}}; DROP USER {{username}};`, renewStatement: "ALTER USER {{username}} VALID UNTIL '{{expiration}}';" - } + }, + environments: environments.length === 1 ? environments : [] } }); const createDynamicSecret = useCreateDynamicSecret(); - const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { + const handleCreateDynamicSecret = async ({ + name, + maxTTL, + provider, + defaultTTL, + environments: selectedEnvs + }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.SapHana, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + const promises = selectedEnvs.map(async (environment) => { + try { + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.SapHana, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug + }); + onCompleted(); + } catch { + createNotification({ + type: "error", + text: "Failed to create dynamic secret" + }); + } + }); + await Promise.all(promises); }; return ( @@ -311,6 +324,30 @@ DROP USER {{username}};`, + {environments.length > 1 && ( + ( + + option.name} + getOptionValue={(option) => option.slug} + menuPlacement="top" + /> + + )} + /> + )} diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx index a5f8b25d9..c5077f2ad 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx @@ -13,12 +13,14 @@ import { AccordionItem, AccordionTrigger, Button, + FilterableSelect, FormControl, Input, TextArea } from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; +import { WorkspaceEnv } from "@app/hooks/api/types"; const formSchema = z.object({ provider: z.object({ @@ -54,7 +56,8 @@ const formSchema = z.object({ .string() .trim() .min(1) - .refine((val) => val.toLowerCase() === val, "Must be lowercase") + .refine((val) => val.toLowerCase() === val, "Must be lowercase"), + environments: z.object({ name: z.string(), slug: z.string() }).array() }); type TForm = z.infer; @@ -63,13 +66,13 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environment: string; + environments: WorkspaceEnv[]; }; export const SnowflakeInputForm = ({ onCompleted, onCancel, - environment, + environments, secretPath, projectSlug }: Props) => { @@ -85,32 +88,42 @@ export const SnowflakeInputForm = ({ "CREATE USER {{username}} PASSWORD = '{{password}}' DEFAULT_ROLE = public DEFAULT_SECONDARY_ROLES = ('ALL') MUST_CHANGE_PASSWORD = FALSE DAYS_TO_EXPIRY = {{expiration}};", revocationStatement: "DROP USER {{username}};", renewStatement: "ALTER USER {{username}} SET DAYS_TO_EXPIRY = {{expiration}};" - } + }, + environments: environments.length === 1 ? environments : [] } }); const createDynamicSecret = useCreateDynamicSecret(); - const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { + const handleCreateDynamicSecret = async ({ + name, + maxTTL, + provider, + defaultTTL, + environments: selectedEnvs + }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.Snowflake, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment - }); - onCompleted(); - } catch (err) { - createNotification({ - type: "error", - text: err instanceof Error ? err.message : "Failed to create dynamic secret" - }); - } + const promises = selectedEnvs.map(async (environment) => { + try { + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.Snowflake, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug + }); + onCompleted(); + } catch (err) { + createNotification({ + type: "error", + text: err instanceof Error ? err.message : "Failed to create dynamic secret" + }); + } + }); + await Promise.all(promises); }; return ( @@ -314,6 +327,30 @@ export const SnowflakeInputForm = ({ + {environments.length > 1 && ( + ( + + option.name} + getOptionValue={(option) => option.slug} + menuPlacement="top" + /> + + )} + /> + )} diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx index 4af92b210..416d9c35b 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx @@ -12,6 +12,7 @@ import { AccordionItem, AccordionTrigger, Button, + FilterableSelect, FormControl, Input, SecretInput, @@ -22,6 +23,7 @@ import { import { useWorkspace } from "@app/context"; import { gatewaysQueryKeys, useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders, SqlProviders } from "@app/hooks/api/dynamicSecret/types"; +import { WorkspaceEnv } from "@app/hooks/api/types"; const passwordRequirementsSchema = z .object({ @@ -79,7 +81,8 @@ const formSchema = z.object({ 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") + name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"), + environments: z.object({ name: z.string(), slug: z.string() }).array() }); type TForm = z.infer; @@ -88,7 +91,7 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environment: string; + environments: WorkspaceEnv[]; }; const getSqlStatements = (provider: SqlProviders) => { @@ -145,7 +148,7 @@ const getDefaultPort = (provider: SqlProviders) => { export const SqlDatabaseInputForm = ({ onCompleted, onCancel, - environment, + environments, secretPath, projectSlug }: Props) => { @@ -172,7 +175,8 @@ export const SqlDatabaseInputForm = ({ }, allowedSymbols: "-_.~!*" } - } + }, + environments: environments.length === 1 ? environments : [] } }); @@ -181,26 +185,35 @@ export const SqlDatabaseInputForm = ({ gatewaysQueryKeys.listProjectGateways({ projectId: currentWorkspace.id }) ); - const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { + const handleCreateDynamicSecret = async ({ + name, + maxTTL, + provider, + defaultTTL, + environments: selectedEnvs + }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.SqlDatabase, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + const promises = selectedEnvs.map(async (env) => { + try { + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.SqlDatabase, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: env.slug + }); + onCompleted(); + } catch { + createNotification({ + type: "error", + text: "Failed to create dynamic secret" + }); + } + }); + await Promise.all(promises); }; const handleDatabaseChange = (type: SqlProviders) => { @@ -649,6 +662,30 @@ export const SqlDatabaseInputForm = ({ + {environments.length > 1 && ( + ( + + option.name} + getOptionValue={(option) => option.slug} + menuPlacement="top" + /> + + )} + /> + )} diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx index 01d5398f0..4ff83a741 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx @@ -5,9 +5,17 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { createNotification } from "@app/components/notifications"; -import { Button, FormControl, Input, Select, SelectItem } from "@app/components/v2"; +import { + Button, + FilterableSelect, + FormControl, + Input, + Select, + SelectItem +} from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; +import { WorkspaceEnv } from "@app/hooks/api/types"; enum ConfigType { URL = "url", @@ -52,7 +60,8 @@ const formSchema = z.object({ .string() .trim() .min(1) - .refine((val) => val.toLowerCase() === val, "Must be lowercase") + .refine((val) => val.toLowerCase() === val, "Must be lowercase"), + environments: z.object({ name: z.string(), slug: z.string() }).array() }); type TForm = z.infer; @@ -62,13 +71,13 @@ type Props = { onCancel: () => void; secretPath: string; projectSlug: string; - environment: string; + environments: WorkspaceEnv[]; }; export const TotpInputForm = ({ onCompleted, onCancel, - environment, + environments, secretPath, projectSlug }: Props) => { @@ -82,7 +91,8 @@ export const TotpInputForm = ({ defaultValues: { provider: { configType: ConfigType.URL - } + }, + environments: environments.length === 1 ? environments : [] } }); @@ -90,26 +100,33 @@ export const TotpInputForm = ({ const createDynamicSecret = useCreateDynamicSecret(); - const handleCreateDynamicSecret = async ({ name, provider }: TForm) => { + const handleCreateDynamicSecret = async ({ + name, + provider, + environments: selectedEnvs + }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.Totp, inputs: provider }, - maxTTL: "24h", - name, - path: secretPath, - defaultTTL: "1m", - projectSlug, - environmentSlug: environment - }); - onCompleted(); - } catch (err) { - createNotification({ - type: "error", - text: err instanceof Error ? err.message : "Failed to create dynamic secret" - }); - } + const promises = selectedEnvs.map(async (environment) => { + try { + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.Totp, inputs: provider }, + maxTTL: "24h", + name, + path: secretPath, + defaultTTL: "1m", + projectSlug, + environmentSlug: environment.slug + }); + onCompleted(); + } catch (err) { + createNotification({ + type: "error", + text: err instanceof Error ? err.message : "Failed to create dynamic secret" + }); + } + }); + await Promise.all(promises); }; return ( @@ -295,6 +312,30 @@ export const TotpInputForm = ({

)} + {environments.length > 1 && ( + ( + + option.name} + getOptionValue={(option) => option.slug} + menuPlacement="top" + /> + + )} + /> + )} From e39d1a053081e98b5eaeede8abb40d517de9f5b9 Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Fri, 14 Mar 2025 12:26:20 -0300 Subject: [PATCH 2/5] Fix call of onCompleted after all promises are resolved --- .../secret-manager/OverviewPage/OverviewPage.tsx | 2 +- .../components/ActionBar/ActionBar.tsx | 2 +- .../AwsElastiCacheInputForm.tsx | 8 ++++++-- .../CreateDynamicSecretForm/AwsIamInputForm.tsx | 12 ++++++++---- .../AzureEntraIdInputForm.tsx | 14 +++++++++----- .../CreateDynamicSecretForm/CassandraInputForm.tsx | 12 ++++++++---- .../ElasticSearchInputForm.tsx | 12 ++++++++---- .../CreateDynamicSecretForm/LdapInputForm.tsx | 12 ++++++++---- .../MongoAtlasInputForm.tsx | 12 ++++++++---- .../CreateDynamicSecretForm/MongoDBInputForm.tsx | 12 ++++++++---- .../CreateDynamicSecretForm/RabbitMqInputForm.tsx | 12 ++++++++---- .../CreateDynamicSecretForm/RedisInputForm.tsx | 12 ++++++++---- .../CreateDynamicSecretForm/SapAseInputForm.tsx | 12 ++++++++---- .../CreateDynamicSecretForm/SapHanaInputForm.tsx | 12 ++++++++---- .../CreateDynamicSecretForm/SnowflakeInputForm.tsx | 10 +++++++--- .../SqlDatabaseInputForm.tsx | 8 ++++++-- .../CreateDynamicSecretForm/TotpInputForm.tsx | 10 +++++++--- 17 files changed, 117 insertions(+), 57 deletions(-) diff --git a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx index a611eb030..12e0100b6 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx @@ -866,7 +866,7 @@ export const OverviewPage = () => { {(isAllowed) => ( )} + + +
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx index 0cf131ba8..681ad99e3 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx @@ -657,6 +657,7 @@ export const ActionBar = ({ projectSlug={projectSlug} environments={[{ slug: environment, name: environment, id: "not-used" }]} secretPath={secretPath} + isSingleEnvironmentMode /> { const { control, @@ -91,7 +93,7 @@ export const AwsElastiCacheInputForm = ({ "UserId": "{{username}}" }` }, - environment: environments.length === 1 ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined } }); @@ -309,7 +311,7 @@ export const AwsElastiCacheInputForm = ({ - {environments.length > 1 && ( + {!isSingleEnvironmentMode && ( { const { control, @@ -68,7 +70,7 @@ export const AwsIamInputForm = ({ } = useForm({ resolver: zodResolver(formSchema), defaultValues: { - environment: environments.length === 1 ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined } }); @@ -298,7 +300,7 @@ export const AwsIamInputForm = ({ )} /> - {environments.length > 1 && ( + {!isSingleEnvironmentMode && ( { const { control, @@ -89,7 +91,7 @@ export const AzureEntraIdInputForm = ({ } = useForm({ resolver: zodResolver(formSchema), defaultValues: { - environment: environments.length === 1 ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined } }); const tenantId = watch("provider.tenantId"); @@ -379,7 +381,7 @@ export const AzureEntraIdInputForm = ({ - {environments.length > 1 && ( + {!isSingleEnvironmentMode && ( { @@ -81,7 +82,8 @@ export const CassandraInputForm = ({ onCancel, environments, secretPath, - projectSlug + projectSlug, + isSingleEnvironmentMode }: Props) => { const { control, @@ -91,7 +93,7 @@ export const CassandraInputForm = ({ resolver: zodResolver(formSchema), defaultValues: { provider: getSqlStatements(), - environment: environments.length === 1 ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined } }); @@ -356,7 +358,7 @@ export const CassandraInputForm = ({ - {environments.length > 1 && ( + {!isSingleEnvironmentMode && ( { const [wizardStep, setWizardStep] = useState(WizardSteps.SelectProvider); const [selectedProvider, setSelectedProvider] = useState(null); @@ -199,6 +201,7 @@ export const CreateDynamicSecretForm = ({ projectSlug={projectSlug} secretPath={secretPath} environments={environments} + isSingleEnvironmentMode={isSingleEnvironmentMode} /> )} @@ -217,6 +220,7 @@ export const CreateDynamicSecretForm = ({ projectSlug={projectSlug} secretPath={secretPath} environments={environments} + isSingleEnvironmentMode={isSingleEnvironmentMode} /> )} @@ -235,6 +239,7 @@ export const CreateDynamicSecretForm = ({ projectSlug={projectSlug} secretPath={secretPath} environments={environments} + isSingleEnvironmentMode={isSingleEnvironmentMode} /> )} @@ -253,6 +258,7 @@ export const CreateDynamicSecretForm = ({ projectSlug={projectSlug} secretPath={secretPath} environments={environments} + isSingleEnvironmentMode={isSingleEnvironmentMode} /> )} @@ -271,6 +277,7 @@ export const CreateDynamicSecretForm = ({ projectSlug={projectSlug} secretPath={secretPath} environments={environments} + isSingleEnvironmentMode={isSingleEnvironmentMode} /> )} @@ -289,6 +296,7 @@ export const CreateDynamicSecretForm = ({ projectSlug={projectSlug} secretPath={secretPath} environments={environments} + isSingleEnvironmentMode={isSingleEnvironmentMode} /> )} @@ -307,6 +315,7 @@ export const CreateDynamicSecretForm = ({ projectSlug={projectSlug} secretPath={secretPath} environments={environments} + isSingleEnvironmentMode={isSingleEnvironmentMode} /> )} @@ -325,6 +334,7 @@ export const CreateDynamicSecretForm = ({ projectSlug={projectSlug} secretPath={secretPath} environments={environments} + isSingleEnvironmentMode={isSingleEnvironmentMode} /> )} @@ -343,6 +353,7 @@ export const CreateDynamicSecretForm = ({ projectSlug={projectSlug} secretPath={secretPath} environments={environments} + isSingleEnvironmentMode={isSingleEnvironmentMode} /> )} @@ -361,6 +372,7 @@ export const CreateDynamicSecretForm = ({ projectSlug={projectSlug} secretPath={secretPath} environments={environments} + isSingleEnvironmentMode={isSingleEnvironmentMode} /> )} @@ -379,6 +391,7 @@ export const CreateDynamicSecretForm = ({ projectSlug={projectSlug} secretPath={secretPath} environments={environments} + isSingleEnvironmentMode={isSingleEnvironmentMode} /> )} @@ -397,6 +410,7 @@ export const CreateDynamicSecretForm = ({ projectSlug={projectSlug} secretPath={secretPath} environments={environments} + isSingleEnvironmentMode={isSingleEnvironmentMode} /> )} @@ -415,6 +429,7 @@ export const CreateDynamicSecretForm = ({ projectSlug={projectSlug} secretPath={secretPath} environments={environments} + isSingleEnvironmentMode={isSingleEnvironmentMode} /> )} @@ -434,6 +449,7 @@ export const CreateDynamicSecretForm = ({ projectSlug={projectSlug} secretPath={secretPath} environments={environments} + isSingleEnvironmentMode={isSingleEnvironmentMode} /> )} @@ -452,6 +468,7 @@ export const CreateDynamicSecretForm = ({ projectSlug={projectSlug} secretPath={secretPath} environments={environments} + isSingleEnvironmentMode={isSingleEnvironmentMode} /> )} diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx index eea23a2e5..a7c84cb50 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx @@ -86,6 +86,7 @@ type Props = { secretPath: string; projectSlug: string; environments: WorkspaceEnv[]; + isSingleEnvironmentMode?: boolean; }; export const ElasticSearchInputForm = ({ @@ -93,7 +94,8 @@ export const ElasticSearchInputForm = ({ onCancel, environments, secretPath, - projectSlug + projectSlug, + isSingleEnvironmentMode }: Props) => { const { control, @@ -111,7 +113,7 @@ export const ElasticSearchInputForm = ({ roles: ["superuser"], port: 443 }, - environment: environments.length === 1 ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined } }); @@ -416,7 +418,7 @@ export const ElasticSearchInputForm = ({ )} /> - {environments.length > 1 && ( + {!isSingleEnvironmentMode && ( { const { control, @@ -118,7 +120,7 @@ export const LdapInputForm = ({ rollbackLdif: "", credentialType: CredentialType.Dynamic }, - environment: environments.length === 1 ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined } }); @@ -386,7 +388,7 @@ export const LdapInputForm = ({ )} /> )} - {environments.length > 1 && ( + {!isSingleEnvironmentMode && ( { const { control, @@ -112,7 +114,7 @@ export const MongoAtlasInputForm = ({ provider: { roles: [{ databaseName: "", roleName: "" }] }, - environment: environments.length === 1 ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined } }); @@ -448,7 +450,7 @@ export const MongoAtlasInputForm = ({ - {environments.length > 1 && ( + {!isSingleEnvironmentMode && ( { const { control, @@ -87,7 +89,7 @@ export const MongoDBDatabaseInputForm = ({ provider: { roles: [{ roleName: "readWrite" }] }, - environment: environments.length === 1 ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined } }); @@ -338,7 +340,7 @@ export const MongoDBDatabaseInputForm = ({ )} /> - {environments.length > 1 && ( + {!isSingleEnvironmentMode && ( { const { control, @@ -100,7 +102,7 @@ export const RabbitMqInputForm = ({ }, tags: [] }, - environment: environments.length === 1 ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined } }); @@ -422,7 +424,7 @@ export const RabbitMqInputForm = ({ )} /> - {environments.length > 1 && ( + {!isSingleEnvironmentMode && ( { const { control, @@ -85,7 +87,7 @@ export const RedisInputForm = ({ creationStatement: "ACL SETUSER {{username}} on >{{password}} ~* &* +@all", revocationStatement: "ACL DELUSER {{username}}" }, - environment: environments.length === 1 ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined } }); @@ -323,7 +325,7 @@ export const RedisInputForm = ({ - {environments.length > 1 && ( + {!isSingleEnvironmentMode && ( { const { control, @@ -86,7 +88,7 @@ sp_role 'grant', 'mon_role', '{{username}}';`, revocationStatement: `sp_dropuser '{{username}}'; sp_droplogin '{{username}}';` }, - environment: environments.length === 1 ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined } }); @@ -301,7 +303,7 @@ sp_droplogin '{{username}}';` - {environments.length > 1 && ( + {!isSingleEnvironmentMode && ( { const { control, @@ -86,7 +88,7 @@ GRANT "MONITORING" TO {{username}};`, DROP USER {{username}};`, renewStatement: "ALTER USER {{username}} VALID UNTIL '{{expiration}}';" }, - environment: environments.length === 1 ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined } }); @@ -321,7 +323,7 @@ DROP USER {{username}};`, - {environments.length > 1 && ( + {!isSingleEnvironmentMode && ( { const { control, @@ -89,7 +91,7 @@ export const SnowflakeInputForm = ({ revocationStatement: "DROP USER {{username}};", renewStatement: "ALTER USER {{username}} SET DAYS_TO_EXPIRY = {{expiration}};" }, - environment: environments.length === 1 ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined } }); @@ -324,7 +326,7 @@ export const SnowflakeInputForm = ({ - {environments.length > 1 && ( + {!isSingleEnvironmentMode && ( { @@ -150,7 +151,8 @@ export const SqlDatabaseInputForm = ({ onCancel, environments, secretPath, - projectSlug + projectSlug, + isSingleEnvironmentMode }: Props) => { const { currentWorkspace } = useWorkspace(); @@ -176,7 +178,7 @@ export const SqlDatabaseInputForm = ({ allowedSymbols: "-_.~!*" } }, - environment: environments.length === 1 ? environments[0] : undefined + environment: isSingleEnvironmentMode ? environments[0] : undefined } }); @@ -660,7 +662,7 @@ export const SqlDatabaseInputForm = ({ - {environments.length > 1 && ( + {!isSingleEnvironmentMode && ( { const { control, @@ -92,7 +94,7 @@ export const TotpInputForm = ({ provider: { configType: ConfigType.URL }, - environment: environments.length === 1 ? environments[0] : environments[0] + environment: isSingleEnvironmentMode ? environments[0] : undefined } }); @@ -305,7 +307,7 @@ export const TotpInputForm = ({

)} - {environments.length > 1 && ( + {!isSingleEnvironmentMode && (