mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 12:31:54 +00:00
feat: disable compliance download when region filter is applied
This commit is contained in:
@@ -257,3 +257,42 @@ export const getExportsZip = async (scanId: string) => {
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const getComplianceCsv = async (
|
||||
scanId: string,
|
||||
complianceId: string,
|
||||
) => {
|
||||
const headers = await getAuthHeaders({ contentType: false });
|
||||
|
||||
const url = new URL(
|
||||
`${apiBaseUrl}/scans/${scanId}/compliance/${complianceId}`,
|
||||
);
|
||||
|
||||
try {
|
||||
const response = await fetch(url.toString(), {
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(
|
||||
errorData?.errors?.[0]?.detail || "Failed to fetch compliance report",
|
||||
);
|
||||
}
|
||||
|
||||
// Get the blob data as an array buffer
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
// Convert to base64
|
||||
const base64 = Buffer.from(arrayBuffer).toString("base64");
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: base64,
|
||||
filename: `scan-${scanId}-compliance-${complianceId}.csv`,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
error: getErrorMessage(error),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -159,6 +159,7 @@ const SSRComplianceGrid = async ({
|
||||
framework,
|
||||
version,
|
||||
requirements_status: { passed, total },
|
||||
compliance_id,
|
||||
} = attributes;
|
||||
|
||||
return (
|
||||
@@ -170,6 +171,8 @@ const SSRComplianceGrid = async ({
|
||||
totalRequirements={total}
|
||||
prevPassingRequirements={passed}
|
||||
prevTotalRequirements={total}
|
||||
scanId={scanId}
|
||||
complianceId={compliance_id}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { Card, CardBody, Progress } from "@nextui-org/react";
|
||||
import Image from "next/image";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import React from "react";
|
||||
|
||||
import { DownloadIconButton, toast } from "@/components/ui";
|
||||
import { downloadComplianceCsv } from "@/lib/helper";
|
||||
|
||||
import { getComplianceIcon } from "../icons";
|
||||
|
||||
interface ComplianceCardProps {
|
||||
@@ -11,6 +17,8 @@ interface ComplianceCardProps {
|
||||
totalRequirements: number;
|
||||
prevPassingRequirements: number;
|
||||
prevTotalRequirements: number;
|
||||
scanId: string;
|
||||
complianceId: string;
|
||||
}
|
||||
|
||||
export const ComplianceCard: React.FC<ComplianceCardProps> = ({
|
||||
@@ -18,7 +26,12 @@ export const ComplianceCard: React.FC<ComplianceCardProps> = ({
|
||||
version,
|
||||
passingRequirements,
|
||||
totalRequirements,
|
||||
scanId,
|
||||
complianceId,
|
||||
}) => {
|
||||
const searchParams = useSearchParams();
|
||||
const hasRegionFilter = searchParams.has("filter[region__in]");
|
||||
|
||||
const formatTitle = (title: string) => {
|
||||
return title.split("-").join(" ");
|
||||
};
|
||||
@@ -27,6 +40,8 @@ export const ComplianceCard: React.FC<ComplianceCardProps> = ({
|
||||
(passingRequirements / totalRequirements) * 100,
|
||||
);
|
||||
|
||||
// Calculates the percentage change in passing requirements compared to the previous scan.
|
||||
//
|
||||
// const prevRatingPercentage = Math.floor(
|
||||
// (prevPassingRequirements / prevTotalRequirements) * 100,
|
||||
// );
|
||||
@@ -79,13 +94,22 @@ export const ComplianceCard: React.FC<ComplianceCardProps> = ({
|
||||
}}
|
||||
color={getRatingColor(ratingPercentage)}
|
||||
/>
|
||||
<div className="mt-2 flex justify-between">
|
||||
<div className="mt-2 flex items-center justify-between">
|
||||
<small>
|
||||
<span className="mr-1 text-xs font-semibold">
|
||||
{passingRequirements} / {totalRequirements}
|
||||
</span>
|
||||
Passing Requirements
|
||||
</small>
|
||||
|
||||
<DownloadIconButton
|
||||
paramId={complianceId}
|
||||
onDownload={() =>
|
||||
downloadComplianceCsv(scanId, complianceId, toast)
|
||||
}
|
||||
textTooltip="Download compliance CSV report"
|
||||
isDisabled={hasRegionFilter}
|
||||
/>
|
||||
{/* <small>{getScanChange()}</small> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { DownloadIconButton, toast } from "@/components/ui";
|
||||
import { DateWithTime, EntityInfoShort } from "@/components/ui/entities";
|
||||
import { TriggerSheet } from "@/components/ui/sheet";
|
||||
import { DataTableColumnHeader, StatusBadge } from "@/components/ui/table";
|
||||
import { downloadScanZip } from "@/lib/helper";
|
||||
import { ScanProps } from "@/types";
|
||||
|
||||
import { LinkToFindingsFromScan } from "../../link-to-findings-from-scan";
|
||||
@@ -135,6 +136,7 @@ export const ColumnGetScans: ColumnDef<ScanProps>[] = [
|
||||
return (
|
||||
<DownloadIconButton
|
||||
paramId={scanId}
|
||||
onDownload={() => downloadScanZip(scanId, toast)}
|
||||
isDisabled={scanState !== "completed"}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,34 +1,40 @@
|
||||
"use client";
|
||||
|
||||
import { downloadScanZip } from "@/lib/helper";
|
||||
import { CustomButton } from "../custom/custom-button";
|
||||
import { toast } from "@/components/ui";
|
||||
import { Tooltip } from "@nextui-org/react";
|
||||
import { DownloadIcon } from "lucide-react";
|
||||
|
||||
import { CustomButton } from "../custom/custom-button";
|
||||
|
||||
interface DownloadIconButtonProps {
|
||||
paramId: string;
|
||||
onDownload: (paramId: string) => void;
|
||||
ariaLabel?: string;
|
||||
isDisabled?: boolean;
|
||||
textTooltip?: string;
|
||||
}
|
||||
|
||||
export const DownloadIconButton = ({
|
||||
paramId,
|
||||
onDownload,
|
||||
ariaLabel = "Download report",
|
||||
isDisabled = false,
|
||||
textTooltip = "Download report",
|
||||
}: DownloadIconButtonProps) => {
|
||||
return (
|
||||
<div className="flex w-14 items-center justify-center">
|
||||
<CustomButton
|
||||
variant="ghost"
|
||||
isDisabled={isDisabled}
|
||||
onPress={() => downloadScanZip(paramId, toast)}
|
||||
className="p-0 text-default-500 hover:text-primary disabled:opacity-30"
|
||||
isIconOnly
|
||||
ariaLabel={ariaLabel}
|
||||
size="sm"
|
||||
>
|
||||
<DownloadIcon size={16} />
|
||||
</CustomButton>
|
||||
<div className="flex items-center justify-end">
|
||||
<Tooltip content={textTooltip} className="text-xs">
|
||||
<CustomButton
|
||||
variant="ghost"
|
||||
isDisabled={isDisabled}
|
||||
onPress={() => onDownload(paramId)}
|
||||
className="p-0 text-default-500 hover:text-primary disabled:opacity-30"
|
||||
isIconOnly
|
||||
ariaLabel={ariaLabel}
|
||||
size="sm"
|
||||
>
|
||||
<DownloadIcon size={16} />
|
||||
</CustomButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ export * from "./alert-dialog/AlertDialog";
|
||||
export * from "./chart/Chart";
|
||||
export * from "./content-layout/content-layout";
|
||||
export * from "./dialog/dialog";
|
||||
export * from "./download-icon-button/download-icon-button";
|
||||
export * from "./dropdown/Dropdown";
|
||||
export * from "./headers/navigation-header";
|
||||
export * from "./label/Label";
|
||||
@@ -11,4 +12,3 @@ export * from "./main-layout/main-layout";
|
||||
export * from "./select/Select";
|
||||
export * from "./sidebar";
|
||||
export * from "./toast";
|
||||
export * from "./download-icon-button/download-icon-button";
|
||||
|
||||
+38
-1
@@ -1,4 +1,4 @@
|
||||
import { getExportsZip } from "@/actions/scans";
|
||||
import { getComplianceCsv, getExportsZip } from "@/actions/scans";
|
||||
import { getTask } from "@/actions/task";
|
||||
import { auth } from "@/auth.config";
|
||||
import { useToast } from "@/components/ui";
|
||||
@@ -91,6 +91,43 @@ export const downloadScanZip = async (
|
||||
}
|
||||
};
|
||||
|
||||
export const downloadComplianceCsv = async (
|
||||
scanId: string,
|
||||
complianceId: string,
|
||||
toast: ReturnType<typeof useToast>["toast"],
|
||||
) => {
|
||||
const result = await getComplianceCsv(scanId, complianceId);
|
||||
|
||||
if (result?.success && result?.data) {
|
||||
const binaryString = window.atob(result.data);
|
||||
const bytes = new Uint8Array(binaryString.length);
|
||||
for (let i = 0; i < binaryString.length; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
const blob = new Blob([bytes], { type: "text/csv" });
|
||||
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = result.filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
|
||||
toast({
|
||||
title: "Download Complete",
|
||||
description: "The compliance report has been downloaded successfully.",
|
||||
});
|
||||
} else if (result?.error) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Download Failed",
|
||||
description: result.error,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const isGoogleOAuthEnabled =
|
||||
!!process.env.SOCIAL_GOOGLE_OAUTH_CLIENT_ID &&
|
||||
!!process.env.SOCIAL_GOOGLE_OAUTH_CLIENT_SECRET;
|
||||
|
||||
Reference in New Issue
Block a user