mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-04-04 14:37:21 +00:00
feat: allow users to download exports
This commit is contained in:
committed by
Adrián Jesús Peña Rodríguez
parent
526b20ca0a
commit
eb0f6addb4
@@ -198,3 +198,40 @@ export const updateScan = async (formData: FormData) => {
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const getExportsZip = async (scanId: string) => {
|
||||
const session = await auth();
|
||||
|
||||
const keyServer = process.env.API_BASE_URL;
|
||||
const url = new URL(`${keyServer}/scans/${scanId}/report`);
|
||||
|
||||
try {
|
||||
const response = await fetch(url.toString(), {
|
||||
headers: {
|
||||
Authorization: `Bearer ${session?.accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(
|
||||
errorData?.errors?.[0]?.detail || "Failed to fetch 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}-report.zip`,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
error: getErrorMessage(error),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -13,14 +13,15 @@ import {
|
||||
EditDocumentBulkIcon,
|
||||
} from "@nextui-org/shared-icons";
|
||||
import { Row } from "@tanstack/react-table";
|
||||
// import clsx from "clsx";
|
||||
import { DownloadIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { getExportsZip } from "@/actions/scans";
|
||||
import { VerticalDotsIcon } from "@/components/icons";
|
||||
import { useToast } from "@/components/ui";
|
||||
import { CustomAlertModal } from "@/components/ui/custom";
|
||||
|
||||
import { EditScanForm } from "../../forms";
|
||||
import { DownloadIcon } from "lucide-react";
|
||||
|
||||
interface DataTableRowActionsProps<ScanProps> {
|
||||
row: Row<ScanProps>;
|
||||
@@ -31,10 +32,47 @@ const iconClasses =
|
||||
export function DataTableRowActions<ScanProps>({
|
||||
row,
|
||||
}: DataTableRowActionsProps<ScanProps>) {
|
||||
const { toast } = useToast();
|
||||
const [isEditOpen, setIsEditOpen] = useState(false);
|
||||
const scanId = (row.original as { id: string }).id;
|
||||
const scanName = (row.original as any).attributes?.name;
|
||||
const scanState = (row.original as any).attributes?.state;
|
||||
|
||||
const handleExportZip = async () => {
|
||||
const result = await getExportsZip(scanId);
|
||||
|
||||
if (result?.success && result?.data) {
|
||||
// Convert base64 to blob
|
||||
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: "application/zip" });
|
||||
|
||||
// Create download link
|
||||
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: "Your scan report has been downloaded successfully.",
|
||||
});
|
||||
} else if (result?.error) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Download Failed",
|
||||
description: result.error,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomAlertModal
|
||||
@@ -71,10 +109,10 @@ export function DataTableRowActions<ScanProps>({
|
||||
description="Available only for completed scans"
|
||||
textValue="Export Scan Artifacts"
|
||||
startContent={<DownloadIcon className={iconClasses} />}
|
||||
onPress={() => setIsEditOpen(true)}
|
||||
onPress={handleExportZip}
|
||||
isDisabled={scanState !== "completed"}
|
||||
>
|
||||
Export .zip
|
||||
Download .zip
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
<DropdownSection title="Actions">
|
||||
|
||||
@@ -95,7 +95,7 @@ const ToastTitle = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ToastPrimitives.Title
|
||||
ref={ref}
|
||||
className={cn("[&+div]:text-md text-lg font-semibold", className)}
|
||||
className={cn("[&+div]:text-md font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
@@ -107,7 +107,7 @@ const ToastDescription = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ToastPrimitives.Description
|
||||
ref={ref}
|
||||
className={cn("text-md opacity-90", className)}
|
||||
className={cn("text-small opacity-90", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
Reference in New Issue
Block a user