diff --git a/backend/src/server/routes/v1/dashboard-router.ts b/backend/src/server/routes/v1/dashboard-router.ts index 135effc4c..029d4a848 100644 --- a/backend/src/server/routes/v1/dashboard-router.ts +++ b/backend/src/server/routes/v1/dashboard-router.ts @@ -270,11 +270,6 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { } } }); - - remainingLimit -= imports.length; - adjustedOffset = 0; - } else { - adjustedOffset = Math.max(0, adjustedOffset - totalImportCount); } } @@ -317,7 +312,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { } } - if (!includeDynamicSecrets && !includeSecrets) + if (!includeDynamicSecrets && !includeSecrets && !includeSecretRotations) return { folders, totalFolderCount, @@ -547,7 +542,6 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { (totalFolderCount ?? 0) + (totalDynamicSecretCount ?? 0) + (totalSecretCount ?? 0) + - (totalImportCount ?? 0) + (totalSecretRotationCount ?? 0) }; } diff --git a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx index 61be72832..5e9832763 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx @@ -10,13 +10,12 @@ import { faArrowRight, faArrowRightToBracket, faArrowUp, - faFileImport, + faFilter, faFingerprint, faFolder, faFolderBlank, faFolderPlus, faKey, - faList, faPlus, faRotate } from "@fortawesome/free-solid-svg-icons"; @@ -96,7 +95,12 @@ 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 { + SecretType, + SecretV3RawSanitized, + TSecretFolder, + WorkspaceEnv +} from "@app/hooks/api/types"; import { ProjectVersion } from "@app/hooks/api/workspace/types"; import { useDynamicSecretOverview, @@ -135,7 +139,6 @@ enum RowType { Folder = "folder", DynamicSecret = "dynamic", Secret = "secret", - Import = "import", SecretRotation = "rotation" } @@ -144,11 +147,10 @@ type Filter = { }; const DEFAULT_FILTER_STATE = { - [RowType.Folder]: true, - [RowType.DynamicSecret]: true, - [RowType.Secret]: true, - [RowType.Import]: true, - [RowType.SecretRotation]: true + [RowType.Folder]: false, + [RowType.DynamicSecret]: false, + [RowType.Secret]: false, + [RowType.SecretRotation]: false }; const DEFAULT_COLLAPSED_HEADER_HEIGHT = 120; @@ -265,11 +267,8 @@ export const OverviewPage = () => { ) ); - const [visibleEnvs, setVisibleEnvs] = useState(userAvailableEnvs); - - useEffect(() => { - setVisibleEnvs(userAvailableEnvs); - }, [userAvailableEnvs]); + const [filteredEnvs, setFilteredEnvs] = useState([]); + const visibleEnvs = filteredEnvs.length ? filteredEnvs : userAvailableEnvs; const { secretImports, @@ -282,6 +281,7 @@ export const OverviewPage = () => { environments: (userAvailableEnvs || []).map(({ slug }) => slug) }); + const isFilteredByResources = Object.values(filter).some(Boolean); const { isPending: isOverviewLoading, data: overview } = useGetProjectSecretsOverview( { projectId: workspaceId, @@ -289,11 +289,11 @@ export const OverviewPage = () => { secretPath, orderDirection, orderBy, - includeFolders: filter.folder, - includeDynamicSecrets: filter.dynamic, - includeSecrets: filter.secret, - includeImports: filter.import, - includeSecretRotations: filter.rotation, + 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 @@ -529,10 +529,10 @@ export const OverviewPage = () => { }; const handleEnvSelect = (envId: string) => { - if (visibleEnvs.map((env) => env.id).includes(envId)) { - setVisibleEnvs(visibleEnvs.filter((env) => env.id !== envId)); + if (filteredEnvs.map((env) => env.id).includes(envId)) { + setFilteredEnvs(filteredEnvs.filter((env) => env.id !== envId)); } else { - setVisibleEnvs(visibleEnvs.concat(userAvailableEnvs.filter((env) => env.id === envId))); + setFilteredEnvs(filteredEnvs.concat(userAvailableEnvs.filter((env) => env.id === envId))); } }; @@ -792,11 +792,11 @@ export const OverviewPage = () => { envNames: string[], envs: { environment: string; importedBy: ProjectSecretsImportedBy[] }[] ): ProjectSecretsImportedBy[] => { - const filteredEnvs = envs.filter((env) => envNames.includes(env.environment)); + const environments = envs.filter((env) => envNames.includes(env.environment)); - if (filteredEnvs.length === 0) return []; + if (environments.length === 0) return []; - const allImportedBy = filteredEnvs.flatMap((env) => env.importedBy); + const allImportedBy = environments.flatMap((env) => env.importedBy); const groupedBySlug: Record = {}; allImportedBy.forEach((item) => { @@ -902,9 +902,7 @@ export const OverviewPage = () => { const isTableEmpty = totalCount === 0; - const isTableFiltered = - Boolean(Object.values(filter).filter((enabled) => !enabled).length) || - userAvailableEnvs.length !== visibleEnvs.length; + const isTableFiltered = isFilteredByResources || filteredEnvs.length > 0; if (!isProjectV3) return ( @@ -969,26 +967,42 @@ export const OverviewPage = () => {
+ {isTableFiltered && ( + + )} {userAvailableEnvs.length > 0 && ( - + } > - - - - + Filters + {/* */} - Filter project resources - { - e.preventDefault(); - handleToggleRowType(RowType.Import); - }} - icon={filter[RowType.Import] && } - iconPos="right" - > -
- - Imports -
-
+ Filter by Resource + { e.preventDefault(); @@ -1070,11 +1072,11 @@ export const OverviewPage = () => { Secrets
- Choose visible environments + Filter by Environment {userAvailableEnvs.map((availableEnv) => { const { id: envId, name } = availableEnv; - const isEnvSelected = visibleEnvs.map((env) => env.id).includes(envId); + const isEnvSelected = filteredEnvs.map((env) => env.id).includes(envId); return ( { @@ -1082,7 +1084,6 @@ export const OverviewPage = () => { handleEnvSelect(envId); }} key={envId} - disabled={visibleEnvs?.length === 1} icon={isEnvSelected && } iconPos="right" > @@ -1405,13 +1406,6 @@ export const OverviewPage = () => { className="bg-mineshaft-700" /> )} - {userAvailableEnvs.length > 0 && visibleEnvs.length === 0 && ( - - - - - - )} {userAvailableEnvs.length === 0 && ( @@ -1444,8 +1438,8 @@ export const OverviewPage = () => { { searchFilter: (routerQueryParams.search as string) || "", // these should always be on by default for the UI, they will be disabled for the query below based off permissions include: { - [RowType.Folder]: true, - [RowType.Import]: true, - [RowType.DynamicSecret]: true, - [RowType.Secret]: true, - [RowType.SecretRotation]: true + [RowType.Folder]: false, + [RowType.Import]: false, + [RowType.DynamicSecret]: false, + [RowType.Secret]: false, + [RowType.SecretRotation]: false } }; @@ -242,6 +242,7 @@ const Page = () => { } }, [currentWorkspace, environment]); + const isResourceTypeFiltered = Object.values(filter.include).some(Boolean); const { data, isPending: isDetailsLoading, @@ -255,12 +256,14 @@ const Page = () => { orderBy, search: debouncedSearchFilter, orderDirection, - includeImports: canReadSecretImports && filter.include.import, - includeFolders: filter.include.folder, + includeImports: canReadSecretImports && (isResourceTypeFiltered ? filter.include.import : true), + includeFolders: isResourceTypeFiltered ? filter.include.folder : true, viewSecretValue: canReadSecretValue, - includeDynamicSecrets: canReadDynamicSecret && filter.include.dynamic, - includeSecrets: canReadSecret && filter.include.secret, - includeSecretRotations: canReadSecretRotations && filter.include.rotation, + includeDynamicSecrets: + canReadDynamicSecret && (isResourceTypeFiltered ? filter.include.dynamic : true), + includeSecrets: canReadSecret && (isResourceTypeFiltered ? filter.include.secret : true), + includeSecretRotations: + canReadSecretRotations && (isResourceTypeFiltered ? filter.include.rotation : true), tags: filter.tags }); @@ -769,6 +772,19 @@ const Page = () => { isPITEnabled={isPITEnabled} hasPathPolicies={hasPathPolicies} onRequestAccess={(params) => handlePopUpOpen("requestAccess", params)} + onClearFilters={() => + setFilter((prev) => ({ + ...prev, + tags: {}, + include: { + secret: false, + import: false, + dynamic: false, + rotation: false, + folder: false + } + })) + } />
void; hasPathPolicies: boolean; + onClearFilters: () => void; }; export const ActionBar = ({ @@ -159,7 +160,8 @@ export const ActionBar = ({ isPITEnabled = false, usedBySecretSyncs, onRequestAccess, - hasPathPolicies + hasPathPolicies, + onClearFilters }: Props) => { const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([ "addFolder", @@ -661,6 +663,9 @@ export const ActionBar = ({ } }; + const isTableFiltered = + Object.values(filter.tags).some(Boolean) || Object.values(filter.include).some(Boolean); + return ( <>
@@ -676,18 +681,22 @@ export const ActionBar = ({
- !include).length) && - "border-primary/50 text-primary" + "flex h-[2.5rem]", + isTableFiltered && "border-primary/40 bg-primary/10" )} + leftIcon={ + + } > - - + Filters + Filter By @@ -762,51 +771,62 @@ export const ActionBar = ({ Secrets
- - } - > - Tags - - - - Apply Tags to Filter Secrets - - {tags.map(({ id, slug, color }) => ( - { - evt.preventDefault(); - onToggleTagFilter(slug); - }} - key={id} - icon={filter?.tags[slug] && } - iconPos="right" - > -
-
- {slug} -
- - ))} - - + {Boolean(tags.length) && ( + + } + > + Tags + + + + Apply Tags to Filter Secrets + + {tags.map(({ id, slug, color }) => ( + { + evt.preventDefault(); + onToggleTagFilter(slug); + }} + key={id} + icon={filter?.tags[slug] && } + iconPos="right" + > +
+
+ {slug} +
+ + ))} + + + )}
+ {isTableFiltered && ( + + )} +
{isProtectedBranch && ( - + )}
-