157
resources/js/Components/NumberOfVulnerabilitiesPieChart.tsx
Normal file
157
resources/js/Components/NumberOfVulnerabilitiesPieChart.tsx
Normal file
@ -0,0 +1,157 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user