diff --git a/Client/src/App.jsx b/Client/src/App.jsx index d6fca146f..09fcfcf5f 100644 --- a/Client/src/App.jsx +++ b/Client/src/App.jsx @@ -83,14 +83,14 @@ function App() { path="integrations" element={} /> - } - /> - */} + {/* } - /> + /> */} } diff --git a/Client/src/Components/Sidebar/index.jsx b/Client/src/Components/Sidebar/index.jsx index 145b6f02e..1333b6f67 100644 --- a/Client/src/Components/Sidebar/index.jsx +++ b/Client/src/Components/Sidebar/index.jsx @@ -55,7 +55,7 @@ const menu = [ }, { name: "Incidents", path: "incidents", icon: }, // { name: "Status pages", path: "status", icon: }, - { name: "Maintenance", path: "maintenance", icon: }, + // { name: "Maintenance", path: "maintenance", icon: }, // { name: "Integrations", path: "integrations", icon: }, { name: "Account", diff --git a/Client/src/Components/TabPanels/Account/TeamPanel.jsx b/Client/src/Components/TabPanels/Account/TeamPanel.jsx index 07d209fc1..ac55cd5b1 100644 --- a/Client/src/Components/TabPanels/Account/TeamPanel.jsx +++ b/Client/src/Components/TabPanels/Account/TeamPanel.jsx @@ -55,7 +55,9 @@ const TeamPanel = () => { useEffect(() => { const fetchTeam = async () => { try { - const response = await networkService.getAllUsers(authToken); + const response = await networkService.getAllUsers({ + authToken: authToken, + }); setMembers(response.data.data); } catch (error) { createToast({ @@ -187,11 +189,11 @@ const TeamPanel = () => { } try { - await networkService.requestInvitationToken( - authToken, - toInvite.email, - toInvite.role - ); + await networkService.requestInvitationToken({ + authToken: authToken, + email: toInvite.email, + role: toInvite.role, + }); closeInviteModal(); createToast({ body: "Member invited. They will receive an email with details on how to create their account.", diff --git a/Client/src/Features/Auth/authSlice.js b/Client/src/Features/Auth/authSlice.js index f6d40c38a..7c8588b52 100644 --- a/Client/src/Features/Auth/authSlice.js +++ b/Client/src/Features/Auth/authSlice.js @@ -66,8 +66,11 @@ export const update = createAsyncThunk( form.deleteProfileImage && fd.append("deleteProfileImage", form.deleteProfileImage); - const res = await networkService.updateUser(token, user._id, fd); - console.log(res); + const res = await networkService.updateUser({ + authToken: token, + userId: user._id, + form: fd, + }); return res.data; } catch (error) { if (error.response && error.response.data) { @@ -88,7 +91,10 @@ export const deleteUser = createAsyncThunk( const user = jwtDecode(data); try { - const res = await networkService.deleteUser(data, user._id); + const res = await networkService.deleteUser({ + authToken: data, + userId: user._id, + }); return res.data; } catch (error) { if (error.response && error.response.data) { @@ -127,8 +133,11 @@ export const setNewPassword = createAsyncThunk( async (data, thunkApi) => { const { token, form } = data; try { - await networkService.validateRecoveryToken(token); - const res = await networkService.setNewPassword(token, form); + await networkService.validateRecoveryToken({ recoveryToken: token }); + const res = await networkService.setNewPassword({ + recoveryToken: token, + form: form, + }); return res.data; } catch (error) { if (error.response.data) { diff --git a/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js b/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js index 2e107e7e7..0322a5143 100644 --- a/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js +++ b/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js @@ -13,7 +13,10 @@ export const createPageSpeed = createAsyncThunk( async (data, thunkApi) => { try { const { authToken, monitor } = data; - const res = await networkService.createMonitor(authToken, monitor); + const res = await networkService.createMonitor({ + authToken: authToken, + monitor: monitor, + }); return res.data; } catch (error) { if (error.response && error.response.data) { @@ -33,7 +36,10 @@ export const getPagespeedMonitorById = createAsyncThunk( async (data, thunkApi) => { try { const { authToken, monitorId } = data; - const res = await networkService.getMonitorByid(authToken, monitorId); + const res = await networkService.getMonitorById({ + authToken: authToken, + monitorId: monitorId, + }); return res.data; } catch (error) { if (error.response && error.response.data) { @@ -53,11 +59,11 @@ export const getPageSpeedByTeamId = createAsyncThunk( async (token, thunkApi) => { const user = jwtDecode(token); try { - const res = await networkService.getMonitorsAndSummaryByTeamId( - token, - user.teamId, - ["pagespeed"] - ); + const res = await networkService.getMonitorsAndSummaryByTeamId({ + authToken: token, + teamId: user.teamId, + types: ["pagespeed"], + }); return res.data; } catch (error) { @@ -84,11 +90,11 @@ export const updatePageSpeed = createAsyncThunk( interval: monitor.interval, // notifications: monitor.notifications, }; - const res = await networkService.updateMonitor( - authToken, - monitor._id, - updatedFields - ); + const res = await networkService.updateMonitor({ + authToken: authToken, + monitorId: monitor._id, + updatedFields: updatedFields, + }); return res.data; } catch (error) { if (error.response && error.response.data) { @@ -108,10 +114,10 @@ export const deletePageSpeed = createAsyncThunk( async (data, thunkApi) => { try { const { authToken, monitor } = data; - const res = await networkService.deleteMonitorById( - authToken, - monitor._id - ); + const res = await networkService.deleteMonitorById({ + authToken: authToken, + monitorId: monitor._id, + }); return res.data; } catch (error) { if (error.response && error.response.data) { @@ -130,7 +136,10 @@ export const pausePageSpeed = createAsyncThunk( async (data, thunkApi) => { try { const { authToken, monitorId } = data; - const res = await networkService.pauseMonitorById(authToken, monitorId); + const res = await networkService.pauseMonitorById({ + authToken: authToken, + monitorId: monitorId, + }); return res.data; } catch (error) { if (error.response && error.response.data) { diff --git a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js index a8b1ea737..f6c3cb10a 100644 --- a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js +++ b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js @@ -13,7 +13,10 @@ export const createUptimeMonitor = createAsyncThunk( async (data, thunkApi) => { try { const { authToken, monitor } = data; - const res = await networkService.createMonitor(authToken, monitor); + const res = await networkService.createMonitor({ + authToken: authToken, + monitor: monitor, + }); return res.data; } catch (error) { if (error.response && error.response.data) { @@ -33,7 +36,10 @@ export const getUptimeMonitorById = createAsyncThunk( async (data, thunkApi) => { try { const { authToken, monitorId } = data; - const res = await networkService.getMonitorByid(authToken, monitorId); + const res = await networkService.getMonitorById({ + authToken: authToken, + monitorId: monitorId, + }); return res.data; } catch (error) { if (error.response && error.response.data) { @@ -53,11 +59,11 @@ export const getUptimeMonitorsByTeamId = createAsyncThunk( async (token, thunkApi) => { const user = jwtDecode(token); try { - const res = await networkService.getMonitorsAndSummaryByTeamId( - token, - user.teamId, - ["http", "ping"] - ); + const res = await networkService.getMonitorsAndSummaryByTeamId({ + authToken: token, + teamId: user.teamId, + types: ["http", "ping"], + }); return res.data; } catch (error) { if (error.response && error.response.data) { @@ -83,11 +89,11 @@ export const updateUptimeMonitor = createAsyncThunk( interval: monitor.interval, notifications: monitor.notifications, }; - const res = await networkService.updateMonitor( - authToken, - monitor._id, - updatedFields - ); + const res = await networkService.updateMonitor({ + authToken: authToken, + monitorId: monitor._id, + updatedFields: updatedFields, + }); return res.data; } catch (error) { if (error.response && error.response.data) { @@ -107,10 +113,10 @@ export const deleteUptimeMonitor = createAsyncThunk( async (data, thunkApi) => { try { const { authToken, monitor } = data; - const res = await networkService.deleteMonitorById( - authToken, - monitor._id - ); + const res = await networkService.deleteMonitorById({ + authToken: authToken, + monitorId: monitor._id, + }); return res.data; } catch (error) { if (error.response && error.response.data) { @@ -130,7 +136,10 @@ export const pauseUptimeMonitor = createAsyncThunk( async (data, thunkApi) => { try { const { authToken, monitorId } = data; - const res = await networkService.pauseMonitorById(authToken, monitorId); + const res = await networkService.pauseMonitorById({ + authToken: authToken, + monitorId: monitorId, + }); return res.data; } catch (error) { if (error.response && error.response.data) { @@ -150,7 +159,10 @@ export const deleteMonitorChecksByTeamId = createAsyncThunk( async (data, thunkApi) => { try { const { authToken, teamId } = data; - const res = await networkService.deleteChecksByTeamId(authToken, teamId); + const res = await networkService.deleteChecksByTeamId({ + authToken: authToken, + teamId: teamId, + }); return res.data; } catch (error) { if (error.response && error.response.data) { @@ -169,7 +181,9 @@ export const addDemoMonitors = createAsyncThunk( async (data, thunkApi) => { try { const { authToken } = data; - const res = await networkService.addDemoMonitors(authToken); + const res = await networkService.addDemoMonitors({ + authToken: authToken, + }); return res.data; } catch (error) { if (error.response && error.response.data) { @@ -188,7 +202,9 @@ export const deleteAllMonitors = createAsyncThunk( async (data, thunkApi) => { try { const { authToken } = data; - const res = await networkService.deleteAllMonitors(authToken); + const res = await networkService.deleteAllMonitors({ + authToken: authToken, + }); return res.data; } catch (error) { if (error.response && error.response.data) { diff --git a/Client/src/Pages/Incidents/IncidentTable/index.jsx b/Client/src/Pages/Incidents/IncidentTable/index.jsx index 6ca18a1e9..f47ad0cf0 100644 --- a/Client/src/Pages/Incidents/IncidentTable/index.jsx +++ b/Client/src/Pages/Incidents/IncidentTable/index.jsx @@ -53,27 +53,27 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => { try { let res; if (selectedMonitor === "0") { - res = await networkService.getChecksByTeam( - authToken, - user.teamId, - "desc", - null, - null, - filter, - paginationController.page, - paginationController.rowsPerPage - ); + res = await networkService.getChecksByTeam({ + authToken: authToken, + userId: user.teamId, + sortOrder: "desc", + limit: null, + dateRange: null, + filter: filter, + page: paginationController.page, + rowsPerPage: paginationController.rowsPerPage, + }); } else { - res = await networkService.getChecksByMonitor( - authToken, - selectedMonitor, - "desc", - null, - null, - filter, - paginationController.page, - paginationController.rowsPerPage - ); + res = await networkService.getChecksByMonitor({ + authToken: authToken, + monitorId: selectedMonitor, + sortOrder: "desc", + limit: null, + dateRange: null, + sitler: filter, + page: paginationController.page, + rowsPerPage: paginationController.rowsPerPage, + }); } setChecks(res.data.data.checks); setChecksCount(res.data.data.checksCount); diff --git a/Client/src/Pages/Monitors/Details/PaginationTable/index.jsx b/Client/src/Pages/Monitors/Details/PaginationTable/index.jsx index b438f104b..8538ac169 100644 --- a/Client/src/Pages/Monitors/Details/PaginationTable/index.jsx +++ b/Client/src/Pages/Monitors/Details/PaginationTable/index.jsx @@ -40,16 +40,16 @@ const PaginationTable = ({ monitorId, dateRange }) => { useEffect(() => { const fetchPage = async () => { try { - const res = await networkService.getChecksByMonitor( - authToken, - monitorId, - "desc", - null, - dateRange, - null, - paginationController.page, - paginationController.rowsPerPage - ); + const res = await networkService.getChecksByMonitor({ + authToken: authToken, + monitoirId: monitorId, + sortOrder: "desc", + limit: null, + dateRange: dateRange, + filter: null, + page: paginationController.page, + rowsPerPage: paginationController.rowsPerPage, + }); setChecks(res.data.data.checks); setChecksCount(res.data.data.checksCount); } catch (error) { diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 50ae8f558..31029fb45 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -63,15 +63,15 @@ const DetailsPage = ({ isAdmin }) => { const fetchMonitor = useCallback(async () => { try { - const res = await networkService.getStatsByMonitorId( - authToken, - monitorId, - null, - null, - dateRange, - 50, - true - ); + const res = await networkService.getStatsByMonitorId({ + authToken: authToken, + monitorId: monitorId, + sortOrder: null, + limit: null, + dateRange: dateRange, + numToDisplay: 50, + normalize: true, + }); setMonitor(res?.data?.data ?? {}); } catch (error) { logger.error(error); @@ -89,10 +89,10 @@ const DetailsPage = ({ isAdmin }) => { return; } try { - const res = await networkService.getCertificateExpiry( - authToken, - monitorId - ); + const res = await networkService.getCertificateExpiry({ + authToken: authToken, + monitorId: monitorId, + }); if (res?.data?.data?.certificateDate) { let [month, day, year] = res.data.data.certificateDate.split("/"); diff --git a/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx b/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx index b7e50a499..b86a739e0 100644 --- a/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx +++ b/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx @@ -17,7 +17,7 @@ import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded"; import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded"; import { setRowsPerPage } from "../../../../Features/UI/uiSlice"; -import { useState, useEffect, memo } from "react"; +import { useState, useEffect, memo, useCallback, useRef } from "react"; import { useNavigate } from "react-router"; import { logger } from "../../../../Utils/Logger"; import Host from "../host"; @@ -49,7 +49,6 @@ import useUtils from "../../utils"; */ const TablePaginationActions = (props) => { const { count, page, rowsPerPage, onPageChange } = props; - const handleFirstPageButtonClick = (event) => { onPageChange(event, 0); }; @@ -108,7 +107,7 @@ TablePaginationActions.propTypes = { onPageChange: PropTypes.func.isRequired, }; -const MonitorTable = ({ isAdmin, filter }) => { +const MonitorTable = ({ isAdmin, filter, setLoading }) => { const theme = useTheme(); const navigate = useNavigate(); const dispatch = useDispatch(); @@ -121,6 +120,7 @@ const MonitorTable = ({ isAdmin, filter }) => { const authState = useSelector((state) => state.auth); const [updateTrigger, setUpdateTrigger] = useState(false); const [sort, setSort] = useState({}); + const prevFilter = useRef(filter); const handleActionMenuDelete = () => { setUpdateTrigger((prev) => !prev); @@ -140,33 +140,53 @@ const MonitorTable = ({ isAdmin, filter }) => { setPage(0); }; + const fetchPage = useCallback(async () => { + try { + const { authToken } = authState; + const user = jwtDecode(authToken); + const res = await networkService.getMonitorsByTeamId({ + authToken, + teamId: user.teamId, + limit: 25, + types: ["http", "ping"], + status: null, + checkOrder: "desc", + normalize: true, + page: page, + rowsPerPage: rowsPerPage, + filter: filter, + field: sort.field, + order: sort.order, + }); + setMonitors(res?.data?.data?.monitors ?? []); + setMonitorCount(res?.data?.data?.monitorCount ?? 0); + setLoading(false); + } catch (error) { + logger.error(error); + } + }, [authState, page, rowsPerPage, filter, sort, setLoading]); + useEffect(() => { - const fetchPage = async () => { - try { - const { authToken } = authState; - const user = jwtDecode(authToken); - const res = await networkService.getMonitorsByTeamId({ - authToken, - teamId: user.teamId, - limit: 25, - types: ["http", "ping"], - status: null, - checkOrder: "desc", - normalize: true, - page: page, - rowsPerPage: rowsPerPage, - filter: filter, - field: sort.field, - order: sort.order, - }); - setMonitors(res?.data?.data?.monitors ?? []); - setMonitorCount(res?.data?.data?.monitorCount ?? 0); - } catch (error) { - logger.error(error); - } - }; fetchPage(); - }, [updateTrigger, authState, page, rowsPerPage, filter, sort]); + }, [ + updateTrigger, + authState, + page, + rowsPerPage, + filter, + sort, + setLoading, + fetchPage, + ]); + + // Listen for changes in filter, if new value reset the page + useEffect(() => { + if (prevFilter.current !== filter) { + setPage(0); + fetchPage(); + } + prevFilter.current = filter; + }, [filter, fetchPage]); /** * Helper function to calculate the range of displayed rows. @@ -407,6 +427,7 @@ const MonitorTable = ({ isAdmin, filter }) => { MonitorTable.propTypes = { isAdmin: PropTypes.bool, filter: PropTypes.string, + setLoading: PropTypes.func, }; const MemoizedMonitorTable = memo(MonitorTable); diff --git a/Client/src/Pages/Monitors/Home/index.jsx b/Client/src/Pages/Monitors/Home/index.jsx index 9db0cbf9a..c2a532bb9 100644 --- a/Client/src/Pages/Monitors/Home/index.jsx +++ b/Client/src/Pages/Monitors/Home/index.jsx @@ -4,7 +4,13 @@ import { useSelector, useDispatch } from "react-redux"; import { getUptimeMonitorsByTeamId } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice"; import { useNavigate } from "react-router-dom"; import { useTheme } from "@emotion/react"; -import { Box, Button, Stack, Typography } from "@mui/material"; +import { + Box, + Button, + CircularProgress, + Stack, + Typography, +} from "@mui/material"; import PropTypes from "prop-types"; import SkeletonLayout from "./skeleton"; import Fallback from "./fallback"; @@ -21,12 +27,19 @@ const Monitors = ({ isAdmin }) => { const monitorState = useSelector((state) => state.uptimeMonitors); const authState = useSelector((state) => state.auth); const [search, setSearch] = useState(""); + const [isSearching, setIsSearching] = useState(false); const dispatch = useDispatch({}); const debouncedFilter = useDebounce(search, 500); + const handleSearch = (value) => { + setIsSearching(true); + setSearch(value); + }; + useEffect(() => { dispatch(getUptimeMonitorsByTeamId(authState.authToken)); }, [authState.authToken, dispatch]); + let loading = monitorState?.isLoading && monitorState?.monitorsSummary?.monitors?.length === 0; @@ -124,11 +137,47 @@ const Monitors = ({ isAdmin }) => { options={monitorState?.monitorsSummary.monitors} filteredBy="name" value={search} - handleInputChange={setSearch} + handleInputChange={handleSearch} /> - + + {isSearching && ( + <> + + + + + + )} + + )} diff --git a/Client/src/Pages/PageSpeed/Details/index.jsx b/Client/src/Pages/PageSpeed/Details/index.jsx index 48d8aa86a..8fc137223 100644 --- a/Client/src/Pages/PageSpeed/Details/index.jsx +++ b/Client/src/Pages/PageSpeed/Details/index.jsx @@ -44,15 +44,15 @@ const PageSpeedDetails = () => { useEffect(() => { const fetchMonitor = async () => { try { - const res = await networkService.getStatsByMonitorId( - authToken, - monitorId, - "desc", - 50, - "day", - null, - null - ); + const res = await networkService.getStatsByMonitorId({ + authToken: authToken, + monitorId: monitorId, + sortOrder: "desc", + limit: 50, + dateRange: "day", + numToDisplay: null, + normalize: null, + }); setMonitor(res?.data?.data ?? {}); setAudits(res?.data?.data?.checks?.[0]?.audits ?? []); } catch (error) { diff --git a/Client/src/Pages/Settings/index.jsx b/Client/src/Pages/Settings/index.jsx index ac25c480d..806f29e75 100644 --- a/Client/src/Pages/Settings/index.jsx +++ b/Client/src/Pages/Settings/index.jsx @@ -69,7 +69,10 @@ const Settings = ({ isAdmin }) => { const handleSave = async () => { try { setChecksIsLoading(true); - await networkService.updateChecksTTL(authToken, form.ttl); + await networkService.updateChecksTTL({ + authToken: authToken, + ttl: form.ttl, + }); const updatedUser = { ...user, checkTTL: form.ttl }; const action = await dispatch( update({ authToken, localData: updatedUser }) diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js index 63323d5a8..c087fb73a 100644 --- a/Client/src/Utils/NetworkService.js +++ b/Client/src/Utils/NetworkService.js @@ -24,15 +24,16 @@ class NetworkService { * ************************************ * * @async - * @param {string} authToken - The authorization token to be used in the request header. - * @param {string} monitorId - The monitor ID to be sent in the param. + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} config.monitorId - The monitor ID to be sent in the param. * @returns {Promise} The response from the axios GET request. */ - async getMonitorByid(authToken, monitorId) { - return this.axiosInstance.get(`/monitors/${monitorId}`, { + async getMonitorById(config) { + return this.axiosInstance.get(`/monitors/${config.monitorId}`, { headers: { - Authorization: `Bearer ${authToken}`, + Authorization: `Bearer ${config.authToken}`, "Content-Type": "application/json", }, }); @@ -45,32 +46,46 @@ class NetworkService { * ************************************ * * @async - * @param {string} authToken - The authorization token to be used in the request header. - * @param {Object} monitor - The monitor object to be sent in the request body. + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {Object} config.monitor - The monitor object to be sent in the request body. * @returns {Promise} The response from the axios POST request. */ - async createMonitor(authToken, monitor) { - return this.axiosInstance.post(`/monitors`, monitor, { + async createMonitor(config) { + return this.axiosInstance.post(`/monitors`, config.monitor, { headers: { - Authorization: `Bearer ${authToken}`, + Authorization: `Bearer ${config.authToken}`, "Content-Type": "application/json", }, }); } - async getMonitorsAndSummaryByTeamId(authToken, teamId, types) { + /** + * + * ************************************ + * Gets monitors and summary of stats by TeamID + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} config.teamId - Team ID + * @param {Array} config.types - Array of monitor types + * @returns {Promise} The response from the axios POST request. + */ + async getMonitorsAndSummaryByTeamId(config) { const params = new URLSearchParams(); - if (types) { - types.forEach((type) => { + if (config.types) { + config.types.forEach((type) => { params.append("type", type); }); } return this.axiosInstance.get( - `/monitors/team/summary/${teamId}?${params.toString()}`, + `/monitors/team/summary/${config.teamId}?${params.toString()}`, { headers: { - Authorization: `Bearer ${authToken}`, + Authorization: `Bearer ${config.authToken}`, "Content-Type": "application/json", }, } @@ -148,61 +163,29 @@ class NetworkService { * ************************************ * * @async - * @param {string} authToken - The authorization token to be used in the request header. - * @param {string} monitorId - The ID of the monitor whose statistics are to be retrieved. - * @param {string} [sortOrder] - The order in which to sort the retrieved statistics. - * @param {number} [limit] - The maximum number of statistics to retrieve. - * @param {string} [dateRange] - The date range for which to retrieve statistics. - * @param {number} [numToDisplay] - The number of checks to display. - * @param {boolean} [normalize] - Whether to normalize the retrieved statistics. + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} config.monitorId - The ID of the monitor whose statistics are to be retrieved. + * @param {string} config.sortOrder - The order in which to sort the retrieved statistics. + * @param {number} config.limit - The maximum number of statistics to retrieve. + * @param {string} config.dateRange - The date range for which to retrieve statistics. + * @param {number} config.numToDisplay - The number of checks to display. + * @param {boolean} config.normalize - Whether to normalize the retrieved statistics. * @returns {Promise} The response from the axios GET request. */ - async getStatsByMonitorId( - authToken, - monitorId, - sortOrder, - limit, - dateRange, - numToDisplay, - normalize - ) { + async getStatsByMonitorId(config) { const params = new URLSearchParams(); - if (sortOrder) params.append("sortOrder", sortOrder); - if (limit) params.append("limit", limit); - if (dateRange) params.append("dateRange", dateRange); - if (numToDisplay) params.append("numToDisplay", numToDisplay); - if (normalize) params.append("normalize", normalize); + if (config.sortOrder) params.append("sortOrder", config.sortOrder); + if (config.limit) params.append("limit", config.limit); + if (config.dateRange) params.append("dateRange", config.dateRange); + if (config.numToDisplay) params.append("numToDisplay", config.numToDisplay); + if (config.normalize) params.append("normalize", config.normalize); return this.axiosInstance.get( - `/monitors/stats/${monitorId}?${params.toString()}`, + `/monitors/stats/${config.monitorId}?${params.toString()}`, { headers: { - Authorization: `Bearer ${authToken}`, - }, - } - ); - } - - /** - * ************************************ - * Gets aggregate stats by monitor ID - * ************************************ - * - * @async - * @param {string} authToken - The authorization token to be used in the request header. - * @param {string} monitorId - The ID of the monitor whose certificate expiry is to be retrieved. - * @returns {Promise} The response from the axios GET request. - * - */ - async getAggregateStatsById(authToken, monitorId, dateRange) { - const params = new URLSearchParams(); - if (dateRange) params.append("dateRange", dateRange); - - return this.axiosInstance.get( - `/monitors/aggregate/${monitorId}?${params.toString()}`, - { - headers: { - Authorization: `Bearer ${authToken}`, + Authorization: `Bearer ${config.authToken}`, }, } ); @@ -214,18 +197,23 @@ class NetworkService { * ************************************ * * @async - * @param {string} authToken - The authorization token to be used in the request header. - * @param {string} monitorId - The ID of the monitor to be updated. - * @param {Object} updatedFields - The fields to be updated for the monitor. + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} config.monitorId - The ID of the monitor to be updated. + * @param {Object} config.updatedFields - The fields to be updated for the monitor. * @returns {Promise} The response from the axios PUT request. */ - async updateMonitor(authToken, monitorId, updatedFields) { - return this.axiosInstance.put(`/monitors/${monitorId}`, updatedFields, { - headers: { - Authorization: `Bearer ${authToken}`, - "Content-Type": "application/json", - }, - }); + async updateMonitor(config) { + return this.axiosInstance.put( + `/monitors/${config.monitorId}`, + config.updatedFields, + { + headers: { + Authorization: `Bearer ${config.authToken}`, + "Content-Type": "application/json", + }, + } + ); } /** @@ -234,14 +222,15 @@ class NetworkService { * ************************************ * * @async - * @param {string} authToken - The authorization token to be used in the request header. - * @param {string} monitorId - The ID of the monitor to be deleted. + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} config.monitorId - The ID of the monitor to be deleted. * @returns {Promise} The response from the axios DELETE request. */ - async deleteMonitorById(authToken, monitorId) { - return this.axiosInstance.delete(`/monitors/${monitorId}`, { + async deleteMonitorById(config) { + return this.axiosInstance.delete(`/monitors/${config.monitorId}`, { headers: { - Authorization: `Bearer ${authToken}`, + Authorization: `Bearer ${config.authToken}`, "Content-Type": "application/json", }, }); @@ -253,14 +242,15 @@ class NetworkService { * ************************************ * * @async - * @param {string} authToken - The authorization token to be used in the request header. - * @param {string} monitorId - The ID of the monitor to be deleted. + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} config.teamId - The team ID of the monitors to be deleted. * @returns {Promise} The response from the axios DELETE request. */ - async deleteChecksByTeamId(authToken, teamId) { - return this.axiosInstance.delete(`/checks/team/${teamId}`, { + async deleteChecksByTeamId(config) { + return this.axiosInstance.delete(`/checks/team/${config.teamId}`, { headers: { - Authorization: `Bearer ${authToken}`, + Authorization: `Bearer ${config.authToken}`, "Content-Type": "application/json", }, }); @@ -271,17 +261,18 @@ class NetworkService { * ************************************ * * @async - * @param {string} authToken - The authorization token to be used in the request header. - * @param {string} monitorId - The ID of the monitor to be paused. + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} config.monitorId - The ID of the monitor to be paused. * @returns {Promise} The response from the axios POST request. */ - async pauseMonitorById(authToken, monitorId) { + async pauseMonitorById(config) { return this.axiosInstance.post( - `/monitors/pause/${monitorId}`, + `/monitors/pause/${config.monitorId}`, {}, { headers: { - Authorization: `Bearer ${authToken}`, + Authorization: `Bearer ${config.authToken}`, "Content-Type": "application/json", }, } @@ -294,16 +285,17 @@ class NetworkService { * ************************************ * * @async - * @param {string} authToken - The authorization token to be used in the request header. + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. * @returns {Promise} The response from the axios POST request. */ - async addDemoMonitors(authToken) { + async addDemoMonitors(config) { return this.axiosInstance.post( `/monitors/demo`, {}, { headers: { - Authorization: `Bearer ${authToken}`, + Authorization: `Bearer ${config.authToken}`, "Content-Type": "application/json", }, } @@ -316,13 +308,14 @@ class NetworkService { * ************************************ * * @async - * @param {string} authToken - The authorization token to be used in the request header. + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. * @returns {Promise} The response from the axios DELETE request. */ - async deleteAllMonitors(authToken) { + async deleteAllMonitors(config) { return this.axiosInstance.delete(`/monitors/`, { headers: { - Authorization: `Bearer ${authToken}`, + Authorization: `Bearer ${config.authToken}`, "Content-Type": "application/json", }, }); @@ -334,15 +327,16 @@ class NetworkService { * ************************************ * * @async - * @param {string} authToken - The authorization token to be used in the request header. - * @param {string} monitorId - The ID of the monitor whose certificate expiry is to be retrieved. + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} config.monitorId - The ID of the monitor whose certificate expiry is to be retrieved. * @returns {Promise} The response from the axios GET request. * */ - async getCertificateExpiry(authToken, monitorId) { - return this.axiosInstance.get(`/monitors/certificate/${monitorId}`, { + async getCertificateExpiry(config) { + return this.axiosInstance.get(`/monitors/certificate/${config.monitorId}`, { headers: { - Authorization: `Bearer ${authToken}`, + Authorization: `Bearer ${config.authToken}`, }, }); } @@ -380,23 +374,35 @@ class NetworkService { * ************************************ * * @async - * @param {string} authToken - The authorization token to be used in the request header. - * @param {string} userId - The ID of the user to be updated. - * @param {Object} form - The form data for the user to be updated. + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} config.userId - The ID of the user to be updated. + * @param {Object} config.form - The form data for the user to be updated. * @returns {Promise} The response from the axios PUT request. * */ - async updateUser(authToken, userId, form) { - return this.axiosInstance.put(`/auth/user/${userId}`, form, { + async updateUser(config) { + return this.axiosInstance.put(`/auth/user/${config.userId}`, config.form, { headers: { - Authorization: `Bearer ${authToken}`, + Authorization: `Bearer ${config.authToken}`, }, }); } - async deleteUser(authToken, userId) { - return this.axiosInstance.delete(`/auth/user/${userId}`, { - headers: { Authorization: `Bearer ${authToken}` }, + /** + * ************************************ + * Deletes a user + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} config.userId - The ID of the user to be deleted. + * + **/ + async deleteUser(config) { + return this.axiosInstance.delete(`/auth/user/${config.userId}`, { + headers: { Authorization: `Bearer ${config.authToken}` }, }); } @@ -420,13 +426,14 @@ class NetworkService { * ************************************ * * @async - * @param {string} recoveryToken - The recovery token to be validated. + * @param {Object} config - The configuration object. + * @param {string} config.recoveryToken - The recovery token to be validated. * @returns {Promise} The response from the axios POST request. * */ - async validateRecoveryToken(recoveryToken) { + async validateRecoveryToken(config) { return this.axiosInstance.post("/auth/recovery/validate", { - recoveryToken, + recoveryToken: config.recoveryToken, }); } @@ -436,14 +443,16 @@ class NetworkService { * ************************************ * * @async - * @param {Object} form - The form data for the password recovery request. + * @param {Object} config - The configuration object. + * @param {string} config.recoveryToken - The token for recovery request. + * @param {Object} config.form - The form data for the password recovery request. * @returns {Promise} The response from the axios POST request. * */ - async setNewPassword(recoveryToken, form) { + async setNewPassword(config) { return this.axiosInstance.post("/auth/recovery/reset", { - ...form, - recoveryToken, + ...config.form, + recoveryToken: config.recoveryToken, }); } @@ -466,13 +475,14 @@ class NetworkService { * ************************************ * * @async - * @param {string} authToken - The authorization token to be used in the request header. + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. * @returns {Promise} The response from the axios GET request. * */ - async getAllUsers(authToken) { + async getAllUsers(config) { return this.axiosInstance.get("/auth/users", { - headers: { Authorization: `Bearer ${authToken}` }, + headers: { Authorization: `Bearer ${config.authToken}` }, }); } @@ -482,18 +492,19 @@ class NetworkService { * ************************************ * * @async - * @param {string} authToken - The authorization token to be used in the request header. - * @param {string} email - The email of the user to be invited. - * @param {string} role - The role of the user to be invited. + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} config.email - The email of the user to be invited. + * @param {string} config.role - The role of the user to be invited. * @returns {Promise} The response from the axios POST request. * */ - async requestInvitationToken(authToken, email, role) { + async requestInvitationToken(config) { return this.axiosInstance.post( `/invite`, - { email, role }, + { email: config.email, role: config.role }, { - headers: { Authorization: `Bearer ${authToken}` }, + headers: { Authorization: `Bearer ${config.authToken}` }, } ); } @@ -520,39 +531,34 @@ class NetworkService { * ************************************ * * @async - * @param {string} authToken - The authorization token to be used in the request header. - * @param {string} monitorId - The ID of the monitor. - * @param {string} sortOrder - The order in which to sort the checks. - * @param {number} limit - The maximum number of checks to retrieve. - * @param {string} dateRange - The range of dates for which to retrieve checks. - * @param {string} filter - The filter to apply to the checks. - * @param {number} page - The page number to retrieve in a paginated list. - * @param {number} rowsPerPage - The number of rows per page in a paginated list. + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} config.monitorId - The ID of the monitor. + * @param {string} config.sortOrder - The order in which to sort the checks. + * @param {number} config.limit - The maximum number of checks to retrieve. + * @param {string} config.dateRange - The range of dates for which to retrieve checks. + * @param {string} config.filter - The filter to apply to the checks. + * @param {number} config.page - The page number to retrieve in a paginated list. + * @param {number} config.rowsPerPage - The number of rows per page in a paginated list. * @returns {Promise} The response from the axios GET request. * */ - async getChecksByMonitor( - authToken, - monitorId, - sortOrder, - limit, - dateRange, - filter, - page, - rowsPerPage - ) { + async getChecksByMonitor(config) { const params = new URLSearchParams(); - if (sortOrder) params.append("sortOrder", sortOrder); - if (limit) params.append("limit", limit); - if (dateRange) params.append("dateRange", dateRange); - if (filter) params.append("filter", filter); - if (page) params.append("page", page); - if (rowsPerPage) params.append("rowsPerPage", rowsPerPage); + if (config.sortOrder) params.append("sortOrder", config.sortOrder); + if (config.limit) params.append("limit", config.limit); + if (config.dateRange) params.append("dateRange", config.dateRange); + if (config.filter) params.append("filter", config.filter); + if (config.page) params.append("page", config.page); + if (config.rowsPerPage) params.append("rowsPerPage", config.rowsPerPage); - return this.axiosInstance.get(`/checks/${monitorId}?${params.toString()}`, { - headers: { Authorization: `Bearer ${authToken}` }, - }); + return this.axiosInstance.get( + `/checks/${config.monitorId}?${params.toString()}`, + { + headers: { Authorization: `Bearer ${config.authToken}` }, + } + ); } /** @@ -561,49 +567,53 @@ class NetworkService { * ************************************ * * @async - * @param {string} authToken - The authorization token to be used in the request header. - * @param {string} userId - The ID of the user. - * @param {string} sortOrder - The order in which to sort the checks. - * @param {number} limit - The maximum number of checks to retrieve. - * @param {string} dateRange - The range of dates for which to retrieve checks. - * @param {string} filter - The filter to apply to the checks. - * @param {number} page - The page number to retrieve in a paginated list. - * @param {number} rowsPerPage - The number of rows per page in a paginated list. + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} config.userId - The ID of the user. + * @param {string} config.sortOrder - The order in which to sort the checks. + * @param {number} config.limit - The maximum number of checks to retrieve. + * @param {string} config.dateRange - The range of dates for which to retrieve checks. + * @param {string} config.filter - The filter to apply to the checks. + * @param {number} config.page - The page number to retrieve in a paginated list. + * @param {number} config.rowsPerPage - The number of rows per page in a paginated list. * @returns {Promise} The response from the axios GET request. * */ - async getChecksByTeam( - authToken, - teamId, - sortOrder, - limit, - dateRange, - filter, - page, - rowsPerPage - ) { + async getChecksByTeam(config) { const params = new URLSearchParams(); - if (sortOrder) params.append("sortOrder", sortOrder); - if (limit) params.append("limit", limit); - if (dateRange) params.append("dateRange", dateRange); - if (filter) params.append("filter", filter); - if (page) params.append("page", page); - if (rowsPerPage) params.append("rowsPerPage", rowsPerPage); + if (config.sortOrder) params.append("sortOrder", config.sortOrder); + if (config.limit) params.append("limit", config.limit); + if (config.dateRange) params.append("dateRange", config.dateRange); + if (config.filter) params.append("filter", config.filter); + if (config.page) params.append("page", config.page); + if (config.rowsPerPage) params.append("rowsPerPage", config.rowsPerPage); return this.axiosInstance.get( - `/checks/team/${teamId}?${params.toString()}`, + `/checks/team/${config.teamId}?${params.toString()}`, { - headers: { Authorization: `Bearer ${authToken}` }, + headers: { Authorization: `Bearer ${config.authToken}` }, } ); } - async updateChecksTTL(authToken, ttl) { + /** + * ************************************ + * Get all checks for a given user + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {number} config.ttl - TTL for checks + * @returns {Promise} The response from the axios GET request. + * + */ + async updateChecksTTL(config) { return this.axiosInstance.put( `/checks/ttl`, - { ttl }, + { ttl: config.ttl }, { headers: { - Authorization: `Bearer ${authToken}`, + Authorization: `Bearer ${config.authToken}`, "Content-Type": "application/json", }, } diff --git a/Docker/.gitignore b/Docker/.gitignore index 89aa6bfac..3035bcf7a 100644 --- a/Docker/.gitignore +++ b/Docker/.gitignore @@ -1,4 +1,5 @@ *.sh +!quickstart.sh !build_images.sh mongo/data/* redis/data/* diff --git a/Docker/mongoDB.Dockerfile b/Docker/mongoDB.Dockerfile index 969a320c2..19fc6e358 100644 --- a/Docker/mongoDB.Dockerfile +++ b/Docker/mongoDB.Dockerfile @@ -1,3 +1,4 @@ FROM mongo +COPY ./Docker/mongo/init/create_users.js /docker-entrypoint-initdb.d/ EXPOSE 27017 CMD ["mongod"] diff --git a/Docker/quickstart/docker-compose.yaml b/Docker/quickstart/docker-compose.yaml new file mode 100644 index 000000000..e2c6e0bae --- /dev/null +++ b/Docker/quickstart/docker-compose.yaml @@ -0,0 +1,34 @@ +services: + client: + image: bluewaveuptime/uptime_client:latest + ports: + - "80:80" + - "443:443" + depends_on: + - server + volumes: + - ./nginx/conf.d:/etc/nginx/conf.d/:ro + server: + image: bluewaveuptime/uptime_server:latest + ports: + - "5000:5000" + env_file: + - server.env + depends_on: + - redis + - mongodb + redis: + image: bluewaveuptime/uptime_redis:latest + ports: + - "6379:6379" + volumes: + - ./redis/data:/data + mongodb: + image: bluewaveuptime/uptime_database_mongo:latest + command: ["mongod", "--quiet", "--auth"] + ports: + - "27017:27017" + volumes: + - ./mongo/data:/data/db + env_file: + - mongo.env diff --git a/Docker/quickstart/quickstart.sh b/Docker/quickstart/quickstart.sh new file mode 100755 index 000000000..b460daaad --- /dev/null +++ b/Docker/quickstart/quickstart.sh @@ -0,0 +1,123 @@ +default_server_base_url="http://localhost:5000/api/v1" +default_client_host="http://localhost" +default_jwt_secret="my_secret" +default_db_type="MongoDB" +default_redis_host="redis" +default_redis_port=6379 +default_token_ttl="99d" + +default_db_username="uptime_user" +default_db_password="uptime_password" + +default_system_email_host="smtp.gmail.com" +default_system_email_port=465 + + +echo "Welcome to the Uptime Monitor Setup Script! \n" +echo + +echo "Configuring server" +printf '%*s\n' "${COLUMNS:-$(tput cols)}" '' | tr ' ' '*' +echo + +db_type=$default_db_type +redis_host=$default_redis_host +redis_port=$default_redis_port +jwt_secret=$default_jwt_secret + + +read -p "Enter a username for your database [$default_db_username]: " db_username +db_username="${db_username:-$default_db_username}" + +read -p "Enter a password for your database [$default_db_password]: " db_password +db_password="${db_password:-$default_db_password}" + +read -p "Enter your system email host [$default_system_email_host]: " system_email_host +system_email_host="${system_email_host:-$default_system_email_host}" +echo "System email host: $system_email_host" +echo + +read -p "Enter your DB connection string [$default_db_connection_string]: " db_connection_string +db_connection_string="mongodb://${db_username}:${db_password}@mongodb:27017/uptime_db" +echo "DB connection string: $db_connection_string" +echo + +read -p "Enter your system email port [$default_system_email_port]: " system_email_port +system_email_port="${system_email_port:-$default_system_email_port}" +echo "System email port: $system_email_port" +echo + + +read -p "Enter your system email address: " system_email_address +echo "System email address: $system_email_address" +echo + +read -p "Enter your system email password: " system_email_password +echo "System email password: $system_email_password" +echo + +read -p "Enter your Token TTL [$default_token_ttl]: " token_ttl +token_ttl="${token_ttl:-$default_token_ttl}" +echo "Token TTL: $token_ttl" +echo + +read -p "Enter your Pagespeed API key: " pagespeed_api_key +echo "Pagespeed API key: $pagespeed_api_key" +echo + +echo "Writing to ./server.env" +echo + +{ + echo "CLIENT_HOST=\"$client_host\"" + echo "JWT_SECRET=\"$jwt_secret\"" + echo "DB_TYPE=\"$db_type\"" + echo "DB_CONNECTION_STRING=\"$db_connection_string\"" + echo "REDIS_HOST=\"$redis_host\"" + echo "REDIS_PORT=$redis_port" + echo "SYSTEM_EMAIL_HOST=\"$system_email_host\"" + echo "SYSTEM_EMAIL_PORT=$system_email_port" + echo "SYSTEM_EMAIL_ADDRESS=\"$system_email_address\"" + echo "SYSTEM_EMAIL_PASSWORD=\"$system_email_password\"" + echo "TOKEN_TTL=\"$token_ttl\"" + echo "PAGESPEED_API_KEY=\"$pagespeed_api_key\"" +} > ./server.env + + +echo +{ + echo USERNAME_ENV_VAR=${db_username} + echo PASSWORD_ENV_VAR=${db_password} +} > ./mongo.env + +mkdir -p ./nginx/conf.d/ + + +cat < ./nginx/conf.d/default.conf +server { + listen 80; + listen [::]:80; + + server_name uptime-demo.bluewavelabs.ca; + server_tokens off; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files \$uri \$uri/ /index.html; + } + + location /api/ { + proxy_pass http://server:5000/api/; + proxy_http_version 1.1; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto \$scheme; + } +} +EOL \ No newline at end of file diff --git a/README.md b/README.md index 92ee37e35..3c96a0af4 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,10 @@ BlueWave Uptime is an open source server monitoring application used to track the operational status and performance of servers and websites. It regularly checks whether a server/website is accessible and performs optimally, providing real-time alerts and reports on the monitored services' availability, downtime, and response time. +## Demo + +See [BlueWave Uptime](https://uptime-demo.bluewavelabs.ca/) in action. The username is uptimedemo@demo.com and the password is Demouser1! + ## Features - [x] Completely open source, deployable on your servers @@ -74,6 +78,7 @@ Also check other developer and contributor-friendly projects of BlueWave: - Clone this repository to your local machine +1. [Quickstart for Users](#user-quickstart) 1. [Quickstart for Developers](#dev-quickstart) 1. [Docker Compose](#docker-compose) 1. [Installation (Client)](#client) @@ -129,6 +134,15 @@ Also check other developer and contributor-friendly projects of BlueWave: --- +#### Quickstart for Users + +1. Download our [Docker Compose File](/Docker/quickstart/docker-compose.yaml) +2. Download our [Quickstart script](/Docker/quickstart/quickstart.sh) +3. Place these files in a directory of your choice +4. Run `quickstart.sh` and generate config files +5. Run `docker compose up` to start the application +6. Application is running at `http://localhost` + #### Quickstart for Developers MAKE SURE YOU CD TO THE SPECIFIED DIRECTORIES AS PATHS IN COMMANDS ARE RELATIVE diff --git a/Server/routes/inviteRoute.js b/Server/routes/inviteRoute.js index 54e74b0fa..17744bf82 100644 --- a/Server/routes/inviteRoute.js +++ b/Server/routes/inviteRoute.js @@ -13,10 +13,6 @@ router.post( verifyJWT, inviteController ); -router.post( - "/verify", - isAllowed(["admin", "superadmin"]), - inviteVerifyController -); +router.post("/verify", inviteVerifyController); module.exports = router;