diff --git a/ui/actions/scans/scans.ts b/ui/actions/scans/scans.ts index 39e17a4b42..5354a5b134 100644 --- a/ui/actions/scans/scans.ts +++ b/ui/actions/scans/scans.ts @@ -16,6 +16,7 @@ export const getScans = async ({ sort = "", filters = {}, pageSize = 10, + include = "", }) => { const headers = await getAuthHeaders({ contentType: false }); @@ -27,6 +28,7 @@ export const getScans = async ({ if (pageSize) url.searchParams.append("page[size]", pageSize.toString()); if (query) url.searchParams.append("filter[search]", query); if (sort) url.searchParams.append("sort", sort); + if (include) url.searchParams.append("include", include); // Handle multiple filters Object.entries(filters).forEach(([key, value]) => { diff --git a/ui/app/(prowler)/findings/page.tsx b/ui/app/(prowler)/findings/page.tsx index f1c5fac71f..168cadcb5d 100644 --- a/ui/app/(prowler)/findings/page.tsx +++ b/ui/app/(prowler)/findings/page.tsx @@ -23,8 +23,13 @@ import { extractSortAndKey, hasDateOrScanFilter, } from "@/lib"; -import { ProviderAccountProps, ProviderProps } from "@/types"; -import { FindingProps, ScanProps, SearchParamsProps } from "@/types/components"; +import { ProviderProps } from "@/types"; +import { + FindingProps, + IncludeProps, + ScanProps, + SearchParamsProps, +} from "@/types/components"; export default async function Findings({ searchParams, @@ -44,7 +49,9 @@ export default async function Findings({ filters, }), getProviders({ pageSize: 50 }), - getScans({}), + getScans({ + include: "provider", + }), ]); // Extract unique regions and services from the new endpoint @@ -62,20 +69,21 @@ export default async function Findings({ ), ); - const providerDetails: Array<{ [uid: string]: ProviderAccountProps }> = - providerUIDs.map((uid) => { - const provider = providersData.data.find( - (p: { attributes: { uid: string } }) => p.attributes?.uid === uid, - ); + const providerDetails = providerUIDs.map((uid) => { + const provider = providersData.data.find( + (p: { attributes: { uid: string } }) => p.attributes?.uid === uid, + ); - return { - [uid]: { + return { + [uid]: { + providerInfo: { provider: provider?.attributes?.provider || "", uid: uid, alias: provider?.attributes?.alias ?? null, }, - }; - }); + }, + }; + }); // Extract scan UUIDs with "completed" state and more than one resource const completedScans = scansData?.data @@ -87,11 +95,43 @@ export default async function Findings({ .map((scan: ScanProps) => ({ id: scan.id, name: scan.attributes.name, + providerId: scan.relationships.provider.data.id, + completed_at: scan.attributes.completed_at, })); const completedScanIds = completedScans?.map((scan: ScanProps) => scan.id) || []; + const providerDetailsAssociatedWithScans = completedScans?.map( + (scan: { + id: string; + name: string; + providerId: string; + completed_at: string; + }) => { + const providerId = scan.providerId; + + const providerDetails = scansData.included.find( + (provider: IncludeProps) => + provider.type === "providers" && provider.id === providerId, + ); + + return { + [scan.id]: { + providerInfo: { + provider: providerDetails?.attributes?.provider, + alias: providerDetails?.attributes?.alias, + uid: providerDetails?.attributes?.uid, + }, + attributes: { + name: scan.name, + completed_at: scan.completed_at, + }, + }, + }; + }, + ); + return ( @@ -124,6 +164,7 @@ export default async function Findings({ key: "scan__in", labelCheckboxGroup: "Scan ID", values: completedScanIds, + valueLabelMapping: providerDetailsAssociatedWithScans, }, ]} defaultOpen={true} diff --git a/ui/components/compliance/compliance-scan-info.tsx b/ui/components/compliance/compliance-scan-info.tsx index c4bff1d7ad..f056ca3679 100644 --- a/ui/components/compliance/compliance-scan-info.tsx +++ b/ui/components/compliance/compliance-scan-info.tsx @@ -10,7 +10,7 @@ interface ComplianceScanInfoProps { alias?: string; uid?: string; }; - attributes: { + attributes?: { name?: string; completed_at: string; }; @@ -28,13 +28,17 @@ export const ComplianceScanInfo: React.FC = ({ entityId={scan.providerInfo.uid} hideCopyButton /> - -
-

- {scan.attributes.name || "- -"} -

- -
+ {scan.attributes && ( + <> + +
+

+ {scan.attributes.name || "- -"} +

+ +
+ + )} ); }; diff --git a/ui/components/ui/custom/custom-dropdown-filter.tsx b/ui/components/ui/custom/custom-dropdown-filter.tsx index ee18bab9bc..564bcd7c12 100644 --- a/ui/components/ui/custom/custom-dropdown-filter.tsx +++ b/ui/components/ui/custom/custom-dropdown-filter.tsx @@ -14,12 +14,11 @@ import { XCircle } from "lucide-react"; import { useSearchParams } from "next/navigation"; import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { ComplianceScanInfo } from "@/components/compliance"; import { PlusCircleIcon } from "@/components/icons"; import { useUrlFilters } from "@/hooks/use-url-filters"; import { CustomDropdownFilterProps } from "@/types"; -import { EntityInfoShort } from "../entities"; - const filterSelectedClass = "inline-flex items-center border py-1 text-xs transition-colors border-transparent bg-default-500 text-secondary-foreground hover:bg-default-500/80 rounded-md px-2 font-normal"; @@ -169,7 +168,7 @@ export const CustomDropdownFilter: React.FC = ({ )} - +
= ({ {memoizedFilterValues.map((value) => { // Find the corresponding entity from valueLabelMapping const matchingEntry = filter.valueLabelMapping?.find( (entry) => entry[value], ); - const entity = matchingEntry?.[value]; + const scanData = matchingEntry?.[value]; return ( = ({ key={value} value={value} > - {entity ? ( - + {scanData ? ( + ) : ( value )} diff --git a/ui/components/ui/entities/entity-info-short.tsx b/ui/components/ui/entities/entity-info-short.tsx index ecf7687e2f..3e1bb21fcd 100644 --- a/ui/components/ui/entities/entity-info-short.tsx +++ b/ui/components/ui/entities/entity-info-short.tsx @@ -20,7 +20,7 @@ export const EntityInfoShort: React.FC = ({ hideCopyButton = false, }) => { return ( -
+
{getProviderLogo(cloudProvider)}
diff --git a/ui/types/components.ts b/ui/types/components.ts index 94c5a06646..d63d083e21 100644 --- a/ui/types/components.ts +++ b/ui/types/components.ts @@ -1,7 +1,7 @@ import { LucideIcon } from "lucide-react"; import { SVGProps } from "react"; -import { ProviderType } from "./providers"; +import { ProviderAccountProps, ProviderType } from "./providers"; export type IconSvgProps = SVGProps & { size?: number; @@ -704,3 +704,15 @@ export interface UserProps { dateAdded: string; status: "active" | "inactive"; } + +export interface Connection { + connected: string; + last_checked_at: string; +} + +export interface IncludeProps { + type: string; + id: string; + attributes: ProviderAccountProps; + connection: Connection; +} diff --git a/ui/types/filters.ts b/ui/types/filters.ts index a8fb78dde9..3236fd20f7 100644 --- a/ui/types/filters.ts +++ b/ui/types/filters.ts @@ -1,10 +1,22 @@ -import { ProviderAccountProps } from "./providers"; +import { ProviderType } from "./providers"; export interface FilterOption { key: string; labelCheckboxGroup: string; values: string[]; - valueLabelMapping?: Array<{ [uid: string]: ProviderAccountProps }>; + valueLabelMapping?: Array<{ + [uid: string]: { + providerInfo: { + provider: ProviderType; + alias?: string; + uid?: string; + }; + attributes: { + name?: string; + completed_at: string; + }; + }; + }>; } export interface CustomDropdownFilterProps {