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:
+[](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
-[](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")}
- {(isApiKeySet === false || hasBeenReset === true) && (
+ {(isApiKeySet === false || apiKeyHasBeenReset === true) && (
)}
- {isApiKeySet === true && hasBeenReset === false && (
+ {isApiKeySet === true && apiKeyHasBeenReset === false && (
{t("pageSpeedApiKeyFieldResetLabel")}