From e84fd1fd653899cbe2fb75579c93a44a35d6d1e1 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 19 Nov 2024 12:39:35 +0100 Subject: [PATCH 01/13] fix: change types because changed in the API specs. --- app/(prowler)/findings/page.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/(prowler)/findings/page.tsx b/app/(prowler)/findings/page.tsx index 4a0a80b8dd..bbfdde2198 100644 --- a/app/(prowler)/findings/page.tsx +++ b/app/(prowler)/findings/page.tsx @@ -53,9 +53,9 @@ const SSRDataTable = async ({ const findingsData = await getFindings({ query, page, sort, filters }); // Create dictionaries for resources, scans, and providers - const resourceDict = createDict("Resource", findingsData); - const scanDict = createDict("Scan", findingsData); - const providerDict = createDict("Provider", findingsData); + const resourceDict = createDict("resources", findingsData); + const scanDict = createDict("scans", findingsData); + const providerDict = createDict("providers", findingsData); // Expand each finding with its corresponding resource, scan, and provider const expandedFindings = findingsData?.data From 73c5764495dea1b49bd80cdc79f9ff56fab2e77f Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 19 Nov 2024 13:05:22 +0100 Subject: [PATCH 02/13] chore: add new component for finding status and add sorting to the findings table --- components/filters/custom-select-provider.tsx | 6 +- components/findings/table/column-findings.tsx | 73 +++++++++++-------- components/ui/table/index.ts | 1 + components/ui/table/severity-badge.tsx | 3 +- components/ui/table/status-finding-badge.tsx | 37 ++++++++++ 5 files changed, 85 insertions(+), 35 deletions(-) create mode 100644 components/ui/table/status-finding-badge.tsx diff --git a/components/filters/custom-select-provider.tsx b/components/filters/custom-select-provider.tsx index 0aff523282..d2a419bbbb 100644 --- a/components/filters/custom-select-provider.tsx +++ b/components/filters/custom-select-provider.tsx @@ -42,16 +42,16 @@ export const CustomSelectProvider: React.FC = () => { (value: string) => { const params = new URLSearchParams(searchParams.toString()); if (value) { - params.set("filter[provider__in]", value); + params.set("filter[provider_type]", value); } else { - params.delete("filter[provider__in]"); + params.delete("filter[provider_type]"); } router.push(`?${params.toString()}`, { scroll: false }); }, [router, searchParams], ); - const currentProvider = searchParams.get("filter[provider__in]") || ""; + const currentProvider = searchParams.get("filter[provider_type]") || ""; const selectedKeys = useMemo(() => { return dataInputsProvider.some( diff --git a/components/findings/table/column-findings.tsx b/components/findings/table/column-findings.tsx index 4d0d7dd3ab..8bfb34998e 100644 --- a/components/findings/table/column-findings.tsx +++ b/components/findings/table/column-findings.tsx @@ -5,19 +5,17 @@ import { ColumnDef } from "@tanstack/react-table"; import { DataTableRowDetails } from "@/components/findings/table"; import { PlusIcon } from "@/components/icons"; import { TriggerSheet } from "@/components/ui/sheet"; -import { SeverityBadge, Status, StatusBadge } from "@/components/ui/table"; +import { + DataTableColumnHeader, + SeverityBadge, + StatusFindingBadge, +} from "@/components/ui/table"; import { FindingProps } from "@/types"; import { DataTableRowActions } from "./data-table-row-actions"; -const statusMap: Record<"PASS" | "FAIL" | "MANUAL" | "MUTED", Status> = { - PASS: "completed", - FAIL: "failed", - MANUAL: "completed", - MUTED: "cancelled", -}; - const getFindingsData = (row: { original: FindingProps }) => { + console.log(row.original, "finding"); return row.original; }; @@ -29,6 +27,7 @@ const getResourceData = ( row: { original: FindingProps }, field: keyof FindingProps["relationships"]["resource"]["attributes"], ) => { + // console.log(row.original, "resource"); return ( row.original.relationships?.resource?.attributes?.[field] || `No ${field} found in resource` @@ -39,6 +38,7 @@ const getProviderData = ( row: { original: FindingProps }, field: keyof FindingProps["relationships"]["provider"]["attributes"], ) => { + // console.log(row.original, "provider"); return ( row.original.relationships?.provider?.attributes?.[field] || `No ${field} found in provider` @@ -49,6 +49,7 @@ const getScanData = ( row: { original: FindingProps }, field: keyof FindingProps["relationships"]["scan"]["attributes"], ) => { + // console.log(row.original, "scan"); return ( row.original.relationships?.scan?.attributes?.[field] || `No ${field} found in scan` @@ -58,30 +59,23 @@ const getScanData = ( export const ColumnFindings: ColumnDef[] = [ { accessorKey: "check", - header: "Check", + header: ({ column }) => ( + + ), cell: ({ row }) => { const { checktitle } = getFindingsMetadata(row); - return

{checktitle}

; - }, - }, - { - accessorKey: "scanName", - header: "Scan Name", - cell: ({ row }) => { - const name = getScanData(row, "name"); - - return ( -

- {typeof name === "string" || typeof name === "number" - ? name - : "Invalid data"} -

- ); + return

{checktitle}

; }, }, { accessorKey: "severity", - header: "Severity", + header: ({ column }) => ( + + ), cell: ({ row }) => { const { attributes: { severity }, @@ -91,15 +85,30 @@ export const ColumnFindings: ColumnDef[] = [ }, { accessorKey: "status", - header: "Status", + header: ({ column }) => ( + + ), cell: ({ row }) => { const { attributes: { status }, } = getFindingsData(row); - const mappedStatus = statusMap[status]; + return ; + }, + }, + { + accessorKey: "scanName", + header: "Scan Name", + cell: ({ row }) => { + const name = getScanData(row, "name"); - return ; + return ( +

+ {typeof name === "string" || typeof name === "number" + ? name + : "Invalid data"} +

+ ); }, }, { @@ -120,7 +129,7 @@ export const ColumnFindings: ColumnDef[] = [ header: "Service", cell: ({ row }) => { const { servicename } = getFindingsMetadata(row); - return

{servicename}

; + return

{servicename}

; }, }, { @@ -131,7 +140,9 @@ export const ColumnFindings: ColumnDef[] = [ return ( <> -
{typeof account === "string" ? account : "Invalid account"}
+

+ {typeof account === "string" ? account : "Invalid account"} +

); }, diff --git a/components/ui/table/index.ts b/components/ui/table/index.ts index a344b959de..48080e8ef7 100644 --- a/components/ui/table/index.ts +++ b/components/ui/table/index.ts @@ -4,4 +4,5 @@ export * from "./data-table-filter-custom"; export * from "./data-table-pagination"; export * from "./severity-badge"; export * from "./status-badge"; +export * from "./status-finding-badge"; export * from "./table"; diff --git a/components/ui/table/severity-badge.tsx b/components/ui/table/severity-badge.tsx index 8b852eea36..d15c8a098a 100644 --- a/components/ui/table/severity-badge.tsx +++ b/components/ui/table/severity-badge.tsx @@ -37,7 +37,8 @@ export const SeverityBadge = ({ severity }: { severity: Severity }) => { return ( = { + FAIL: "danger", + PASS: "success", + MANUAL: "warning", + MUTED: "default", +}; + +export const StatusFindingBadge = ({ + status, + size = "sm", + ...props +}: { + status: FindingStatus; + size?: "sm" | "md" | "lg"; +}) => { + const color = statusColorMap[status]; + + return ( + + {status} + + ); +}; From b28cfede8c057f57356b9cf9bcb9dcc82b6cd36a Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 19 Nov 2024 17:36:56 +0100 Subject: [PATCH 03/13] chore: remove container class and style tweaks for status finding badge --- app/(prowler)/compliance/page.tsx | 2 +- app/(prowler)/layout.tsx | 2 +- components/findings/table/column-findings.tsx | 30 +++++++++++-------- components/ui/sheet/trigger-sheet.tsx | 2 +- components/ui/table/status-finding-badge.tsx | 2 +- 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/app/(prowler)/compliance/page.tsx b/app/(prowler)/compliance/page.tsx index 08368c69da..af2652cc66 100644 --- a/app/(prowler)/compliance/page.tsx +++ b/app/(prowler)/compliance/page.tsx @@ -92,7 +92,7 @@ const SSRComplianceGrid = async ({ } return ( -
+
{compliancesData.data.map((compliance: ComplianceOverviewData) => { const { attributes } = compliance; const { diff --git a/app/(prowler)/layout.tsx b/app/(prowler)/layout.tsx index ea8d1f13bc..40a2fd9bf9 100644 --- a/app/(prowler)/layout.tsx +++ b/app/(prowler)/layout.tsx @@ -46,7 +46,7 @@ export default function RootLayout({
-
+
{children}
diff --git a/components/findings/table/column-findings.tsx b/components/findings/table/column-findings.tsx index 8bfb34998e..d5c06862db 100644 --- a/components/findings/table/column-findings.tsx +++ b/components/findings/table/column-findings.tsx @@ -1,9 +1,10 @@ "use client"; import { ColumnDef } from "@tanstack/react-table"; +import { useSearchParams } from "next/navigation"; import { DataTableRowDetails } from "@/components/findings/table"; -import { PlusIcon } from "@/components/icons"; +import { InfoIcon } from "@/components/icons"; import { TriggerSheet } from "@/components/ui/sheet"; import { DataTableColumnHeader, @@ -15,7 +16,6 @@ import { FindingProps } from "@/types"; import { DataTableRowActions } from "./data-table-row-actions"; const getFindingsData = (row: { original: FindingProps }) => { - console.log(row.original, "finding"); return row.original; }; @@ -27,7 +27,6 @@ const getResourceData = ( row: { original: FindingProps }, field: keyof FindingProps["relationships"]["resource"]["attributes"], ) => { - // console.log(row.original, "resource"); return ( row.original.relationships?.resource?.attributes?.[field] || `No ${field} found in resource` @@ -38,7 +37,6 @@ const getProviderData = ( row: { original: FindingProps }, field: keyof FindingProps["relationships"]["provider"]["attributes"], ) => { - // console.log(row.original, "provider"); return ( row.original.relationships?.provider?.attributes?.[field] || `No ${field} found in provider` @@ -49,7 +47,6 @@ const getScanData = ( row: { original: FindingProps }, field: keyof FindingProps["relationships"]["scan"]["attributes"], ) => { - // console.log(row.original, "scan"); return ( row.original.relationships?.scan?.attributes?.[field] || `No ${field} found in scan` @@ -151,14 +148,23 @@ export const ColumnFindings: ColumnDef[] = [ id: "moreInfo", header: "Details", cell: ({ row }) => { + const searchParams = useSearchParams(); + const findingId = searchParams.get("id"); + const isOpen = findingId === row.original.id; return ( - } - title="Finding Details" - description="View the finding details" - > - - +
+ } + title="Finding Details" + description="View the finding details" + defaultOpen={isOpen} + > + + +
); }, }, diff --git a/components/ui/sheet/trigger-sheet.tsx b/components/ui/sheet/trigger-sheet.tsx index 992b3ac098..232173d2d4 100644 --- a/components/ui/sheet/trigger-sheet.tsx +++ b/components/ui/sheet/trigger-sheet.tsx @@ -27,7 +27,7 @@ export function TriggerSheet({ {triggerComponent} - + {title} {description} diff --git a/components/ui/table/status-finding-badge.tsx b/components/ui/table/status-finding-badge.tsx index 277db680dd..3dfc6e2613 100644 --- a/components/ui/table/status-finding-badge.tsx +++ b/components/ui/table/status-finding-badge.tsx @@ -25,7 +25,7 @@ export const StatusFindingBadge = ({ return ( Date: Wed, 20 Nov 2024 08:31:29 +0100 Subject: [PATCH 04/13] chore: finding details tweaks --- components/filters/custom-date-picker.tsx | 2 +- .../findings/table/data-table-row-details.tsx | 40 ++- components/findings/table/finding-detail.tsx | 308 ++++++++++++------ components/invitations/invitation-details.tsx | 2 +- .../workflow/workflow-send-invite.tsx | 2 +- .../workflow/workflow-add-provider.tsx | 2 +- components/scans/table/scan-detail.tsx | 89 ++--- components/ui/custom/custom-button.tsx | 3 +- components/ui/entities/date-with-time.tsx | 6 +- components/ui/sheet/sheet.tsx | 4 +- components/ui/sheet/trigger-sheet.tsx | 2 +- components/ui/sidebar/sidebar.tsx | 2 +- components/ui/table/data-table-pagination.tsx | 8 +- components/ui/table/data-table.tsx | 2 +- components/ui/table/table.tsx | 2 +- tailwind.config.js | 1 + 16 files changed, 310 insertions(+), 165 deletions(-) diff --git a/components/filters/custom-date-picker.tsx b/components/filters/custom-date-picker.tsx index 91118322af..2a982c1781 100644 --- a/components/filters/custom-date-picker.tsx +++ b/components/filters/custom-date-picker.tsx @@ -63,7 +63,7 @@ export const CustomDatePicker = () => { CalendarTopContent={ { - return ; +import { FindingProps } from "@/types/components"; + +import { FindingDetail } from "./finding-detail"; + +export const DataTableRowDetails = ({ + // entityId, + findingDetails, +}: { + entityId: string; + findingDetails: FindingProps; +}) => { + // const router = useRouter(); + // const pathname = usePathname(); + // const searchParams = useSearchParams(); + + // useEffect(() => { + // if (entityId) { + // const params = new URLSearchParams(searchParams.toString()); + // params.set("id", entityId); + // router.push(`${pathname}?${params.toString()}`, { scroll: false }); + // } + + // return () => { + // if (entityId) { + // const cleanupParams = new URLSearchParams(searchParams.toString()); + // cleanupParams.delete("id"); + // router.push(`${pathname}?${cleanupParams.toString()}`, { + // scroll: false, + // }); + // } + // }; + // }, [entityId, pathname, router, searchParams]); + + return ; }; diff --git a/components/findings/table/finding-detail.tsx b/components/findings/table/finding-detail.tsx index 4d2dfac867..b5865c8c73 100644 --- a/components/findings/table/finding-detail.tsx +++ b/components/findings/table/finding-detail.tsx @@ -1,14 +1,11 @@ "use client"; -import { - Table, - TableBody, - TableCell, - TableColumn, - TableHeader, - TableRow, -} from "@nextui-org/react"; +import { Snippet } from "@nextui-org/react"; +import Link from "next/link"; +import { SnippetId } from "@/components/ui/entities"; +import { DateWithTime } from "@/components/ui/entities/date-with-time"; +import { SeverityBadge } from "@/components/ui/table/severity-badge"; import { FindingProps } from "@/types"; export const FindingDetail = ({ @@ -17,108 +14,203 @@ export const FindingDetail = ({ findingDetails: FindingProps; }) => { const finding = findingDetails; + const attributes = finding.attributes; + const resource = finding.relationships.resource.attributes; + + const remediation = attributes.check_metadata.remediation; + return ( -
+
+ {/* Header */}
- - - Name - Value - - - - Resource ID - {finding.relationships.resource.id} - - - Resource ARN - - {finding.relationships.resource.attributes.uid} - - - - Check ID - {finding.attributes.check_id} - - - Types - - {finding.attributes.check_metadata.checktype} - - - - Scan time - {finding.attributes.inserted_at} - - - Prowler Finding ID - - {finding.relationships.resource.attributes.uid} - - - - Severity - {finding.id} - - - Status - {finding.attributes.status} - - - Region - - {finding.relationships.resource.attributes.region} - - - - Service - - {finding.relationships.resource.attributes.service} - - - - Account - - {finding.relationships.provider.attributes.uid} - - - - Details - {finding.attributes.status_extended} - - - Risk - {finding.attributes.check_metadata.risk} - - - Recommendation - - { - finding.attributes.check_metadata.remediation.recommendation - .text - } - - - - CLI - - {finding.attributes.check_metadata.remediation.code.cli} - - - - Other - - {finding.attributes.check_metadata.remediation.code.other} - - - - Terraform - - {finding.attributes.check_metadata.remediation.code.terraform} - - - -
+
+

+ {attributes.check_metadata.checktitle} +

+

+ {resource.service} +

+
+
+ {attributes.status} +
+
+ + {/* Check Metadata */} +
+
+

+ Check Metadata +

+ +
+ {attributes.status === "FAIL" && ( + +

+ Risk +

+

+ {attributes.check_metadata.risk} +

+
+ )} + +
+

+ Description +

+

+ {attributes.check_metadata.description} +

+
+ +
+

+ Remediation +

+
+ {remediation.recommendation && ( + <> +

Recommendation:

+

{remediation.recommendation.text}

+ + Learn more + + + )} + {remediation.code && + Object.values(remediation.code).some(Boolean) && ( + <> +

+ Check these links: +

+
+ {remediation.code.cli && ( +
+

CLI Command:

+ +

+ {remediation.code.cli} +

+
+
+ )} + {Object.entries(remediation.code) + .filter(([key]) => key !== "cli") + .map(([key, value]) => + value ? ( + + {key === "other" + ? "External doc" + : key.charAt(0).toUpperCase() + + key.slice(1).toLowerCase()} + + ) : null, + )} +
+ + )} +
+
+
+ + {/* Resources Section */} +
+

+ Resource Details +

+
+
+

+ Resource ID +

+ +

{resource.uid}

+
+
+
+

+ Resource Name +

+

+ {resource.name} +

+
+
+

+ Region +

+

+ {resource.region} +

+
+
+

+ Resource Type +

+

+ {resource.type} +

+
+
+

+ Severity +

+ +
+ {resource.tags && + Object.entries(resource.tags).map(([key, value]) => ( +
+

+ Tag: {key} +

+ +

{value}

+
+
+ ))} +
+
+

+ Inserted At +

+ +
+
+

+ Updated At +

+ +
+
+
); diff --git a/components/invitations/invitation-details.tsx b/components/invitations/invitation-details.tsx index 7739cf8adb..f335ad6487 100644 --- a/components/invitations/invitation-details.tsx +++ b/components/invitations/invitation-details.tsx @@ -32,7 +32,7 @@ export const InvitationDetails = ({ attributes }: InvitationDetailsProps) => {
diff --git a/components/invitations/workflow/workflow-send-invite.tsx b/components/invitations/workflow/workflow-send-invite.tsx index cf87d04f06..9cbe848093 100644 --- a/components/invitations/workflow/workflow-send-invite.tsx +++ b/components/invitations/workflow/workflow-send-invite.tsx @@ -55,7 +55,7 @@ export const WorkflowSendInvite = () => { diff --git a/components/providers/workflow/workflow-add-provider.tsx b/components/providers/workflow/workflow-add-provider.tsx index 69bc885638..1f2e631f06 100644 --- a/components/providers/workflow/workflow-add-provider.tsx +++ b/components/providers/workflow/workflow-add-provider.tsx @@ -67,7 +67,7 @@ export const WorkflowAddProvider = () => { diff --git a/components/scans/table/scan-detail.tsx b/components/scans/table/scan-detail.tsx index b0a85f73f3..d6d9fedf7b 100644 --- a/components/scans/table/scan-detail.tsx +++ b/components/scans/table/scan-detail.tsx @@ -17,22 +17,25 @@ export const ScanDetail = ({ scanDetails }: ScanDetailsProps) => { const taskDetails = scanDetails.taskDetails; return ( -
+
+ {/* Header */}
-
-

Scan Details

-
- +

+ Scan Details +

- -
-
-
+ + + + {/* Details Section */} +
+
+
{ value={`${scanOnDemand.duration} seconds`} />
-
+
{ dateTime={scanOnDemand.completed_at.toString()} /> ) : ( - "Not Started" + "Not Completed" ) } /> @@ -109,17 +112,21 @@ export const ScanDetail = ({ scanDetails }: ScanDetailsProps) => {
- - -

Scan arguments

+ + {/* Scan Arguments Section */} + + +

+ Scan Arguments +

- - - - + +
- Checks - + + Checks + + {(scanOnDemand.scanner_args as any)?.checks_to_execute?.join( ", ", ) || "N/A"} @@ -127,25 +134,28 @@ export const ScanDetail = ({ scanDetails }: ScanDetailsProps) => {
+ + {/* Task Details Section */} {taskDetails && ( - - -

State details

+ + +

+ State Details +

- - -
+ + +
- {taskDetails.attributes.result && ( <> {taskDetails.attributes.result.exc_message && ( { )} )} -
@@ -180,8 +191,10 @@ const DateItem = ({ value: React.ReactNode; }) => (
- {label}: - {value} +

+ {label}: +

+

{value}

); @@ -193,7 +206,9 @@ const DetailItem = ({ value: React.ReactNode; }) => (
- {label}: - {value} +

+ {label}: +

+

{value}

); diff --git a/components/ui/custom/custom-button.tsx b/components/ui/custom/custom-button.tsx index eeffd07783..a5cdf54a14 100644 --- a/components/ui/custom/custom-button.tsx +++ b/components/ui/custom/custom-button.tsx @@ -8,7 +8,8 @@ import { NextUIColors, NextUIVariants } from "@/types"; export const buttonClasses = { base: "px-4 inline-flex items-center justify-center relative z-0 text-center whitespace-nowrap", - primary: "bg-default-100 hover:bg-default-200 text-default-800", + primary: + "bg-default-100 hover:bg-default-200 text-default-800 dark:bg-prowler-blue-800", secondary: "bg-prowler-grey-light dark:bg-prowler-grey-medium text-white", action: "bg-prowler-theme-green font-bold text-prowler-theme-midnight", dashed: diff --git a/components/ui/entities/date-with-time.tsx b/components/ui/entities/date-with-time.tsx index ae89c61985..05a00b1b3a 100644 --- a/components/ui/entities/date-with-time.tsx +++ b/components/ui/entities/date-with-time.tsx @@ -4,11 +4,13 @@ import React from "react"; interface DateWithTimeProps { dateTime: string | null; // e.g., "2024-07-17T09:55:14.191475Z" showTime?: boolean; + inline?: boolean; } export const DateWithTime: React.FC = ({ dateTime, showTime = true, + inline = false, }) => { if (!dateTime) return --; const date = parseISO(dateTime); @@ -17,7 +19,9 @@ export const DateWithTime: React.FC = ({ return (
-
+
{formattedDate} {showTime && ( {formattedTime} diff --git a/components/ui/sheet/sheet.tsx b/components/ui/sheet/sheet.tsx index cf945c0f9d..d1fc590d84 100644 --- a/components/ui/sheet/sheet.tsx +++ b/components/ui/sheet/sheet.tsx @@ -31,7 +31,7 @@ const SheetOverlay = React.forwardRef< SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; const sheetVariants = cva( - "fixed z-50 gap-4 bg-white p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out dark:bg-neutral-950", + "fixed z-50 gap-4 bg-white p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out dark:bg-neutral-950 dark:border-prowler-blue-800", { variants: { side: { @@ -40,7 +40,7 @@ const sheetVariants = cva( "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left", right: - "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right", + "inset-y-0 right-0 h-full w-3/4 border-t-1 border-b-1 border-l-2 data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right", }, }, defaultVariants: { diff --git a/components/ui/sheet/trigger-sheet.tsx b/components/ui/sheet/trigger-sheet.tsx index 232173d2d4..5d5fc45ce6 100644 --- a/components/ui/sheet/trigger-sheet.tsx +++ b/components/ui/sheet/trigger-sheet.tsx @@ -27,7 +27,7 @@ export function TriggerSheet({ {triggerComponent} - + {title} {description} diff --git a/components/ui/sidebar/sidebar.tsx b/components/ui/sidebar/sidebar.tsx index 23a58abc79..63feff298e 100644 --- a/components/ui/sidebar/sidebar.tsx +++ b/components/ui/sidebar/sidebar.tsx @@ -280,7 +280,7 @@ const Sidebar = React.forwardRef( itemClasses={{ ...itemClasses, base: clsx( - "px-3 rounded-large data-[selected=true]:bg-default-100", + "px-3 rounded-large data-[selected=true]:bg-default-100 dark:data-[selected=true]:bg-prowler-blue-800", itemClasses?.base, ), title: clsx( diff --git a/components/ui/table/data-table-pagination.tsx b/components/ui/table/data-table-pagination.tsx index e17170d7c8..dfcec441a3 100644 --- a/components/ui/table/data-table-pagination.tsx +++ b/components/ui/table/data-table-pagination.tsx @@ -49,7 +49,7 @@ export function DataTablePagination({ metadata }: DataTablePaginationProps) {
@@ -57,7 +57,7 @@ export function DataTablePagination({ metadata }: DataTablePaginationProps) { @@ -65,14 +65,14 @@ export function DataTablePagination({ metadata }: DataTablePaginationProps) {
)} -
+
{table.getHeaderGroups().map((headerGroup) => ( diff --git a/components/ui/table/table.tsx b/components/ui/table/table.tsx index aca884960c..bd04b9daf3 100644 --- a/components/ui/table/table.tsx +++ b/components/ui/table/table.tsx @@ -75,7 +75,7 @@ const TableHead = React.forwardRef<
Date: Wed, 20 Nov 2024 09:46:04 +0100 Subject: [PATCH 05/13] chore: color tweaks --- components/findings/table/data-table-row-actions.tsx | 5 ++++- components/invitations/table/data-table-row-actions.tsx | 5 ++++- components/providers/table/data-table-row-actions.tsx | 5 ++++- components/scans/table/scans/data-table-row-actions.tsx | 5 ++++- components/ui/custom/custom-alert-modal.tsx | 1 + components/ui/table/data-table.tsx | 2 +- components/users/table/data-table-row-actions.tsx | 5 ++++- 7 files changed, 22 insertions(+), 6 deletions(-) diff --git a/components/findings/table/data-table-row-actions.tsx b/components/findings/table/data-table-row-actions.tsx index f96f804fc9..287e268ffb 100644 --- a/components/findings/table/data-table-row-actions.tsx +++ b/components/findings/table/data-table-row-actions.tsx @@ -56,7 +56,10 @@ export function DataTableRowActions({ */}
- +
)} -
+
{table.getHeaderGroups().map((headerGroup) => ( diff --git a/components/users/table/data-table-row-actions.tsx b/components/users/table/data-table-row-actions.tsx index 07a00cd6a7..61f4cdc390 100644 --- a/components/users/table/data-table-row-actions.tsx +++ b/components/users/table/data-table-row-actions.tsx @@ -62,7 +62,10 @@ export function DataTableRowActions({
- +
- + )} From 07beb094fb1065a6005cbf43f46a18d955fc6678 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 20 Nov 2024 09:57:31 +0100 Subject: [PATCH 07/13] chore:color tweaks --- components/ui/custom/custom-dropdown-filter.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/ui/custom/custom-dropdown-filter.tsx b/components/ui/custom/custom-dropdown-filter.tsx index d333545f49..f05e473d6a 100644 --- a/components/ui/custom/custom-dropdown-filter.tsx +++ b/components/ui/custom/custom-dropdown-filter.tsx @@ -97,7 +97,7 @@ export const CustomDropdownFilter: React.FC = ({ - +
Date: Wed, 20 Nov 2024 10:15:14 +0100 Subject: [PATCH 08/13] chore: move filters outside of the table --- app/(prowler)/findings/page.tsx | 7 ++++--- app/(prowler)/invitations/page.tsx | 8 ++++---- app/(prowler)/providers/page.tsx | 8 ++++---- app/(prowler)/scans/page.tsx | 5 +++-- app/(prowler)/users/page.tsx | 8 ++++---- components/ui/table/data-table.tsx | 7 ------- 6 files changed, 19 insertions(+), 24 deletions(-) diff --git a/app/(prowler)/findings/page.tsx b/app/(prowler)/findings/page.tsx index bbfdde2198..505a614a6b 100644 --- a/app/(prowler)/findings/page.tsx +++ b/app/(prowler)/findings/page.tsx @@ -9,7 +9,7 @@ import { SkeletonTableFindings, } from "@/components/findings/table"; import { Header } from "@/components/ui"; -import { DataTable } from "@/components/ui/table"; +import { DataTable, DataTableFilterCustom } from "@/components/ui/table"; import { createDict } from "@/lib"; import { FindingProps, SearchParamsProps } from "@/types/components"; @@ -26,7 +26,9 @@ export default async function Findings({ - + + + }> @@ -84,7 +86,6 @@ const SSRDataTable = async ({ columns={ColumnFindings} data={expandedResponse?.data || []} metadata={findingsData?.meta} - customFilters={filterFindings} /> ); }; diff --git a/app/(prowler)/invitations/page.tsx b/app/(prowler)/invitations/page.tsx index b32573cd45..f444aa474a 100644 --- a/app/(prowler)/invitations/page.tsx +++ b/app/(prowler)/invitations/page.tsx @@ -10,7 +10,7 @@ import { SkeletonTableInvitation, } from "@/components/invitations/table"; import { Header } from "@/components/ui"; -import { DataTable } from "@/components/ui/table"; +import { DataTable, DataTableFilterCustom } from "@/components/ui/table"; import { SearchParamsProps } from "@/types"; export default async function Invitations({ @@ -25,9 +25,10 @@ export default async function Invitations({
- + - + + }> @@ -59,7 +60,6 @@ const SSRDataTable = async ({ columns={ColumnsInvitation} data={invitationsData?.data || []} metadata={invitationsData?.meta} - customFilters={filterInvitations} /> ); }; diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index 5b44c3d2d1..03c8ea8edf 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -9,7 +9,7 @@ import { SkeletonTableProviders, } from "@/components/providers/table"; import { Header } from "@/components/ui"; -import { DataTable } from "@/components/ui/table"; +import { DataTable, DataTableFilterCustom } from "@/components/ui/table"; import { SearchParamsProps } from "@/types"; export default async function Providers({ @@ -25,9 +25,10 @@ export default async function Providers({ - + - + + }> @@ -58,7 +59,6 @@ const SSRDataTable = async ({ columns={ColumnProviders} data={providersData?.data || []} metadata={providersData?.meta} - customFilters={filterProviders} /> ); }; diff --git a/app/(prowler)/scans/page.tsx b/app/(prowler)/scans/page.tsx index 4ea3db2b76..1c251114cb 100644 --- a/app/(prowler)/scans/page.tsx +++ b/app/(prowler)/scans/page.tsx @@ -8,7 +8,7 @@ import { LaunchScanWorkflow } from "@/components/scans/launch-workflow"; import { SkeletonTableScans } from "@/components/scans/table"; import { ColumnGetScans } from "@/components/scans/table/scans"; import { Header } from "@/components/ui"; -import { DataTable } from "@/components/ui/table"; +import { DataTable, DataTableFilterCustom } from "@/components/ui/table"; import { ProviderProps, SearchParamsProps } from "@/types"; export default async function Scans({ @@ -39,6 +39,8 @@ export default async function Scans({ + +
@@ -76,7 +78,6 @@ const SSRDataTableScans = async ({ columns={ColumnGetScans} data={scansData?.data || []} metadata={scansData?.meta} - customFilters={filterScans} /> ); }; diff --git a/app/(prowler)/users/page.tsx b/app/(prowler)/users/page.tsx index 805c8271e6..853bf2209e 100644 --- a/app/(prowler)/users/page.tsx +++ b/app/(prowler)/users/page.tsx @@ -5,7 +5,7 @@ import { getUsers } from "@/actions/users/users"; import { FilterControls } from "@/components/filters"; import { filterUsers } from "@/components/filters/data-filters"; import { Header } from "@/components/ui"; -import { DataTable } from "@/components/ui/table"; +import { DataTable, DataTableFilterCustom } from "@/components/ui/table"; import { AddUserButton } from "@/components/users"; import { ColumnsUser, SkeletonTableUser } from "@/components/users/table"; import { SearchParamsProps } from "@/types"; @@ -22,9 +22,10 @@ export default async function Users({
- + - + + }> @@ -56,7 +57,6 @@ const SSRDataTable = async ({ columns={ColumnsUser} data={usersData?.data || []} metadata={usersData?.meta} - customFilters={filterUsers} /> ); }; diff --git a/components/ui/table/data-table.tsx b/components/ui/table/data-table.tsx index f1ddd28072..ad5a866a01 100644 --- a/components/ui/table/data-table.tsx +++ b/components/ui/table/data-table.tsx @@ -14,7 +14,6 @@ import { import { useState } from "react"; import { - DataTableFilterCustom, Table, TableBody, TableCell, @@ -36,7 +35,6 @@ export function DataTable({ columns, data, metadata, - customFilters, }: DataTableProviderProps) { const [sorting, setSorting] = useState([]); const [columnFilters, setColumnFilters] = useState([]); @@ -58,11 +56,6 @@ export function DataTable({ return ( <> - {customFilters && ( -
- -
- )}
From ebc96bed06509a7ad01353ce1e026b120d1d7c32 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 20 Nov 2024 10:16:56 +0100 Subject: [PATCH 09/13] chore: spacing tweaks --- app/(prowler)/invitations/page.tsx | 1 + app/(prowler)/providers/page.tsx | 1 + app/(prowler)/users/page.tsx | 1 + 3 files changed, 3 insertions(+) diff --git a/app/(prowler)/invitations/page.tsx b/app/(prowler)/invitations/page.tsx index f444aa474a..b18cbbcba3 100644 --- a/app/(prowler)/invitations/page.tsx +++ b/app/(prowler)/invitations/page.tsx @@ -27,6 +27,7 @@ export default async function Invitations({ + diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index 03c8ea8edf..7aae9df08d 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -27,6 +27,7 @@ export default async function Providers({ + diff --git a/app/(prowler)/users/page.tsx b/app/(prowler)/users/page.tsx index 853bf2209e..364651ca22 100644 --- a/app/(prowler)/users/page.tsx +++ b/app/(prowler)/users/page.tsx @@ -24,6 +24,7 @@ export default async function Users({ + From 7fd53c1bc3462bf2c8fe3add23cc3efb55804542 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 20 Nov 2024 13:58:45 +0100 Subject: [PATCH 10/13] feat: tweaks filters --- app/(prowler)/findings/page.tsx | 86 ++++++++++++++++++- components/compliance/compliance-card.tsx | 32 ++++--- components/filters/data-filters.ts | 11 ++- .../findings/table/data-table-row-actions.tsx | 2 +- .../ui/custom/custom-dropdown-filter.tsx | 6 +- .../ui/table/data-table-filter-custom.tsx | 23 +++-- 6 files changed, 129 insertions(+), 31 deletions(-) diff --git a/app/(prowler)/findings/page.tsx b/app/(prowler)/findings/page.tsx index 505a614a6b..c1da5ffbb9 100644 --- a/app/(prowler)/findings/page.tsx +++ b/app/(prowler)/findings/page.tsx @@ -2,6 +2,8 @@ import { Spacer } from "@nextui-org/react"; import React, { Suspense } from "react"; import { getFindings } from "@/actions/findings"; +import { getProviders } from "@/actions/providers"; +import { getScans } from "@/actions/scans"; import { filterFindings } from "@/components/filters/data-filters"; import { FilterControls } from "@/components/filters/filter-controls"; import { @@ -20,14 +22,94 @@ export default async function Findings({ }) { const searchParamsKey = JSON.stringify(searchParams || {}); + // Get findings data + const findingsData = await getFindings({}); + const providersData = await getProviders({}); + const scansData = await getScans({}); + + // Extract provider UIDs + const providerUIDs = providersData?.data + ?.map((provider: any) => provider.attributes.uid) + .filter(Boolean); + + // Extract scan UUIDs with "completed" state and more than one resource + const completedScans = scansData?.data + ?.filter( + (scan: any) => + scan.attributes.state === "completed" && + scan.attributes.unique_resource_count > 1 && + scan.attributes.name, // Ensure it has a name + ) + .map((scan: any) => ({ + id: scan.id, + name: scan.attributes.name, + })); + + const completedScanIds = completedScans?.map((scan: any) => scan.id) || []; + + // Create resource dictionary + 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 uniqueRegions = Array.from( + new Set( + allRegionsAndServices + .map((item: { region: string }) => item.region) + .filter(Boolean), + ), + ); + const uniqueServices = Array.from( + new Set( + allRegionsAndServices + .map((item: { service: string }) => item.service) + .filter(Boolean), + ), + ); + return ( <>
- + - + }> diff --git a/components/compliance/compliance-card.tsx b/components/compliance/compliance-card.tsx index 41cd740a73..7fcfdbf5dd 100644 --- a/components/compliance/compliance-card.tsx +++ b/components/compliance/compliance-card.tsx @@ -16,27 +16,25 @@ export const ComplianceCard: React.FC = ({ title, passingRequirements, totalRequirements, - prevPassingRequirements, - prevTotalRequirements, }) => { const ratingPercentage = Math.floor( (passingRequirements / totalRequirements) * 100, ); - const prevRatingPercentage = Math.floor( - (prevPassingRequirements / prevTotalRequirements) * 100, - ); + // const prevRatingPercentage = Math.floor( + // (prevPassingRequirements / prevTotalRequirements) * 100, + // ); - const getScanChange = () => { - const scanDifference = ratingPercentage - prevRatingPercentage; - if (scanDifference < 0 && scanDifference <= -1) { - return `${scanDifference}% from last scan`; - } - if (scanDifference > 0 && scanDifference >= 1) { - return `+${scanDifference}% from last scan`; - } - return "No change from last scan"; - }; + // const getScanChange = () => { + // const scanDifference = ratingPercentage - prevRatingPercentage; + // if (scanDifference < 0 && scanDifference <= -1) { + // return `${scanDifference}% from last scan`; + // } + // if (scanDifference > 0 && scanDifference >= 1) { + // return `+${scanDifference}% from last scan`; + // } + // return "No changes from last scan"; + // }; const getRatingColor = (ratingPercentage: number) => { if (ratingPercentage <= 10) { @@ -50,7 +48,7 @@ export const ComplianceCard: React.FC = ({ return ( - +
= ({ Passing Requirements - {getScanChange()} + {/* {getScanChange()} */}
diff --git a/components/filters/data-filters.ts b/components/filters/data-filters.ts index 91a039605d..e396791b64 100644 --- a/components/filters/data-filters.ts +++ b/components/filters/data-filters.ts @@ -35,20 +35,25 @@ export const filterScans = [ export const filterFindings = [ { - key: "severity", + key: "severity__in", labelCheckboxGroup: "Severity", values: ["critical", "high", "medium", "low", "informational"], }, { - key: "status", + key: "status__in", labelCheckboxGroup: "Status", values: ["PASS", "FAIL", "MANUAL", "MUTED"], }, { - key: "delta", + key: "delta__in", labelCheckboxGroup: "Delta", values: ["new", "changed"], }, + { + key: "provider_type__in", + labelCheckboxGroup: "Provider", + values: ["aws", "azure", "gcp", "kubernetes"], + }, // Add more filter categories as needed ]; diff --git a/components/findings/table/data-table-row-actions.tsx b/components/findings/table/data-table-row-actions.tsx index 287e268ffb..8e15c942a0 100644 --- a/components/findings/table/data-table-row-actions.tsx +++ b/components/findings/table/data-table-row-actions.tsx @@ -31,7 +31,6 @@ export function DataTableRowActions({ row, }: DataTableRowActionsProps) { const findingId = (row.original as { id: string }).id; - console.log(findingId); return ( <> {/* ({ startContent={} // onClick={() => setIsEditOpen(true)} > + {findingId} Send to Jira = ({ filter, @@ -97,9 +97,9 @@ export const CustomDropdownFilter: React.FC = ({ )} @@ -149,7 +172,7 @@ export const CustomDropdownFilter: React.FC = ({ > {allFilterKeys.map((value) => ( - {_.capitalize(value)} + {value} ))}