mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-05 09:26:37 +00:00
improvement: use multi-select for environment selection on create secret
This commit is contained in:
@@ -1128,7 +1128,6 @@ export const SecretOverviewPage = () => {
|
|||||||
>
|
>
|
||||||
<CreateSecretForm
|
<CreateSecretForm
|
||||||
secretPath={secretPath}
|
secretPath={secretPath}
|
||||||
getSecretByKey={getSecretByKey}
|
|
||||||
onClose={() => handlePopUpClose("addSecretsInAllEnvs")}
|
onClose={() => handlePopUpClose("addSecretsInAllEnvs")}
|
||||||
/>
|
/>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|||||||
+51
-88
@@ -1,13 +1,13 @@
|
|||||||
import { ClipboardEvent } from "react";
|
import { ClipboardEvent } from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { subject } from "@casl/ability";
|
import { subject } from "@casl/ability";
|
||||||
import { faTriangleExclamation, faWarning } from "@fortawesome/free-solid-svg-icons";
|
import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { Button, Checkbox, FormControl, FormLabel, Input, Tooltip } from "@app/components/v2";
|
import { Button, FilterableSelect, FormControl, Input } from "@app/components/v2";
|
||||||
import { CreatableSelect } from "@app/components/v2/CreatableSelect";
|
import { CreatableSelect } from "@app/components/v2/CreatableSelect";
|
||||||
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
|
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
|
||||||
import {
|
import {
|
||||||
@@ -17,20 +17,14 @@ import {
|
|||||||
useWorkspace
|
useWorkspace
|
||||||
} from "@app/context";
|
} from "@app/context";
|
||||||
import { getKeyValue } from "@app/helpers/parseEnvVar";
|
import { getKeyValue } from "@app/helpers/parseEnvVar";
|
||||||
import {
|
import { useCreateFolder, useCreateSecretV3, useCreateWsTag, useGetWsTags } from "@app/hooks/api";
|
||||||
useCreateFolder,
|
import { SecretType } from "@app/hooks/api/types";
|
||||||
useCreateSecretV3,
|
|
||||||
useCreateWsTag,
|
|
||||||
useGetWsTags,
|
|
||||||
useUpdateSecretV3
|
|
||||||
} from "@app/hooks/api";
|
|
||||||
import { SecretType, SecretV3RawSanitized } from "@app/hooks/api/types";
|
|
||||||
|
|
||||||
const typeSchema = z
|
const typeSchema = z
|
||||||
.object({
|
.object({
|
||||||
key: z.string().trim().min(1, "Key is required"),
|
key: z.string().trim().min(1, "Key is required"),
|
||||||
value: z.string().optional(),
|
value: z.string().optional(),
|
||||||
environments: z.record(z.boolean().optional()),
|
environments: z.object({ name: z.string(), slug: z.string() }).array(),
|
||||||
tags: z.array(z.object({ label: z.string().trim(), value: z.string().trim() })).optional()
|
tags: z.array(z.object({ label: z.string().trim(), value: z.string().trim() })).optional()
|
||||||
})
|
})
|
||||||
.refine((data) => data.key !== undefined, {
|
.refine((data) => data.key !== undefined, {
|
||||||
@@ -41,22 +35,19 @@ type TFormSchema = z.infer<typeof typeSchema>;
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
secretPath?: string;
|
secretPath?: string;
|
||||||
getSecretByKey: (slug: string, key: string) => SecretV3RawSanitized | undefined;
|
|
||||||
// modal props
|
// modal props
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CreateSecretForm = ({ secretPath = "/", getSecretByKey, onClose }: Props) => {
|
export const CreateSecretForm = ({ secretPath = "/", onClose }: Props) => {
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
control,
|
control,
|
||||||
reset,
|
reset,
|
||||||
watch,
|
|
||||||
setValue,
|
setValue,
|
||||||
formState: { isSubmitting, errors }
|
formState: { isSubmitting, errors }
|
||||||
} = useForm<TFormSchema>({ resolver: zodResolver(typeSchema) });
|
} = useForm<TFormSchema>({ resolver: zodResolver(typeSchema) });
|
||||||
const newSecretKey = watch("key");
|
|
||||||
|
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const { permission } = useProjectPermission();
|
const { permission } = useProjectPermission();
|
||||||
@@ -65,22 +56,14 @@ export const CreateSecretForm = ({ secretPath = "/", getSecretByKey, onClose }:
|
|||||||
const environments = currentWorkspace?.environments || [];
|
const environments = currentWorkspace?.environments || [];
|
||||||
|
|
||||||
const { mutateAsync: createSecretV3 } = useCreateSecretV3();
|
const { mutateAsync: createSecretV3 } = useCreateSecretV3();
|
||||||
const { mutateAsync: updateSecretV3 } = useUpdateSecretV3();
|
// const { mutateAsync: updateSecretV3 } = useUpdateSecretV3();
|
||||||
const { mutateAsync: createFolder } = useCreateFolder();
|
const { mutateAsync: createFolder } = useCreateFolder();
|
||||||
const { data: projectTags, isLoading: isTagsLoading } = useGetWsTags(
|
const { data: projectTags, isLoading: isTagsLoading } = useGetWsTags(
|
||||||
canReadTags ? workspaceId : ""
|
canReadTags ? workspaceId : ""
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFormSubmit = async ({ key, value, environments: selectedEnv, tags }: TFormSchema) => {
|
const handleFormSubmit = async ({ key, value, environments: selectedEnv, tags }: TFormSchema) => {
|
||||||
const environmentsSelected = environments.filter(({ slug }) => selectedEnv[slug]);
|
const promises = selectedEnv.map(async (env) => {
|
||||||
const isEnvironmentsSelected = environmentsSelected.length;
|
|
||||||
|
|
||||||
if (!isEnvironmentsSelected) {
|
|
||||||
createNotification({ type: "error", text: "Select at least one environment" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const promises = environmentsSelected.map(async (env) => {
|
|
||||||
const environment = env.slug;
|
const environment = env.slug;
|
||||||
// create folder if not existing
|
// create folder if not existing
|
||||||
if (secretPath !== "/") {
|
if (secretPath !== "/") {
|
||||||
@@ -106,21 +89,22 @@ export const CreateSecretForm = ({ secretPath = "/", getSecretByKey, onClose }:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isEdit = getSecretByKey(environment, key) !== undefined;
|
// TODO: add back - need to fetch secrets by key to check for conflicts as this method broke with pagination
|
||||||
if (isEdit) {
|
// const isEdit = getSecretByKey(environment, key) !== undefined;
|
||||||
return {
|
// if (isEdit) {
|
||||||
...(await updateSecretV3({
|
// return {
|
||||||
environment,
|
// ...(await updateSecretV3({
|
||||||
workspaceId,
|
// environment,
|
||||||
secretPath,
|
// workspaceId,
|
||||||
secretKey: key,
|
// secretPath,
|
||||||
secretValue: value || "",
|
// secretKey: key,
|
||||||
type: SecretType.Shared,
|
// secretValue: value || "",
|
||||||
tagIds: tags?.map((el) => el.value)
|
// type: SecretType.Shared,
|
||||||
})),
|
// tagIds: tags?.map((el) => el.value)
|
||||||
environment
|
// })),
|
||||||
};
|
// environment
|
||||||
}
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...(await createSecretV3({
|
...(await createSecretV3({
|
||||||
@@ -278,54 +262,33 @@ export const CreateSecretForm = ({ secretPath = "/", getSecretByKey, onClose }:
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormLabel label="Environments" className="mb-2" />
|
<Controller
|
||||||
<div className="thin-scrollbar grid max-h-64 grid-cols-3 gap-4 overflow-auto py-2">
|
control={control}
|
||||||
{environments
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
.filter((environmentSlug) =>
|
<FormControl label="Environments" isError={Boolean(error)} errorText={error?.message}>
|
||||||
permission.can(
|
<FilterableSelect
|
||||||
ProjectPermissionActions.Create,
|
isMulti
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
options={environments.filter((environment) =>
|
||||||
environment: environmentSlug.slug,
|
permission.can(
|
||||||
secretPath,
|
ProjectPermissionActions.Create,
|
||||||
secretName: "*",
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
secretTags: ["*"]
|
environment: environment.slug,
|
||||||
})
|
secretPath,
|
||||||
)
|
secretName: "*",
|
||||||
)
|
secretTags: ["*"]
|
||||||
.map((env) => {
|
})
|
||||||
return (
|
)
|
||||||
<Controller
|
)}
|
||||||
name={`environments.${env.slug}`}
|
value={value}
|
||||||
key={`secret-input-${env.slug}`}
|
onChange={onChange}
|
||||||
control={control}
|
placeholder="Select environments to create secret in..."
|
||||||
render={({ field }) => (
|
getOptionLabel={(option) => option.name}
|
||||||
<Checkbox
|
getOptionValue={(option) => option.slug}
|
||||||
isChecked={field.value}
|
/>
|
||||||
onCheckedChange={field.onChange}
|
</FormControl>
|
||||||
id={`secret-input-${env.slug}`}
|
)}
|
||||||
className="!justify-start"
|
name="environments"
|
||||||
>
|
/>
|
||||||
<span className="flex w-full flex-row items-center justify-start whitespace-pre-wrap">
|
|
||||||
<span title={env.name} className="truncate">
|
|
||||||
{env.name}
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
{getSecretByKey(env.slug, newSecretKey) && (
|
|
||||||
<Tooltip
|
|
||||||
className="max-w-[150px]"
|
|
||||||
content="Secret already exists, and it will be overwritten"
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faWarning} className="ml-1 text-yellow-400" />
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</Checkbox>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<div className="mt-7 flex items-center">
|
<div className="mt-7 flex items-center">
|
||||||
<Button
|
<Button
|
||||||
isDisabled={isSubmitting}
|
isDisabled={isSubmitting}
|
||||||
|
|||||||
Reference in New Issue
Block a user