Merge pull request #170 from bluewave-labs/feat/monitor-to-backend
Feat/monitor to backend, resolves #160
This commit is contained in:
10 files changed
+177
-83
No files matched your search
+1
-1
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user