mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 13:01:56 +00:00
feat: first iteration of compliance dashboard is working
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<Header title="Compliance" icon="fluent-mdl2:compliance-audit" />
|
||||
<Spacer y={4} />
|
||||
<div className="mb-6">
|
||||
<DataCompliance scans={scanList} />
|
||||
</div>
|
||||
<FilterControls mutedFindings={false} />
|
||||
<Spacer y={8} />
|
||||
<DataCompliance scans={scanList} regions={regions as string[]} />
|
||||
<Spacer y={12} />
|
||||
<Suspense fallback={<ComplianceSkeletonGrid />}>
|
||||
<SSRComplianceGrid scanId={selectedScanId} />
|
||||
<SSRComplianceGrid searchParams={searchParams} />
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3">
|
||||
{compliancesData?.data?.map((compliance: ComplianceOverviewData) => {
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{compliancesData.data.map((compliance: ComplianceOverviewData) => {
|
||||
const { attributes } = compliance;
|
||||
const {
|
||||
framework,
|
||||
|
||||
@@ -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 (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="grid grid-cols-1 items-center gap-x-4 gap-y-4 md:grid-cols-2 xl:grid-cols-4">
|
||||
<SelectScanComplianceData
|
||||
scans={scans}
|
||||
selectedScanId={selectedScanId}
|
||||
onSelectionChange={handleSelectionChange}
|
||||
onSelectionChange={handleScanChange}
|
||||
/>
|
||||
<CustomDropdownFilter
|
||||
filter={{
|
||||
key: "region__in",
|
||||
values: regions,
|
||||
labelCheckboxGroup: "Regions",
|
||||
}}
|
||||
onFilterChange={pushDropdownFilter}
|
||||
/>
|
||||
{showClearButton && (
|
||||
<CustomButton
|
||||
ariaLabel="Reset"
|
||||
className="w-full md:w-fit"
|
||||
onPress={clearAllFilters}
|
||||
variant="dashed"
|
||||
size="sm"
|
||||
endContent={<CrossIcon size={24} />}
|
||||
radius="sm"
|
||||
>
|
||||
Reset
|
||||
</CustomButton>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { Select, SelectItem } from "@nextui-org/react";
|
||||
|
||||
interface SelectScanComplianceDataProps {
|
||||
|
||||
Reference in New Issue
Block a user