mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 20:27:12 +00:00
Merge pull request #2707 from akhilmhdh/feat/create-secret-tag
feat: added tag support on create secret
This commit is contained in:
@@ -31,7 +31,8 @@ export const useCreateSecretV3 = ({
|
|||||||
secretKey,
|
secretKey,
|
||||||
secretValue,
|
secretValue,
|
||||||
secretComment,
|
secretComment,
|
||||||
skipMultilineEncoding
|
skipMultilineEncoding,
|
||||||
|
tagIds
|
||||||
}) => {
|
}) => {
|
||||||
const { data } = await apiRequest.post(`/api/v3/secrets/raw/${secretKey}`, {
|
const { data } = await apiRequest.post(`/api/v3/secrets/raw/${secretKey}`, {
|
||||||
secretPath,
|
secretPath,
|
||||||
@@ -40,7 +41,8 @@ export const useCreateSecretV3 = ({
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
secretValue,
|
secretValue,
|
||||||
secretComment,
|
secretComment,
|
||||||
skipMultilineEncoding
|
skipMultilineEncoding,
|
||||||
|
tagIds
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ export type TCreateSecretsV3DTO = {
|
|||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
type: SecretType;
|
type: SecretType;
|
||||||
|
tagIds?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TUpdateSecretsV3DTO = {
|
export type TUpdateSecretsV3DTO = {
|
||||||
|
|||||||
@@ -9,7 +9,14 @@ import { twMerge } from "tailwind-merge";
|
|||||||
import NavHeader from "@app/components/navigation/NavHeader";
|
import NavHeader from "@app/components/navigation/NavHeader";
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { PermissionDeniedBanner } from "@app/components/permissions";
|
import { PermissionDeniedBanner } from "@app/components/permissions";
|
||||||
import { Checkbox, ContentLoader, Pagination, Tooltip } from "@app/components/v2";
|
import {
|
||||||
|
Checkbox,
|
||||||
|
ContentLoader,
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
|
Pagination,
|
||||||
|
Tooltip
|
||||||
|
} from "@app/components/v2";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
ProjectPermissionDynamicSecretActions,
|
ProjectPermissionDynamicSecretActions,
|
||||||
@@ -41,7 +48,10 @@ import { SecretDropzone } from "./components/SecretDropzone";
|
|||||||
import { SecretListView, SecretNoAccessListView } from "./components/SecretListView";
|
import { SecretListView, SecretNoAccessListView } from "./components/SecretListView";
|
||||||
import { SnapshotView } from "./components/SnapshotView";
|
import { SnapshotView } from "./components/SnapshotView";
|
||||||
import {
|
import {
|
||||||
|
PopUpNames,
|
||||||
StoreProvider,
|
StoreProvider,
|
||||||
|
usePopUpAction,
|
||||||
|
usePopUpState,
|
||||||
useSelectedSecretActions,
|
useSelectedSecretActions,
|
||||||
useSelectedSecrets
|
useSelectedSecrets
|
||||||
} from "./SecretMainPage.store";
|
} from "./SecretMainPage.store";
|
||||||
@@ -123,6 +133,9 @@ const SecretMainPageContent = () => {
|
|||||||
const [debouncedSearchFilter, setDebouncedSearchFilter] = useDebounce(filter.searchFilter);
|
const [debouncedSearchFilter, setDebouncedSearchFilter] = useDebounce(filter.searchFilter);
|
||||||
const [filterHistory, setFilterHistory] = useState<Map<string, Filter>>(new Map());
|
const [filterHistory, setFilterHistory] = useState<Map<string, Filter>>(new Map());
|
||||||
|
|
||||||
|
const createSecretPopUp = usePopUpState(PopUpNames.CreateSecretForm);
|
||||||
|
const { togglePopUp } = usePopUpAction();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
!isWorkspaceLoading &&
|
!isWorkspaceLoading &&
|
||||||
@@ -520,13 +533,24 @@ const SecretMainPageContent = () => {
|
|||||||
onChangePerPage={(newPerPage) => setPerPage(newPerPage)}
|
onChangePerPage={(newPerPage) => setPerPage(newPerPage)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<CreateSecretForm
|
<Modal
|
||||||
environment={environment}
|
isOpen={createSecretPopUp.isOpen}
|
||||||
workspaceId={workspaceId}
|
onOpenChange={(state) => togglePopUp(PopUpNames.CreateSecretForm, state)}
|
||||||
secretPath={secretPath}
|
>
|
||||||
autoCapitalize={currentWorkspace?.autoCapitalization}
|
<ModalContent
|
||||||
isProtectedBranch={isProtectedBranch}
|
title="Create Secret"
|
||||||
/>
|
subTitle="Add a secret to this particular environment and folder"
|
||||||
|
bodyClassName="overflow-visible"
|
||||||
|
>
|
||||||
|
<CreateSecretForm
|
||||||
|
environment={environment}
|
||||||
|
workspaceId={workspaceId}
|
||||||
|
secretPath={secretPath}
|
||||||
|
autoCapitalize={currentWorkspace?.autoCapitalization}
|
||||||
|
isProtectedBranch={isProtectedBranch}
|
||||||
|
/>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
<SecretDropzone
|
<SecretDropzone
|
||||||
secrets={secrets}
|
secrets={secrets}
|
||||||
environment={environment}
|
environment={environment}
|
||||||
|
|||||||
+97
-65
@@ -1,20 +1,24 @@
|
|||||||
import { ClipboardEvent } from "react";
|
import { ClipboardEvent } from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
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, FormControl, Input, Modal, ModalContent } from "@app/components/v2";
|
import { Button, FormControl, Input, MultiSelect } from "@app/components/v2";
|
||||||
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
|
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context";
|
||||||
import { getKeyValue } from "@app/helpers/parseEnvVar";
|
import { getKeyValue } from "@app/helpers/parseEnvVar";
|
||||||
import { useCreateSecretV3 } from "@app/hooks/api";
|
import { useCreateSecretV3, useGetWsTags } from "@app/hooks/api";
|
||||||
import { SecretType } from "@app/hooks/api/types";
|
import { SecretType } from "@app/hooks/api/types";
|
||||||
|
|
||||||
import { PopUpNames, usePopUpAction, usePopUpState } from "../../SecretMainPage.store";
|
import { PopUpNames, usePopUpAction } from "../../SecretMainPage.store";
|
||||||
|
|
||||||
const typeSchema = z.object({
|
const typeSchema = z.object({
|
||||||
key: z.string().trim().min(1, { message: "Secret key is required" }),
|
key: z.string().trim().min(1, { message: "Secret key is required" }),
|
||||||
value: z.string().optional()
|
value: z.string().optional(),
|
||||||
|
tags: z.array(z.object({ label: z.string().trim(), value: z.string().trim() })).optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
type TFormSchema = z.infer<typeof typeSchema>;
|
type TFormSchema = z.infer<typeof typeSchema>;
|
||||||
@@ -43,12 +47,16 @@ export const CreateSecretForm = ({
|
|||||||
setValue,
|
setValue,
|
||||||
formState: { errors, isSubmitting }
|
formState: { errors, isSubmitting }
|
||||||
} = useForm<TFormSchema>({ resolver: zodResolver(typeSchema) });
|
} = useForm<TFormSchema>({ resolver: zodResolver(typeSchema) });
|
||||||
const { isOpen } = usePopUpState(PopUpNames.CreateSecretForm);
|
const { closePopUp } = usePopUpAction();
|
||||||
const { closePopUp, togglePopUp } = usePopUpAction();
|
|
||||||
|
|
||||||
const { mutateAsync: createSecretV3 } = useCreateSecretV3();
|
const { mutateAsync: createSecretV3 } = useCreateSecretV3();
|
||||||
|
const { permission } = useProjectPermission();
|
||||||
|
const canReadTags = permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
|
||||||
|
const { data: projectTags, isLoading: isTagsLoading } = useGetWsTags(
|
||||||
|
canReadTags ? workspaceId : ""
|
||||||
|
);
|
||||||
|
|
||||||
const handleFormSubmit = async ({ key, value }: TFormSchema) => {
|
const handleFormSubmit = async ({ key, value, tags }: TFormSchema) => {
|
||||||
try {
|
try {
|
||||||
await createSecretV3({
|
await createSecretV3({
|
||||||
environment,
|
environment,
|
||||||
@@ -57,7 +65,8 @@ export const CreateSecretForm = ({
|
|||||||
secretKey: key,
|
secretKey: key,
|
||||||
secretValue: value || "",
|
secretValue: value || "",
|
||||||
secretComment: "",
|
secretComment: "",
|
||||||
type: SecretType.Shared
|
type: SecretType.Shared,
|
||||||
|
tagIds: tags?.map((el) => el.value)
|
||||||
});
|
});
|
||||||
closePopUp(PopUpNames.CreateSecretForm);
|
closePopUp(PopUpNames.CreateSecretForm);
|
||||||
reset();
|
reset();
|
||||||
@@ -88,67 +97,90 @@ export const CreateSecretForm = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<form onSubmit={handleSubmit(handleFormSubmit)} noValidate>
|
||||||
isOpen={isOpen}
|
<FormControl
|
||||||
onOpenChange={(state) => togglePopUp(PopUpNames.CreateSecretForm, state)}
|
label="Key"
|
||||||
>
|
isRequired
|
||||||
<ModalContent
|
isError={Boolean(errors?.key)}
|
||||||
title="Create secret"
|
errorText={errors?.key?.message}
|
||||||
subTitle="Add a secret to the particular environment and folder"
|
|
||||||
>
|
>
|
||||||
<form onSubmit={handleSubmit(handleFormSubmit)} noValidate>
|
<Input
|
||||||
|
{...register("key")}
|
||||||
|
placeholder="Type your secret name"
|
||||||
|
onPaste={handlePaste}
|
||||||
|
autoCapitalization={autoCapitalize}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="value"
|
||||||
|
render={({ field }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Key"
|
label="Value"
|
||||||
isRequired
|
isError={Boolean(errors?.value)}
|
||||||
isError={Boolean(errors?.key)}
|
errorText={errors?.value?.message}
|
||||||
errorText={errors?.key?.message}
|
|
||||||
>
|
>
|
||||||
<Input
|
<InfisicalSecretInput
|
||||||
{...register("key")}
|
{...field}
|
||||||
placeholder="Type your secret name"
|
environment={environment}
|
||||||
onPaste={handlePaste}
|
secretPath={secretPath}
|
||||||
autoCapitalization={autoCapitalize}
|
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-mineshaft-900 px-2 py-1.5"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Controller
|
)}
|
||||||
control={control}
|
/>
|
||||||
name="value"
|
<Controller
|
||||||
render={({ field }) => (
|
control={control}
|
||||||
<FormControl
|
name="tags"
|
||||||
label="Value"
|
render={({ field }) => (
|
||||||
isError={Boolean(errors?.value)}
|
<FormControl
|
||||||
errorText={errors?.value?.message}
|
label="Tags"
|
||||||
>
|
isError={Boolean(errors?.value)}
|
||||||
<InfisicalSecretInput
|
errorText={errors?.value?.message}
|
||||||
{...field}
|
helperText={
|
||||||
environment={environment}
|
!canReadTags ? (
|
||||||
secretPath={secretPath}
|
<div className="flex items-center space-x-2">
|
||||||
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-mineshaft-900 px-2 py-1.5"
|
<FontAwesomeIcon icon={faTriangleExclamation} className="text-yellow-400" />
|
||||||
/>
|
<span>You do not have permission to read tags.</span>
|
||||||
</FormControl>
|
</div>
|
||||||
)}
|
) : (
|
||||||
/>
|
""
|
||||||
<div className="mt-7 flex items-center">
|
)
|
||||||
<Button
|
}
|
||||||
isDisabled={isSubmitting}
|
>
|
||||||
isLoading={isSubmitting}
|
<MultiSelect
|
||||||
key="layout-create-project-submit"
|
className="w-full"
|
||||||
className="mr-4"
|
placeholder="Select tags to assign to secret..."
|
||||||
type="submit"
|
isMulti
|
||||||
>
|
name="tagIds"
|
||||||
Create Secret
|
isDisabled={!canReadTags}
|
||||||
</Button>
|
isLoading={isTagsLoading}
|
||||||
<Button
|
options={projectTags?.map((el) => ({ label: el.slug, value: el.id }))}
|
||||||
key="layout-cancel-create-project"
|
value={field.value}
|
||||||
onClick={() => closePopUp(PopUpNames.CreateSecretForm)}
|
onChange={field.onChange}
|
||||||
variant="plain"
|
/>
|
||||||
colorSchema="secondary"
|
</FormControl>
|
||||||
>
|
)}
|
||||||
Cancel
|
/>
|
||||||
</Button>
|
<div className="mt-7 flex items-center">
|
||||||
</div>
|
<Button
|
||||||
</form>
|
isDisabled={isSubmitting}
|
||||||
</ModalContent>
|
isLoading={isSubmitting}
|
||||||
</Modal>
|
key="layout-create-project-submit"
|
||||||
|
className="mr-4"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Create Secret
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
key="layout-cancel-create-project"
|
||||||
|
onClick={() => closePopUp(PopUpNames.CreateSecretForm)}
|
||||||
|
variant="plain"
|
||||||
|
colorSchema="secondary"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1116,13 +1116,23 @@ export const SecretOverviewPage = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<CreateSecretForm
|
<Modal
|
||||||
secretPath={secretPath}
|
|
||||||
isOpen={popUp.addSecretsInAllEnvs.isOpen}
|
isOpen={popUp.addSecretsInAllEnvs.isOpen}
|
||||||
getSecretByKey={getSecretByKey}
|
onOpenChange={(isOpen) => handlePopUpToggle("addSecretsInAllEnvs", isOpen)}
|
||||||
onTogglePopUp={(isOpen) => handlePopUpToggle("addSecretsInAllEnvs", isOpen)}
|
>
|
||||||
onClose={() => handlePopUpClose("addSecretsInAllEnvs")}
|
<ModalContent
|
||||||
/>
|
className="max-h-[80vh]"
|
||||||
|
bodyClassName="overflow-visible"
|
||||||
|
title="Create Secrets"
|
||||||
|
subTitle="Create a secret across multiple environments"
|
||||||
|
>
|
||||||
|
<CreateSecretForm
|
||||||
|
secretPath={secretPath}
|
||||||
|
getSecretByKey={getSecretByKey}
|
||||||
|
onClose={() => handlePopUpClose("addSecretsInAllEnvs")}
|
||||||
|
/>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={popUp.addFolder.isOpen}
|
isOpen={popUp.addFolder.isOpen}
|
||||||
onOpenChange={(isOpen) => handlePopUpToggle("addFolder", isOpen)}
|
onOpenChange={(isOpen) => handlePopUpToggle("addFolder", isOpen)}
|
||||||
|
|||||||
+145
-120
@@ -1,7 +1,7 @@
|
|||||||
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 { faWarning } from "@fortawesome/free-solid-svg-icons";
|
import { faTriangleExclamation, faWarning } 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";
|
||||||
@@ -13,8 +13,7 @@ import {
|
|||||||
FormControl,
|
FormControl,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
Input,
|
Input,
|
||||||
Modal,
|
MultiSelect,
|
||||||
ModalContent,
|
|
||||||
Tooltip
|
Tooltip
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
|
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
|
||||||
@@ -25,14 +24,20 @@ import {
|
|||||||
useWorkspace
|
useWorkspace
|
||||||
} from "@app/context";
|
} from "@app/context";
|
||||||
import { getKeyValue } from "@app/helpers/parseEnvVar";
|
import { getKeyValue } from "@app/helpers/parseEnvVar";
|
||||||
import { useCreateFolder, useCreateSecretV3, useUpdateSecretV3 } from "@app/hooks/api";
|
import {
|
||||||
|
useCreateFolder,
|
||||||
|
useCreateSecretV3,
|
||||||
|
useGetWsTags,
|
||||||
|
useUpdateSecretV3
|
||||||
|
} from "@app/hooks/api";
|
||||||
import { SecretType, SecretV3RawSanitized } from "@app/hooks/api/types";
|
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.record(z.boolean().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, {
|
||||||
message: "Please enter secret name"
|
message: "Please enter secret name"
|
||||||
@@ -44,18 +49,10 @@ type Props = {
|
|||||||
secretPath?: string;
|
secretPath?: string;
|
||||||
getSecretByKey: (slug: string, key: string) => SecretV3RawSanitized | undefined;
|
getSecretByKey: (slug: string, key: string) => SecretV3RawSanitized | undefined;
|
||||||
// modal props
|
// modal props
|
||||||
isOpen?: boolean;
|
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onTogglePopUp: (isOpen: boolean) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CreateSecretForm = ({
|
export const CreateSecretForm = ({ secretPath = "/", getSecretByKey, onClose }: Props) => {
|
||||||
secretPath = "/",
|
|
||||||
isOpen,
|
|
||||||
getSecretByKey,
|
|
||||||
onClose,
|
|
||||||
onTogglePopUp
|
|
||||||
}: Props) => {
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@@ -69,14 +66,18 @@ export const CreateSecretForm = ({
|
|||||||
|
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const { permission } = useProjectPermission();
|
const { permission } = useProjectPermission();
|
||||||
|
const canReadTags = permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
|
||||||
const workspaceId = currentWorkspace?.id || "";
|
const workspaceId = currentWorkspace?.id || "";
|
||||||
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(
|
||||||
|
canReadTags ? workspaceId : ""
|
||||||
|
);
|
||||||
|
|
||||||
const handleFormSubmit = async ({ key, value, environments: selectedEnv }: TFormSchema) => {
|
const handleFormSubmit = async ({ key, value, environments: selectedEnv, tags }: TFormSchema) => {
|
||||||
const environmentsSelected = environments.filter(({ slug }) => selectedEnv[slug]);
|
const environmentsSelected = environments.filter(({ slug }) => selectedEnv[slug]);
|
||||||
const isEnvironmentsSelected = environmentsSelected.length;
|
const isEnvironmentsSelected = environmentsSelected.length;
|
||||||
|
|
||||||
@@ -120,7 +121,8 @@ export const CreateSecretForm = ({
|
|||||||
secretPath,
|
secretPath,
|
||||||
secretKey: key,
|
secretKey: key,
|
||||||
secretValue: value || "",
|
secretValue: value || "",
|
||||||
type: SecretType.Shared
|
type: SecretType.Shared,
|
||||||
|
tagIds: tags?.map((el) => el.value)
|
||||||
})),
|
})),
|
||||||
environment
|
environment
|
||||||
};
|
};
|
||||||
@@ -134,7 +136,8 @@ export const CreateSecretForm = ({
|
|||||||
secretKey: key,
|
secretKey: key,
|
||||||
secretValue: value || "",
|
secretValue: value || "",
|
||||||
secretComment: "",
|
secretComment: "",
|
||||||
type: SecretType.Shared
|
type: SecretType.Shared,
|
||||||
|
tagIds: tags?.map((el) => el.value)
|
||||||
})),
|
})),
|
||||||
environment
|
environment
|
||||||
};
|
};
|
||||||
@@ -197,114 +200,136 @@ export const CreateSecretForm = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} onOpenChange={onTogglePopUp}>
|
<form onSubmit={handleSubmit(handleFormSubmit)} noValidate>
|
||||||
<ModalContent
|
<FormControl
|
||||||
className="max-h-[80vh] overflow-y-auto"
|
label="Key"
|
||||||
title="Bulk Create & Update"
|
isRequired
|
||||||
subTitle="Create & update a secret across many environments"
|
isError={Boolean(errors?.key)}
|
||||||
|
errorText={errors?.key?.message}
|
||||||
>
|
>
|
||||||
<form onSubmit={handleSubmit(handleFormSubmit)} noValidate>
|
<Input
|
||||||
|
{...register("key")}
|
||||||
|
placeholder="Type your secret name"
|
||||||
|
onPaste={handlePaste}
|
||||||
|
autoCapitalization={currentWorkspace?.autoCapitalization}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="value"
|
||||||
|
render={({ field }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Key"
|
label="Value"
|
||||||
isRequired
|
isError={Boolean(errors?.value)}
|
||||||
isError={Boolean(errors?.key)}
|
errorText={errors?.value?.message}
|
||||||
errorText={errors?.key?.message}
|
|
||||||
>
|
>
|
||||||
<Input
|
<InfisicalSecretInput
|
||||||
{...register("key")}
|
{...field}
|
||||||
placeholder="Type your secret name"
|
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-mineshaft-900 px-2 py-1.5"
|
||||||
onPaste={handlePaste}
|
|
||||||
autoCapitalization={currentWorkspace?.autoCapitalization}
|
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Controller
|
)}
|
||||||
control={control}
|
/>
|
||||||
name="value"
|
<Controller
|
||||||
render={({ field }) => (
|
control={control}
|
||||||
<FormControl
|
name="tags"
|
||||||
label="Value"
|
render={({ field }) => (
|
||||||
isError={Boolean(errors?.value)}
|
<FormControl
|
||||||
errorText={errors?.value?.message}
|
label="Tags"
|
||||||
>
|
isError={Boolean(errors?.value)}
|
||||||
<InfisicalSecretInput
|
errorText={errors?.value?.message}
|
||||||
{...field}
|
helperText={
|
||||||
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-mineshaft-900 px-2 py-1.5"
|
!canReadTags ? (
|
||||||
/>
|
<div className="flex items-center space-x-2">
|
||||||
</FormControl>
|
<FontAwesomeIcon icon={faTriangleExclamation} className="text-yellow-400" />
|
||||||
)}
|
<span>You do not have permission to read tags.</span>
|
||||||
/>
|
</div>
|
||||||
<FormLabel label="Environments" className="mb-2" />
|
) : (
|
||||||
<div className="thin-scrollbar grid max-h-64 grid-cols-3 gap-4 overflow-auto py-2">
|
""
|
||||||
{environments
|
|
||||||
.filter((environmentSlug) =>
|
|
||||||
permission.can(
|
|
||||||
ProjectPermissionActions.Create,
|
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
|
||||||
environment: environmentSlug.slug,
|
|
||||||
secretPath,
|
|
||||||
secretName: "*",
|
|
||||||
secretTags: ["*"]
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
.map((env) => {
|
}
|
||||||
return (
|
>
|
||||||
<Controller
|
<MultiSelect
|
||||||
name={`environments.${env.slug}`}
|
className="w-full"
|
||||||
key={`secret-input-${env.slug}`}
|
placeholder="Select tags to assign to secrets..."
|
||||||
control={control}
|
isMulti
|
||||||
render={({ field }) => (
|
name="tagIds"
|
||||||
<Checkbox
|
isDisabled={!canReadTags}
|
||||||
isChecked={field.value}
|
isLoading={isTagsLoading}
|
||||||
onCheckedChange={field.onChange}
|
options={projectTags?.map((el) => ({ label: el.slug, value: el.id }))}
|
||||||
id={`secret-input-${env.slug}`}
|
value={field.value}
|
||||||
className="!justify-start"
|
onChange={field.onChange}
|
||||||
>
|
/>
|
||||||
<span className="flex w-full flex-row items-center justify-start whitespace-pre-wrap">
|
</FormControl>
|
||||||
<span title={env.name} className="truncate">
|
)}
|
||||||
{env.name}
|
/>
|
||||||
</span>
|
<FormLabel label="Environments" className="mb-2" />
|
||||||
<span>
|
<div className="thin-scrollbar grid max-h-64 grid-cols-3 gap-4 overflow-auto py-2">
|
||||||
{getSecretByKey(env.slug, newSecretKey) && (
|
{environments
|
||||||
<Tooltip
|
.filter((environmentSlug) =>
|
||||||
className="max-w-[150px]"
|
permission.can(
|
||||||
content="Secret already exists, and it will be overwritten"
|
ProjectPermissionActions.Create,
|
||||||
>
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
<FontAwesomeIcon
|
environment: environmentSlug.slug,
|
||||||
icon={faWarning}
|
secretPath,
|
||||||
className="ml-1 text-yellow-400"
|
secretName: "*",
|
||||||
/>
|
secretTags: ["*"]
|
||||||
</Tooltip>
|
})
|
||||||
)}
|
)
|
||||||
</span>
|
)
|
||||||
</span>
|
.map((env) => {
|
||||||
</Checkbox>
|
return (
|
||||||
)}
|
<Controller
|
||||||
/>
|
name={`environments.${env.slug}`}
|
||||||
);
|
key={`secret-input-${env.slug}`}
|
||||||
})}
|
control={control}
|
||||||
</div>
|
render={({ field }) => (
|
||||||
<div className="mt-7 flex items-center">
|
<Checkbox
|
||||||
<Button
|
isChecked={field.value}
|
||||||
isDisabled={isSubmitting}
|
onCheckedChange={field.onChange}
|
||||||
isLoading={isSubmitting}
|
id={`secret-input-${env.slug}`}
|
||||||
key="layout-create-project-submit"
|
className="!justify-start"
|
||||||
className="mr-4"
|
>
|
||||||
type="submit"
|
<span className="flex w-full flex-row items-center justify-start whitespace-pre-wrap">
|
||||||
>
|
<span title={env.name} className="truncate">
|
||||||
Create Secret
|
{env.name}
|
||||||
</Button>
|
</span>
|
||||||
<Button
|
<span>
|
||||||
key="layout-cancel-create-project"
|
{getSecretByKey(env.slug, newSecretKey) && (
|
||||||
onClick={onClose}
|
<Tooltip
|
||||||
variant="plain"
|
className="max-w-[150px]"
|
||||||
colorSchema="secondary"
|
content="Secret already exists, and it will be overwritten"
|
||||||
>
|
>
|
||||||
Cancel
|
<FontAwesomeIcon icon={faWarning} className="ml-1 text-yellow-400" />
|
||||||
</Button>
|
</Tooltip>
|
||||||
</div>
|
)}
|
||||||
</form>
|
</span>
|
||||||
</ModalContent>
|
</span>
|
||||||
</Modal>
|
</Checkbox>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className="mt-7 flex items-center">
|
||||||
|
<Button
|
||||||
|
isDisabled={isSubmitting}
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
key="layout-create-project-submit"
|
||||||
|
className="mr-4"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Create Secret
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
key="layout-cancel-create-project"
|
||||||
|
onClick={onClose}
|
||||||
|
variant="plain"
|
||||||
|
colorSchema="secondary"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user