Merge pull request #170 from bluewave-labs/feat/monitor-to-backend

Feat/monitor to backend, resolves #160
This commit is contained in:
Alexander Holliday authored and GitHub committed 2024-06-24 14:00:23 -04:00
commit 173baf76be
10 files changed
+177 -83

No files matched your search

+1 -1
View File
@@ -31,7 +31,7 @@ function App() {
<Route exact path="/register" element={<Register />} />
<Route exact path="/login" element={<Login />} />
<Route exact path="/demo" element={<Demo />} />
<Route path="/toast" element={<ToastComponent />} />
{/* <Route path="/toast" element={<ToastComponent />} /> */}
<Route path="*" element={<NotFound />} />
<Route path="/playground" element={<PlayGround />} />
<Route path="/forgot-password" element={<ForgotPassword />} />
@@ -1,17 +1,57 @@
import React from "react";
import "./barChart.css";
import PropTypes from "prop-types";
const RANGE_MAX = 100;
const RANGE_MIN = 1;
/**
* Normalizes the response times of a set of checks to a specified range.
* This function calculates the minimum and maximum response times from the input,
* then scales each check's response time linearly between `RANGE_MIN` and `RANGE_MAX`.
*
* @param {Array<Checks>} checks - An array of check objects. Each check should have a `responseTime` property.
* @returns {Array<Checks>} An array of check objects with normalized `responseTime` values.
*/
const normalizeData = (checks) => {
const min = checks.reduce((accum, cur) => {
return Math.min(accum, cur.responseTime);
}, Infinity);
const max = checks.reduce((accum, cur) => {
return Math.max(accum, cur.responseTime);
}, 0);
const normalizedChecks = checks.map((check) => {
let normailzedResponseTime =
RANGE_MIN +
((check.responseTime - min) * (RANGE_MAX - RANGE_MIN)) / (max - min);
const normalizedCheck = { ...check, responseTime: normailzedResponseTime };
return normalizedCheck;
});
return normalizedChecks;
};
/**
* BarChart renders a bar chart visualization for a set of checks. Each bar represents a check's response time,
* with the color indicating the check's status (green for successful checks, red for failed ones).
*
* @component
* @param {Array<Checks>} checks - An array of check objects to be visualized. Each check object should have an `_id`,
* a `status` indicating success (true) or failure (false), and a `responseTime` representing the height of the bar in percentage.
*/
const BarChart = ({ checks = [] }) => {
const normailzedChecks = normalizeData(checks);
return (
<div className="bar-chart">
{checks.map((value, index) => (
<div
key={index}
className={`bar ${value.status ? "green" : "red"}`}
style={{ height: `${value.responseTime / 3}%` }}
/>
))}
{normailzedChecks.map((check) => {
return (
<div
key={check._id}
className={`bar ${check.status ? "green" : "red"}`}
style={{ height: `${check.responseTime}%` }}
/>
);
})}
</div>
);
};
@@ -7,7 +7,7 @@ import { useTheme } from "@mui/material";
* @component
* @param {Object} props
* @param {string} props.title - The title text for the server status (required)
* @param {string} props.value - The value text to be displayed (required)
* @param {number} props.value - The value text to be displayed (required)
* @param {string} props.state - The state of the server (e.g., "online", "offline", "warning") (required)
* @returns {JSX.Element} - Renders the server status component
*/
@@ -30,7 +30,7 @@ const ServerStatus = ({ title, value, state }) => {
ServerStatus.propTypes = {
title: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
value: PropTypes.number.isRequired,
state: PropTypes.string.isRequired,
};
@@ -1,8 +1,15 @@
import SearchTextField from "../TextFields/Search/SearchTextField";
import "./index.css";
import React from "react";
import HostsTable from "../HostsTable";
import Pagination from "../Pagination";
import PropTypes from "prop-types";
/**
* CurrentMonitors displays the current status of monitor
*
* @component
* @param {Array<Monitor>} monitors - An array of monitor objects to be displayed.
*/
const CurrentMonitors = ({ monitors }) => {
return (
@@ -10,7 +17,7 @@ const CurrentMonitors = ({ monitors }) => {
<div className="current-monitors-bar">
<div className="current-monitors-title-holder">
<div className="current-monitors-title">Current monitors</div>
<div className="current-monitors-counter">5</div>
<div className="current-monitors-counter">{monitors.length}</div>
</div>
<div className="current-monitors-search-bar">
<SearchTextField />
@@ -24,4 +31,8 @@ const CurrentMonitors = ({ monitors }) => {
);
};
CurrentMonitors.propTypes = {
monitors: PropTypes.arrayOf(PropTypes.object).isRequired,
};
export default CurrentMonitors;
+60 -10
View File
@@ -1,4 +1,32 @@
import React from "react";
import BarChart from "../Charts/BarChart/BarChart";
import PropTypes from "prop-types";
import Host from "../Host/";
import HostStatus from "../HostStatus";
/**
* HostsTable displays the current status of monitor
*
* @component
* @param {Array<Monitor>} monitors - An array of monitor objects to be displayed.
*/
const getLastCheckStatus = (monitor) => {
if (monitor.checks.length === 0) {
// Default values for when there are no checks
return {
color: "var(--env-var-color-21)",
statusText: "No Data",
dotColor: "var(--env-var-color-19)",
};
}
const lastCheck = monitor.checks[monitor.checks.length - 1];
const isUp = lastCheck.status === true;
return {
color: isUp ? "var(--env-var-color-20)" : "var(--env-var-color-21)",
statusText: isUp ? "Up" : "Down",
dotColor: isUp ? "var(--env-var-color-17)" : "var(--env-var-color-19)",
};
};
const HostsTable = ({ monitors }) => {
return (
@@ -8,23 +36,45 @@ const HostsTable = ({ monitors }) => {
<tr className="theader-row">
<td className="theader-row-cell">Host</td>
<td className="theader-row-cell">Status</td>
<td className="theader-row-cell">Team</td>
<td className="theader-row-cell">Response</td>
<td className="theader-row-cell">Actions</td>
</tr>
</thead>
<tbody>
{monitors.map((monitor, index) => (
<tr className="tbody-row" key={index}>
<td className="tbody-row-cell">{monitor.host}</td>
<td className="tbody-row-cell">{monitor.status}</td>
<td className="tbody-row-cell">{monitor.team}</td>
<td className="tbody-row-cell actions-cell">{monitor.actions}</td>
</tr>
))}
{monitors.map((monitor) => {
const host = Host(
monitor.name,
100,
"var(--env-var-color-17)",
monitor.url
);
const { color, currentStatus, dotColor } =
getLastCheckStatus(monitor);
const status = HostStatus(color, currentStatus, dotColor);
return (
<tr className="tbody-row" key={monitor._id}>
<td className="tbody-row-cell">{host}</td>
<td className="tbody-row-cell">{status}</td>
<td className="tbody-row-cell">
<BarChart checks={monitor.checks} />
</td>
<td className="tbody-row-cell actions-cell">
{monitor.actions}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
};
HostsTable.propTypes = {
monitors: PropTypes.arrayOf(PropTypes.object).isRequired,
};
export default HostsTable;
+11 -4
View File
@@ -1,5 +1,6 @@
import axios from "axios";
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { jwtDecode } from "jwt-decode";
const initialState = {
isLoading: false,
@@ -25,9 +26,17 @@ export const getMonitors = createAsyncThunk(
export const getMonitorsByUserId = createAsyncThunk(
"montiors/getMonitorsByUserId",
async (token, thunkApi) => {
const user = jwtDecode(token);
try {
// TODO get userId and make request
console.log(token);
const res = await axios.get(
import.meta.env.VITE_APP_API_BASE_URL + "/monitors/user/" + user._id,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
return res.data;
} catch (error) {
return thunkApi.rejectWithValue(error.response.data);
}
@@ -50,7 +59,6 @@ const monitorsSlice = createSlice({
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
console.log(action.payload);
state.monitors = action.payload.data;
})
.addCase(getMonitors.rejected, (state, action) => {
@@ -68,7 +76,6 @@ const monitorsSlice = createSlice({
.addCase(getMonitorsByUserId.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.msg;
console.log(action.payload);
state.monitors = action.payload.data;
})
.addCase(getMonitorsByUserId.rejected, (state, action) => {
-12
View File
@@ -3,24 +3,12 @@ import { Outlet } from "react-router";
import DashboardSidebar from "../../Components/DashboardSidebar";
import "./index.css";
// Demo, remove me
import { useSelector } from "react-redux";
// Emd Demo
const HomeLayout = () => {
const authState = useSelector((state) => state.auth);
const { user, msg } = authState;
return (
<div className="home-layout">
<NavBar />
<div className="main-content">
<DashboardSidebar />
{/* Remove me later */}
{user && <p>{user.firstname}</p>}
{user && <p>{user.lastname}</p>}
{user && <p>{user.email}</p>}
{msg && <p>Msg: {msg}</p>}
{/* I am a demo */}
<Outlet />
</div>
</div>
+26 -39
View File
@@ -1,46 +1,29 @@
import React, { useEffect, useState } from "react";
import Host from "../../Components/Host";
import HostStatus from "../../Components/HostStatus";
import HostActions from "../../Components/HostActions";
import BarChart from "../../Components/Charts/BarChart/BarChart";
import Button from "../../Components/Button";
import ServerStatus from "../../Components/Charts/Servers/ServerStatus";
import CurrentMonitors from "../../Components/CurrentMonitors";
import MockData from "../../Mock/sample_data.json";
import PropTypes from "prop-types";
const CurrentStats = (mockdata = true) => {
// The hardcoded part is passed as default value for the purpose of demonstration.
// Pass an empty array, [], before fetching the data
const [monitors, setMonitors] = useState([]);
/**
* CurrentStats displays the current status of monitor
*
* @component
* @param {Array<Monitor>} monitors - An array of monitor objects to be displayed.
*/
useEffect(() => {
if (mockdata) {
console.log(MockData.data);
setMonitors(
MockData.data.map((item) => ({
host: Host(item.name, 100, "var(--env-var-color-17)", item.url),
status: HostStatus(
item.isActive
? "var(--env-var-color-20)"
: "var(--env-var-color-21)",
item.isActive ? "Up" : "Down",
item.isActive
? "var(--env-var-color-17)"
: "var(--env-var-color-19)"
),
team: <BarChart checks={item.checks} />,
actions: HostActions(),
}))
);
const CurrentStats = ({ monitors }) => {
const up = monitors.reduce((acc, cur) => {
if (cur.checks.length > 0) {
return cur.checks[cur.checks.length - 1].status === true ? acc + 1 : acc;
}
}, [mockdata]);
return 0;
}, 0);
// useEffect(() => {
// fetch("API_URL")
// .then((response) => response.json())
// .then((data) => setMonitors(data))
// .catch((error) => console.error(error));
// }, []);
const down = monitors.reduce((acc, cur) => {
if (cur.checks.length > 0) {
return cur.checks[cur.checks.length - 1].status === false ? acc + 1 : acc;
}
return 0;
}, 0);
return (
<div>
@@ -56,9 +39,9 @@ const CurrentStats = (mockdata = true) => {
</div>
<div className="monitors-gaps-medium"></div>
<div className="monitors-stats">
<ServerStatus title="Up" value="4" state="up" />
<ServerStatus title="Down" value="0" state="down" />
<ServerStatus title="Paused" value="0" state="pause" />
<ServerStatus title="Up" value={up} state="up" />
<ServerStatus title="Down" value={down} state="down" />
<ServerStatus title="Paused" value={0} state="pause" />
</div>
<div className="monitors-gaps-medium"></div>
<CurrentMonitors monitors={monitors} />
@@ -66,4 +49,8 @@ const CurrentStats = (mockdata = true) => {
);
};
CurrentStats.propTypes = {
monitors: PropTypes.arrayOf(PropTypes.object).isRequired,
};
export default CurrentStats;
+13 -3
View File
@@ -1,13 +1,23 @@
// import CreateNewMonitor from "./CreateNewMonitor";
import CurrentStats from "./CurrentStats";
import "./index.css";
import React from "react";
import { useEffect } from "react";
import { useSelector } from "react-redux";
import { useDispatch } from "react-redux";
import { getMonitorsByUserId } from "../../Features/Monitors/monitorsSlice";
const Monitors = () => {
const monitorState = useSelector((state) => state.monitors);
const authstate = useSelector((state) => state.auth);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getMonitorsByUserId(authstate.authToken));
}, []);
return (
<div className="monitors">
<CurrentStats />
{/* <CreateNewMonitor /> */}
<CurrentStats monitors={monitorState.monitors} />
</div>
);
};
+3 -2
View File
@@ -2,6 +2,7 @@ const axios = require("axios");
const ping = require("ping");
const logger = require("../utils/logger");
const Check = require("../models/Check");
const { Console } = require("winston/lib/winston/transports");
class NetworkService {
constructor(db) {
@@ -149,9 +150,9 @@ class NetworkService {
const insertedCheck = await check.save();
return insertedCheck.status;
} catch (error) {
logger.error(`Error wrtiting check for ${job.id}`, {
logger.error(`Error wrtiting check for ${check.monitorId}`, {
service: this.SERVICE_NAME,
jobId: job.id,
monitorId: check.monitorId,
error: error,
});
return false;