diff --git a/actions/overview/overview.ts b/actions/overview/overview.ts new file mode 100644 index 0000000000..8d83ab7088 --- /dev/null +++ b/actions/overview/overview.ts @@ -0,0 +1,49 @@ +"use server"; +import { revalidatePath } from "next/cache"; +import { redirect } from "next/navigation"; + +import { auth } from "@/auth.config"; +import { parseStringify } from "@/lib"; + +export const getProvidersOverview = async ({ + page = 1, + query = "", + sort = "", + filters = {}, +}) => { + const session = await auth(); + + if (isNaN(Number(page)) || page < 1) redirect("/providers-overview"); + + const keyServer = process.env.API_BASE_URL; + const url = new URL(`${keyServer}/overviews/providers`); + + if (page) url.searchParams.append("page[number]", page.toString()); + if (query) url.searchParams.append("filter[search]", query); + if (sort) url.searchParams.append("sort", sort); + + // Handle multiple filters + Object.entries(filters).forEach(([key, value]) => { + if (key !== "filter[search]") { + url.searchParams.append(key, String(value)); + } + }); + + try { + const response = await fetch(url.toString(), { + headers: { + Accept: "application/vnd.api+json", + Authorization: `Bearer ${session?.accessToken}`, + }, + }); + + const data = await response.json(); + const parsedData = parseStringify(data); + revalidatePath("/providers-overview"); + return parsedData; + } catch (error) { + // eslint-disable-next-line no-console + console.error("Error fetching providers overview:", error); + return undefined; + } +}; diff --git a/app/(prowler)/page.tsx b/app/(prowler)/page.tsx index 335b53b4f4..bae0d835c4 100644 --- a/app/(prowler)/page.tsx +++ b/app/(prowler)/page.tsx @@ -1,32 +1,42 @@ import { Spacer } from "@nextui-org/react"; +import { Suspense } from "react"; +import { getProvidersOverview } from "@/actions/overview/overview"; +import { + ProvidersOverview, + SkeletonProvidersOverview, +} from "@/components/overview"; import { Header } from "@/components/ui"; export default function Home() { return ( <>
- - {/*
- - - - - - - - - -
*/} + +
+
+ {/* Providers Overview */} +
+ }> + + +
+ +
+ +
+
+
); } + +const SSRProvidersOverview = async () => { + const providersOverview = await getProvidersOverview({}); + + if (!providersOverview) { + return

There is no providers overview info available

; + } + + return ; +}; diff --git a/components/overview/index.ts b/components/overview/index.ts index 452749b068..eb393767f1 100644 --- a/components/overview/index.ts +++ b/components/overview/index.ts @@ -1 +1,3 @@ export * from "./AttackSurface"; +export * from "./provider-overview/provider-overview"; +export * from "./provider-overview/skeleton-provider-overview"; diff --git a/components/overview/provider-overview/provider-overview.tsx b/components/overview/provider-overview/provider-overview.tsx new file mode 100644 index 0000000000..cd69f02595 --- /dev/null +++ b/components/overview/provider-overview/provider-overview.tsx @@ -0,0 +1,150 @@ +"use client"; + +import { Card, CardBody, CardHeader } from "@nextui-org/react"; + +import { AddIcon } from "@/components/icons/Icons"; +import { + AWSProviderBadge, + AzureProviderBadge, + GCPProviderBadge, + KS8ProviderBadge, +} from "@/components/icons/providers-badge"; +import { CustomButton } from "@/components/ui/custom/custom-button"; +import { ProviderOverviewProps } from "@/types"; + +export const ProvidersOverview = ({ + providersOverview, +}: { + providersOverview: ProviderOverviewProps; +}) => { + console.log(providersOverview); + if (!providersOverview || !Array.isArray(providersOverview.data)) { + return

No provider data available

; + } + + const calculatePassingPercentage = (pass: number, total: number) => + total > 0 ? ((pass / total) * 100).toFixed(2) : "0.00"; + + const renderProviderBadge = (providerId: string) => { + switch (providerId) { + case "aws": + return ; + case "azure": + return ; + case "gcp": + return ; + case "kubernetes": + return ; + default: + return null; + } + }; + + const providers = [ + { id: "aws", name: "AWS" }, + { id: "azure", name: "Azure" }, + { id: "gcp", name: "GCP" }, + { id: "kubernetes", name: "Kubernetes" }, + ]; + + return ( + + +

Providers Overview

+
+ +
+
+ Provider + + Percent + Passing + + + Failing + Checks + + + Total + Resources + +
+ + {providers.map((providerTemplate) => { + const providerData = providersOverview.data.find( + (p) => p.id === providerTemplate.id, + ); + + return ( +
+ + {renderProviderBadge(providerTemplate.id)} + + + {providerData + ? calculatePassingPercentage( + providerData.attributes.findings.pass, + providerData.attributes.findings.total, + ) + : "0.00"} + % + + + {providerData ? providerData.attributes.findings.fail : "-"} + + + {providerData ? providerData.attributes.resources.total : "-"} + +
+ ); + })} + + {/* Totals row */} +
+ Total + + {calculatePassingPercentage( + providersOverview.data.reduce( + (sum, provider) => sum + provider.attributes.findings.pass, + 0, + ), + providersOverview.data.reduce( + (sum, provider) => sum + provider.attributes.findings.total, + 0, + ), + )} + % + + + {providersOverview.data.reduce( + (sum, provider) => sum + provider.attributes.findings.fail, + 0, + )} + + + {providersOverview.data.reduce( + (sum, provider) => sum + provider.attributes.resources.total, + 0, + )} + +
+
+
+ } + > + Add Provider + +
+
+
+ ); +}; diff --git a/components/overview/provider-overview/skeleton-provider-overview.tsx b/components/overview/provider-overview/skeleton-provider-overview.tsx new file mode 100644 index 0000000000..47febd1700 --- /dev/null +++ b/components/overview/provider-overview/skeleton-provider-overview.tsx @@ -0,0 +1,64 @@ +import { Card, CardBody, CardHeader, Skeleton } from "@nextui-org/react"; + +export const SkeletonProvidersOverview = () => { + const rows = 4; + + return ( + + + +
+
+
+ +
+ {/* Header Skeleton */} +
+ +
+
+ +
+
+ +
+
+ +
+
+
+ + {/* Row Skeletons */} + {Array.from({ length: rows }).map((_, index) => ( +
+ {/* Provider Name */} +
+ +
+
+ +
+
+
+ {/* Percent Passing */} + +
+
+ {/* Failing Checks */} + +
+
+ {/* Total Resources */} + +
+
+
+ ))} +
+
+
+ ); +}; diff --git a/types/components.ts b/types/components.ts index 7ec383d408..bbc7024646 100644 --- a/types/components.ts +++ b/types/components.ts @@ -26,6 +26,27 @@ export type NextUIColors = | "danger" | "default"; +export interface ProviderOverviewProps { + data: { + type: "provider-overviews"; + id: "aws" | "gcp" | "azure" | "kubernetes"; + attributes: { + findings: { + pass: number; + fail: number; + manual: number; + total: number; + }; + resources: { + total: number; + }; + }; + }[]; + meta: { + version: string; + }; +} + export interface TaskDetails { attributes: { state: string;