Files
asm-dashboard/resources/js/Components/NumberOfVulnerabilitiesPieChart.tsx
Andrea Pavone 0e18b53f91 Init commit
Signed-off-by: Andrea Pavone <info@andreapavone.com>
2024-11-10 16:10:14 +01:00

158 lines
7.0 KiB
TypeScript

import React, { useState, useMemo } from 'react';
import { PieChart, Pie, Sector, Label } from 'recharts';
import { PieSectorDataItem } from "recharts/types/polar/Pie";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/Components/ui/card";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/Components/ui/select";
import {SecuritySummaryReportResultType} from "@/types/security-summary";
export default function NumberOfVulnerabilitiesPieChart({reportData}: { reportData: SecuritySummaryReportResultType }) {
const vulnerabilityData = useMemo(() => [
{ name: 'Active Critical', value: reportData.n_vulns.active.critical, fill: 'hsl(var(--chart-1))' },
{ name: 'Active High', value: reportData.n_vulns.active.high, fill: 'hsl(var(--chart-2))' },
{ name: 'Active Medium', value: reportData.n_vulns.active.medium, fill: 'hsl(var(--chart-3))' },
{ name: 'Passive High', value: reportData.n_vulns.passive.high, fill: 'hsl(var(--chart-4))' },
{ name: 'Passive Medium', value: reportData.n_vulns.passive.medium, fill: 'hsl(var(--chart-5))' }
], [reportData]);
const [activeMetric, setActiveMetric] = useState(vulnerabilityData[0].name);
const activeIndex = useMemo(
() => vulnerabilityData.findIndex((item) => item.name === activeMetric),
[activeMetric, vulnerabilityData]
);
const metrics = useMemo(() => vulnerabilityData.map((item) => item.name), [vulnerabilityData]);
const chartConfig: any = {
'Active Critical': {
label: "Active Critical",
color: 'hsl(var(--chart-1))'
},
'Active High': {
label: "Active High",
color: 'hsl(var(--chart-2))'
},
'Active Medium': {
label: "Active Medium",
color: 'hsl(var(--chart-3))'
},
'Passive High': {
label: "Passive High",
color: 'hsl(var(--chart-4))'
},
'Passive Medium': {
label: "Passive Medium",
color: 'hsl(var(--chart-5))'
}
};
return (
<Card>
<CardHeader className="flex flex-row items-start justify-between space-y-0 pb-0">
<div className="grid gap-1">
<CardTitle>Number of Vulnerabilities</CardTitle>
<CardDescription>Vulnerabilities details</CardDescription>
</div>
<Select value={activeMetric} onValueChange={setActiveMetric}>
<SelectTrigger
className="ml-auto h-8 w-[180px] rounded-lg"
aria-label="Select security metric"
>
<SelectValue placeholder="Select metric" />
</SelectTrigger>
<SelectContent align="end" className="rounded-lg">
{metrics.map((metric) => (
<SelectItem
key={metric}
value={metric}
className="rounded-lg [&_span]:flex"
>
<div className="flex items-center gap-2 text-xs">
<span
className="h-3 w-3 rounded-sm"
style={{
backgroundColor: chartConfig[metric]?.color
}}
/>
{chartConfig[metric].label}
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</CardHeader>
<CardContent className="flex flex-1 justify-center pb-0">
<div className="mx-auto aspect-square w-full max-w-[400px]">
<PieChart width={400} height={400}>
<Pie
data={vulnerabilityData}
dataKey="value"
nameKey="name"
cx="50%"
cy="50%"
innerRadius={60}
outerRadius={80}
activeIndex={activeIndex}
activeShape={({
outerRadius = 0,
...props
}: PieSectorDataItem) => (
<g>
<Sector {...props} outerRadius={outerRadius + 10} />
<Sector
{...props}
outerRadius={outerRadius + 25}
innerRadius={outerRadius + 12}
/>
</g>
)}
>
<Label
content={({ viewBox }) => {
if (viewBox && "cx" in viewBox && "cy" in viewBox) {
return (
<text
x={viewBox.cx}
y={viewBox.cy}
textAnchor="middle"
dominantBaseline="middle"
>
<tspan
x={viewBox.cx}
y={viewBox.cy}
className="fill-foreground text-3xl font-bold"
>
{vulnerabilityData[activeIndex].value}
</tspan>
<tspan
x={viewBox.cx}
y={(viewBox.cy || 0) + 24}
className="fill-muted-foreground text-sm"
>
{vulnerabilityData[activeIndex].name}
</tspan>
</text>
)
}
}}
/>
</Pie>
</PieChart>
</div>
</CardContent>
</Card>
);
}