From f45edc18a97bb0f95a2ba8f6c3f1b7d3463b1e9c Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Sun, 24 Nov 2024 09:27:18 +0100 Subject: [PATCH 1/4] chore: updating changes with prowler cloud ui --- components/ui/custom/custom-alert-modal.tsx | 8 +++++--- components/ui/table/data-table.tsx | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/components/ui/custom/custom-alert-modal.tsx b/components/ui/custom/custom-alert-modal.tsx index bd93c6839b..4da8ba284d 100644 --- a/components/ui/custom/custom-alert-modal.tsx +++ b/components/ui/custom/custom-alert-modal.tsx @@ -4,9 +4,10 @@ import React, { ReactNode } from "react"; interface CustomAlertModalProps { isOpen: boolean; onOpenChange: (isOpen: boolean) => void; - title: string; - description: string; + title?: string; + description?: string; children: ReactNode; + className?: string; } export const CustomAlertModal: React.FC = ({ @@ -15,13 +16,14 @@ export const CustomAlertModal: React.FC = ({ title, description, children, + className, }) => { return ( diff --git a/components/ui/table/data-table.tsx b/components/ui/table/data-table.tsx index ad5a866a01..4577f1bf08 100644 --- a/components/ui/table/data-table.tsx +++ b/components/ui/table/data-table.tsx @@ -106,9 +106,11 @@ export function DataTable({ -
- -
+ {metadata && ( +
+ +
+ )} ); } From 520a5fc756287bd67748e0c5100b9496d1da06d3 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Sun, 24 Nov 2024 10:25:30 +0100 Subject: [PATCH 2/4] chore: changes for setup provider's workflow --- .../workflow/forms/connect-account-form.tsx | 4 +++- .../workflow/forms/launch-scan-form.tsx | 23 ++++++++++++++---- .../workflow/forms/test-connection-form.tsx | 24 ++++++++++++++++--- .../scans/table/scans/column-get-scans.tsx | 4 +++- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/components/providers/workflow/forms/connect-account-form.tsx b/components/providers/workflow/forms/connect-account-form.tsx index 9a0825ef66..54acff1491 100644 --- a/components/providers/workflow/forms/connect-account-form.tsx +++ b/components/providers/workflow/forms/connect-account-form.tsx @@ -60,7 +60,9 @@ export const ConnectAccountForm = () => { if (data?.errors && data.errors.length > 0) { data.errors.forEach((error: ApiError) => { const errorMessage = error.detail; - switch (error.source.pointer) { + const pointer = error.source?.pointer; + + switch (pointer) { case "/data/attributes/provider": form.setError("providerType", { type: "server", diff --git a/components/providers/workflow/forms/launch-scan-form.tsx b/components/providers/workflow/forms/launch-scan-form.tsx index 86325d437b..2532fd2e90 100644 --- a/components/providers/workflow/forms/launch-scan-form.tsx +++ b/components/providers/workflow/forms/launch-scan-form.tsx @@ -7,7 +7,7 @@ import { useForm } from "react-hook-form"; import { z } from "zod"; import { scanOnDemand } from "@/actions/scans/scans"; -import { RocketIcon, ScheduleIcon } from "@/components/icons"; +import { AddIcon } from "@/components/icons"; import { CustomButton } from "@/components/ui/custom"; import { Form } from "@/components/ui/form"; import { ProviderProps } from "@/types"; // Asegúrate de importar la interfaz correcta @@ -51,7 +51,7 @@ export const LaunchScanForm = ({ }, }); - const isLoading = form.formState.isSubmitting; + // const isLoading = form.formState.isSubmitting; const onSubmitClient = async (values: FormValues) => { const formData = new FormData(); @@ -94,10 +94,11 @@ export const LaunchScanForm = ({ >
- Launch scan + Scan started
- Launch the scan now or schedule it for a later date and time. + The scan has just started. From now on, a new scan will be launched + every 24 hours, starting from this moment.
@@ -116,7 +117,7 @@ export const LaunchScanForm = ({ -
+ {/*
{isLoading ? <>Loading : Start now} +
*/} +
+ } + > + Go to Scans +
diff --git a/components/providers/workflow/forms/test-connection-form.tsx b/components/providers/workflow/forms/test-connection-form.tsx index 6d7ca16dbe..6431c1a814 100644 --- a/components/providers/workflow/forms/test-connection-form.tsx +++ b/components/providers/workflow/forms/test-connection-form.tsx @@ -12,6 +12,7 @@ import { checkConnectionProvider, deleteCredentials, } from "@/actions/providers"; +import { scanOnDemand } from "@/actions/scans"; import { getTask } from "@/actions/task/tasks"; import { CheckIcon, SaveIcon } from "@/components/icons"; import { useToast } from "@/components/ui"; @@ -115,9 +116,26 @@ export const TestConnectionForm = ({ }); if (connected) { - router.push( - `/providers/launch-scan?type=${providerType}&id=${providerId}`, - ); + try { + const data = await scanOnDemand(formData); + + if (data.error) { + setApiErrorMessage(data.error); + form.setError("providerId", { + type: "server", + message: data.error, + }); + } else { + router.push( + `/providers/launch-scan?type=${providerType}&id=${providerId}`, + ); + } + } catch (error) { + form.setError("providerId", { + type: "server", + message: "An unexpected error occurred. Please try again.", + }); + } } else { setConnectionStatus({ connected: false, diff --git a/components/scans/table/scans/column-get-scans.tsx b/components/scans/table/scans/column-get-scans.tsx index 0d8bfb4360..ed235d2965 100644 --- a/components/scans/table/scans/column-get-scans.tsx +++ b/components/scans/table/scans/column-get-scans.tsx @@ -127,9 +127,11 @@ export const ColumnGetScans: ColumnDef[] = [ const { attributes: { name }, } = getScanData(row); - if (name.length === 0) { + + if (!name || name.length === 0) { return -; } + return {name}; }, }, From c7b463d61e3f98cad75411e769539180e11cf443 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Sun, 24 Nov 2024 11:57:29 +0100 Subject: [PATCH 3/4] chore: refresh scan's data with a button --- app/(prowler)/scans/page.tsx | 23 +++++++++++++++++- components/scans/button-refresh-data.tsx | 30 ++++++++++++++++++++++++ components/scans/index.ts | 1 + components/ui/custom/custom-button.tsx | 2 +- 4 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 components/scans/button-refresh-data.tsx create mode 100644 components/scans/index.ts diff --git a/app/(prowler)/scans/page.tsx b/app/(prowler)/scans/page.tsx index 1c251114cb..5b92328cb8 100644 --- a/app/(prowler)/scans/page.tsx +++ b/app/(prowler)/scans/page.tsx @@ -4,6 +4,7 @@ import { Suspense } from "react"; import { getProviders } from "@/actions/providers"; import { getScans } from "@/actions/scans"; import { filterScans } from "@/components/filters"; +import { ButtonRefreshData } from "@/components/scans"; import { LaunchScanWorkflow } from "@/components/scans/launch-workflow"; import { SkeletonTableScans } from "@/components/scans/table"; import { ColumnGetScans } from "@/components/scans/table/scans"; @@ -32,6 +33,8 @@ export default async function Scans({ })) : []; + // const executingScans = await getExecutingScans(); + return ( <>
@@ -39,7 +42,16 @@ export default async function Scans({ - +
+ + { + "use server"; + await getScans({}); + }} + /> +
+
@@ -81,3 +93,12 @@ const SSRDataTableScans = async ({ /> ); }; + +// const getExecutingScans = async () => { +// const scansData = await getScans({}); + +// return scansData?.data?.some( +// (scan: ScanProps) => +// scan.attributes.state === "executing" && scan.attributes.progress < 100, +// ); +// }; diff --git a/components/scans/button-refresh-data.tsx b/components/scans/button-refresh-data.tsx new file mode 100644 index 0000000000..41d05e3981 --- /dev/null +++ b/components/scans/button-refresh-data.tsx @@ -0,0 +1,30 @@ +"use client"; + +import { RefreshCcwIcon } from "lucide-react"; +import { useTransition } from "react"; + +import { CustomButton } from "../ui/custom"; + +export const ButtonRefreshData = ({ + onPress, +}: { + onPress: () => Promise; +}) => { + const [isPending, startTransition] = useTransition(); + + return ( + } + isLoading={isPending} + onPress={() => { + startTransition(async () => { + await onPress(); + }); + }} + /> + ); +}; diff --git a/components/scans/index.ts b/components/scans/index.ts new file mode 100644 index 0000000000..7d435fad43 --- /dev/null +++ b/components/scans/index.ts @@ -0,0 +1 @@ +export * from "./button-refresh-data"; diff --git a/components/ui/custom/custom-button.tsx b/components/ui/custom/custom-button.tsx index a5cdf54a14..a585ca8274 100644 --- a/components/ui/custom/custom-button.tsx +++ b/components/ui/custom/custom-button.tsx @@ -42,7 +42,7 @@ interface CustomButtonProps { | "danger" | "transparent"; onPress?: (e: PressEvent) => void; - children: React.ReactNode; + children?: React.ReactNode; startContent?: React.ReactNode; endContent?: React.ReactNode; size?: "sm" | "md" | "lg"; From 121b24b7d1e61c5b5d20cf58e16dca44130e805b Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Sun, 24 Nov 2024 13:21:42 +0100 Subject: [PATCH 4/4] chore: improve filtering component --- app/(prowler)/findings/page.tsx | 25 ++++---- .../ui/custom/custom-dropdown-filter.tsx | 58 ++++++++++--------- 2 files changed, 43 insertions(+), 40 deletions(-) diff --git a/app/(prowler)/findings/page.tsx b/app/(prowler)/findings/page.tsx index c1da5ffbb9..53c0795f23 100644 --- a/app/(prowler)/findings/page.tsx +++ b/app/(prowler)/findings/page.tsx @@ -51,29 +51,30 @@ export default async function Findings({ const resourceDict = createDict("resources", findingsData); // Get unique regions and services - const allRegionsAndServices = findingsData?.data - ?.flatMap((finding: FindingProps) => { - const resource = - resourceDict[finding.relationships?.resources?.data?.[0]?.id]; - return { - region: resource?.attributes?.region, - service: resource?.attributes?.service, - }; - }) - .filter(Boolean); + const allRegionsAndServices = + findingsData?.data + ?.flatMap((finding: FindingProps) => { + const resource = + resourceDict[finding.relationships?.resources?.data?.[0]?.id]; + return { + region: resource?.attributes?.region, + service: resource?.attributes?.service, + }; + }) + .filter(Boolean) || []; const uniqueRegions = Array.from( new Set( allRegionsAndServices .map((item: { region: string }) => item.region) - .filter(Boolean), + .filter(Boolean) || [], ), ); const uniqueServices = Array.from( new Set( allRegionsAndServices .map((item: { service: string }) => item.service) - .filter(Boolean), + .filter(Boolean) || [], ), ); diff --git a/components/ui/custom/custom-dropdown-filter.tsx b/components/ui/custom/custom-dropdown-filter.tsx index c1fc5ace9d..da7f2e72a8 100644 --- a/components/ui/custom/custom-dropdown-filter.tsx +++ b/components/ui/custom/custom-dropdown-filter.tsx @@ -44,55 +44,57 @@ export const CustomDropdownFilter: React.FC = ({ return currentFilters; }, [searchParams, filter]); + const memoizedFilterValues = useMemo( + () => filter?.values || [], + [filter?.values], + ); + useEffect(() => { if (filter && getActiveFilter[filter.key]) { const activeValues = getActiveFilter[filter.key].split(","); const newSelection = new Set(activeValues); - if (newSelection.size === allFilterKeys.length) { + if (newSelection.size === memoizedFilterValues.length) { newSelection.add("all"); } setGroupSelected(newSelection); } else { setGroupSelected(new Set()); } - }, [getActiveFilter, filter, allFilterKeys]); + }, [getActiveFilter, filter?.key, memoizedFilterValues]); const onSelectionChange = useCallback( (keys: string[]) => { - const newSelection = new Set(keys); + setGroupSelected((prevGroupSelected) => { + const newSelection = new Set(keys); - if ( - newSelection.size === allFilterKeys.length && - !newSelection.has("all") - ) { - setGroupSelected(new Set(["all", ...allFilterKeys])); - } else if (groupSelected.has("all")) { - newSelection.delete("all"); - const remainingValues = allFilterKeys.filter((key) => - newSelection.has(key), - ); - setGroupSelected(new Set(remainingValues)); - } else { - setGroupSelected(newSelection); - } + if ( + newSelection.size === allFilterKeys.length && + !newSelection.has("all") + ) { + return new Set(["all", ...allFilterKeys]); + } else if (prevGroupSelected.has("all")) { + newSelection.delete("all"); + return new Set(allFilterKeys.filter((key) => newSelection.has(key))); + } + return newSelection; + }); if (onFilterChange && filter) { - const selectedValues = Array.from(newSelection).filter( - (key) => key !== "all", - ); + const selectedValues = keys.filter((key) => key !== "all"); onFilterChange(filter.key, selectedValues); } }, - [allFilterKeys, groupSelected, onFilterChange, filter], + [allFilterKeys, onFilterChange, filter], ); const handleSelectAllClick = useCallback(() => { - if (groupSelected.has("all")) { - setGroupSelected(new Set()); - } else { - setGroupSelected(new Set(["all", ...allFilterKeys])); - } - }, [groupSelected, allFilterKeys]); + setGroupSelected((prevGroupSelected) => { + if (prevGroupSelected.has("all")) { + return new Set(); + } + return new Set(["all", ...allFilterKeys]); + }); + }, [allFilterKeys]); const onClearFilter = useCallback( (filterKey: string) => { @@ -170,7 +172,7 @@ export const CustomDropdownFilter: React.FC = ({ hideScrollBar className="flex max-h-96 max-w-56 flex-col gap-y-2 py-2" > - {allFilterKeys.map((value) => ( + {memoizedFilterValues.map((value) => ( {value}