+
New failing findings to date
-
+
+
+
>
);
diff --git a/ui/components/charts/SeverityChart.tsx b/ui/components/charts/SeverityChart.tsx
deleted file mode 100644
index 4c3060c8bd..0000000000
--- a/ui/components/charts/SeverityChart.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-"use client";
-
-import { Bar, BarChart, LabelList, XAxis, YAxis } from "recharts";
-
-import {
- ChartConfig,
- ChartContainer,
- ChartTooltip,
- ChartTooltipContent,
-} from "@/components/ui/chart/Chart";
-
-const chartData = [
- { severity: "critical", findings: 32, fill: "var(--color-critical)" },
- { severity: "high", findings: 78, fill: "var(--color-high)" },
- { severity: "medium", findings: 117, fill: "var(--color-medium)" },
- { severity: "low", findings: 39, fill: "var(--color-low)" },
-];
-
-const chartConfig = {
- findings: {
- label: "Findings",
- },
- critical: {
- label: "Critical",
- color: "hsl(var(--chart-critical))",
- },
- high: {
- label: "High",
- color: "hsl(var(--chart-fail))",
- },
- medium: {
- label: "Medium",
- color: "hsl(var(--chart-medium))",
- },
- low: {
- label: "Low",
- color: "hsl(var(--chart-low))",
- },
-} satisfies ChartConfig;
-
-export const SeverityChart = () => {
- return (
-
-
-
-
- chartConfig[value as keyof typeof chartConfig]?.label
- }
- />
-
-
-
- }
- />
-
-
-
-
-
-
-
- );
-};
diff --git a/ui/components/charts/StatusChart.tsx b/ui/components/charts/StatusChart.tsx
deleted file mode 100644
index d906db502e..0000000000
--- a/ui/components/charts/StatusChart.tsx
+++ /dev/null
@@ -1,148 +0,0 @@
-"use client";
-
-import { Chip, Divider, Spacer } from "@nextui-org/react";
-import { TrendingUp } from "lucide-react";
-import * as React from "react";
-import { Label, Pie, PieChart } from "recharts";
-
-import { NotificationIcon, SuccessIcon } from "../icons";
-import {
- ChartConfig,
- ChartContainer,
- ChartTooltip,
- ChartTooltipContent,
-} from "../ui";
-
-const calculatePercent = (
- chartData: { findings: string; number: number; fill: string }[],
-) => {
- const total = chartData.reduce((sum, item) => sum + item.number, 0);
-
- return chartData.map((item) => ({
- ...item,
- percent: Math.round((item.number / total) * 100) + "%",
- }));
-};
-
-const chartData = [
- {
- findings: "Success",
- number: 436,
- fill: "var(--color-success)",
- },
- { findings: "Fail", number: 293, fill: "var(--color-fail)" },
-];
-
-const updatedChartData = calculatePercent(chartData);
-
-const chartConfig = {
- number: {
- label: "Findings",
- },
- success: {
- label: "Success",
- color: "hsl(var(--chart-success))",
- },
- fail: {
- label: "Fail",
- color: "hsl(var(--chart-fail))",
- },
-} satisfies ChartConfig;
-
-export function StatusChart() {
- const totalVisitors = React.useMemo(() => {
- return chartData.reduce((acc, curr) => acc + curr.number, 0);
- }, []);
-
- return (
-
-
-
- } />
-
-
-
-
-
-
-
}
- color="success"
- radius="lg"
- size="md"
- >
- {chartData[0].number}
-
-
-
{updatedChartData[0].percent}
-
-
-
- No change from last scan
-
-
-
-
-
}
- color="danger"
- radius="lg"
- size="md"
- >
- {chartData[1].number}
-
-
-
{updatedChartData[1].percent}
-
-
-
- +2 findings from last scan
-
-
-
-
- );
-}
diff --git a/ui/components/charts/index.ts b/ui/components/charts/index.ts
deleted file mode 100644
index b4d3debb45..0000000000
--- a/ui/components/charts/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from "./SeverityChart";
-export * from "./StatusChart";
diff --git a/ui/components/filters/custom-date-picker.tsx b/ui/components/filters/custom-date-picker.tsx
index 2a982c1781..21bb209282 100644
--- a/ui/components/filters/custom-date-picker.tsx
+++ b/ui/components/filters/custom-date-picker.tsx
@@ -60,6 +60,8 @@ export const CustomDatePicker = () => {
{
+export const CustomRegionSelection: React.FC = () => {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ // Memoize selected keys based on the URL
+ const selectedKeys = useMemo(() => {
+ const params = searchParams.get("filter[regions]");
+ return params ? params.split(",") : [];
+ }, [searchParams]);
+
+ const applyRegionFilter = useCallback(
+ (values: string[]) => {
+ const params = new URLSearchParams(searchParams.toString());
+ if (values.length > 0) {
+ params.set("filter[regions]", values.join(","));
+ } else {
+ params.delete("filter[regions]");
+ }
+ router.push(`?${params.toString()}`, { scroll: false });
+ },
+ [router, searchParams],
+ );
+
return (
);
diff --git a/ui/components/filters/custom-search-input.tsx b/ui/components/filters/custom-search-input.tsx
index f3d9808125..a5f4519877 100644
--- a/ui/components/filters/custom-search-input.tsx
+++ b/ui/components/filters/custom-search-input.tsx
@@ -39,8 +39,9 @@ export const CustomSearchInput: React.FC = () => {
}
onChange={(e) => {
diff --git a/ui/components/filters/custom-select-provider.tsx b/ui/components/filters/custom-select-provider.tsx
index d2a419bbbb..907e8be10b 100644
--- a/ui/components/filters/custom-select-provider.tsx
+++ b/ui/components/filters/custom-select-provider.tsx
@@ -66,7 +66,8 @@ export const CustomSelectProvider: React.FC = () => {
items={dataInputsProvider}
aria-label="Select a Provider"
placeholder="Select a provider"
- labelPlacement="outside"
+ label="Provider"
+ labelPlacement="inside"
size="sm"
onChange={(e) => {
const value = e.target.value;
diff --git a/ui/components/overview/findings-by-severity-chart/findings-by-severity-chart.tsx b/ui/components/overview/findings-by-severity-chart/findings-by-severity-chart.tsx
new file mode 100644
index 0000000000..7e590fb0d1
--- /dev/null
+++ b/ui/components/overview/findings-by-severity-chart/findings-by-severity-chart.tsx
@@ -0,0 +1,108 @@
+"use client";
+
+import { Card, CardBody } from "@nextui-org/react";
+import { Bar, BarChart, LabelList, XAxis, YAxis } from "recharts";
+
+import {
+ ChartConfig,
+ ChartContainer,
+ ChartTooltip,
+ ChartTooltipContent,
+} from "@/components/ui/chart/Chart";
+import { FindingsSeverityOverview } from "@/types/components";
+
+const chartConfig = {
+ critical: {
+ label: "Critical",
+ color: "hsl(var(--chart-critical))",
+ },
+ high: {
+ label: "High",
+ color: "hsl(var(--chart-fail))",
+ },
+ medium: {
+ label: "Medium",
+ color: "hsl(var(--chart-medium))",
+ },
+ low: {
+ label: "Low",
+ color: "hsl(var(--chart-low))",
+ },
+ informational: {
+ label: "Informational",
+ color: "hsl(var(--chart-informational))",
+ },
+} satisfies ChartConfig;
+
+export const FindingsBySeverityChart = ({
+ findingsBySeverity,
+}: {
+ findingsBySeverity: FindingsSeverityOverview;
+}) => {
+ const defaultAttributes = {
+ critical: 0,
+ high: 0,
+ medium: 0,
+ low: 0,
+ informational: 0,
+ };
+
+ const attributes = findingsBySeverity?.data?.attributes || defaultAttributes;
+
+ const chartData = Object.entries(attributes).map(([severity, findings]) => ({
+ severity,
+ findings,
+ fill: chartConfig[severity as keyof typeof chartConfig]?.color,
+ }));
+
+ return (
+
+
+
+
+
+
+ chartConfig[value as keyof typeof chartConfig]?.label
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/ui/components/overview/findings-by-severity-chart/skeleton-findings-severity-chart.tsx b/ui/components/overview/findings-by-severity-chart/skeleton-findings-severity-chart.tsx
new file mode 100644
index 0000000000..e3eb51b49f
--- /dev/null
+++ b/ui/components/overview/findings-by-severity-chart/skeleton-findings-severity-chart.tsx
@@ -0,0 +1,62 @@
+import { Card, CardBody, CardHeader, Skeleton } from "@nextui-org/react";
+
+export const SkeletonFindingsBySeverityChart = () => {
+ return (
+
+
+
+
+
+
+
+
+ {/* Critical */}
+
+ {/* High */}
+
+ {/* Medium */}
+
+ {/* Low */}
+
+ {/* Informational */}
+
+
+
+
+ );
+};
diff --git a/ui/components/overview/findings-by-status-chart/findings-by-status-chart.tsx b/ui/components/overview/findings-by-status-chart/findings-by-status-chart.tsx
new file mode 100644
index 0000000000..0f6151ce96
--- /dev/null
+++ b/ui/components/overview/findings-by-status-chart/findings-by-status-chart.tsx
@@ -0,0 +1,196 @@
+"use client";
+
+import { Card, CardBody } from "@nextui-org/react";
+import { Chip } from "@nextui-org/react";
+import { TrendingUp } from "lucide-react";
+import React, { useMemo } from "react";
+import { Label, Pie, PieChart } from "recharts";
+
+import { NotificationIcon, SuccessIcon } from "@/components/icons";
+import {
+ ChartConfig,
+ ChartContainer,
+ ChartTooltip,
+ ChartTooltipContent,
+} from "@/components/ui/chart/Chart";
+
+const calculatePercent = (
+ chartData: { findings: string; number: number; fill: string }[],
+) => {
+ const total = chartData.reduce((sum, item) => sum + item.number, 0);
+
+ return chartData.map((item) => ({
+ ...item,
+ percent: total > 0 ? Math.round((item.number / total) * 100) + "%" : "0%",
+ }));
+};
+
+interface FindingsByStatusChartProps {
+ findingsByStatus: {
+ data: {
+ attributes: {
+ fail: number;
+ pass: number;
+ pass_new: number;
+ fail_new: number;
+ total: number;
+ };
+ };
+ };
+}
+
+const chartConfig = {
+ number: {
+ label: "Findings",
+ },
+ success: {
+ label: "Success",
+ color: "hsl(var(--chart-success))",
+ },
+ fail: {
+ label: "Fail",
+ color: "hsl(var(--chart-fail))",
+ },
+} satisfies ChartConfig;
+
+export const FindingsByStatusChart: React.FC = ({
+ findingsByStatus,
+}) => {
+ const {
+ fail = 0,
+ pass = 0,
+ pass_new = 0,
+ fail_new = 0,
+ } = findingsByStatus?.data?.attributes || {};
+ const chartData = [
+ {
+ findings: "Success",
+ number: pass,
+ fill: "var(--color-success)",
+ },
+ {
+ findings: "Fail",
+ number: fail,
+ fill: "var(--color-fail)",
+ },
+ ];
+
+ const updatedChartData = calculatePercent(chartData);
+
+ const totalFindings = useMemo(
+ () => chartData.reduce((acc, curr) => acc + curr.number, 0),
+ [chartData],
+ );
+
+ const emptyChartData = [
+ {
+ findings: "Empty",
+ number: 1,
+ fill: "hsl(var(--nextui-default-200))",
+ },
+ ];
+
+ return (
+
+
+
+
+
+ } />
+ 0 ? chartData : emptyChartData}
+ dataKey="number"
+ nameKey="findings"
+ innerRadius={40}
+ strokeWidth={35}
+ >
+
+
+
+
+
+
+ }
+ color="success"
+ radius="lg"
+ size="md"
+ >
+ {chartData[0].number}
+
+ {updatedChartData[0].percent}
+
+
+ {pass_new > 0 ? (
+ <>
+ +{pass_new} pass findings from last day{" "}
+
+ >
+ ) : pass_new < 0 ? (
+ <>{pass_new} pass findings from last day>
+ ) : (
+ "No change from last day"
+ )}
+
+
+
+
+ }
+ color="danger"
+ radius="lg"
+ size="md"
+ >
+ {chartData[1].number}
+
+ {updatedChartData[1].percent}
+
+
+ +{fail_new} fail findings from last day{" "}
+
+
+
+
+
+
+
+ );
+};
diff --git a/ui/components/overview/findings-by-status-chart/skeleton-findings-status-chart.tsx b/ui/components/overview/findings-by-status-chart/skeleton-findings-status-chart.tsx
new file mode 100644
index 0000000000..f4b8a9d12b
--- /dev/null
+++ b/ui/components/overview/findings-by-status-chart/skeleton-findings-status-chart.tsx
@@ -0,0 +1,54 @@
+import { Card, CardBody, CardHeader, Skeleton } from "@nextui-org/react";
+
+export const SkeletonFindingsByStatusChart = () => {
+ return (
+
+
+
+
+
+
+
+
+ {/* Circle Chart Skeleton */}
+
+
+
+
+ {/* Text Details Skeleton */}
+
+ {/* Pass Findings */}
+
+
+ {/* Fail Findings */}
+
+
+
+
+
+ );
+};
diff --git a/ui/components/overview/index.ts b/ui/components/overview/index.ts
index ed098ab865..e2beebb034 100644
--- a/ui/components/overview/index.ts
+++ b/ui/components/overview/index.ts
@@ -1,4 +1,8 @@
export * from "./AttackSurface";
+export * from "./findings-by-severity-chart/findings-by-severity-chart";
+export * from "./findings-by-severity-chart/skeleton-findings-severity-chart";
+export * from "./findings-by-status-chart/findings-by-status-chart";
+export * from "./findings-by-status-chart/skeleton-findings-status-chart";
export * from "./new-findings-table/link-to-findings/link-to-findings";
export * from "./provider-overview/provider-overview";
export * from "./provider-overview/skeleton-provider-overview";
diff --git a/ui/components/overview/new-findings-table/link-to-findings/link-to-findings.tsx b/ui/components/overview/new-findings-table/link-to-findings/link-to-findings.tsx
index 2cd675147a..533b32cb01 100644
--- a/ui/components/overview/new-findings-table/link-to-findings/link-to-findings.tsx
+++ b/ui/components/overview/new-findings-table/link-to-findings/link-to-findings.tsx
@@ -7,7 +7,7 @@ export const LinkToFindings = () => {
return (