import { useCallback, useEffect, useMemo, useState } from "react"; import { Helmet } from "react-helmet"; import { useTranslation } from "react-i18next"; import { subject } from "@casl/ability"; import { faCheckCircle } from "@fortawesome/free-regular-svg-icons"; import { faAngleDown, faArrowDown, faArrowUp, faFileImport, faFingerprint, faFolder, faFolderBlank, faFolderPlus, faKey, faList, faPlus, faRotate } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Link, useNavigate, useRouter, useSearch } from "@tanstack/react-router"; import { twMerge } from "tailwind-merge"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { CreateSecretRotationV2Modal } from "@app/components/secret-rotations-v2"; import { DeleteSecretRotationV2Modal } from "@app/components/secret-rotations-v2/DeleteSecretRotationV2Modal"; import { EditSecretRotationV2Modal } from "@app/components/secret-rotations-v2/EditSecretRotationV2Modal"; import { RotateSecretRotationV2Modal } from "@app/components/secret-rotations-v2/RotateSecretRotationV2Modal"; import { ViewSecretRotationV2GeneratedCredentialsModal } from "@app/components/secret-rotations-v2/ViewSecretRotationV2GeneratedCredentials"; import { Button, Checkbox, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger, EmptyState, IconButton, Modal, ModalContent, PageHeader, Pagination, Table, TableContainer, TableSkeleton, TBody, Td, TFoot, Th, THead, Tooltip, Tr } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; import { ProjectPermissionActions, ProjectPermissionDynamicSecretActions, ProjectPermissionSub, useProjectPermission, useSubscription, useWorkspace } from "@app/context"; import { ProjectPermissionSecretRotationActions } from "@app/context/ProjectPermissionContext/types"; import { getUserTablePreference, PreferenceKey, setUserTablePreference } from "@app/helpers/userTablePreferences"; import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks"; import { useCreateFolder, useCreateSecretV3, useDeleteSecretV3, useGetImportedSecretsAllEnvs, useGetWsTags, useUpdateSecretV3 } from "@app/hooks/api"; import { useGetProjectSecretsOverview } from "@app/hooks/api/dashboard/queries"; import { DashboardSecretsOrderBy, ProjectSecretsImportedBy } from "@app/hooks/api/dashboard/types"; import { OrderByDirection } from "@app/hooks/api/generic/types"; import { useUpdateFolderBatch } from "@app/hooks/api/secretFolders/queries"; import { TUpdateFolderBatchDTO } from "@app/hooks/api/secretFolders/types"; import { TSecretRotationV2 } from "@app/hooks/api/secretRotationsV2"; import { SecretType, SecretV3RawSanitized, TSecretFolder } from "@app/hooks/api/types"; import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types"; import { useDynamicSecretOverview, useFolderOverview, useSecretOverview, useSecretRotationOverview } from "@app/hooks/utils"; import { SecretOverviewSecretRotationRow } from "@app/pages/secret-manager/OverviewPage/components/SecretOverviewSecretRotationRow"; import { CreateDynamicSecretForm } from "../SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm"; import { FolderForm } from "../SecretDashboardPage/components/ActionBar/FolderForm"; import { CreateSecretForm } from "./components/CreateSecretForm"; import { FolderBreadCrumbs } from "./components/FolderBreadCrumbs"; import { SecretOverviewDynamicSecretRow } from "./components/SecretOverviewDynamicSecretRow"; import { SecretOverviewFolderRow } from "./components/SecretOverviewFolderRow"; import { SecretNoAccessOverviewTableRow, SecretOverviewTableRow } from "./components/SecretOverviewTableRow"; import { SecretSearchInput } from "./components/SecretSearchInput"; import { SecretTableResourceCount } from "./components/SecretTableResourceCount"; import { SecretV2MigrationSection } from "./components/SecretV2MigrationSection"; import { SelectionPanel } from "./components/SelectionPanel/SelectionPanel"; export enum EntryType { FOLDER = "folder", SECRET = "secret" } enum RowType { Folder = "folder", DynamicSecret = "dynamic", Secret = "secret", Import = "import", SecretRotation = "rotation" } type Filter = { [key in RowType]: boolean; }; const DEFAULT_FILTER_STATE = { [RowType.Folder]: true, [RowType.DynamicSecret]: true, [RowType.Secret]: true, [RowType.Import]: true, [RowType.SecretRotation]: true }; export const OverviewPage = () => { const { t } = useTranslation(); const router = useRouter(); const navigate = useNavigate({ from: ROUTE_PATHS.SecretManager.OverviewPage.path }); const routerSearch = useSearch({ from: ROUTE_PATHS.SecretManager.OverviewPage.id, select: (el) => ({ secretPath: el.secretPath, search: el.search }) }); const [scrollOffset, setScrollOffset] = useState(0); const [debouncedScrollOffset] = useDebounce(scrollOffset); const { permission } = useProjectPermission(); const { currentWorkspace } = useWorkspace(); const isProjectV3 = currentWorkspace?.version === ProjectVersion.V3; const workspaceId = currentWorkspace?.id as string; const projectSlug = currentWorkspace?.slug as string; const [searchFilter, setSearchFilter] = useState(""); const [debouncedSearchFilter, setDebouncedSearchFilter] = useDebounce(searchFilter); const secretPath = (routerSearch?.secretPath as string) || "/"; const { subscription } = useSubscription(); const [filter, setFilter] = useState(DEFAULT_FILTER_STATE); const [filterHistory, setFilterHistory] = useState< Map >(new Map()); const [selectedEntries, setSelectedEntries] = useState<{ // selectedEntries[name/key][envSlug][resource] [EntryType.FOLDER]: Record>; [EntryType.SECRET]: Record>; }>({ [EntryType.FOLDER]: {}, [EntryType.SECRET]: {} }); const { offset, limit, orderDirection, setOrderDirection, setPage, perPage, page, setPerPage, orderBy } = usePagination(DashboardSecretsOrderBy.Name, { initPerPage: getUserTablePreference("secretOverviewTable", PreferenceKey.PerPage, 100) }); const handlePerPageChange = (newPerPage: number) => { setPerPage(newPerPage); setUserTablePreference("secretOverviewTable", PreferenceKey.PerPage, newPerPage); }; const resetSelectedEntries = useCallback(() => { setSelectedEntries({ [EntryType.FOLDER]: {}, [EntryType.SECRET]: {} }); }, []); useEffect(() => { const onRouteChangeStart = () => { resetSelectedEntries(); }; const unsubscribeRouterEvent = router.subscribe("onLoad", onRouteChangeStart); return () => { unsubscribeRouterEvent(); }; }, []); const userAvailableEnvs = currentWorkspace?.environments || []; const userAvailableDynamicSecretEnvs = userAvailableEnvs.filter((env) => permission.can( ProjectPermissionDynamicSecretActions.CreateRootCredential, subject(ProjectPermissionSub.DynamicSecrets, { environment: env.slug, secretPath, metadata: ["*"] }) ) ); const userAvailableSecretRotationEnvs = userAvailableEnvs.filter((env) => permission.can( ProjectPermissionSecretRotationActions.Create, subject(ProjectPermissionSub.SecretRotation, { environment: env.slug, secretPath }) ) ); const [visibleEnvs, setVisibleEnvs] = useState(userAvailableEnvs); useEffect(() => { setVisibleEnvs(userAvailableEnvs); }, [userAvailableEnvs]); const { secretImports, isImportedSecretPresentInEnv, getImportedSecretByKey, getEnvImportedSecretKeyCount } = useGetImportedSecretsAllEnvs({ projectId: workspaceId, path: secretPath, environments: (userAvailableEnvs || []).map(({ slug }) => slug) }); const { isPending: isOverviewLoading, data: overview } = useGetProjectSecretsOverview( { projectId: workspaceId, environments: visibleEnvs.map((env) => env.slug), secretPath, orderDirection, orderBy, includeFolders: filter.folder, includeDynamicSecrets: filter.dynamic, includeSecrets: filter.secret, includeImports: filter.import, includeSecretRotations: filter.rotation, search: debouncedSearchFilter, limit, offset }, { enabled: isProjectV3 } ); const { secrets, folders, dynamicSecrets, secretRotations, totalFolderCount, totalSecretCount, totalDynamicSecretCount, totalSecretRotationCount, totalImportCount, totalCount = 0, totalUniqueFoldersInPage, totalUniqueSecretsInPage, totalUniqueSecretImportsInPage, totalUniqueDynamicSecretsInPage, totalUniqueSecretRotationsInPage, importedByEnvs, usedBySecretSyncs } = 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, getFolderByNameAndEnv, isFolderPresentInEnv } = useFolderOverview(folders); const { dynamicSecretNames, isDynamicSecretPresentInEnv } = useDynamicSecretOverview(dynamicSecrets); const { secretRotationNames, isSecretRotationPresentInEnv, getSecretRotationByName, getSecretRotationStatusesByName } = 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 { data: tags } = useGetWsTags( permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags) ? workspaceId : "" ); const { mutateAsync: createSecretV3 } = useCreateSecretV3(); const { mutateAsync: updateSecretV3 } = useUpdateSecretV3(); const { mutateAsync: deleteSecretV3 } = useDeleteSecretV3(); const { mutateAsync: createFolder } = useCreateFolder(); const { mutateAsync: updateFolderBatch } = useUpdateFolderBatch(); const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([ "addSecretsInAllEnvs", "addFolder", "misc", "updateFolder", "addDynamicSecret", "addSecretRotation", "editSecretRotation", "rotateSecretRotation", "viewSecretRotationGeneratedCredentials", "deleteSecretRotation", "upgradePlan" ] as const); const handleFolderCreate = async (folderName: string, description: string | null) => { const promises = userAvailableEnvs.map((env) => { const environment = env.slug; return createFolder({ name: folderName, path: secretPath, environment, projectId: workspaceId, description }); }); 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 handleFolderUpdate = async (newFolderName: string, description: string | null) => { const { name: oldFolderName } = popUp.updateFolder.data as TSecretFolder; const updatedFolders: TUpdateFolderBatchDTO["folders"] = []; userAvailableEnvs.forEach((env) => { if ( permission.can( ProjectPermissionActions.Edit, subject(ProjectPermissionSub.SecretFolders, { environment: env.slug, secretPath }) ) ) { const folder = getFolderByNameAndEnv(oldFolderName, env.slug); if (folder) { updatedFolders.push({ environment: env.slug, name: newFolderName, id: folder.id, path: secretPath, description }); } } }); if (updatedFolders.length === 0) { createNotification({ type: "info", text: "You don't have access to rename selected folder" }); handlePopUpClose("updateFolder"); return; } try { await updateFolderBatch({ projectSlug, folders: updatedFolders, projectId: workspaceId }); createNotification({ type: "success", text: "Successfully renamed folder across environments" }); } catch { createNotification({ type: "error", text: "Failed to rename folder across environments" }); } finally { handlePopUpClose("updateFolder"); } }; const handleSecretCreate = async (env: string, key: string, value: string) => { try { // create folder if not existing if (secretPath !== "/") { // /hello/world -> [hello","world"] const pathSegment = secretPath.split("/").filter(Boolean); const parentPath = `/${pathSegment.slice(0, -1).join("/")}`; const folderName = pathSegment.at(-1); const canCreateFolder = permission.can( ProjectPermissionActions.Create, subject(ProjectPermissionSub.SecretFolders, { environment: env, secretPath: parentPath }) ); if (folderName && parentPath && canCreateFolder) { await createFolder({ projectId: workspaceId, path: parentPath, environment: env, name: folderName }); } } const result = await createSecretV3({ environment: env, workspaceId, secretPath, secretKey: key, secretValue: value, secretComment: "", type: SecretType.Shared }); if ("approval" in result) { createNotification({ type: "info", text: "Requested change has been sent for review" }); } else { createNotification({ type: "success", text: "Successfully created secret" }); } } catch (error) { console.log(error); createNotification({ type: "error", text: "Failed to create secret" }); } }; const handleEnvSelect = (envId: string) => { if (visibleEnvs.map((env) => env.id).includes(envId)) { setVisibleEnvs(visibleEnvs.filter((env) => env.id !== envId)); } else { setVisibleEnvs(visibleEnvs.concat(userAvailableEnvs.filter((env) => env.id === envId))); } }; const handleSecretUpdate = async ( env: string, key: string, value: string, type = SecretType.Shared ) => { try { const result = await updateSecretV3({ environment: env, workspaceId, secretPath, secretKey: key, secretValue: value, type }); if ("approval" in result) { createNotification({ type: "info", text: "Requested change has been sent for review" }); } else { createNotification({ type: "success", text: "Successfully updated secret" }); } } catch (error) { console.log(error); createNotification({ type: "error", text: "Failed to update secret" }); } }; const handleSecretDelete = async (env: string, key: string, secretId?: string) => { try { const result = await deleteSecretV3({ environment: env, workspaceId, secretPath, secretKey: key, secretId, type: SecretType.Shared }); if ("approval" in result) { createNotification({ type: "info", text: "Requested change has been sent for review" }); } else { createNotification({ type: "success", text: "Successfully deleted secret" }); } } catch (error) { console.log(error); createNotification({ type: "error", text: "Failed to delete secret" }); } }; const handleResetSearch = (path: string) => { const restore = filterHistory.get(path); setFilter(restore?.filter ?? DEFAULT_FILTER_STATE); const el = restore?.searchFilter ?? ""; setSearchFilter(el); setDebouncedSearchFilter(el); }; const handleFolderClick = (path: string) => { // store for breadcrumb nav to restore previously used filters setFilterHistory((prev) => { const curr = new Map(prev); curr.set(secretPath, { filter, searchFilter }); return curr; }); navigate({ search: (prev) => ({ ...prev, secretPath: `${routerSearch.secretPath === "/" ? "" : routerSearch.secretPath}/${path}` }) }).then(() => { setFilter(DEFAULT_FILTER_STATE); setSearchFilter(""); setDebouncedSearchFilter(""); }); }; const handleExploreEnvClick = async (slug: string) => { if (secretPath !== "/") { const pathSegment = secretPath.split("/").filter(Boolean); const parentPath = `/${pathSegment.slice(0, -1).join("/")}`; const folderName = pathSegment.at(-1); const canCreateFolder = permission.can( ProjectPermissionActions.Create, subject(ProjectPermissionSub.SecretFolders, { environment: slug, secretPath: parentPath }) ); if (folderName && parentPath && canCreateFolder) { await createFolder({ projectId: workspaceId, environment: slug, path: parentPath, name: folderName }); } } const query: Record = { ...routerSearch, search: searchFilter }; const envIndex = visibleEnvs.findIndex((el) => slug === el.slug); if (envIndex !== -1) { navigate({ to: `/${ProjectType.SecretManager}/$projectId/secrets/$envSlug` as const, params: { projectId: workspaceId, envSlug: slug }, search: query }); } }; const handleToggleRowType = useCallback( (rowType: RowType) => setFilter((state) => { return { ...state, [rowType]: !state[rowType] }; }), [] ); const allRowsSelectedOnPage = useMemo(() => { if (!secrets?.length && !folders?.length) return { isChecked: false, isIndeterminate: false }; if ( (!secrets?.length || secrets?.every((secret) => selectedEntries[EntryType.SECRET][secret.key])) && (!folders?.length || folders?.every((folder) => selectedEntries[EntryType.FOLDER][folder.name])) ) return { isChecked: true, isIndeterminate: false }; if ( secrets?.some((secret) => selectedEntries[EntryType.SECRET][secret.key]) || folders?.some((folder) => selectedEntries[EntryType.FOLDER][folder.name]) ) return { isChecked: true, isIndeterminate: true }; return { isChecked: false, isIndeterminate: false }; }, [selectedEntries, secrets, folders]); const toggleSelectedEntry = useCallback( (type: EntryType, key: string) => { const isChecked = Boolean(selectedEntries[type]?.[key]); const newChecks = { ...selectedEntries }; // remove selection if its present else add it if (isChecked) { delete newChecks[type][key]; } else { newChecks[type][key] = {}; userAvailableEnvs.forEach((env) => { const resource = type === EntryType.SECRET ? getSecretByKey(env.slug, key) : getFolderByNameAndEnv(key, env.slug); if (resource) newChecks[type][key][env.slug] = resource; }); } setSelectedEntries(newChecks); }, [selectedEntries, getFolderByNameAndEnv, getSecretByKey] ); const toggleSelectAllRows = () => { const newChecks = { ...selectedEntries }; userAvailableEnvs.forEach((env) => { secrets?.forEach((secret) => { if (allRowsSelectedOnPage.isChecked) { delete newChecks[EntryType.SECRET][secret.key]; } else { if (!newChecks[EntryType.SECRET][secret.key]) newChecks[EntryType.SECRET][secret.key] = {}; const resource = getSecretByKey(env.slug, secret.key); if (resource) newChecks[EntryType.SECRET][secret.key][env.slug] = resource; } }); folders?.forEach((folder) => { if (allRowsSelectedOnPage.isChecked) { delete newChecks[EntryType.FOLDER][folder.name]; } else { if (!newChecks[EntryType.FOLDER][folder.name]) newChecks[EntryType.FOLDER][folder.name] = {}; const resource = getFolderByNameAndEnv(folder.name, env.slug); if (resource) newChecks[EntryType.FOLDER][folder.name][env.slug] = resource; } }); }); setSelectedEntries(newChecks); }; useEffect(() => { if (routerSearch.search) { const { search, ...query } = routerSearch; // temp workaround until we transition state to query params navigate({ search: query }); setFilter(DEFAULT_FILTER_STATE); setSearchFilter(routerSearch.search as string); setDebouncedSearchFilter(routerSearch.search as string); } }, [routerSearch.search]); const selectedKeysCount = Object.keys(selectedEntries.secret).length; const secretsToDeleteKeys = useMemo(() => { return Object.values(selectedEntries.secret).flatMap((entries) => Object.values(entries).map((secret) => secret.key) ); }, [selectedEntries]); const filterAndMergeEnvironments = ( envNames: string[], envs: { environment: string; importedBy: ProjectSecretsImportedBy[] }[] ): ProjectSecretsImportedBy[] => { const filteredEnvs = envs.filter((env) => envNames.includes(env.environment)); if (filteredEnvs.length === 0) return []; const allImportedBy = filteredEnvs.flatMap((env) => env.importedBy); const groupedBySlug: Record = {}; allImportedBy.forEach((item) => { const { slug } = item.environment; if (!groupedBySlug[slug]) groupedBySlug[slug] = []; groupedBySlug[slug].push(item); }); const mergedImportedBy = Object.values(groupedBySlug).map((group) => { const { environment } = group[0]; const allFolders = group.flatMap((item) => item.folders); const foldersByName: Record = {}; allFolders.forEach((folder) => { if (!foldersByName[folder.name]) foldersByName[folder.name] = []; foldersByName[folder.name].push(folder); }); const mergedFolders = Object.entries(foldersByName).map(([name, foldersData]) => { const isImported = foldersData.some((folder) => folder.isImported); const allSecrets = foldersData.flatMap((folder) => folder.secrets || []); const uniqueSecrets: { secretId: string; referencedSecretKey: string; referencedSecretEnv: string; }[] = []; const secretIds = new Set(); allSecrets .filter( (secret) => !secretsToDeleteKeys || secretsToDeleteKeys.length === 0 || secretsToDeleteKeys.includes(secret.referencedSecretKey) ) .forEach((secret) => { if (!secretIds.has(secret.secretId)) { secretIds.add(secret.secretId); uniqueSecrets.push(secret); } }); return { name, isImported, ...(uniqueSecrets.length > 0 ? { secrets: uniqueSecrets } : {}) }; }); return { environment, folders: mergedFolders.filter( (folder) => folder.isImported || (folder.secrets && folder.secrets.length > 0) ) }; }); return mergedImportedBy; }; const importedBy = useMemo(() => { if (!importedByEnvs) return []; if (selectedKeysCount === 0) { return filterAndMergeEnvironments( visibleEnvs.map(({ slug }) => slug), importedByEnvs ); } return filterAndMergeEnvironments( Object.values(selectedEntries.secret).flatMap((entries) => Object.keys(entries)), importedByEnvs ); }, [importedByEnvs, selectedEntries, selectedKeysCount]); if (isProjectV3 && visibleEnvs.length > 0 && isOverviewLoading) { return (
loading animation
); } const canViewOverviewPage = Boolean(userAvailableEnvs.length); // This is needed to also show imports from other paths – right now those are missing. // const combinedKeys = [...secKeys, ...secretImports.map((impSecrets) => impSecrets?.data?.map((impSec) => impSec.secrets?.map((impSecKey) => impSecKey.key))).flat().flat()]; const isTableEmpty = totalCount === 0; const isTableFiltered = Boolean(Object.values(filter).filter((enabled) => !enabled).length) || userAvailableEnvs.length !== visibleEnvs.length; if (!isProjectV3) return (
); return (
{t("common.head-title", { title: t("dashboard.title") })}
Inject your secrets using Infisical CLI , Infisical API , Infisical SDKs , and more . Click the Explore button to view the secret details section.

} />
{userAvailableEnvs.length > 0 && ( {/* */} Filter project resources { e.preventDefault(); handleToggleRowType(RowType.Import); }} icon={filter[RowType.Import] && } iconPos="right" >
Imports
{ 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
Choose visible environments {userAvailableEnvs.map((availableEnv) => { const { id: envId, name } = availableEnv; const isEnvSelected = visibleEnvs.map((env) => env.id).includes(envId); return ( { e.preventDefault(); handleEnvSelect(envId); }} key={envId} disabled={visibleEnvs?.length === 1} icon={isEnvSelected && } iconPos="right" >
{name}
); })}
)} {userAvailableEnvs.length > 0 && (
handlePopUpToggle("misc", isOpen)} >
{(isAllowed) => ( )}
)}
setScrollOffset(e.currentTarget.scrollLeft)} className="thin-scrollbar rounded-b-none" > {visibleEnvs?.map(({ name, slug }, index) => { const envSecKeyCount = getEnvSecretKeyCount(slug); const importedSecKeyCount = getEnvImportedSecretKeyCount(slug); const missingKeyCount = secKeys.length - envSecKeyCount - importedSecKeyCount; return ( ); })} {canViewOverviewPage && isOverviewLoading && ( )} {userAvailableEnvs.length > 0 && visibleEnvs.length === 0 && ( )} {userAvailableEnvs.length === 0 && ( )} {isTableEmpty && !isOverviewLoading && visibleEnvs.length > 0 && ( )} {!isOverviewLoading && visibleEnvs.length > 0 && ( <> {folderNamesAndDescriptions.map(({ name: folderName, description }, index) => ( toggleSelectedEntry(EntryType.FOLDER, folderName) } environments={visibleEnvs} key={`overview-${folderName}-${index + 1}`} onClick={handleFolderClick} onToggleFolderEdit={(name: string) => handlePopUpOpen("updateFolder", { name, description }) } /> ))} {dynamicSecretNames.map((dynamicSecretName, index) => ( ))} {secretRotationNames.map((secretRotationName, index) => ( handlePopUpOpen("editSecretRotation", secretRotation) } onRotate={(secretRotation) => handlePopUpOpen("rotateSecretRotation", secretRotation) } onViewGeneratedCredentials={(secretRotation) => handlePopUpOpen("viewSecretRotationGeneratedCredentials", secretRotation) } onDelete={(secretRotation) => handlePopUpOpen("deleteSecretRotation", secretRotation) } /> ))} {secKeys.map((key, index) => ( toggleSelectedEntry(EntryType.SECRET, key)} secretPath={secretPath} getImportedSecretByKey={getImportedSecretByKey} isImportedSecretPresentInEnv={handleIsImportedSecretPresentInEnv} onSecretCreate={handleSecretCreate} onSecretDelete={handleSecretDelete} onSecretUpdate={handleSecretUpdate} key={`overview-${key}-${index + 1}`} environments={visibleEnvs} secretKey={key} getSecretByKey={getSecretByKey} scrollOffset={debouncedScrollOffset} importedBy={importedBy} /> ))} totalCount ? totalCount % perPage : perPage) - (totalUniqueFoldersInPage || 0) - (totalUniqueDynamicSecretsInPage || 0) - (totalUniqueSecretsInPage || 0) - (totalUniqueSecretImportsInPage || 0) - (totalUniqueSecretRotationsInPage || 0), 0 )} /> )} {visibleEnvs?.map(({ name, slug }) => ( ))}
0 ? `${ !allRowsSelectedOnPage.isChecked ? "Select" : "Unselect" } all folders and secrets on page` : "" } >
Name setOrderDirection((prev) => prev === OrderByDirection.ASC ? OrderByDirection.DESC : OrderByDirection.ASC ) } >
{missingKeyCount > 0 && (
{missingKeyCount}
)}
{!isOverviewLoading && totalCount > 0 && ( } className="rounded-b-md border-t border-solid border-t-mineshaft-600" count={totalCount} page={page} perPage={perPage} onChangePage={(newPage) => setPage(newPage)} onChangePerPage={handlePerPageChange} /> )}
handlePopUpToggle("addSecretsInAllEnvs", isOpen)} > e.preventDefault()} > handlePopUpClose("addSecretsInAllEnvs")} /> handlePopUpToggle("addFolder", isOpen)} > handlePopUpToggle("updateFolder", isOpen)} > )?.name} defaultDescription={ (popUp.updateFolder?.data as Pick)?.description } onUpdateFolder={handleFolderUpdate} showDescriptionOverwriteWarning /> handlePopUpToggle("addDynamicSecret", isOpen)} projectSlug={projectSlug} environments={userAvailableDynamicSecretEnvs} secretPath={secretPath} /> {subscription && ( handlePopUpToggle("upgradePlan", isOpen)} text={ subscription.slug === null ? "You can perform this action under an Enterprise license" : "You can perform this action if you switch to Infisical's Team plan" } /> )} handlePopUpToggle("addSecretRotation", isOpen)} /> handlePopUpToggle("editSecretRotation", isOpen)} /> handlePopUpToggle("rotateSecretRotation", isOpen)} /> handlePopUpToggle("viewSecretRotationGeneratedCredentials", isOpen) } /> handlePopUpToggle("deleteSecretRotation", isOpen)} />
); };