From 985efc67cc37b406de893d5887923cc05b61f261 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 18 Nov 2024 14:05:36 +0100 Subject: [PATCH] feat: first iteration of compliance dashboard is working --- actions/compliances/compliances.ts | 15 +++- app/(prowler)/compliance/page.tsx | 48 +++++++++--- .../data-compliance/data-compliance.tsx | 74 ++++++++++++++++--- .../select-scan-compliance-data.tsx | 2 - 4 files changed, 112 insertions(+), 27 deletions(-) diff --git a/actions/compliances/compliances.ts b/actions/compliances/compliances.ts index 6b254acff0..e9c8814c31 100644 --- a/actions/compliances/compliances.ts +++ b/actions/compliances/compliances.ts @@ -6,15 +6,23 @@ import { parseStringify } from "@/lib"; export const getCompliancesOverview = async ({ scanId, + region, }: { scanId: string; + region?: string | string[]; }) => { const session = await auth(); const keyServer = process.env.API_BASE_URL; - const url = new URL( - `${keyServer}/compliance-overviews?filter[scan_id]=${scanId}`, - ); + const url = new URL(`${keyServer}/compliance-overviews`); + + if (scanId) url.searchParams.append("filter[scan_id]", scanId); + + if (region) { + const regionValue = Array.isArray(region) ? region.join(",") : region; + url.searchParams.append("filter[region__in]", regionValue); + } + try { const compliances = await fetch(url.toString(), { headers: { @@ -24,6 +32,7 @@ export const getCompliancesOverview = async ({ }); const data = await compliances.json(); const parsedData = parseStringify(data); + revalidatePath("/compliance"); return parsedData; } catch (error) { diff --git a/app/(prowler)/compliance/page.tsx b/app/(prowler)/compliance/page.tsx index 68a9a89dbe..08368c69da 100644 --- a/app/(prowler)/compliance/page.tsx +++ b/app/(prowler)/compliance/page.tsx @@ -8,7 +8,6 @@ import { ComplianceSkeletonGrid, } from "@/components/compliance"; import { DataCompliance } from "@/components/compliance/data-compliance"; -import { FilterControls } from "@/components/filters"; import { Header } from "@/components/ui"; import { ComplianceOverviewData, SearchParamsProps } from "@/types"; @@ -24,26 +23,53 @@ export default async function Compliance({ state: scan.attributes.state, progress: scan.attributes.progress, })); + const selectedScanId = searchParams.scanId || scanList[0]?.id; + // Fetch compliance data for regions + const compliancesData = await getCompliancesOverview({ + scanId: selectedScanId, + }); + + // Extract unique regions + const regions = compliancesData?.data + ? Array.from( + new Set( + compliancesData.data.map( + (compliance: ComplianceOverviewData) => + compliance.attributes.region as string, + ), + ), + ) + : []; + return ( <>
-
- -
- - + + }> - + ); } -const SSRComplianceGrid = async ({ scanId }: { scanId: string }) => { - const compliancesData = await getCompliancesOverview({ scanId }); +const SSRComplianceGrid = async ({ + searchParams, +}: { + searchParams: SearchParamsProps; +}) => { + const scanId = searchParams.scanId?.toString() || ""; + + const regionFilter = searchParams["filter[region__in]"]?.toString() || ""; + + // Fetch compliance data + const compliancesData = await getCompliancesOverview({ + scanId, + region: regionFilter, + }); // Check if the response contains no data if (!compliancesData || compliancesData?.data?.length === 0) { @@ -66,8 +92,8 @@ const SSRComplianceGrid = async ({ scanId }: { scanId: string }) => { } return ( -
- {compliancesData?.data?.map((compliance: ComplianceOverviewData) => { +
+ {compliancesData.data.map((compliance: ComplianceOverviewData) => { const { attributes } = compliance; const { framework, diff --git a/components/compliance/data-compliance/data-compliance.tsx b/components/compliance/data-compliance/data-compliance.tsx index 67e45f9ff4..10fcc44f6b 100644 --- a/components/compliance/data-compliance/data-compliance.tsx +++ b/components/compliance/data-compliance/data-compliance.tsx @@ -1,37 +1,89 @@ "use client"; import { useRouter, useSearchParams } from "next/navigation"; +import { useCallback, useEffect, useState } from "react"; import { SelectScanComplianceData } from "@/components/compliance/data-compliance"; +import { CrossIcon } from "@/components/icons"; +import { CustomButton, CustomDropdownFilter } from "@/components/ui/custom"; interface DataComplianceProps { scans: { id: string; name: string; state: string; progress: number }[]; + regions: string[]; } -export const DataCompliance = ({ scans }: DataComplianceProps) => { +export const DataCompliance = ({ scans, regions }: DataComplianceProps) => { const router = useRouter(); const searchParams = useSearchParams(); + const [showClearButton, setShowClearButton] = useState(false); const scanIdParam = searchParams.get("scanId"); - const selectedScanId = - scanIdParam === "undefined" || !scanIdParam ? scans[0]?.id : scanIdParam; + const selectedScanId = scanIdParam || scans[0]?.id; - if (scanIdParam === "undefined") { - router.replace("/compliance"); - return null; - } - - const handleSelectionChange = (selectedKey: string) => { - router.push(`?scanId=${selectedKey}`); + useEffect(() => { + const hasFilters = Array.from(searchParams.keys()).some( + (key) => key.startsWith("filter[") || key === "sort", + ); + setShowClearButton(hasFilters); + }, [searchParams]); + const handleScanChange = (selectedKey: string) => { + const params = new URLSearchParams(searchParams); + params.set("scanId", selectedKey); + router.push(`?${params.toString()}`); }; + const pushDropdownFilter = useCallback( + (key: string, values: string[]) => { + const params = new URLSearchParams(searchParams); + const filterKey = `filter[${key}]`; + + if (values.length === 0) { + params.delete(filterKey); + } else { + params.set(filterKey, values.join(",")); + } + + router.push(`?${params.toString()}`); + }, + [router, searchParams], + ); + const clearAllFilters = useCallback(() => { + const params = new URLSearchParams(searchParams.toString()); + Array.from(params.keys()).forEach((key) => { + if (key.startsWith("filter[") || key === "sort") { + params.delete(key); + } + }); + router.push(`?${params.toString()}`, { scroll: false }); + }, [router, searchParams]); return (
+ + {showClearButton && ( + } + radius="sm" + > + Reset + + )}
); diff --git a/components/compliance/data-compliance/select-scan-compliance-data.tsx b/components/compliance/data-compliance/select-scan-compliance-data.tsx index fb1189019c..5ebc36515d 100644 --- a/components/compliance/data-compliance/select-scan-compliance-data.tsx +++ b/components/compliance/data-compliance/select-scan-compliance-data.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Select, SelectItem } from "@nextui-org/react"; interface SelectScanComplianceDataProps {