diff --git a/frontend/src/views/SecretMainPage/components/ActionBar/CreateSecretImportForm.tsx b/frontend/src/views/SecretMainPage/components/ActionBar/CreateSecretImportForm.tsx
index 8a97a799f..c178e7514 100644
--- a/frontend/src/views/SecretMainPage/components/ActionBar/CreateSecretImportForm.tsx
+++ b/frontend/src/views/SecretMainPage/components/ActionBar/CreateSecretImportForm.tsx
@@ -81,7 +81,7 @@ export const CreateSecretImportForm = ({
});
} catch (err) {
console.error(err);
- const axiosError = err as AxiosError
+ const axiosError = err as AxiosError;
if (axiosError?.response?.status === 401) {
createNotification({
text: "You do not have access to the selected environment/path",
diff --git a/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx b/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx
index b3e0efb8a..ed4f490f8 100644
--- a/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx
+++ b/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx
@@ -2,22 +2,31 @@ import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import Link from "next/link";
import { useRouter } from "next/router";
+import { subject } from "@casl/ability";
import {
+ faAngleDown,
faArrowDown,
faArrowUp,
faFolderBlank,
- faMagnifyingGlass
+ faFolderPlus,
+ faMagnifyingGlass,
+ faPlus
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import NavHeader from "@app/components/navigation/NavHeader";
-import { PermissionDeniedBanner } from "@app/components/permissions";
+import { PermissionDeniedBanner, ProjectPermissionCan } from "@app/components/permissions";
import {
Button,
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuTrigger,
EmptyState,
IconButton,
Input,
+ Modal,
+ ModalContent,
Table,
TableContainer,
TableSkeleton,
@@ -30,7 +39,13 @@ import {
Tr
} from "@app/components/v2";
import { UpgradeProjectAlert } from "@app/components/v2/UpgradeProjectAlert";
-import { useOrganization, useWorkspace } from "@app/context";
+import {
+ ProjectPermissionActions,
+ ProjectPermissionSub,
+ useOrganization,
+ useWorkspace
+} from "@app/context";
+import { usePopUp } from "@app/hooks";
import {
useCreateFolder,
useCreateSecretV3,
@@ -42,6 +57,8 @@ import {
} from "@app/hooks/api";
import { ProjectVersion } from "@app/hooks/api/workspace/types";
+import { FolderForm } from "../SecretMainPage/components/ActionBar/FolderForm";
+import { CreateSecretInAllEnvsForm } from "./components/CreateSecretsSection/CreateSecretInAllEnvsForm";
import { FolderBreadCrumbs } from "./components/FolderBreadCrumbs";
import { ProjectIndexSecretsSection } from "./components/ProjectIndexSecretsSection";
import { SecretOverviewFolderRow } from "./components/SecretOverviewFolderRow";
@@ -110,6 +127,40 @@ export const SecretOverviewPage = () => {
const { mutateAsync: deleteSecretV3 } = useDeleteSecretV3();
const { mutateAsync: createFolder } = useCreateFolder();
+ const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([
+ "addSecretsInAllEnvs",
+ "addFolder",
+ "misc"
+ ] as const);
+
+ const handleFolderCreate = async (folderName: string) => {
+ const promises = userAvailableEnvs.map((env) => {
+ const environment = env.slug;
+ return createFolder({
+ name: folderName,
+ path: secretPath,
+ environment,
+ projectId: workspaceId
+ });
+ });
+
+ const results = await Promise.allSettled(promises);
+ const isFoldersAdded = results.some((result) => result.status === "fulfilled");
+
+ if (isFoldersAdded) {
+ handlePopUpClose("addFolder");
+ createNotification({
+ type: "success",
+ text: "Successfully created folder"
+ });
+ } else {
+ createNotification({
+ type: "error",
+ text: "Failed to create folder"
+ });
+ }
+ };
+
const handleSecretCreate = async (env: string, key: string, value: string) => {
try {
// create folder if not existing
@@ -269,210 +320,286 @@ export const SecretOverviewPage = () => {
filteredFolderNames?.length === 0;
return (
-
-
-
-
-
-
-
-
Secrets Overview
-
- Inject your secrets using
-
- Infisical CLI
-
- ,
-
- Infisical API
-
- ,
-
- Infisical SDKs
-
- , and
-
- more
-
- .
-
+ <>
+
+
+
+
+
+
- {currentWorkspace?.version === ProjectVersion.V1 && (
-
- )}
+ {currentWorkspace?.version === ProjectVersion.V1 && (
+
+ )}
-
-
-
-
setSearchFilter(e.target.value)}
- leftIcon={
}
- />
+
+
+
+
+ setSearchFilter(e.target.value)}
+ leftIcon={}
+ />
+
+
+
+ {(isAllowed) => (
+ }
+ onClick={() => handlePopUpOpen("addSecretsInAllEnvs")}
+ className="h-10 rounded-r-none"
+ isDisabled={!isAllowed}
+ >
+ Add Secret
+
+ )}
+
+
handlePopUpToggle("misc", isOpen)}
+ >
+
+
+
+
+
+
+
+
+ {(isAllowed) => (
+ }
+ onClick={() => {
+ handlePopUpOpen("addFolder");
+ handlePopUpClose("misc");
+ }}
+ isDisabled={!isAllowed}
+ variant="outline_bg"
+ className="h-10"
+ isFullWidth
+ >
+ Add Folder
+
+ )}
+
+
+
+
+
+
-
-
-
-
-
-
- |
-
- Name
- setSortDir((prev) => (prev === "asc" ? "desc" : "asc"))}
- >
-
-
-
- |
- {userAvailableEnvs?.map(({ name, slug }, index) => {
- const envSecKeyCount = getEnvSecretKeyCount(slug);
- const missingKeyCount = secKeys.length - envSecKeyCount;
- return (
-
-
- |
+
+
-
+
handlePopUpToggle("addSecretsInAllEnvs", isOpen)}
+ onClose={() => handlePopUpClose("addSecretsInAllEnvs")}
+ decryptFileKey={latestFileKey!}
+ />
+ handlePopUpToggle("addFolder", isOpen)}
+ >
+
+
+
+
+ >
);
};
diff --git a/frontend/src/views/SecretOverviewPage/components/CreateSecretsSection/CreateSecretInAllEnvsForm.tsx b/frontend/src/views/SecretOverviewPage/components/CreateSecretsSection/CreateSecretInAllEnvsForm.tsx
new file mode 100644
index 000000000..bc716edff
--- /dev/null
+++ b/frontend/src/views/SecretOverviewPage/components/CreateSecretsSection/CreateSecretInAllEnvsForm.tsx
@@ -0,0 +1,156 @@
+import { Controller, useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { z } from "zod";
+
+import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
+import { Button, FormControl, Input, Modal, ModalContent, SecretInput } from "@app/components/v2";
+import { useWorkspace } from "@app/context";
+import { useCreateSecretV3 } from "@app/hooks/api";
+import { UserWsKeyPair } from "@app/hooks/api/types";
+
+const typeSchema = z
+ .object({
+ key: z.string(),
+ values: z.record(z.string().optional())
+ })
+ .refine((data) => data.key !== undefined, {
+ message: "Please enter secret name"
+ });
+
+type TFormSchema = z.infer;
+
+type Props = {
+ workspaceId: string;
+ secretPath?: string;
+ decryptFileKey: UserWsKeyPair;
+
+ // modal props
+ isOpen?: boolean;
+ onClose: () => void;
+ onTogglePopUp: (isOpen: boolean) => void;
+};
+
+export const CreateSecretInAllEnvsForm = ({
+ workspaceId,
+ secretPath = "/",
+ decryptFileKey,
+ isOpen,
+ onClose,
+ onTogglePopUp
+}: Props) => {
+ const {
+ register,
+ handleSubmit,
+ control,
+ reset,
+ setError,
+ formState: { isSubmitting, errors }
+ } = useForm({ resolver: zodResolver(typeSchema) });
+ const { currentWorkspace } = useWorkspace();
+ const environments = currentWorkspace?.environments || [];
+
+ const { createNotification } = useNotificationContext();
+
+ const { mutateAsync: createSecretV3 } = useCreateSecretV3();
+
+ const handleFormSubmit = async ({ key, values }: TFormSchema) => {
+ if (!key) {
+ setError("key", { message: "Please enter secret name" });
+ return;
+ }
+
+ const promises = environments.map((env) => {
+ const environment = env.slug;
+ const value = values[environment] || "";
+
+ return createSecretV3({
+ environment,
+ workspaceId,
+ secretPath,
+ secretName: key,
+ secretValue: value,
+ secretComment: "",
+ type: "shared",
+ latestFileKey: decryptFileKey
+ });
+ });
+
+ const results = await Promise.allSettled(promises);
+ const isSecretsAdded = results.some((result) => result.status === "fulfilled");
+
+ if (isSecretsAdded) {
+ createNotification({
+ type: "success",
+ text: "Secrets created successfully"
+ });
+ onClose();
+ reset();
+ } else {
+ createNotification({
+ type: "error",
+ text: "Failed to create secrets"
+ });
+ }
+ };
+ return (
+
+
+
+
+
+ );
+};