mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-17 12:56:29 +00:00
Add dynamic secrets modal form on secrets overview page
This commit is contained in:
+1
-1
@@ -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}
|
||||
/>
|
||||
<Modal
|
||||
|
||||
+58
-22
@@ -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<typeof formSchema>;
|
||||
|
||||
@@ -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 = ({
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
{environments.length > 1 && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="environments"
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Environments"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<FilterableSelect
|
||||
isMulti
|
||||
options={environments}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder="Select environments to create secret in..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.slug}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+61
-23
@@ -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<typeof formSchema>;
|
||||
|
||||
@@ -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<TForm>({
|
||||
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 = ({
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{environments.length > 1 && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="environments"
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Environments"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<FilterableSelect
|
||||
isMulti
|
||||
options={environments}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder="Select environments to create secret in..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.slug}
|
||||
menuPlacement="top"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+67
-34
@@ -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<typeof formSchema>;
|
||||
|
||||
@@ -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<TForm>({
|
||||
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 = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{environments.length > 1 && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="environments"
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Environments"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<FilterableSelect
|
||||
isMulti
|
||||
options={environments}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder="Select environments to create secret in..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.slug}
|
||||
menuPlacement="top"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-4 flex items-center space-x-4">
|
||||
<Button type="submit" isLoading={isSubmitting} isDisabled={isLoading || isError}>
|
||||
|
||||
+59
-22
@@ -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({
|
||||
@@ -52,7 +54,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<typeof formSchema>;
|
||||
|
||||
@@ -61,7 +64,7 @@ type Props = {
|
||||
onCancel: () => void;
|
||||
secretPath: string;
|
||||
projectSlug: string;
|
||||
environment: string;
|
||||
environments: WorkspaceEnv[];
|
||||
};
|
||||
|
||||
const getSqlStatements = () => {
|
||||
@@ -76,7 +79,7 @@ const getSqlStatements = () => {
|
||||
export const CassandraInputForm = ({
|
||||
onCompleted,
|
||||
onCancel,
|
||||
environment,
|
||||
environments,
|
||||
secretPath,
|
||||
projectSlug
|
||||
}: Props) => {
|
||||
@@ -87,32 +90,42 @@ export const CassandraInputForm = ({
|
||||
} = useForm<TForm>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
provider: getSqlStatements()
|
||||
provider: getSqlStatements(),
|
||||
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.Cassandra, 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.Cassandra, 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 (
|
||||
@@ -345,6 +358,30 @@ export const CassandraInputForm = ({
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
{environments.length > 1 && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="environments"
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Environments"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<FilterableSelect
|
||||
isMulti
|
||||
options={environments}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder="Select environments to create secret in..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.slug}
|
||||
menuPlacement="top"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+18
-17
@@ -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}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
@@ -215,7 +216,7 @@ export const CreateDynamicSecretForm = ({
|
||||
onCancel={handleFormReset}
|
||||
projectSlug={projectSlug}
|
||||
secretPath={secretPath}
|
||||
environment={environment}
|
||||
environments={environments}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
@@ -233,7 +234,7 @@ export const CreateDynamicSecretForm = ({
|
||||
onCancel={handleFormReset}
|
||||
projectSlug={projectSlug}
|
||||
secretPath={secretPath}
|
||||
environment={environment}
|
||||
environments={environments}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
@@ -251,7 +252,7 @@ export const CreateDynamicSecretForm = ({
|
||||
onCancel={handleFormReset}
|
||||
projectSlug={projectSlug}
|
||||
secretPath={secretPath}
|
||||
environment={environment}
|
||||
environments={environments}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
@@ -269,7 +270,7 @@ export const CreateDynamicSecretForm = ({
|
||||
onCancel={handleFormReset}
|
||||
projectSlug={projectSlug}
|
||||
secretPath={secretPath}
|
||||
environment={environment}
|
||||
environments={environments}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
@@ -287,7 +288,7 @@ export const CreateDynamicSecretForm = ({
|
||||
onCancel={handleFormReset}
|
||||
projectSlug={projectSlug}
|
||||
secretPath={secretPath}
|
||||
environment={environment}
|
||||
environments={environments}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
@@ -305,7 +306,7 @@ export const CreateDynamicSecretForm = ({
|
||||
onCancel={handleFormReset}
|
||||
projectSlug={projectSlug}
|
||||
secretPath={secretPath}
|
||||
environment={environment}
|
||||
environments={environments}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
@@ -323,7 +324,7 @@ export const CreateDynamicSecretForm = ({
|
||||
onCancel={handleFormReset}
|
||||
projectSlug={projectSlug}
|
||||
secretPath={secretPath}
|
||||
environment={environment}
|
||||
environments={environments}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
@@ -341,7 +342,7 @@ export const CreateDynamicSecretForm = ({
|
||||
onCancel={handleFormReset}
|
||||
projectSlug={projectSlug}
|
||||
secretPath={secretPath}
|
||||
environment={environment}
|
||||
environments={environments}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
@@ -359,7 +360,7 @@ export const CreateDynamicSecretForm = ({
|
||||
onCancel={handleFormReset}
|
||||
projectSlug={projectSlug}
|
||||
secretPath={secretPath}
|
||||
environment={environment}
|
||||
environments={environments}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
@@ -377,7 +378,7 @@ export const CreateDynamicSecretForm = ({
|
||||
onCancel={handleFormReset}
|
||||
projectSlug={projectSlug}
|
||||
secretPath={secretPath}
|
||||
environment={environment}
|
||||
environments={environments}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
@@ -395,7 +396,7 @@ export const CreateDynamicSecretForm = ({
|
||||
onCancel={handleFormReset}
|
||||
projectSlug={projectSlug}
|
||||
secretPath={secretPath}
|
||||
environment={environment}
|
||||
environments={environments}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
@@ -413,7 +414,7 @@ export const CreateDynamicSecretForm = ({
|
||||
onCancel={handleFormReset}
|
||||
projectSlug={projectSlug}
|
||||
secretPath={secretPath}
|
||||
environment={environment}
|
||||
environments={environments}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
@@ -432,7 +433,7 @@ export const CreateDynamicSecretForm = ({
|
||||
onCancel={handleFormReset}
|
||||
projectSlug={projectSlug}
|
||||
secretPath={secretPath}
|
||||
environment={environment}
|
||||
environments={environments}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
@@ -450,7 +451,7 @@ export const CreateDynamicSecretForm = ({
|
||||
onCancel={handleFormReset}
|
||||
projectSlug={projectSlug}
|
||||
secretPath={secretPath}
|
||||
environment={environment}
|
||||
environments={environments}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
+59
-22
@@ -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<typeof formSchema>;
|
||||
|
||||
@@ -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 = ({
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{environments.length > 1 && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="environments"
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Environments"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<FilterableSelect
|
||||
isMulti
|
||||
options={environments}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder="Select environments to create secret in..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.slug}
|
||||
menuPlacement="top"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+67
-23
@@ -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<typeof formSchema>;
|
||||
@@ -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 && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="environments"
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Environments"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<FilterableSelect
|
||||
isMulti
|
||||
options={environments}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder="Select environments to create secret in..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.slug}
|
||||
menuPlacement="top"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+59
-22
@@ -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<typeof formSchema>;
|
||||
|
||||
@@ -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 = ({
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
{environments.length > 1 && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="environments"
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Environments"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<FilterableSelect
|
||||
isMulti
|
||||
options={environments}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder="Select environments to create secret in..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.slug}
|
||||
menuPlacement="top"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+74
-30
@@ -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<typeof formSchema>;
|
||||
|
||||
@@ -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 = ({
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{environments.length > 1 && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="environments"
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Environments"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<FilterableSelect
|
||||
isMulti
|
||||
options={environments}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder="Select environments to create secret in..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.slug}
|
||||
menuPlacement="top"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+67
-23
@@ -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<typeof formSchema>;
|
||||
|
||||
@@ -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 = ({
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{environments.length > 1 && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="environments"
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Environments"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<FilterableSelect
|
||||
isMulti
|
||||
options={environments}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder="Select environments to create secret in..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.slug}
|
||||
menuPlacement="top"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+59
-22
@@ -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<typeof formSchema>;
|
||||
|
||||
@@ -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 = ({
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
{environments.length > 1 && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="environments"
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Environments"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<FilterableSelect
|
||||
isMulti
|
||||
options={environments}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder="Select environments to create secret in..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.slug}
|
||||
menuPlacement="top"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+59
-22
@@ -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<typeof formSchema>;
|
||||
|
||||
@@ -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}}';`
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
{environments.length > 1 && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="environments"
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Environments"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<FilterableSelect
|
||||
isMulti
|
||||
options={environments}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder="Select environments to create secret in..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.slug}
|
||||
menuPlacement="top"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+59
-22
@@ -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<typeof formSchema>;
|
||||
|
||||
@@ -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}};`,
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
{environments.length > 1 && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="environments"
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Environments"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<FilterableSelect
|
||||
isMulti
|
||||
options={environments}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder="Select environments to create secret in..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.slug}
|
||||
menuPlacement="top"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+59
-22
@@ -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<typeof formSchema>;
|
||||
|
||||
@@ -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 = ({
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
{environments.length > 1 && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="environments"
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Environments"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<FilterableSelect
|
||||
isMulti
|
||||
options={environments}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder="Select environments to create secret in..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.slug}
|
||||
menuPlacement="top"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+59
-22
@@ -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<typeof formSchema>;
|
||||
|
||||
@@ -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 = ({
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
{environments.length > 1 && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="environments"
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Environments"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<FilterableSelect
|
||||
isMulti
|
||||
options={environments}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder="Select environments to create secret in..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.slug}
|
||||
menuPlacement="top"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+64
-23
@@ -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<typeof formSchema>;
|
||||
@@ -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 = ({
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
{environments.length > 1 && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="environments"
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Environments"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<FilterableSelect
|
||||
isMulti
|
||||
options={environments}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder="Select environments to create secret in..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.slug}
|
||||
menuPlacement="top"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user