env.slug === environment)?.name ?? environment
- }
+ title="Secrets Management"
description={
Inject your secrets using
@@ -759,6 +782,8 @@ const Page = () => {
}
/>
+
+
{!isRollbackMode ? (
<>
{
workspaceId={workspaceId}
secretPath={secretPath}
onNavigateToFolder={handleResetFilter}
+ canNavigate={isFetched}
/>
)}
{canReadDynamicSecret && Boolean(dynamicSecrets?.length) && (
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/CompareEnvironments.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/CompareEnvironments.tsx
new file mode 100644
index 000000000..c3570bf5a
--- /dev/null
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/CompareEnvironments.tsx
@@ -0,0 +1,595 @@
+import { useCallback, useEffect, useRef, useState } from "react";
+import { MultiValue } from "react-select";
+import {
+ faArrowDown,
+ faArrowUp,
+ faCheckCircle,
+ faFilter,
+ faFingerprint,
+ faFolder,
+ faKey,
+ faRotate,
+ faSearch,
+ faWarning
+} from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { twMerge } from "tailwind-merge";
+
+import {
+ Badge,
+ Button,
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuTrigger,
+ EmptyState,
+ FilterableSelect,
+ FormLabel,
+ IconButton,
+ Input,
+ Lottie,
+ Pagination,
+ Table,
+ TableContainer,
+ TBody,
+ Th,
+ THead,
+ Tooltip,
+ Tr
+} from "@app/components/v2";
+import { useWorkspace } from "@app/context";
+import {
+ getUserTablePreference,
+ PreferenceKey,
+ setUserTablePreference
+} from "@app/helpers/userTablePreferences";
+import { useDebounce, usePagination, useResetPageHelper } from "@app/hooks";
+import { useGetImportedSecretsAllEnvs } from "@app/hooks/api";
+import { useGetProjectSecretsOverview } from "@app/hooks/api/dashboard";
+import { DashboardSecretsOrderBy } from "@app/hooks/api/dashboard/types";
+import { OrderByDirection } from "@app/hooks/api/generic/types";
+import { WorkspaceEnv } from "@app/hooks/api/workspace/types";
+import { useResizableColWidth } from "@app/hooks/useResizableColWidth";
+import {
+ useDynamicSecretOverview,
+ useFolderOverview,
+ useSecretOverview,
+ useSecretRotationOverview
+} from "@app/hooks/utils";
+import { SecretTableResourceCount } from "@app/pages/secret-manager/OverviewPage/components/SecretTableResourceCount";
+
+import { DynamicSecretRow } from "./components/DynamicSecretRow";
+import { FolderRow } from "./components/FolderRow";
+import { SecretRotationRow } from "./components/SecretRotationRow";
+import { SecretNoAccessRow, SecretRow } from "./components/SecretRow";
+
+type Props = {
+ secretPath: string;
+};
+
+enum RowType {
+ Folder = "folder",
+ DynamicSecret = "dynamic",
+ Secret = "secret",
+ SecretRotation = "rotation"
+}
+
+type Filter = {
+ [key in RowType]: boolean;
+};
+
+const DEFAULT_FILTER_STATE = {
+ [RowType.Folder]: false,
+ [RowType.DynamicSecret]: false,
+ [RowType.Secret]: false,
+ [RowType.SecretRotation]: false
+};
+
+const COL_WIDTH_OFFSET = 220;
+
+export const CompareEnvironments = ({ secretPath }: Props) => {
+ const { currentWorkspace } = useWorkspace();
+ const compareEnvironmentsKey = `compare-environments-${currentWorkspace.id}`;
+
+ const [selectedEnvironments, setSelectedEnvironments] = useState(() => {
+ try {
+ const storedEnvironments = JSON.parse(localStorage.getItem(compareEnvironmentsKey) ?? "[]");
+
+ if (Array.isArray(storedEnvironments) && storedEnvironments.length > 0) {
+ const potentialEnvs: string[] = [];
+ storedEnvironments.forEach((env) => {
+ if (typeof env === "string") {
+ potentialEnvs.push(env);
+ }
+ });
+
+ return currentWorkspace.environments.filter((env) => potentialEnvs.includes(env.id));
+ }
+ } catch {
+ // do nothing and proceed
+ }
+ return currentWorkspace.environments.slice(0, 2);
+ });
+
+ const [filter, setFilter] = useState(DEFAULT_FILTER_STATE);
+
+ const {
+ offset,
+ limit,
+ orderDirection,
+ setOrderDirection,
+ setPage,
+ perPage,
+ page,
+ setPerPage,
+ orderBy
+ } = usePagination(DashboardSecretsOrderBy.Name, {
+ initPerPage: getUserTablePreference("secretCompareTable", PreferenceKey.PerPage, 50)
+ });
+
+ const handlePerPageChange = (newPerPage: number) => {
+ setPerPage(newPerPage);
+ setUserTablePreference("secretCompareTable", PreferenceKey.PerPage, newPerPage);
+ };
+
+ const workspaceId = currentWorkspace.id;
+ const [searchFilter, setSearchFilter] = useState("");
+ const [debouncedSearchFilter] = useDebounce(searchFilter);
+ const [debouncedSelectedEnvironments] = useDebounce(selectedEnvironments);
+
+ useEffect(() => {
+ localStorage.setItem(
+ compareEnvironmentsKey,
+ JSON.stringify(selectedEnvironments.map((env) => env.id))
+ );
+ }, [debouncedSelectedEnvironments]);
+
+ const {
+ secretImports,
+ isImportedSecretPresentInEnv,
+ getImportedSecretByKey,
+ getEnvImportedSecretKeyCount
+ } = useGetImportedSecretsAllEnvs({
+ projectId: workspaceId,
+ path: secretPath,
+ environments: (currentWorkspace.environments || []).map(({ slug }) => slug)
+ });
+
+ const compareEnvironments = selectedEnvironments.length
+ ? selectedEnvironments
+ : currentWorkspace.environments;
+
+ const isFilteredByResources = Object.values(filter).some(Boolean);
+ const { isPending: isOverviewLoading, data: overview } = useGetProjectSecretsOverview(
+ {
+ projectId: workspaceId,
+ environments: compareEnvironments.map((env) => env.slug),
+ secretPath,
+ orderDirection,
+ orderBy,
+ includeFolders: isFilteredByResources ? filter.folder : true,
+ includeDynamicSecrets: isFilteredByResources ? filter.dynamic : true,
+ includeSecrets: isFilteredByResources ? filter.secret : true,
+ includeImports: true,
+ includeSecretRotations: isFilteredByResources ? filter.rotation : true,
+ search: debouncedSearchFilter,
+ limit,
+ offset
+ },
+ { enabled: Boolean(compareEnvironments.length) }
+ );
+
+ const {
+ secrets,
+ folders,
+ dynamicSecrets,
+ secretRotations,
+ totalFolderCount,
+ totalSecretCount,
+ totalDynamicSecretCount,
+ totalSecretRotationCount,
+ totalCount = 0,
+ totalUniqueFoldersInPage,
+ totalUniqueSecretsInPage,
+ totalUniqueSecretImportsInPage,
+ totalUniqueDynamicSecretsInPage,
+ totalUniqueSecretRotationsInPage
+ } = overview ?? {};
+
+ const secretImportsShaped = secretImports
+ ?.flatMap(({ data }) => data)
+ .filter(Boolean)
+ .flatMap((item) => item?.secrets || []);
+
+ const handleIsImportedSecretPresentInEnv = (envSlug: string, secretName: string) => {
+ if (secrets?.some((s) => s.key === secretName && s.env === envSlug)) {
+ return false;
+ }
+ if (secretImportsShaped.some((s) => s.key === secretName && s.sourceEnv === envSlug)) {
+ return true;
+ }
+ return isImportedSecretPresentInEnv(envSlug, secretName);
+ };
+
+ useResetPageHelper({
+ totalCount,
+ offset,
+ setPage
+ });
+
+ const { folderNamesAndDescriptions, isFolderPresentInEnv } = useFolderOverview(folders);
+
+ const { dynamicSecretNames, isDynamicSecretPresentInEnv } =
+ useDynamicSecretOverview(dynamicSecrets);
+
+ const { secretRotationNames, isSecretRotationPresentInEnv, getSecretRotationByName } =
+ useSecretRotationOverview(secretRotations);
+
+ const { secKeys, getEnvSecretKeyCount } = useSecretOverview(
+ secrets?.concat(secretImportsShaped) || []
+ );
+
+ const getSecretByKey = useCallback(
+ (env: string, key: string) => {
+ const sec = secrets?.find((s) => s.env === env && s.key === key);
+ return sec;
+ },
+ [secrets]
+ );
+
+ const [tableWidth, setTableWidth] = useState(0);
+ const tableRef = useRef(null);
+
+ const { handleMouseDown, isResizing, colWidth } = useResizableColWidth({
+ initialWidth: 320,
+ minWidth: 160,
+ maxWidth: tableRef.current
+ ? tableRef.current.clientWidth - COL_WIDTH_OFFSET // ensure value column can't collapse completely
+ : 800,
+ ref: tableRef
+ });
+
+ const handleToggleRowType = useCallback(
+ (rowType: RowType) =>
+ setFilter((state) => {
+ return {
+ ...state,
+ [rowType]: !state[rowType]
+ };
+ }),
+ []
+ );
+
+ const isTableEmpty = totalCount === 0;
+
+ const isTableFiltered = isFilteredByResources;
+
+ useEffect(() => {
+ const element = tableRef.current;
+ if (!element) return;
+
+ const handleResize = () => {
+ setTableWidth(element.clientWidth - 1);
+ };
+
+ const resizeObserver = new ResizeObserver(handleResize);
+ resizeObserver.observe(element);
+
+ // eslint-disable-next-line consistent-return
+ return () => {
+ resizeObserver.disconnect();
+ };
+ }, [tableRef]);
+
+ return (
+ // scott: this is reverse to fix z-indexing bug of dropdown with sticky table cols; couldn't resolve with flex-col
+
+ {!isOverviewLoading && totalCount > 0 && (
+
+ }
+ className="rounded-b-lg border border-solid border-mineshaft-500 bg-mineshaft-700"
+ count={totalCount}
+ page={page}
+ perPage={perPage}
+ onChangePage={(newPage) => setPage(newPage)}
+ onChangePerPage={handlePerPageChange}
+ />
+ )}
+
+
+ {/* eslint-disable-next-line no-nested-ternary */}
+ {isOverviewLoading ? (
+
+
+
+ ) : isTableEmpty ? (
+
+ ) : (
+
+
+
+
+
+
+
+
+ Name
+
+ setOrderDirection((prev) =>
+ prev === OrderByDirection.ASC
+ ? OrderByDirection.DESC
+ : OrderByDirection.ASC
+ )
+ }
+ >
+
+
+
+
+ |
+ {compareEnvironments?.map(({ name, slug }, index) => {
+ const envSecKeyCount = getEnvSecretKeyCount(slug);
+ const importedSecKeyCount = getEnvImportedSecretKeyCount(slug);
+ const missingKeyCount = secKeys.length - envSecKeyCount - importedSecKeyCount;
+
+ return (
+
+
+ {name}
+ {missingKeyCount > 0 && (
+
+ {missingKeyCount} secret{missingKeyCount > 1 ? "s" : ""} missing
+ compared to other environments on this page
+ >
+ }
+ >
+
+
+ {missingKeyCount}
+
+
+ )}
+
+ |
+ );
+ })}
+
+
+
+ {folderNamesAndDescriptions.map(({ name: folderName }, index) => (
+
+ ))}
+ {dynamicSecretNames.map((dynamicSecretName, index) => (
+
+ ))}
+ {secretRotationNames.map((secretRotationName, index) => (
+
+ ))}
+ {secKeys.map((key, index) => (
+
+ ))}
+ totalCount ? totalCount % perPage : perPage) -
+ (totalUniqueFoldersInPage || 0) -
+ (totalUniqueDynamicSecretsInPage || 0) -
+ (totalUniqueSecretsInPage || 0) -
+ (totalUniqueSecretImportsInPage || 0) -
+ (totalUniqueSecretRotationsInPage || 0),
+ 0
+ )}
+ />
+
+
+ )}
+
+
+
+
setSearchFilter(e.target.value)}
+ className="h-full flex-1"
+ placeholder="Search by resource name..."
+ leftIcon={
}
+ containerClassName="h-10"
+ />
+ {isTableFiltered && (
+
+ )}
+ {compareEnvironments.length > 0 && (
+
+
+
+ }
+ >
+ Filters
+
+
+
+ Filter by Resource
+ {
+ e.preventDefault();
+ handleToggleRowType(RowType.Folder);
+ }}
+ icon={filter[RowType.Folder] && }
+ iconPos="right"
+ >
+
+
+ Folders
+
+
+ {
+ e.preventDefault();
+ handleToggleRowType(RowType.DynamicSecret);
+ }}
+ icon={filter[RowType.DynamicSecret] && }
+ iconPos="right"
+ >
+
+
+ Dynamic Secrets
+
+
+ {
+ e.preventDefault();
+ handleToggleRowType(RowType.SecretRotation);
+ }}
+ icon={filter[RowType.SecretRotation] && }
+ iconPos="right"
+ >
+
+
+ Secret Rotations
+
+
+ {
+ e.preventDefault();
+ handleToggleRowType(RowType.Secret);
+ }}
+ icon={filter[RowType.Secret] && }
+ iconPos="right"
+ >
+
+
+ Secrets
+
+
+
+
+ )}
+
+
+
+ {
+ const selected = value as MultiValue;
+
+ setSelectedEnvironments((selected as WorkspaceEnv[]) ?? []);
+ }}
+ placeholder="Leave blank to compare all environments"
+ options={currentWorkspace.environments}
+ getOptionValue={(option) => option.slug}
+ getOptionLabel={(option) => option.name}
+ isMulti
+ />
+
+
+ );
+};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/DynamicSecretRow/DynamicSecretRow.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/DynamicSecretRow/DynamicSecretRow.tsx
new file mode 100644
index 000000000..16de6533b
--- /dev/null
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/DynamicSecretRow/DynamicSecretRow.tsx
@@ -0,0 +1,41 @@
+import { faFingerprint } from "@fortawesome/free-solid-svg-icons";
+
+import { Tr } from "@app/components/v2";
+
+import { EnvironmentStatusCell, ResourceNameCell } from "../shared";
+
+type Props = {
+ dynamicSecretName: string;
+ environments: { name: string; slug: string }[];
+ isDynamicSecretInEnv: (name: string, env: string) => boolean;
+ colWidth: number;
+};
+
+export const DynamicSecretRow = ({
+ dynamicSecretName,
+ environments = [],
+ isDynamicSecretInEnv,
+ colWidth
+}: Props) => {
+ return (
+
+
+ {environments.map(({ slug }, i) => {
+ const isPresent = isDynamicSecretInEnv(dynamicSecretName, slug);
+
+ return (
+
+ );
+ })}
+
+ );
+};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/DynamicSecretRow/index.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/DynamicSecretRow/index.tsx
new file mode 100644
index 000000000..5c7bf445a
--- /dev/null
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/DynamicSecretRow/index.tsx
@@ -0,0 +1 @@
+export { DynamicSecretRow } from "./DynamicSecretRow";
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/FolderRow/FolderRow.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/FolderRow/FolderRow.tsx
new file mode 100644
index 000000000..a3d672e13
--- /dev/null
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/FolderRow/FolderRow.tsx
@@ -0,0 +1,41 @@
+import { faFolder } from "@fortawesome/free-solid-svg-icons";
+
+import { Tr } from "@app/components/v2";
+
+import { EnvironmentStatusCell, ResourceNameCell } from "../shared";
+
+type Props = {
+ folderName: string;
+ environments: { name: string; slug: string }[];
+ isFolderPresentInEnv: (name: string, env: string) => boolean;
+ colWidth: number;
+};
+
+export const FolderRow = ({
+ folderName,
+ environments = [],
+ isFolderPresentInEnv,
+ colWidth
+}: Props) => {
+ return (
+
+
+ {environments.map(({ slug }, i) => {
+ const isPresent = isFolderPresentInEnv(folderName, slug);
+
+ return (
+
+ );
+ })}
+
+ );
+};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/FolderRow/index.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/FolderRow/index.tsx
new file mode 100644
index 000000000..171b2e596
--- /dev/null
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/FolderRow/index.tsx
@@ -0,0 +1 @@
+export { FolderRow } from "./FolderRow";
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/SecretRotationRow/SecretRotationRow.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/SecretRotationRow/SecretRotationRow.tsx
new file mode 100644
index 000000000..d5c938e9b
--- /dev/null
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/SecretRotationRow/SecretRotationRow.tsx
@@ -0,0 +1,179 @@
+import { faEye, faEyeSlash, faInfoCircle, faRotate } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { twMerge } from "tailwind-merge";
+
+import { IconButton, TableContainer, Tag, Td, Tooltip, Tr } from "@app/components/v2";
+import { Blur } from "@app/components/v2/Blur";
+import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
+import { SECRET_ROTATION_MAP } from "@app/helpers/secretRotationsV2";
+import { useToggle } from "@app/hooks";
+import { TSecretRotationV2 } from "@app/hooks/api/secretRotationsV2";
+
+import { EnvironmentStatusCell, ResourceNameCell } from "../shared";
+
+type Props = {
+ secretRotationName: string;
+ environments: { name: string; slug: string }[];
+ isSecretRotationInEnv: (name: string, env: string) => boolean;
+ getSecretRotationByName: (slug: string, name: string) => TSecretRotationV2 | undefined;
+ colWidth: number;
+ tableWidth: number;
+};
+
+export const SecretRotationRow = ({
+ secretRotationName,
+ environments = [],
+ isSecretRotationInEnv,
+ colWidth,
+ getSecretRotationByName,
+ tableWidth
+}: Props) => {
+ const [isExpanded, setIsExpanded] = useToggle(false);
+ const [isSecretVisible, setIsSecretVisible] = useToggle();
+
+ const totalCols = environments.length + 1; // secret key row
+
+ return (
+ <>
+
+
+ {environments.map(({ slug }, i) => {
+ const isPresent = isSecretRotationInEnv(secretRotationName, slug);
+
+ return (
+
+ );
+ })}
+
+ {isExpanded &&
+ environments.map(({ name: envName, slug }) => {
+ const secretRotation = getSecretRotationByName(slug, secretRotationName);
+
+ if (!secretRotation) return null;
+
+ const { type, secrets, description } = secretRotation;
+
+ const { name: rotationType, image } = SECRET_ROTATION_MAP[type];
+
+ return (
+
+
+
+
+
+
+ {envName}
+
+
+ {rotationType}
+
+ {description && (
+
+
+
+ )}
+
+
+
+ setIsSecretVisible.toggle()}
+ >
+
+
+
+
+
+
+
+ {secrets.map((secret, index) => {
+ return (
+
+
+ |
+
+
+ {secret?.key ?? "********"}
+
+
+ |
+
+ {/* eslint-disable-next-line no-nested-ternary */}
+ {!secret ? (
+ ********
+ ) : secret.secretValueHidden ? (
+
+ ) : (
+ {}}
+ />
+ )}
+ |
+
+
+ );
+ })}
+
+
+
+
+ |
+
+ );
+ })}
+ >
+ );
+};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/SecretRotationRow/index.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/SecretRotationRow/index.tsx
new file mode 100644
index 000000000..a0b2fcfa7
--- /dev/null
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/SecretRotationRow/index.tsx
@@ -0,0 +1 @@
+export { SecretRotationRow } from "./SecretRotationRow";
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/SecretRow/EnvironmentSecretRow.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/SecretRow/EnvironmentSecretRow.tsx
new file mode 100644
index 000000000..87db191f6
--- /dev/null
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/SecretRow/EnvironmentSecretRow.tsx
@@ -0,0 +1,49 @@
+import { faEyeSlash } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+
+import { Tooltip } from "@app/components/v2";
+import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
+
+type Props = {
+ defaultValue?: string | null;
+ isOverride?: boolean;
+ isVisible?: boolean;
+ isImportedSecret: boolean;
+ environment: string;
+ secretValueHidden: boolean;
+ secretPath: string;
+};
+
+export const EnvironmentSecretRow = ({
+ defaultValue,
+ isOverride,
+ isImportedSecret,
+ secretValueHidden,
+ environment,
+ secretPath,
+ isVisible
+}: Props) => {
+ return (
+
+ {secretValueHidden && !isOverride && (
+
+
+
+ )}
+
+ {}}
+ isReadOnly
+ value={defaultValue as string}
+ key="secret-input"
+ isVisible={isVisible && !secretValueHidden}
+ secretPath={secretPath}
+ environment={environment}
+ isImport={isImportedSecret}
+ defaultValue={secretValueHidden ? "" : undefined}
+ canEditButNotView={secretValueHidden && !isOverride}
+ />
+
+
+ );
+};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/SecretRow/SecretNoAccessRow.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/SecretRow/SecretNoAccessRow.tsx
new file mode 100644
index 000000000..075726d1b
--- /dev/null
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/SecretRow/SecretNoAccessRow.tsx
@@ -0,0 +1,44 @@
+import { faLock } from "@fortawesome/free-solid-svg-icons";
+
+import { Tr } from "@app/components/v2";
+import { Blur } from "@app/components/v2/Blur";
+
+import { EnvironmentStatusCell, ResourceNameCell } from "../shared";
+
+type Props = {
+ environments: { name: string; slug: string }[];
+ count: number;
+ colWidth: number;
+};
+
+export const SecretNoAccessRow = ({ environments = [], count, colWidth }: Props) => {
+ return (
+ <>
+ {Array.from(Array(count)).map((_, j) => (
+
+ }
+ iconClassName="text-bunker-400"
+ icon={faLock}
+ colWidth={colWidth}
+ tooltipContent="You do not have permission to view this secret"
+ />
+ {environments.map(({ slug }, i) => {
+ return (
+
+ );
+ })}
+
+ ))}
+ >
+ );
+};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/SecretRow/SecretRow.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/SecretRow/SecretRow.tsx
new file mode 100644
index 000000000..095b50356
--- /dev/null
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/SecretRow/SecretRow.tsx
@@ -0,0 +1,231 @@
+import { subject } from "@casl/ability";
+import {
+ faCodeBranch,
+ faEye,
+ faEyeSlash,
+ faFileImport,
+ faKey,
+ faRotate
+} from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+
+import { IconButton, TableContainer, Td, Tooltip, Tr } from "@app/components/v2";
+import { useProjectPermission } from "@app/context";
+import {
+ ProjectPermissionSecretActions,
+ ProjectPermissionSub
+} from "@app/context/ProjectPermissionContext/types";
+import { useToggle } from "@app/hooks";
+import { SecretV3RawSanitized } from "@app/hooks/api/secrets/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
+import { HIDDEN_SECRET_VALUE } from "@app/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem";
+
+import { EnvironmentStatus, EnvironmentStatusCell, ResourceNameCell } from "../shared";
+import { EnvironmentSecretRow } from "./EnvironmentSecretRow";
+
+type Props = {
+ secretKey: string;
+ secretPath: string;
+ environments: { name: string; slug: string }[];
+ getSecretByKey: (slug: string, key: string) => SecretV3RawSanitized | undefined;
+ isImportedSecretPresentInEnv: (env: string, secretName: string) => boolean;
+ getImportedSecretByKey: (
+ env: string,
+ secretName: string
+ ) => { secret?: SecretV3RawSanitized; environmentInfo?: WorkspaceEnv } | undefined;
+ colWidth: number;
+ tableWidth: number;
+};
+
+export const SecretRow = ({
+ secretKey,
+ environments = [],
+ secretPath,
+ getSecretByKey,
+ isImportedSecretPresentInEnv,
+ getImportedSecretByKey,
+ colWidth,
+ tableWidth
+}: Props) => {
+ const [isFormExpanded, setIsFormExpanded] = useToggle();
+ const totalCols = environments.length + 1; // secret key row
+ const [isSecretVisible, setIsSecretVisible] = useToggle();
+
+ const { permission } = useProjectPermission();
+
+ const getDefaultValue = (
+ secret: SecretV3RawSanitized | undefined,
+ importedSecret: { secret?: SecretV3RawSanitized } | undefined
+ ) => {
+ const canEditSecretValue = permission.can(
+ ProjectPermissionSecretActions.Edit,
+ subject(ProjectPermissionSub.Secrets, {
+ environment: secret?.env || "",
+ secretPath: secret?.path || "",
+ secretName: secret?.key || "",
+ secretTags: ["*"]
+ })
+ );
+
+ if (secret?.secretValueHidden && !secret?.valueOverride) {
+ return canEditSecretValue ? HIDDEN_SECRET_VALUE : "";
+ }
+ return secret?.valueOverride || secret?.value || importedSecret?.secret?.value || "";
+ };
+
+ return (
+ <>
+ setIsFormExpanded.toggle()}
+ className="group border-mineshaft-500"
+ >
+
+ {environments.map(({ slug }, i) => {
+ const secret = getSecretByKey(slug, secretKey);
+
+ const isSecretImported = isImportedSecretPresentInEnv(slug, secretKey);
+
+ const isSecretPresent = Boolean(secret);
+ const isSecretEmpty = secret?.value === "";
+
+ let status: EnvironmentStatus;
+
+ if (isSecretEmpty) {
+ status = "empty";
+ } else if (isSecretPresent) {
+ status = "present";
+ } else if (isSecretImported) {
+ status = "imported";
+ } else {
+ status = "missing";
+ }
+
+ return (
+
+ );
+ })}
+
+ {isFormExpanded && (
+
+
+
+
+
+
+
+ |
+ Environment
+ |
+
+ Value
+ |
+
+
+ setIsSecretVisible.toggle()}
+ >
+
+
+
+
+
+
+
+ {environments.map(({ name, slug }) => {
+ const secret = getSecretByKey(slug, secretKey);
+
+ const isImportedSecret = isImportedSecretPresentInEnv(slug, secretKey);
+ const importedSecret = getImportedSecretByKey(slug, secretKey);
+
+ return (
+
+ |
+
+ {name}
+ {isImportedSecret && (
+
+
+
+ )}
+ {secret?.isRotatedSecret && (
+
+
+
+ )}
+ {secret?.valueOverride && (
+
+
+
+ )}
+
+ |
+
+
+ |
+
+ );
+ })}
+
+
+
+
+ |
+
+ )}
+ >
+ );
+};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/SecretRow/index.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/SecretRow/index.tsx
new file mode 100644
index 000000000..a1e4a3d51
--- /dev/null
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/SecretRow/index.tsx
@@ -0,0 +1,2 @@
+export { SecretNoAccessRow } from "./SecretNoAccessRow";
+export { SecretRow } from "./SecretRow";
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/shared/EnvironmentStatusCell.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/shared/EnvironmentStatusCell.tsx
new file mode 100644
index 000000000..fa5d2f8bb
--- /dev/null
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/shared/EnvironmentStatusCell.tsx
@@ -0,0 +1,75 @@
+import { IconDefinition } from "@fortawesome/free-brands-svg-icons";
+import { faCircle } from "@fortawesome/free-regular-svg-icons";
+import { faBan, faCheck, faFileImport, faXmark } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { twMerge } from "tailwind-merge";
+
+import { Td, Tooltip } from "@app/components/v2";
+
+export type EnvironmentStatus = "present" | "missing" | "empty" | "imported" | "no-access";
+
+type Props = {
+ isLast: boolean;
+ status: EnvironmentStatus;
+};
+
+export const EnvironmentStatusCell = ({ isLast, status }: Props) => {
+ let tooltipContent: string;
+ let icon: IconDefinition;
+ let iconClassName: string;
+
+ switch (status) {
+ case "present":
+ tooltipContent = "Present in environment";
+ icon = faCheck;
+ iconClassName = "h-3 w-3";
+ break;
+ case "missing":
+ tooltipContent = "Missing from environment";
+ icon = faXmark;
+ iconClassName = "h-3.5 w-3.5";
+ break;
+ case "empty":
+ tooltipContent = "Empty value in environment";
+ icon = faCircle;
+ iconClassName = "h-3 w-3";
+ break;
+ case "imported":
+ tooltipContent = "Imported into environment";
+ icon = faFileImport;
+ iconClassName = "h-3 w-3";
+ break;
+ case "no-access":
+ tooltipContent = "You do not have permission to view this secret";
+ icon = faBan;
+ iconClassName = "h-3 w-3";
+ break;
+ default:
+ throw new Error(`Unhandled environment status: ${status as string}`);
+ }
+
+ return (
+
+
+ |
+ );
+};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/shared/ResourceNameCell.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/shared/ResourceNameCell.tsx
new file mode 100644
index 000000000..079b563d5
--- /dev/null
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/shared/ResourceNameCell.tsx
@@ -0,0 +1,47 @@
+import { ReactElement } from "react";
+import { IconDefinition } from "@fortawesome/free-brands-svg-icons";
+import { faAngleDown } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+
+import { Td, Tooltip } from "@app/components/v2";
+
+type Props = {
+ isRowExpanded?: boolean;
+ label: ReactElement | string;
+ icon: IconDefinition;
+ iconClassName?: string;
+ colWidth: number;
+ tooltipContent?: string;
+};
+
+export const ResourceNameCell = ({
+ isRowExpanded,
+ label,
+ icon,
+ iconClassName,
+ colWidth,
+ tooltipContent
+}: Props) => {
+ return (
+
+
+
+
+
+
+ {typeof label === "string" ? {label} : label}
+
+
+ |
+ );
+};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/shared/index.ts b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/shared/index.ts
new file mode 100644
index 000000000..c8bd53cbe
--- /dev/null
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/components/shared/index.ts
@@ -0,0 +1,2 @@
+export * from "./EnvironmentStatusCell";
+export * from "./ResourceNameCell";
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/index.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/index.tsx
new file mode 100644
index 000000000..f5c997972
--- /dev/null
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/index.tsx
@@ -0,0 +1 @@
+export * from "./CompareEnvironments";
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/EnvironmentTabs/EnvironmentTabs.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/EnvironmentTabs/EnvironmentTabs.tsx
new file mode 100644
index 000000000..66fd3a878
--- /dev/null
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/EnvironmentTabs/EnvironmentTabs.tsx
@@ -0,0 +1,242 @@
+import { useState } from "react";
+import { faArrowRightArrowLeft, faEllipsisH, faPlus } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { useQueryClient } from "@tanstack/react-query";
+import { useNavigate, useParams } from "@tanstack/react-router";
+
+import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
+import { ProjectPermissionCan } from "@app/components/permissions";
+import {
+ Button,
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuTrigger,
+ Modal,
+ ModalContent,
+ Tab,
+ TabList,
+ Tabs,
+ Tooltip
+} from "@app/components/v2";
+import { ROUTE_PATHS } from "@app/const/routes";
+import {
+ ProjectPermissionActions,
+ ProjectPermissionSub,
+ useSubscription,
+ useWorkspace
+} from "@app/context";
+import { usePopUp } from "@app/hooks";
+import { workspaceKeys } from "@app/hooks/api";
+import { WorkspaceEnv } from "@app/hooks/api/workspace/types";
+import { AddEnvironmentModal } from "@app/pages/secret-manager/SettingsPage/components/EnvironmentSection/AddEnvironmentModal";
+
+import { CompareEnvironments } from "../CompareEnvironments";
+
+const COMPARE_ENVIRONMENT_TAB = "__COMPARE_ENVIRONMENT_TAB__";
+const ADD_ENVIRONMENT_TAB = "__ADD_ENVIRONMENT_TAB__";
+const VIEW_MORE_ENVIRONMENT_TAB = "__VIEW_MORE_ENVIRONMENT_TAB__";
+
+type Props = {
+ secretPath: string;
+};
+
+const TABS_TO_SHOW = 5;
+
+export const EnvironmentTabs = ({ secretPath }: Props) => {
+ const { currentWorkspace } = useWorkspace();
+ const currentEnv = useParams({
+ from: ROUTE_PATHS.SecretManager.SecretDashboardPage.id,
+ select: (el) => el.envSlug
+ });
+
+ const { subscription } = useSubscription();
+
+ const isMoreEnvironmentsAllowed =
+ subscription?.environmentLimit && currentWorkspace?.environments
+ ? currentWorkspace.environments.length < subscription.environmentLimit
+ : true;
+
+ const [isNavigating, setIsNavigating] = useState(false);
+
+ const navigate = useNavigate();
+
+ const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
+ "compareEnvironments",
+ "createEnvironment",
+ "upgradePlan"
+ ] as const);
+
+ const selectedIndex = currentWorkspace.environments.findIndex((env) => env.slug === currentEnv);
+
+ let tabEnvironments: WorkspaceEnv[];
+ let dropdownEnvironments: WorkspaceEnv[];
+
+ if (selectedIndex < TABS_TO_SHOW) {
+ tabEnvironments = currentWorkspace.environments.slice(0, TABS_TO_SHOW);
+ dropdownEnvironments = currentWorkspace.environments.slice(TABS_TO_SHOW);
+ } else {
+ tabEnvironments = [
+ ...currentWorkspace.environments.slice(0, TABS_TO_SHOW - 1),
+ currentWorkspace.environments[selectedIndex]
+ ];
+ dropdownEnvironments = currentWorkspace.environments
+ .slice(TABS_TO_SHOW - 1)
+ .filter((env) => env.slug !== currentEnv);
+ }
+
+ const queryClient = useQueryClient();
+
+ const handleSelect = async (envSlug: string) => {
+ if (isNavigating) return;
+
+ setIsNavigating(true);
+ await navigate({
+ to: ROUTE_PATHS.SecretManager.SecretDashboardPage.path,
+ params: {
+ envSlug,
+ projectId: currentWorkspace.id
+ },
+ search: (prev) => prev
+ });
+ setIsNavigating(false);
+ };
+
+ const handleAddEnvironment = () => {
+ if (isMoreEnvironmentsAllowed) {
+ handlePopUpOpen("createEnvironment");
+ } else {
+ handlePopUpOpen("upgradePlan");
+ }
+ };
+
+ return (
+ <>
+ {
+ if (value === COMPARE_ENVIRONMENT_TAB) {
+ handlePopUpOpen("compareEnvironments");
+ return;
+ }
+
+ if (value === ADD_ENVIRONMENT_TAB) {
+ handleAddEnvironment();
+ return;
+ }
+
+ handleSelect(value);
+ }}
+ defaultValue="environment-tabs"
+ >
+
+ {tabEnvironments.map((environment) => (
+
+ {environment.name}
+
+ ))}
+ {dropdownEnvironments.length ? (
+
+
+
+
+
+
+
+
+
+
+
+ Environments
+
+ {dropdownEnvironments.map((environment) => (
+ {
+ e.stopPropagation();
+ handleSelect(environment.slug);
+ }}
+ >
+ {environment.name}
+
+ ))}
+
+
+
+ {(isAllowed) => (
+
+ }
+ className="w-full"
+ >
+ Add Environment
+
+
+ )}
+
+
+
+ ) : (
+
+
+
+
+
+
+
+ )}
+ {currentWorkspace.environments.length > 1 && (
+
+
+
+ Compare Environments
+
+
+ )}
+
+
+ handlePopUpToggle("compareEnvironments", isOpen)}
+ >
+
+
+
+
+ handlePopUpToggle("upgradePlan", isOpen)}
+ text="You can add custom environments if you switch to Infisical's Team plan."
+ />
+ handlePopUpToggle("createEnvironment", isOpen)}
+ onComplete={async (env) => {
+ await queryClient.refetchQueries({
+ queryKey: workspaceKeys.getWorkspaceById(currentWorkspace.id)
+ });
+ handleSelect(env.slug);
+ }}
+ />
+ >
+ );
+};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/EnvironmentTabs/index.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/EnvironmentTabs/index.tsx
new file mode 100644
index 000000000..76be6609b
--- /dev/null
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/EnvironmentTabs/index.tsx
@@ -0,0 +1 @@
+export * from "./EnvironmentTabs";
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/FolderBreadCrumbs/FolderBreadCrumbs.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/FolderBreadCrumbs/FolderBreadCrumbs.tsx
new file mode 100644
index 000000000..a2169fa5a
--- /dev/null
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/FolderBreadCrumbs/FolderBreadCrumbs.tsx
@@ -0,0 +1,52 @@
+import { faFolderOpen } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { useNavigate } from "@tanstack/react-router";
+
+type Props = {
+ secretPath: string;
+};
+
+export const FolderBreadCrumbs = ({ secretPath = "/" }: Props) => {
+ const navigate = useNavigate({
+ from: "/projects/secret-management/$projectId/secrets/$envSlug"
+ });
+
+ const onFolderCrumbClick = (index: number) => {
+ const newSecPath = `/${secretPath.split("/").filter(Boolean).slice(0, index).join("/")}`;
+ if (secretPath === newSecPath) return;
+ navigate({
+ search: (prev) => ({ ...prev, secretPath: newSecPath })
+ });
+ };
+
+ return (
+
+
onFolderCrumbClick(0)}
+ onKeyDown={() => null}
+ role="button"
+ tabIndex={0}
+ >
+
+
+ {(secretPath || "")
+ .split("/")
+ .filter(Boolean)
+ .map((path, index, arr) => (
+
onFolderCrumbClick(index + 1)}
+ onKeyDown={() => null}
+ role="button"
+ tabIndex={0}
+ >
+ {path}
+
+ ))}
+
+ );
+};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/FolderBreadCrumbs/index.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/FolderBreadCrumbs/index.tsx
new file mode 100644
index 000000000..8224cdb25
--- /dev/null
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/FolderBreadCrumbs/index.tsx
@@ -0,0 +1 @@
+export { FolderBreadCrumbs } from "./FolderBreadCrumbs";
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/FolderListView/FolderListView.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/FolderListView/FolderListView.tsx
index 9b8c9ccaa..fd65bc639 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/FolderListView/FolderListView.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/FolderListView/FolderListView.tsx
@@ -36,6 +36,7 @@ type Props = {
workspaceId: string;
secretPath?: string;
onNavigateToFolder: (path: string) => void;
+ canNavigate: boolean;
};
export const FolderListView = ({
@@ -43,7 +44,8 @@ export const FolderListView = ({
environment,
workspaceId,
secretPath = "/",
- onNavigateToFolder
+ onNavigateToFolder,
+ canNavigate
}: Props) => {
const { popUp, handlePopUpToggle, handlePopUpOpen, handlePopUpClose } = usePopUp([
"updateFolder",
@@ -190,7 +192,7 @@ export const FolderListView = ({
};
const handleFolderClick = (name: string, isPending?: boolean) => {
- if (isPending) {
+ if (isPending || !canNavigate) {
return;
}
const path = `${secretPathQueryparam === "/" ? "" : secretPathQueryparam}/${name}`;
diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/AddEnvironmentModal.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/AddEnvironmentModal.tsx
index 61841165b..0832dfe9d 100644
--- a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/AddEnvironmentModal.tsx
+++ b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/AddEnvironmentModal.tsx
@@ -3,16 +3,16 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
-import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2";
+import { Button, FormControl, Input, Modal, ModalClose, ModalContent } from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { useCreateWsEnvironment } from "@app/hooks/api";
-import { UsePopUpState } from "@app/hooks/usePopUp";
+import { WorkspaceEnv } from "@app/hooks/api/workspace/types";
import { slugSchema } from "@app/lib/schemas";
type Props = {
- popUp: UsePopUpState<["createEnv"]>;
- handlePopUpClose: (popUpName: keyof UsePopUpState<["createEnv"]>) => void;
- handlePopUpToggle: (popUpName: keyof UsePopUpState<["createEnv"]>, state?: boolean) => void;
+ isOpen: boolean;
+ onOpenChange: (isOpen: boolean) => void;
+ onComplete?: (environment: WorkspaceEnv) => void;
};
const schema = z.object({
@@ -24,10 +24,14 @@ const schema = z.object({
export type FormData = z.infer;
-export const AddEnvironmentModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) => {
+type ContentProps = {
+ onComplete: (environment: WorkspaceEnv) => void;
+};
+
+const Content = ({ onComplete }: ContentProps) => {
const { currentWorkspace } = useWorkspace();
const { mutateAsync, isPending } = useCreateWsEnvironment();
- const { control, handleSubmit, reset } = useForm({
+ const { control, handleSubmit } = useForm({
resolver: zodResolver(schema)
});
@@ -35,7 +39,7 @@ export const AddEnvironmentModal = ({ popUp, handlePopUpClose, handlePopUpToggle
try {
if (!currentWorkspace?.id) return;
- await mutateAsync({
+ const env = await mutateAsync({
workspaceId: currentWorkspace.id,
name: environmentName,
slug: environmentSlug
@@ -46,7 +50,7 @@ export const AddEnvironmentModal = ({ popUp, handlePopUpClose, handlePopUpToggle
type: "success"
});
- handlePopUpClose("createEnv");
+ onComplete(env);
} catch (err) {
console.error(err);
createNotification({
@@ -57,64 +61,62 @@ export const AddEnvironmentModal = ({ popUp, handlePopUpClose, handlePopUpToggle
};
return (
- {
- handlePopUpToggle("createEnv", isOpen);
- reset();
- }}
- >
-
-
+export const AddEnvironmentModal = ({ onComplete, ...props }: Props) => {
+ return (
+
+
+ {
+ if (onComplete) onComplete(env);
+ props.onOpenChange(false);
+ }}
+ />
);
diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentSection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentSection.tsx
index 323fe2640..34acf0c7b 100644
--- a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentSection.tsx
+++ b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentSection.tsx
@@ -103,9 +103,8 @@ export const EnvironmentSection = () => {
)}
handlePopUpToggle("createEnv", isOpen)}
/>