diff --git a/ui/app/(prowler)/providers/page.tsx b/ui/app/(prowler)/providers/page.tsx index e6186df10a..72b0af9d7f 100644 --- a/ui/app/(prowler)/providers/page.tsx +++ b/ui/app/(prowler)/providers/page.tsx @@ -118,6 +118,7 @@ const ProvidersTabContent = async ({ isCloud={process.env.NEXT_PUBLIC_IS_CLOUD_ENV === "true"} filters={providersView.filters} providers={providersView.providers} + providerGroups={providersView.providerGroups} metadata={providersView.metadata} rows={providersView.rows} /> diff --git a/ui/app/(prowler)/providers/providers-page.utils.test.ts b/ui/app/(prowler)/providers/providers-page.utils.test.ts index b5dcd6d844..87a758f7b4 100644 --- a/ui/app/(prowler)/providers/providers-page.utils.test.ts +++ b/ui/app/(prowler)/providers/providers-page.utils.test.ts @@ -18,6 +18,10 @@ const schedulesActionsMock = vi.hoisted(() => ({ getSchedules: vi.fn(), })); +const manageGroupsActionsMock = vi.hoisted(() => ({ + getAllProviderGroups: vi.fn(), +})); + vi.mock("@/actions/providers", () => providersActionsMock); vi.mock( "@/actions/organizations/organizations", @@ -25,6 +29,7 @@ vi.mock( ); vi.mock("@/actions/scans", () => scansActionsMock); vi.mock("@/actions/schedules", () => schedulesActionsMock); +vi.mock("@/actions/manage-groups/manage-groups", () => manageGroupsActionsMock); import { SearchParamsProps } from "@/types"; import { ProvidersApiResponse } from "@/types/providers"; diff --git a/ui/app/(prowler)/providers/providers-page.utils.ts b/ui/app/(prowler)/providers/providers-page.utils.ts index a37d6eb510..b6ef0d2cd1 100644 --- a/ui/app/(prowler)/providers/providers-page.utils.ts +++ b/ui/app/(prowler)/providers/providers-page.utils.ts @@ -1,3 +1,4 @@ +import { getAllProviderGroups } from "@/actions/manage-groups/manage-groups"; import { listOrganizationsSafe, listOrganizationUnitsSafe, @@ -483,6 +484,7 @@ export async function loadProvidersAccountsViewData({ const [ providersResponse, allProvidersResponse, + allProviderGroupsResponse, scansResponse, schedulesResponse, organizationsResponse, @@ -500,6 +502,8 @@ export async function loadProvidersAccountsViewData({ // Unfiltered fetch for ProviderTypeSelector — only needs distinct types; // TODO: Replace with a dedicated lightweight endpoint when available. resolveActionResult(getAllProviders()), + // Unfiltered fetch for the Provider Group selector dropdown. + resolveActionResult(getAllProviderGroups()), // Fetch active scheduled scans to flag providers whose schedule has fired. resolveActionResult( getScans({ @@ -545,6 +549,7 @@ export async function loadProvidersAccountsViewData({ filters: createProvidersFilters(), metadata: providersResponse?.meta, providers: allProvidersResponse?.data ?? [], + providerGroups: allProviderGroupsResponse?.data ?? [], rows, }; } diff --git a/ui/components/providers/providers-accounts-view.tsx b/ui/components/providers/providers-accounts-view.tsx index d1ff22a958..9dcfc5e477 100644 --- a/ui/components/providers/providers-accounts-view.tsx +++ b/ui/components/providers/providers-accounts-view.tsx @@ -28,6 +28,7 @@ import { getTourTargetSelector, } from "@/lib/tours/use-driver-tour"; import type { FilterOption, MetaDataProps, ProviderProps } from "@/types"; +import type { ProviderGroup } from "@/types/components"; import type { ProvidersTableRow } from "@/types/providers-table"; import type { ScanScheduleCapability } from "@/types/schedules"; @@ -51,6 +52,7 @@ interface ProvidersAccountsViewProps { filters: FilterOption[]; metadata?: MetaDataProps; providers: ProviderProps[]; + providerGroups?: ProviderGroup[]; rows: ProvidersTableRow[]; /** Cloud overlay seam for provider-creation scan launch. */ scanScheduleCapability?: ScanScheduleCapability; @@ -62,6 +64,7 @@ export function ProvidersAccountsView({ filters, metadata, providers, + providerGroups = [], rows, scanScheduleCapability, isScanLimitReached, @@ -141,6 +144,7 @@ export function ProvidersAccountsView({ diff --git a/ui/components/providers/providers-filters.test.tsx b/ui/components/providers/providers-filters.test.tsx index b6e6655247..4b6741eb40 100644 --- a/ui/components/providers/providers-filters.test.tsx +++ b/ui/components/providers/providers-filters.test.tsx @@ -16,6 +16,13 @@ vi.mock("@/app/(prowler)/_overview/_components/provider-type-selector", () => ({ ProviderTypeSelector: () =>
Provider type selector
, })); +vi.mock( + "@/app/(prowler)/_overview/_components/provider-group-selector", + () => ({ + ProviderGroupSelector: () =>
Provider group selector
, + }), +); + vi.mock("@/components/filters/clear-filters-button", () => ({ ClearFiltersButton: () => , })); diff --git a/ui/components/providers/providers-filters.tsx b/ui/components/providers/providers-filters.tsx index 285bae4c1e..d87ac52993 100644 --- a/ui/components/providers/providers-filters.tsx +++ b/ui/components/providers/providers-filters.tsx @@ -3,6 +3,7 @@ import { useSearchParams } from "next/navigation"; import type { ReactNode } from "react"; +import { ProviderGroupSelector } from "@/app/(prowler)/_overview/_components/provider-group-selector"; import { ProviderTypeSelector } from "@/app/(prowler)/_overview/_components/provider-type-selector"; import { ClearFiltersButton } from "@/components/filters/clear-filters-button"; import { @@ -18,6 +19,7 @@ import { EntityInfo } from "@/components/ui/entities/entity-info"; import { useUrlFilters } from "@/hooks/use-url-filters"; import { isConnectionStatus, isGroupFilterEntity } from "@/lib/helper-filters"; import { FilterEntity, FilterOption, ProviderEntity } from "@/types"; +import { ProviderGroup } from "@/types/components"; import { GroupFilterEntity, ProviderConnectionStatus, @@ -31,12 +33,14 @@ function isNonEmptyString(value: string | null | undefined): value is string { interface ProvidersFiltersProps { filters: FilterOption[]; providers: ProviderProps[]; + providerGroups?: ProviderGroup[]; actions?: ReactNode; } export const ProvidersFilters = ({ filters, providers, + providerGroups = [], actions, }: ProvidersFiltersProps) => { const { updateFilter } = useUrlFilters(); @@ -153,6 +157,9 @@ export const ProvidersFilters = ({
+
+ +
{sortedFilters.map((filter) => { const selectedValues = getSelectedValues(filter); return ( diff --git a/ui/types/providers-table.ts b/ui/types/providers-table.ts index 21bfb1ab8c..1d54bf3323 100644 --- a/ui/types/providers-table.ts +++ b/ui/types/providers-table.ts @@ -1,4 +1,4 @@ -import { MetaDataProps } from "./components"; +import { MetaDataProps, ProviderGroup } from "./components"; import { FilterOption } from "./filters"; import { OrganizationResource, @@ -83,6 +83,7 @@ export interface ProvidersAccountsViewData { filters: FilterOption[]; metadata?: MetaDataProps; providers: ProviderProps[]; + providerGroups: ProviderGroup[]; rows: ProvidersTableRow[]; }