From b64cf3f6b6eadaefdb3bd725fbed92cb6d65d4dc Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 19 Feb 2026 19:23:40 +0000 Subject: [PATCH] email --- client/src/Pages/Settings/index.tsx | 340 +++++++++++++++++++++++++++- client/src/locales/en.json | 53 ++++- 2 files changed, 390 insertions(+), 3 deletions(-) diff --git a/client/src/Pages/Settings/index.tsx b/client/src/Pages/Settings/index.tsx index d956fbe82..d1b6df976 100644 --- a/client/src/Pages/Settings/index.tsx +++ b/client/src/Pages/Settings/index.tsx @@ -1,5 +1,5 @@ import { BasePage, ConfigBox } from "@/Components/v2/design-elements"; -import { Autocomplete, Select, Dialog } from "@/Components/v2/inputs"; +import { Autocomplete, Select, Dialog, SwitchComponent } from "@/Components/v2/inputs"; import { Stack, useTheme, MenuItem, type SelectChangeEvent } from "@mui/material"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; @@ -14,7 +14,7 @@ import type { SettingsFormData } from "@/Validation/settings"; import { useState } from "react"; import { Controller } from "react-hook-form"; import { TextField, Button, FieldLabel } from "@/Components/v2/inputs"; -import { Box } from "@mui/material"; +import { Box, Typography } from "@mui/material"; import { useDelete } from "@/Hooks/UseApi"; import { @@ -59,6 +59,13 @@ export const SettingsPage = () => { fetchedSettings?.pagespeedKeySet ?? false ); const [apiKeyHasBeenReset, setApiKeyHasBeenReset] = useState(false); + // Local state for email password reset + const [isEmailPasswordSet, setIsEmailPasswordSet] = useState( + fetchedSettings?.emailPasswordSet ?? false + ); + const [emailPasswordHasBeenReset, setEmailPasswordHasBeenReset] = useState(false); + // Test email functionality + const { post: sendTestEmail, loading: isSendingTestEmail } = usePost(); // Local state for clear stats dialog const [isStatsDialogOpen, setIsStatsDialogOpen] = useState(false); const { deleteFn: deleteStats, loading: isDeletingStats } = useDelete(); @@ -81,6 +88,7 @@ export const SettingsPage = () => { useEffect(() => { if (fetchedSettings) { setIsApiKeySet(fetchedSettings.pagespeedKeySet); + setIsEmailPasswordSet(fetchedSettings.emailPasswordSet); } }, [fetchedSettings]); @@ -91,6 +99,8 @@ export const SettingsPage = () => { chartType = "histogram", } = useSelector((state: RootState) => state.ui); + const user = useSelector((state: RootState) => state.auth.user); + // Convert timezones to match AutoComplete format (id instead of _id) const timezoneOptions: Timezone[] = timezones.map((tz) => ({ id: tz._id, @@ -122,6 +132,48 @@ export const SettingsPage = () => { setApiKeyHasBeenReset(true); }; + const handleResetEmailPassword = () => { + form.setValue("systemEmailPassword", ""); + setEmailPasswordHasBeenReset(true); + }; + + const handleSendTestEmail = async () => { + const formValues = form.getValues(); + if (!user) { + alert("User not authenticated"); + return; + } + if ( + !formValues.systemEmailHost || + !formValues.systemEmailPort || + !formValues.systemEmailAddress || + !formValues.systemEmailPassword + ) { + alert("Please fill in all required email fields before testing."); + return; + } + + await sendTestEmail("/settings/test-email", { + to: user.email, + systemEmailHost: formValues.systemEmailHost, + systemEmailPort: formValues.systemEmailPort, + systemEmailAddress: formValues.systemEmailAddress, + systemEmailPassword: formValues.systemEmailPassword, + systemEmailSecure: formValues.systemEmailSecure, + systemEmailPool: formValues.systemEmailPool, + systemEmailIgnoreTLS: formValues.systemEmailIgnoreTLS, + systemEmailRequireTLS: formValues.systemEmailRequireTLS, + systemEmailRejectUnauthorized: formValues.systemEmailRejectUnauthorized, + ...(formValues.systemEmailUser && { systemEmailUser: formValues.systemEmailUser }), + ...(formValues.systemEmailTLSServername && { + systemEmailTLSServername: formValues.systemEmailTLSServername, + }), + ...(formValues.systemEmailConnectionHost && { + systemEmailConnectionHost: formValues.systemEmailConnectionHost, + }), + }); + }; + const handleClearStats = async () => { await deleteStats("/checks/team"); setIsStatsDialogOpen(false); @@ -133,6 +185,9 @@ export const SettingsPage = () => { if (isApiKeySet && !apiKeyHasBeenReset) { delete (dataToSend as any).pagespeedApiKey; } + if (isEmailPasswordSet && !emailPasswordHasBeenReset) { + delete (dataToSend as any).systemEmailPassword; + } // Convert undefined to empty string for backend unsetting const processedData = Object.entries(dataToSend).reduce((acc, [key, value]) => { @@ -163,6 +218,8 @@ export const SettingsPage = () => { if (result.data) { setIsApiKeySet(result.data.pagespeedKeySet); setApiKeyHasBeenReset(false); + setIsEmailPasswordSet(result.data.emailPasswordSet); + setEmailPasswordHasBeenReset(false); } } }; @@ -455,6 +512,285 @@ export const SettingsPage = () => { )} + {/* Email Settings - Admin Only */} + {isAdmin && ( + + + {JSON.stringify( + { + host: form.watch("systemEmailHost") || "", + port: form.watch("systemEmailPort") || "", + secure: form.watch("systemEmailSecure") ?? false, + auth: { + user: + form.watch("systemEmailUser") || + form.watch("systemEmailAddress") || + "", + pass: "", + }, + name: form.watch("systemEmailConnectionHost") || "localhost", + pool: form.watch("systemEmailPool") ?? false, + tls: { + rejectUnauthorized: + form.watch("systemEmailRejectUnauthorized") ?? true, + ignoreTLS: form.watch("systemEmailIgnoreTLS") ?? false, + requireTLS: form.watch("systemEmailRequireTLS") ?? false, + servername: form.watch("systemEmailTLSServername") || "", + }, + }, + null, + 2 + )} + + + } + rightContent={ + + {/* Email Host */} + ( + + )} + /> + + {/* Email Port */} + ( + { + const val = e.target.value; + field.onChange(val === "" ? 0 : Number(val)); + }} + fieldLabel={t("pages.settings.form.email.option.port.label")} + type="number" + inputProps={{ min: 0 }} + placeholder={t("pages.settings.form.email.option.port.placeholder")} + error={!!fieldState.error} + helperText={fieldState.error?.message} + /> + )} + /> + + {/* Email Address */} + ( + + )} + /> + + {/* Email User (Optional) */} + ( + + )} + /> + + {/* Email Password with Reset Pattern */} + {isEmailPasswordSet && !emailPasswordHasBeenReset ? ( + + + {t("pages.settings.form.email.option.password.labelSet")} + + + + + + ) : ( + ( + + )} + /> + )} + + {/* TLS Servername (Optional) */} + ( + + )} + /> + + {/* Connection Host (Optional) */} + ( + + )} + /> + + {/* Boolean Switches */} + + {[ + { + name: "systemEmailSecure", + label: t("pages.settings.form.email.option.secure.label"), + }, + { + name: "systemEmailPool", + label: t("pages.settings.form.email.option.pool.label"), + }, + { + name: "systemEmailIgnoreTLS", + label: t("pages.settings.form.email.option.ignoreTLS.label"), + }, + { + name: "systemEmailRequireTLS", + label: t("pages.settings.form.email.option.requireTLS.label"), + }, + { + name: "systemEmailRejectUnauthorized", + label: t("pages.settings.form.email.option.rejectUnauthorized.label"), + }, + ].map(({ name, label }) => ( + ( + + {label} + ) => + field.onChange(e.target.checked) + } + /> + + )} + /> + ))} + + + {/* Test Email Button */} + + + + + } + /> + )} + {/* Demo Monitors - Admin Only */} {isAdmin && (