diff --git a/app/(prowler)/compliance/page.tsx b/app/(prowler)/compliance/page.tsx index 08368c69da..38c9800467 100644 --- a/app/(prowler)/compliance/page.tsx +++ b/app/(prowler)/compliance/page.tsx @@ -17,12 +17,18 @@ export default async function Compliance({ searchParams: SearchParamsProps; }) { const scansData = await getScans({}); - const scanList = scansData?.data.map((scan: any) => ({ - id: scan.id, - name: scan.attributes.name || "Unnamed Scan", - state: scan.attributes.state, - progress: scan.attributes.progress, - })); + const scanList = scansData?.data + .filter( + (scan: any) => + scan.attributes.state === "completed" && + scan.attributes.progress === 100, + ) + .map((scan: any) => ({ + id: scan.id, + name: scan.attributes.name || "Unnamed Scan", + state: scan.attributes.state, + progress: scan.attributes.progress, + })); const selectedScanId = searchParams.scanId || scanList[0]?.id; @@ -92,7 +98,7 @@ const SSRComplianceGrid = async ({ } return ( -
+
{compliancesData.data.map((compliance: ComplianceOverviewData) => { const { attributes } = compliance; const { diff --git a/app/(prowler)/findings/page.tsx b/app/(prowler)/findings/page.tsx index 4a0a80b8dd..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 { @@ -9,7 +11,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"; @@ -20,13 +22,95 @@ 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 ( <>
- - + + + + }> @@ -53,9 +137,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 @@ -84,7 +168,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..b18cbbcba3 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,11 @@ export default async function Invitations({
- + + + }> @@ -59,7 +61,6 @@ const SSRDataTable = async ({ columns={ColumnsInvitation} data={invitationsData?.data || []} metadata={invitationsData?.meta} - customFilters={filterInvitations} /> ); }; 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/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index 5b44c3d2d1..7aae9df08d 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,11 @@ export default async function Providers({ - + + + }> @@ -58,7 +60,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..364651ca22 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,11 @@ export default async function Users({
- + + + }> @@ -56,7 +58,6 @@ const SSRDataTable = async ({ columns={ColumnsUser} data={usersData?.data || []} metadata={usersData?.meta} - customFilters={filterUsers} /> ); }; 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/custo-checkbox-muted-findings.tsx b/components/filters/custom-checkbox-muted-findings.tsx similarity index 100% rename from components/filters/custo-checkbox-muted-findings.tsx rename to components/filters/custom-checkbox-muted-findings.tsx 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={ { (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/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/filters/filter-controls.tsx b/components/filters/filter-controls.tsx index 28c455ceea..6f08b5c928 100644 --- a/components/filters/filter-controls.tsx +++ b/components/filters/filter-controls.tsx @@ -8,8 +8,8 @@ import { FilterControlsProps } from "@/types"; import { CrossIcon } from "../icons"; import { CustomButton } from "../ui/custom"; import { DataTableFilterCustom } from "../ui/table"; -import { CustomCheckboxMutedFindings } from "./custo-checkbox-muted-findings"; import { CustomAccountSelection } from "./custom-account-selection"; +import { CustomCheckboxMutedFindings } from "./custom-checkbox-muted-findings"; import { CustomDatePicker } from "./custom-date-picker"; import { CustomRegionSelection } from "./custom-region-selection"; import { CustomSearchInput } from "./custom-search-input"; diff --git a/components/filters/index.ts b/components/filters/index.ts index fb712b3168..915bbd3aec 100644 --- a/components/filters/index.ts +++ b/components/filters/index.ts @@ -1,5 +1,5 @@ -export * from "./custo-checkbox-muted-findings"; export * from "./custom-account-selection"; +export * from "./custom-checkbox-muted-findings"; export * from "./custom-date-picker"; export * from "./custom-provider-inputs"; export * from "./custom-region-selection"; diff --git a/components/findings/table/column-findings.tsx b/components/findings/table/column-findings.tsx index 4d0d7dd3ab..d5c06862db 100644 --- a/components/findings/table/column-findings.tsx +++ b/components/findings/table/column-findings.tsx @@ -1,22 +1,20 @@ "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 { 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 }) => { return row.original; }; @@ -58,30 +56,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 +82,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 +126,7 @@ export const ColumnFindings: ColumnDef[] = [ header: "Service", cell: ({ row }) => { const { servicename } = getFindingsMetadata(row); - return

{servicename}

; + return

{servicename}

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

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

); }, @@ -140,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/findings/table/data-table-row-actions.tsx b/components/findings/table/data-table-row-actions.tsx index f96f804fc9..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 ( <> {/* ({ */}
- +
)} - +
= ({ > {allFilterKeys.map((value) => ( - {_.capitalize(value)} + {value} ))} 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 992b3ac098..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-filter-custom.tsx b/components/ui/table/data-table-filter-custom.tsx index 5dc2f94dc2..3ef0c0d330 100644 --- a/components/ui/table/data-table-filter-custom.tsx +++ b/components/ui/table/data-table-filter-custom.tsx @@ -10,14 +10,16 @@ import { FilterOption } from "@/types"; export interface DataTableFilterCustomProps { filters: FilterOption[]; + defaultOpen?: boolean; } export const DataTableFilterCustom = ({ filters, + defaultOpen = false, }: DataTableFilterCustomProps) => { const router = useRouter(); const searchParams = useSearchParams(); - const [showFilters, setShowFilters] = useState(false); + const [showFilters, setShowFilters] = useState(defaultOpen); const pushDropdownFilter = useCallback( (key: string, values: string[]) => { @@ -36,14 +38,19 @@ export const DataTableFilterCustom = ({ ); return ( -
+
4 ? "flex-col" : "flex-col md:flex-row" + } gap-4`} + > } onPress={() => setShowFilters(!showFilters)} + className="w-fit" >

{showFilters ? "Hide Filters" : "Show Filters"} @@ -53,11 +60,17 @@ export const DataTableFilterCustom = ({
-
+
4 + ? "grid-cols-1 md:grid-cols-4" + : "grid-cols-1 md:grid-cols-3" + }`} + > {filters.map((filter) => ( @@ -57,7 +57,7 @@ export function DataTablePagination({ metadata }: DataTablePaginationProps) { @@ -65,14 +65,14 @@ export function DataTablePagination({ metadata }: DataTablePaginationProps) {