From 9cfbf6addea3e274864a6219ae047d6705cc3580 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 12 May 2025 11:36:54 -0700 Subject: [PATCH 1/6] conditionally render pages, add test email button --- .../Pages/Settings/SettingsDemoMonitors.jsx | 8 ++++++- client/src/Pages/Settings/SettingsEmail.jsx | 24 +++++++++++++++++++ .../src/Pages/Settings/SettingsPagespeed.jsx | 6 +++++ client/src/Pages/Settings/SettingsStats.jsx | 8 ++++++- client/src/Pages/Settings/index.jsx | 7 ++++++ 5 files changed, 51 insertions(+), 2 deletions(-) diff --git a/client/src/Pages/Settings/SettingsDemoMonitors.jsx b/client/src/Pages/Settings/SettingsDemoMonitors.jsx index 1a592e776..3a288f558 100644 --- a/client/src/Pages/Settings/SettingsDemoMonitors.jsx +++ b/client/src/Pages/Settings/SettingsDemoMonitors.jsx @@ -9,11 +9,16 @@ import { useTranslation } from "react-i18next"; import Dialog from "../../Components/Dialog"; import { useState } from "react"; -const SettingsDemoMonitors = ({ HEADER_SX, handleChange, isLoading }) => { +const SettingsDemoMonitors = ({ isAdmin, HEADER_SX, handleChange, isLoading }) => { const { t } = useTranslation(); const theme = useTheme(); // Local state const [isOpen, setIsOpen] = useState(false); + + if (!isAdmin) { + return null; + } + return ( <> @@ -83,6 +88,7 @@ const SettingsDemoMonitors = ({ HEADER_SX, handleChange, isLoading }) => { }; SettingsDemoMonitors.propTypes = { + isAdmin: PropTypes.bool, handleChange: PropTypes.func, HEADER_SX: PropTypes.object, }; diff --git a/client/src/Pages/Settings/SettingsEmail.jsx b/client/src/Pages/Settings/SettingsEmail.jsx index ecc2a51f2..4bcca5f72 100644 --- a/client/src/Pages/Settings/SettingsEmail.jsx +++ b/client/src/Pages/Settings/SettingsEmail.jsx @@ -10,19 +10,28 @@ import { PropTypes } from "prop-types"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments"; +import { useSendTestEmail } from "../../Hooks/useSendTestEmail"; + const SettingsEmail = ({ + isAdmin, HEADER_SX, handleChange, settingsData, setSettingsData, isPasswordSet, }) => { + // Setup const { t } = useTranslation(); const theme = useTheme(); + // Local state const [password, setPassword] = useState(""); const [hasBeenReset, setHasBeenReset] = useState(false); + // Network + const [isSending, error, sendTestEmail] = useSendTestEmail(); + + // Handlers const handlePasswordChange = (e) => { setPassword(e.target.value); setSettingsData({ @@ -31,6 +40,10 @@ const SettingsEmail = ({ }); }; + if (!isAdmin) { + return null; + } + return ( @@ -109,6 +122,16 @@ const SettingsEmail = ({ )} + + + @@ -116,6 +139,7 @@ const SettingsEmail = ({ }; SettingsEmail.propTypes = { + isAdmin: PropTypes.bool, settingsData: PropTypes.object, setSettingsData: PropTypes.func, handleChange: PropTypes.func, diff --git a/client/src/Pages/Settings/SettingsPagespeed.jsx b/client/src/Pages/Settings/SettingsPagespeed.jsx index 95b159fa5..c3636d0a1 100644 --- a/client/src/Pages/Settings/SettingsPagespeed.jsx +++ b/client/src/Pages/Settings/SettingsPagespeed.jsx @@ -12,6 +12,7 @@ import { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; const SettingsPagespeed = ({ + isAdmin, HEADING_SX, settingsData, setSettingsData, @@ -33,6 +34,10 @@ const SettingsPagespeed = ({ }); }; + if (!isAdmin) { + return null; + } + return ( @@ -78,6 +83,7 @@ const SettingsPagespeed = ({ }; SettingsPagespeed.propTypes = { + isAdmin: PropTypes.bool, HEADING_SX: PropTypes.object, settingsData: PropTypes.object, setSettingsData: PropTypes.func, diff --git a/client/src/Pages/Settings/SettingsStats.jsx b/client/src/Pages/Settings/SettingsStats.jsx index 961f13665..dd4fa11c0 100644 --- a/client/src/Pages/Settings/SettingsStats.jsx +++ b/client/src/Pages/Settings/SettingsStats.jsx @@ -12,10 +12,15 @@ import { PropTypes } from "prop-types"; import { useTranslation } from "react-i18next"; import { useState } from "react"; -const SettingsStats = ({ HEADING_SX, handleChange, settingsData, errors }) => { +const SettingsStats = ({ isAdmin, HEADING_SX, handleChange, settingsData, errors }) => { const theme = useTheme(); const { t } = useTranslation(); const [isOpen, setIsOpen] = useState(false); + + if (!isAdmin) { + return null; + } + return ( @@ -75,6 +80,7 @@ const SettingsStats = ({ HEADING_SX, handleChange, settingsData, errors }) => { }; SettingsStats.propTypes = { + isAdmin: PropTypes.bool, HEADING_SX: PropTypes.object, handleChange: PropTypes.func, settingsData: PropTypes.object, diff --git a/client/src/Pages/Settings/index.jsx b/client/src/Pages/Settings/index.jsx index eb516ba01..66662dc58 100644 --- a/client/src/Pages/Settings/index.jsx +++ b/client/src/Pages/Settings/index.jsx @@ -24,6 +24,7 @@ import { } from "../../Features/UptimeMonitors/uptimeMonitorsSlice"; import { useFetchSettings, useSaveSettings } from "../../Hooks/useFetchSettings"; import { UseDeleteMonitorStats } from "../../Hooks/useDeleteMonitorStats"; +import { useIsAdmin } from "../../Hooks/useIsAdmin"; // Constants const BREADCRUMBS = [{ name: `Settings`, path: "/settings" }]; @@ -36,6 +37,7 @@ const Settings = () => { // Local state const [settingsData, setSettingsData] = useState({}); const [errors, setErrors] = useState({}); + // Network const [isSettingsLoading, settingsError] = useFetchSettings({ setSettingsData, @@ -45,6 +47,7 @@ const Settings = () => { const [deleteMonitorStats, isDeletingMonitorStats] = UseDeleteMonitorStats(); // Setup + const isAdmin = useIsAdmin(); const theme = useTheme(); const HEADING_SX = { mt: theme.spacing(2), mb: theme.spacing(2) }; const { t, i18n } = useTranslation(); @@ -155,23 +158,27 @@ const Settings = () => { language={language} /> Date: Mon, 12 May 2025 11:37:15 -0700 Subject: [PATCH 2/6] add method for sending test emails --- client/src/Utils/NetworkService.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client/src/Utils/NetworkService.js b/client/src/Utils/NetworkService.js index d6447a198..4adf8ff8b 100644 --- a/client/src/Utils/NetworkService.js +++ b/client/src/Utils/NetworkService.js @@ -1152,6 +1152,14 @@ class NetworkService { } ); } + + // ************************************ + // Send tes t email + // ************************************ + async sendTestEmail(config) { + const { to } = config; + return this.axiosInstance.post(`/monitors/test-email`, { to }); + } } export default NetworkService; From 880f57a3083ce0798f2290a26c6e786b55e7a30d Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 12 May 2025 11:37:28 -0700 Subject: [PATCH 3/6] Add hook for sending test emails --- client/src/Hooks/useSendTestEmail.js | 33 ++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 client/src/Hooks/useSendTestEmail.js diff --git a/client/src/Hooks/useSendTestEmail.js b/client/src/Hooks/useSendTestEmail.js new file mode 100644 index 000000000..fef45c3ea --- /dev/null +++ b/client/src/Hooks/useSendTestEmail.js @@ -0,0 +1,33 @@ +import { useState } from "react"; +import { networkService } from "../main"; +import { useSelector } from "react-redux"; +import { createToast } from "../Utils/toastUtils"; + +const useSendTestEmail = () => { + const [isSending, setIsSending] = useState(false); + const [error, setError] = useState(null); + const user = useSelector((state) => state.auth.user); + + const sendTestEmail = async () => { + try { + setIsSending(true); + setError(null); + + const response = await networkService.sendTestEmail({ to: user.email }); + if (typeof response?.data?.data?.messageId !== "undefined") { + createToast({ + body: "Test email sent successfully", + }); + } else { + throw new Error("Failed to send test email"); + } + } catch (error) { + setError(error); + } finally { + setIsSending(false); + } + }; + return [isSending, error, sendTestEmail]; +}; + +export { useSendTestEmail }; From ea64316c55c12ca95ffe6b4f6e9d9a7fe2f9d67f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 12 May 2025 11:37:59 -0700 Subject: [PATCH 4/6] add missing FROM field --- server/service/emailService.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server/service/emailService.js b/server/service/emailService.js index 57f8c78e6..94d19f0f4 100755 --- a/server/service/emailService.js +++ b/server/service/emailService.js @@ -130,6 +130,7 @@ class EmailService { try { const info = await this.transporter.sendMail({ to: to, + from: systemEmailAddress, subject: subject, html: html, }); From 395cc5db77dc677ac4d622e2bec69aaa5428c1b5 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 12 May 2025 11:38:51 -0700 Subject: [PATCH 5/6] Add missing string getter --- server/controllers/monitorController.js | 20 +++++++++++--------- server/locales/en.json | 4 ++-- server/service/stringService.js | 4 ++++ 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/server/controllers/monitorController.js b/server/controllers/monitorController.js index 53abc7dff..d537e8cee 100755 --- a/server/controllers/monitorController.js +++ b/server/controllers/monitorController.js @@ -582,16 +582,18 @@ class MonitorController { } try { - const monitor = await Monitor.findOneAndUpdate({ _id: req.params.monitorId }, [ - { - $set: { - isActive: { $not: "$isActive" }, - status: "$$REMOVE", + const monitor = await Monitor.findOneAndUpdate( + { _id: req.params.monitorId }, + [ + { + $set: { + isActive: { $not: "$isActive" }, + status: "$$REMOVE", + }, }, - }, - ], - { new: true } - ); + ], + { new: true } + ); monitor.isActive === true ? await this.jobQueue.deleteJob(monitor) : await this.jobQueue.addJob(monitor._id, monitor); diff --git a/server/locales/en.json b/server/locales/en.json index 6cd956c37..a693c6eef 100755 --- a/server/locales/en.json +++ b/server/locales/en.json @@ -158,8 +158,8 @@ "platformRequired": "Platform is required", "testNotificationFailed": "Failed to send test notification", "monitorUpAlert": "Uptime Alert: One of your monitors is back online.\nšŸ“Œ Monitor: {monitorName}\nšŸ“… Time: {time}\nāš ļø Status: UP\nšŸ“Ÿ Status Code: {code}\n\u200B\n", - "monitorDownAlert": "Downtime Alert: One of your monitors went offline.\nšŸ“Œ Monitor: {monitorName}\nšŸ“… Time: {time}\nāš ļø Status: DOWN\nšŸ“Ÿ Status Code: {code}\n\u200B\n", + "monitorDownAlert": "Downtime Alert: One of your monitors went offline.\nšŸ“Œ Monitor: {monitorName}\nšŸ“… Time: {time}\nāš ļø Status: DOWN\nšŸ“Ÿ Status Code: {code}\n\u200B\n", "sendTestEmail": "Test email sent successfully", "errorForValidEmailAddress": "A valid recipient email address is required.", - "testEmailSubject": "Test Email from Monitoring System" + "testEmailSubject": "Test E-mail from Checkmate" } diff --git a/server/service/stringService.js b/server/service/stringService.js index ef99eb82b..b02254255 100755 --- a/server/service/stringService.js +++ b/server/service/stringService.js @@ -428,6 +428,10 @@ class StringService { get dbUserExists() { return this.translationService.getTranslation("dbUserExists"); } + + get testEmailSubject() { + return this.translationService.getTranslation("testEmailSubject"); + } } export default StringService; From e46d94aac32c1622728c0f722c978cb5d8ca0058 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 12 May 2025 11:43:48 -0700 Subject: [PATCH 6/6] fix case --- client/src/Pages/Settings/SettingsEmail.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/Pages/Settings/SettingsEmail.jsx b/client/src/Pages/Settings/SettingsEmail.jsx index 4bcca5f72..694219614 100644 --- a/client/src/Pages/Settings/SettingsEmail.jsx +++ b/client/src/Pages/Settings/SettingsEmail.jsx @@ -129,7 +129,7 @@ const SettingsEmail = ({ loading={isSending} onClick={sendTestEmail} > - Send Test E-mail + Send test e-mail