diff --git a/README.md b/README.md index a57f9b270..c9c9073ad 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Checkmate also has an agent, called [Capture](https://github.com/bluewave-labs/c Checkmate has been stress-tested with 1000+ active monitors without any particular issues or performance bottlenecks. -We **love** what we are building here, and we continuously learn a few things about Reactjs, Nodejs, MongoDB, and Docker while building Checkmate. +**If you would like to sponsor a feature, [see this link](https://checkmate.so/sponsored-features).** ## 📚 Table of contents @@ -130,7 +130,7 @@ If you have any questions, suggestions or comments, please use our [Discord chan We are [Alex](http://github.com/ajhollid) (team lead), [Vishnu](http://github.com/vishnusn77), [Mohadeseh](http://github.com/mohicody), [Gorkem](http://github.com/gorkem-bwl/), [Owaise](http://github.com/Owaiseimdad), [Aryaman](https://github.com/Br0wnHammer) and [Mert](https://github.com/mertssmnoglu) helping individuals and businesses monitor their infra and servers. -We pride ourselves on building strong connections with contributors at every level. Despite being a young project, Checkmate has already earned 5800+ stars and attracted 70+ contributors from around the globe. +We pride ourselves on building strong connections with contributors at every level. Despite being a young project, Checkmate has already earned 6000+ stars and attracted 80+ contributors from around the globe. Our repo is starred by employees from **Google, Microsoft, Intel, Cisco, Tencent, Electronic Arts, ByteDance, JP Morgan Chase, Deloitte, Accenture, Foxconn, Broadcom, China Telecom, Barclays, Capgemini, Wipro, Cloudflare, Dassault Systèmes and NEC**, so don’t hold back — jump in, contribute and learn with us! @@ -148,11 +148,13 @@ Here's how you can contribute: +[![Star History Chart](https://api.star-history.com/svg?repos=bluewave-labs/checkmate&type=Date)](https://star-history.com/#bluewave-labs/bluewave-uptime&Date) + ## 💰 Our sponsors Thanks to [Gitbook](https://gitbook.io/) for giving us a free tier for their documentation platform, and [Poeditor](https://poeditor.com/) providing us a free account to use their i18n services. If you would like to sponsor Checkmate, please send an email to hello@bluewavelabs.ca -[![Star History Chart](https://api.star-history.com/svg?repos=bluewave-labs/checkmate&type=Date)](https://star-history.com/#bluewave-labs/bluewave-uptime&Date) +If you would like to sponsor a feature, [see this page](https://checkmate.so/sponsored-features). Also check other developer and contributor-friendly projects of BlueWave: diff --git a/client/src/Components/Breadcrumbs/index.jsx b/client/src/Components/Breadcrumbs/index.jsx index 0ebd8dab1..bf04afa2e 100644 --- a/client/src/Components/Breadcrumbs/index.jsx +++ b/client/src/Components/Breadcrumbs/index.jsx @@ -1,7 +1,7 @@ import PropTypes from "prop-types"; import { Box, Breadcrumbs as MUIBreadcrumbs } from "@mui/material"; import { useTheme } from "@emotion/react"; -import { useNavigate } from "react-router"; +import { useNavigate } from "react-router-dom"; import ArrowRight from "../../assets/icons/right-arrow.svg?react"; import "./index.css"; diff --git a/client/src/Components/Fallback/index.jsx b/client/src/Components/Fallback/index.jsx index 47497ba02..b77432d58 100644 --- a/client/src/Components/Fallback/index.jsx +++ b/client/src/Components/Fallback/index.jsx @@ -39,7 +39,11 @@ const Fallback = ({ const { t } = useTranslation(); const [settingsData, setSettingsData] = useState(undefined); - const [isLoading, error] = useFetchSettings({ setSettingsData }); + const [isLoading, error] = useFetchSettings({ + setSettingsData, + setIsApiKeySet: () => {}, + setIsEmailPasswordSet: () => {}, + }); // Custom warning message with clickable link const renderWarningMessage = () => { return ( diff --git a/client/src/Components/Inputs/Radio/index.jsx b/client/src/Components/Inputs/Radio/index.jsx index 8c1c06b38..646b38e93 100644 --- a/client/src/Components/Inputs/Radio/index.jsx +++ b/client/src/Components/Inputs/Radio/index.jsx @@ -16,26 +16,27 @@ import "./index.css"; * size="small" * /> * - * @param {Object} props - The component props. - * @param {string} props.id - The id of the radio button. - * @param {string} props.title - The title of the radio button. - * @param {string} [props.desc] - The description of the radio button. - * @param {string} [props.size="small"] - The size of the radio button. + * @param {Object} props - The component + * @param {string} id - The id of the radio button. + * @param {string} title - The title of the radio button. + * @param {string} [desc] - The description of the radio button. + * @param {string} [size="small"] - The size of the radio button. * @returns {JSX.Element} - The rendered Radio component. */ -const Radio = (props) => { +const Radio = ({ name, checked, value, id, size, title, desc, onChange }) => { const theme = useTheme(); return ( } sx={{ color: "transparent", @@ -49,16 +50,16 @@ const Radio = (props) => { }} /> } - onChange={props.onChange} + onChange={onChange} label={ <> - {props.title} + {title} - {props.desc} + {desc} } @@ -81,9 +82,14 @@ const Radio = (props) => { }; Radio.propTypes = { - title: PropTypes.string.isRequired, + title: PropTypes.string, desc: PropTypes.string, size: PropTypes.string, + name: PropTypes.string, + checked: PropTypes.bool, + value: PropTypes.string, + id: PropTypes.string, + onChange: PropTypes.func, }; export default Radio; diff --git a/client/src/Hooks/settingsHooks.js b/client/src/Hooks/settingsHooks.js index 7d3ff28fc..98a21c1fa 100644 --- a/client/src/Hooks/settingsHooks.js +++ b/client/src/Hooks/settingsHooks.js @@ -3,7 +3,7 @@ import { networkService } from "../main"; import { createToast } from "../Utils/toastUtils"; import { useTranslation } from "react-i18next"; -const useFetchSettings = ({ setSettingsData }) => { +const useFetchSettings = ({ setSettingsData, setIsApiKeySet, setIsEmailPasswordSet }) => { const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(undefined); useEffect(() => { @@ -12,6 +12,8 @@ const useFetchSettings = ({ setSettingsData }) => { try { const response = await networkService.getAppSettings(); setSettingsData(response?.data?.data); + setIsApiKeySet(response?.data?.data?.pagespeedKeySet); + setIsEmailPasswordSet(response?.data?.data?.emailPasswordSet); } catch (error) { createToast({ body: "Failed to fetch settings" }); setError(error); @@ -20,12 +22,18 @@ const useFetchSettings = ({ setSettingsData }) => { } }; fetchSettings(); - }, []); + }, [setSettingsData]); return [isLoading, error]; }; -const useSaveSettings = () => { +const useSaveSettings = ({ + setSettingsData, + setIsApiKeySet, + setApiKeyHasBeenReset, + setIsEmailPasswordSet, + setEmailPasswordHasBeenReset, +}) => { const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(undefined); const { t } = useTranslation(); @@ -39,7 +47,15 @@ const useSaveSettings = () => { ttl: settings.checkTTL, }); } - console.log({ settingsResponse }); + setIsApiKeySet(settingsResponse.data.data.pagespeedKeySet); + setIsEmailPasswordSet(settingsResponse.data.data.emailPasswordSet); + if (settingsResponse.data.data.pagespeedKeySet === true) { + setApiKeyHasBeenReset(false); + } + if (settingsResponse.data.data.emailPasswordSet === true) { + setEmailPasswordHasBeenReset(false); + } + setSettingsData(settingsResponse.data.data); createToast({ body: t("settingsSuccessSaved") }); } catch (error) { createToast({ body: t("settingsFailedToSave") }); diff --git a/client/src/Pages/Account/index.jsx b/client/src/Pages/Account/index.jsx index 9ba57eb06..d8672f51c 100644 --- a/client/src/Pages/Account/index.jsx +++ b/client/src/Pages/Account/index.jsx @@ -1,6 +1,6 @@ import { useState } from "react"; import PropTypes from "prop-types"; -import { useNavigate } from "react-router"; +import { useNavigate } from "react-router-dom"; import { useSelector } from "react-redux"; import { Box, Tab, useTheme } from "@mui/material"; import CustomTabList from "../../Components/Tab"; diff --git a/client/src/Pages/Auth/CheckEmail.jsx b/client/src/Pages/Auth/CheckEmail.jsx index d85fa0c2f..e1f5e43ed 100644 --- a/client/src/Pages/Auth/CheckEmail.jsx +++ b/client/src/Pages/Auth/CheckEmail.jsx @@ -2,7 +2,7 @@ import { Box, Button, Stack, Typography } from "@mui/material"; import { useEffect, useState } from "react"; import { useTheme } from "@emotion/react"; import { useDispatch } from "react-redux"; -import { useNavigate } from "react-router"; +import { useNavigate } from "react-router-dom"; import { createToast } from "../../Utils/toastUtils"; import { forgotPassword } from "../../Features/Auth/authSlice"; import { Trans, useTranslation } from "react-i18next"; diff --git a/client/src/Pages/Auth/NewPasswordConfirmed.jsx b/client/src/Pages/Auth/NewPasswordConfirmed.jsx index 945a0d52d..7ace77abc 100644 --- a/client/src/Pages/Auth/NewPasswordConfirmed.jsx +++ b/client/src/Pages/Auth/NewPasswordConfirmed.jsx @@ -1,6 +1,6 @@ import { Box, Button, Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; -import { useNavigate } from "react-router"; +import { useNavigate } from "react-router-dom"; import { useDispatch } from "react-redux"; import { clearAuthState } from "../../Features/Auth/authSlice"; import Background from "../../assets/Images/background-grid.svg?react"; diff --git a/client/src/Pages/NotFound/index.jsx b/client/src/Pages/NotFound/index.jsx index f9dfe8ec1..fdae92cb5 100644 --- a/client/src/Pages/NotFound/index.jsx +++ b/client/src/Pages/NotFound/index.jsx @@ -1,8 +1,7 @@ -import React from "react"; import PropTypes from "prop-types"; import NotFoundSvg from "../../../src/assets/Images/sushi_404.svg"; import { Button, Stack, Typography } from "@mui/material"; -import { useNavigate } from "react-router"; +import { useNavigate } from "react-router-dom"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/Notifications/create/index.jsx b/client/src/Pages/Notifications/create/index.jsx index 3c17c113c..2fd83e4e1 100644 --- a/client/src/Pages/Notifications/create/index.jsx +++ b/client/src/Pages/Notifications/create/index.jsx @@ -10,7 +10,6 @@ import TextInput from "../../../Components/Inputs/TextInput"; // Utils import { useState } from "react"; -import { useSelector } from "react-redux"; import { useTheme } from "@emotion/react"; import { useCreateNotification, @@ -44,7 +43,6 @@ const CreateNotifications = () => { ]; // Redux state - const { user } = useSelector((state) => state.auth); // local state const [notification, setNotification] = useState({ @@ -73,7 +71,7 @@ const CreateNotifications = () => { type: NOTIFICATION_TYPES.find((type) => type._id === notification.type).value, }; - if (notification.type === 2) { + if (form.type === "slack" || form.type === "discord") { form.type = "webhook"; } @@ -128,27 +126,33 @@ const CreateNotifications = () => { // Handle config/platform initialization if type is webhook - if (newNotification.type === 1) { + const type = NOTIFICATION_TYPES.find( + (type) => type._id === newNotification.type + ).value; + + if (type === "email") { newNotification.config = null; - } else if (newNotification.type === 2) { + } else if (type === "slack" || type === "webhook" || type === "discord") { newNotification.address = ""; newNotification.config = newNotification.config || {}; if (name === "config") { newNotification.config = value; } - newNotification.config.platform = "slack"; - } else if (newNotification.type === 3) { + if (type === "webhook") { + newNotification.config.platform = "webhook"; + } + if (type === "slack") { + newNotification.config.platform = "slack"; + } + if (type === "discord") { + newNotification.config.platform = "discord"; + } + } else if (type === "pager_duty") { newNotification.config = newNotification.config || {}; if (name === "config") { newNotification.config = value; } newNotification.config.platform = "pager_duty"; - } else if (newNotification.type === 4) { - newNotification.config = newNotification.config || {}; - if (name === "config") { - newNotification.config = value; - } - newNotification.config.platform = "webhook"; } // Field-level validation @@ -159,12 +163,15 @@ const CreateNotifications = () => { fieldError = error?.message; } - if (newNotification.type === 1 && name === "address") { + if (type === "email" && name === "address") { const { error } = notificationEmailValidation.extract(name).validate(value); fieldError = error?.message; } - if (newNotification.type === 2 && name === "config") { + if ( + (type === "slack" || type === "webhook" || type === "discord") && + name === "config" + ) { // Validate only webhookUrl inside config const { error } = notificationWebhookValidation.extract("config").validate(value); fieldError = error?.message; @@ -185,7 +192,7 @@ const CreateNotifications = () => { type: NOTIFICATION_TYPES.find((type) => type._id === notification.type).value, }; - if (notification.type === 2) { + if (form.type === "slack" || form.type === "discord") { form.type = "webhook"; } @@ -405,6 +412,39 @@ const CreateNotifications = () => { )} + {notification.type === 5 && ( + + + + {t("createNotifications.discordSettings.title")} + + + {t("createNotifications.discordSettings.description")} + + + + { + const updatedConfig = { + ...notification.config, + webhookUrl: e.target.value, + }; + + onChange({ + target: { + name: "config", + value: updatedConfig, + }, + }); + }} + /> + + + )} { - const { time: uptimeDuration, units: uptimeUnits } = getHumanReadableDuration( - monitor?.uptimeDuration - ); - - const { time: lastCheckTime, units: lastCheckUnits } = getHumanReadableDuration( - monitor?.lastChecked - ); + const uptimeDuration = getHumanReadableDuration(monitor?.uptimeDuration); + const time = getHumanReadableDuration(monitor?.lastChecked); const { t } = useTranslation(); @@ -22,7 +17,6 @@ const PageSpeedStatusBoxes = ({ shouldRender, monitor }) => { subHeading={ <> {uptimeDuration} - {uptimeUnits} {t("ago")} } @@ -31,8 +25,7 @@ const PageSpeedStatusBoxes = ({ shouldRender, monitor }) => { heading="last check" subHeading={ <> - {lastCheckTime} - {lastCheckUnits} + {time} {t("ago")} } diff --git a/client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx b/client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx index 2220a4b16..94d6b137b 100644 --- a/client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx +++ b/client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx @@ -3,7 +3,7 @@ import PropTypes from "prop-types"; import PageSpeedIcon from "../../../../../assets/icons/page-speed.svg?react"; import { StatusLabel } from "../../../../../Components/Label"; import { Box, Grid, Stack, Typography } from "@mui/material"; -import { useNavigate } from "react-router"; +import { useNavigate } from "react-router-dom"; import { useTheme } from "@emotion/react"; import { Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip } from "recharts"; import { useSelector } from "react-redux"; diff --git a/client/src/Pages/ServerUnreachable.jsx b/client/src/Pages/ServerUnreachable.jsx index 7ab96dd96..466633144 100644 --- a/client/src/Pages/ServerUnreachable.jsx +++ b/client/src/Pages/ServerUnreachable.jsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import { Box, Typography, Button, Stack } from "@mui/material"; import { useTheme } from "@emotion/react"; -import { useNavigate } from "react-router"; +import { useNavigate } from "react-router-dom"; import { networkService } from "../Utils/NetworkService"; import Alert from "../Components/Alert"; import { createToast } from "../Utils/toastUtils"; diff --git a/client/src/Pages/Settings/SettingsEmail.jsx b/client/src/Pages/Settings/SettingsEmail.jsx index e7ae88154..4bd6ff3ad 100644 --- a/client/src/Pages/Settings/SettingsEmail.jsx +++ b/client/src/Pages/Settings/SettingsEmail.jsx @@ -7,7 +7,7 @@ import Stack from "@mui/material/Stack"; // Utils import { useTheme } from "@emotion/react"; import { PropTypes } from "prop-types"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments"; import { useSendTestEmail } from "../../Hooks/useSendTestEmail"; @@ -20,7 +20,9 @@ const SettingsEmail = ({ handleChange, settingsData, setSettingsData, - isPasswordSet, + isEmailPasswordSet, + emailPasswordHasBeenReset, + setEmailPasswordHasBeenReset, }) => { // Setup const { t } = useTranslation(); @@ -43,7 +45,6 @@ const SettingsEmail = ({ } = settingsData?.settings || {}; // Local state const [password, setPassword] = useState(""); - const [hasBeenReset, setHasBeenReset] = useState(false); // Network const [isSending, , sendTestEmail] = useSendTestEmail(); // Using empty placeholder for unused error variable @@ -152,7 +153,7 @@ const SettingsEmail = ({ onChange={handleChange} /> - {(isPasswordSet === false || hasBeenReset === true) && ( + {(isEmailPasswordSet === false || emailPasswordHasBeenReset === true) && ( {t("settingsEmailPassword")} )} - {isPasswordSet === true && hasBeenReset === false && ( + + {isEmailPasswordSet === true && emailPasswordHasBeenReset === false && ( {t("settingsEmailFieldResetLabel")}