Skip to content

Commit

Permalink
✨ feat: Add Client Dashboard (#160)
Browse files Browse the repository at this point in the history
  • Loading branch information
caverav authored Nov 5, 2024
2 parents 7838149 + 24f70b1 commit f1cbc31
Show file tree
Hide file tree
Showing 21 changed files with 954 additions and 52 deletions.
4 changes: 4 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.3",
"@types/react-router-dom": "^5.3.3",
"@visx/text": "^3.3.0",
"@visx/wordcloud": "^3.3.0",
"ae-cvss-calculator": "^1.0.2",
"chart.js": "^4.4.4",
"chartjs-plugin-annotation": "^3.0.1",
Expand All @@ -42,6 +45,7 @@
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.3.1",
"react-i18next": "^15.0.0",
"react-icons": "^5.3.0",
"react-quill-new": "^3.3.0",
"react-router-dom": "^6.25.1",
"sonner": "^1.5.0",
Expand Down
115 changes: 115 additions & 0 deletions frontend/src/components/charts/CIATriadChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import {
Chart as ChartJS,
Filler,
Legend,
LineElement,
PointElement,
RadialLinearScale,
Tooltip,
} from 'chart.js';
import { t } from 'i18next';
import React from 'react';
import { Radar } from 'react-chartjs-2';

ChartJS.register(
RadialLinearScale,
PointElement,
LineElement,
Filler,
Tooltip,
Legend,
);

type CIAData = {
subject: string;
current: 0 | 1 | 2;
target: 0 | 1 | 2;
};

type Props = {
data: CIAData[];
};

export const CIATriadChart: React.FC<Props> = ({ data }) => {
if (!data.length) {
return (
<p className="text-sm text-gray-500">{t('err.noMatchingRecords')}</p>
);
}
const chartData = {
labels: data.map(d => d.subject),
datasets: [
{
label: 'Average',
data: data.map(d => d.current),
backgroundColor: 'rgba(130, 202, 157, 0.5)',
borderColor: 'rgb(130, 202, 157)',
borderWidth: 1,
},
],
};

const options = {
responsive: true,
maintainAspectRatio: false,
scales: {
r: {
angleLines: {
display: true,
color: 'rgba(255, 255, 255, 0.1)',
},
suggestedMin: 0,
suggestedMax: 2,
ticks: {
stepSize: 1,
callback: (value: number | string) => {
if (value === 0) {
return 'None';
}
if (value === 1) {
return 'Low';
}
if (value === 2) {
return 'High';
}
return value;
},
display: true,
color: 'black',
},
pointLabels: {
font: {
size: 14,
},
color: 'white',
},
grid: {
color: 'rgba(255, 255, 255, 0.1)',
},
},
},
plugins: {
legend: {
position: 'bottom' as const,
labels: {
color: 'white',
boxWidth: 20,
padding: 20,
},
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
titleColor: 'white',
bodyColor: 'white',
borderColor: 'white',
borderWidth: 1,
},
},
};

return (
<div className="h-[300px] w-full">
<Radar data={chartData} options={options} />
</div>
);
};
107 changes: 107 additions & 0 deletions frontend/src/components/charts/CVSSChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import {
BarElement,
CategoryScale,
Chart as ChartJS,
LinearScale,
Title,
Tooltip,
} from 'chart.js';
import annotationPlugin from 'chartjs-plugin-annotation';
import { t } from 'i18next';
import React from 'react';
import { Bar } from 'react-chartjs-2';

ChartJS.register(
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
annotationPlugin,
);

type CVSSData = {
name: string;
score: number;
};

type Props = {
data: CVSSData[];
};

export const CVSSChart: React.FC<Props> = ({ data }) => {
if (!data.length) {
return (
<p className="text-sm text-gray-500">{t('err.noMatchingRecords')}</p>
);
}

const averageCVSS = (
data.reduce((acc, d) => acc + d.score, 0) / data.length
).toFixed(2);

const chartData = {
labels: data.map(d => d.name),
datasets: [
{
data: data.map(d => d.score),
backgroundColor: data.map(d => {
if (d.score >= 9.0) {
return '#dc3545';
} else if (d.score >= 7.0) {
return '#fd7e14';
} else if (d.score >= 4.0) {
return '#ffc107';
} else if (d.score >= 0.1) {
return '#28a745';
} else {
return '#6c757d';
}
}),
},
],
};

const options = {
responsive: true,
maintainAspectRatio: false,
indexAxis: 'y' as const,
plugins: {
legend: {
display: false,
},
annotation: {
annotations: {
line1: {
type: 'line' as const,
xMin: parseFloat(averageCVSS),
xMax: parseFloat(averageCVSS),
borderColor: '#2ecc71',
borderWidth: 2,
borderDash: [5, 5],
},
},
},
},
scales: {
x: {
min: 0,
max: 10,
grid: {
drawOnChartArea: true,
},
},
},
};

return (
<div className="relative">
<div className="absolute top-0 left-0 w-full text-right pr-4 text-green-400 text-sm">
Average CVSS: {averageCVSS}
</div>
<div className="h-[300px] w-full">
<Bar data={chartData} options={options} />
</div>
</div>
);
};
61 changes: 61 additions & 0 deletions frontend/src/components/charts/CWECloud.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { t } from 'i18next';
import React from 'react';
import { FaBug } from 'react-icons/fa';

import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '../ui/tooltip';

type CWEItem = {
id: string;
size: number;
};

type Props = {
items: CWEItem[];
mostCommon: string;
};

export const CWECloud: React.FC<Props> = ({ items, mostCommon }) => {
items = items.filter(item => item.id !== '');
if (!items.length) {
return (
<p className="text-sm text-gray-500">{t('err.noMatchingRecords')}</p>
);
}

return (
<div className="bg-gray-900 rounded-lg p-4">
<p className="text-sm text-gray-400 mb-4">
{t('mostCommon')}: {mostCommon}
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
<TooltipProvider>
{items.map(item => (
<Tooltip key={item.id}>
<TooltipTrigger>
<div className="bg-gray-800 p-4 rounded-lg shadow-md flex items-center">
<FaBug className="text-red-500 mr-3" size={24} />
<div>
<p className="text-white text-lg font-semibold">
{item.id.split(' ')[0].replace(':', '')}
</p>
<p className="text-gray-400">
{t('occurrences')}: {item.size}
</p>
</div>
</div>
</TooltipTrigger>
<TooltipContent>
<p>{item.id}</p>
</TooltipContent>
</Tooltip>
))}
</TooltipProvider>
</div>
</div>
);
};
69 changes: 69 additions & 0 deletions frontend/src/components/charts/RemediationPriorityChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import {
BarElement,
CategoryScale,
Chart as ChartJS,
Legend,
LinearScale,
Title,
Tooltip,
} from 'chart.js';
import { t } from 'i18next';
import React from 'react';
import { Bar } from 'react-chartjs-2';

ChartJS.register(
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
);

type PriorityData = {
name: string;
count: number;
color: string;
};

type Props = {
data: PriorityData[];
};

export const RemediationPriorityChart: React.FC<Props> = ({ data }) => {
if (!data.length) {
return (
<p className="text-sm text-gray-500">{t('err.noMatchingRecords')}</p>
);
}
const chartData = {
labels: data.map(d => d.name),
datasets: [
{
data: data.map(d => d.count),
backgroundColor: data.map(d => d.color),
},
],
};

const options = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
},
scales: {
y: {
beginAtZero: true,
},
},
};

return (
<div className="h-[300px] w-full">
<Bar data={chartData} options={options} />
</div>
);
};
Loading

0 comments on commit f1cbc31

Please sign in to comment.