Skip to content

Commit

Permalink
feat: add polling for scan execution state with revalidatePath
Browse files Browse the repository at this point in the history
  • Loading branch information
paabloLC committed Feb 19, 2025
1 parent c5a43de commit 2ed55b3
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 38 deletions.
91 changes: 70 additions & 21 deletions ui/actions/scans/scans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,42 @@ export const getScans = async ({
}
};

export const getScansByState = async () => {
const session = await auth();

const keyServer = process.env.API_BASE_URL;
const url = new URL(`${keyServer}/scans`);

// Request only the necessary fields to optimize the response
url.searchParams.append("fields[scans]", "state");

try {
const response = await fetch(url.toString(), {
headers: {
Accept: "application/vnd.api+json",
Authorization: `Bearer ${session?.accessToken}`,
},
});

if (!response.ok) {
try {
const errorData = await response.json();
throw new Error(errorData?.message || "Failed to fetch scans by state");
} catch {
throw new Error("Failed to fetch scans by state");
}
}

const data = await response.json();

return parseStringify(data);
} catch (error) {
// eslint-disable-next-line no-console
console.error("Error fetching scans by state:", error);
return undefined;
}
};

export const getScan = async (scanId: string) => {
const session = await auth();

Expand Down Expand Up @@ -77,44 +113,57 @@ export const scanOnDemand = async (formData: FormData) => {
const keyServer = process.env.API_BASE_URL;

const providerId = formData.get("providerId");
const scanName = formData.get("scanName");
const scanName = formData.get("scanName") || undefined;

if (!providerId) {
return { error: "Provider ID is required" };
}

const url = new URL(`${keyServer}/scans`);

try {
const requestBody = {
data: {
type: "scans",
attributes: scanName ? { name: scanName } : {},
relationships: {
provider: {
data: {
type: "providers",
id: providerId,
},
},
},
},
};

const response = await fetch(url.toString(), {
method: "POST",
headers: {
"Content-Type": "application/vnd.api+json",
Accept: "application/vnd.api+json",
Authorization: `Bearer ${session?.accessToken}`,
},
body: JSON.stringify({
data: {
type: "scans",
attributes: {
name: scanName,
},
relationships: {
provider: {
data: {
type: "providers",
id: providerId,
},
},
},
},
}),
body: JSON.stringify(requestBody),
});

if (!response.ok) {
try {
const errorData = await response.json();
throw new Error(errorData?.message || "Failed to start scan");
} catch {
throw new Error("Failed to start scan");
}
}

const data = await response.json();

revalidatePath("/scans");
return parseStringify(data);
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
return {
error: getErrorMessage(error),
};
console.error("Error starting scan:", error);
return { error: getErrorMessage(error) };
}
};

Expand Down
4 changes: 2 additions & 2 deletions ui/app/(prowler)/scans/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Spacer } from "@nextui-org/react";
import { Suspense } from "react";

import { getProvider, getProviders } from "@/actions/providers";
import { getScans } from "@/actions/scans";
import { getScans, getScansByState } from "@/actions/scans";
import { FilterControls, filterScans } from "@/components/filters";
import {
AutoRefresh,
Expand Down Expand Up @@ -49,7 +49,7 @@ export default async function Scans({
);

// Get scans data to check for executing scans
const scansData = await getScans({});
const scansData = await getScansByState();
const hasExecutingScan = scansData?.data?.some(
(scan: ScanProps) => scan.attributes.state === "executing",
);
Expand Down
9 changes: 6 additions & 3 deletions ui/components/findings/table/finding-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,10 @@ export const FindingDetail = ({

<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<InfoField label="Check ID" variant="simple">
<Snippet className="max-w-full" hideSymbol>
<Snippet
className="max-w-full bg-gray-50 py-1 dark:bg-slate-800"
hideSymbol
>
{attributes.check_id}
</Snippet>
</InfoField>
Expand Down Expand Up @@ -160,7 +163,7 @@ export const FindingDetail = ({
{/* CLI Command section */}
{attributes.check_metadata.remediation.code.cli && (
<InfoField label="CLI Command" variant="simple">
<Snippet>
<Snippet className="bg-gray-50 py-1 dark:bg-slate-800">
<span className="whitespace-pre-line text-xs">
{attributes.check_metadata.remediation.code.cli}
</span>
Expand Down Expand Up @@ -191,7 +194,7 @@ export const FindingDetail = ({
{/* Resource Details */}
<Section title="Resource Details">
<InfoField label="Resource ID" variant="simple">
<Snippet hideSymbol>
<Snippet className="bg-gray-50 py-1 dark:bg-slate-800" hideSymbol>
<span className="whitespace-pre-line text-xs">
{renderValue(resource.uid)}
</span>
Expand Down
2 changes: 1 addition & 1 deletion ui/components/invitations/invitation-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export const InvitationDetails = ({ attributes }: InvitationDetailsProps) => {
}}
hideSymbol
variant="bordered"
className="overflow-hidden text-ellipsis whitespace-nowrap"
className="overflow-hidden text-ellipsis whitespace-nowrap bg-gray-50 py-1 dark:bg-slate-800"
>
<p className="no-scrollbar w-fit overflow-hidden overflow-x-scroll text-ellipsis whitespace-nowrap text-small">
{invitationLink}
Expand Down
12 changes: 10 additions & 2 deletions ui/components/overview/new-findings-table/table/finding-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,11 @@ export const FindingDetail = ({
{remediation.code.cli && (
<div>
<p className="text-sm font-semibold">CLI Command:</p>
<Snippet hideSymbol size="sm" className="max-w-full">
<Snippet
hideSymbol
size="sm"
className="max-w-full bg-gray-50 py-1 dark:bg-slate-800"
>
<p className="whitespace-pre-line">
{remediation.code.cli}
</p>
Expand Down Expand Up @@ -148,7 +152,11 @@ export const FindingDetail = ({
<p className="text-sm font-semibold dark:text-prowler-theme-pale">
Resource ID
</p>
<Snippet size="sm" hideSymbol className="max-w-full">
<Snippet
size="sm"
hideSymbol
className="max-w-full bg-gray-50 py-1 dark:bg-slate-800"
>
<p className="whitespace-pre-line">{resource.uid}</p>
</Snippet>
</div>
Expand Down
6 changes: 5 additions & 1 deletion ui/components/providers/workflow/credentials-role-helper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ export const CredentialsRoleHelper = () => {
<p className="text-xs font-bold text-gray-600 dark:text-gray-400">
The External ID will also be required:
</p>
<Snippet className="max-w-full py-1" color="warning" hideSymbol>
<Snippet
className="max-w-full bg-gray-50 py-1 dark:bg-slate-800"
color="warning"
hideSymbol
>
<p className="whitespace-pre-line text-xs font-bold">
{session?.tenantId}
</p>
Expand Down
2 changes: 1 addition & 1 deletion ui/components/scans/auto-refresh.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function AutoRefresh({ hasExecutingScan }: AutoRefreshProps) {

const interval = setInterval(() => {
router.refresh();
}, 2000);
}, 5000);

return () => clearInterval(interval);
}, [hasExecutingScan, router]);
Expand Down
19 changes: 12 additions & 7 deletions ui/components/scans/table/scan-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ export const ScanDetail = ({
{/* Header */}
<div className="flex items-center justify-between">
<StatusBadge
size="lg"
size="md"
className="w-fit"
status={scan.state}
loadingProgress={scan.progress}
/>
Expand All @@ -86,11 +87,20 @@ export const ScanDetail = ({
</InfoField>
</div>

<InfoField label="Scan ID" variant="simple">
<Snippet className="bg-gray-50 py-1 dark:bg-slate-800" hideSymbol>
{renderValue(taskDetails?.attributes.task_args.scan_id)}
</Snippet>
</InfoField>

{scan.state === "failed" && taskDetails?.attributes.result && (
<>
{taskDetails.attributes.result.exc_message && (
<InfoField label="Error Message" variant="simple">
<Snippet hideSymbol>
<Snippet
className="bg-gray-50 py-1 dark:bg-slate-800"
hideSymbol
>
<span className="whitespace-pre-line text-xs">
{taskDetails.attributes.result.exc_message.join("\n")}
</span>
Expand All @@ -101,11 +111,6 @@ export const ScanDetail = ({
<InfoField label="Error Type">
{renderValue(taskDetails.attributes.result.exc_type)}
</InfoField>
<InfoField label="Scan ID" variant="simple">
<Snippet hideSymbol>
{renderValue(taskDetails?.attributes.task_args.scan_id)}
</Snippet>
</InfoField>
</div>
</>
)}
Expand Down
3 changes: 3 additions & 0 deletions ui/components/ui/table/status-badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@ export const StatusBadge = ({
status,
size = "sm",
loadingProgress,
className,
...props
}: {
status: Status;
size?: "sm" | "md" | "lg";
loadingProgress?: number;
className?: string;
}) => {
const color = statusColorMap[status as keyof typeof statusColorMap];

Expand All @@ -41,6 +43,7 @@ export const StatusBadge = ({
className={clsx(
"relative w-full max-w-full border-none text-xs capitalize text-default-600",
status === "executing" && "border-1 border-solid border-transparent",
className,
)}
size={size}
variant="flat"
Expand Down

0 comments on commit 2ed55b3

Please sign in to comment.