diff --git a/Client/src/App.jsx b/Client/src/App.jsx
index 151512701..772c1aa45 100644
--- a/Client/src/App.jsx
+++ b/Client/src/App.jsx
@@ -39,6 +39,7 @@ import { getAppSettings } from "./Features/Settings/settingsSlice";
import { logger } from "./Utils/Logger"; // Import the logger
import { networkService } from "./main";
import { Infrastructure } from "./Pages/Infrastructure";
+import InfrastructureDetails from "./Pages/Infrastructure/Details";
function App() {
const AdminCheckedRegister = withAdminCheck(Register);
const MonitorsWithAdminProp = withAdminProp(Monitors);
@@ -48,6 +49,7 @@ function App() {
const MaintenanceWithAdminProp = withAdminProp(Maintenance);
const SettingsWithAdminProp = withAdminProp(Settings);
const AdvancedSettingsWithAdminProp = withAdminProp(AdvancedSettings);
+ const InfrastructureDetailsWithAdminProp = withAdminProp(InfrastructureDetails);
const mode = useSelector((state) => state.ui.mode);
const { authToken } = useSelector((state) => state.auth);
const dispatch = useDispatch();
@@ -128,6 +130,11 @@ function App() {
element={}
/>
+ }
+ />
+
}
diff --git a/Client/src/Components/Charts/AreaChart/index.jsx b/Client/src/Components/Charts/AreaChart/index.jsx
new file mode 100644
index 000000000..d05dbb14a
--- /dev/null
+++ b/Client/src/Components/Charts/AreaChart/index.jsx
@@ -0,0 +1,157 @@
+import {
+ AreaChart,
+ Area,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ ResponsiveContainer,
+} from "recharts";
+import { createGradient } from "../Utils/gradientUtils";
+import PropTypes from "prop-types";
+import { useTheme } from "@mui/material";
+import { useId } from "react";
+/**
+ * CustomAreaChart component for rendering an area chart with optional gradient and custom ticks.
+ *
+ * @param {Object} props - The properties object.
+ * @param {Array} props.data - The data array for the chart.
+ * @param {string} props.xKey - The key for the x-axis data.
+ * @param {string} props.yKey - The key for the y-axis data.
+ * @param {Object} [props.xTick] - Custom tick component for the x-axis.
+ * @param {Object} [props.yTick] - Custom tick component for the y-axis.
+ * @param {string} [props.strokeColor] - The stroke color for the area.
+ * @param {string} [props.fillColor] - The fill color for the area.
+ * @param {boolean} [props.gradient=false] - Whether to apply a gradient fill.
+ * @param {string} [props.gradientDirection="vertical"] - The direction of the gradient.
+ * @param {string} [props.gradientStartColor] - The start color of the gradient.
+ * @param {string} [props.gradientEndColor] - The end color of the gradient.
+ * @param {Object} [props.customTooltip] - Custom tooltip component.
+ * @returns {JSX.Element} The rendered area chart component.
+ *
+ * @example
+ * // Example usage of CustomAreaChart
+ * import React from 'react';
+ * import CustomAreaChart from './CustomAreaChart';
+ * import { TzTick, PercentTick, InfrastructureTooltip } from './chartUtils';
+ *
+ * const data = [
+ * { createdAt: '2023-01-01T00:00:00Z', cpu: { usage_percent: 0.5 } },
+ * { createdAt: '2023-01-01T01:00:00Z', cpu: { usage_percent: 0.6 } },
+ * // more data points...
+ * ];
+ *
+ * const MyChartComponent = () => {
+ * return (
+ * }
+ * yTick={}
+ * strokeColor="#8884d8"
+ * fillColor="#8884d8"
+ * gradient={true}
+ * gradientStartColor="#8884d8"
+ * gradientEndColor="#82ca9d"
+ * customTooltip={({ active, payload, label }) => (
+ *
+ * )}
+ * />
+ * );
+ * };
+ *
+ * export default MyChartComponent;
+ */
+const CustomAreaChart = ({
+ data,
+ dataKey,
+ xKey,
+ yKey,
+ xTick,
+ yTick,
+ strokeColor,
+ fillColor,
+ gradient = false,
+ gradientDirection = "vertical",
+ gradientStartColor,
+ gradientEndColor,
+ customTooltip,
+ height = "100%",
+}) => {
+ const theme = useTheme();
+ const uniqueId = useId();
+ const gradientId = `gradient-${uniqueId}`;
+ return (
+
+
+
+
+ {gradient === true &&
+ createGradient({
+ id: gradientId,
+ startColor: gradientStartColor,
+ endColor: gradientEndColor,
+ direction: gradientDirection,
+ })}
+
+
+ {customTooltip ? (
+
+ ) : (
+
+ )}
+
+
+ );
+};
+
+CustomAreaChart.propTypes = {
+ data: PropTypes.array.isRequired,
+ dataKey: PropTypes.string.isRequired,
+ xTick: PropTypes.object, // Recharts takes an instance of component, so we can't pass the component itself
+ yTick: PropTypes.object, // Recharts takes an instance of component, so we can't pass the component itself
+ xKey: PropTypes.string.isRequired,
+ yKey: PropTypes.string.isRequired,
+ fillColor: PropTypes.string,
+ strokeColor: PropTypes.string,
+ gradient: PropTypes.bool,
+ gradientDirection: PropTypes.string,
+ gradientStartColor: PropTypes.string,
+ gradientEndColor: PropTypes.string,
+ customTooltip: PropTypes.func,
+ height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+};
+
+export default CustomAreaChart;
diff --git a/Client/src/Components/Charts/CustomGauge/index.css b/Client/src/Components/Charts/CustomGauge/index.css
new file mode 100644
index 000000000..1b64a6b9a
--- /dev/null
+++ b/Client/src/Components/Charts/CustomGauge/index.css
@@ -0,0 +1,14 @@
+.radial-chart {
+ position: relative;
+ display: inline-block;
+}
+
+.radial-chart-base {
+ opacity: 0.3;
+}
+
+.radial-chart-progress {
+ transform: rotate(-90deg);
+ transform-origin: center;
+ transition: stroke-dashoffset 1.5s ease-in-out;
+}
diff --git a/Client/src/Components/Charts/CustomGauge/index.jsx b/Client/src/Components/Charts/CustomGauge/index.jsx
new file mode 100644
index 000000000..f3ad15b0b
--- /dev/null
+++ b/Client/src/Components/Charts/CustomGauge/index.jsx
@@ -0,0 +1,113 @@
+import { useTheme } from "@emotion/react";
+import { useEffect, useState, useMemo } from "react";
+import { Box, Typography } from "@mui/material";
+import PropTypes from "prop-types";
+import "./index.css";
+
+/**
+ * A Performant SVG based circular gauge
+ *
+ * @component
+ * @param {Object} props - Component properties
+ * @param {number} [props.progress=0] - Progress percentage (0-100)
+ * @param {number} [props.radius=60] - Radius of the gauge circle
+ * @param {string} [props.color="#000000"] - Color of the progress stroke
+ * @param {number} [props.strokeWidth=15] - Width of the gauge stroke
+ *
+ * @example
+ *
+ *
+ * @returns {React.ReactElement} Rendered CustomGauge component
+ */
+const CustomGauge = ({
+ progress = 0,
+ radius = 60,
+ color = "#000000",
+ strokeWidth = 15,
+}) => {
+ // Calculate the length of the stroke for the circle
+ const { circumference, totalSize, strokeLength } = useMemo(
+ () => ({
+ circumference: 2 * Math.PI * radius,
+ totalSize: radius * 2 + strokeWidth * 2,
+ strokeLength: (progress / 100) * (2 * Math.PI * radius),
+ }),
+ [radius, strokeWidth, progress]
+ );
+ const [offset, setOffset] = useState(circumference);
+ const theme = useTheme();
+
+ // Handle initial animation
+ useEffect(() => {
+ setOffset(circumference);
+ const timer = setTimeout(() => {
+ setOffset(circumference - strokeLength);
+ }, 100);
+
+ return () => clearTimeout(timer);
+ }, [progress, circumference, strokeLength]);
+
+ return (
+
+
+
+
+ {`${progress.toFixed(2)}%`}
+
+
+ );
+};
+
+export default CustomGauge;
+
+CustomGauge.propTypes = {
+ progress: PropTypes.number,
+ radius: PropTypes.number,
+ color: PropTypes.string,
+ strokeWidth: PropTypes.number,
+};
diff --git a/Client/src/Components/Charts/Utils/chartUtils.jsx b/Client/src/Components/Charts/Utils/chartUtils.jsx
new file mode 100644
index 000000000..043203525
--- /dev/null
+++ b/Client/src/Components/Charts/Utils/chartUtils.jsx
@@ -0,0 +1,190 @@
+import PropTypes from "prop-types";
+import { useSelector } from "react-redux";
+import { useTheme } from "@mui/material";
+import { Text } from "recharts";
+import { formatDateWithTz } from "../../../Utils/timeUtils";
+import { Box, Stack, Typography } from "@mui/material";
+
+/**
+ * Custom tick component for rendering time with timezone.
+ *
+ * @param {Object} props - The properties object.
+ * @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 payload object containing tick data.
+ * @param {number} props.index - The index of the tick.
+ * @returns {JSX.Element} The rendered tick component.
+ */
+export const TzTick = ({ x, y, payload, index }) => {
+ const theme = useTheme();
+
+ const uiTimezone = useSelector((state) => state.ui.timezone);
+ return (
+
+ {formatDateWithTz(payload?.value, "h:mm a", uiTimezone)}
+
+ );
+};
+
+TzTick.propTypes = {
+ x: PropTypes.number,
+ y: PropTypes.number,
+ payload: PropTypes.object,
+ index: PropTypes.number,
+};
+
+/**
+ * Custom tick component for rendering percentage values.
+ *
+ * @param {Object} props - The properties object.
+ * @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 payload object containing tick data.
+ * @param {number} props.index - The index of the tick.
+ * @returns {JSX.Element|null} The rendered tick component or null for the first tick.
+ */
+export const PercentTick = ({ x, y, payload, index }) => {
+ const theme = useTheme();
+ if (index === 0) return null;
+ return (
+
+ {`${payload?.value * 100}%`}
+
+ );
+};
+
+PercentTick.propTypes = {
+ x: PropTypes.number,
+ y: PropTypes.number,
+ payload: PropTypes.object,
+ index: PropTypes.number,
+};
+
+/**
+ * Converts a decimal value to a formatted percentage string.
+ *
+ * @param {number} value - The decimal value to convert (e.g., 0.75)
+ * @returns {string} Formatted percentage string (e.g., "75.00%") or original input if not a number
+ *
+ * @example
+ * getFormattedPercentage(0.7543) // Returns "75.43%"
+ * getFormattedPercentage(1) // Returns "100.00%"
+ * getFormattedPercentage("test") // Returns "test"
+ */
+const getFormattedPercentage = (value) => {
+ if (typeof value !== "number") return value;
+ return `${(value * 100).toFixed(2)}.%`;
+};
+
+/**
+ * Custom tooltip component for displaying infrastructure data.
+ *
+ * @param {Object} props - The properties object.
+ * @param {boolean} props.active - Indicates if the tooltip is active.
+ * @param {Array} props.payload - The payload array containing tooltip data.
+ * @param {string} props.label - The label for the tooltip.
+ * @param {string} props.yKey - The key for the y-axis data.
+ * @param {string} props.yLabel - The label for the y-axis data.
+ * @param {string} props.dotColor - The color of the dot in the tooltip.
+ * @returns {JSX.Element|null} The rendered tooltip component or null if inactive.
+ */
+export const InfrastructureTooltip = ({
+ active,
+ payload,
+ label,
+ yKey,
+ yIdx = -1,
+ yLabel,
+ dotColor,
+}) => {
+ const uiTimezone = useSelector((state) => state.ui.timezone);
+ const theme = useTheme();
+ if (active && payload && payload.length) {
+ const [hardwareType, metric] = yKey.split(".");
+ return (
+
+
+ {formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)}
+
+
+
+
+
+ {yIdx >= 0
+ ? `${yLabel} ${getFormattedPercentage(payload[0].payload[hardwareType][yIdx][metric])}`
+ : `${yLabel} ${getFormattedPercentage(payload[0].payload[hardwareType][metric])}`}
+
+
+
+
+ {/* Display original value */}
+
+ );
+ }
+ return null;
+};
+
+InfrastructureTooltip.propTypes = {
+ active: PropTypes.bool,
+ payload: PropTypes.array,
+ label: PropTypes.oneOfType([
+ PropTypes.instanceOf(Date),
+ PropTypes.string,
+ PropTypes.number,
+ ]),
+ yKey: PropTypes.string,
+ yIdx: PropTypes.number,
+ yLabel: PropTypes.string,
+ dotColor: PropTypes.string,
+};
diff --git a/Client/src/Components/Charts/Utils/gradientUtils.jsx b/Client/src/Components/Charts/Utils/gradientUtils.jsx
new file mode 100644
index 000000000..b5920374d
--- /dev/null
+++ b/Client/src/Components/Charts/Utils/gradientUtils.jsx
@@ -0,0 +1,47 @@
+/**
+ * Creates an SVG gradient definition for use in charts
+ * @param {Object} params - The gradient parameters
+ * @param {string} [params.id="colorUv"] - Unique identifier for the gradient
+ * @param {string} params.startColor - Starting color of the gradient (hex, rgb, or color name)
+ * @param {string} params.endColor - Ending color of the gradient (hex, rgb, or color name)
+ * @param {number} [params.startOpacity=0.8] - Starting opacity (0-1)
+ * @param {number} [params.endOpacity=0] - Ending opacity (0-1)
+ * @param {('vertical'|'horizontal')} [params.direction="vertical"] - Direction of the gradient
+ * @returns {JSX.Element} SVG gradient definition element
+ * @example
+ * createCustomGradient({
+ * startColor: "#1976D2",
+ * endColor: "#42A5F5",
+ * direction: "horizontal"
+ * })
+ */
+
+export const createGradient = ({
+ id,
+ startColor,
+ endColor,
+ startOpacity = 0.8,
+ endOpacity = 0,
+ direction = "vertical", // or "horizontal"
+}) => (
+
+
+
+
+
+
+);
diff --git a/Client/src/Pages/Infrastructure/Details/index.jsx b/Client/src/Pages/Infrastructure/Details/index.jsx
new file mode 100644
index 000000000..4fb547a5a
--- /dev/null
+++ b/Client/src/Pages/Infrastructure/Details/index.jsx
@@ -0,0 +1,394 @@
+import { useParams } from "react-router-dom";
+import { useEffect, useState } from "react";
+import Breadcrumbs from "../../../Components/Breadcrumbs";
+import { Stack, Box, Typography } from "@mui/material";
+import { useTheme } from "@emotion/react";
+import CustomGauge from "../../../Components/Charts/CustomGauge";
+import AreaChart from "../../../Components/Charts/AreaChart";
+import PulseDot from "../../../Components/Animated/PulseDot";
+import useUtils from "../../Monitors/utils";
+import { formatDurationRounded, formatDurationSplit } from "../../../Utils/timeUtils";
+import axios from "axios";
+import {
+ TzTick,
+ PercentTick,
+ InfrastructureTooltip,
+} from "../../../Components/Charts/Utils/chartUtils";
+import PropTypes from "prop-types";
+
+const BASE_BOX_PADDING_VERTICAL = 4;
+const BASE_BOX_PADDING_HORIZONTAL = 8;
+const TYPOGRAPHY_PADDING = 8;
+/**
+ * Converts bytes to gigabytes
+ * @param {number} bytes - Number of bytes to convert
+ * @returns {number} Converted value in gigabytes
+ */
+const formatBytes = (bytes) => {
+ if (typeof bytes !== "number") return "0 GB";
+ if (bytes === 0) return "0 GB";
+
+ const GB = bytes / (1024 * 1024 * 1024);
+ const MB = bytes / (1024 * 1024);
+
+ if (GB >= 1) {
+ return `${Number(GB.toFixed(0))} GB`;
+ } else {
+ return `${Number(MB.toFixed(0))} MB`;
+ }
+};
+
+/**
+ * Renders a base box with consistent styling
+ * @param {Object} props - Component properties
+ * @param {React.ReactNode} props.children - Child components to render inside the box
+ * @param {Object} props.sx - Additional styling for the box
+ * @returns {React.ReactElement} Styled box component
+ */
+const BaseBox = ({ children, sx = {} }) => {
+ const theme = useTheme();
+ return (
+
+ {children}
+
+ );
+};
+
+BaseBox.propTypes = {
+ children: PropTypes.node.isRequired,
+ sx: PropTypes.object,
+};
+
+/**
+ * Renders a statistic box with a heading and subheading
+ * @param {Object} props - Component properties
+ * @param {string} props.heading - Primary heading text
+ * @param {string} props.subHeading - Secondary heading text
+ * @returns {React.ReactElement} Stat box component
+ */
+const StatBox = ({ heading, subHeading }) => {
+ return (
+
+ {heading}
+ {subHeading}
+
+ );
+};
+
+StatBox.propTypes = {
+ heading: PropTypes.string.isRequired,
+ subHeading: PropTypes.string.isRequired,
+};
+
+/**
+ * Renders a gauge box with usage visualization
+ * @param {Object} props - Component properties
+ * @param {number} props.value - Percentage value for gauge
+ * @param {string} props.heading - Box heading
+ * @param {string} props.metricOne - First metric label
+ * @param {string} props.valueOne - First metric value
+ * @param {string} props.metricTwo - Second metric label
+ * @param {string} props.valueTwo - Second metric value
+ * @returns {React.ReactElement} Gauge box component
+ */
+const GaugeBox = ({ value, heading, metricOne, valueOne, metricTwo, valueTwo }) => {
+ const theme = useTheme();
+ return (
+
+
+
+ {heading}
+
+
+ {metricOne}
+ {valueOne}
+
+
+ {metricTwo}
+ {valueTwo}
+
+
+
+
+ );
+};
+
+GaugeBox.propTypes = {
+ value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ heading: PropTypes.string.isRequired,
+ metricOne: PropTypes.string.isRequired,
+ valueOne: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
+ metricTwo: PropTypes.string.isRequired,
+ valueTwo: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
+};
+
+/**
+ * Renders the infrastructure details page
+ * @returns {React.ReactElement} Infrastructure details page component
+ */
+const InfrastructureDetails = () => {
+ const theme = useTheme();
+ const { monitorId } = useParams();
+ const navList = [
+ { name: "infrastructure monitors", path: "/infrastructure" },
+ { name: "details", path: `/infrastructure/${monitorId}` },
+ ];
+ const [monitor, setMonitor] = useState(null);
+ const { statusColor, determineState } = useUtils();
+ // These calculations are needed because ResponsiveContainer
+ // doesn't take padding of parent/siblings into account
+ // when calculating height.
+ const chartContainerHeight = 300;
+ const totalChartContainerPadding =
+ parseInt(theme.spacing(BASE_BOX_PADDING_VERTICAL), 10) * 2;
+ const totalTypographyPadding = parseInt(theme.spacing(TYPOGRAPHY_PADDING), 10) * 2;
+ const areaChartHeight =
+ (chartContainerHeight - totalChartContainerPadding - totalTypographyPadding) * 0.95;
+ // end height calculations
+
+ // Fetch data
+ useEffect(() => {
+ const fetchData = async () => {
+ try {
+ const response = await axios.get("http://localhost:5000/api/v1/dummy-data", {
+ headers: {
+ "Content-Type": "application/json",
+ "Cache-Control": "no-cache",
+ },
+ });
+ setMonitor(response.data.data);
+ } catch (error) {
+ console.error(error);
+ }
+ };
+ fetchData();
+ }, []);
+
+ const statBoxConfigs = [
+ {
+ id: 0,
+ heading: "CPU",
+ subHeading: `${monitor?.checks[0]?.cpu?.physical_core} cores`,
+ },
+ {
+ id: 1,
+ heading: "Memory",
+ subHeading: formatBytes(monitor?.checks[0]?.memory?.total_bytes),
+ },
+ {
+ id: 2,
+ heading: "Disk",
+ subHeading: formatBytes(monitor?.checks[0]?.disk[0]?.total_bytes),
+ },
+ { id: 3, heading: "Uptime", subHeading: "100%" },
+ {
+ id: 4,
+ heading: "Status",
+ subHeading: monitor?.status === true ? "Active" : "Inactive",
+ },
+ ];
+
+ const gaugeBoxConfigs = [
+ {
+ type: "memory",
+ value: monitor?.checks[0]?.memory?.usage_percent * 100,
+ heading: "Memory Usage",
+ metricOne: "Used",
+ valueOne: formatBytes(monitor?.checks[0]?.memory?.used_bytes),
+ metricTwo: "Total",
+ valueTwo: formatBytes(monitor?.checks[0]?.memory?.total_bytes),
+ },
+ {
+ type: "cpu",
+ value: monitor?.checks[0]?.cpu?.usage_percent * 100,
+ heading: "CPU Usage",
+ metricOne: "Cores",
+ valueOne: monitor?.checks[0]?.cpu?.physical_core,
+ metricTwo: "Frequency",
+ valueTwo: `${(monitor?.checks[0]?.cpu?.frequency / 1000).toFixed(2)} Ghz`,
+ },
+ ...(monitor?.checks[0]?.disk ?? []).map((disk, idx) => ({
+ type: "disk",
+ diskIndex: idx,
+ value: disk.usage_percent * 100,
+ heading: `Disk${idx} usage`,
+ metricOne: "Used",
+ valueOne: formatBytes(disk.total_bytes - disk.free_bytes),
+ metricTwo: "Total",
+ valueTwo: formatBytes(disk.total_bytes),
+ })),
+ ];
+
+ const areaChartConfigs = [
+ {
+ type: "memory",
+ dataKey: "memory.usage_percent",
+ heading: "Memory usage",
+ strokeColor: theme.palette.primary.main,
+ yLabel: "Memory Usage",
+ },
+ {
+ type: "cpu",
+ dataKey: "cpu.usage_percent",
+ heading: "CPU usage",
+ strokeColor: theme.palette.success.main,
+ yLabel: "CPU Usage",
+ },
+ ...(monitor?.checks?.[0]?.disk?.map((disk, idx) => ({
+ type: "disk",
+ diskIndex: idx,
+ dataKey: `disk[${idx}].usage_percent`,
+ heading: `Disk${idx} usage`,
+ strokeColor: theme.palette.warning.main,
+ yLabel: "Disk Usage",
+ })) || []),
+ ];
+
+ return (
+ monitor && (
+
+
+
+
+
+
+
+
+ {monitor.name}
+
+ {monitor.url || "..."}
+
+
+ Checking every {formatDurationRounded(monitor?.interval)}
+
+
+ Last checked {formatDurationSplit(monitor?.lastChecked).time}{" "}
+ {formatDurationSplit(monitor?.lastChecked).format} ago
+
+
+
+ {statBoxConfigs.map((statBox) => (
+
+ ))}
+
+
+ {gaugeBoxConfigs.map((config) => (
+
+ ))}
+
+ *": {
+ flexBasis: `calc(50% - ${theme.spacing(8)})`,
+ maxWidth: `calc(50% - ${theme.spacing(8)})`,
+ },
+ }}
+ >
+ {areaChartConfigs.map((config) => (
+
+
+ {config.heading}
+
+ (
+
+ )}
+ xTick={}
+ yTick={}
+ strokeColor={config.strokeColor}
+ gradient={true}
+ gradientStartColor={config.strokeColor}
+ gradientEndColor="#ffffff"
+ />
+
+ ))}
+
+
+
+ )
+ );
+};
+
+export default InfrastructureDetails;
diff --git a/Server/index.js b/Server/index.js
index 0e9811a43..996c4fd07 100644
--- a/Server/index.js
+++ b/Server/index.js
@@ -92,6 +92,23 @@ const startApp = async () => {
app.use("/api/v1/queue", verifyJWT, queueRouter);
app.use("/api/v1/status-page", statusPageRouter);
+ app.use("/api/v1/dummy-data", async (req, res) => {
+ try {
+ const response = await axios.get(
+ "https://gist.githubusercontent.com/ajhollid/9afa39410c7bbf52cc905f285a2225bf/raw/429a231a3559ebc95f6f488ed2c766bd7d6f46e5/dummyData.json",
+ {
+ headers: {
+ "Content-Type": "application/json",
+ "Cache-Control": "no-cache",
+ },
+ }
+ );
+ return res.status(200).json(response.data);
+ } catch (error) {
+ return res.status(500).json({ message: error.message });
+ }
+ });
+
//health check
app.use("/api/v1/healthy", (req, res) => {
try {