diff --git a/client/src/components/inputs/SwitchSettings.tsx b/client/src/components/inputs/SwitchSettings.tsx index 25861e807..2936bdaca 100644 --- a/client/src/components/inputs/SwitchSettings.tsx +++ b/client/src/components/inputs/SwitchSettings.tsx @@ -47,6 +47,8 @@ export const SettingsSwitch = () => { orgPermissions.includes("monitors.read") || orgPermissions.includes("*"); + const hasMaster = orgPermissions.includes("master"); + return ( <> @@ -85,6 +87,11 @@ export const SettingsSwitch = () => { handleClick("settings")}> General settings + {hasMaster && ( + handleClick("settings/admin")}> + Admin settings + + )} {hasTeamEdit && ( handleClick("teams")}> Teams diff --git a/client/src/hooks/forms/UseInitSettingsForm.tsx b/client/src/hooks/forms/UseInitSettingsForm.tsx index a060abc7e..1a98fe48a 100644 --- a/client/src/hooks/forms/UseInitSettingsForm.tsx +++ b/client/src/hooks/forms/UseInitSettingsForm.tsx @@ -28,6 +28,7 @@ export const useInitForm = ({ systemEmailRequireTLS: initialData?.systemEmailRequireTLS ?? false, systemEmailRejectUnauthorized: initialData?.systemEmailRejectUnauthorized ?? true, + checksRetentionDays: initialData?.checksRetentionDays ?? 90, }; return { defaults }; }, [initialData]); diff --git a/client/src/pages/settings/AdminSettings.tsx b/client/src/pages/settings/AdminSettings.tsx new file mode 100644 index 000000000..977ebb96d --- /dev/null +++ b/client/src/pages/settings/AdminSettings.tsx @@ -0,0 +1,115 @@ +import Stack from "@mui/material/Stack"; +import Box from "@mui/material/Box"; +import { BasePage, ConfigBox } from "@/components/design-elements"; +import { + AutoComplete, + Select, + LanguageSelector, + Button, + RadioWithDescription, + Dialog, +} from "@/components/inputs"; +import MenuItem from "@mui/material/MenuItem"; +import Typography from "@mui/material/Typography"; +import { SettingsForm } from "@/pages/settings/SettingsForm"; +import DummyChart from "@/pages/settings/DummyChart"; + +import { useTheme } from "@mui/material/styles"; +import { useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { setTimezone, setMode, setChartType } from "@/features/uiSlice"; +import { useAppSelector, useAppDispatch } from "@/hooks/AppHooks"; +import timezones from "@/utils/timezones.json"; +import type { ITimeZone } from "@/types/timezone"; +import { useGet, usePatch, usePost } from "@/hooks/UseApi"; +import type { ApiResponse } from "@/hooks/UseApi"; +import { z } from "zod"; +import { systemSettingsSchema } from "@/validation/zod"; + +const AdminSettingsPage = () => { + type FormValues = z.infer; + + const user = useAppSelector((state) => state.auth.user); + + const orgPermissions = user?.org?.permissions || []; + const hasMaster = orgPermissions.includes("master"); + + const theme = useTheme(); + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const timezone = useAppSelector((state) => state.ui.timezone); + const mode = useAppSelector((state) => state.ui.mode); + const chartType = useAppSelector((state) => state.ui.chartType); + const [selectedTimezone, setSelectedTimezone] = useState( + null + ); + useEffect(() => { + const tz = timezones.find((tz) => tz._id === timezone); + setSelectedTimezone(tz || null); + }, [timezone]); + + const { post, loading: posting } = usePost, any>(); + const { patch, loading: patching } = usePatch, any>(); + + const { response, loading } = useGet>( + hasMaster ? "/settings" : null + ); + const settings = response?.data || {}; + const initialSettings = useMemo(() => settings, [settings]); + + const onSubmitEmail = async (data: Partial) => { + const emailSettings = { + systemEmailHost: data.systemEmailHost, + systemEmailPort: data.systemEmailPort, + systemEmailAddress: data.systemEmailAddress, + systemEmailPassword: data.systemEmailPassword, + systemEmailUser: data.systemEmailUser, + systemEmailConnectionHost: data.systemEmailConnectionHost, + systemEmailTLSServername: data.systemEmailTLSServername, + systemEmailSecure: data.systemEmailSecure, + systemEmailPool: data.systemEmailPool, + systemEmailIgnoreTLS: data.systemEmailIgnoreTLS, + systemEmailRequireTLS: data.systemEmailRequireTLS, + systemEmailRejectUnauthorized: data.systemEmailRejectUnauthorized, + }; + + await patch("/settings/email", emailSettings); + }; + + const onSubmitRetention = async (data: Partial) => { + const { checksRetentionDays } = data; + await patch("/settings/retention", { checksRetentionDays }); + }; + + const onTest = async (data: Partial) => { + const emailSettings = { + systemEmailHost: data.systemEmailHost, + systemEmailPort: data.systemEmailPort, + systemEmailAddress: data.systemEmailAddress, + systemEmailPassword: data.systemEmailPassword, + systemEmailUser: data.systemEmailUser, + systemEmailConnectionHost: data.systemEmailConnectionHost, + systemEmailTLSServername: data.systemEmailTLSServername, + systemEmailSecure: data.systemEmailSecure, + systemEmailPool: data.systemEmailPool, + systemEmailIgnoreTLS: data.systemEmailIgnoreTLS, + systemEmailRequireTLS: data.systemEmailRequireTLS, + systemEmailRejectUnauthorized: data.systemEmailRejectUnauthorized, + }; + await post("/settings/test-transport", emailSettings); + }; + + return ( + + + + ); +}; + +export default AdminSettingsPage; diff --git a/client/src/pages/settings/Settings.tsx b/client/src/pages/settings/Settings.tsx index 1e71f3692..31d847b31 100644 --- a/client/src/pages/settings/Settings.tsx +++ b/client/src/pages/settings/Settings.tsx @@ -11,25 +11,19 @@ import { } from "@/components/inputs"; import MenuItem from "@mui/material/MenuItem"; import Typography from "@mui/material/Typography"; -import { SettingsForm } from "@/pages/settings/SettingsForm"; import DummyChart from "@/pages/settings/DummyChart"; import { useTheme } from "@mui/material/styles"; -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { setTimezone, setMode, setChartType } from "@/features/uiSlice"; import { useAppSelector, useAppDispatch } from "@/hooks/AppHooks"; import timezones from "@/utils/timezones.json"; import type { ITimeZone } from "@/types/timezone"; import type { SelectChangeEvent } from "@mui/material/Select"; -import { useGet, usePatch, usePost, useDelete } from "@/hooks/UseApi"; -import type { ApiResponse } from "@/hooks/UseApi"; -import { z } from "zod"; -import { systemSettingsSchema } from "@/validation/zod"; +import { useDelete } from "@/hooks/UseApi"; const SettingsPage = () => { - type FormValues = z.infer; - const user = useAppSelector((state) => state.auth.user); const orgPermissions = user?.org?.permissions || []; @@ -53,23 +47,8 @@ const SettingsPage = () => { setSelectedTimezone(tz || null); }, [timezone]); - const { post, loading: posting } = usePost, any>(); - const { patch, loading: patching } = usePatch, any>(); const { deleteFn, loading: isDeleting } = useDelete(); - const { response, loading } = useGet>( - hasMaster ? "/settings" : null - ); - const settings = response?.data || {}; - const initialSettings = useMemo(() => settings, [settings]); - - const onSubmit = async (data: Partial) => { - await patch("/settings/email", data); - }; - - const onTest = async (data: Partial) => { - await post("/settings/test-transport", data); - }; const [isDialogOpen, setIsDialogOpen] = useState(false); const handleOpen = () => setIsDialogOpen(true); @@ -152,30 +131,19 @@ const SettingsPage = () => { subtitle="Monitor related settings" rightContent={ - - - - - + + + + + Removes all monitors from your organization. + } /> )} - {hasMaster && ( - - )} ; export const SettingsForm = ({ initialData, - onSubmit, + onSubmitEmail, + onSubmitRetention, onTest, loading, }: { initialData?: Partial; - onSubmit: SubmitHandler; + onSubmitEmail: SubmitHandler; + onSubmitRetention: SubmitHandler; onTest: (data: Partial) => Promise; loading?: boolean; }) => { @@ -48,277 +50,331 @@ export const SettingsForm = ({ }, [initialData, reset, defaults]); return ( - - - - - - {JSON.stringify( - { - host: formValues.systemEmailHost, - port: formValues.systemEmailPort, - secure: formValues.systemEmailSecure, - auth: { - user: - formValues.systemEmailUser || - formValues.systemEmailAddress, - pass: "", - }, - name: formValues.systemEmailConnectionHost || "localhost", - pool: formValues.systemEmailPool, - tls: { - rejectUnauthorized: - formValues.systemEmailRejectUnauthorized, - ignoreTLS: formValues.systemEmailIgnoreTLS, - requireTLS: formValues.systemEmailRequireTLS, - servername: formValues.systemEmailTLSServername, - }, - }, - null, - 2 - )} - - - - } - rightContent={ - - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - ( - - - - {t("settingsPage.emailSettings.labelSecure")} - - - )} - /> - ( - - - - - {t("settingsPage.emailSettings.labelPool")} - - - )} - /> - ( - - - - - {t("settingsPage.emailSettings.labelIgnoreTLS")} - - - )} - /> - ( - - - - - {t("settingsPage.emailSettings.labelRequireTLS")} - - - )} - /> - ( - - - - - {t("settingsPage.emailSettings.labelRejectUnauthorized")} - - - )} - /> - - - + + {JSON.stringify( + { + host: formValues.systemEmailHost, + port: formValues.systemEmailPort, + secure: formValues.systemEmailSecure, + auth: { + user: + formValues.systemEmailUser || + formValues.systemEmailAddress, + pass: "", + }, + name: formValues.systemEmailConnectionHost || "localhost", + pool: formValues.systemEmailPool, + tls: { + rejectUnauthorized: + formValues.systemEmailRejectUnauthorized, + ignoreTLS: formValues.systemEmailIgnoreTLS, + requireTLS: formValues.systemEmailRequireTLS, + servername: formValues.systemEmailTLSServername, + }, + }, + null, + 2 + )} + + - - } - /> + } + rightContent={ + + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + + + {t("settingsPage.emailSettings.labelSecure")} + + + )} + /> + ( + + + + + {t("settingsPage.emailSettings.labelPool")} + + + )} + /> + ( + + + + + {t("settingsPage.emailSettings.labelIgnoreTLS")} + + + )} + /> + ( + + + + + {t("settingsPage.emailSettings.labelRequireTLS")} + + + )} + /> + ( + + + + + {t("settingsPage.emailSettings.labelRejectUnauthorized")} + + + )} + /> + + + + + + } + /> + + + + ( + + )} + /> + + + + + } + /> + ); }; diff --git a/client/src/routes/Router.tsx b/client/src/routes/Router.tsx index 202dc2b02..39005bf3a 100644 --- a/client/src/routes/Router.tsx +++ b/client/src/routes/Router.tsx @@ -7,6 +7,7 @@ import UptimeCreatePage from "@/pages/uptime/UptimeCreate"; import UptimeDetailsPage from "@/pages/uptime/UptimeDetails"; import UptimeConfigurePage from "@/pages/uptime/UptimeConfigure"; import SettingsPage from "@/pages/settings/Settings"; +import AdminSettingsPage from "@/pages/settings/AdminSettings"; import TeamsPage from "@/pages/teams/Teams"; import TeamsCreatePage from "@/pages/teams/TeamsCreate"; import TeamsConfigPage from "@/pages/teams/TeamsConfig"; @@ -78,6 +79,7 @@ const Router = () => { } /> } /> } /> + } /> } /> ( @@ -31,7 +31,7 @@ const SystemSettingsSchema = new Schema( systemEmailIgnoreTLS: { type: Boolean, default: false }, systemEmailRequireTLS: { type: Boolean, default: false }, systemEmailRejectUnauthorized: { type: Boolean, default: true }, - dataRetentionDays: { type: Number, default: 90 }, + checksRetentionDays: { type: Number, default: 90 }, }, { timestamps: true,