diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 9ff54e807..26956309b 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -18,7 +18,7 @@ Fixes #123
const { t } = useTranslation();
{t('add')}
```
-- [ ] The issue I am working on is assigned to me.
+- [ ] I have **not** included any files that are not related to my pull request, including package-lock and package-json if dependencies have not changed
- [ ] I didn't use any hardcoded values (otherwise it will not scale, and will make it difficult to maintain consistency across the application).
- [ ] I made sure font sizes, color choices etc are all referenced from the theme. I have no hardcoded dimensions.
- [ ] My PR is granular and targeted to one specific feature.
diff --git a/client/src/Components/Inputs/Select/index.jsx b/client/src/Components/Inputs/Select/index.jsx
index 2aa979434..927247d77 100644
--- a/client/src/Components/Inputs/Select/index.jsx
+++ b/client/src/Components/Inputs/Select/index.jsx
@@ -156,7 +156,7 @@ const Select = ({
};
Select.propTypes = {
- id: PropTypes.string.isRequired,
+ id: PropTypes.string,
name: PropTypes.string,
label: PropTypes.string,
placeholder: PropTypes.string,
diff --git a/client/src/Components/Inputs/TextInput/index.jsx b/client/src/Components/Inputs/TextInput/index.jsx
index 58e0e17eb..bb2df5d21 100644
--- a/client/src/Components/Inputs/TextInput/index.jsx
+++ b/client/src/Components/Inputs/TextInput/index.jsx
@@ -141,9 +141,9 @@ TextInput.displayName = "TextInput";
TextInput.propTypes = {
type: PropTypes.string,
- id: PropTypes.string.isRequired,
+ id: PropTypes.string,
name: PropTypes.string,
- value: PropTypes.string,
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
placeholder: PropTypes.string,
isRequired: PropTypes.bool,
isOptional: PropTypes.bool,
diff --git a/client/src/Components/Table/index.jsx b/client/src/Components/Table/index.jsx
index 199e548d7..b6d31bebe 100644
--- a/client/src/Components/Table/index.jsx
+++ b/client/src/Components/Table/index.jsx
@@ -65,7 +65,6 @@ const DataTable = ({
backgroundColor: theme.palette.secondary.main,
color: theme.palette.secondary.contrastText,
fontWeight: 600,
- fontSize: "13px",
},
"& :is(td)": {
backgroundColor: theme.palette.primary.main,
diff --git a/client/src/Hooks/useDeleteMonitorStats.js b/client/src/Hooks/useDeleteMonitorStats.js
new file mode 100644
index 000000000..64138929f
--- /dev/null
+++ b/client/src/Hooks/useDeleteMonitorStats.js
@@ -0,0 +1,24 @@
+import { useState } from "react";
+import { networkService } from "../main";
+import { createToast } from "../Utils/toastUtils";
+import { useTranslation } from "react-i18next";
+
+const UseDeleteMonitorStats = () => {
+ const { t } = useTranslation();
+ const [isLoading, setIsLoading] = useState(false);
+ const deleteMonitorStats = async ({ teamId }) => {
+ setIsLoading(true);
+ try {
+ const res = await networkService.deleteChecksByTeamId({ teamId });
+ createToast({ body: t("settingsStatsCleared") });
+ } catch (error) {
+ createToast({ body: t("settingsFailedToClearStats") });
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return [deleteMonitorStats, isLoading];
+};
+
+export { UseDeleteMonitorStats };
diff --git a/client/src/Hooks/useFetchSettings.js b/client/src/Hooks/useFetchSettings.js
new file mode 100644
index 000000000..f7651e789
--- /dev/null
+++ b/client/src/Hooks/useFetchSettings.js
@@ -0,0 +1,55 @@
+import { useState, useEffect } from "react";
+import { networkService } from "../main";
+import { createToast } from "../Utils/toastUtils";
+import { useTranslation } from "react-i18next";
+
+const useFetchSettings = ({ setSettingsData }) => {
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState(undefined);
+ useEffect(() => {
+ const fetchSettings = async () => {
+ setIsLoading(true);
+ try {
+ const response = await networkService.getAppSettings();
+ setSettingsData(response?.data?.data);
+ } catch (error) {
+ createToast({ body: "Failed to fetch settings" });
+ setError(error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ fetchSettings();
+ }, []);
+
+ return [isLoading, error];
+};
+
+const useSaveSettings = () => {
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState(undefined);
+ const { t } = useTranslation();
+
+ const saveSettings = async (settings) => {
+ setIsLoading(true);
+ try {
+ await networkService.updateAppSettings({ settings });
+ if (settings.checkTTL) {
+ await networkService.updateChecksTTL({
+ ttl: settings.checkTTL,
+ });
+ }
+ createToast({ body: t("settingsSuccessSaved") });
+ } catch (error) {
+ createToast({ body: t("settingsFailedToSave") });
+
+ setError(error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return [isLoading, error, saveSettings];
+};
+
+export { useFetchSettings, useSaveSettings };
diff --git a/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx b/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx
index 817e414b6..25847b1be 100644
--- a/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx
+++ b/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx
@@ -68,8 +68,16 @@ const MonitorsTable = ({ shouldRender, monitors, isAdmin, handleActionMenuDelete
),
},
{ id: "cpu", content: t("cpu"), render: (row) => },
- { id: "memory", content: t("memory"), render: (row) => },
- { id: "disk", content: t("disk"), render: (row) => },
+ {
+ id: "memory",
+ content: t("memory"),
+ render: (row) => ,
+ },
+ {
+ id: "disk",
+ content: t("disk"),
+ render: (row) => ,
+ },
{
id: "actions",
content: t("actions"),
@@ -129,6 +137,7 @@ const MonitorsTable = ({ shouldRender, monitors, isAdmin, handleActionMenuDelete
},
},
onRowClick: (row) => openDetails(row.id),
+ emptyView: "No monitors found",
}}
/>
);
diff --git a/client/src/Pages/Infrastructure/Monitors/index.jsx b/client/src/Pages/Infrastructure/Monitors/index.jsx
index cc2a03138..0d22e98b5 100644
--- a/client/src/Pages/Infrastructure/Monitors/index.jsx
+++ b/client/src/Pages/Infrastructure/Monitors/index.jsx
@@ -73,7 +73,7 @@ const InfrastructureMonitors = () => {
);
}
- if (!isLoading && monitors?.length === 0) {
+ if (!isLoading && typeof summary?.totalMonitors === "undefined" ) {
return (
{
+ const theme = useTheme();
+ const { t } = useTranslation();
+ return (
+
+
+ {t("settingsAbout")}
+
+
+ Checkmate {2.0}
+
+ {t("settingsDevelopedBy")}
+
+
+
+
+ );
+};
+
+export default SettingsAbout;
diff --git a/client/src/Pages/Settings/SettingsDemoMonitors.jsx b/client/src/Pages/Settings/SettingsDemoMonitors.jsx
new file mode 100644
index 000000000..1a592e776
--- /dev/null
+++ b/client/src/Pages/Settings/SettingsDemoMonitors.jsx
@@ -0,0 +1,90 @@
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import Button from "@mui/material/Button";
+import ConfigBox from "../../Components/ConfigBox";
+// Utils
+import { useTheme } from "@emotion/react";
+import { PropTypes } from "prop-types";
+import { useTranslation } from "react-i18next";
+import Dialog from "../../Components/Dialog";
+import { useState } from "react";
+
+const SettingsDemoMonitors = ({ HEADER_SX, handleChange, isLoading }) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ // Local state
+ const [isOpen, setIsOpen] = useState(false);
+ return (
+ <>
+
+
+ {t("settingsDemoMonitors")}
+ {t("settingsDemoMonitorsDescription")}
+
+
+ {t("settingsAddDemoMonitors")}
+
+
+
+
+
+ {t("settingsSystemReset")}
+
+ {t("settingsSystemResetDescription")}
+
+
+
+ {t("settingsRemoveAllMonitors")}
+
+
+
+ >
+ );
+};
+
+SettingsDemoMonitors.propTypes = {
+ handleChange: PropTypes.func,
+ HEADER_SX: PropTypes.object,
+};
+
+export default SettingsDemoMonitors;
diff --git a/client/src/Pages/Settings/SettingsEmail.jsx b/client/src/Pages/Settings/SettingsEmail.jsx
new file mode 100644
index 000000000..ecc2a51f2
--- /dev/null
+++ b/client/src/Pages/Settings/SettingsEmail.jsx
@@ -0,0 +1,126 @@
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import ConfigBox from "../../Components/ConfigBox";
+import TextInput from "../../Components/Inputs/TextInput";
+import Button from "@mui/material/Button";
+import Stack from "@mui/material/Stack";
+// Utils
+import { useTheme } from "@emotion/react";
+import { PropTypes } from "prop-types";
+import { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments";
+const SettingsEmail = ({
+ HEADER_SX,
+ handleChange,
+ settingsData,
+ setSettingsData,
+ isPasswordSet,
+}) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+
+ const [password, setPassword] = useState("");
+ const [hasBeenReset, setHasBeenReset] = useState(false);
+
+ const handlePasswordChange = (e) => {
+ setPassword(e.target.value);
+ setSettingsData({
+ ...settingsData,
+ settings: { ...settingsData.settings, systemEmailPassword: e.target.value },
+ });
+ };
+
+ return (
+
+
+ {t("settingsEmail")}
+ {t("settingsEmailDescription")}
+
+
+
+
+ {t("settingsEmailHost")}
+
+
+
+ {t("settingsEmailPort")}
+
+
+
+ {t("settingsEmailUser")}
+
+
+
+ {t("settingsEmailAddress")}
+
+
+ {(isPasswordSet === false || hasBeenReset === true) && (
+
+ {t("settingsEmailPassword")}
+ }
+ />
+
+ )}
+ {isPasswordSet === true && hasBeenReset === false && (
+
+ {t("settingsEmailFieldResetLabel")}
+
+
+ )}
+
+
+
+ );
+};
+
+SettingsEmail.propTypes = {
+ settingsData: PropTypes.object,
+ setSettingsData: PropTypes.func,
+ handleChange: PropTypes.func,
+ HEADER_SX: PropTypes.object,
+ isPasswordSet: PropTypes.bool,
+};
+
+export default SettingsEmail;
diff --git a/client/src/Pages/Settings/SettingsPagespeed.jsx b/client/src/Pages/Settings/SettingsPagespeed.jsx
new file mode 100644
index 000000000..95b159fa5
--- /dev/null
+++ b/client/src/Pages/Settings/SettingsPagespeed.jsx
@@ -0,0 +1,87 @@
+import Box from "@mui/material/Box";
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import Button from "@mui/material/Button";
+import ConfigBox from "../../Components/ConfigBox";
+import TextInput from "../../Components/Inputs/TextInput";
+import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments";
+// Utils
+import { useTheme } from "@emotion/react";
+import { PropTypes } from "prop-types";
+import { useState, useEffect } from "react";
+import { useTranslation } from "react-i18next";
+
+const SettingsPagespeed = ({
+ HEADING_SX,
+ settingsData,
+ setSettingsData,
+ isApiKeySet,
+}) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+
+ // Local state
+ const [apiKey, setApiKey] = useState("");
+ const [hasBeenReset, setHasBeenReset] = useState(false);
+
+ // Handler
+ const handleChange = (e) => {
+ setApiKey(e.target.value);
+ setSettingsData({
+ ...settingsData,
+ settings: { ...settingsData.settings, pagespeedApiKey: e.target.value },
+ });
+ };
+
+ return (
+
+
+ {t("pageSpeedApiKeyFieldTitle")}
+ {t("pageSpeedApiKeyFieldDescription")}
+
+
+ {(isApiKeySet === false || hasBeenReset === true) && (
+ }
+ />
+ )}
+
+ {isApiKeySet === true && hasBeenReset === false && (
+
+ {t("pageSpeedApiKeyFieldResetLabel")}
+
+
+ )}
+
+
+ );
+};
+
+SettingsPagespeed.propTypes = {
+ HEADING_SX: PropTypes.object,
+ settingsData: PropTypes.object,
+ setSettingsData: PropTypes.func,
+ isApiKeySet: PropTypes.bool,
+};
+
+export default SettingsPagespeed;
diff --git a/client/src/Pages/Settings/SettingsStats.jsx b/client/src/Pages/Settings/SettingsStats.jsx
new file mode 100644
index 000000000..961f13665
--- /dev/null
+++ b/client/src/Pages/Settings/SettingsStats.jsx
@@ -0,0 +1,84 @@
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import Button from "@mui/material/Button";
+import Stack from "@mui/material/Stack";
+import ConfigBox from "../../Components/ConfigBox";
+import TextInput from "../../Components/Inputs/TextInput";
+import Dialog from "../../Components/Dialog";
+
+// Utils
+import { useTheme } from "@emotion/react";
+import { PropTypes } from "prop-types";
+import { useTranslation } from "react-i18next";
+import { useState } from "react";
+
+const SettingsStats = ({ HEADING_SX, handleChange, settingsData, errors }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ const [isOpen, setIsOpen] = useState(false);
+ return (
+
+
+
+ {t("settingsHistoryAndMonitoring")}
+
+
+ {t("settingsHistoryAndMonitoringDescription")}
+
+
+
+
+
+ {t("settingsClearAllStats")}
+
+
+
+
+ );
+};
+
+SettingsStats.propTypes = {
+ HEADING_SX: PropTypes.object,
+ handleChange: PropTypes.func,
+ settingsData: PropTypes.object,
+ errors: PropTypes.object,
+};
+
+export default SettingsStats;
diff --git a/client/src/Pages/Settings/SettingsTimeZone.jsx b/client/src/Pages/Settings/SettingsTimeZone.jsx
new file mode 100644
index 000000000..e6ba1a77b
--- /dev/null
+++ b/client/src/Pages/Settings/SettingsTimeZone.jsx
@@ -0,0 +1,43 @@
+import Box from "@mui/material/Box";
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import ConfigBox from "../../Components/ConfigBox";
+import Select from "../../Components/Inputs/Select";
+import timezones from "../../Utils/timezones.json";
+
+// Utils
+import { useTheme } from "@emotion/react";
+import { PropTypes } from "prop-types";
+import { useTranslation } from "react-i18next";
+const SettingsTimeZone = ({ HEADING_SX, handleChange, timezone }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ return (
+
+
+ {t("settingsGeneralSettings")}
+
+ {t("settingsDisplayTimezone")}-{" "}
+ {t("settingsDisplayTimezoneDescription")}
+
+
+
+
+
+
+ );
+};
+
+SettingsTimeZone.propTypes = {
+ HEADING_SX: PropTypes.object,
+ handleChange: PropTypes.func,
+ timezone: PropTypes.string,
+};
+
+export default SettingsTimeZone;
diff --git a/client/src/Pages/Settings/SettingsUI.jsx b/client/src/Pages/Settings/SettingsUI.jsx
new file mode 100644
index 000000000..24588d79e
--- /dev/null
+++ b/client/src/Pages/Settings/SettingsUI.jsx
@@ -0,0 +1,52 @@
+import Box from "@mui/material/Box";
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import ConfigBox from "../../Components/ConfigBox";
+import Select from "../../Components/Inputs/Select";
+
+// Utils
+import { useTheme } from "@emotion/react";
+import { PropTypes } from "prop-types";
+import { useTranslation } from "react-i18next";
+
+const SettingsUI = ({ HEADING_SX, handleChange, mode, language }) => {
+ const { t, i18n } = useTranslation();
+ const theme = useTheme();
+ const languages = Object.keys(i18n.options.resources || {});
+ return (
+
+
+ {t("settingsAppearance")}
+ {t("settingsAppearanceDescription")}
+
+
+
+
+
+
+ );
+};
+
+SettingsUI.propTypes = {
+ HEADING_SX: PropTypes.object,
+ handleChange: PropTypes.func,
+ mode: PropTypes.string,
+ language: PropTypes.string,
+};
+
+export default SettingsUI;
diff --git a/client/src/Pages/Settings/index.jsx b/client/src/Pages/Settings/index.jsx
index 79e5ea657..eb516ba01 100644
--- a/client/src/Pages/Settings/index.jsx
+++ b/client/src/Pages/Settings/index.jsx
@@ -1,116 +1,69 @@
-// Components
-import { Box, Stack, Typography, Button } from "@mui/material";
-import TextInput from "../../Components/Inputs/TextInput";
-import Link from "../../Components/Link";
-import Select from "../../Components/Inputs/Select";
-import { useIsAdmin } from "../../Hooks/useIsAdmin";
-import Dialog from "../../Components/Dialog";
-import ConfigBox from "../../Components/ConfigBox";
-import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments";
-import { getAppSettings } from "../../Features/Settings/settingsSlice";
-// import {
-// WalletMultiButton,
-// WalletDisconnectButton,
-// } from "@solana/wallet-adapter-react-ui";
-
-//Utils
-import { useTheme } from "@emotion/react";
-import { logger } from "../../Utils/Logger";
-import { useDispatch, useSelector } from "react-redux";
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import Breadcrumbs from "../../Components/Breadcrumbs";
+import SettingsTimeZone from "./SettingsTimeZone";
+import SettingsUI from "./SettingsUI";
+import SettingsPagespeed from "./SettingsPagespeed";
+import SettingsDemoMonitors from "./SettingsDemoMonitors";
+import SettingsAbout from "./SettingsAbout";
+import SettingsEmail from "./SettingsEmail";
+import Button from "@mui/material/Button";
+// Utils
+import { settingsValidation } from "../../Validation/validation";
import { createToast } from "../../Utils/toastUtils";
+import { useState } from "react";
+import { useTheme } from "@emotion/react";
+import { useTranslation } from "react-i18next";
+import { useSelector, useDispatch } from "react-redux";
+import { setTimezone, setMode, setLanguage } from "../../Features/UI/uiSlice";
+import SettingsStats from "./SettingsStats";
import {
deleteMonitorChecksByTeamId,
addDemoMonitors,
deleteAllMonitors,
} from "../../Features/UptimeMonitors/uptimeMonitorsSlice";
-import { update } from "../../Features/Auth/authSlice";
-import PropTypes from "prop-types";
-import { setTimezone, setMode, setLanguage } from "../../Features/UI/uiSlice";
-import timezones from "../../Utils/timezones.json";
-import { useState, useEffect } from "react";
-import { networkService } from "../../main";
-import { settingsValidation } from "../../Validation/validation";
-import { updateAppSettings } from "../../Features/Settings/settingsSlice";
-import { useTranslation } from "react-i18next";
+import { useFetchSettings, useSaveSettings } from "../../Hooks/useFetchSettings";
+import { UseDeleteMonitorStats } from "../../Hooks/useDeleteMonitorStats";
// Constants
-const SECONDS_PER_DAY = 86400;
+const BREADCRUMBS = [{ name: `Settings`, path: "/settings" }];
const Settings = () => {
- const theme = useTheme();
- const { t, i18n } = useTranslation();
- const isAdmin = useIsAdmin();
+ // Redux state
+ const { mode, language, timezone } = useSelector((state) => state.ui);
const { user } = useSelector((state) => state.auth);
- const { language } = useSelector((state) => state.ui);
- const { checkTTL } = user;
- const { isLoading } = useSelector((state) => state.uptimeMonitors);
- const { isLoading: authIsLoading } = useSelector((state) => state.auth);
- const { timezone, distributedUptimeEnabled } = useSelector((state) => state.ui);
- const { mode } = useSelector((state) => state.ui);
- const { pagespeedApiKey } = useSelector((state) => state.settings);
- const [checksIsLoading, setChecksIsLoading] = useState(false);
- const [form, setForm] = useState({
- enableDistributedUptime: distributedUptimeEnabled,
- ttl: checkTTL ? (checkTTL / SECONDS_PER_DAY).toString() : 0,
- pagespeedApiKey: pagespeedApiKey,
+
+ // Local state
+ const [settingsData, setSettingsData] = useState({});
+ const [errors, setErrors] = useState({});
+ // Network
+ const [isSettingsLoading, settingsError] = useFetchSettings({
+ setSettingsData,
});
- const [version, setVersion] = useState("unknown");
- const [apiKeyFieldType, setApiKeyFieldType] = useState("password");
- const [isApiKeySet, setIsApiKeySet] = useState(pagespeedApiKey ? true : false);
- const [tempPSKey, setTempPSKey] = useState("");
- const [errors, setErrors] = useState({});
- const deleteStatsMonitorsInitState = { deleteMonitors: false, deleteStats: false };
- const [isOpen, setIsOpen] = useState(deleteStatsMonitorsInitState);
+ const [isSaving, saveError, saveSettings] = useSaveSettings();
+ const [deleteMonitorStats, isDeletingMonitorStats] = UseDeleteMonitorStats();
+
+ // Setup
+ const theme = useTheme();
+ const HEADING_SX = { mt: theme.spacing(2), mb: theme.spacing(2) };
+ const { t, i18n } = useTranslation();
const dispatch = useDispatch();
- //Fetching latest release version from github
- useEffect(() => {
- const fetchLatestVersion = async () => {
- let version = "unknown";
- try {
- const response = await networkService.fetchGithubLatestRelease();
- if (!response.status === 200) {
- throw new Error("Failed to fetch latest version");
- }
- version = response.data.tag_name;
- } catch (error) {
- createToast({ body: error.message || "Error fetching latest version" }); // Set error message
- } finally {
- setVersion(version);
- }
+ // Handlers
+ const handleChange = async (e) => {
+ const { name, value } = e.target;
+
+ // Build next state early
+ const newSettingsData = {
+ ...settingsData,
+ settings: { ...settingsData.settings, [name]: value },
};
- fetchLatestVersion();
- }, []);
-
- useEffect(() => {
- dispatch(getAppSettings());
- }, [dispatch]);
-
- const handleChange = (event) => {
- const { type, checked, value, id } = event.target;
-
- if (type === "checkbox") {
- setForm((prev) => ({
- ...prev,
- [id]: checked,
- }));
- return;
- }
-
- if (id === "pagespeedApiKey") {
- setTempPSKey(value);
- return;
- }
-
- let inputValue = value;
- if (id === "ttl") {
- inputValue = value.replace(/[^0-9]/g, "");
- }
-
- const updatedForm = { ...form, [id]: inputValue };
- const { error } = settingsValidation.validate(updatedForm, { abortEarly: false });
+ // Validate
+ const { error } = settingsValidation.validate(newSettingsData.settings, {
+ abortEarly: false,
+ });
if (!error || error.details.length === 0) {
setErrors({});
} else {
@@ -119,397 +72,130 @@ const Settings = () => {
newErrors[err.path[0]] = err.message;
});
setErrors(newErrors);
- logger.error("Validation errors:", error.details);
}
- setForm(updatedForm);
- };
-
- // TODO Handle saving
- const handleSave = async () => {
- try {
- setChecksIsLoading(true);
- await networkService.updateChecksTTL({
- ttl: form.ttl,
- });
- const updatedUser = { ...user, checkTTL: form.ttl };
- const [userAction, settingsAction] = await Promise.all([
- dispatch(update({ localData: updatedUser })),
- dispatch(
- updateAppSettings({
- settings: {
- language: language,
- pagespeedApiKey: tempPSKey ? tempPSKey : form.pagespeedApiKey,
- },
- })
- ),
- ]);
-
- if (userAction.payload.success && settingsAction.payload.success) {
- createToast({ body: t("settingsSuccessSaved") });
- } else {
- throw new Error("Failed to save settings");
- }
- } catch (error) {
- createToast({ body: t("settingsFailedToSave") });
- } finally {
- setChecksIsLoading(false);
+ if (name === "timezone") {
+ dispatch(setTimezone({ timezone: value }));
}
- };
- const handleClearStats = async () => {
- try {
- const action = await dispatch(deleteMonitorChecksByTeamId({ teamId: user.teamId }));
-
- if (deleteMonitorChecksByTeamId.fulfilled.match(action)) {
- createToast({ body: t("settingsStatsCleared") });
- } else {
- createToast({ body: t("settingsFailedToClearStats") });
- }
- } catch (error) {
- logger.error(error);
- createToast({ body: t("settingsFailedToClearStats") });
- } finally {
- setIsOpen(deleteStatsMonitorsInitState);
+ if (name === "mode") {
+ dispatch(setMode(value));
}
- };
- const handleInsertDemoMonitors = async () => {
- try {
- const action = await dispatch(addDemoMonitors());
- if (addDemoMonitors.fulfilled.match(action)) {
- createToast({ body: t("settingsDemoMonitorsAdded") });
- } else {
+ if (name === "language") {
+ dispatch(setLanguage(value));
+ i18n.changeLanguage(value);
+ }
+
+ if (name === "deleteStats") {
+ await deleteMonitorStats({ teamId: user.teamId });
+ return;
+ }
+
+ if (name === "demo") {
+ try {
+ const action = await dispatch(addDemoMonitors());
+ if (addDemoMonitors.fulfilled.match(action)) {
+ createToast({ body: t("settingsDemoMonitorsAdded") });
+ } else {
+ createToast({ body: t("settingsFailedToAddDemoMonitors") });
+ }
+ } catch (error) {
createToast({ body: t("settingsFailedToAddDemoMonitors") });
}
- } catch (error) {
- logger.error(error);
- createToast({ Body: t("settingsFailedToAddDemoMonitors") });
+ return;
}
- };
- const handleDeleteAllMonitors = async () => {
- try {
- const action = await dispatch(deleteAllMonitors());
- if (deleteAllMonitors.fulfilled.match(action)) {
- createToast({ body: t("settingsMonitorsDeleted") });
- } else {
+ if (name === "deleteMonitors") {
+ try {
+ const action = await dispatch(deleteAllMonitors());
+ if (deleteAllMonitors.fulfilled.match(action)) {
+ createToast({ body: t("settingsMonitorsDeleted") });
+ } else {
+ createToast({ body: t("settingsFailedToDeleteMonitors") });
+ }
+ } catch (error) {
createToast({ body: t("settingsFailedToDeleteMonitors") });
}
- } catch (error) {
- logger.error(error);
- createToast({ Body: t("settingsFailedToDeleteMonitors") });
- } finally {
- setIsOpen(deleteStatsMonitorsInitState);
+ return;
}
+
+ setSettingsData(newSettingsData);
};
- const handleResetApiKey = () => {
- setIsApiKeySet(false);
- setForm((prev) => ({
- ...prev,
- pagespeedApiKey: "",
- }));
+ const handleSave = () => {
+ const { error } = settingsValidation.validate(settingsData.settings, {
+ abortEarly: false,
+ });
+ if (!error || error.details.length === 0) {
+ setErrors({});
+ } else {
+ const newErrors = {};
+ error.details.forEach((err) => {
+ newErrors[err.path[0]] = err.message;
+ });
+ setErrors(newErrors);
+ }
+ saveSettings(settingsData?.settings);
};
- const languages = Object.keys(i18n.options.resources || {});
-
return (
-
+
+
+ Settings
+
+
+
+
+
+
+
-
-
- {t("settingsGeneralSettings")}
-
- {t("settingsDisplayTimezone")}-{" "}
- {t("settingsDisplayTimezoneDescription")}
-
-
-
-
-
-
-
- {t("settingsAppearance")}
-
- {t("settingsAppearanceDescription")}
-
-
-
-
-
-
-
- {/* {isAdmin && (
-
-
- {t("settingsDistributedUptime")}
-
- {t("settingsDistributedUptimeDescription")}
-
-
-
- {
- dispatch(setDistributedUptimeEnabled(e.target.checked));
- }}
- />
- {distributedUptimeEnabled === true
- ? t("settingsEnabled")
- : t("settingsDisabled")}
-
-
- )} */}
-
-
- {t("pageSpeedApiKeyFieldTitle")}
-
- {t("pageSpeedApiKeyFieldDescription")}
-
-
-
-
- )
- }
- />
- {isApiKeySet && (
-
- {t("pageSpeedApiKeyFieldResetLabel")}
-
-
- )}
-
-
- {/* {isAdmin && (
-
-
- {t("settingsWallet")}
-
- {t("settingsWalletDescription")}
-
-
-
-
-
-
-
-
-
- )} */}
- {isAdmin && (
-
-
- {t("settingsHistoryAndMonitoring")}
-
- {t("settingsHistoryAndMonitoringDescription")}
-
-
-
-
-
- {t("settingsClearAllStats")}
-
-
-
-
- )}
- {isAdmin && (
- <>
- {/* Demo Monitors Section */}
-
-
- {t("settingsDemoMonitors")}
-
- {t("settingsDemoMonitorsDescription")}
-
-
-
- {t("settingsAddDemoMonitors")}
-
-
-
-
- {/* System Reset Section */}
-
-
- {t("settingsSystemReset")}
-
- {t("settingsSystemResetDescription")}
-
-
-
- {t("settingsRemoveAllMonitors")}
-
-
-
- >
- )}
-
-
-
- {t("settingsAbout")}
-
-
- Checkmate {version}
-
- {t("settingsDevelopedBy")}
-
-
-
-
- 0}
+ variant="contained"
+ color="accent"
+ sx={{ px: theme.spacing(12), mt: theme.spacing(20) }}
+ onClick={handleSave}
>
-
-
+ {t("settingsSave")}
+
-
+
);
};
-Settings.propTypes = {
- isAdmin: PropTypes.bool,
-};
export default Settings;
diff --git a/client/src/Pages/Uptime/Configure/index.jsx b/client/src/Pages/Uptime/Configure/index.jsx
index 246f9cde1..5afbc4a27 100644
--- a/client/src/Pages/Uptime/Configure/index.jsx
+++ b/client/src/Pages/Uptime/Configure/index.jsx
@@ -420,7 +420,9 @@ const Configure = () => {
- {t("distributedUptimeCreateIncidentNotification")}
+
+ {t("distributedUptimeCreateIncidentNotification")}
+
{t("distributedUptimeCreateIncidentDescription")}
@@ -491,6 +493,7 @@ const Configure = () => {
{
isLoading={isLoading}
/>
- {
({
MuiTableCell: {
styleOverrides: {
root: ({ theme }) => ({
+ fontSize: typographyLevels.base,
borderBottomColor: theme.palette.primary.lowContrast,
}),
},
diff --git a/client/src/Validation/validation.js b/client/src/Validation/validation.js
index 3d030f84e..be6cb19f7 100644
--- a/client/src/Validation/validation.js
+++ b/client/src/Validation/validation.js
@@ -11,7 +11,8 @@ const nameSchema = joi
.messages({
"string.empty": "Name is required",
"string.max": "Name must be less than 50 characters",
- "string.pattern.base": "Name must contain only letters, spaces, apostrophes, or hyphens"
+ "string.pattern.base":
+ "Name must contain only letters, spaces, apostrophes, or hyphens",
});
const lastnameSchema = joi
@@ -163,20 +164,21 @@ const monitorValidation = joi.object({
"string.invalidUrl": "Please enter a valid URL with optional port",
"string.pattern.base": "Please enter a valid container ID.",
}),
- port: joi.number()
- .integer()
- .min(1)
- .max(65535)
- .when("type", {
- is: "port",
- then: joi.required().messages({
- "number.base": "Port must be a number.",
- "number.min": "Port must be at least 1.",
- "number.max": "Port must be at most 65535.",
- "any.required": "Port is required for port monitors.",
+ port: joi
+ .number()
+ .integer()
+ .min(1)
+ .max(65535)
+ .when("type", {
+ is: "port",
+ then: joi.required().messages({
+ "number.base": "Port must be a number.",
+ "number.min": "Port must be at least 1.",
+ "number.max": "Port must be at most 65535.",
+ "any.required": "Port is required for port monitors.",
+ }),
+ otherwise: joi.optional(),
}),
- otherwise: joi.optional(),
- }),
name: joi.string().trim().max(50).allow("").messages({
"string.max": "This field should not exceed the 50 characters limit.",
}),
@@ -264,14 +266,19 @@ const statusPageValidation = joi.object({
showCharts: joi.boolean(),
});
const settingsValidation = joi.object({
- ttl: joi.number().required().messages({
+ checkTTL: joi.number().required().messages({
"string.empty": "Please enter a value",
"number.base": "Please enter a valid number",
- "any.required": "Please enter a value"
+ "any.required": "Please enter a value",
}),
pagespeedApiKey: joi.string().allow("").optional(),
-})
-.unknown(true);
+ language: joi.string().required(),
+ systemEmailHost: joi.string().allow(""),
+ systemEmailPort: joi.number().allow(null, ""),
+ systemEmailAddress: joi.string().allow(""),
+ systemEmailPassword: joi.string().allow(""),
+ systemEmailUser: joi.string().allow(""),
+});
const dayjsValidator = (value, helpers) => {
if (!dayjs(value).isValid()) {
@@ -374,9 +381,12 @@ const infrastructureMonitorValidation = joi.object({
notifications: joi.array().items(
joi.object({
type: joi.string().valid("email").required(),
- address: joi.string().email({ tlds: { allow: false } }).required(),
+ address: joi
+ .string()
+ .email({ tlds: { allow: false } })
+ .required(),
})
- )
+ ),
});
export {
@@ -388,5 +398,5 @@ export {
advancedSettingsValidation,
infrastructureMonitorValidation,
statusPageValidation,
- logoImageValidation
+ logoImageValidation,
};
diff --git a/client/src/locales/gb.json b/client/src/locales/gb.json
index 1b9040235..6984b28fb 100644
--- a/client/src/locales/gb.json
+++ b/client/src/locales/gb.json
@@ -1,507 +1,517 @@
{
- "dontHaveAccount": "Don't have account",
- "doNotHaveAccount": "Do not have an account?",
- "registerHere": "Register here",
- "email": "E-mail",
- "forgotPassword": "Forgot Password",
- "password": "Password",
- "signUp": "Sign Up",
- "submit": "Submit",
- "title": "Title",
- "continue": "Continue",
- "enterEmail": "Enter your email",
- "authLoginTitle": "Log In",
- "authLoginEnterPassword": "Enter your password",
- "commonPassword": "Password",
- "commonBack": "Back",
- "authForgotPasswordTitle": "Forgot password?",
- "authForgotPasswordResetPassword": "Reset password",
- "createPassword": "Create your password",
- "createAPassword": "Password",
- "authRegisterAlreadyHaveAccount": "Already have an account?",
- "authRegisterLoginLink": "Log In",
- "commonAppName": "Checkmate",
- "welcomeBack": "Welcome back! You're successfully logged in.",
- "authLoginEnterEmail": "Enter your email",
- "authRegisterTitle": "Create an account",
- "authRegisterStepOneTitle": "Create your account",
- "authRegisterStepOneDescription": "Enter your details to get started",
- "authRegisterStepTwoTitle": "Set up your profile",
- "authRegisterStepTwoDescription": "Tell us more about yourself",
- "authRegisterStepThreeTitle": "Almost done!",
- "authRegisterStepThreeDescription": "Review your information",
- "authForgotPasswordDescription": "No worries, we'll send you reset instructions.",
- "authForgotPasswordSendInstructions": "Send instructions",
- "authForgotPasswordBackTo": "Back to",
- "authCheckEmailTitle": "Check your email",
- "authCheckEmailDescription": "We sent a password reset link to",
- "authCheckEmailResendEmail": "Resend email",
- "authCheckEmailBackTo": "Back to",
- "goBackTo": "Go back to",
- "authCheckEmailDidntReceiveEmail": "Didn't receive the email?",
- "authCheckEmailClickToResend": "Click to resend",
- "authSetNewPasswordTitle": "Set new password",
- "authSetNewPasswordDescription": "Your new password must be different from previously used passwords.",
- "authSetNewPasswordNewPassword": "New password",
- "authSetNewPasswordConfirmPassword": "Confirm password",
- "confirmPassword": "Re-enter password to confirm",
- "authSetNewPasswordResetPassword": "Reset password",
- "authSetNewPasswordBackTo": "Back to",
- "authPasswordMustBeAtLeast": "Must be at least",
- "authPasswordCharactersLong": "8 characters long",
- "authPasswordMustContainAtLeast": "Must contain at least",
- "authPasswordSpecialCharacter": "one special character",
- "authPasswordOneNumber": "one number",
- "authPasswordUpperCharacter": "one upper character",
- "authPasswordLowerCharacter": "one lower character",
- "authPasswordConfirmAndPassword": "Confirm password and password",
- "authPasswordMustMatch": "Passwords must match",
- "validationNameRequired": "Please enter your name",
- "validationNameTooLong": "Name should be less than 50 characters",
- "validationNameInvalidCharacters": "Please use only letters, spaces, apostrophes, or hyphens",
- "authRegisterCreateAccount": "Create your account to get started",
- "authRegisterCreateSuperAdminAccount": "Create your super admin account to get started",
- "authRegisterSignUpWithEmail": "Create super admin account",
- "authRegisterBySigningUp": "By creating an account, you agree to our Terms of Service and Privacy Policy.",
- "distributedStatusHeaderText": "Real-time, real-device coverage",
- "distributedStatusSubHeaderText": "Powered by millions devices worldwide, view a system performance by global region, country or city",
- "settingsGeneralSettings": "General settings",
- "settingsDisplayTimezone": "Display timezone",
- "settingsDisplayTimezoneDescription": "Select the timezone used to display dates and times throughout the application.",
- "settingsAppearance": "Appearance",
- "settingsAppearanceDescription": "Switch between light and dark mode, or change user interface language",
- "settingsThemeMode": "Theme Mode",
- "settingsLanguage": "Language",
- "settingsDistributedUptime": "Distributed uptime",
- "settingsDistributedUptimeDescription": "Enable/disable distributed uptime monitoring.",
- "settingsEnabled": "Enabled",
- "settingsDisabled": "Disabled",
- "settingsHistoryAndMonitoring": "History of monitoring",
- "settingsHistoryAndMonitoringDescription": "Define how long you want to retain historical data. You can also clear all existing data.",
- "settingsTTLLabel": "The days you want to keep monitoring history.",
- "settingsTTLOptionalLabel": "0 for infinite",
- "settingsClearAllStats": "Clear all stats. This is irreversible.",
- "settingsClearAllStatsButton": "Clear all stats",
- "settingsClearAllStatsDialogTitle": "Do you want to clear all stats?",
- "settingsClearAllStatsDialogDescription": "Once removed, the monitoring history and stats cannot be retrieved.",
- "settingsClearAllStatsDialogConfirm": "Yes, clear all stats",
- "settingsDemoMonitors": "Demo monitors",
- "settingsDemoMonitorsDescription": "Add sample monitors for demonstration purposes.",
- "settingsAddDemoMonitors": "Adding demo monitors",
- "settingsAddDemoMonitorsButton": "Add demo monitors",
- "settingsSystemReset": "System reset",
- "settingsSystemResetDescription": "Remove all monitors from your system.",
- "settingsRemoveAllMonitors": "Removing all monitors",
- "settingsRemoveAllMonitorsButton": "Remove all monitors",
- "settingsRemoveAllMonitorsDialogTitle": "Do you want to remove all monitors?",
- "settingsRemoveAllMonitorsDialogConfirm": "Yes, remove all monitors",
- "settingsWallet": "Wallet",
- "settingsWalletDescription": "Connect your wallet here. This is required for the Distributed Uptime monitor to connect to multiple nodes globally.",
- "settingsAbout": "About",
- "settingsDevelopedBy": "Developed by Bluewave Labs.",
- "settingsSave": "Save",
- "settingsSuccessSaved": "Settings saved successfully",
- "settingsFailedToSave": "Failed to save settings",
- "settingsStatsCleared": "Stats cleared successfully",
- "settingsFailedToClearStats": "Failed to clear stats",
- "settingsDemoMonitorsAdded": "Successfully added demo monitors",
- "settingsFailedToAddDemoMonitors": "Failed to add demo monitors",
- "settingsMonitorsDeleted": "Successfully deleted all monitors",
- "settingsFailedToDeleteMonitors": "Failed to delete all monitors",
- "backendUnreachable": "Server Connection Error",
- "backendUnreachableMessage": "We're unable to connect to the server. Please check your internet connection or verify your deployment configuration if the problem persists.",
- "backendUnreachableError": "Cannot connect to the server. Please try again later.",
- "retryConnection": "Retry connection",
- "retryingConnection": "Connecting...",
- "backendReconnected": "Successfully reconnected to the server.",
- "backendStillUnreachable": "Server is still unreachable. Please try again later.",
- "backendConnectionError": "Error connecting to the server. Please check your network connection.",
- "starPromptTitle": "Star Checkmate",
- "starPromptDescription": "See the latest releases and help grow the community on GitHub",
- "https": "HTTPS",
- "http": "HTTP",
- "monitor": "monitor",
- "aboutus": "About Us",
+ "dontHaveAccount": "Don't have account",
+ "doNotHaveAccount": "Do not have an account?",
+ "registerHere": "Register here",
+ "email": "E-mail",
+ "forgotPassword": "Forgot Password",
+ "password": "Password",
+ "signUp": "Sign Up",
+ "submit": "Submit",
+ "title": "Title",
+ "continue": "Continue",
+ "enterEmail": "Enter your email",
+ "authLoginTitle": "Log In",
+ "authLoginEnterPassword": "Enter your password",
+ "commonPassword": "Password",
+ "commonBack": "Back",
+ "authForgotPasswordTitle": "Forgot password?",
+ "authForgotPasswordResetPassword": "Reset password",
+ "createPassword": "Create your password",
+ "createAPassword": "Password",
+ "authRegisterAlreadyHaveAccount": "Already have an account?",
+ "authRegisterLoginLink": "Log In",
+ "commonAppName": "Checkmate",
+ "welcomeBack": "Welcome back! You're successfully logged in.",
+ "authLoginEnterEmail": "Enter your email",
+ "authRegisterTitle": "Create an account",
+ "authRegisterStepOneTitle": "Create your account",
+ "authRegisterStepOneDescription": "Enter your details to get started",
+ "authRegisterStepTwoTitle": "Set up your profile",
+ "authRegisterStepTwoDescription": "Tell us more about yourself",
+ "authRegisterStepThreeTitle": "Almost done!",
+ "authRegisterStepThreeDescription": "Review your information",
+ "authForgotPasswordDescription": "No worries, we'll send you reset instructions.",
+ "authForgotPasswordSendInstructions": "Send instructions",
+ "authForgotPasswordBackTo": "Back to",
+ "authCheckEmailTitle": "Check your email",
+ "authCheckEmailDescription": "We sent a password reset link to",
+ "authCheckEmailResendEmail": "Resend email",
+ "authCheckEmailBackTo": "Back to",
+ "goBackTo": "Go back to",
+ "authCheckEmailDidntReceiveEmail": "Didn't receive the email?",
+ "authCheckEmailClickToResend": "Click to resend",
+ "authSetNewPasswordTitle": "Set new password",
+ "authSetNewPasswordDescription": "Your new password must be different from previously used passwords.",
+ "authSetNewPasswordNewPassword": "New password",
+ "authSetNewPasswordConfirmPassword": "Confirm password",
+ "confirmPassword": "Re-enter password to confirm",
+ "authSetNewPasswordResetPassword": "Reset password",
+ "authSetNewPasswordBackTo": "Back to",
+ "authPasswordMustBeAtLeast": "Must be at least",
+ "authPasswordCharactersLong": "8 characters long",
+ "authPasswordMustContainAtLeast": "Must contain at least",
+ "authPasswordSpecialCharacter": "one special character",
+ "authPasswordOneNumber": "one number",
+ "authPasswordUpperCharacter": "one upper character",
+ "authPasswordLowerCharacter": "one lower character",
+ "authPasswordConfirmAndPassword": "Confirm password and password",
+ "authPasswordMustMatch": "Passwords must match",
+ "validationNameRequired": "Please enter your name",
+ "validationNameTooLong": "Name should be less than 50 characters",
+ "validationNameInvalidCharacters": "Please use only letters, spaces, apostrophes, or hyphens",
+ "authRegisterCreateAccount": "Create your account to get started",
+ "authRegisterCreateSuperAdminAccount": "Create your super admin account to get started",
+ "authRegisterSignUpWithEmail": "Create super admin account",
+ "authRegisterBySigningUp": "By creating an account, you agree to our Terms of Service and Privacy Policy.",
+ "distributedStatusHeaderText": "Real-time, real-device coverage",
+ "distributedStatusSubHeaderText": "Powered by millions devices worldwide, view a system performance by global region, country or city",
+ "settingsGeneralSettings": "General settings",
+ "settingsDisplayTimezone": "Display timezone",
+ "settingsDisplayTimezoneDescription": "Select the timezone used to display dates and times throughout the application.",
+ "settingsAppearance": "Appearance",
+ "settingsAppearanceDescription": "Switch between light and dark mode, or change user interface language",
+ "settingsThemeMode": "Theme Mode",
+ "settingsLanguage": "Language",
+ "settingsDistributedUptime": "Distributed uptime",
+ "settingsDistributedUptimeDescription": "Enable/disable distributed uptime monitoring.",
+ "settingsEnabled": "Enabled",
+ "settingsDisabled": "Disabled",
+ "settingsHistoryAndMonitoring": "History of monitoring",
+ "settingsHistoryAndMonitoringDescription": "Define how long you want to retain historical data. You can also clear all existing data.",
+ "settingsTTLLabel": "The days you want to keep monitoring history.",
+ "settingsTTLOptionalLabel": "0 for infinite",
+ "settingsClearAllStats": "Clear all stats. This is irreversible.",
+ "settingsClearAllStatsButton": "Clear all stats",
+ "settingsClearAllStatsDialogTitle": "Do you want to clear all stats?",
+ "settingsClearAllStatsDialogDescription": "Once removed, the monitoring history and stats cannot be retrieved.",
+ "settingsClearAllStatsDialogConfirm": "Yes, clear all stats",
+ "settingsDemoMonitors": "Demo monitors",
+ "settingsDemoMonitorsDescription": "Add sample monitors for demonstration purposes.",
+ "settingsAddDemoMonitors": "Adding demo monitors",
+ "settingsAddDemoMonitorsButton": "Add demo monitors",
+ "settingsSystemReset": "System reset",
+ "settingsSystemResetDescription": "Remove all monitors from your system.",
+ "settingsRemoveAllMonitors": "Removing all monitors",
+ "settingsRemoveAllMonitorsButton": "Remove all monitors",
+ "settingsRemoveAllMonitorsDialogTitle": "Do you want to remove all monitors?",
+ "settingsRemoveAllMonitorsDialogConfirm": "Yes, remove all monitors",
+ "settingsWallet": "Wallet",
+ "settingsWalletDescription": "Connect your wallet here. This is required for the Distributed Uptime monitor to connect to multiple nodes globally.",
+ "settingsAbout": "About",
+ "settingsDevelopedBy": "Developed by Bluewave Labs.",
+ "settingsSave": "Save",
+ "settingsSuccessSaved": "Settings saved successfully",
+ "settingsFailedToSave": "Failed to save settings",
+ "settingsStatsCleared": "Stats cleared successfully",
+ "settingsFailedToClearStats": "Failed to clear stats",
+ "settingsDemoMonitorsAdded": "Successfully added demo monitors",
+ "settingsFailedToAddDemoMonitors": "Failed to add demo monitors",
+ "settingsMonitorsDeleted": "Successfully deleted all monitors",
+ "settingsFailedToDeleteMonitors": "Failed to delete all monitors",
+ "settingsEmail": "Email settings",
+ "settingsEmailDescription": "Configure email settings",
+ "settingsEmailHost": "Email host",
+ "settingsEmailPort": "Email port",
+ "settingsEmailAddress": "Email address",
+ "settingsEmailPassword": "Email password",
+ "settingsEmailUser": "Email user",
+ "settingsEmailFieldResetLabel": "Password is set. Click Reset to change it.",
+ "backendUnreachable": "Server Connection Error",
+ "backendUnreachableMessage": "We're unable to connect to the server. Please check your internet connection or verify your deployment configuration if the problem persists.",
+ "backendUnreachableError": "Cannot connect to the server. Please try again later.",
+ "retryConnection": "Retry connection",
+ "retryingConnection": "Connecting...",
+ "backendReconnected": "Successfully reconnected to the server.",
+ "backendStillUnreachable": "Server is still unreachable. Please try again later.",
+ "backendConnectionError": "Error connecting to the server. Please check your network connection.",
+ "starPromptTitle": "Star Checkmate",
+ "starPromptDescription": "See the latest releases and help grow the community on GitHub",
+ "https": "HTTPS",
+ "http": "HTTP",
+ "monitor": "monitor",
+ "aboutus": "About Us",
+ "state": "State",
+ "now": "Now",
+ "delete": "Delete",
+ "configure": "Configure",
+ "networkError": "Network error",
+ "responseTime": "Response time",
+ "ms": "ms",
+ "bar": "Bar",
+ "area": "Area",
+ "country": "COUNTRY",
+ "city": "CITY",
+ "response": "RESPONSE",
+ "checkConnection": "Please check your connection",
+ "passwordreset": "Password Reset",
+ "authRegisterStepOnePersonalDetails": "Enter your personal details",
+ "authCheckEmailOpenEmailButton": "Open email app",
+ "authNewPasswordConfirmed": "Your password has been successfully reset. Click below to log in magically.",
+ "monitorStatusUp": "Monitor {name} ({url}) is now UP and responding",
+ "monitorStatusDown": "Monitor {name} ({url}) is DOWN and not responding",
+ "webhookSendSuccess": "Webhook notification sent successfully",
+ "webhookSendError": "Error sending webhook notification to {platform}",
+ "webhookUnsupportedPlatform": "Unsupported platform: {platform}",
+ "distributedRightCategoryTitle": "Monitor",
+ "distributedStatusServerMonitors": "Server Monitors",
+ "distributedStatusServerMonitorsDescription": "Monitor status of related servers",
+ "distributedUptimeCreateSelectURL": "Here you can select the URL of the host, together with the type of monitor.",
+ "distributedUptimeCreateChecks": "Checks to perform",
+ "distributedUptimeCreateChecksDescription": "You can always add or remove checks after adding your site.",
+ "distributedUptimeCreateIncidentNotification": "Incident notifications",
+ "distributedUptimeCreateIncidentDescription": "When there is an incident, notify users.",
+ "distributedUptimeCreateAdvancedSettings": "Advanced settings",
+ "distributedUptimeDetailsNoMonitorHistory": "There is no check history for this monitor yet.",
+ "distributedUptimeDetailsFooterHeading": "Made with ❤️ by UpRock & Bluewave Labs",
+ "distributedUptimeDetailsFooterBuilt": "Built on",
+ "distributedUptimeDetailsFooterSolana": "Solana",
+ "distributedUptimeDetailsMonitorHeader": "Distributed Uptime Monitoring powered by DePIN",
+ "distributedUptimeDetailsStatusHeaderUptime": "Uptime:",
+ "distributedUptimeDetailsStatusHeaderLastUpdate": "Last updated",
+ "notifications": {
+ "enableNotifications": "Enable {{platform}} notifications",
+ "testNotification": "Test notification",
+ "addOrEditNotifications": "Add or edit notifications",
+ "slack": {
+ "label": "Slack",
+ "description": "To enable Slack notifications, create a Slack app and enable incoming webhooks. After that, simply provide the webhook URL here.",
+ "webhookLabel": "Webhook URL",
+ "webhookPlaceholder": "https://hooks.slack.com/services/...",
+ "webhookRequired": "Slack webhook URL is required"
+ },
+ "discord": {
+ "label": "Discord",
+ "description": "To send data to a Discord channel from Checkmate via Discord notifications using webhooks, you can use Discord's incoming Webhooks feature.",
+ "webhookLabel": "Discord Webhook URL",
+ "webhookPlaceholder": "https://discord.com/api/webhooks/...",
+ "webhookRequired": "Discord webhook URL is required"
+ },
+ "telegram": {
+ "label": "Telegram",
+ "description": "To enable Telegram notifications, create a Telegram bot using BotFather, an official bot for creating and managing Telegram bots. Then, get the API token and chat ID and write them down here.",
+ "tokenLabel": "Your bot token",
+ "tokenPlaceholder": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11",
+ "chatIdLabel": "Your Chat ID",
+ "chatIdPlaceholder": "-1001234567890",
+ "fieldsRequired": "Telegram token and chat ID are required"
+ },
+ "webhook": {
+ "label": "Webhooks",
+ "description": "You can set up a custom webhook to receive notifications when incidents occur.",
+ "urlLabel": "Webhook URL",
+ "urlPlaceholder": "https://your-server.com/webhook",
+ "urlRequired": "Webhook URL is required"
+ },
+ "testNotificationDevelop": "Test notification 2",
+ "integrationButton": "Notification Integration",
+ "testSuccess": "Test notification sent successfully!",
+ "testFailed": "Failed to send test notification",
+ "unsupportedType": "Unsupported notification type",
+ "networkError": "Network error occurred"
+ },
+ "testLocale": "testLocale",
+ "add": "Add",
+ "monitors": "monitors",
+ "distributedUptimeStatusCreateStatusPage": "status page",
+ "distributedUptimeStatusCreateStatusPageAccess": "Access",
+ "distributedUptimeStatusCreateStatusPageReady": "If your status page is ready, you can mark it as published.",
+ "distributedUptimeStatusBasicInfoHeader": "Basic Information",
+ "distributedUptimeStatusBasicInfoDescription": "Define company name and the subdomain that your status page points to.",
+ "distributedUptimeStatusLogoHeader": "Logo",
+ "distributedUptimeStatusLogoDescription": "Upload a logo for your status page",
+ "distributedUptimeStatusLogoUploadButton": "Upload logo",
+ "distributedUptimeStatusStandardMonitorsHeader": "Standard Monitors",
+ "distributedUptimeStatusStandardMonitorsDescription": "Attach standard monitors to your status page.",
+ "distributedUptimeStatusCreateYour": "Create your",
+ "distributedUptimeStatusEditYour": "Edit your",
+ "distributedUptimeStatusPublishedLabel": "Published and visible to the public",
+ "distributedUptimeStatusCompanyNameLabel": "Company name",
+ "distributedUptimeStatusPageAddressLabel": "Your status page address",
+ "distributedUptimeStatus30Days": "30 days",
+ "distributedUptimeStatus60Days": "60 days",
+ "distributedUptimeStatus90Days": "90 days",
+ "distributedUptimeStatusPageNotSetUp": "A status page is not set up.",
+ "distributedUptimeStatusContactAdmin": "Please contact your administrator",
+ "distributedUptimeStatusPageNotPublic": "This status page is not public.",
+ "distributedUptimeStatusPageDeleteDialog": "Do you want to delete this status page?",
+ "distributedUptimeStatusPageDeleteConfirm": "Yes, delete status page",
+ "distributedUptimeStatusPageDeleteDescription": "Once deleted, your status page cannot be retrieved.",
+ "distributedUptimeStatusDevices": "Devices",
+ "distributedUptimeStatusUpt": "UPT",
+ "distributedUptimeStatusUptBurned": "UPT Burned",
+ "distributedUptimeStatusUptLogo": "Upt Logo",
+ "incidentsTableNoIncidents": "No incidents recorded",
+ "incidentsTablePaginationLabel": "incidents",
+ "incidentsTableMonitorName": "Monitor Name",
+ "incidentsTableStatus": "Status",
+ "incidentsTableDateTime": "Date & Time",
+ "incidentsTableStatusCode": "Status Code",
+ "incidentsTableMessage": "Message",
+ "incidentsOptionsHeader": "Incidents for:",
+ "incidentsOptionsHeaderFilterBy": "Filter by:",
+ "incidentsOptionsHeaderFilterAll": "All",
+ "incidentsOptionsHeaderFilterDown": "Down",
+ "incidentsOptionsHeaderFilterCannotResolve": "Cannot resolve",
+ "incidentsOptionsHeaderShow": "Show:",
+ "incidentsOptionsHeaderLastHour": "Last hour",
+ "incidentsOptionsHeaderLastDay": "Last day",
+ "incidentsOptionsHeaderLastWeek": "Last week",
+ "incidentsOptionsPlaceholderAllServers": "All servers",
+ "infrastructureCreateYour": "Create your",
+ "infrastructureCreateGeneralSettingsDescription": "Here you can select the URL of the host, together with the friendly name and authorization secret to connect to the server agent.",
+ "infrastructureServerRequirement": "The server you are monitoring must be running the",
+ "infrastructureCustomizeAlerts": "Customize alerts",
+ "infrastructureAlertNotificationDescription": "Send a notification to user(s) when thresholds exceed a specified percentage.",
+ "infrastructureCreateMonitor": "Create Infrastructure Monitor",
+ "infrastructureProtocol": "Protocol",
+ "infrastructureServerUrlLabel": "Server URL",
+ "infrastructureDisplayNameLabel": "Display name",
+ "infrastructureAuthorizationSecretLabel": "Authorization secret",
+ "gb": "GB",
+ "mb": "MB",
+ "mem": "Mem",
+ "memoryUsage": "Memory usage",
+ "cpu": "CPU",
+ "cpuUsage": "CPU usage",
+ "cpuTemperature": "CPU Temperature",
+ "diskUsage": "Disk Usage",
+ "used": "Used",
+ "total": "Total",
+ "cores": "Cores",
+ "frequency": "Frequency",
+ "status": "Status",
+ "cpuPhysical": "CPU (Physical)",
+ "cpuLogical": "CPU (Logical)",
+ "cpuFrequency": "CPU Frequency",
+ "avgCpuTemperature": "Average CPU Temperature",
+ "memory": "Memory",
+ "disk": "Disk",
+ "uptime": "Uptime",
+ "os": "OS",
+ "host": "Host",
+ "actions": "Actions",
+ "integrations": "Integrations",
+ "integrationsPrism": "Connect Prism to your favorite service.",
+ "integrationsSlack": "Slack",
+ "integrationsSlackInfo": "Connect with Slack and see incidents in a channel",
+ "integrationsDiscord": "Discord",
+ "integrationsDiscordInfo": "Connect with Discord and view incidents directly in a channel",
+ "integrationsZapier": "Zapier",
+ "integrationsZapierInfo": "Send all incidents to Zapier, and then see them everywhere",
+ "commonSave": "Save",
+ "createYour": "Create your",
+ "createMonitor": "Create monitor",
+ "pause": "Pause",
+ "resume": "Resume",
+ "editing": "Editing...",
+ "url": "URL",
+ "access": "Access",
+ "timezone": "Timezone",
+ "features": "Features",
+ "administrator": "Administrator?",
+ "loginHere": "Login here",
+ "displayName": "Display name",
+ "urlMonitor": "URL to monitor",
+ "portToMonitor": "Port to monitor",
+ "websiteMonitoring": "Website monitoring",
+ "websiteMonitoringDescription": "Use HTTP(s) to monitor your website or API endpoint.",
+ "pingMonitoring": "Ping monitoring",
+ "pingMonitoringDescription": "Check whether your server is available or not.",
+ "dockerContainerMonitoring": "Docker container monitoring",
+ "dockerContainerMonitoringDescription": "Check whether your Docker container is running or not.",
+ "portMonitoring": "Port monitoring",
+ "portMonitoringDescription": "Check whether your port is open or not.",
+ "createMaintenanceWindow": "Create maintenance window",
+ "createMaintenance": "Create maintenance",
+ "editMaintenance": "Edit maintenance",
+ "maintenanceWindowName": "Maintenance Window Name",
+ "friendlyNameInput": "Friendly name",
+ "friendlyNamePlaceholder": "Maintenance at __ : __ for ___ minutes",
+ "maintenanceRepeat": "Maintenance Repeat",
+ "maintenance": "maintenance",
+ "duration": "Duration",
+ "addMonitors": "Add monitors",
+ "window": "window",
+ "cancel": "Cancel",
+ "message": "Message",
+ "low": "low",
+ "high": "high",
+ "statusCode": "Status code",
+ "date&Time": "Date & Time",
+ "type": "Type",
+ "statusPageName": "Status page name",
+ "publicURL": "Public URL",
+ "repeat": "Repeat",
+ "edit": "Edit",
+ "createA": "Create a",
+ "remove": "Remove",
+ "maintenanceWindowDescription": "Your pings won't be sent during this time frame",
+ "startTime": "Start time",
+ "timeZoneInfo": "All dates and times are in GMT+0 time zone.",
+ "monitorsToApply": "Monitors to apply maintenance window to",
+ "nextWindow": "Next window",
+ "notFoundButton": "Go to the main dashboard",
+ "pageSpeedConfigureSettingsDescription": "Here you can select the URL of the host, together with the type of monitor.",
+ "monitorDisplayName": "Monitor display name",
+ "whenNewIncident": "When there is a new incident,",
+ "notifySMS": "Notify via SMS (coming soon)",
+ "notifyEmails": "Also notify via email to multiple addresses (coming soon)",
+ "seperateEmails": "You can separate multiple emails with a comma",
+ "checkFrequency": "Check frequency",
+ "matchMethod": "Match Method",
+ "expectedValue": "Expected value",
+ "deleteDialogTitle": "Do you really want to delete this monitor?",
+ "deleteDialogDescription": "Once deleted, this monitor cannot be retrieved.",
+ "pageSpeedMonitor": "PageSpeed monitor",
+ "shown": "Shown",
+ "ago": "ago",
+ "companyName": "Company name",
+ "pageSpeedDetailsPerformanceReport": "Values are estimated and may vary.",
+ "pageSpeedDetailsPerformanceReportCalculator": "See calculator",
+ "checkingEvery": "Checking every",
+ "statusPageCreateSettings": "If your status page is ready, you can mark it as published.",
+ "basicInformation": "Basic Information",
+ "statusPageCreateBasicInfoDescription": "Define company name and the subdomain that your status page points to.",
+ "statusPageCreateSelectTimeZoneDescription": "Select the timezone that your status page will be displayed in.",
+ "statusPageCreateAppearanceDescription": "Define the default look and feel of your public status page.",
+ "statusPageCreateSettingsCheckboxLabel": "Published and visible to the public",
+ "statusPageCreateBasicInfoStatusPageAddress": "Your status page address",
+ "statusPageCreateTabsContent": "Status page servers",
+ "statusPageCreateTabsContentDescription": "You can add any number of servers that you monitor to your status page. You can also reorder them for the best viewing experience.",
+ "statusPageCreateTabsContentFeaturesDescription": "Show more details on the status page",
+ "showCharts": "Show charts",
+ "showUptimePercentage": "Show uptime percentage",
+ "removeLogo": "Remove Logo",
+ "statusPageStatus": "A public status page is not set up.",
+ "statusPageStatusContactAdmin": "Please contact to your administrator",
+ "statusPageStatusNotPublic": "This status page is not public.",
+ "statusPageStatusNoPage": "There's no status page here.",
+ "statusPageStatusServiceStatus": "Service status",
+ "statusBreadCrumbsStatusPages": "Status Pages",
+ "statusBreadCrumbsDetails": "Details",
+ "deleteStatusPage": "Do you want to delete this status page?",
+ "deleteStatusPageConfirm": "Yes, delete status page",
+ "deleteStatusPageDescription": "Once deleted, your status page cannot be retrieved.",
+ "uptimeCreate": "The expected value is used to match against response result, and the match determines the status.",
+ "uptimeCreateJsonPath": "This expression will be evaluated against the reponse JSON data and the result will be used to match against the expected value. See",
+ "uptimeCreateJsonPathQuery": "for query language documentation.",
+ "maintenanceTableActionMenuDialogTitle": "Do you really want to remove this maintenance window?",
+ "infrastructureEditYour": "Edit your",
+ "infrastructureEditMonitor": "Save Infrastructure Monitor",
+ "infrastructureMonitorCreated": "Infrastructure monitor created successfully!",
+ "infrastructureMonitorUpdated": "Infrastructure monitor updated successfully!",
+ "errorInvalidTypeId": "Invalid notification type provided",
+ "errorInvalidFieldId": "Invalid field ID provided",
+ "inviteNoTokenFound": "No invite token found",
+ "pageSpeedWarning": "Warning: You haven't added a Google PageSpeed API key yet. Without it, the PageSpeed monitor won't function.",
+ "pageSpeedLearnMoreLink": "Click here",
+ "pageSpeedAddApiKey": "to add your API key.",
+ "update": "Update",
+ "invalidFileFormat": "Unsupported file format!",
+ "invalidFileSize": "File size is too large!",
+ "ClickUpload": "Click to upload",
+ "DragandDrop": "drag and drop",
+ "MaxSize": "Maximum Size",
+ "SupportedFormats": "Supported formats",
+ "FirstName": "First name",
+ "LastName": "Last name",
+ "EmailDescriptionText": "This is your current email address — it cannot be changed.",
+ "ignoreTLSError": "Ignore TLS/SSL error",
+ "tlsErrorIgnored": "TLS/SSL errors ignored",
+ "ignoreTLSErrorDescription": "Ignore TLS/SSL errors and continue checking the website's availability",
+ "YourPhoto": "Profile photo",
+ "PhotoDescriptionText": "This photo will be displayed in your profile page.",
+ "save": "Save",
+ "DeleteAccountTitle": "Remove account",
+ "DeleteAccountButton": "Remove account",
+ "DeleteDescriptionText": "This will remove the account and all associated data from the server. This isn't reversible.",
+ "DeleteAccountWarning": "Removing your account means you won't be able to sign in again and all your data will be removed. This isn't reversible.",
+ "DeleteWarningTitle": "Really remove this account?",
+ "authRegisterFirstName": "Name",
+ "authRegisterLastName": "Surname",
+ "authRegisterEmail": "Email",
+ "authRegisterEmailRequired": "To continue, please enter your email address",
+ "authRegisterEmailInvalid": "Please enter a valid email address",
+ "bulkImport": {
+ "title": "Bulk Import",
+ "selectFileTips": "Select CSV file to upload",
+ "selectFileDescription": "You can download our template or sample",
+ "selectFile": "Select File",
+ "parsingFailed": "Parsing failed",
+ "uploadSuccess": "Monitors created successfully!",
+ "validationFailed": "Validation failed",
+ "noFileSelected": "No file selected",
+ "fallbackPage": "Import a file to upload a list of servers in bulk"
+ },
+ "publicLink": "Public link",
+ "maskedPageSpeedKeyPlaceholder": "*************************************",
+ "pageSpeedApiKeyFieldTitle": "Google PageSpeed API key",
+ "pageSpeedApiKeyFieldLabel": "PageSpeed API key",
+ "pageSpeedApiKeyFieldDescription": "Enter your Google PageSpeed API key to enable pagespeed monitoring. Click Reset to update the key.",
+ "pageSpeedApiKeyFieldResetLabel": "API key is set. Click Reset to change it.",
- "now": "Now",
- "delete": "Delete",
- "configure": "Configure",
- "networkError": "Network error",
- "responseTime": "Response time",
- "ms": "ms",
- "bar": "Bar",
- "area": "Area",
- "country": "COUNTRY",
- "city": "CITY",
- "response": "RESPONSE",
- "checkConnection": "Please check your connection",
- "passwordreset": "Password Reset",
- "authRegisterStepOnePersonalDetails": "Enter your personal details",
- "authCheckEmailOpenEmailButton": "Open email app",
- "authNewPasswordConfirmed": "Your password has been successfully reset. Click below to log in magically.",
- "monitorStatusUp": "Monitor {name} ({url}) is now UP and responding",
- "monitorStatusDown": "Monitor {name} ({url}) is DOWN and not responding",
- "webhookSendSuccess": "Webhook notification sent successfully",
- "webhookSendError": "Error sending webhook notification to {platform}",
- "webhookUnsupportedPlatform": "Unsupported platform: {platform}",
- "distributedRightCategoryTitle": "Monitor",
- "distributedStatusServerMonitors": "Server Monitors",
- "distributedStatusServerMonitorsDescription": "Monitor status of related servers",
- "distributedUptimeCreateSelectURL": "Here you can select the URL of the host, together with the type of monitor.",
- "distributedUptimeCreateChecks": "Checks to perform",
- "distributedUptimeCreateChecksDescription": "You can always add or remove checks after adding your site.",
- "distributedUptimeCreateIncidentNotification": "Incident notifications",
- "distributedUptimeCreateIncidentDescription": "When there is an incident, notify users.",
- "distributedUptimeCreateAdvancedSettings": "Advanced settings",
- "distributedUptimeDetailsNoMonitorHistory": "There is no check history for this monitor yet.",
- "distributedUptimeDetailsFooterHeading": "Made with ❤️ by UpRock & Bluewave Labs",
- "distributedUptimeDetailsFooterBuilt": "Built on",
- "distributedUptimeDetailsFooterSolana": "Solana",
- "distributedUptimeDetailsMonitorHeader": "Distributed Uptime Monitoring powered by DePIN",
- "distributedUptimeDetailsStatusHeaderUptime": "Uptime:",
- "distributedUptimeDetailsStatusHeaderLastUpdate": "Last updated",
- "notifications": {
- "enableNotifications": "Enable {{platform}} notifications",
- "testNotification": "Test notification",
- "addOrEditNotifications": "Add or edit notifications",
- "slack": {
- "label": "Slack",
- "description": "To enable Slack notifications, create a Slack app and enable incoming webhooks. After that, simply provide the webhook URL here.",
- "webhookLabel": "Webhook URL",
- "webhookPlaceholder": "https://hooks.slack.com/services/...",
- "webhookRequired": "Slack webhook URL is required"
- },
- "discord": {
- "label": "Discord",
- "description": "To send data to a Discord channel from Checkmate via Discord notifications using webhooks, you can use Discord's incoming Webhooks feature.",
- "webhookLabel": "Discord Webhook URL",
- "webhookPlaceholder": "https://discord.com/api/webhooks/...",
- "webhookRequired": "Discord webhook URL is required"
- },
- "telegram": {
- "label": "Telegram",
- "description": "To enable Telegram notifications, create a Telegram bot using BotFather, an official bot for creating and managing Telegram bots. Then, get the API token and chat ID and write them down here.",
- "tokenLabel": "Your bot token",
- "tokenPlaceholder": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11",
- "chatIdLabel": "Your Chat ID",
- "chatIdPlaceholder": "-1001234567890",
- "fieldsRequired": "Telegram token and chat ID are required"
- },
- "webhook": {
- "label": "Webhooks",
- "description": "You can set up a custom webhook to receive notifications when incidents occur.",
- "urlLabel": "Webhook URL",
- "urlPlaceholder": "https://your-server.com/webhook",
- "urlRequired": "Webhook URL is required"
- },
- "testNotificationDevelop": "Test notification 2",
- "integrationButton": "Notification Integration",
- "testSuccess": "Test notification sent successfully!",
- "testFailed": "Failed to send test notification",
- "unsupportedType": "Unsupported notification type",
- "networkError": "Network error occurred"
- },
- "testLocale": "testLocale",
- "add": "Add",
- "monitors": "monitors",
- "distributedUptimeStatusCreateStatusPage": "status page",
- "distributedUptimeStatusCreateStatusPageAccess": "Access",
- "distributedUptimeStatusCreateStatusPageReady": "If your status page is ready, you can mark it as published.",
- "distributedUptimeStatusBasicInfoHeader": "Basic Information",
- "distributedUptimeStatusBasicInfoDescription": "Define company name and the subdomain that your status page points to.",
- "distributedUptimeStatusLogoHeader": "Logo",
- "distributedUptimeStatusLogoDescription": "Upload a logo for your status page",
- "distributedUptimeStatusLogoUploadButton": "Upload logo",
- "distributedUptimeStatusStandardMonitorsHeader": "Standard Monitors",
- "distributedUptimeStatusStandardMonitorsDescription": "Attach standard monitors to your status page.",
- "distributedUptimeStatusCreateYour": "Create your",
- "distributedUptimeStatusEditYour": "Edit your",
- "distributedUptimeStatusPublishedLabel": "Published and visible to the public",
- "distributedUptimeStatusCompanyNameLabel": "Company name",
- "distributedUptimeStatusPageAddressLabel": "Your status page address",
- "distributedUptimeStatus30Days": "30 days",
- "distributedUptimeStatus60Days": "60 days",
- "distributedUptimeStatus90Days": "90 days",
- "distributedUptimeStatusPageNotSetUp": "A status page is not set up.",
- "distributedUptimeStatusContactAdmin": "Please contact your administrator",
- "distributedUptimeStatusPageNotPublic": "This status page is not public.",
- "distributedUptimeStatusPageDeleteDialog": "Do you want to delete this status page?",
- "distributedUptimeStatusPageDeleteConfirm": "Yes, delete status page",
- "distributedUptimeStatusPageDeleteDescription": "Once deleted, your status page cannot be retrieved.",
- "distributedUptimeStatusDevices": "Devices",
- "distributedUptimeStatusUpt": "UPT",
- "distributedUptimeStatusUptBurned": "UPT Burned",
- "distributedUptimeStatusUptLogo": "Upt Logo",
- "incidentsTableNoIncidents": "No incidents recorded",
- "incidentsTablePaginationLabel": "incidents",
- "incidentsTableMonitorName": "Monitor Name",
- "incidentsTableStatus": "Status",
- "incidentsTableDateTime": "Date & Time",
- "incidentsTableStatusCode": "Status Code",
- "incidentsTableMessage": "Message",
- "incidentsOptionsHeader": "Incidents for:",
- "incidentsOptionsHeaderFilterBy": "Filter by:",
- "incidentsOptionsHeaderFilterAll": "All",
- "incidentsOptionsHeaderFilterDown": "Down",
- "incidentsOptionsHeaderFilterCannotResolve": "Cannot resolve",
- "incidentsOptionsHeaderShow": "Show:",
- "incidentsOptionsHeaderLastHour": "Last hour",
- "incidentsOptionsHeaderLastDay": "Last day",
- "incidentsOptionsHeaderLastWeek": "Last week",
- "incidentsOptionsPlaceholderAllServers": "All servers",
- "infrastructureCreateYour": "Create your",
- "infrastructureCreateGeneralSettingsDescription": "Here you can select the URL of the host, together with the friendly name and authorization secret to connect to the server agent.",
- "infrastructureServerRequirement": "The server you are monitoring must be running the",
- "infrastructureCustomizeAlerts": "Customize alerts",
- "infrastructureAlertNotificationDescription": "Send a notification to user(s) when thresholds exceed a specified percentage.",
- "infrastructureCreateMonitor": "Create Infrastructure Monitor",
- "infrastructureProtocol": "Protocol",
- "infrastructureServerUrlLabel": "Server URL",
- "infrastructureDisplayNameLabel": "Display name",
- "infrastructureAuthorizationSecretLabel": "Authorization secret",
- "gb": "GB",
- "mb": "MB",
- "mem": "Mem",
- "memoryUsage": "Memory usage",
- "cpu": "CPU",
- "cpuUsage": "CPU usage",
- "cpuTemperature": "CPU Temperature",
- "diskUsage": "Disk Usage",
- "used": "Used",
- "total": "Total",
- "cores": "Cores",
- "frequency": "Frequency",
- "status": "Status",
- "cpuPhysical": "CPU (Physical)",
- "cpuLogical": "CPU (Logical)",
- "cpuFrequency": "CPU Frequency",
- "avgCpuTemperature": "Average CPU Temperature",
- "memory": "Memory",
- "disk": "Disk",
- "uptime": "Uptime",
- "os": "OS",
- "host": "Host",
- "actions": "Actions",
- "integrations": "Integrations",
- "integrationsPrism": "Connect Prism to your favorite service.",
- "integrationsSlack": "Slack",
- "integrationsSlackInfo": "Connect with Slack and see incidents in a channel",
- "integrationsDiscord": "Discord",
- "integrationsDiscordInfo": "Connect with Discord and view incidents directly in a channel",
- "integrationsZapier": "Zapier",
- "integrationsZapierInfo": "Send all incidents to Zapier, and then see them everywhere",
- "commonSave": "Save",
- "createYour": "Create your",
- "createMonitor": "Create monitor",
- "pause": "Pause",
- "resume": "Resume",
- "editing": "Editing...",
- "url": "URL",
- "access": "Access",
- "timezone": "Timezone",
- "features": "Features",
- "administrator": "Administrator?",
- "loginHere": "Login here",
- "displayName": "Display name",
- "urlMonitor": "URL to monitor",
- "portToMonitor": "Port to monitor",
- "websiteMonitoring": "Website monitoring",
- "websiteMonitoringDescription": "Use HTTP(s) to monitor your website or API endpoint.",
- "pingMonitoring": "Ping monitoring",
- "pingMonitoringDescription": "Check whether your server is available or not.",
- "dockerContainerMonitoring": "Docker container monitoring",
- "dockerContainerMonitoringDescription": "Check whether your Docker container is running or not.",
- "portMonitoring": "Port monitoring",
- "portMonitoringDescription": "Check whether your port is open or not.",
- "createMaintenanceWindow": "Create maintenance window",
- "createMaintenance": "Create maintenance",
- "editMaintenance": "Edit maintenance",
- "maintenanceWindowName": "Maintenance Window Name",
- "friendlyNameInput": "Friendly name",
- "friendlyNamePlaceholder": "Maintenance at __ : __ for ___ minutes",
- "maintenanceRepeat": "Maintenance Repeat",
- "maintenance": "maintenance",
- "duration": "Duration",
- "addMonitors": "Add monitors",
- "window": "window",
- "cancel": "Cancel",
- "message": "Message",
- "low": "low",
- "high": "high",
- "statusCode": "Status code",
- "date&Time": "Date & Time",
- "type": "Type",
- "statusPageName": "Status page name",
- "publicURL": "Public URL",
- "repeat": "Repeat",
- "edit": "Edit",
- "createA": "Create a",
- "remove": "Remove",
- "maintenanceWindowDescription": "Your pings won't be sent during this time frame",
- "startTime": "Start time",
- "timeZoneInfo": "All dates and times are in GMT+0 time zone.",
- "monitorsToApply": "Monitors to apply maintenance window to",
- "nextWindow": "Next window",
- "notFoundButton": "Go to the main dashboard",
- "pageSpeedConfigureSettingsDescription": "Here you can select the URL of the host, together with the type of monitor.",
- "monitorDisplayName": "Monitor display name",
- "whenNewIncident": "When there is a new incident,",
- "notifySMS": "Notify via SMS (coming soon)",
- "notifyEmails": "Also notify via email to multiple addresses (coming soon)",
- "seperateEmails": "You can separate multiple emails with a comma",
- "checkFrequency": "Check frequency",
- "matchMethod": "Match Method",
- "expectedValue": "Expected value",
- "deleteDialogTitle": "Do you really want to delete this monitor?",
- "deleteDialogDescription": "Once deleted, this monitor cannot be retrieved.",
- "pageSpeedMonitor": "PageSpeed monitor",
- "shown": "Shown",
- "ago": "ago",
- "companyName": "Company name",
- "pageSpeedDetailsPerformanceReport": "Values are estimated and may vary.",
- "pageSpeedDetailsPerformanceReportCalculator": "See calculator",
- "checkingEvery": "Checking every",
- "statusPageCreateSettings": "If your status page is ready, you can mark it as published.",
- "basicInformation": "Basic Information",
- "statusPageCreateBasicInfoDescription": "Define company name and the subdomain that your status page points to.",
- "statusPageCreateSelectTimeZoneDescription": "Select the timezone that your status page will be displayed in.",
- "statusPageCreateAppearanceDescription": "Define the default look and feel of your public status page.",
- "statusPageCreateSettingsCheckboxLabel": "Published and visible to the public",
- "statusPageCreateBasicInfoStatusPageAddress": "Your status page address",
- "statusPageCreateTabsContent": "Status page servers",
- "statusPageCreateTabsContentDescription": "You can add any number of servers that you monitor to your status page. You can also reorder them for the best viewing experience.",
- "statusPageCreateTabsContentFeaturesDescription": "Show more details on the status page",
- "showCharts": "Show charts",
- "showUptimePercentage": "Show uptime percentage",
- "removeLogo": "Remove Logo",
- "statusPageStatus": "A public status page is not set up.",
- "statusPageStatusContactAdmin": "Please contact to your administrator",
- "statusPageStatusNotPublic": "This status page is not public.",
- "statusPageStatusNoPage": "There's no status page here.",
- "statusPageStatusServiceStatus": "Service status",
- "deleteStatusPage": "Do you want to delete this status page?",
- "deleteStatusPageConfirm": "Yes, delete status page",
- "deleteStatusPageDescription": "Once deleted, your status page cannot be retrieved.",
- "uptimeCreate": "The expected value is used to match against response result, and the match determines the status.",
- "uptimeCreateJsonPath": "This expression will be evaluated against the reponse JSON data and the result will be used to match against the expected value. See",
- "uptimeCreateJsonPathQuery": "for query language documentation.",
- "maintenanceTableActionMenuDialogTitle": "Do you really want to remove this maintenance window?",
- "infrastructureEditYour": "Edit your",
- "infrastructureEditMonitor": "Save Infrastructure Monitor",
- "infrastructureMonitorCreated": "Infrastructure monitor created successfully!",
- "infrastructureMonitorUpdated": "Infrastructure monitor updated successfully!",
- "errorInvalidTypeId": "Invalid notification type provided",
- "errorInvalidFieldId": "Invalid field ID provided",
- "inviteNoTokenFound": "No invite token found",
- "pageSpeedWarning": "Warning: You haven't added a Google PageSpeed API key yet. Without it, the PageSpeed monitor won't function.",
- "pageSpeedLearnMoreLink": "Click here",
- "pageSpeedAddApiKey": "to add your API key.",
- "update": "Update",
- "invalidFileFormat": "Unsupported file format!",
- "invalidFileSize": "File size is too large!",
- "ClickUpload": "Click to upload",
- "DragandDrop": "drag and drop",
- "MaxSize": "Maximum Size",
- "SupportedFormats": "Supported formats",
- "FirstName": "First name",
- "LastName": "Last name",
- "EmailDescriptionText": "This is your current email address — it cannot be changed.",
- "ignoreTLSError": "Ignore TLS/SSL error",
- "tlsErrorIgnored": "TLS/SSL errors ignored",
- "ignoreTLSErrorDescription": "Ignore TLS/SSL errors and continue checking the website's availability",
- "YourPhoto": "Profile photo",
- "PhotoDescriptionText": "This photo will be displayed in your profile page.",
- "save": "Save",
- "DeleteAccountTitle": "Remove account",
- "DeleteAccountButton": "Remove account",
- "DeleteDescriptionText": "This will remove the account and all associated data from the server. This isn't reversible.",
- "DeleteAccountWarning": "Removing your account means you won't be able to sign in again and all your data will be removed. This isn't reversible.",
- "DeleteWarningTitle": "Really remove this account?",
- "authRegisterFirstName": "Name",
- "authRegisterLastName": "Surname",
- "authRegisterEmail": "Email",
- "authRegisterEmailRequired": "To continue, please enter your email address",
- "authRegisterEmailInvalid": "Please enter a valid email address",
- "bulkImport": {
- "title": "Bulk Import",
- "selectFileTips": "Select CSV file to upload",
- "selectFileDescription": "You can download our template or sample",
- "selectFile": "Select File",
- "parsingFailed": "Parsing failed",
- "uploadSuccess": "Monitors created successfully!",
- "validationFailed": "Validation failed",
- "noFileSelected": "No file selected",
- "fallbackPage": "Import a file to upload a list of servers in bulk"
- },
- "publicLink": "Public link",
- "maskedPageSpeedKeyPlaceholder": "*************************************",
- "pageSpeedApiKeyFieldTitle": "Google PageSpeed API key",
- "pageSpeedApiKeyFieldLabel": "PageSpeed API key",
- "pageSpeedApiKeyFieldDescription": "Enter your Google PageSpeed API key to enable pagespeed monitoring. Click Reset to update the key.",
- "pageSpeedApiKeyFieldResetLabel": "API key is set. Click Reset to change it.",
- "reset": "Reset",
- "createNew": "Create new",
- "greeting": {
- "prepend": "Hey there",
- "append": "The afternoon is your playground—let's make it epic!",
- "overview": "Here's an overview of your {{type}} monitors."
- },
- "monitorStatus": {
- "up": "up",
- "down": "down",
- "paused": "paused"
- },
- "roles": {
- "superAdmin": "Super admin",
- "admin": "Admin",
- "teamMember": "Team member",
- "demoUser": "Demo user"
- },
- "teamPanel": {
- "teamMembers": "Team members",
- "filter": {
- "all": "All",
- "member": "Member"
- },
- "inviteTeamMember": "Invite a team member",
- "inviteNewTeamMember": "Invite new team member",
- "inviteDescription": "When you add a new team member, they will get access to all monitors.",
- "email": "Email",
- "selectRole": "Select role",
- "inviteLink": "Invite link",
- "cancel": "Cancel",
- "noMembers": "There are no team members with this role",
- "getToken": "Get token",
- "emailToken": "E-mail token",
- "table": {
- "name": "Name",
- "email": "Email",
- "role": "Role",
- "created": "Created"
- }
- },
- "monitorState": {
- "paused": "paused",
- "resumed": "resumed",
- "active": "active"
- },
- "menu": {
- "uptime": "Uptime",
- "pagespeed": "Pagespeed",
- "infrastructure": "Infrastructure",
- "distributedUptime": "Distributed uptime",
- "incidents": "Incidents",
- "statusPages": "Status pages",
- "maintenance": "Maintenance",
- "integrations": "Integrations",
- "settings": "Settings",
- "support": "Support",
- "discussions": "Discussions",
- "docs": "Docs",
- "changelog": "Changelog",
- "profile": "Profile",
- "password": "Password",
- "team": "Team"
- }
+ "reset": "Reset",
+ "createNew": "Create new",
+ "greeting": {
+ "prepend": "Hey there",
+ "append": "The afternoon is your playground—let's make it epic!",
+ "overview": "Here's an overview of your {{type}} monitors."
+ },
+ "monitorStatus": {
+ "up": "up",
+ "down": "down",
+ "paused": "paused"
+ },
+ "roles": {
+ "superAdmin": "Super admin",
+ "admin": "Admin",
+ "teamMember": "Team member",
+ "demoUser": "Demo user"
+ },
+ "teamPanel": {
+ "teamMembers": "Team members",
+ "filter": {
+ "all": "All",
+ "member": "Member"
+ },
+ "inviteTeamMember": "Invite a team member",
+ "inviteNewTeamMember": "Invite new team member",
+ "inviteDescription": "When you add a new team member, they will get access to all monitors.",
+ "email": "Email",
+ "selectRole": "Select role",
+ "inviteLink": "Invite link",
+ "cancel": "Cancel",
+ "noMembers": "There are no team members with this role",
+ "getToken": "Get token",
+ "emailToken": "E-mail token",
+ "table": {
+ "name": "Name",
+ "email": "Email",
+ "role": "Role",
+ "created": "Created"
+ }
+ },
+ "monitorState": {
+ "paused": "Paused",
+ "resumed": "Resumed",
+ "active": "Active"
+ },
+ "menu": {
+ "uptime": "Uptime",
+ "pagespeed": "Pagespeed",
+ "infrastructure": "Infrastructure",
+ "distributedUptime": "Distributed uptime",
+ "incidents": "Incidents",
+ "statusPages": "Status pages",
+ "maintenance": "Maintenance",
+ "integrations": "Integrations",
+ "settings": "Settings",
+ "support": "Support",
+ "discussions": "Discussions",
+ "docs": "Docs",
+ "changelog": "Changelog",
+ "profile": "Profile",
+ "password": "Password",
+ "team": "Team"
+ }
}
-
diff --git a/docker/dev/docker-compose.yaml b/docker/dev/docker-compose.yaml
index 50b573d38..292d0b1d5 100755
--- a/docker/dev/docker-compose.yaml
+++ b/docker/dev/docker-compose.yaml
@@ -24,8 +24,6 @@ services:
redis:
image: uptime_redis:latest
restart: always
- ports:
- - "6379:6379"
volumes:
- ./redis/data:/data
healthcheck:
@@ -38,8 +36,6 @@ services:
image: uptime_mongo:latest
restart: always
command: ["mongod", "--quiet", "--replSet", "rs0", "--bind_ip_all"]
- ports:
- - "27017:27017"
volumes:
- ./mongo/data:/data/db
healthcheck:
diff --git a/docker/dist/docker-compose.yaml b/docker/dist/docker-compose.yaml
index ca8e24061..0bc0d89e8 100755
--- a/docker/dist/docker-compose.yaml
+++ b/docker/dist/docker-compose.yaml
@@ -14,7 +14,7 @@ services:
image: ghcr.io/bluewave-labs/checkmate:backend-dist
restart: always
ports:
- - "5000:5000"
+ - "52345:52345"
depends_on:
- redis
- mongodb
@@ -27,8 +27,6 @@ services:
redis:
image: ghcr.io/bluewave-labs/checkmate:redis-dist
restart: always
- ports:
- - "6379:6379"
volumes:
- ./redis/data:/data
healthcheck:
@@ -43,8 +41,6 @@ services:
volumes:
- ./mongo/data:/data/db
command: ["mongod", "--quiet", "--replSet", "rs0", "--bind_ip_all"]
- ports:
- - "27017:27017"
healthcheck:
test: echo "try { rs.status() } catch (err) { rs.initiate({_id:'rs0',members:[{_id:0,host:'mongodb:27017'}]}) }" | mongosh --port 27017 --quiet
interval: 5s
diff --git a/docker/prod/docker-compose.yaml b/docker/prod/docker-compose.yaml
index db142396d..9cc4a82cb 100755
--- a/docker/prod/docker-compose.yaml
+++ b/docker/prod/docker-compose.yaml
@@ -36,8 +36,6 @@ services:
redis:
image: ghcr.io/bluewave-labs/checkmate:redis-demo
restart: always
- ports:
- - "6379:6379"
volumes:
- ./redis/data:/data
healthcheck:
@@ -50,8 +48,6 @@ services:
image: ghcr.io/bluewave-labs/checkmate:mongo-demo
restart: always
command: ["mongod", "--quiet", "--replSet", "rs0", "--bind_ip_all"]
- ports:
- - "27017:27017"
volumes:
- ./mongo/data:/data/db
# - ./mongo/init/init.js:/docker-entrypoint-initdb.d/init.js // No longer needed
diff --git a/server/controllers/monitorController.js b/server/controllers/monitorController.js
index 5ab654375..9a42ea4e5 100755
--- a/server/controllers/monitorController.js
+++ b/server/controllers/monitorController.js
@@ -649,9 +649,8 @@ class MonitorController {
sendTestEmail = async (req, res, next) => {
try {
const { to } = req.body;
-
if (!to || typeof to !== "string") {
- return res.error({ msg: this.stringService.errorForValidEmailAddress });
+ throw new Error(this.stringService.errorForValidEmailAddress);
}
const subject = this.stringService.testEmailSubject;
diff --git a/server/controllers/settingsController.js b/server/controllers/settingsController.js
index 4d561216e..b557e8dd6 100755
--- a/server/controllers/settingsController.js
+++ b/server/controllers/settingsController.js
@@ -13,12 +13,25 @@ class SettingsController {
getAppSettings = async (req, res, next) => {
const dbSettings = await this.settingsService.getDBSettings();
const sanitizedSettings = { ...dbSettings };
+
+ const returnSettings = {
+ pagespeedKeySet: false,
+ emailPasswordSet: false,
+ };
+
if (typeof sanitizedSettings.pagespeedApiKey !== "undefined") {
- sanitizedSettings.pagespeedApiKey = "********";
+ returnSettings.pagespeedKeySet = true;
+ delete sanitizedSettings.pagespeedApiKey;
}
+ if (typeof sanitizedSettings.systemEmailPassword !== "undefined") {
+ returnSettings.emailPasswordSet = true;
+ delete sanitizedSettings.systemEmailPassword;
+ }
+
+ returnSettings.settings = sanitizedSettings;
return res.success({
msg: this.stringService.getAppSettings,
- data: sanitizedSettings,
+ data: returnSettings,
});
};
diff --git a/server/db/models/AppSettings.js b/server/db/models/AppSettings.js
index 3942305e8..06ba8c1df 100755
--- a/server/db/models/AppSettings.js
+++ b/server/db/models/AppSettings.js
@@ -2,13 +2,16 @@ import mongoose from "mongoose";
const AppSettingsSchema = mongoose.Schema(
{
+ checkTTL: {
+ type: Number,
+ default: 30,
+ },
language: {
type: String,
default: "gb",
},
pagespeedApiKey: {
type: String,
- default: "",
},
systemEmailHost: {
type: String,
@@ -22,6 +25,9 @@ const AppSettingsSchema = mongoose.Schema(
systemEmailPassword: {
type: String,
},
+ systemEmailUser: {
+ type: String,
+ },
singleton: {
type: Boolean,
required: true,
diff --git a/server/db/mongo/modules/settingsModule.js b/server/db/mongo/modules/settingsModule.js
index b817d2a32..d3568ad32 100755
--- a/server/db/mongo/modules/settingsModule.js
+++ b/server/db/mongo/modules/settingsModule.js
@@ -14,12 +14,22 @@ const getAppSettings = async () => {
const updateAppSettings = async (newSettings) => {
try {
- console.log(newSettings);
- const settings = await AppSettings.findOneAndUpdate(
- {},
- { $set: newSettings },
- { new: true, upsert: true }
- );
+ const update = { $set: { ...newSettings } };
+
+ if (newSettings.pagespeedApiKey === "") {
+ update.$unset = { pagespeedApiKey: "" };
+ delete update.$set.pagespeedApiKey;
+ }
+
+ if (newSettings.systemEmailPassword === "") {
+ update.$unset = { systemEmailPassword: "" };
+ delete update.$set.systemEmailPassword;
+ }
+
+ const settings = await AppSettings.findOneAndUpdate({}, update, {
+ new: true,
+ upsert: true,
+ });
return settings;
} catch (error) {
error.service = SERVICE_NAME;
diff --git a/server/service/emailService.js b/server/service/emailService.js
index c9e34eeda..57f8c78e6 100755
--- a/server/service/emailService.js
+++ b/server/service/emailService.js
@@ -29,7 +29,10 @@ class EmailService {
this.mjml2html = mjml2html;
this.nodemailer = nodemailer;
this.logger = logger;
+ this.init();
+ }
+ init = async () => {
/**
* Loads an email template from the filesystem.
*
@@ -67,34 +70,14 @@ class EmailService {
serverIsUpTemplate: this.loadTemplate("serverIsUp"),
passwordResetTemplate: this.loadTemplate("passwordReset"),
hardwareIncidentTemplate: this.loadTemplate("hardwareIncident"),
- testEmailTemplate: this.loadTemplate("testEmailTemplate")
+ testEmailTemplate: this.loadTemplate("testEmailTemplate"),
};
/**
* The email transporter used to send emails.
* @type {Object}
*/
-
- const {
- systemEmailHost,
- systemEmailPort,
- systemEmailUser,
- systemEmailAddress,
- systemEmailPassword,
- } = this.settingsService.getSettings();
-
- const emailConfig = {
- host: systemEmailHost,
- port: systemEmailPort,
- secure: true,
- auth: {
- user: systemEmailUser || systemEmailAddress,
- pass: systemEmailPassword,
- },
- };
-
- this.transporter = this.nodemailer.createTransport(emailConfig);
- }
+ };
/**
* Asynchronously builds and sends an email using a specified template and context.
@@ -106,6 +89,28 @@ class EmailService {
* @returns {Promise} A promise that resolves to the messageId of the sent email.
*/
buildAndSendEmail = async (template, context, to, subject) => {
+ // TODO - Consider an update transporter method so this only needs to be recreated when smtp settings change
+ const {
+ systemEmailHost,
+ systemEmailPort,
+ systemEmailUser,
+ systemEmailAddress,
+ systemEmailPassword,
+ } = await this.settingsService.getDBSettings();
+
+ const emailConfig = {
+ host: systemEmailHost,
+ port: systemEmailPort,
+ secure: true,
+ auth: {
+ user: systemEmailUser || systemEmailAddress,
+ pass: systemEmailPassword,
+ },
+ connectionTimeout: 5000,
+ };
+
+ this.transporter = this.nodemailer.createTransport(emailConfig);
+
const buildHtml = async (template, context) => {
try {
const mjml = this.templateLookup[template](context);
diff --git a/server/service/settingsService.js b/server/service/settingsService.js
index 548d76eee..290e645e9 100755
--- a/server/service/settingsService.js
+++ b/server/service/settingsService.js
@@ -59,7 +59,17 @@ class SettingsService {
}
async getDBSettings() {
- const settings = await this.appSettings.findOne({ singleton: true }).lean();
+ let settings = await this.appSettings
+ .findOne({ singleton: true })
+ .select("-__v -_id -createdAt -updatedAt -singleton")
+ .lean();
+ if (settings === null) {
+ await this.appSettings.create({});
+ settings = await this.appSettings
+ .findOne({ singleton: true })
+ .select("-__v -_id -createdAt -updatedAt -singleton")
+ .lean();
+ }
return settings;
}
}
diff --git a/server/validation/joi.js b/server/validation/joi.js
index bd402fb8a..997d35d65 100755
--- a/server/validation/joi.js
+++ b/server/validation/joi.js
@@ -424,21 +424,14 @@ const editMaintenanceByIdWindowBodyValidation = joi.object({
// SettingsValidation
//****************************************
const updateAppSettingsBodyValidation = joi.object({
- apiBaseUrl: joi.string().allow(""),
- logLevel: joi.string().valid("debug", "none", "error", "warn").allow(""),
- clientHost: joi.string().allow(""),
- dbType: joi.string().allow(""),
- dbConnectionString: joi.string().allow(""),
- redisHost: joi.string().allow(""),
- redisPort: joi.number().allow(null, ""),
- redisUrl: joi.string().allow(""),
- jwtTTL: joi.string().allow(""),
+ checkTTL: joi.number().allow(""),
pagespeedApiKey: joi.string().allow(""),
language: joi.string().allow(""),
systemEmailHost: joi.string().allow(""),
systemEmailPort: joi.number().allow(""),
systemEmailAddress: joi.string().allow(""),
systemEmailPassword: joi.string().allow(""),
+ systemEmailUser: joi.string().allow(""),
});
//****************************************