diff --git a/frontend/src/hooks/api/secretImports/types.ts b/frontend/src/hooks/api/secretImports/types.ts index 83abd300d..7592657c0 100644 --- a/frontend/src/hooks/api/secretImports/types.ts +++ b/frontend/src/hooks/api/secretImports/types.ts @@ -1,5 +1,4 @@ import { SecretV3Raw } from "../secrets/types"; -import { WsTag } from "../types"; import { WorkspaceEnv } from "../workspace/types"; export type TSecretImport = { @@ -92,7 +91,7 @@ export type TDeleteSecretImportDTO = { path?: string; }; -export type SecretImportData = { +export type TSecretImportMultiEnvData = { currentEnv: string; environment: string; secretPath: string; @@ -104,7 +103,15 @@ export type SecretImportData = { key: string; value: string; secretValueHidden: boolean; - tags?: WsTag[]; + tags?: { + id: string; + slug: string; + color?: string; + projectId: string; + createdAt: string; + updatedAt: string; + __v: number; + }[]; comment?: string; createdAt: string; updatedAt: string; diff --git a/frontend/src/hooks/utils/secrets-overview.tsx b/frontend/src/hooks/utils/secrets-overview.tsx index 4076cb5c0..155893d4b 100644 --- a/frontend/src/hooks/utils/secrets-overview.tsx +++ b/frontend/src/hooks/utils/secrets-overview.tsx @@ -1,8 +1,7 @@ import { useCallback, useMemo } from "react"; import { DashboardProjectSecretsOverview } from "@app/hooks/api/dashboard/types"; - -import { SecretImportData } from "../api/secretImports/types"; +import { TSecretImportMultiEnvData } from "@app/hooks/api/secretImports/types"; type FolderNameAndDescription = { name: string; @@ -99,31 +98,28 @@ export const useSecretOverview = (secrets: DashboardProjectSecretsOverview["secr return { secKeys, getSecretByKey, getEnvSecretKeyCount }; }; -export const useSecretImportOverview = (secretImports: SecretImportData[][] | undefined) => { +export const useSecretImportOverview = (secretImports: TSecretImportMultiEnvData[] | undefined) => { const uniqueEnvSecretPaths = useMemo(() => { - const uniqueMap: Record = {}; + const uniqueMap = new Map(); secretImports?.forEach((importData) => { - importData?.forEach((envImport) => { - if (envImport) { - const key = `${envImport.environment}-${envImport.secretPath}`; - const existing = uniqueMap[key]; - uniqueMap[key] = existing ? [...existing, envImport] : [envImport]; + if (importData) { + const key = `${importData.environment}-${importData.secretPath}`; + if (!uniqueMap.has(key)) { + uniqueMap.set(key, importData); } - }); + } }); - return uniqueMap; + return Array.from(uniqueMap.values()); }, [secretImports]); const isSecretImportPresent = useCallback( (sourceEnv: string, targetEnv: string, secretPath: string) => { return ( - secretImports?.some((importData) => - importData?.some( - (envImport) => - envImport?.currentEnv === sourceEnv && - envImport?.environment === targetEnv && - envImport?.secretPath === secretPath - ) + secretImports?.some( + (importData) => + importData?.currentEnv === sourceEnv && + importData?.environment === targetEnv && + importData?.secretPath === secretPath ) ?? false ); }, diff --git a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx index 4c8a93279..29acae74c 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx @@ -7,6 +7,7 @@ import { faAngleDown, faArrowDown, faArrowUp, + faFileImport, faFingerprint, faFolder, faFolderBlank, @@ -67,7 +68,7 @@ import { DashboardSecretsOrderBy } 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 { SecretImportData } from "@app/hooks/api/secretImports/types"; +import { TSecretImportMultiEnvData } from "@app/hooks/api/secretImports/types"; import { SecretType, SecretV3RawSanitized, TSecretFolder } from "@app/hooks/api/types"; import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types"; import { @@ -100,7 +101,8 @@ export enum EntryType { enum RowType { Folder = "folder", DynamicSecret = "dynamic", - Secret = "secret" + Secret = "secret", + Import = "import" } type Filter = { @@ -110,7 +112,8 @@ type Filter = { const DEFAULT_FILTER_STATE = { [RowType.Folder]: true, [RowType.DynamicSecret]: true, - [RowType.Secret]: true + [RowType.Secret]: true, + [RowType.Import]: true }; export const OverviewPage = () => { @@ -203,7 +206,18 @@ export const OverviewPage = () => { environments: (userAvailableEnvs || []).map(({ slug }) => slug) }); const secretImportsData = useMemo( - () => secretImports?.map((s) => s.data as SecretImportData[]) ?? [], + () => + (secretImports?.map((s) => s.data as TSecretImportMultiEnvData[]) ?? []) + ?.flatMap((s) => s ?? []) + ?.filter((secretImport) => + permission.can( + ProjectPermissionActions.Read, + subject(ProjectPermissionSub.SecretImports, { + environment: secretImport.currentEnv, + secretPath + }) + ) + ), [secretImports] ); const { uniqueEnvSecretPaths, isSecretImportPresent } = @@ -768,6 +782,19 @@ export const OverviewPage = () => { */} Filter project resources + { + e.preventDefault(); + handleToggleRowType(RowType.Import); + }} + icon={filter[RowType.Import] && } + iconPos="right" + > +
+ + Imports +
+
{ e.preventDefault(); @@ -1071,16 +1098,19 @@ export const OverviewPage = () => { key={`overview-${dynamicSecretName}-${index + 1}`} /> ))} - {Object.entries(uniqueEnvSecretPaths).map(([key, secretImportsAllEnvs]) => ( - - ))} + {filter.import && + Object.entries(uniqueEnvSecretPaths).map(([key, secretImportsAllEnvs]) => ( + s ?? []) ?? [] + ).filter(Boolean)} + /> + ))} {secKeys.map((key, index) => ( boolean; scrollOffset: number; - allSecretImports: SecretImportData[][] | undefined; + allSecretImports: TSecretImportMultiEnvData[]; }; export const SecretOverviewImportListView = ({ @@ -27,27 +25,18 @@ export const SecretOverviewImportListView = ({ environments = [], isImportedSecretPresentInEnv, scrollOffset, - allSecretImports + allSecretImports = [] }: Props) => { const [isFormExpanded, setIsFormExpanded] = useToggle(); - const { permission } = useProjectPermission(); const environmentImportDetails = secretImport.environmentInfo; const totalCols = environments.length + 1; - const canReadSecretImports = permission.can( - ProjectPermissionActions.Read, - subject(ProjectPermissionSub.SecretImports, { - environment: environmentImportDetails.slug, - secretPath: secretImport.secretPath - }) - ); - const computeImportedSecrets = - canReadSecretImports && allSecretImports + allSecretImports.length > 0 ? computeImportedSecretRows( environmentImportDetails.slug, secretImport.secretPath, - (allSecretImports?.flatMap((s) => s ?? []) ?? []).filter(Boolean) as SecretImportData[] + allSecretImports ) : []; return ( @@ -55,7 +44,7 @@ export const SecretOverviewImportListView = ({ canReadSecretImports && setIsFormExpanded.toggle()} + onClick={() => setIsFormExpanded.toggle()} className={`group ${isFormExpanded ? "border-t-2 border-mineshaft-500" : ""}`} > @@ -106,7 +95,7 @@ export const SecretOverviewImportListView = ({ ); })} - {canReadSecretImports && isFormExpanded && ( + {isFormExpanded && (