diff --git a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx
index 6742f9d90..2ecb2f6a8 100644
--- a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx
+++ b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx
@@ -6,6 +6,7 @@ import {
Tooltip,
CartesianGrid,
ResponsiveContainer,
+ Text,
} from "recharts";
import { Box, Stack, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
@@ -80,16 +81,37 @@ const CustomToolTip = ({ active, payload, label }) => {
return null;
};
-const MonitorDetailsAreaChart = ({ checks }) => {
+const CustomTick = ({ x, y, payload, index }) => {
+ const theme = useTheme();
+
const uiTimezone = useSelector((state) => state.ui.timezone);
- const formatDate = (timestamp) => {
- return formatDateWithTz(timestamp, "HH:mm:ss", uiTimezone);
- };
+ // Render nothing for the first tick
+ if (index === 0) return null;
+ return (
+
+ {formatDateWithTz(payload?.value, "HH:mm:ss", uiTimezone)}
+
+ );
+};
- const memoizedChecks = useMemo(() => checks, [checks[0]]);
+CustomTick.propTypes = {
+ x: PropTypes.number,
+ y: PropTypes.number,
+ payload: PropTypes.object,
+ index: PropTypes.number,
+};
+const MonitorDetailsAreaChart = ({ checks }) => {
const theme = useTheme();
+ const memoizedChecks = useMemo(() => checks, [checks[0]]);
return (
@@ -128,10 +150,12 @@ const MonitorDetailsAreaChart = ({ checks }) => {
}
+ minTickGap={0}
+ axisLine={false}
tickLine={false}
- height={18}
+ height={20}
+ interval="equidistantPreserveStart"
/>
{
- const keyToLabel = {
- performance: "Performance",
- seo: "SEO",
- bestPractices: "Best practices",
- accessibility: "Accessibility",
- };
-
- const colors = {
- performance: "#2aa02b",
- seo: "#9467bd",
- bestPractices: "#ff7f0e",
- accessibility: "#1f76b3",
- };
-
- const customize = {
- legend: { position: { vertical: "bottom", horizontal: "middle" } },
- margin: { bottom: 75 },
- };
-
- const xLabels = pageSpeedChecks.map((check) => {
- return check.createdAt;
- });
-
- return (
- ({
- dataKey: key,
- label: keyToLabel[key],
- color: colors[key],
- showMark: false,
- }))}
- yAxis={[{ min: 0, max: 100 }]}
- xAxis={[
- {
- scaleType: "point",
- data: xLabels,
- valueFormatter: (val) => new Date(val).toLocaleDateString(),
- },
- ]}
- dataset={pageSpeedChecks}
- {...customize}
- grid={{ vertical: true, horizontal: true }}
- tooltip={{ trigger: "none" }}
- slotProps={{
- legend: {
- direction: "row",
- position: { vertical: "bottom", horizontal: "middle" },
- padding: 2,
- itemMarkWidth: 8,
- itemMarkHeight: 8,
- markGap: 5,
- itemGap: 15,
- labelStyle: {
- fontSize: 13,
- color: "#344054"
- }
- },
- }}
- />
- );
-};
-
-PageSpeedLineChart.propTypes = {
- pageSpeedChecks: PropTypes.array,
-};
-
-export default PageSpeedLineChart;
diff --git a/Client/src/Pages/Incidents/IncidentTable/index.jsx b/Client/src/Pages/Incidents/IncidentTable/index.jsx
index b80fdee67..f501c6f4e 100644
--- a/Client/src/Pages/Incidents/IncidentTable/index.jsx
+++ b/Client/src/Pages/Incidents/IncidentTable/index.jsx
@@ -113,6 +113,7 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
{...item}
/>
)}
+ sx={{ mt: "auto" }}
/>
);
}
diff --git a/Client/src/Pages/Incidents/index.jsx b/Client/src/Pages/Incidents/index.jsx
index afaea43eb..8bd2928cb 100644
--- a/Client/src/Pages/Incidents/index.jsx
+++ b/Client/src/Pages/Incidents/index.jsx
@@ -58,7 +58,7 @@ const Incidents = () => {
};
return (
-
+
{loading ? (
) : (
diff --git a/Client/src/Pages/Monitors/Details/Charts/index.jsx b/Client/src/Pages/Monitors/Details/Charts/index.jsx
index b70712340..d38335800 100644
--- a/Client/src/Pages/Monitors/Details/Charts/index.jsx
+++ b/Client/src/Pages/Monitors/Details/Charts/index.jsx
@@ -42,8 +42,8 @@ const CustomLabels = ({
CustomLabels.propTypes = {
x: PropTypes.number.isRequired,
- width: PropTypes.number.isRequired,
- height: PropTypes.number.isRequired,
+ width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+ height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
firstDataPoint: PropTypes.object.isRequired,
lastDataPoint: PropTypes.object.isRequired,
type: PropTypes.string.isRequired,
diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx
index eb18e36fc..8358897d9 100644
--- a/Client/src/Pages/Monitors/Details/index.jsx
+++ b/Client/src/Pages/Monitors/Details/index.jsx
@@ -218,6 +218,11 @@ const DetailsPage = ({ isAdmin }) => {
onClick={openCertificate}
sx={{
cursor: "pointer",
+ "& svg": {
+ width: 23,
+ height: 23,
+ top: "52%",
+ },
}}
>
@@ -444,14 +449,8 @@ const DetailsPage = ({ isAdmin }) => {
data={[{ response: monitor?.periodAvgResponseTime }]}
/>
-
-
+
+
diff --git a/Client/src/Pages/Monitors/Details/styled.jsx b/Client/src/Pages/Monitors/Details/styled.jsx
index 99112802d..728df4922 100644
--- a/Client/src/Pages/Monitors/Details/styled.jsx
+++ b/Client/src/Pages/Monitors/Details/styled.jsx
@@ -76,6 +76,7 @@ export const StatBox = styled(Box)(({ theme }) => ({
borderColor: theme.palette.border.light,
borderRadius: 4,
backgroundColor: theme.palette.background.main,
+ background: `linear-gradient(340deg, ${theme.palette.background.accent} 20%, ${theme.palette.background.main} 45%)`,
"& h2": {
fontSize: 13,
fontWeight: 500,
diff --git a/Client/src/Pages/Monitors/utils.jsx b/Client/src/Pages/Monitors/utils.jsx
index 7e6722be7..c6f0a8476 100644
--- a/Client/src/Pages/Monitors/utils.jsx
+++ b/Client/src/Pages/Monitors/utils.jsx
@@ -29,22 +29,26 @@ const useUtils = () => {
const statusStyles = {
up: {
backgroundColor: theme.palette.success.bg,
+ background: `linear-gradient(340deg, ${theme.palette.success.light} -60%, ${theme.palette.success.bg} 35%)`,
borderColor: theme.palette.success.light,
"& h2": { color: theme.palette.success.main },
},
down: {
backgroundColor: theme.palette.error.bg,
+ background: `linear-gradient(340deg, ${theme.palette.error.light} -60%, ${theme.palette.error.bg} 35%)`,
borderColor: theme.palette.error.light,
"& h2": { color: theme.palette.error.main },
},
paused: {
backgroundColor: theme.palette.warning.light,
- borderColor: theme.palette.warning.border,
+ background: `linear-gradient(340deg, ${theme.palette.warning.dark} -60%, ${theme.palette.warning.light} 35%)`,
+ borderColor: theme.palette.warning.dark,
"& h2": { color: theme.palette.warning.main },
},
pending: {
backgroundColor: theme.palette.warning.light,
- borderColor: theme.palette.warning.border,
+ background: `linear-gradient(340deg, ${theme.palette.warning.dark} -60%, ${theme.palette.warning.light} 35%)`,
+ borderColor: theme.palette.warning.dark,
"& h2": { color: theme.palette.warning.main },
},
};
diff --git a/Client/src/Pages/PageSpeed/Details/Charts/AreaChart.jsx b/Client/src/Pages/PageSpeed/Details/Charts/AreaChart.jsx
new file mode 100644
index 000000000..fea80f5c4
--- /dev/null
+++ b/Client/src/Pages/PageSpeed/Details/Charts/AreaChart.jsx
@@ -0,0 +1,313 @@
+import PropTypes from "prop-types";
+import {
+ AreaChart,
+ Area,
+ XAxis,
+ Tooltip,
+ CartesianGrid,
+ ResponsiveContainer,
+ Text,
+} from "recharts";
+import { useTheme } from "@emotion/react";
+import { useMemo } from "react";
+import { Box, Stack, Typography } from "@mui/material";
+import { formatDate } from "../../../../Utils/timeUtils";
+
+const config = {
+ seo: {
+ id: "seo",
+ text: "SEO",
+ color: "unresolved",
+ },
+ performance: {
+ id: "performance",
+ text: "performance",
+ color: "success",
+ },
+ bestPractices: {
+ id: "bestPractices",
+ text: "best practices",
+ color: "warning",
+ },
+ accessibility: {
+ id: "accessibility",
+ text: "accessibility",
+ color: "primary",
+ },
+};
+
+/**
+ * Custom tooltip for the area chart.
+ * @param {Object} props
+ * @param {boolean} props.active - Whether the tooltip is active.
+ * @param {Array} props.payload - The payload data for the tooltip.
+ * @param {string} props.label - The label for the tooltip.
+ * @returns {JSX.Element|null} The tooltip element or null if not active.
+ */
+
+const CustomToolTip = ({ active, payload, label, config }) => {
+ const theme = useTheme();
+ console.log(payload);
+ if (active && payload && payload.length) {
+ return (
+
+
+ {formatDate(new Date(label))}
+
+ {Object.keys(config)
+ .reverse()
+ .map((key) => {
+ const { color } = config[key];
+ const dotColor = theme.palette[color].main;
+
+ return (
+
+
+
+ {config[key].text}
+ {" "}
+
+ {payload[0].payload[key]}
+
+
+ );
+ })}
+
+ );
+ }
+ return null;
+};
+
+CustomToolTip.propTypes = {
+ active: PropTypes.bool,
+ payload: PropTypes.array,
+ label: PropTypes.string,
+ config: PropTypes.object,
+};
+
+/**
+ * Processes data to insert gaps with null values based on the interval.
+ * @param {Array} data
+ * @param {number} interval - The interval in milliseconds for gaps.
+ * @returns {Array} The formatted data with gaps.
+ */
+const processDataWithGaps = (data, interval) => {
+ if (data.length === 0) return [];
+ let formattedData = [];
+ let last = new Date(data[0].createdAt).getTime();
+
+ // Helper function to add a null entry
+ const addNullEntry = (timestamp) => {
+ formattedData.push({
+ accessibility: "N/A",
+ bestPractices: "N/A",
+ performance: "N/A",
+ seo: "N/A",
+ createdAt: timestamp,
+ });
+ };
+
+ data.forEach((entry) => {
+ const current = new Date(entry.createdAt).getTime();
+
+ if (current - last > interval * 2) {
+ // Insert null entries for each interval
+ let temp = last + interval;
+ while (temp < current) {
+ addNullEntry(new Date(temp).toISOString());
+ temp += interval;
+ }
+ }
+
+ formattedData.push(entry);
+ last = current;
+ });
+
+ return formattedData;
+};
+
+/**
+ * Custom tick component to render ticks on the XAxis.
+ *
+ * @param {Object} props
+ * @param {number} props.x - The x coordinate for the tick.
+ * @param {number} props.y - The y coordinate for the tick.
+ * @param {Object} props.payload - The data object containing the tick value.
+ * @param {number} props.index - The index of the tick in the array of ticks.
+ *
+ * @returns {JSX.Element|null} The tick element or null if the tick should be hidden.
+ */
+const CustomTick = ({ x, y, payload, index }) => {
+ const theme = useTheme();
+
+ // Render nothing for the first tick
+ if (index === 0) return null;
+
+ return (
+
+ {formatDate(new Date(payload.value), {
+ year: undefined,
+ month: undefined,
+ day: undefined,
+ })}
+
+ );
+};
+CustomTick.propTypes = {
+ x: PropTypes.number,
+ y: PropTypes.number,
+ payload: PropTypes.shape({
+ value: PropTypes.string.isRequired,
+ }),
+ index: PropTypes.number,
+};
+
+/**
+ * A chart displaying pagespeed details over time.
+ * @param {Object} props
+ * @param {Array} props.data - The data to display in the chart.
+ * @param {number} props.interval - The interval in milliseconds for processing gaps.
+ * @returns {JSX.Element} The area chart component.
+ */
+
+const PagespeedDetailsAreaChart = ({ data, interval, metrics }) => {
+ const theme = useTheme();
+ const memoizedData = useMemo(
+ () => processDataWithGaps(data, interval),
+ [data[0]]
+ );
+
+ const filteredConfig = Object.keys(config).reduce((result, key) => {
+ if (metrics[key]) {
+ result[key] = config[key];
+ }
+ return result;
+ }, {});
+
+ return (
+
+
+
+ }
+ axisLine={false}
+ tickLine={false}
+ height={18}
+ minTickGap={0}
+ interval="equidistantPreserveStart"
+ />
+ }
+ />
+
+ {Object.values(filteredConfig).map(({ id, color }) => {
+ const startColor = theme.palette[color].main;
+ const endColor = theme.palette[color].light;
+
+ return (
+
+
+
+
+ );
+ })}
+
+ {Object.keys(filteredConfig).map((key) => {
+ const { color } = filteredConfig[key];
+ const strokeColor = theme.palette[color].main;
+ const bgColor = theme.palette.background.main;
+
+ return (
+
+ );
+ })}
+
+
+ );
+};
+
+PagespeedDetailsAreaChart.propTypes = {
+ data: PropTypes.arrayOf(
+ PropTypes.shape({
+ createdAt: PropTypes.string.isRequired,
+ accessibility: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
+ .isRequired,
+ bestPractices: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
+ .isRequired,
+ performance: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
+ .isRequired,
+ seo: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
+ })
+ ).isRequired,
+ interval: PropTypes.number.isRequired,
+ metrics: PropTypes.object,
+};
+
+export default PagespeedDetailsAreaChart;
diff --git a/Client/src/Pages/PageSpeed/Details/Charts/PieChart.jsx b/Client/src/Pages/PageSpeed/Details/Charts/PieChart.jsx
new file mode 100644
index 000000000..37556f178
--- /dev/null
+++ b/Client/src/Pages/PageSpeed/Details/Charts/PieChart.jsx
@@ -0,0 +1,312 @@
+import PropTypes from "prop-types";
+import { PieChart as MuiPieChart } from "@mui/x-charts/PieChart";
+import { useDrawingArea } from "@mui/x-charts";
+import { useState } from "react";
+import { useTheme } from "@emotion/react";
+import { Box } from "@mui/material";
+
+/**
+ * Renders a centered label within a pie chart.
+ *
+ * @param {Object} props
+ * @param {string | number} props.value - The value to display in the label.
+ * @param {string} props.color - The color of the text.
+ * @returns {JSX.Element}
+ */
+const PieCenterLabel = ({ value, color, setExpand }) => {
+ const { width, height } = useDrawingArea();
+ return (
+ setExpand(true)}
+ >
+
+
+ {value}
+
+
+ );
+};
+
+PieCenterLabel.propTypes = {
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ color: PropTypes.string,
+ setExpand: PropTypes.func,
+};
+
+/**
+ * A component that renders a label on a pie chart slice.
+ * The label is positioned relative to the center of the pie chart and is optionally highlighted.
+ *
+ * @param {Object} props
+ * @param {number} props.value - The value to display inside the pie slice.
+ * @param {number} props.startAngle - The starting angle of the pie slice in degrees.
+ * @param {number} props.endAngle - The ending angle of the pie slice in degrees.
+ * @param {string} props.color - The color of the label text when highlighted.
+ * @param {boolean} props.highlighted - Determines if the label should be highlighted or not.
+ * @returns {JSX.Element}
+ */
+const PieValueLabel = ({ value, startAngle, endAngle, color, highlighted }) => {
+ const { width, height } = useDrawingArea();
+
+ // Compute the midpoint angle in radians
+ const angle = (((startAngle + endAngle) / 2) * Math.PI) / 180;
+ const radius = height / 4; // length from center of the circle to where the text is positioned
+
+ // Calculate x and y positions
+ const x = Math.sin(angle) * radius;
+ const y = -Math.cos(angle) * radius;
+
+ return (
+
+
+ +{value}
+
+
+ );
+};
+
+// Validate props using PropTypes
+PieValueLabel.propTypes = {
+ value: PropTypes.number.isRequired,
+ startAngle: PropTypes.number.isRequired,
+ endAngle: PropTypes.number.isRequired,
+ color: PropTypes.string.isRequired,
+ highlighted: PropTypes.bool.isRequired,
+};
+
+const PieChart = ({ audits }) => {
+ const theme = useTheme();
+
+ /**
+ * Weight constants for different performance metrics.
+ * @type {Object}
+ */
+ const weights = {
+ fcp: 10,
+ si: 10,
+ lcp: 25,
+ tbt: 30,
+ cls: 25,
+ };
+
+ /**
+ * Retrieves color properties based on the performance value.
+ *
+ * @param {number} value - The performance score used to determine the color properties.
+ * @returns {{stroke: string, strokeBg: string, text: string, bg: string}} The color properties for the given performance value.
+ */
+ const getColors = (value) => {
+ if (value >= 90 && value <= 100)
+ return {
+ stroke: theme.palette.success.main,
+ strokeBg: theme.palette.success.light,
+ text: theme.palette.success.text,
+ bg: theme.palette.success.bg,
+ };
+ else if (value >= 50 && value < 90)
+ return {
+ stroke: theme.palette.warning.main,
+ strokeBg: theme.palette.warning.light,
+ text: theme.palette.warning.text,
+ bg: theme.palette.warning.bg,
+ };
+ else if (value >= 0 && value < 50)
+ return {
+ stroke: theme.palette.error.text,
+ strokeBg: theme.palette.error.light,
+ text: theme.palette.error.text,
+ bg: theme.palette.error.bg,
+ };
+ return {
+ stroke: theme.palette.unresolved.main,
+ strokeBg: theme.palette.unresolved.light,
+ text: theme.palette.unresolved.main,
+ bg: theme.palette.unresolved.bg,
+ };
+ };
+
+ /**
+ * Calculates and formats the data needed for rendering a pie chart based on audit scores and weights.
+ * This function generates properties for each pie slice, including angles, radii, and colors.
+ * It also calculates performance based on the weighted values.
+ *
+ * @returns {Array
>
)}
diff --git a/Client/src/Pages/PageSpeed/Details/skeleton.jsx b/Client/src/Pages/PageSpeed/Details/skeleton.jsx
index 24604e8a3..5f64b1e0d 100644
--- a/Client/src/Pages/PageSpeed/Details/skeleton.jsx
+++ b/Client/src/Pages/PageSpeed/Details/skeleton.jsx
@@ -1,5 +1,4 @@
import { Box, Skeleton, Stack } from "@mui/material";
-import { useTheme } from "@emotion/react";
/**
* Renders a skeleton layout.
@@ -7,21 +6,14 @@ import { useTheme } from "@emotion/react";
* @returns {JSX.Element}
*/
const SkeletonLayout = () => {
- const theme = useTheme();
-
return (
<>
-
+
-
+
{
diff --git a/Client/src/Pages/PageSpeed/Details/styled.jsx b/Client/src/Pages/PageSpeed/Details/styled.jsx
new file mode 100644
index 000000000..d4feb7c8d
--- /dev/null
+++ b/Client/src/Pages/PageSpeed/Details/styled.jsx
@@ -0,0 +1,90 @@
+import { Box, Stack, styled } from "@mui/material";
+
+export const ChartBox = styled(Stack)(({ theme }) => ({
+ display: "grid",
+ height: 300,
+ minWidth: 250,
+ border: 1,
+ borderStyle: "solid",
+ borderColor: theme.palette.border.light,
+ borderRadius: 4,
+ borderTopRightRadius: 16,
+ borderBottomRightRadius: 16,
+ backgroundColor: theme.palette.background.main,
+ "& h2": {
+ color: theme.palette.text.secondary,
+ fontSize: 15,
+ fontWeight: 500,
+ },
+ "& p": { color: theme.palette.text.tertiary },
+ "& > :nth-of-type(1)": {
+ gridColumn: 1,
+ gridRow: 1,
+ height: "fit-content",
+ paddingTop: theme.spacing(8),
+ paddingLeft: theme.spacing(8),
+ },
+ "& > :nth-of-type(2)": { gridColumn: 1, gridRow: 2 },
+ "& > :nth-of-type(3)": {
+ gridColumn: 2,
+ gridRow: "span 2",
+ padding: theme.spacing(8),
+ borderLeft: 1,
+ borderLeftStyle: "solid",
+ borderLeftColor: theme.palette.border.light,
+ borderRadius: 16,
+ backgroundColor: theme.palette.background.main,
+ background: `linear-gradient(325deg, ${theme.palette.background.accent} 20%, ${theme.palette.background.main} 45%)`,
+ },
+}));
+
+export const IconBox = styled(Box)(({ theme }) => ({
+ height: 34,
+ minWidth: 34,
+ width: 34,
+ position: "relative",
+ border: 1,
+ borderStyle: "solid",
+ borderColor: theme.palette.border.dark,
+ borderRadius: 4,
+ backgroundColor: theme.palette.background.accent,
+ "& svg": {
+ position: "absolute",
+ top: "50%",
+ left: "50%",
+ transform: "translate(-50%, -50%)",
+ width: 20,
+ height: 20,
+ "& path": {
+ stroke: theme.palette.text.tertiary,
+ },
+ },
+}));
+
+export const StatBox = styled(Box)(({ theme }) => ({
+ padding: `${theme.spacing(4)} ${theme.spacing(8)}`,
+ minWidth: 200,
+ width: 225,
+ border: 1,
+ borderStyle: "solid",
+ borderColor: theme.palette.border.light,
+ borderRadius: 4,
+ backgroundColor: theme.palette.background.main,
+ background: `linear-gradient(340deg, ${theme.palette.background.accent} 20%, ${theme.palette.background.main} 45%)`,
+ "& h2": {
+ fontSize: 13,
+ fontWeight: 500,
+ color: theme.palette.text.secondary,
+ textTransform: "uppercase",
+ },
+ "& p": {
+ fontSize: 18,
+ color: theme.palette.text.primary,
+ marginTop: theme.spacing(2),
+ "& span": {
+ color: theme.palette.text.tertiary,
+ marginLeft: theme.spacing(2),
+ fontSize: 15,
+ },
+ },
+}));
diff --git a/Client/src/Utils/Theme/darkTheme.js b/Client/src/Utils/Theme/darkTheme.js
index 261f8135f..2d1f80635 100644
--- a/Client/src/Utils/Theme/darkTheme.js
+++ b/Client/src/Utils/Theme/darkTheme.js
@@ -63,7 +63,7 @@ const darkTheme = createTheme({
uptimeGood: "#ffd600",
uptimeExcellent: "#079455",
},
- unresolved: { main: "#4e5ba6", light: "#e2eaf7", bg: "#f2f4f7" },
+ unresolved: { main: "#664eff", light: "#3a1bff", bg: "#f2f4f7" },
divider: border.light,
other: {
icon: "#e6e6e6",
diff --git a/Client/src/Utils/Theme/lightTheme.js b/Client/src/Utils/Theme/lightTheme.js
index f00ece091..92e7f7817 100644
--- a/Client/src/Utils/Theme/lightTheme.js
+++ b/Client/src/Utils/Theme/lightTheme.js
@@ -51,7 +51,7 @@ const lightTheme = createTheme({
text: "#DC6803",
main: "#fdb022",
light: "#ffecbc",
- bg: "#fffcf5",
+ bg: "#fef8ea",
border: "#fec84b",
},
percentage: {
diff --git a/Client/src/assets/icons/certificate.svg b/Client/src/assets/icons/certificate.svg
index a985bef9f..2bc6c7e74 100644
--- a/Client/src/assets/icons/certificate.svg
+++ b/Client/src/assets/icons/certificate.svg
@@ -1,3 +1,3 @@
+
+
\ No newline at end of file
diff --git a/Client/src/assets/icons/monitor-graph-line.svg b/Client/src/assets/icons/monitor-graph-line.svg
new file mode 100644
index 000000000..86049eb9e
--- /dev/null
+++ b/Client/src/assets/icons/monitor-graph-line.svg
@@ -0,0 +1,3 @@
+
diff --git a/Client/src/assets/icons/performance-report.svg b/Client/src/assets/icons/performance-report.svg
new file mode 100644
index 000000000..8b7073ac3
--- /dev/null
+++ b/Client/src/assets/icons/performance-report.svg
@@ -0,0 +1,3 @@
+
diff --git a/Client/src/assets/icons/ruler-icon.svg b/Client/src/assets/icons/ruler-icon.svg
new file mode 100644
index 000000000..7266d2876
--- /dev/null
+++ b/Client/src/assets/icons/ruler-icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/Client/src/assets/icons/spedometer-icon.svg b/Client/src/assets/icons/spedometer-icon.svg
new file mode 100644
index 000000000..19b13f44e
--- /dev/null
+++ b/Client/src/assets/icons/spedometer-icon.svg
@@ -0,0 +1,3 @@
+