import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import Link from "next/link"; import { useRouter } from "next/router"; import { faArrowDown, faArrowUp, faFolderBlank, faMagnifyingGlass } 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 { Button, EmptyState, IconButton, Input, Table, TableContainer, TableSkeleton, TBody, Td, TFoot, Th, THead, Tooltip, Tr } from "@app/components/v2"; import { useOrganization, useWorkspace } from "@app/context"; import { useCreateFolder, useCreateSecretV3, useDeleteSecretV3, useGetFoldersByEnv, useGetProjectSecretsAllEnv, useGetUserWsKey, useUpdateSecretV3 } from "@app/hooks/api"; import { FolderBreadCrumbs } from "./components/FolderBreadCrumbs"; import { ProjectIndexSecretsSection } from "./components/ProjectIndexSecretsSection"; // import { ProjectIndexSecretsSection } from "./components/ProjectIndexSecretsSection"; import { SecretOverviewFolderRow } from "./components/SecretOverviewFolderRow"; import { SecretOverviewTableRow } from "./components/SecretOverviewTableRow"; export const SecretOverviewPage = () => { const { t } = useTranslation(); const { createNotification } = useNotificationContext(); const router = useRouter(); // this is to set expandable table width // coz when overflow the table goes to the right const parentTableRef = useRef(null); const [expandableTableWidth, setExpandableTableWidth] = useState(0); const [sortDir, setSortDir] = useState<"asc" | "desc">("asc"); useEffect(() => { const handleParentTableWidthResize = () => { setExpandableTableWidth(parentTableRef.current?.clientWidth || 0); }; window.addEventListener("resize", handleParentTableWidthResize); return () => window.removeEventListener("resize", handleParentTableWidthResize); }, []); useEffect(() => { if (parentTableRef.current) { setExpandableTableWidth(parentTableRef.current.clientWidth); } }, [parentTableRef.current]); const { currentWorkspace, isLoading: isWorkspaceLoading } = useWorkspace(); const { currentOrg } = useOrganization(); const workspaceId = currentWorkspace?._id as string; const { data: latestFileKey } = useGetUserWsKey(workspaceId); const [searchFilter, setSearchFilter] = useState(""); const secretPath = (router.query?.secretPath as string) || "/"; useEffect(() => { if (!isWorkspaceLoading && !workspaceId && router.isReady) { router.push(`/org/${currentOrg?._id}/overview`); } }, [isWorkspaceLoading, workspaceId, router.isReady]); const userAvailableEnvs = currentWorkspace?.environments || []; const { data: secrets, getSecretByKey, secKeys, getEnvSecretKeyCount } = useGetProjectSecretsAllEnv({ workspaceId, envs: userAvailableEnvs.map(({ slug }) => slug), secretPath, decryptFileKey: latestFileKey! }); const { folders, folderNames, isFolderPresentInEnv } = useGetFoldersByEnv({ workspaceId, environments: userAvailableEnvs.map(({ slug }) => slug), directory: secretPath }); const { mutateAsync: createSecretV3 } = useCreateSecretV3(); const { mutateAsync: updateSecretV3 } = useUpdateSecretV3(); const { mutateAsync: deleteSecretV3 } = useDeleteSecretV3(); const { mutateAsync: createFolder } = useCreateFolder(); 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); if (folderName && parentPath) { await createFolder({ workspaceId, environment: env, directory: parentPath, folderName }); } } await createSecretV3({ environment: env, workspaceId, secretPath, secretName: key, secretValue: value, secretComment: "", type: "shared", latestFileKey: latestFileKey! }); createNotification({ type: "success", text: "Successfully created secret" }); } catch (error) { console.log(error); createNotification({ type: "error", text: "Failed to create secret" }); } }; const handleSecretUpdate = async (env: string, key: string, value: string, secretId?: string) => { try { await updateSecretV3({ environment: env, workspaceId, secretPath, secretId, secretName: key, secretValue: value, type: "shared", latestFileKey: latestFileKey! }); 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 { await deleteSecretV3({ environment: env, workspaceId, secretPath, secretName: key, secretId, type: "shared" }); createNotification({ type: "success", text: "Successfully deleted secret" }); } catch (error) { console.log(error); createNotification({ type: "error", text: "Failed to delete secret" }); } }; const handleResetSearch = () => setSearchFilter(""); const handleFolderClick = (path: string) => { router.push({ pathname: router.pathname, query: { ...router.query, secretPath: `${router.query?.secretPath || ""}/${path}` } }); }; const handleExploreEnvClick = async (slug: string) => { if (secretPath !== "/") { const path = secretPath.split("/"); const directory = path.slice(0, -1).join("/"); const folderName = path.at(-1); if (folderName && directory) { await createFolder({ workspaceId, environment: slug, directory, folderName }); } } const query: Record = { ...router.query, env: slug }; const envIndex = userAvailableEnvs.findIndex((el) => slug === el.slug); if (envIndex !== -1) { router.push({ pathname: "/project/[id]/secrets/[env]", query }); } }; if (isWorkspaceLoading) { return (
loading animation
); } const isTableLoading = !( folders?.some(({ isLoading }) => !isLoading) && secrets?.some(({ isLoading }) => !isLoading) ); const canViewOverviewPage = Boolean(userAvailableEnvs.length); const filteredSecretNames = secKeys ?.filter((name) => name.toUpperCase().includes(searchFilter.toUpperCase())) .sort((a, b) => (sortDir === "asc" ? a.localeCompare(b) : b.localeCompare(a))); const filteredFolderNames = folderNames?.filter((name) => name.toLowerCase().includes(searchFilter.toLowerCase()) ); const isTableEmpty = !( folders?.every(({ isLoading }) => isLoading) && secrets?.every(({ isLoading }) => isLoading) ) && filteredSecretNames?.length === 0 && filteredFolderNames?.length === 0; return (

Secrets Overview

Inject your secrets using Infisical CLI or Infisical SDKs

setSearchFilter(e.target.value)} leftIcon={} />
{userAvailableEnvs?.map(({ name, slug }, index) => { const envSecKeyCount = getEnvSecretKeyCount(slug); const missingKeyCount = secKeys.length - envSecKeyCount; return ( ); })} {canViewOverviewPage && isTableLoading && ( )} {isTableEmpty && !isTableLoading && ( )} {filteredFolderNames.map((folderName, index) => ( ))} {userAvailableEnvs?.length > 0 ? ( filteredSecretNames.map((key, index) => ( )) ) : ( )} {userAvailableEnvs.map(({ name, slug }) => ( ))}
Name setSortDir((prev) => (prev === "asc" ? "desc" : "asc"))} >
{missingKeyCount > 0 && (
{missingKeyCount}
)}
); };