mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-03-22 03:08:23 +00:00
feat(ui): add CSV and PDF download buttons to compliance views (#10093)
This commit is contained in:
@@ -7,6 +7,7 @@ All notable changes to the **Prowler UI** are documented in this file.
|
||||
### 🚀 Added
|
||||
|
||||
- PDF report available for the CSA CCM compliance framework [(#10088)](https://github.com/prowler-cloud/prowler/pull/10088)
|
||||
- CSV and PDF download buttons in compliance views [(#10093)](https://github.com/prowler-cloud/prowler/pull/10093)
|
||||
|
||||
### 🔄 Changed
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { getThreatScore } from "@/actions/overview";
|
||||
import {
|
||||
ClientAccordionWrapper,
|
||||
ComplianceDownloadButton,
|
||||
ComplianceDownloadContainer,
|
||||
ComplianceHeader,
|
||||
RequirementsStatusCard,
|
||||
RequirementsStatusCardSkeleton,
|
||||
@@ -128,20 +128,17 @@ export default async function ComplianceDetail({
|
||||
selectedScan={selectedScan}
|
||||
/>
|
||||
</div>
|
||||
{(() => {
|
||||
const framework = attributesData?.data?.[0]?.attributes?.framework;
|
||||
const reportType = getReportTypeForFramework(framework);
|
||||
|
||||
return selectedScanId && reportType ? (
|
||||
<div className="mb-4 flex-shrink-0 self-end sm:mb-0 sm:self-start sm:pt-1">
|
||||
<ComplianceDownloadButton
|
||||
scanId={selectedScanId}
|
||||
reportType={reportType}
|
||||
label="Download report"
|
||||
/>
|
||||
</div>
|
||||
) : null;
|
||||
})()}
|
||||
{selectedScanId && (
|
||||
<div className="mb-4 flex-shrink-0 self-end sm:mb-0 sm:self-start sm:pt-1">
|
||||
<ComplianceDownloadContainer
|
||||
scanId={selectedScanId}
|
||||
complianceId={complianceId}
|
||||
reportType={getReportTypeForFramework(
|
||||
attributesData?.data?.[0]?.attributes?.framework,
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Suspense
|
||||
|
||||
@@ -3,14 +3,13 @@
|
||||
import { Progress } from "@heroui/progress";
|
||||
import Image from "next/image";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Card, CardContent } from "@/components/shadcn/card/card";
|
||||
import { DownloadIconButton, toast } from "@/components/ui";
|
||||
import { downloadComplianceCsv } from "@/lib/helper";
|
||||
import { getReportTypeForFramework } from "@/lib/compliance/compliance-report-types";
|
||||
import { ScanEntity } from "@/types/scans";
|
||||
|
||||
import { getComplianceIcon } from "../icons";
|
||||
import { ComplianceDownloadContainer } from "./compliance-download-container";
|
||||
|
||||
interface ComplianceCardProps {
|
||||
title: string;
|
||||
@@ -38,7 +37,6 @@ export const ComplianceCard: React.FC<ComplianceCardProps> = ({
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
const hasRegionFilter = searchParams.has("filter[region__in]");
|
||||
const [isDownloading, setIsDownloading] = useState<boolean>(false);
|
||||
|
||||
const formatTitle = (title: string) => {
|
||||
return title.split("-").join(" ");
|
||||
@@ -85,14 +83,6 @@ export const ComplianceCard: React.FC<ComplianceCardProps> = ({
|
||||
|
||||
router.push(`${path}?${params.toString()}`);
|
||||
};
|
||||
const handleDownload = async () => {
|
||||
setIsDownloading(true);
|
||||
try {
|
||||
await downloadComplianceCsv(scanId, complianceId, toast);
|
||||
} finally {
|
||||
setIsDownloading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
@@ -143,15 +133,15 @@ export const ComplianceCard: React.FC<ComplianceCardProps> = ({
|
||||
e.stopPropagation();
|
||||
}
|
||||
}}
|
||||
role="button"
|
||||
role="group"
|
||||
tabIndex={0}
|
||||
>
|
||||
<DownloadIconButton
|
||||
paramId={complianceId}
|
||||
onDownload={handleDownload}
|
||||
textTooltip="Download compliance CSV report"
|
||||
isDisabled={hasRegionFilter}
|
||||
isDownloading={isDownloading}
|
||||
<ComplianceDownloadContainer
|
||||
compact
|
||||
scanId={scanId}
|
||||
complianceId={complianceId}
|
||||
reportType={getReportTypeForFramework(title)}
|
||||
disabled={hasRegionFilter}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { DownloadIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Button } from "@/components/shadcn/button/button";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/shadcn/tooltip";
|
||||
import { toast } from "@/components/ui";
|
||||
import {
|
||||
COMPLIANCE_REPORT_BUTTON_LABELS,
|
||||
type ComplianceReportType,
|
||||
} from "@/lib/compliance/compliance-report-types";
|
||||
import { downloadComplianceReportPdf } from "@/lib/helper";
|
||||
|
||||
interface ComplianceDownloadButtonProps {
|
||||
scanId: string;
|
||||
reportType: ComplianceReportType;
|
||||
label?: string;
|
||||
/** Show only icon with tooltip on mobile (sm and below) */
|
||||
iconOnlyOnMobile?: boolean;
|
||||
}
|
||||
|
||||
export const ComplianceDownloadButton = ({
|
||||
scanId,
|
||||
reportType,
|
||||
label,
|
||||
iconOnlyOnMobile = false,
|
||||
}: ComplianceDownloadButtonProps) => {
|
||||
const [isDownloading, setIsDownloading] = useState<boolean>(false);
|
||||
|
||||
const handleDownload = async () => {
|
||||
setIsDownloading(true);
|
||||
try {
|
||||
await downloadComplianceReportPdf(scanId, reportType, toast);
|
||||
} finally {
|
||||
setIsDownloading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const defaultLabel = COMPLIANCE_REPORT_BUTTON_LABELS[reportType];
|
||||
const buttonLabel = label || defaultLabel;
|
||||
|
||||
if (iconOnlyOnMobile) {
|
||||
return (
|
||||
<>
|
||||
{/* Mobile and Tablet: Icon only with tooltip */}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="default"
|
||||
size="icon"
|
||||
onClick={handleDownload}
|
||||
disabled={isDownloading}
|
||||
className="md:hidden"
|
||||
aria-label={buttonLabel}
|
||||
>
|
||||
<DownloadIcon
|
||||
className={isDownloading ? "animate-download-icon" : ""}
|
||||
size={16}
|
||||
/>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{buttonLabel}</TooltipContent>
|
||||
</Tooltip>
|
||||
{/* Desktop: Full button with label */}
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
onClick={handleDownload}
|
||||
disabled={isDownloading}
|
||||
className="hidden md:inline-flex"
|
||||
>
|
||||
<DownloadIcon
|
||||
className={isDownloading ? "animate-download-icon" : ""}
|
||||
size={16}
|
||||
/>
|
||||
{buttonLabel}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
onClick={handleDownload}
|
||||
disabled={isDownloading}
|
||||
>
|
||||
<DownloadIcon
|
||||
className={isDownloading ? "animate-download-icon" : ""}
|
||||
size={16}
|
||||
/>
|
||||
{buttonLabel}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
92
ui/components/compliance/compliance-download-container.tsx
Normal file
92
ui/components/compliance/compliance-download-container.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
"use client";
|
||||
|
||||
import { DownloadIcon, FileTextIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Button } from "@/components/shadcn/button/button";
|
||||
import { toast } from "@/components/ui";
|
||||
import type { ComplianceReportType } from "@/lib/compliance/compliance-report-types";
|
||||
import {
|
||||
downloadComplianceCsv,
|
||||
downloadComplianceReportPdf,
|
||||
} from "@/lib/helper";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface ComplianceDownloadContainerProps {
|
||||
scanId: string;
|
||||
complianceId: string;
|
||||
reportType?: ComplianceReportType;
|
||||
compact?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const ComplianceDownloadContainer = ({
|
||||
scanId,
|
||||
complianceId,
|
||||
reportType,
|
||||
compact = false,
|
||||
disabled = false,
|
||||
}: ComplianceDownloadContainerProps) => {
|
||||
const [isDownloadingCsv, setIsDownloadingCsv] = useState(false);
|
||||
const [isDownloadingPdf, setIsDownloadingPdf] = useState(false);
|
||||
|
||||
const handleDownloadCsv = async () => {
|
||||
if (isDownloadingCsv) return;
|
||||
setIsDownloadingCsv(true);
|
||||
try {
|
||||
await downloadComplianceCsv(scanId, complianceId, toast);
|
||||
} finally {
|
||||
setIsDownloadingCsv(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownloadPdf = async () => {
|
||||
if (!reportType || isDownloadingPdf) return;
|
||||
setIsDownloadingPdf(true);
|
||||
try {
|
||||
await downloadComplianceReportPdf(scanId, reportType, toast);
|
||||
} finally {
|
||||
setIsDownloadingPdf(false);
|
||||
}
|
||||
};
|
||||
|
||||
const buttonClassName = cn(
|
||||
"border-button-primary text-button-primary hover:bg-button-primary/10",
|
||||
compact && "h-7 px-2 text-xs",
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cn("flex gap-2", compact ? "items-center" : "flex-col")}>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className={buttonClassName}
|
||||
onClick={handleDownloadCsv}
|
||||
disabled={disabled || isDownloadingCsv}
|
||||
aria-label="Download compliance CSV report"
|
||||
>
|
||||
<FileTextIcon
|
||||
size={14}
|
||||
className={isDownloadingCsv ? "animate-download-icon" : ""}
|
||||
/>
|
||||
CSV
|
||||
</Button>
|
||||
{reportType && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className={buttonClassName}
|
||||
onClick={handleDownloadPdf}
|
||||
disabled={disabled || isDownloadingPdf}
|
||||
aria-label="Download compliance PDF report"
|
||||
>
|
||||
<DownloadIcon
|
||||
size={14}
|
||||
className={isDownloadingPdf ? "animate-download-icon" : ""}
|
||||
/>
|
||||
PDF
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -12,7 +12,7 @@ export * from "./compliance-charts/top-failed-sections-card";
|
||||
export * from "./compliance-custom-details/cis-details";
|
||||
export * from "./compliance-custom-details/ens-details";
|
||||
export * from "./compliance-custom-details/iso-details";
|
||||
export * from "./compliance-download-button";
|
||||
export * from "./compliance-download-container";
|
||||
export * from "./compliance-header/compliance-header";
|
||||
export * from "./compliance-header/compliance-scan-info";
|
||||
export * from "./compliance-header/data-compliance";
|
||||
|
||||
Reference in New Issue
Block a user