admin settings
This commit is contained in:
1 parent
08887bbc58
commit
86ce30b5f4
8 files changed
+467
-314
No files matched your search
@@ -47,6 +47,8 @@ export const SettingsSwitch = () => {
|
||||
orgPermissions.includes("monitors.read") ||
|
||||
orgPermissions.includes("*");
|
||||
|
||||
const hasMaster = orgPermissions.includes("master");
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip title="Settings" placement="top">
|
||||
@@ -85,6 +87,11 @@ export const SettingsSwitch = () => {
|
||||
<MenuItem onClick={() => handleClick("settings")}>
|
||||
<Typography>General settings</Typography>
|
||||
</MenuItem>
|
||||
{hasMaster && (
|
||||
<MenuItem onClick={() => handleClick("settings/admin")}>
|
||||
<Typography>Admin settings</Typography>
|
||||
</MenuItem>
|
||||
)}
|
||||
{hasTeamEdit && (
|
||||
<MenuItem onClick={() => handleClick("teams")}>
|
||||
<Typography>Teams</Typography>
|
||||
|
||||
@@ -28,6 +28,7 @@ export const useInitForm = ({
|
||||
systemEmailRequireTLS: initialData?.systemEmailRequireTLS ?? false,
|
||||
systemEmailRejectUnauthorized:
|
||||
initialData?.systemEmailRejectUnauthorized ?? true,
|
||||
checksRetentionDays: initialData?.checksRetentionDays ?? 90,
|
||||
};
|
||||
return { defaults };
|
||||
}, [initialData]);
|
||||
|
||||
@@ -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<typeof systemSettingsSchema>;
|
||||
|
||||
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<ITimeZone | null>(
|
||||
null
|
||||
);
|
||||
useEffect(() => {
|
||||
const tz = timezones.find((tz) => tz._id === timezone);
|
||||
setSelectedTimezone(tz || null);
|
||||
}, [timezone]);
|
||||
|
||||
const { post, loading: posting } = usePost<Partial<FormValues>, any>();
|
||||
const { patch, loading: patching } = usePatch<Partial<FormValues>, any>();
|
||||
|
||||
const { response, loading } = useGet<ApiResponse<any>>(
|
||||
hasMaster ? "/settings" : null
|
||||
);
|
||||
const settings = response?.data || {};
|
||||
const initialSettings = useMemo(() => settings, [settings]);
|
||||
|
||||
const onSubmitEmail = async (data: Partial<FormValues>) => {
|
||||
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<FormValues>) => {
|
||||
const { checksRetentionDays } = data;
|
||||
await patch("/settings/retention", { checksRetentionDays });
|
||||
};
|
||||
|
||||
const onTest = async (data: Partial<FormValues>) => {
|
||||
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 (
|
||||
<BasePage>
|
||||
<SettingsForm
|
||||
onSubmitEmail={onSubmitEmail}
|
||||
onSubmitRetention={onSubmitRetention}
|
||||
onTest={onTest}
|
||||
initialData={initialSettings}
|
||||
loading={posting || loading || patching}
|
||||
/>
|
||||
</BasePage>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdminSettingsPage;
|
||||
@@ -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<typeof systemSettingsSchema>;
|
||||
|
||||
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<Partial<FormValues>, any>();
|
||||
const { patch, loading: patching } = usePatch<Partial<FormValues>, any>();
|
||||
const { deleteFn, loading: isDeleting } = useDelete();
|
||||
|
||||
const { response, loading } = useGet<ApiResponse<any>>(
|
||||
hasMaster ? "/settings" : null
|
||||
);
|
||||
const settings = response?.data || {};
|
||||
const initialSettings = useMemo(() => settings, [settings]);
|
||||
|
||||
const onSubmit = async (data: Partial<FormValues>) => {
|
||||
await patch("/settings/email", data);
|
||||
};
|
||||
|
||||
const onTest = async (data: Partial<FormValues>) => {
|
||||
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={
|
||||
<Stack gap={theme.spacing(4)}>
|
||||
<Stack gap={theme.spacing(4)}>
|
||||
<Box>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={handleOpen}
|
||||
>
|
||||
Remove all monitors
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Box>
|
||||
<Button variant="contained" color="error" onClick={handleOpen}>
|
||||
Remove all monitors
|
||||
</Button>
|
||||
</Box>
|
||||
<Typography>
|
||||
Removes all monitors from your organization.
|
||||
</Typography>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{hasMaster && (
|
||||
<SettingsForm
|
||||
onSubmit={onSubmit}
|
||||
onTest={onTest}
|
||||
initialData={initialSettings}
|
||||
loading={posting || loading || patching}
|
||||
/>
|
||||
)}
|
||||
<Dialog
|
||||
open={isDialogOpen}
|
||||
title={t("settings.removeAllMonitors.title", "Delete all monitors?")}
|
||||
|
||||
@@ -17,12 +17,14 @@ type FormValues = z.infer<typeof systemSettingsSchema>;
|
||||
|
||||
export const SettingsForm = ({
|
||||
initialData,
|
||||
onSubmit,
|
||||
onSubmitEmail,
|
||||
onSubmitRetention,
|
||||
onTest,
|
||||
loading,
|
||||
}: {
|
||||
initialData?: Partial<FormValues>;
|
||||
onSubmit: SubmitHandler<FormValues>;
|
||||
onSubmitEmail: SubmitHandler<FormValues>;
|
||||
onSubmitRetention: SubmitHandler<FormValues>;
|
||||
onTest: (data: Partial<FormValues>) => Promise<void>;
|
||||
loading?: boolean;
|
||||
}) => {
|
||||
@@ -48,277 +50,331 @@ export const SettingsForm = ({
|
||||
}, [initialData, reset, defaults]);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
spacing={theme.spacing(10)}
|
||||
component="form"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<ConfigBox
|
||||
title={t("settingsPage.emailSettings.title")}
|
||||
subtitle={t("settingsPage.emailSettings.description")}
|
||||
leftContent={
|
||||
<Stack>
|
||||
<TextLink
|
||||
text={t("settingsPage.emailSettings.descriptionTransport")}
|
||||
linkText={t("settingsPage.emailSettings.linkTransport")}
|
||||
href="https://nodemailer.com/smtp"
|
||||
target="_blank"
|
||||
/>
|
||||
<Box
|
||||
component={"pre"}
|
||||
sx={{
|
||||
fontFamily: "monospace",
|
||||
p: 2,
|
||||
borderRadius: 1,
|
||||
overflow: "auto",
|
||||
}}
|
||||
>
|
||||
<code>
|
||||
{JSON.stringify(
|
||||
{
|
||||
host: formValues.systemEmailHost,
|
||||
port: formValues.systemEmailPort,
|
||||
secure: formValues.systemEmailSecure,
|
||||
auth: {
|
||||
user:
|
||||
formValues.systemEmailUser ||
|
||||
formValues.systemEmailAddress,
|
||||
pass: "<your_password>",
|
||||
},
|
||||
name: formValues.systemEmailConnectionHost || "localhost",
|
||||
pool: formValues.systemEmailPool,
|
||||
tls: {
|
||||
rejectUnauthorized:
|
||||
formValues.systemEmailRejectUnauthorized,
|
||||
ignoreTLS: formValues.systemEmailIgnoreTLS,
|
||||
requireTLS: formValues.systemEmailRequireTLS,
|
||||
servername: formValues.systemEmailTLSServername,
|
||||
},
|
||||
},
|
||||
null,
|
||||
2
|
||||
)}
|
||||
</code>
|
||||
</Box>
|
||||
</Stack>
|
||||
}
|
||||
rightContent={
|
||||
<Stack spacing={theme.spacing(10)}>
|
||||
<Controller
|
||||
name="systemEmailHost"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
{...field}
|
||||
type="text"
|
||||
fieldLabel={t("settingsPage.emailSettings.labelHost")}
|
||||
fullWidth
|
||||
error={!!errors.systemEmailHost}
|
||||
helperText={
|
||||
errors.systemEmailHost ? errors.systemEmailHost.message : ""
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="systemEmailPort"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
{...field}
|
||||
type="number"
|
||||
fieldLabel={t("settingsPage.emailSettings.labelPort")}
|
||||
fullWidth
|
||||
error={!!errors.systemEmailPort}
|
||||
helperText={
|
||||
errors.systemEmailPort ? errors.systemEmailPort.message : ""
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="systemEmailUser"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
{...field}
|
||||
type="text"
|
||||
fieldLabel={t("settingsPage.emailSettings.labelUser")}
|
||||
fullWidth
|
||||
error={!!errors.systemEmailUser}
|
||||
helperText={
|
||||
errors.systemEmailUser ? errors.systemEmailUser.message : ""
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="systemEmailAddress"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
{...field}
|
||||
type="text"
|
||||
fieldLabel={t("settingsPage.emailSettings.labelAddress")}
|
||||
fullWidth
|
||||
error={!!errors.systemEmailAddress}
|
||||
helperText={
|
||||
errors.systemEmailAddress
|
||||
? errors.systemEmailAddress.message
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="systemEmailPassword"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
{...field}
|
||||
type="password"
|
||||
fieldLabel={t("settingsPage.emailSettings.labelPassword")}
|
||||
fullWidth
|
||||
error={!!errors.systemEmailPassword}
|
||||
helperText={
|
||||
errors.systemEmailPassword
|
||||
? errors.systemEmailPassword.message
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="systemEmailTLSServername"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
{...field}
|
||||
type="text"
|
||||
fieldLabel={t(
|
||||
"settingsPage.emailSettings.labelTLSServername"
|
||||
)}
|
||||
fullWidth
|
||||
error={!!errors.systemEmailTLSServername}
|
||||
helperText={
|
||||
errors.systemEmailTLSServername
|
||||
? errors.systemEmailTLSServername.message
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="systemEmailConnectionHost"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
{...field}
|
||||
type="text"
|
||||
fieldLabel={t(
|
||||
"settingsPage.emailSettings.labelConnectionHost"
|
||||
)}
|
||||
fullWidth
|
||||
error={!!errors.systemEmailConnectionHost}
|
||||
helperText={
|
||||
errors.systemEmailConnectionHost
|
||||
? errors.systemEmailConnectionHost.message
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="systemEmailSecure"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Stack direction="row" alignItems={"center"}>
|
||||
<Switch {...field} checked={field.value} />
|
||||
<Typography>
|
||||
{t("settingsPage.emailSettings.labelSecure")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="systemEmailPool"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Stack direction="row" alignItems={"center"}>
|
||||
<Switch {...field} checked={field.value} />
|
||||
|
||||
<Typography>
|
||||
{t("settingsPage.emailSettings.labelPool")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="systemEmailIgnoreTLS"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Stack direction="row" alignItems={"center"}>
|
||||
<Switch {...field} checked={field.value} />
|
||||
|
||||
<Typography>
|
||||
{t("settingsPage.emailSettings.labelIgnoreTLS")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="systemEmailRequireTLS"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Stack direction="row" alignItems={"center"}>
|
||||
<Switch {...field} checked={field.value} />
|
||||
|
||||
<Typography>
|
||||
{t("settingsPage.emailSettings.labelRequireTLS")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="systemEmailRejectUnauthorized"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Stack direction="row" alignItems={"center"}>
|
||||
<Switch {...field} checked={field.value} />
|
||||
|
||||
<Typography>
|
||||
{t("settingsPage.emailSettings.labelRejectUnauthorized")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
/>
|
||||
<Stack
|
||||
gap={theme.spacing(4)}
|
||||
direction="row"
|
||||
justifyContent="flex-end"
|
||||
>
|
||||
<Button
|
||||
loading={loading}
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={() => {
|
||||
onTest(formValues);
|
||||
<Stack spacing={theme.spacing(10)}>
|
||||
<Stack
|
||||
spacing={theme.spacing(10)}
|
||||
component="form"
|
||||
onSubmit={handleSubmit(onSubmitEmail)}
|
||||
>
|
||||
<ConfigBox
|
||||
title={t("settingsPage.emailSettings.title")}
|
||||
subtitle={t("settingsPage.emailSettings.description")}
|
||||
leftContent={
|
||||
<Stack>
|
||||
<TextLink
|
||||
text={t("settingsPage.emailSettings.descriptionTransport")}
|
||||
linkText={t("settingsPage.emailSettings.linkTransport")}
|
||||
href="https://nodemailer.com/smtp"
|
||||
target="_blank"
|
||||
/>
|
||||
<Box
|
||||
component={"pre"}
|
||||
sx={{
|
||||
fontFamily: "monospace",
|
||||
p: 2,
|
||||
borderRadius: 1,
|
||||
overflow: "auto",
|
||||
}}
|
||||
>
|
||||
Test transport config
|
||||
</Button>
|
||||
<Button
|
||||
loading={loading}
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="accent"
|
||||
>
|
||||
Save e-mail settings
|
||||
</Button>
|
||||
<code>
|
||||
{JSON.stringify(
|
||||
{
|
||||
host: formValues.systemEmailHost,
|
||||
port: formValues.systemEmailPort,
|
||||
secure: formValues.systemEmailSecure,
|
||||
auth: {
|
||||
user:
|
||||
formValues.systemEmailUser ||
|
||||
formValues.systemEmailAddress,
|
||||
pass: "<your_password>",
|
||||
},
|
||||
name: formValues.systemEmailConnectionHost || "localhost",
|
||||
pool: formValues.systemEmailPool,
|
||||
tls: {
|
||||
rejectUnauthorized:
|
||||
formValues.systemEmailRejectUnauthorized,
|
||||
ignoreTLS: formValues.systemEmailIgnoreTLS,
|
||||
requireTLS: formValues.systemEmailRequireTLS,
|
||||
servername: formValues.systemEmailTLSServername,
|
||||
},
|
||||
},
|
||||
null,
|
||||
2
|
||||
)}
|
||||
</code>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
}
|
||||
rightContent={
|
||||
<Stack spacing={theme.spacing(10)}>
|
||||
<Controller
|
||||
name="systemEmailHost"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
{...field}
|
||||
type="text"
|
||||
fieldLabel={t("settingsPage.emailSettings.labelHost")}
|
||||
fullWidth
|
||||
error={!!errors.systemEmailHost}
|
||||
helperText={
|
||||
errors.systemEmailHost
|
||||
? errors.systemEmailHost.message
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="systemEmailPort"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
{...field}
|
||||
type="number"
|
||||
fieldLabel={t("settingsPage.emailSettings.labelPort")}
|
||||
fullWidth
|
||||
error={!!errors.systemEmailPort}
|
||||
helperText={
|
||||
errors.systemEmailPort
|
||||
? errors.systemEmailPort.message
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="systemEmailUser"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
{...field}
|
||||
type="text"
|
||||
fieldLabel={t("settingsPage.emailSettings.labelUser")}
|
||||
fullWidth
|
||||
error={!!errors.systemEmailUser}
|
||||
helperText={
|
||||
errors.systemEmailUser
|
||||
? errors.systemEmailUser.message
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="systemEmailAddress"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
{...field}
|
||||
type="text"
|
||||
fieldLabel={t("settingsPage.emailSettings.labelAddress")}
|
||||
fullWidth
|
||||
error={!!errors.systemEmailAddress}
|
||||
helperText={
|
||||
errors.systemEmailAddress
|
||||
? errors.systemEmailAddress.message
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="systemEmailPassword"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
{...field}
|
||||
type="password"
|
||||
fieldLabel={t("settingsPage.emailSettings.labelPassword")}
|
||||
fullWidth
|
||||
error={!!errors.systemEmailPassword}
|
||||
helperText={
|
||||
errors.systemEmailPassword
|
||||
? errors.systemEmailPassword.message
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="systemEmailTLSServername"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
{...field}
|
||||
type="text"
|
||||
fieldLabel={t(
|
||||
"settingsPage.emailSettings.labelTLSServername"
|
||||
)}
|
||||
fullWidth
|
||||
error={!!errors.systemEmailTLSServername}
|
||||
helperText={
|
||||
errors.systemEmailTLSServername
|
||||
? errors.systemEmailTLSServername.message
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="systemEmailConnectionHost"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
{...field}
|
||||
type="text"
|
||||
fieldLabel={t(
|
||||
"settingsPage.emailSettings.labelConnectionHost"
|
||||
)}
|
||||
fullWidth
|
||||
error={!!errors.systemEmailConnectionHost}
|
||||
helperText={
|
||||
errors.systemEmailConnectionHost
|
||||
? errors.systemEmailConnectionHost.message
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="systemEmailSecure"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Stack direction="row" alignItems={"center"}>
|
||||
<Switch {...field} checked={field.value} />
|
||||
<Typography>
|
||||
{t("settingsPage.emailSettings.labelSecure")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="systemEmailPool"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Stack direction="row" alignItems={"center"}>
|
||||
<Switch {...field} checked={field.value} />
|
||||
|
||||
<Typography>
|
||||
{t("settingsPage.emailSettings.labelPool")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="systemEmailIgnoreTLS"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Stack direction="row" alignItems={"center"}>
|
||||
<Switch {...field} checked={field.value} />
|
||||
|
||||
<Typography>
|
||||
{t("settingsPage.emailSettings.labelIgnoreTLS")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="systemEmailRequireTLS"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Stack direction="row" alignItems={"center"}>
|
||||
<Switch {...field} checked={field.value} />
|
||||
|
||||
<Typography>
|
||||
{t("settingsPage.emailSettings.labelRequireTLS")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="systemEmailRejectUnauthorized"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Stack direction="row" alignItems={"center"}>
|
||||
<Switch {...field} checked={field.value} />
|
||||
|
||||
<Typography>
|
||||
{t("settingsPage.emailSettings.labelRejectUnauthorized")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
/>
|
||||
<Stack
|
||||
gap={theme.spacing(4)}
|
||||
direction="row"
|
||||
justifyContent="flex-end"
|
||||
>
|
||||
<Button
|
||||
loading={loading}
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={() => {
|
||||
onTest(formValues);
|
||||
}}
|
||||
>
|
||||
Test transport config
|
||||
</Button>
|
||||
<Button
|
||||
loading={loading}
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="accent"
|
||||
>
|
||||
Save e-mail settings
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack
|
||||
component="form"
|
||||
onSubmit={handleSubmit(onSubmitRetention)}
|
||||
spacing={theme.spacing(10)}
|
||||
>
|
||||
<ConfigBox
|
||||
title="Global Data retention"
|
||||
subtitle="Set data retention policy. This affects all organizations and teams"
|
||||
rightContent={
|
||||
<Stack spacing={theme.spacing(10)}>
|
||||
<Controller
|
||||
name="checksRetentionDays"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
{...field}
|
||||
type="number"
|
||||
fieldLabel="Retention days"
|
||||
fullWidth
|
||||
error={!!errors.checksRetentionDays}
|
||||
helperText={
|
||||
errors.checksRetentionDays
|
||||
? errors.checksRetentionDays.message
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Stack
|
||||
gap={theme.spacing(4)}
|
||||
direction="row"
|
||||
justifyContent="flex-end"
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
type="submit"
|
||||
loading={loading}
|
||||
>
|
||||
Save retention settings
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -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 = () => {
|
||||
<Route path="uptime/:id" element={<UptimeDetailsPage />} />
|
||||
<Route path="uptime" element={<UptimeMonitorsPage />} />
|
||||
<Route path="settings" element={<SettingsPage />} />
|
||||
<Route path="settings/admin" element={<AdminSettingsPage />} />
|
||||
<Route path="teams/create" element={<TeamsCreatePage />} />
|
||||
<Route
|
||||
path="teams/:id/member/:memberId/configure"
|
||||
|
||||
@@ -291,6 +291,10 @@ export const systemSettingsSchema = z.object({
|
||||
systemEmailIgnoreTLS: z.boolean().optional(),
|
||||
systemEmailRequireTLS: z.boolean().optional(),
|
||||
systemEmailRejectUnauthorized: z.boolean().optional(),
|
||||
checksRetentionDays: z.coerce
|
||||
.number({ message: "Number required" })
|
||||
.min(1, "Retention days must be at least 1")
|
||||
.max(365, "Retention days cannot exceed 365 days"),
|
||||
});
|
||||
|
||||
const statusPageUrlRegex = /^[A-Za-z0-9]+$/;
|
||||
|
||||
@@ -13,7 +13,7 @@ export interface ISystemSettings extends Document {
|
||||
systemEmailIgnoreTLS: boolean;
|
||||
systemEmailRequireTLS: boolean;
|
||||
systemEmailRejectUnauthorized: boolean;
|
||||
dataRetentionDays: number;
|
||||
checksRetentionDays: number;
|
||||
}
|
||||
|
||||
const SystemSettingsSchema = new Schema<ISystemSettings>(
|
||||
@@ -31,7 +31,7 @@ const SystemSettingsSchema = new Schema<ISystemSettings>(
|
||||
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,
|
||||
|
||||
Reference in new issue
Block a user