diff --git a/.github/scripts/download-translations.js b/.github/scripts/download-translations.js index 8f35be0de..7a8125ab8 100644 --- a/.github/scripts/download-translations.js +++ b/.github/scripts/download-translations.js @@ -6,7 +6,9 @@ import { URLSearchParams } from "url"; // POEditor API information const API_TOKEN = process.env.POEDITOR_API; const PROJECT_ID = process.env.POEDITOR_PROJECT_ID; -const LANGUAGES = (process.env.LANGUAGES || "tr,en").split(","); +const LANGUAGES = ( + process.env.LANGUAGES || "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja" +).split(","); const EXPORT_FORMAT = process.env.EXPORT_FORMAT || "key_value_json"; // POEditor API endpoint diff --git a/.github/workflows/poeditor-sync.yml b/.github/workflows/poeditor-sync.yml index 83dac7f09..a8b330290 100644 --- a/.github/workflows/poeditor-sync.yml +++ b/.github/workflows/poeditor-sync.yml @@ -7,7 +7,7 @@ on: languages: description: "Languages to synchronize (comma separated, e.g.: tr,en,es)" required: false - default: "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr" + default: "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja" format: description: "Export format (key_value_json or json)" required: false diff --git a/README.md b/README.md index d584d832d..2dfcc2ffb 100644 --- a/README.md +++ b/README.md @@ -94,14 +94,16 @@ Feel free to ask questions or share your ideas - we'd love to hear from you! - E-mail, Webhooks, Discord, Telegram, Slack notifications - Scheduled maintenance - JSON query monitoring -- Support for multiple languages +- Multi-language support for English, German, Japanese, Portuguese (Brazil), Russian, Turkish, Ukrainian, Vietnamese, Chinese (Traditional, Taiwan) -**Short term roadmap:** ([Milestone 2.2](https://github.com/bluewave-labs/Checkmate/milestone/8)) +**Short term roadmap:** - Better notifications - Network monitoring - ..and a few more features +If you would like to sponsor an additional feature, [see this page](https://checkmate.so/sponsored-features). + ## Screenshots

diff --git a/client/src/Components/ArrowLeft/index.jsx b/client/src/Components/ArrowLeft/index.jsx new file mode 100644 index 000000000..7909441f3 --- /dev/null +++ b/client/src/Components/ArrowLeft/index.jsx @@ -0,0 +1,35 @@ +import LeftArrow from "../../assets/icons/left-arrow.svg?react"; +import LeftArrowDouble from "../../assets/icons/left-arrow-double.svg?react"; +import LeftArrowLong from "../../assets/icons/left-arrow-long.svg?react"; +import PropTypes from "prop-types"; + +const ArrowLeft = ({ type, color = "#667085", ...props }) => { + if (type === "double") { + return ( + + ); + } else if (type === "long") { + return ( + + ); + } else { + return ( + + ); + } +}; + +ArrowLeft.propTypes = { + color: PropTypes.string, + type: PropTypes.oneOf(["double", "long", "default"]), +}; +export default ArrowLeft; diff --git a/client/src/Components/ArrowRight/index.jsx b/client/src/Components/ArrowRight/index.jsx new file mode 100644 index 000000000..335bc19f2 --- /dev/null +++ b/client/src/Components/ArrowRight/index.jsx @@ -0,0 +1,28 @@ +import RightArrow from "../../assets/icons/right-arrow.svg?react"; +import RightArrowDouble from "../../assets/icons/right-arrow-double.svg?react"; +import PropTypes from "prop-types"; + +const ArrowRight = ({ type, color = "#667085", ...props }) => { + if (type === "double") { + return ( + + ); + } else { + return ( + + ); + } +}; + +ArrowRight.propTypes = { + type: PropTypes.oneOf(["double", "default"]), + color: PropTypes.string, +}; + +export default ArrowRight; diff --git a/client/src/Components/Avatar/index.jsx b/client/src/Components/Avatar/index.jsx index aa2050827..d089080ff 100644 --- a/client/src/Components/Avatar/index.jsx +++ b/client/src/Components/Avatar/index.jsx @@ -15,7 +15,7 @@ import { useTheme } from "@emotion/react"; * */ -const Avatar = ({ src, small, sx }) => { +const Avatar = ({ src, small, sx, onClick = () => {} }) => { const { user } = useSelector((state) => state.auth); const theme = useTheme(); @@ -31,6 +31,7 @@ const Avatar = ({ src, small, sx }) => { return ( sx={{ color: variant === "info" - ? theme.palette.primary.contrastTextTertiary + ? theme.palette.primary.contrastTextSecondary : colors[variant], - opacity: 0.8, + opacity: 0.9, + fontWeight: 450, }} > {noHighlightText && {noHighlightText}}{" "} diff --git a/client/src/Components/ConfigBox/index.jsx b/client/src/Components/ConfigBox/index.jsx index e26e49e8d..01b35729e 100644 --- a/client/src/Components/ConfigBox/index.jsx +++ b/client/src/Components/ConfigBox/index.jsx @@ -20,6 +20,11 @@ const ConfigBox = styled(Stack)(({ theme }) => ({ borderRightColor: theme.palette.primary.lowContrast, paddingRight: theme.spacing(15), paddingLeft: theme.spacing(15), + backgroundColor: theme.palette.tertiary.background, + "& :is(h1, h2):first-of-type": { + fontWeight: 600, + marginBottom: theme.spacing(4), + }, }, "& > div:last-of-type": { flex: 1, diff --git a/client/src/Components/Fallback/FallBackActionButtons.jsx b/client/src/Components/Fallback/FallBackActionButtons.jsx index fbe8fe807..144b16501 100644 --- a/client/src/Components/Fallback/FallBackActionButtons.jsx +++ b/client/src/Components/Fallback/FallBackActionButtons.jsx @@ -17,6 +17,7 @@ const FallbackActionButtons = ({ link, type }) => { ); diff --git a/client/src/Components/Table/index.jsx b/client/src/Components/Table/index.jsx index b6d31bebe..66db50fbe 100644 --- a/client/src/Components/Table/index.jsx +++ b/client/src/Components/Table/index.jsx @@ -70,6 +70,9 @@ const DataTable = ({ backgroundColor: theme.palette.primary.main, color: theme.palette.primary.contrastTextSecondary, }, + "& .MuiTableBody-root .MuiTableRow-root:last-child .MuiTableCell-root": { + borderBottom: "none", + }, }} > diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js index 4f0e9c049..616a37877 100644 --- a/client/src/Hooks/monitorHooks.js +++ b/client/src/Hooks/monitorHooks.js @@ -300,6 +300,28 @@ const useCreateMonitor = () => { return [createMonitor, isLoading]; }; +const useFetchGlobalSettings = () => { + const [isLoading, setIsLoading] = useState(true); + const [globalSettings, setGlobalSettings] = useState(undefined); + useEffect(() => { + const fetchGlobalSettings = async () => { + try { + const res = await networkService.getAppSettings(); + setGlobalSettings(res?.data); + } catch (error) { + console.error("Failed to fetch global settings:", error); + createToast({ body: "Failed to load global settings" }); + } finally { + setIsLoading(false); + } + }; + + fetchGlobalSettings(); + }, []); + + return [globalSettings, isLoading]; +}; + const useDeleteMonitor = () => { const [isLoading, setIsLoading] = useState(false); const navigate = useNavigate(); @@ -499,6 +521,7 @@ export { useFetchUptimeMonitorById, useFetchHardwareMonitorById, useCreateMonitor, + useFetchGlobalSettings, useDeleteMonitor, useUpdateMonitor, usePauseMonitor, diff --git a/client/src/Pages/Auth/Login/index.jsx b/client/src/Pages/Auth/Login/index.jsx index 098fbbf08..8c827f6fb 100644 --- a/client/src/Pages/Auth/Login/index.jsx +++ b/client/src/Pages/Auth/Login/index.jsx @@ -7,7 +7,9 @@ import { PasswordEndAdornment } from "../../../Components/Inputs/TextInput/Adorn import { loginCredentials } from "../../../Validation/validation"; import TextLink from "../../../Components/TextLink"; import Typography from "@mui/material/Typography"; - +import Box from "@mui/material/Box"; +import Logo from "../../../assets/icons/checkmate-icon.svg?react"; +import Background from "../../../assets/Images/background-grid.svg?react"; // Utils import { login } from "../../../Features/Auth/authSlice"; import { useNavigate } from "react-router-dom"; @@ -92,34 +94,105 @@ const Login = () => { - - - {t("auth.login.heading")} + + + + + + + + + + + + + + {t("auth.login.welcome")} + + {t("auth.login.heading")} + { type="password" name="password" label={t("auth.common.inputs.password.label")} - isRequired={true} placeholder="••••••••••" autoComplete="current-password" value={form.password} @@ -144,7 +216,7 @@ const Login = () => { variant="contained" color="accent" type="submit" - sx={{ width: "30%", alignSelf: "flex-end" }} + sx={{ width: "100%", alignSelf: "center", fontWeight: 700 }} > Login diff --git a/client/src/Pages/Auth/components/AuthHeader.jsx b/client/src/Pages/Auth/components/AuthHeader.jsx index c6ad46a47..ce454c065 100644 --- a/client/src/Pages/Auth/components/AuthHeader.jsx +++ b/client/src/Pages/Auth/components/AuthHeader.jsx @@ -9,7 +9,7 @@ import ThemeSwitch from "../../../Components/ThemeSwitch"; import { useTheme } from "@mui/material/styles"; import { useTranslation } from "react-i18next"; -const AuthHeader = () => { +const AuthHeader = ({ hideLogo = false }) => { // Hooks const theme = useTheme(); const { t } = useTranslation(); @@ -27,8 +27,12 @@ const AuthHeader = () => { alignItems="center" gap={theme.spacing(4)} > - - {t("common.appName")} + {!hideLogo && ( + <> + + {t("common.appName")} + + )} { const theme = useTheme(); let sharedStyles = { position: "absolute", right: 8, - opacity: 0.5, - "& svg path": { stroke: theme.palette.primary.contrastTextTertiary }, + "& svg": { + width: 20, + height: 20, + opacity: 0.9, + "& path": { stroke: theme.palette.primary.contrastTextTertiary, strokeWidth: 1.7 }, + }, }; let color; @@ -22,28 +26,28 @@ const StatusBox = ({ title, value, status }) => { color = theme.palette.success.lowContrast; icon = ( - + ); } else if (status === "down") { color = theme.palette.error.lowContrast; icon = ( - + ); } else if (status === "paused") { color = theme.palette.warning.lowContrast; icon = ( - + ); } else { color = theme.palette.accent.main; icon = ( - + ); } diff --git a/client/src/Pages/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx index 0800bb500..ae95cce33 100644 --- a/client/src/Pages/Infrastructure/Create/index.jsx +++ b/client/src/Pages/Infrastructure/Create/index.jsx @@ -1,74 +1,51 @@ -// React, Redux, Router -import { useTheme } from "@emotion/react"; -import { useParams } from "react-router-dom"; -import { useState, useEffect } from "react"; -import { useSelector } from "react-redux"; - -// Utility and Network -import { infrastructureMonitorValidation } from "../../../Validation/validation"; -import { useFetchHardwareMonitorById } from "../../../Hooks/monitorHooks"; -import { capitalizeFirstLetter } from "../../../Utils/stringUtils"; -import { useTranslation } from "react-i18next"; -import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications"; -import NotificationsConfig from "../../../Components/NotificationConfig"; -import { useUpdateMonitor, useCreateMonitor } from "../../../Hooks/monitorHooks"; - -// MUI -import { Box, Stack, Typography, Button, ButtonGroup } from "@mui/material"; - //Components import Breadcrumbs from "../../../Components/Breadcrumbs"; -import Link from "../../../Components/Link"; import ConfigBox from "../../../Components/ConfigBox"; +import Dialog from "../../../Components/Dialog"; +import FieldWrapper from "../../../Components/Inputs/FieldWrapper"; +import Link from "../../../Components/Link"; +import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline"; +import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded"; +import PulseDot from "../../../Components/Animated/PulseDot"; +import Select from "../../../Components/Inputs/Select"; import TextInput from "../../../Components/Inputs/TextInput"; +import { Box, Stack, Tooltip, Typography, Button, ButtonGroup } from "@mui/material"; +import { CustomThreshold } from "./Components/CustomThreshold"; import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments"; import { createToast } from "../../../Utils/toastUtils"; -import Select from "../../../Components/Inputs/Select"; -import { CustomThreshold } from "./Components/CustomThreshold"; -import FieldWrapper from "../../../Components/Inputs/FieldWrapper"; - -const SELECT_VALUES = [ - { _id: 0.25, name: "15 seconds" }, - { _id: 0.5, name: "30 seconds" }, - { _id: 1, name: "1 minute" }, - { _id: 2, name: "2 minutes" }, - { _id: 5, name: "5 minutes" }, - { _id: 10, name: "10 minutes" }, -]; - -const METRICS = ["cpu", "memory", "disk", "temperature"]; -const METRIC_PREFIX = "usage_"; -const MS_PER_MINUTE = 60000; - -const hasAlertError = (errors) => { - return Object.keys(errors).filter((k) => k.startsWith(METRIC_PREFIX)).length > 0; -}; - -const getAlertError = (errors) => { - return Object.keys(errors).find((key) => key.startsWith(METRIC_PREFIX)) - ? errors[Object.keys(errors).find((key) => key.startsWith(METRIC_PREFIX))] - : null; -}; +// Utils +import NotificationsConfig from "../../../Components/NotificationConfig"; +import { capitalizeFirstLetter } from "../../../Utils/stringUtils"; +import { infrastructureMonitorValidation } from "../../../Validation/validation"; +import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications"; +import { useMonitorUtils } from "../../../Hooks/useMonitorUtils"; +import { useParams } from "react-router-dom"; +import { useSelector } from "react-redux"; +import { useState, useEffect } from "react"; +import { useTheme } from "@emotion/react"; +import { useTranslation } from "react-i18next"; +import { + useCreateMonitor, + useDeleteMonitor, + useFetchGlobalSettings, + useFetchHardwareMonitorById, + usePauseMonitor, + useUpdateMonitor, +} from "../../../Hooks/monitorHooks"; const CreateInfrastructureMonitor = () => { - const theme = useTheme(); const { user } = useSelector((state) => state.auth); const { monitorId } = useParams(); - const { t } = useTranslation(); - - // Determine if we are creating or editing const isCreate = typeof monitorId === "undefined"; - // Fetch monitor details if editing - const [monitor, isLoading, networkError] = useFetchHardwareMonitorById({ monitorId }); - const [notifications, notificationsAreLoading, notificationsError] = - useGetNotificationsByTeamId(); - const [updateMonitor, isUpdating] = useUpdateMonitor(); - const [createMonitor, isCreating] = useCreateMonitor(); + const theme = useTheme(); + const { t } = useTranslation(); // State const [errors, setErrors] = useState({}); const [https, setHttps] = useState(false); + const [isOpen, setIsOpen] = useState(false); + const [updateTrigger, setUpdateTrigger] = useState(false); const [infrastructureMonitor, setInfrastructureMonitor] = useState({ url: "", name: "", @@ -86,36 +63,107 @@ const CreateInfrastructureMonitor = () => { secret: "", }); + // Fetch monitor details if editing + const { statusColor, pagespeedStatusMsg, determineState } = useMonitorUtils(); + const [monitor, isLoading] = useFetchHardwareMonitorById({ + monitorId, + updateTrigger, + }); + const [createMonitor, isCreating] = useCreateMonitor(); + const [deleteMonitor, isDeleting] = useDeleteMonitor(); + const [globalSettings, globalSettingsLoading] = useFetchGlobalSettings(); + const [notifications, notificationsAreLoading] = useGetNotificationsByTeamId(); + const [pauseMonitor, isPausing] = usePauseMonitor(); + const [updateMonitor, isUpdating] = useUpdateMonitor(); + + const FREQUENCIES = [ + { _id: 0.25, name: t("time.fifteenSeconds") }, + { _id: 0.5, name: t("time.thirtySeconds") }, + { _id: 1, name: t("time.oneMinute") }, + { _id: 2, name: t("time.twoMinutes") }, + { _id: 5, name: t("time.fiveMinutes") }, + { _id: 10, name: t("time.tenMinutes") }, + ]; + const CRUMBS = [ + { name: "Infrastructure monitors", path: "/infrastructure" }, + ...(isCreate + ? [{ name: "Create", path: "/infrastructure/create" }] + : [ + { name: "Details", path: `/infrastructure/${monitorId}` }, + { name: "Configure", path: `/infrastructure/configure/${monitorId}` }, + ]), + ]; + const METRICS = ["cpu", "memory", "disk", "temperature"]; + const METRIC_PREFIX = "usage_"; + const MS_PER_MINUTE = 60000; + + const hasAlertError = (errors) => { + return Object.keys(errors).filter((k) => k.startsWith(METRIC_PREFIX)).length > 0; + }; + + const getAlertError = (errors) => { + const errorKey = Object.keys(errors).find((key) => key.startsWith(METRIC_PREFIX)); + return errorKey ? errors[errorKey] : null; + }; + // Populate form fields if editing useEffect(() => { - if (isCreate || !monitor) return; + if (isCreate) { + if (globalSettingsLoading) return; - setInfrastructureMonitor({ - url: monitor.url.replace(/^https?:\/\//, ""), - name: monitor.name || "", - notifications: monitor.notifications, - interval: monitor.interval / MS_PER_MINUTE, - cpu: monitor.thresholds?.usage_cpu !== undefined, - usage_cpu: monitor.thresholds?.usage_cpu ? monitor.thresholds.usage_cpu * 100 : "", + const gt = globalSettings?.data?.settings?.globalThresholds || {}; - memory: monitor.thresholds?.usage_memory !== undefined, - usage_memory: monitor.thresholds?.usage_memory - ? monitor.thresholds.usage_memory * 100 - : "", + setHttps(false); - disk: monitor.thresholds?.usage_disk !== undefined, - usage_disk: monitor.thresholds?.usage_disk - ? monitor.thresholds.usage_disk * 100 - : "", + setInfrastructureMonitor({ + url: "", + name: "", + notifications: [], + interval: 0.25, + cpu: gt.cpu !== undefined, + usage_cpu: gt.cpu !== undefined ? gt.cpu.toString() : "", + memory: gt.memory !== undefined, + usage_memory: gt.memory !== undefined ? gt.memory.toString() : "", + disk: gt.disk !== undefined, + usage_disk: gt.disk !== undefined ? gt.disk.toString() : "", + temperature: gt.temperature !== undefined, + usage_temperature: gt.temperature !== undefined ? gt.temperature.toString() : "", + secret: "", + }); + } else if (monitor) { + const { thresholds = {} } = monitor; - temperature: monitor.thresholds?.usage_temperature !== undefined, - usage_temperature: monitor.thresholds?.usage_temperature - ? monitor.thresholds.usage_temperature * 100 - : "", - secret: monitor.secret || "", - }); - setHttps(monitor.url.startsWith("https")); - }, [isCreate, monitor]); + setHttps(monitor.url.startsWith("https")); + + setInfrastructureMonitor({ + url: monitor.url.replace(/^https?:\/\//, ""), + name: monitor.name || "", + notifications: monitor.notifications || [], + interval: monitor.interval / MS_PER_MINUTE, + cpu: thresholds.usage_cpu !== undefined, + usage_cpu: + thresholds.usage_cpu !== undefined + ? (thresholds.usage_cpu * 100).toString() + : "", + memory: thresholds.usage_memory !== undefined, + usage_memory: + thresholds.usage_memory !== undefined + ? (thresholds.usage_memory * 100).toString() + : "", + disk: thresholds.usage_disk !== undefined, + usage_disk: + thresholds.usage_disk !== undefined + ? (thresholds.usage_disk * 100).toString() + : "", + temperature: thresholds.usage_temperature !== undefined, + usage_temperature: + thresholds.usage_temperature !== undefined + ? (thresholds.usage_temperature * 100).toString() + : "", + secret: monitor.secret || "", + }); + } + }, [isCreate, monitor, globalSettings, globalSettingsLoading]); // Handlers const onSubmit = async (event) => { @@ -198,6 +246,10 @@ const CreateInfrastructureMonitor = () => { : await updateMonitor({ monitor: form, redirect: "/infrastructure" }); }; + const triggerUpdate = () => { + setUpdateTrigger(!updateTrigger); + }; + const onChange = (event) => { const { value, name } = event.target; setInfrastructureMonitor({ @@ -224,19 +276,26 @@ const CreateInfrastructureMonitor = () => { }); }; + const handlePause = async () => { + await pauseMonitor({ monitorId, triggerUpdate }); + }; + + const handleRemove = async (event) => { + event.preventDefault(); + await deleteMonitor({ monitor, redirect: "/infrastructure" }); + }; + + const isBusy = + isLoading || + isUpdating || + isCreating || + isDeleting || + isPausing || + notificationsAreLoading; + return ( - + { gap={theme.spacing(12)} mt={theme.spacing(6)} > - - - {t(isCreate ? "infrastructureCreateYour" : "infrastructureEditYour")}{" "} - - - {t("monitor")} - - + + + + {!isCreate ? infrastructureMonitor.name : t("createYour") + " "} + + {isCreate ? ( + + {t("monitor")} + + ) : ( + <> + )} + + {!isCreate && ( + + + + + + + + {infrastructureMonitor.url?.replace(/^https?:\/\//, "") || "..."} + + + {t("editing")} + + + )} + + {!isCreate && ( + + + + + )} + - + { - {t("notificationConfig.title")} + + {t("notificationConfig.title")} + {t("notificationConfig.description")} { label="Check frequency" value={infrastructureMonitor.interval || 15} onChange={onChange} - items={SELECT_VALUES} + items={FREQUENCIES} /> @@ -436,12 +614,23 @@ const CreateInfrastructureMonitor = () => { type="submit" variant="contained" color="accent" - loading={isLoading || isUpdating || isCreating || notificationsAreLoading} + loading={isBusy} > {t(isCreate ? "infrastructureCreateMonitor" : "infrastructureEditMonitor")} + {!isCreate && ( +

setIsOpen(false)} + confirmationButtonLabel={t("delete")} + onConfirm={handleRemove} + /> + )} ); }; diff --git a/client/src/Pages/Notifications/create/index.jsx b/client/src/Pages/Notifications/create/index.jsx index b78f9a40a..10c280efb 100644 --- a/client/src/Pages/Notifications/create/index.jsx +++ b/client/src/Pages/Notifications/create/index.jsx @@ -170,7 +170,10 @@ const CreateNotifications = () => { > - + {t("createNotifications.nameSettings.title")} @@ -191,7 +194,10 @@ const CreateNotifications = () => { - + {t("createNotifications.typeSettings.title")} @@ -210,7 +216,12 @@ const CreateNotifications = () => { - {t(TITLE_MAP[type])} + + {t(TITLE_MAP[type])} + {t(DESCRIPTION_MAP[type])} diff --git a/client/src/Pages/Settings/SettingsGlobalThresholds.jsx b/client/src/Pages/Settings/SettingsGlobalThresholds.jsx new file mode 100644 index 000000000..acc101ab4 --- /dev/null +++ b/client/src/Pages/Settings/SettingsGlobalThresholds.jsx @@ -0,0 +1,95 @@ +import Box from "@mui/material/Box"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import ConfigBox from "../../Components/ConfigBox"; +import TextInput from "../../Components/Inputs/TextInput"; + +import { useTheme } from "@emotion/react"; +import { PropTypes } from "prop-types"; +import { useTranslation } from "react-i18next"; + +const SettingsGlobalThresholds = ({ + isAdmin, + HEADING_SX, + settingsData, + setSettingsData, +}) => { + const { t } = useTranslation(); // For language translation + const theme = useTheme(); // MUI theme access + + // Handles input change and updates parent state + const handleChange = (e, min, max) => { + const { name, value } = e.target; + + const numValue = parseFloat(value); + const isValidNumber = + value === "" || + (!isNaN(numValue) && isFinite(numValue) && numValue >= min && numValue <= max); + + if (isValidNumber) { + setSettingsData((prev) => ({ + ...prev, + settings: { + ...prev.settings, + globalThresholds: { + ...prev.settings?.globalThresholds, + [name]: value, + }, + }, + })); + } + }; + + // Only render this section for admins + if (!isAdmin) return null; + + return ( + + {/* Header and description */} + + + {t("settingsPage.globalThresholds.title", "Global Thresholds")} + + + {t( + "settingsPage.globalThresholds.description", + "Configure global CPU, Memory, Disk, and Temperature thresholds." + )} + + + + {/* Threshold inputs */} + + {[ + ["CPU Threshold (%)", "cpu", 1, 100], + ["Memory Threshold (%)", "memory", 1, 100], + ["Disk Threshold (%)", "disk", 1, 100], + ["Temperature Threshold (°C)", "temperature", 1, 150], + ].map(([label, name, min, max]) => ( + handleChange(e, min, max)} + /> + ))} + + + ); +}; + +// Prop types +SettingsGlobalThresholds.propTypes = { + isAdmin: PropTypes.bool, + HEADING_SX: PropTypes.object, + settingsData: PropTypes.object, + setSettingsData: PropTypes.func, +}; + +export default SettingsGlobalThresholds; diff --git a/client/src/Pages/Settings/SettingsURL.jsx b/client/src/Pages/Settings/SettingsURL.jsx index 298dfa821..1ce2e8c73 100644 --- a/client/src/Pages/Settings/SettingsURL.jsx +++ b/client/src/Pages/Settings/SettingsURL.jsx @@ -15,7 +15,12 @@ const SettingsURL = ({ HEADING_SX, handleChange, showURL = false }) => { return ( - {t("settingsPage.urlSettings.title")} + + {t("settingsPage.urlSettings.title")} + {t("settingsPage.urlSettings.description")} diff --git a/client/src/Pages/Settings/index.jsx b/client/src/Pages/Settings/index.jsx index dae6bd680..92f58b4c0 100644 --- a/client/src/Pages/Settings/index.jsx +++ b/client/src/Pages/Settings/index.jsx @@ -8,6 +8,7 @@ import SettingsPagespeed from "./SettingsPagespeed"; import SettingsDemoMonitors from "./SettingsDemoMonitors"; import SettingsAbout from "./SettingsAbout"; import SettingsEmail from "./SettingsEmail"; +import SettingsGlobalThresholds from "./SettingsGlobalThresholds"; import Button from "@mui/material/Button"; // Utils import { settingsValidation } from "../../Validation/validation"; @@ -48,6 +49,7 @@ const Settings = () => { setIsApiKeySet, setIsEmailPasswordSet, }); + const [addDemoMonitors, isAddingDemoMonitors] = useAddDemoMonitors(); const [isSaving, saveError, saveSettings] = useSaveSettings({ @@ -149,6 +151,7 @@ const Settings = () => { error.details.forEach((err) => { newErrors[err.path[0]] = err.message; }); + setErrors(newErrors); } saveSettings(settingsData?.settings); @@ -190,6 +193,13 @@ const Settings = () => { handleChange={handleChange} errors={errors} /> + + { diff --git a/client/src/Pages/Uptime/Configure/index.css b/client/src/Pages/Uptime/Configure/index.css deleted file mode 100644 index 3f24bd011..000000000 --- a/client/src/Pages/Uptime/Configure/index.css +++ /dev/null @@ -1,11 +0,0 @@ -.configure-monitor button.MuiButtonBase-root { - height: var(--env-var-height-2); -} - -.configure-monitor .MuiStack-root:has(span.MuiTypography-root.input-error) { - position: relative; -} -.configure-monitor span.MuiTypography-root.input-error { - position: absolute; - top: 100%; -} diff --git a/client/src/Pages/Uptime/Configure/index.jsx b/client/src/Pages/Uptime/Configure/index.jsx deleted file mode 100644 index 061db6ac0..000000000 --- a/client/src/Pages/Uptime/Configure/index.jsx +++ /dev/null @@ -1,526 +0,0 @@ -// Components -import Box from "@mui/material/Box"; -import Stack from "@mui/material/Stack"; -import Tooltip from "@mui/material/Tooltip"; -import Typography from "@mui/material/Typography"; -import Button from "@mui/material/Button"; -import FormControlLabel from "@mui/material/FormControlLabel"; -import Switch from "@mui/material/Switch"; -import ConfigBox from "../../../Components/ConfigBox"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import TextInput from "../../../Components/Inputs/TextInput"; -import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments"; -import Select from "../../../Components/Inputs/Select"; -import Dialog from "../../../Components/Dialog"; -import PulseDot from "../../../Components/Animated/PulseDot"; -import Checkbox from "../../../Components/Inputs/Checkbox"; - -// Utils -import { useParams } from "react-router"; -import { useTheme } from "@emotion/react"; -import { useState } from "react"; -import { monitorValidation } from "../../../Validation/validation"; -import { createToast } from "../../../Utils/toastUtils"; -import { useTranslation } from "react-i18next"; -import PauseOutlinedIcon from "@mui/icons-material/PauseOutlined"; -import PlayArrowOutlinedIcon from "@mui/icons-material/PlayArrowOutlined"; -import { useMonitorUtils } from "../../../Hooks/useMonitorUtils"; -import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications"; -import { - useDeleteMonitor, - useUpdateMonitor, - usePauseMonitor, - useFetchMonitorById, -} from "../../../Hooks/monitorHooks"; -import NotificationsConfig from "../../../Components/NotificationConfig"; - -/** - * Parses a URL string and returns a URL object. - * - * @param {string} url - The URL string to parse. - * @returns {URL} - The parsed URL object if valid, otherwise an empty string. - */ -const parseUrl = (url) => { - try { - return new URL(url); - } catch (error) { - return null; - } -}; - -/** - * Configure page displays monitor configurations and allows for editing actions. - * @component - */ -const Configure = () => { - const { monitorId } = useParams(); - - // Local state - const [form, setForm] = useState({ - ignoreTlsErrors: false, - interval: 60000, - matchMethod: "equal", - expectedValue: "", - jsonPath: "", - notifications: [], - port: "", - type: "http", - }); - const [useAdvancedMatching, setUseAdvancedMatching] = useState(false); - const [updateTrigger, setUpdateTrigger] = useState(false); - const [isOpen, setIsOpen] = useState(false); - const [errors, setErrors] = useState({}); - - const triggerUpdate = () => { - setUpdateTrigger(!updateTrigger); - }; - - // Network - const [notifications, notificationsAreLoading, notificationsError] = - useGetNotificationsByTeamId(); - const [pauseMonitor, isPausing, pauseError] = usePauseMonitor({}); - const [deleteMonitor, isDeleting] = useDeleteMonitor(); - const [updateMonitor, isUpdating] = useUpdateMonitor(); - const [isLoading] = useFetchMonitorById({ - monitorId, - setMonitor: setForm, - updateTrigger, - }); - - const MS_PER_MINUTE = 60000; - const theme = useTheme(); - - const matchMethodOptions = [ - { _id: "equal", name: "Equal" }, - { _id: "include", name: "Include" }, - { _id: "regex", name: "Regex" }, - ]; - - const frequencies = [ - { _id: 1, name: "1 minute" }, - { _id: 2, name: "2 minutes" }, - { _id: 3, name: "3 minutes" }, - { _id: 4, name: "4 minutes" }, - { _id: 5, name: "5 minutes" }, - ]; - - const expectedValuePlaceholders = { - regex: "^(success|ok)$", - equal: "success", - include: "ok", - }; - - // Handlers - const handlePause = async () => { - const res = await pauseMonitor({ monitorId: form?._id, triggerUpdate }); - if (typeof res !== "undefined") { - triggerUpdate(); - } - }; - - const handleRemove = async (event) => { - event.preventDefault(); - await deleteMonitor({ monitor: form, redirect: "/uptime" }); - }; - - const onChange = (event) => { - let { name, value, checked } = event.target; - - if (name === "ignoreTlsErrors") { - value = checked; - } - - if (name === "useAdvancedMatching") { - setForm((prevForm) => { - return { - ...prevForm, - matchMethod: "equal", - }; - }); - setUseAdvancedMatching(!useAdvancedMatching); - return; - } - - if (name === "interval") { - value = value * MS_PER_MINUTE; - } - setForm({ ...form, [name]: value }); - - const validation = monitorValidation.validate( - { [name]: value }, - { abortEarly: false } - ); - - setErrors((prev) => { - const updatedErrors = { ...prev }; - if (validation.error) updatedErrors[name] = validation.error.details[0].message; - else delete updatedErrors[name]; - return updatedErrors; - }); - }; - - const onSubmit = async (e) => { - e.preventDefault(); - - const toSubmit = { - _id: form?._id, - url: form?.url, - name: form?.name, - type: form?.type, - matchMethod: form?.matchMethod, - expectedValue: form?.expectedValue, - jsonPath: form?.jsonPath, - interval: form?.interval, - teamId: form?.teamId, - userId: form?.userId, - port: form?.port, - ignoreTlsErrors: form?.ignoreTlsErrors, - }; - - if (!useAdvancedMatching) { - toSubmit.matchMethod = ""; - toSubmit.expectedValue = ""; - toSubmit.jsonPath = ""; - } - - const validation = monitorValidation.validate(toSubmit, { - abortEarly: false, - }); - - if (validation.error) { - const newErrors = {}; - validation.error.details.forEach((err) => { - newErrors[err.path[0]] = err.message; - }); - setErrors(newErrors); - createToast({ body: "Please check the form for errors." }); - return; - } - - toSubmit.notifications = form?.notifications; - await updateMonitor({ monitor: toSubmit, redirect: "/uptime" }); - }; - - // Parse the URL - const parsedUrl = parseUrl(form?.url); - const protocol = parsedUrl?.protocol?.replace(":", "") || ""; - - const { determineState, statusColor } = useMonitorUtils(); - - const { t } = useTranslation(); - - return ( - - - - - - - {form?.name} - - - - - - - - - {form?.url?.replace(/^https?:\/\//, "") || "..."} - - - {t("editing")} - - - - - - - - - - - - {t("settingsGeneralSettings")} - - {t("distributedUptimeCreateSelectURL")} - - - - } - id="monitor-url" - label={t("urlMonitor")} - placeholder="google.com" - value={parsedUrl?.host || form?.url || ""} - disabled={true} - /> - - - - - {t("notificationConfig.title")} - {t("notificationConfig.description")} - - - - - - - {t("ignoreTLSError")} - - {t("ignoreTLSErrorDescription")} - - - - } - label={t("tlsErrorIgnored")} - /> - - - - - - {t("distributedUptimeCreateAdvancedSettings")} - - - - - - - - {t("uptimeCreate")} - - - - - - {t("uptimeCreateJsonPath")}  - - jmespath.org - -  {t("uptimeCreateJsonPathQuery")} - - - - )} - - - - - - - setIsOpen(false)} - confirmationButtonLabel="Delete" - onConfirm={handleRemove} - isLoading={isLoading} - /> - - ); -}; - -export default Configure; diff --git a/client/src/Pages/Uptime/Create/index.jsx b/client/src/Pages/Uptime/Create/index.jsx index 761fd6346..2d918cb9f 100644 --- a/client/src/Pages/Uptime/Create/index.jsx +++ b/client/src/Pages/Uptime/Create/index.jsx @@ -1,4 +1,14 @@ //Components +import { + Box, + Button, + ButtonGroup, + FormControlLabel, + Stack, + Switch, + Tooltip, + Typography, +} from "@mui/material"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import TextInput from "../../../Components/Inputs/TextInput"; import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments"; @@ -6,132 +16,186 @@ import Radio from "../../../Components/Inputs/Radio"; import Select from "../../../Components/Inputs/Select"; import ConfigBox from "../../../Components/ConfigBox"; import NotificationsConfig from "../../../Components/NotificationConfig"; -import Button from "@mui/material/Button"; -import ButtonGroup from "@mui/material/ButtonGroup"; -import Box from "@mui/material/Box"; -import Stack from "@mui/material/Stack"; -import Typography from "@mui/material/Typography"; -import Switch from "@mui/material/Switch"; -import FormControlLabel from "@mui/material/FormControlLabel"; import Checkbox from "../../../Components/Inputs/Checkbox"; +import Dialog from "../../../Components/Dialog"; +import PulseDot from "../../../Components/Animated/PulseDot"; +import SkeletonLayout from "./skeleton"; // Utils +import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { monitorValidation } from "../../../Validation/validation"; import { createToast } from "../../../Utils/toastUtils"; +import { + PauseOutlined as PauseOutlinedIcon, + PlayArrowOutlined as PlayArrowOutlinedIcon, +} from "@mui/icons-material"; +import { useMonitorUtils } from "../../../Hooks/useMonitorUtils"; import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications"; -import { useCreateMonitor, useFetchMonitorById } from "../../../Hooks/monitorHooks"; import { useParams } from "react-router-dom"; +import { + useCreateMonitor, + useDeleteMonitor, + useUpdateMonitor, + usePauseMonitor, + useFetchMonitorById, +} from "../../../Hooks/monitorHooks"; -const CreateMonitor = () => { - // Local state - const [errors, setErrors] = useState({}); - const [https, setHttps] = useState(true); - const [useAdvancedMatching, setUseAdvancedMatching] = useState(false); +/** + * Parses a URL string and returns a URL object. + * + * @param {string} url - The URL string to parse. + * @returns {URL} - The parsed URL object if valid, otherwise an empty string. + */ +const parseUrl = (url) => { + try { + return new URL(url); + } catch (error) { + return null; + } +}; + +/** + * Create page renders monitor creation or configuration views. + * @component + */ +const UptimeCreate = ({ isClone = false }) => { + const { monitorId } = useParams(); + const isCreate = typeof monitorId === "undefined" || isClone; + + // States const [monitor, setMonitor] = useState({ - url: "", - name: "", type: "http", matchMethod: "equal", expectedValue: "", jsonPath: "", notifications: [], - interval: 1, + interval: 60000, ignoreTlsErrors: false, + ...(isCreate ? { url: "", name: "" } : { port: undefined }), }); - - // Setup - const MS_PER_MINUTE = 60000; - const theme = useTheme(); - const { t } = useTranslation(); - const [notifications, notificationsAreLoading, error] = useGetNotificationsByTeamId(); - const [createMonitor, isCreating] = useCreateMonitor(); - const { monitorId } = useParams(); - - const formatAndSet = (monitor) => { - monitor.interval = monitor.interval / MS_PER_MINUTE; - setMonitor(monitor); + const [errors, setErrors] = useState({}); + const [https, setHttps] = useState(true); + const [isOpen, setIsOpen] = useState(false); + const [useAdvancedMatching, setUseAdvancedMatching] = useState(false); + const [updateTrigger, setUpdateTrigger] = useState(false); + const triggerUpdate = () => { + setUpdateTrigger(!updateTrigger); }; + + // Hooks + const [notifications, notificationsAreLoading, notificationsError] = + useGetNotificationsByTeamId(); + const { determineState, statusColor } = useMonitorUtils(); + // Network const [isLoading] = useFetchMonitorById({ monitorId, - setMonitor: formatAndSet, + setMonitor, updateTrigger: true, }); + const [createMonitor, isCreating] = useCreateMonitor(); + const [pauseMonitor, isPausing] = usePauseMonitor({}); + const [deleteMonitor, isDeleting] = useDeleteMonitor(); + const [updateMonitor, isUpdating] = useUpdateMonitor(); - const SELECT_VALUES = [ - { _id: 1, name: "1 minute" }, - { _id: 2, name: "2 minutes" }, - { _id: 3, name: "3 minutes" }, - { _id: 4, name: "4 minutes" }, - { _id: 5, name: "5 minutes" }, + // Setup + const theme = useTheme(); + const { t } = useTranslation(); + + // Constants + const MS_PER_MINUTE = 60000; + const FREQUENCIES = [ + { _id: 1, name: t("time.oneMinute") }, + { _id: 2, name: t("time.twoMinutes") }, + { _id: 3, name: t("time.threeMinutes") }, + { _id: 4, name: t("time.fourMinutes") }, + { _id: 5, name: t("time.fiveMinutes") }, + ]; + const CRUMBS = [ + { name: "uptime", path: "/uptime" }, + ...(isCreate + ? [{ name: "create", path: `/uptime/create` }] + : [ + { name: "details", path: `/uptime/${monitorId}` }, + { name: "configure", path: `/uptime/configure/${monitorId}` }, + ]), ]; - const matchMethodOptions = [ - { _id: "equal", name: "Equal" }, - { _id: "include", name: "Include" }, - { _id: "regex", name: "Regex" }, + { _id: "equal", name: t("matchMethodOptions.equal") }, + { _id: "include", name: t("matchMethodOptions.include") }, + { _id: "regex", name: t("matchMethodOptions.regex") }, ]; - const expectedValuePlaceholders = { - regex: "^(success|ok)$", - equal: "success", - include: "ok", + regex: t("matchMethodOptions.regexPlaceholder"), + equal: t("matchMethodOptions.equalPlaceholder"), + include: t("matchMethodOptions.includePlaceholder"), }; - const monitorTypeMaps = { http: { - label: "URL to monitor", - placeholder: "google.com", - namePlaceholder: "Google", + label: t("monitorType.http.label"), + placeholder: t("monitorType.http.placeholder"), + namePlaceholder: t("monitorType.http.namePlaceholder"), }, ping: { - label: "IP address to monitor", - placeholder: "1.1.1.1", - namePlaceholder: "Google", + label: t("monitorType.ping.label"), + placeholder: t("monitorType.ping.placeholder"), + namePlaceholder: t("monitorType.ping.namePlaceholder"), }, docker: { - label: "Container ID", - placeholder: "abc123", - namePlaceholder: "My Container", + label: t("monitorType.docker.label"), + placeholder: t("monitorType.docker.placeholder"), + namePlaceholder: t("monitorType.docker.namePlaceholder"), }, port: { - label: "URL to monitor", - placeholder: "localhost", - namePlaceholder: "Localhost:5173", + label: t("monitorType.port.label"), + placeholder: t("monitorType.port.placeholder"), + namePlaceholder: t("monitorType.port.namePlaceholder"), }, }; - const BREADCRUMBS = [ - { name: "uptime", path: "/uptime" }, - { name: "create", path: `/uptime/create` }, - ]; - // Handlers - const onSubmit = async (event) => { event.preventDefault(); const { notifications, ...rest } = monitor; - let form = { - ...rest, - url: - //prepending protocol for url - monitor.type === "http" - ? `http${https ? "s" : ""}://` + monitor.url - : monitor.url, - port: monitor.type === "port" ? monitor.port : undefined, - name: monitor.name || monitor.url.substring(0, 50), - type: monitor.type, - interval: monitor.interval * MS_PER_MINUTE, - }; - - // If not using advanced matching, remove advanced settings + let form = {}; + if (isCreate) { + form = { + url: + monitor.type === "http" && !isClone + ? `http${https ? "s" : ""}://` + monitor.url + : monitor.url, + name: monitor.name || monitor.url.substring(0, 50), + type: monitor.type, + port: monitor.type === "port" ? monitor.port : undefined, + interval: monitor.interval, + matchMethod: monitor.matchMethod, + expectedValue: monitor.expectedValue, + jsonPath: monitor.jsonPath, + ignoreTlsErrors: monitor.ignoreTlsErrors, + }; + } else { + form = { + _id: monitor._id, + url: monitor.url, + name: monitor.name || monitor.url.substring(0, 50), + type: monitor.type, + matchMethod: monitor.matchMethod, + expectedValue: monitor.expectedValue, + jsonPath: monitor.jsonPath, + interval: monitor.interval, + teamId: monitor.teamId, + userId: monitor.userId, + port: monitor.type === "port" ? monitor.port : undefined, + ignoreTlsErrors: monitor.ignoreTlsErrors, + }; + } if (!useAdvancedMatching) { - form.matchMethod = undefined; - form.expectedValue = undefined; - form.jsonPath = undefined; + form.matchMethod = isCreate ? undefined : ""; + form.expectedValue = isCreate ? undefined : ""; + form.jsonPath = isCreate ? undefined : ""; } const { error } = monitorValidation.validate(form, { @@ -144,7 +208,7 @@ const CreateMonitor = () => { newErrors[err.path[0]] = err.message; }); setErrors(newErrors); - createToast({ body: "Please check the form for errors." }); + createToast({ body: t("checkFormError") }); return; } @@ -154,15 +218,18 @@ const CreateMonitor = () => { notifications: monitor.notifications, }; - await createMonitor({ monitor: form, redirect: "/uptime" }); + if (isCreate) { + await createMonitor({ monitor: form, redirect: "/uptime" }); + } else { + await updateMonitor({ monitor: form, redirect: "/uptime" }); + } }; const onChange = (event) => { - const { name, value, checked } = event.target; + let { name, value, checked } = event.target; - let newValue = value; if (name === "ignoreTlsErrors") { - newValue = checked; + value = checked; } if (name === "useAdvancedMatching") { @@ -170,15 +237,14 @@ const CreateMonitor = () => { return; } - const updatedMonitor = { - ...monitor, - [name]: newValue, - }; + if (name === "interval") { + value = value * MS_PER_MINUTE; + } - setMonitor(updatedMonitor); + setMonitor((prev) => ({ ...prev, [name]: value })); const { error } = monitorValidation.validate( - { type: monitor.type, [name]: newValue }, + { type: monitor.type, [name]: value }, { abortEarly: false } ); @@ -188,122 +254,254 @@ const CreateMonitor = () => { })); }; + const handlePause = async () => { + await pauseMonitor({ monitorId, triggerUpdate }); + }; + + const handleRemove = async (event) => { + event.preventDefault(); + await deleteMonitor({ monitor, redirect: "/uptime" }); + }; + + const isBusy = isLoading || isCreating || isDeleting || isUpdating || isPausing; + const displayInterval = monitor?.interval / MS_PER_MINUTE || 1; + const parsedUrl = parseUrl(monitor?.url); + const protocol = parsedUrl?.protocol?.replace(":", "") || ""; + + useEffect(() => { + if (!isCreate || isClone) { + if (monitor.matchMethod) { + setUseAdvancedMatching(true); + } else { + setUseAdvancedMatching(false); + } + } + }, [monitor, isCreate]); + + if (Object.keys(monitor).length === 0) { + return ; + } + return ( - + - - - {t("createYour")}{" "} - - - {t("monitor")} - - - + - {t("distributedUptimeCreateChecks")} - - - {t("distributedUptimeCreateChecksDescription")} + + {!isCreate ? monitor.name : t("createYour") + " "} + + {isCreate && ( + + {t("monitor")} + + )} + {!isCreate && ( + + + + + + + + {monitor.url?.replace(/^https?:\/\//, "") || "..."} + + + {t("editing")} + + + )} - - + {!isCreate && ( + + + + + )} + + {isCreate && ( + + + + {t("distributedUptimeCreateChecks")} + + + {t("distributedUptimeCreateChecksDescription")} + + + + + + {monitor.type === "http" ? ( + + + + + ) : ( + "" + )} + - {monitor.type === "http" ? ( - - - - + {errors["type"]} + + ) : ( "" )} - - - - {errors["type"] ? ( - - - {errors["type"]} - - - ) : ( - "" - )} - - + + )} { {t("settingsGeneralSettings")} - {t(`uptimeGeneralInstructions.${monitor.type}`)} + {isCreate + ? t(`uptimeGeneralInstructions.${monitor.type}`) + : t("distributedUptimeCreateSelectURL")} - + : null + type={monitor?.type === "http" ? "url" : "text"} + label={ + (monitor.type === "http" || monitor.type === "port") && !isCreate + ? t("url") + : monitorTypeMaps[monitor.type].label || t("urlMonitor") } - label={monitorTypeMaps[monitor.type].label || "URL to monitor"} - https={https} placeholder={monitorTypeMaps[monitor.type].placeholder || ""} - value={monitor.url} - onChange={onChange} - error={errors["url"] ? true : false} + value={parsedUrl?.host + parsedUrl?.pathname || monitor?.url || ""} + https={isCreate ? https : protocol === "https"} + startAdornment={ + monitor?.type === "http" && ( + + ) + } helperText={errors["url"]} + onChange={onChange} + disabled={!isCreate} /> { type="text" label={t("displayName")} isOptional={true} - placeholder={monitorTypeMaps[monitor.type].namePlaceholder || ""} - value={monitor.name} + placeholder={monitorTypeMaps[monitor.type].namePlaceholder} + value={monitor.name || ""} onChange={onChange} error={errors["name"] ? true : false} helperText={errors["name"]} @@ -357,12 +564,18 @@ const CreateMonitor = () => { - {t("notificationConfig.title")} + + {t("notificationConfig.title")} + {t("notificationConfig.description")} @@ -377,7 +590,7 @@ const CreateMonitor = () => { { {t("distributedUptimeCreateAdvancedSettings")} - + { { color={theme.palette.primary.contrastTextTertiary} opacity={0.8} > - {t("uptimeCreateJsonPath")}  + {t("uptimeCreateJsonPath") + " "} { > jmespath.org -  {t("uptimeCreateJsonPathQuery")} + {" " + t("uptimeCreateJsonPathQuery")} @@ -486,14 +701,32 @@ const CreateMonitor = () => { variant="contained" color="accent" disabled={!Object.values(errors).every((value) => value === undefined)} - loading={isCreating} + loading={isBusy} + sx={{ px: theme.spacing(12) }} > - {t("createMonitor")} + {t("settingsSave")} + + {!isCreate && ( + setIsOpen(false)} + confirmationButtonLabel={t("delete")} + onConfirm={handleRemove} + isLoading={isLoading} + /> + )} ); }; -export default CreateMonitor; +UptimeCreate.propTypes = { + isClone: PropTypes.bool, +}; + +export default UptimeCreate; diff --git a/client/src/Pages/Uptime/Configure/skeleton.jsx b/client/src/Pages/Uptime/Create/skeleton.jsx similarity index 100% rename from client/src/Pages/Uptime/Configure/skeleton.jsx rename to client/src/Pages/Uptime/Create/skeleton.jsx diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx index 6de07a2de..350d2f82d 100644 --- a/client/src/Routes/index.jsx +++ b/client/src/Routes/index.jsx @@ -14,7 +14,6 @@ import AuthNewPasswordConfirmed from "../Pages/Auth/NewPasswordConfirmed"; import Uptime from "../Pages/Uptime/Monitors"; import UptimeDetails from "../Pages/Uptime/Details"; import UptimeCreate from "../Pages/Uptime/Create"; -import UptimeConfigure from "../Pages/Uptime/Configure"; // PageSpeed import PageSpeed from "../Pages/PageSpeed/Monitors"; @@ -81,16 +80,20 @@ const Routes = () => { /> } /> + } + /> } /> } + element={} /> ({ styleOverrides: { root: ({ theme }) => ({ variants: [ + { + props: (props) => props.variant === "contained" && props.color === "accent", + style: { + backgroundColor: theme.palette.accent.main, + color: theme.palette.primary.contrastTextSecondaryDarkBg, + letterSpacing: "0.5px", + textShadow: "0 0 1px rgba(0, 0, 0, 0.15)", + "&:hover": { + backgroundColor: darken(theme.palette.accent.darker, 0.05), + boxShadow: `0 2px 6px rgba(0, 0, 0, 0.1)`, + transition: "all 0.2s ease-in-out", + }, + }, + }, { props: (props) => props.color === "accent", style: { @@ -217,16 +232,16 @@ const baseTheme = (palette) => ({ }, MuiList: { styleOverrides: { - root: { + root: ({ theme }) => ({ padding: 0, - }, + }), }, }, MuiListItemButton: { styleOverrides: { - root: { - transition: "none", - }, + root: ({ theme }) => ({ + transition: "background-color .3s", + }), }, }, MuiListItemText: { @@ -263,6 +278,7 @@ const baseTheme = (palette) => ({ }), }, }, + MuiTableHead: { styleOverrides: { root: ({ theme }) => ({ @@ -566,7 +582,7 @@ const baseTheme = (palette) => ({ root: ({ theme }) => ({ ml: "auto", "& .MuiButtonBase-root, & .MuiButtonBase-root:hover": { - borderColor: theme.palette.primary.contrastBorder, + borderColor: theme.palette.primary.lowContrast, width: "auto", whiteSpace: "nowrap", }, @@ -707,6 +723,14 @@ const baseTheme = (palette) => ({ }, ], }, + + MuiTooltip: { + styleOverrides: { + tooltip: () => ({ + fontSize: typographyLevels.m, + }), + }, + }, }, shape: { borderRadius: 2, diff --git a/client/src/Validation/validation.js b/client/src/Validation/validation.js index 7754ee475..9c6699d4b 100644 --- a/client/src/Validation/validation.js +++ b/client/src/Validation/validation.js @@ -302,6 +302,14 @@ const settingsValidation = joi.object({ systemEmailIgnoreTLS: joi.boolean(), systemEmailRequireTLS: joi.boolean(), systemEmailRejectUnauthorized: joi.boolean(), + globalThresholds: joi + .object({ + cpu: joi.number().min(1).max(100).allow("").optional(), + memory: joi.number().min(1).max(100).allow("").optional(), + disk: joi.number().min(1).max(100).allow("").optional(), + temperature: joi.number().min(1).max(150).allow("").optional(), + }) + .optional(), }); const dayjsValidator = (value, helpers) => { diff --git a/client/src/assets/Animations/darkmodeOutput.gif b/client/src/assets/Animations/darkmodeOutput.gif new file mode 100644 index 000000000..7629e30b4 Binary files /dev/null and b/client/src/assets/Animations/darkmodeOutput.gif differ diff --git a/client/src/assets/Animations/output.gif b/client/src/assets/Animations/output.gif new file mode 100644 index 000000000..c7823227a Binary files /dev/null and b/client/src/assets/Animations/output.gif differ diff --git a/client/src/assets/icons/alert-icon.svg b/client/src/assets/icons/alert-icon.svg new file mode 100644 index 000000000..1dc7fc2b7 --- /dev/null +++ b/client/src/assets/icons/alert-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/src/assets/icons/check-icon.svg b/client/src/assets/icons/check-icon.svg new file mode 100644 index 000000000..9a2cfac5a --- /dev/null +++ b/client/src/assets/icons/check-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/src/assets/icons/close-icon.svg b/client/src/assets/icons/close-icon.svg new file mode 100644 index 000000000..e1b6b690a --- /dev/null +++ b/client/src/assets/icons/close-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/src/assets/icons/left-arrow-double.svg b/client/src/assets/icons/left-arrow-double.svg index 6001a3ead..4cb2a5186 100644 --- a/client/src/assets/icons/left-arrow-double.svg +++ b/client/src/assets/icons/left-arrow-double.svg @@ -1,3 +1,4 @@ - - + + \ No newline at end of file diff --git a/client/src/assets/icons/left-arrow-long.svg b/client/src/assets/icons/left-arrow-long.svg index db9b827ed..56a774730 100644 --- a/client/src/assets/icons/left-arrow-long.svg +++ b/client/src/assets/icons/left-arrow-long.svg @@ -1,3 +1,4 @@ - - + + \ No newline at end of file diff --git a/client/src/assets/icons/left-arrow.svg b/client/src/assets/icons/left-arrow.svg index 8b38c408a..649d1681c 100644 --- a/client/src/assets/icons/left-arrow.svg +++ b/client/src/assets/icons/left-arrow.svg @@ -1,3 +1,4 @@ - - + + \ No newline at end of file diff --git a/client/src/assets/icons/right-arrow-double.svg b/client/src/assets/icons/right-arrow-double.svg index 4235134d7..6ad4882ad 100644 --- a/client/src/assets/icons/right-arrow-double.svg +++ b/client/src/assets/icons/right-arrow-double.svg @@ -1,3 +1,4 @@ - - + + \ No newline at end of file diff --git a/client/src/assets/icons/right-arrow.svg b/client/src/assets/icons/right-arrow.svg index 6b02062f2..77d022798 100644 --- a/client/src/assets/icons/right-arrow.svg +++ b/client/src/assets/icons/right-arrow.svg @@ -1,3 +1,4 @@ - - + + \ No newline at end of file diff --git a/client/src/assets/icons/warning-icon.svg b/client/src/assets/icons/warning-icon.svg new file mode 100644 index 000000000..468611758 --- /dev/null +++ b/client/src/assets/icons/warning-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/src/index.css b/client/src/index.css index f83cdc461..a5d305d46 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -28,6 +28,8 @@ html { --env-var-nav-bar-height: 70px; --env-var-side-bar-width: 250px; + --env-var-side-bar-collapsed-width: 64px; + --env-var-side-bar-auth-footer-height: 50px; --env-var-spacing-1: 12px; --env-var-spacing-1-plus: 16px; diff --git a/client/src/locales/ar.json b/client/src/locales/ar.json new file mode 100644 index 000000000..7188c3711 --- /dev/null +++ b/client/src/locales/ar.json @@ -0,0 +1,969 @@ +{ + "submit": "", + "title": "", + "distributedStatusHeaderText": "", + "distributedStatusSubHeaderText": "", + "settingsDisabled": "", + "settingsSuccessSaved": "", + "settingsFailedToSave": "", + "settingsStatsCleared": "", + "settingsFailedToClearStats": "", + "settingsMonitorsDeleted": "", + "settingsFailedToDeleteMonitors": "", + "starPromptTitle": "", + "starPromptDescription": "", + "https": "", + "http": "", + "monitor": "", + "aboutus": "", + "now": "", + "delete": "", + "configure": "", + "responseTime": "", + "ms": "", + "bar": "", + "area": "", + "country": "", + "city": "", + "response": "", + "monitorStatusUp": "", + "monitorStatusDown": "", + "webhookSendSuccess": "", + "webhookSendError": "", + "webhookUnsupportedPlatform": "", + "distributedRightCategoryTitle": "", + "distributedStatusServerMonitors": "", + "distributedStatusServerMonitorsDescription": "", + "distributedUptimeCreateSelectURL": "", + "distributedUptimeCreateChecks": "", + "distributedUptimeCreateChecksDescription": "", + "distributedUptimeCreateIncidentNotification": "", + "distributedUptimeCreateIncidentDescription": "", + "distributedUptimeCreateAdvancedSettings": "", + "distributedUptimeDetailsNoMonitorHistory": "", + "distributedUptimeDetailsStatusHeaderUptime": "", + "distributedUptimeDetailsStatusHeaderLastUpdate": "", + "notifications": { + "enableNotifications": "", + "testNotification": "", + "addOrEditNotifications": "", + "slack": { + "label": "", + "description": "", + "webhookLabel": "", + "webhookPlaceholder": "", + "webhookRequired": "" + }, + "discord": { + "label": "", + "description": "", + "webhookLabel": "", + "webhookPlaceholder": "", + "webhookRequired": "" + }, + "telegram": { + "label": "", + "description": "", + "tokenLabel": "", + "tokenPlaceholder": "", + "chatIdLabel": "", + "chatIdPlaceholder": "", + "fieldsRequired": "" + }, + "webhook": { + "label": "", + "description": "", + "urlLabel": "", + "urlPlaceholder": "", + "urlRequired": "" + }, + "testNotificationDevelop": "", + "integrationButton": "", + "testSuccess": "", + "testFailed": "", + "unsupportedType": "", + "networkError": "", + "fallback": { + "title": "", + "checks": [""], + "actionButton": "" + }, + "createButton": "", + "createTitle": "", + "create": { + "success": "", + "failed": "" + }, + "fetch": { + "success": "", + "failed": "" + }, + "delete": { + "success": "", + "failed": "" + }, + "edit": { + "success": "", + "failed": "" + }, + "test": { + "success": "", + "failed": "" + } + }, + "testLocale": "", + "add": "", + "monitors": "", + "distributedUptimeStatusCreateStatusPage": "", + "distributedUptimeStatusCreateStatusPageAccess": "", + "distributedUptimeStatusCreateStatusPageReady": "", + "distributedUptimeStatusBasicInfoHeader": "", + "distributedUptimeStatusBasicInfoDescription": "", + "distributedUptimeStatusLogoHeader": "", + "distributedUptimeStatusLogoDescription": "", + "distributedUptimeStatusLogoUploadButton": "", + "distributedUptimeStatusStandardMonitorsHeader": "", + "distributedUptimeStatusStandardMonitorsDescription": "", + "distributedUptimeStatusCreateYour": "", + "distributedUptimeStatusEditYour": "", + "distributedUptimeStatusPublishedLabel": "", + "distributedUptimeStatusCompanyNameLabel": "", + "distributedUptimeStatusPageAddressLabel": "", + "distributedUptimeStatus30Days": "", + "distributedUptimeStatus60Days": "", + "distributedUptimeStatus90Days": "", + "distributedUptimeStatusPageNotSetUp": "", + "distributedUptimeStatusContactAdmin": "", + "distributedUptimeStatusPageNotPublic": "", + "distributedUptimeStatusPageDeleteDialog": "", + "distributedUptimeStatusPageDeleteConfirm": "", + "distributedUptimeStatusPageDeleteDescription": "", + "distributedUptimeStatusDevices": "", + "distributedUptimeStatusUpt": "", + "distributedUptimeStatusUptBurned": "", + "distributedUptimeStatusUptLogo": "", + "incidentsTableNoIncidents": "", + "incidentsTablePaginationLabel": "", + "incidentsTableMonitorName": "", + "incidentsTableStatus": "", + "incidentsTableDateTime": "", + "incidentsTableStatusCode": "", + "incidentsTableMessage": "", + "incidentsOptionsHeader": "", + "incidentsOptionsHeaderFilterBy": "", + "incidentsOptionsHeaderFilterAll": "", + "incidentsOptionsHeaderFilterDown": "", + "incidentsOptionsHeaderFilterCannotResolve": "", + "incidentsOptionsHeaderShow": "", + "incidentsOptionsHeaderLastHour": "", + "incidentsOptionsHeaderLastDay": "", + "incidentsOptionsHeaderLastWeek": "", + "incidentsOptionsPlaceholderAllServers": "", + "infrastructureCreateYour": "", + "infrastructureCreateGeneralSettingsDescription": "", + "infrastructureServerRequirement": "", + "infrastructureCustomizeAlerts": "", + "infrastructureAlertNotificationDescription": "", + "infrastructureCreateMonitor": "", + "infrastructureProtocol": "", + "infrastructureServerUrlLabel": "", + "infrastructureDisplayNameLabel": "", + "infrastructureAuthorizationSecretLabel": "", + "gb": "", + "mb": "", + "mem": "", + "memoryUsage": "", + "cpu": "", + "cpuUsage": "", + "cpuTemperature": "", + "diskUsage": "", + "used": "", + "total": "", + "cores": "", + "frequency": "", + "status": "", + "cpuPhysical": "", + "cpuLogical": "", + "cpuFrequency": "", + "avgCpuTemperature": "", + "memory": "", + "disk": "", + "uptime": "", + "os": "", + "host": "", + "actions": "", + "integrations": "", + "integrationsPrism": "", + "integrationsSlack": "", + "integrationsSlackInfo": "", + "integrationsDiscord": "", + "integrationsDiscordInfo": "", + "integrationsZapier": "", + "integrationsZapierInfo": "", + "commonSave": "", + "createYour": "", + "createMonitor": "", + "pause": "", + "resume": "", + "editing": "", + "url": "", + "access": "", + "timezone": "", + "features": "", + "administrator": "", + "loginHere": "", + "displayName": "", + "urlMonitor": "", + "portToMonitor": "", + "websiteMonitoring": "", + "websiteMonitoringDescription": "", + "pingMonitoring": "", + "pingMonitoringDescription": "", + "dockerContainerMonitoring": "", + "dockerContainerMonitoringDescription": "", + "portMonitoring": "", + "portMonitoringDescription": "", + "createMaintenanceWindow": "", + "createMaintenance": "", + "editMaintenance": "", + "maintenanceWindowName": "", + "friendlyNameInput": "", + "friendlyNamePlaceholder": "", + "maintenanceRepeat": "", + "maintenance": "", + "duration": "", + "addMonitors": "", + "window": "", + "cancel": "", + "message": "", + "low": "", + "high": "", + "statusCode": "", + "date&Time": "", + "type": "", + "statusPageName": "", + "publicURL": "", + "repeat": "", + "edit": "", + "createA": "", + "remove": "", + "maintenanceWindowDescription": "", + "startTime": "", + "timeZoneInfo": "", + "monitorsToApply": "", + "nextWindow": "", + "notFoundButton": "", + "pageSpeedConfigureSettingsDescription": "", + "monitorDisplayName": "", + "whenNewIncident": "", + "notifySMS": "", + "notifyEmails": "", + "seperateEmails": "", + "checkFrequency": "", + "matchMethod": "", + "expectedValue": "", + "deleteDialogTitle": "", + "deleteDialogDescription": "", + "pageSpeedMonitor": "", + "shown": "", + "ago": "", + "companyName": "", + "pageSpeedDetailsPerformanceReport": "", + "pageSpeedDetailsPerformanceReportCalculator": "", + "checkingEvery": "", + "statusPageCreateSettings": "", + "basicInformation": "", + "statusPageCreateBasicInfoDescription": "", + "statusPageCreateSelectTimeZoneDescription": "", + "statusPageCreateAppearanceDescription": "", + "statusPageCreateSettingsCheckboxLabel": "", + "statusPageCreateBasicInfoStatusPageAddress": "", + "statusPageCreateTabsContent": "", + "statusPageCreateTabsContentDescription": "", + "statusPageCreateTabsContentFeaturesDescription": "", + "showCharts": "", + "showUptimePercentage": "", + "removeLogo": "", + "statusPageStatus": "", + "statusPageStatusContactAdmin": "", + "statusPageStatusNotPublic": "", + "statusPageStatusNoPage": "", + "statusPageStatusServiceStatus": "", + "deleteStatusPage": "", + "deleteStatusPageConfirm": "", + "deleteStatusPageDescription": "", + "uptimeCreate": "", + "uptimeCreateJsonPath": "", + "uptimeCreateJsonPathQuery": "", + "maintenanceTableActionMenuDialogTitle": "", + "infrastructureEditYour": "", + "infrastructureEditMonitor": "", + "infrastructureMonitorCreated": "", + "infrastructureMonitorUpdated": "", + "errorInvalidTypeId": "", + "errorInvalidFieldId": "", + "inviteNoTokenFound": "", + "pageSpeedWarning": "", + "pageSpeedLearnMoreLink": "", + "pageSpeedAddApiKey": "", + "update": "", + "invalidFileFormat": "", + "invalidFileSize": "", + "ClickUpload": "", + "DragandDrop": "", + "MaxSize": "", + "SupportedFormats": "", + "FirstName": "", + "LastName": "", + "EmailDescriptionText": "", + "YourPhoto": "", + "PhotoDescriptionText": "", + "save": "", + "DeleteDescriptionText": "", + "DeleteAccountWarning": "", + "DeleteWarningTitle": "", + "bulkImport": { + "title": "", + "selectFileTips": "", + "selectFileDescription": "", + "selectFile": "", + "parsingFailed": "", + "uploadSuccess": "", + "validationFailed": "", + "noFileSelected": "", + "fallbackPage": "", + "invalidFileType": "", + "uploadFailed": "" + }, + "DeleteAccountTitle": "", + "DeleteAccountButton": "", + "publicLink": "", + "maskedPageSpeedKeyPlaceholder": "", + "reset": "", + "ignoreTLSError": "", + "tlsErrorIgnored": "", + "ignoreTLSErrorDescription": "", + "createNew": "", + "greeting": { + "prepend": "", + "append": "", + "overview": "" + }, + "roles": { + "superAdmin": "", + "admin": "", + "teamMember": "", + "demoUser": "" + }, + "teamPanel": { + "teamMembers": "", + "filter": { + "all": "", + "member": "" + }, + "inviteTeamMember": "", + "inviteNewTeamMember": "", + "inviteDescription": "", + "email": "", + "selectRole": "", + "inviteLink": "", + "cancel": "", + "noMembers": "", + "getToken": "", + "emailToken": "", + "table": { + "name": "", + "email": "", + "role": "", + "created": "" + } + }, + "monitorState": { + "paused": "", + "resumed": "", + "active": "" + }, + "menu": { + "uptime": "", + "pagespeed": "", + "infrastructure": "", + "incidents": "", + "statusPages": "", + "maintenance": "", + "integrations": "", + "settings": "", + "support": "", + "discussions": "", + "docs": "", + "changelog": "", + "profile": "", + "password": "", + "team": "", + "logOut": "", + "notifications": "", + "logs": "" + }, + "settingsEmailUser": "", + "state": "", + "statusBreadCrumbsStatusPages": "", + "statusBreadCrumbsDetails": "", + "commonSaving": "", + "navControls": "", + "incidentsPageTitle": "", + "passwordPanel": { + "passwordChangedSuccess": "", + "passwordInputIncorrect": "", + "currentPassword": "", + "enterCurrentPassword": "", + "newPassword": "", + "enterNewPassword": "", + "confirmNewPassword": "", + "passwordRequirements": "", + "saving": "" + }, + "emailSent": "", + "failedToSendEmail": "", + "settingsTestEmailSuccess": "", + "settingsTestEmailFailed": "", + "settingsTestEmailFailedWithReason": "", + "settingsTestEmailUnknownError": "", + "statusMsg": { + "paused": "", + "up": "", + "down": "", + "pending": "" + }, + "uptimeGeneralInstructions": { + "http": "", + "ping": "", + "docker": "", + "port": "" + }, + "common": { + "appName": "", + "monitoringAgentName": "", + "buttons": { + "toggleTheme": "" + }, + "toasts": { + "networkError": "", + "checkConnection": "", + "unknownError": "" + } + }, + "auth": { + "common": { + "navigation": { + "continue": "", + "back": "" + }, + "inputs": { + "email": { + "label": "", + "placeholder": "", + "errors": { + "empty": "", + "invalid": "" + } + }, + "password": { + "label": "", + "rules": { + "length": { + "beginning": "", + "highlighted": "" + }, + "special": { + "beginning": "", + "highlighted": "" + }, + "number": { + "beginning": "", + "highlighted": "" + }, + "uppercase": { + "beginning": "", + "highlighted": "" + }, + "lowercase": { + "beginning": "", + "highlighted": "" + }, + "match": { + "beginning": "", + "highlighted": "" + } + }, + "errors": { + "empty": "", + "length": "", + "uppercase": "", + "lowercase": "", + "number": "", + "special": "", + "incorrect": "" + } + }, + "passwordConfirm": { + "label": "", + "placeholder": "", + "errors": { + "empty": "", + "different": "" + } + }, + "firstName": { + "label": "", + "placeholder": "", + "errors": { + "empty": "", + "length": "", + "pattern": "" + } + }, + "lastName": { + "label": "", + "placeholder": "", + "errors": { + "empty": "", + "length": "", + "pattern": "" + } + } + }, + "errors": { + "validation": "" + }, + "fields": { + "password": { + "errors": { + "incorrect": "" + } + }, + "role": { + "errors": { + "min": "" + } + } + } + }, + "login": { + "heading": "", + "subheadings": { + "stepOne": "", + "stepTwo": "" + }, + "links": { + "forgotPassword": "", + "register": "", + "forgotPasswordLink": "", + "registerLink": "" + }, + "toasts": { + "success": "", + "incorrectPassword": "" + }, + "errors": { + "password": { + "incorrect": "" + } + } + }, + "registration": { + "heading": { + "superAdmin": "", + "user": "" + }, + "subheadings": { + "stepOne": "", + "stepTwo": "", + "stepThree": "" + }, + "description": { + "superAdmin": "", + "user": "" + }, + "gettingStartedButton": { + "superAdmin": "", + "user": "" + }, + "termsAndPolicies": "", + "links": { + "login": "" + }, + "toasts": { + "success": "" + } + }, + "forgotPassword": { + "heading": "", + "subheadings": { + "stepOne": "", + "stepTwo": "", + "stepThree": "", + "stepFour": "" + }, + "buttons": { + "openEmail": "", + "resetPassword": "" + }, + "imageAlts": { + "passwordKey": "", + "email": "", + "lock": "", + "passwordConfirm": "" + }, + "links": { + "login": "", + "resend": "" + }, + "toasts": { + "sent": "", + "emailNotFound": "", + "redirect": "", + "success": "", + "error": "" + } + } + }, + "errorPages": { + "serverUnreachable": { + "toasts": { + "reconnected": "", + "stillUnreachable": "" + }, + "alertBox": "", + "description": "", + "retryButton": { + "default": "", + "processing": "" + } + } + }, + "createNotifications": { + "title": "", + "nameSettings": { + "title": "", + "description": "", + "nameLabel": "", + "namePlaceholder": "" + }, + "typeSettings": { + "title": "", + "description": "", + "typeLabel": "" + }, + "emailSettings": { + "title": "", + "description": "", + "emailLabel": "", + "emailPlaceholder": "" + }, + "slackSettings": { + "title": "", + "description": "", + "webhookLabel": "", + "webhookPlaceholder": "" + }, + "pagerdutySettings": { + "title": "", + "description": "", + "integrationKeyLabel": "", + "integrationKeyPlaceholder": "" + }, + "discordSettings": { + "title": "", + "description": "", + "webhookLabel": "", + "webhookPlaceholder": "" + }, + "webhookSettings": { + "title": "", + "description": "", + "webhookLabel": "", + "webhookPlaceholder": "" + }, + "testNotification": "", + "dialogDeleteTitle": "", + "dialogDeleteConfirm": "" + }, + "notificationConfig": { + "title": "", + "description": "" + }, + "monitorStatus": { + "checkingEvery": "", + "withCaptureAgent": "", + "up": "", + "down": "", + "paused": "" + }, + "advancedMatching": "", + "sendTestNotifications": "", + "selectAll": "", + "showAdminLoginLink": "", + "logsPage": { + "title": "", + "description": "", + "tabs": { + "queue": "", + "logs": "", + "diagnostics": "" + }, + "toast": { + "fetchLogsSuccess": "" + }, + "logLevelSelect": { + "title": "", + "values": { + "all": "", + "info": "", + "warn": "", + "error": "", + "debug": "" + } + } + }, + "queuePage": { + "title": "", + "refreshButton": "", + "flushButton": "", + "jobTable": { + "title": "", + "idHeader": "", + "urlHeader": "", + "typeHeader": "", + "activeHeader": "", + "lockedAtHeader": "", + "runCountHeader": "", + "failCountHeader": "", + "lastRunHeader": "", + "lastFinishedAtHeader": "", + "lastRunTookHeader": "" + }, + "metricsTable": { + "title": "", + "metricHeader": "", + "valueHeader": "" + }, + "failedJobTable": { + "title": "", + "monitorIdHeader": "", + "monitorUrlHeader": "", + "failCountHeader": "", + "failedAtHeader": "", + "failReasonHeader": "" + } + }, + "export": { + "title": "", + "success": "", + "failed": "" + }, + "monitorActions": { + "title": "", + "import": "", + "export": "", + "deleteSuccess": "", + "deleteFailed": "", + "details": "" + }, + "settingsPage": { + "aboutSettings": { + "labelDevelopedBy": "", + "labelVersion": "", + "title": "" + }, + "demoMonitorsSettings": { + "buttonAddMonitors": "", + "description": "", + "title": "" + }, + "emailSettings": { + "buttonSendTestEmail": "", + "description": "", + "descriptionTransport": "", + "labelAddress": "", + "labelConnectionHost": "", + "labelHost": "", + "labelIgnoreTLS": "", + "labelPassword": "", + "labelPasswordSet": "", + "labelPool": "", + "labelPort": "", + "labelRejectUnauthorized": "", + "labelRequireTLS": "", + "labelSecure": "", + "labelTLSServername": "", + "labelUser": "", + "linkTransport": "", + "placeholderUser": "", + "title": "", + "toastEmailRequiredFieldsError": "" + }, + "pageSpeedSettings": { + "description": "", + "labelApiKeySet": "", + "labelApiKey": "", + "title": "" + }, + "saveButtonLabel": "", + "statsSettings": { + "clearAllStatsButton": "", + "clearAllStatsDescription": "", + "clearAllStatsDialogConfirm": "", + "clearAllStatsDialogDescription": "", + "clearAllStatsDialogTitle": "", + "description": "", + "labelTTL": "", + "labelTTLOptional": "", + "title": "" + }, + "systemResetSettings": { + "buttonRemoveAllMonitors": "", + "description": "", + "dialogConfirm": "", + "dialogDescription": "", + "dialogTitle": "", + "title": "" + }, + "timezoneSettings": { + "description": "", + "label": "", + "title": "" + }, + "title": "", + "uiSettings": { + "description": "", + "labelLanguage": "", + "labelTheme": "", + "title": "" + }, + "urlSettings": { + "description": "", + "label": "", + "selectDisabled": "", + "selectEnabled": "", + "title": "" + } + }, + "statusPageCreate": { + "buttonSave": "" + }, + "incidentsOptionsHeaderFilterResolved": "", + "settingsSave": "", + "statusPageCreateAppearanceTitle": "", + "confirmPassword": "", + "monitorHooks": { + "failureAddDemoMonitors": "", + "successAddDemoMonitors": "" + }, + "settingsAppearance": "", + "settingsDisplayTimezone": "", + "settingsGeneralSettings": "", + "incidentsOptionsHeaderTotalIncidents": "", + "statusPage": { + "deleteSuccess": "", + "deleteFailed": "", + "createSuccess": "", + "updateSuccess": "", + "generalSettings": "", + "contents": "", + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "testNotificationsDisabled": "", + "incidentsTableResolvedAt": "", + "incidentsTableActionResolve": "", + "checkHooks": { + "failureResolveOne": "", + "failureResolveAll": "", + "failureResolveMonitor": "" + }, + "checkFormError": "", + "diagnosticsPage": { + "diagnosticDescription": "", + "statsDescription": "", + "gauges": { + "heapAllocationTitle": "", + "heapAllocationSubtitle": "", + "heapUsageTitle": "", + "heapUsageSubtitle": "", + "heapUtilizationTitle": "", + "heapUtilizationSubtitle": "", + "instantCpuUsageTitle": "", + "instantCpuUsageSubtitle": "" + }, + "stats": { + "eventLoopDelayTitle": "", + "uptimeTitle": "", + "usedHeapSizeTitle": "", + "totalHeapSizeTitle": "", + "osMemoryLimitTitle": "" + } + }, + "pageSpeedLighthouseAPI": "", + "time": { + "threeMinutes": "", + "fiveMinutes": "", + "tenMinutes": "", + "twentyMinutes": "", + "oneHour": "", + "oneDay": "", + "oneWeek": "" + }, + "general": { + "noOptionsFound": "" + }, + "infrastructureMonitor": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "maintenanceWindow": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "pageSpeed": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "uptimeMonitor": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "editUserPage": { + "form": { + "email": "", + "firstName": "", + "lastName": "", + "role": "", + "save": "" + }, + "table": { + "actionHeader": "", + "roleHeader": "" + }, + "title": "", + "toast": { + "successUserUpdate": "", + "validationErrors": "" + } + }, + "incidentsPageActionResolveMonitor": "", + "incidentsPageActionResolveAll": "" +} diff --git a/client/src/locales/cs.json b/client/src/locales/cs.json new file mode 100644 index 000000000..00c513203 --- /dev/null +++ b/client/src/locales/cs.json @@ -0,0 +1,969 @@ +{ + "submit": "Odeslat", + "title": "", + "distributedStatusHeaderText": "", + "distributedStatusSubHeaderText": "", + "settingsDisabled": "Vypnuto", + "settingsSuccessSaved": "Nastavení bylo úspěšně uloženo", + "settingsFailedToSave": "Nepodařilo se uložit nastavení", + "settingsStatsCleared": "", + "settingsFailedToClearStats": "", + "settingsMonitorsDeleted": "", + "settingsFailedToDeleteMonitors": "", + "starPromptTitle": "", + "starPromptDescription": "", + "https": "", + "http": "", + "monitor": "", + "aboutus": "", + "now": "", + "delete": "", + "configure": "", + "responseTime": "", + "ms": "", + "bar": "", + "area": "", + "country": "", + "city": "", + "response": "", + "monitorStatusUp": "", + "monitorStatusDown": "", + "webhookSendSuccess": "", + "webhookSendError": "", + "webhookUnsupportedPlatform": "", + "distributedRightCategoryTitle": "", + "distributedStatusServerMonitors": "", + "distributedStatusServerMonitorsDescription": "", + "distributedUptimeCreateSelectURL": "", + "distributedUptimeCreateChecks": "", + "distributedUptimeCreateChecksDescription": "", + "distributedUptimeCreateIncidentNotification": "", + "distributedUptimeCreateIncidentDescription": "", + "distributedUptimeCreateAdvancedSettings": "", + "distributedUptimeDetailsNoMonitorHistory": "", + "distributedUptimeDetailsStatusHeaderUptime": "", + "distributedUptimeDetailsStatusHeaderLastUpdate": "", + "notifications": { + "enableNotifications": "", + "testNotification": "", + "addOrEditNotifications": "", + "slack": { + "label": "", + "description": "", + "webhookLabel": "", + "webhookPlaceholder": "", + "webhookRequired": "" + }, + "discord": { + "label": "", + "description": "", + "webhookLabel": "", + "webhookPlaceholder": "", + "webhookRequired": "" + }, + "telegram": { + "label": "", + "description": "", + "tokenLabel": "", + "tokenPlaceholder": "", + "chatIdLabel": "", + "chatIdPlaceholder": "", + "fieldsRequired": "" + }, + "webhook": { + "label": "", + "description": "", + "urlLabel": "", + "urlPlaceholder": "", + "urlRequired": "" + }, + "testNotificationDevelop": "", + "integrationButton": "", + "testSuccess": "", + "testFailed": "", + "unsupportedType": "", + "networkError": "", + "fallback": { + "title": "", + "checks": [""], + "actionButton": "" + }, + "createButton": "", + "createTitle": "", + "create": { + "success": "", + "failed": "" + }, + "fetch": { + "success": "", + "failed": "" + }, + "delete": { + "success": "", + "failed": "" + }, + "edit": { + "success": "", + "failed": "" + }, + "test": { + "success": "", + "failed": "" + } + }, + "testLocale": "", + "add": "", + "monitors": "", + "distributedUptimeStatusCreateStatusPage": "", + "distributedUptimeStatusCreateStatusPageAccess": "", + "distributedUptimeStatusCreateStatusPageReady": "", + "distributedUptimeStatusBasicInfoHeader": "", + "distributedUptimeStatusBasicInfoDescription": "", + "distributedUptimeStatusLogoHeader": "", + "distributedUptimeStatusLogoDescription": "", + "distributedUptimeStatusLogoUploadButton": "", + "distributedUptimeStatusStandardMonitorsHeader": "", + "distributedUptimeStatusStandardMonitorsDescription": "", + "distributedUptimeStatusCreateYour": "", + "distributedUptimeStatusEditYour": "", + "distributedUptimeStatusPublishedLabel": "", + "distributedUptimeStatusCompanyNameLabel": "", + "distributedUptimeStatusPageAddressLabel": "", + "distributedUptimeStatus30Days": "", + "distributedUptimeStatus60Days": "", + "distributedUptimeStatus90Days": "", + "distributedUptimeStatusPageNotSetUp": "", + "distributedUptimeStatusContactAdmin": "", + "distributedUptimeStatusPageNotPublic": "", + "distributedUptimeStatusPageDeleteDialog": "", + "distributedUptimeStatusPageDeleteConfirm": "", + "distributedUptimeStatusPageDeleteDescription": "", + "distributedUptimeStatusDevices": "", + "distributedUptimeStatusUpt": "", + "distributedUptimeStatusUptBurned": "", + "distributedUptimeStatusUptLogo": "", + "incidentsTableNoIncidents": "", + "incidentsTablePaginationLabel": "", + "incidentsTableMonitorName": "", + "incidentsTableStatus": "", + "incidentsTableDateTime": "", + "incidentsTableStatusCode": "", + "incidentsTableMessage": "", + "incidentsOptionsHeader": "", + "incidentsOptionsHeaderFilterBy": "", + "incidentsOptionsHeaderFilterAll": "", + "incidentsOptionsHeaderFilterDown": "", + "incidentsOptionsHeaderFilterCannotResolve": "", + "incidentsOptionsHeaderShow": "", + "incidentsOptionsHeaderLastHour": "", + "incidentsOptionsHeaderLastDay": "", + "incidentsOptionsHeaderLastWeek": "", + "incidentsOptionsPlaceholderAllServers": "", + "infrastructureCreateYour": "", + "infrastructureCreateGeneralSettingsDescription": "", + "infrastructureServerRequirement": "", + "infrastructureCustomizeAlerts": "", + "infrastructureAlertNotificationDescription": "", + "infrastructureCreateMonitor": "", + "infrastructureProtocol": "", + "infrastructureServerUrlLabel": "", + "infrastructureDisplayNameLabel": "", + "infrastructureAuthorizationSecretLabel": "", + "gb": "", + "mb": "", + "mem": "", + "memoryUsage": "", + "cpu": "", + "cpuUsage": "", + "cpuTemperature": "", + "diskUsage": "", + "used": "", + "total": "", + "cores": "", + "frequency": "", + "status": "", + "cpuPhysical": "", + "cpuLogical": "", + "cpuFrequency": "", + "avgCpuTemperature": "", + "memory": "", + "disk": "", + "uptime": "", + "os": "", + "host": "", + "actions": "", + "integrations": "", + "integrationsPrism": "", + "integrationsSlack": "", + "integrationsSlackInfo": "", + "integrationsDiscord": "", + "integrationsDiscordInfo": "", + "integrationsZapier": "", + "integrationsZapierInfo": "", + "commonSave": "", + "createYour": "", + "createMonitor": "", + "pause": "", + "resume": "", + "editing": "", + "url": "", + "access": "", + "timezone": "", + "features": "", + "administrator": "", + "loginHere": "", + "displayName": "", + "urlMonitor": "", + "portToMonitor": "", + "websiteMonitoring": "", + "websiteMonitoringDescription": "", + "pingMonitoring": "", + "pingMonitoringDescription": "", + "dockerContainerMonitoring": "", + "dockerContainerMonitoringDescription": "", + "portMonitoring": "", + "portMonitoringDescription": "", + "createMaintenanceWindow": "", + "createMaintenance": "", + "editMaintenance": "", + "maintenanceWindowName": "", + "friendlyNameInput": "", + "friendlyNamePlaceholder": "", + "maintenanceRepeat": "", + "maintenance": "", + "duration": "", + "addMonitors": "", + "window": "", + "cancel": "", + "message": "", + "low": "", + "high": "", + "statusCode": "", + "date&Time": "", + "type": "", + "statusPageName": "", + "publicURL": "", + "repeat": "", + "edit": "", + "createA": "", + "remove": "", + "maintenanceWindowDescription": "", + "startTime": "", + "timeZoneInfo": "", + "monitorsToApply": "", + "nextWindow": "", + "notFoundButton": "", + "pageSpeedConfigureSettingsDescription": "", + "monitorDisplayName": "", + "whenNewIncident": "", + "notifySMS": "", + "notifyEmails": "", + "seperateEmails": "", + "checkFrequency": "", + "matchMethod": "", + "expectedValue": "", + "deleteDialogTitle": "", + "deleteDialogDescription": "", + "pageSpeedMonitor": "", + "shown": "", + "ago": "", + "companyName": "", + "pageSpeedDetailsPerformanceReport": "", + "pageSpeedDetailsPerformanceReportCalculator": "", + "checkingEvery": "", + "statusPageCreateSettings": "", + "basicInformation": "", + "statusPageCreateBasicInfoDescription": "", + "statusPageCreateSelectTimeZoneDescription": "", + "statusPageCreateAppearanceDescription": "", + "statusPageCreateSettingsCheckboxLabel": "", + "statusPageCreateBasicInfoStatusPageAddress": "", + "statusPageCreateTabsContent": "", + "statusPageCreateTabsContentDescription": "", + "statusPageCreateTabsContentFeaturesDescription": "", + "showCharts": "", + "showUptimePercentage": "", + "removeLogo": "", + "statusPageStatus": "", + "statusPageStatusContactAdmin": "", + "statusPageStatusNotPublic": "", + "statusPageStatusNoPage": "", + "statusPageStatusServiceStatus": "", + "deleteStatusPage": "", + "deleteStatusPageConfirm": "", + "deleteStatusPageDescription": "", + "uptimeCreate": "", + "uptimeCreateJsonPath": "", + "uptimeCreateJsonPathQuery": "", + "maintenanceTableActionMenuDialogTitle": "", + "infrastructureEditYour": "", + "infrastructureEditMonitor": "", + "infrastructureMonitorCreated": "", + "infrastructureMonitorUpdated": "", + "errorInvalidTypeId": "", + "errorInvalidFieldId": "", + "inviteNoTokenFound": "", + "pageSpeedWarning": "", + "pageSpeedLearnMoreLink": "", + "pageSpeedAddApiKey": "", + "update": "", + "invalidFileFormat": "", + "invalidFileSize": "", + "ClickUpload": "", + "DragandDrop": "", + "MaxSize": "", + "SupportedFormats": "", + "FirstName": "", + "LastName": "", + "EmailDescriptionText": "", + "YourPhoto": "", + "PhotoDescriptionText": "", + "save": "", + "DeleteDescriptionText": "", + "DeleteAccountWarning": "", + "DeleteWarningTitle": "", + "bulkImport": { + "title": "", + "selectFileTips": "", + "selectFileDescription": "", + "selectFile": "", + "parsingFailed": "", + "uploadSuccess": "", + "validationFailed": "", + "noFileSelected": "", + "fallbackPage": "", + "invalidFileType": "", + "uploadFailed": "" + }, + "DeleteAccountTitle": "", + "DeleteAccountButton": "", + "publicLink": "", + "maskedPageSpeedKeyPlaceholder": "", + "reset": "", + "ignoreTLSError": "", + "tlsErrorIgnored": "", + "ignoreTLSErrorDescription": "", + "createNew": "", + "greeting": { + "prepend": "", + "append": "", + "overview": "" + }, + "roles": { + "superAdmin": "", + "admin": "", + "teamMember": "", + "demoUser": "" + }, + "teamPanel": { + "teamMembers": "", + "filter": { + "all": "", + "member": "" + }, + "inviteTeamMember": "", + "inviteNewTeamMember": "", + "inviteDescription": "", + "email": "", + "selectRole": "", + "inviteLink": "", + "cancel": "", + "noMembers": "", + "getToken": "", + "emailToken": "", + "table": { + "name": "", + "email": "", + "role": "", + "created": "" + } + }, + "monitorState": { + "paused": "", + "resumed": "", + "active": "" + }, + "menu": { + "uptime": "", + "pagespeed": "", + "infrastructure": "", + "incidents": "", + "statusPages": "", + "maintenance": "", + "integrations": "", + "settings": "", + "support": "", + "discussions": "", + "docs": "", + "changelog": "", + "profile": "", + "password": "", + "team": "", + "logOut": "", + "notifications": "", + "logs": "" + }, + "settingsEmailUser": "", + "state": "", + "statusBreadCrumbsStatusPages": "", + "statusBreadCrumbsDetails": "", + "commonSaving": "", + "navControls": "", + "incidentsPageTitle": "", + "passwordPanel": { + "passwordChangedSuccess": "", + "passwordInputIncorrect": "", + "currentPassword": "", + "enterCurrentPassword": "", + "newPassword": "", + "enterNewPassword": "", + "confirmNewPassword": "", + "passwordRequirements": "", + "saving": "" + }, + "emailSent": "", + "failedToSendEmail": "", + "settingsTestEmailSuccess": "", + "settingsTestEmailFailed": "", + "settingsTestEmailFailedWithReason": "", + "settingsTestEmailUnknownError": "", + "statusMsg": { + "paused": "", + "up": "", + "down": "", + "pending": "" + }, + "uptimeGeneralInstructions": { + "http": "", + "ping": "", + "docker": "", + "port": "" + }, + "common": { + "appName": "Checkmate", + "monitoringAgentName": "Capture", + "buttons": { + "toggleTheme": "Přepnout mezi světlým a tmavým motivem" + }, + "toasts": { + "networkError": "Chyba připojení k síti", + "checkConnection": "Zkontrolujte prosím své připojení k síti", + "unknownError": "Nastala neznámá chyba" + } + }, + "auth": { + "common": { + "navigation": { + "continue": "Pokračovat", + "back": "Zpět" + }, + "inputs": { + "email": { + "label": "E-mail", + "placeholder": "jan.novak@domena.cz", + "errors": { + "empty": "Zadejte prosím e-mailovou adresu", + "invalid": "Překontrolujte si prosím správnost zadané e-mailové adresy" + } + }, + "password": { + "label": "Heslo", + "rules": { + "length": { + "beginning": "Heslo musí být dlouhé alespoň", + "highlighted": "8 znaků" + }, + "special": { + "beginning": "Heslo musí obsahovat alespoň", + "highlighted": "1 speciální znak" + }, + "number": { + "beginning": "Heslo musí obsahovat alespoň", + "highlighted": "1 číslo" + }, + "uppercase": { + "beginning": "Heslo musí obsahovat alespoň", + "highlighted": "1 velké písmeno" + }, + "lowercase": { + "beginning": "Heslo musí obsahovat alespoň", + "highlighted": "1 malé písmeno" + }, + "match": { + "beginning": "Obě hesla se", + "highlighted": "musí shodovat" + } + }, + "errors": { + "empty": "Zadejte prosím heslo", + "length": "Heslo musí být dlouhé alespoň 8 znaků", + "uppercase": "Heslo musí obsahovat alespoň 1 velké písmeno", + "lowercase": "Heslo musí obsahovat alespoň 1 malé písmeno", + "number": "Heslo musí obsahovat alespoň 1 číslo", + "special": "Heslo musí obsahovat alespoň 1 speciální znak", + "incorrect": "Zadané heslo neodpovídá tomu, co bylo nastaveno" + } + }, + "passwordConfirm": { + "label": "Potvrzení hesla", + "placeholder": "Pro ověření správnosti zadejte heslo ještě jednou", + "errors": { + "empty": "Zadejte prosím své heslo ještě jednou, aby mohla být potvrzena jeho správnost (odhalí překlepy)", + "different": "Zadaná hesla se liší, takže je nejspíše jedno z nich špatně zapsané" + } + }, + "firstName": { + "label": "Jméno", + "placeholder": "Jan", + "errors": { + "empty": "Zadejte prosím své křestní jméno", + "length": "Jméno se musí vejít do 50 znaků", + "pattern": "Součástí jména mohou být pouze písmena, mezery apostrofy nebo spojovníky" + } + }, + "lastName": { + "label": "Příjmení", + "placeholder": "Novák", + "errors": { + "empty": "Zadejte prosím své příjmení", + "length": "Příjmení se musí vejít do 50 znaků", + "pattern": "Součástí příjmení mohou být pouze písmena, mezery apostrofy nebo spojovníky" + } + } + }, + "errors": { + "validation": "Nastala chyba při ověřování dat." + }, + "fields": { + "password": { + "errors": { + "incorrect": "" + } + }, + "role": { + "errors": { + "min": "" + } + } + } + }, + "login": { + "heading": "Přihlášení", + "subheadings": { + "stepOne": "Zadejte svůj e-mail", + "stepTwo": "Zadejte své heslo" + }, + "links": { + "forgotPassword": "Zapomněli jste heslo? Obnovte si ho", + "register": "Ještě nemáte účet? Zaregistrujte se", + "forgotPasswordLink": "", + "registerLink": "" + }, + "toasts": { + "success": "Vítejte zpět", + "incorrectPassword": "Nesprávné heslo" + }, + "errors": { + "password": { + "incorrect": "" + } + } + }, + "registration": { + "heading": { + "superAdmin": "Vytvoření superadministrátora", + "user": "Registrace" + }, + "subheadings": { + "stepOne": "Zadejte své osobní údaje", + "stepTwo": "Zadejte svůj e-mail", + "stepThree": "Nastavte si heslo" + }, + "description": { + "superAdmin": "Pro začátek vytvořte účet superadministrátora", + "user": "Zaregistrujte se jako uživatel a požádejte svého superadministrátora, aby vám přidělil oprávnění k hlídačům" + }, + "gettingStartedButton": { + "superAdmin": "Vytvořit účet superadministrátora", + "user": "Zaregistrovat se jako běžný uživatel" + }, + "termsAndPolicies": "Vytvořením účtu souhlasíte s našimi Podmínkami použití (anglicky) a Podmínkami zpracování osobních údajů (anglicky).", + "links": { + "login": "Máte již vytvořený účet? Přihlašte se" + }, + "toasts": { + "success": "Vítejte! Váš účet byl úspěšně vytvořen." + } + }, + "forgotPassword": { + "heading": "Zapomenuté heslo", + "subheadings": { + "stepOne": "Nebojte se, zašleme vám pokyny pro obnovení hesla.", + "stepTwo": "Odkaz pro obnovení hesla byl odeslán na ", + "stepThree": "Nové heslo se musí lišit od těch, které jste již použili.", + "stepFour": "Heslo bylo úspěšně obnoveno. Klepněte na tlačítko a dojde k čarovnému přihlášení." + }, + "buttons": { + "openEmail": "Otevřít e-mailovou aplikaci", + "resetPassword": "Obnovit heslo" + }, + "imageAlts": { + "passwordKey": "Ikonka přístupového klíče", + "email": "Ikonka e-mailu", + "lock": "Ikonka zámku", + "passwordConfirm": "Ikonka potvrzeného hesla" + }, + "links": { + "login": "Vrátit se k Přihlášení", + "resend": "Nedorazil vám e-mail? Klepněte pro opětovné zaslání" + }, + "toasts": { + "sent": "Instrukce vám dorazí na .", + "emailNotFound": "Nepodařilo se nalézt e-mailovou adresu.", + "redirect": "Přesměrování proběhne za ...", + "success": "Heslo bylo úspěšně obnoveno.", + "error": "Obnova hesla se nepodařila. Zkuste to prosím znovu nebo kontaktujte podporu." + } + } + }, + "errorPages": { + "serverUnreachable": { + "toasts": { + "reconnected": "Podařilo se úspěšně obnovit spojení se serverem", + "stillUnreachable": "Server je stále nedosažitelný. Zkuste to prosím později." + }, + "alertBox": "Chyba připojení k serveru", + "description": "Nepodařilo se připojit k serveru. Zkontrolujte si prosím internetové připojení a pokud problém přetrvává, ověřte konfiguraci aplikace Checkmate.", + "retryButton": { + "default": "Zkusit se znovu připojit", + "processing": "Probíhá připojování…" + } + } + }, + "createNotifications": { + "title": "", + "nameSettings": { + "title": "", + "description": "", + "nameLabel": "", + "namePlaceholder": "" + }, + "typeSettings": { + "title": "", + "description": "", + "typeLabel": "" + }, + "emailSettings": { + "title": "", + "description": "", + "emailLabel": "", + "emailPlaceholder": "" + }, + "slackSettings": { + "title": "", + "description": "", + "webhookLabel": "", + "webhookPlaceholder": "" + }, + "pagerdutySettings": { + "title": "", + "description": "", + "integrationKeyLabel": "", + "integrationKeyPlaceholder": "" + }, + "discordSettings": { + "title": "", + "description": "", + "webhookLabel": "", + "webhookPlaceholder": "" + }, + "webhookSettings": { + "title": "", + "description": "", + "webhookLabel": "", + "webhookPlaceholder": "" + }, + "testNotification": "", + "dialogDeleteTitle": "", + "dialogDeleteConfirm": "" + }, + "notificationConfig": { + "title": "", + "description": "" + }, + "monitorStatus": { + "checkingEvery": "", + "withCaptureAgent": "", + "up": "", + "down": "", + "paused": "" + }, + "advancedMatching": "", + "sendTestNotifications": "", + "selectAll": "", + "showAdminLoginLink": "", + "logsPage": { + "title": "", + "description": "", + "tabs": { + "queue": "", + "logs": "", + "diagnostics": "" + }, + "toast": { + "fetchLogsSuccess": "" + }, + "logLevelSelect": { + "title": "", + "values": { + "all": "", + "info": "", + "warn": "", + "error": "", + "debug": "" + } + } + }, + "queuePage": { + "title": "", + "refreshButton": "", + "flushButton": "", + "jobTable": { + "title": "", + "idHeader": "", + "urlHeader": "", + "typeHeader": "", + "activeHeader": "", + "lockedAtHeader": "", + "runCountHeader": "", + "failCountHeader": "", + "lastRunHeader": "", + "lastFinishedAtHeader": "", + "lastRunTookHeader": "" + }, + "metricsTable": { + "title": "", + "metricHeader": "", + "valueHeader": "" + }, + "failedJobTable": { + "title": "", + "monitorIdHeader": "", + "monitorUrlHeader": "", + "failCountHeader": "", + "failedAtHeader": "", + "failReasonHeader": "" + } + }, + "export": { + "title": "", + "success": "", + "failed": "" + }, + "monitorActions": { + "title": "", + "import": "", + "export": "", + "deleteSuccess": "", + "deleteFailed": "", + "details": "" + }, + "settingsPage": { + "aboutSettings": { + "labelDevelopedBy": "", + "labelVersion": "", + "title": "" + }, + "demoMonitorsSettings": { + "buttonAddMonitors": "", + "description": "", + "title": "" + }, + "emailSettings": { + "buttonSendTestEmail": "", + "description": "", + "descriptionTransport": "", + "labelAddress": "", + "labelConnectionHost": "", + "labelHost": "", + "labelIgnoreTLS": "", + "labelPassword": "", + "labelPasswordSet": "", + "labelPool": "", + "labelPort": "", + "labelRejectUnauthorized": "", + "labelRequireTLS": "", + "labelSecure": "", + "labelTLSServername": "", + "labelUser": "", + "linkTransport": "", + "placeholderUser": "", + "title": "", + "toastEmailRequiredFieldsError": "" + }, + "pageSpeedSettings": { + "description": "", + "labelApiKeySet": "", + "labelApiKey": "", + "title": "" + }, + "saveButtonLabel": "", + "statsSettings": { + "clearAllStatsButton": "", + "clearAllStatsDescription": "", + "clearAllStatsDialogConfirm": "", + "clearAllStatsDialogDescription": "", + "clearAllStatsDialogTitle": "", + "description": "", + "labelTTL": "", + "labelTTLOptional": "", + "title": "" + }, + "systemResetSettings": { + "buttonRemoveAllMonitors": "", + "description": "", + "dialogConfirm": "", + "dialogDescription": "", + "dialogTitle": "", + "title": "" + }, + "timezoneSettings": { + "description": "", + "label": "", + "title": "" + }, + "title": "", + "uiSettings": { + "description": "", + "labelLanguage": "", + "labelTheme": "", + "title": "" + }, + "urlSettings": { + "description": "", + "label": "", + "selectDisabled": "", + "selectEnabled": "", + "title": "" + } + }, + "statusPageCreate": { + "buttonSave": "" + }, + "incidentsOptionsHeaderFilterResolved": "", + "settingsSave": "", + "statusPageCreateAppearanceTitle": "", + "confirmPassword": "", + "monitorHooks": { + "failureAddDemoMonitors": "", + "successAddDemoMonitors": "" + }, + "settingsAppearance": "", + "settingsDisplayTimezone": "", + "settingsGeneralSettings": "", + "incidentsOptionsHeaderTotalIncidents": "", + "statusPage": { + "deleteSuccess": "", + "deleteFailed": "", + "createSuccess": "", + "updateSuccess": "", + "generalSettings": "", + "contents": "", + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "testNotificationsDisabled": "", + "incidentsTableResolvedAt": "", + "incidentsTableActionResolve": "", + "checkHooks": { + "failureResolveOne": "", + "failureResolveAll": "", + "failureResolveMonitor": "" + }, + "checkFormError": "", + "diagnosticsPage": { + "diagnosticDescription": "", + "statsDescription": "", + "gauges": { + "heapAllocationTitle": "", + "heapAllocationSubtitle": "", + "heapUsageTitle": "", + "heapUsageSubtitle": "", + "heapUtilizationTitle": "", + "heapUtilizationSubtitle": "", + "instantCpuUsageTitle": "", + "instantCpuUsageSubtitle": "" + }, + "stats": { + "eventLoopDelayTitle": "", + "uptimeTitle": "", + "usedHeapSizeTitle": "", + "totalHeapSizeTitle": "", + "osMemoryLimitTitle": "" + } + }, + "pageSpeedLighthouseAPI": "", + "time": { + "threeMinutes": "", + "fiveMinutes": "", + "tenMinutes": "", + "twentyMinutes": "", + "oneHour": "", + "oneDay": "", + "oneWeek": "" + }, + "general": { + "noOptionsFound": "" + }, + "infrastructureMonitor": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "maintenanceWindow": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "pageSpeed": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "uptimeMonitor": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "editUserPage": { + "form": { + "email": "", + "firstName": "", + "lastName": "", + "role": "", + "save": "" + }, + "table": { + "actionHeader": "", + "roleHeader": "" + }, + "title": "", + "toast": { + "successUserUpdate": "", + "validationErrors": "" + } + }, + "incidentsPageActionResolveMonitor": "", + "incidentsPageActionResolveAll": "" +} diff --git a/client/src/locales/de.json b/client/src/locales/de.json index 1938dcccd..aa416bd95 100644 --- a/client/src/locales/de.json +++ b/client/src/locales/de.json @@ -1,84 +1,83 @@ { "submit": "Absenden", "title": "Titel", - "distributedStatusHeaderText": "", - "distributedStatusSubHeaderText": "", + "distributedStatusHeaderText": "Echtzeit Abdeckung", + "distributedStatusSubHeaderText": "Genutzt von Millionen von Geräten weltweit, siehst Du die Systemleistung nach globaler Region, Land oder Stadt", "settingsDisabled": "Inaktiv", "settingsSuccessSaved": "Einstellungen erfolgreich gespeichert", "settingsFailedToSave": "Fehler beim Speichern der Einstellungen", "settingsStatsCleared": "Statistiken gelöscht", "settingsFailedToClearStats": "Fehler beim löschen der Statistiken", - "settingsFailedToAddDemoMonitors": "", - "settingsMonitorsDeleted": "", - "settingsFailedToDeleteMonitors": "", - "starPromptTitle": "", - "starPromptDescription": "", + "settingsMonitorsDeleted": "Alle Monitore erfolgreich gelöscht.", + "settingsFailedToDeleteMonitors": "Fehler beim Löschen aller Monitore", + "starPromptTitle": "Stern vergeben", + "starPromptDescription": "Sieh dir die neuesten Veröffentlichungen an und hilf der Community auf GitHub", "https": "HTTPS", "http": "HTTP", - "monitor": "", + "monitor": "monitor", "aboutus": "Über uns", "now": "Jetzt", "delete": "Löschen", "configure": "Einstellungen", "responseTime": "Antwortzeit", "ms": "ms", - "bar": "", - "area": "", + "bar": "Bar", + "area": "Bereich", "country": "Land", "city": "Stadt", "response": "Antwort", - "monitorStatusUp": "", - "monitorStatusDown": "", - "webhookSendSuccess": "", - "webhookSendError": "", - "webhookUnsupportedPlatform": "", - "distributedRightCategoryTitle": "", - "distributedStatusServerMonitors": "", - "distributedStatusServerMonitorsDescription": "", - "distributedUptimeCreateSelectURL": "", - "distributedUptimeCreateChecks": "", - "distributedUptimeCreateChecksDescription": "", - "distributedUptimeCreateIncidentNotification": "", - "distributedUptimeCreateIncidentDescription": "", + "monitorStatusUp": "Monitor {name} ({url}) ist jetzt ONLINE und antwortet", + "monitorStatusDown": "Monitor {name} ({url}) ist jetzt OFFLINE und antwortet nicht", + "webhookSendSuccess": "Webhook-Benachrichtigung erfolgreich gesendet", + "webhookSendError": "Fehler beim Senden der Webhook-Benachrichtigung an {platform}", + "webhookUnsupportedPlatform": "Nicht unterstützte Plattform: {platform}", + "distributedRightCategoryTitle": "Monitor", + "distributedStatusServerMonitors": "Server Monitore", + "distributedStatusServerMonitorsDescription": "Überwache den Status der zugehörigen Server", + "distributedUptimeCreateSelectURL": "Hier kannst Du die URL und Typ des Hosts auswählen", + "distributedUptimeCreateChecks": "Durchzuführende Prüfungen", + "distributedUptimeCreateChecksDescription": "Du kannst jederzeit Überprüfungen hinzufügen oder entfernen, nachdem Du Deine Seite hinzugefügt hast.\n", + "distributedUptimeCreateIncidentNotification": "Ereignisbenachrichtigungen", + "distributedUptimeCreateIncidentDescription": "Wenn es ein Ereignis gibt, benachrichtige die Benutzer.", "distributedUptimeCreateAdvancedSettings": "Erweiterte Einstellungen", - "distributedUptimeDetailsNoMonitorHistory": "", - "distributedUptimeDetailsStatusHeaderUptime": "", - "distributedUptimeDetailsStatusHeaderLastUpdate": "", + "distributedUptimeDetailsNoMonitorHistory": "Es gibt noch keine Überprüfungshistorie für diesen Monitor.", + "distributedUptimeDetailsStatusHeaderUptime": "Uptime:", + "distributedUptimeDetailsStatusHeaderLastUpdate": "Zuletzt aktualisiert", "notifications": { - "enableNotifications": "", - "testNotification": "", - "addOrEditNotifications": "", + "enableNotifications": "Aktiviere {{platform}} Benachrichtigungen\n", + "testNotification": "Test Benachrichtigung", + "addOrEditNotifications": "Benachrichtigungen hinzufügen oder bearbeiten", "slack": { "label": "Slack", - "description": "", + "description": "Um Slack-Benachrichtigungen zu aktivieren, erstelle eine Slack-App und aktiviere eingehende Webhooks. Danach gib einfach die Webhook-URL hier ein.", "webhookLabel": "Webhook URL", "webhookPlaceholder": "", - "webhookRequired": "" + "webhookRequired": "Slack webhook URL wird benötigt" }, "discord": { "label": "Discord", - "description": "", + "description": "Um Daten von Checkmate an einen Discord-Kanal über Discord-Benachrichtigungen mithilfe von Webhooks zu senden, kannst Du die eingehenden Webhooks von Discord verwenden.", "webhookLabel": "Discord Webhook URL", "webhookPlaceholder": "", - "webhookRequired": "" + "webhookRequired": "Discord webhook URL wird benötigt" }, "telegram": { "label": "Telegram", - "description": "", - "tokenLabel": "", + "description": "Um Telegram-Benachrichtigungen zu aktivieren, erstelle einen Telegram-Bot mit BotFather, einem offiziellen Bot zum Erstellen und Verwalten von Telegram-Bots. Dann hole dir das API-Token und die Chat-ID und notiere sie hier.", + "tokenLabel": "Dein bot token", "tokenPlaceholder": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", - "chatIdLabel": "", + "chatIdLabel": "Deine Chat ID", "chatIdPlaceholder": "-1001234567890", - "fieldsRequired": "" + "fieldsRequired": "Telegram token und Chat-ID werden benötigt" }, "webhook": { "label": "Webhooks", - "description": "", + "description": "Du kannst einen benutzerdefinierten Webhook einrichten, um Benachrichtigungen zu erhalten, wenn Ereignisse auftreten.", "urlLabel": "Webhook URL", "urlPlaceholder": "https://your-server.com/webhook", - "urlRequired": "" + "urlRequired": "Webhook URL wird benötigt" }, - "testNotificationDevelop": "", + "testNotificationDevelop": "Testbenachrichtigung 2", "integrationButton": "", "testSuccess": "", "testFailed": "", @@ -86,7 +85,8 @@ "networkError": "", "fallback": { "title": "", - "checks": [""] + "checks": [""], + "actionButton": "" }, "createButton": "", "createTitle": "", @@ -107,166 +107,166 @@ "failed": "" }, "test": { - "success": "", - "failed": "" + "success": "Test Benachrichtigung erfolgreich gesendet", + "failed": "Fehler beim senden der Test Benachrichtigung" } }, - "testLocale": "", - "add": "", - "monitors": "", - "distributedUptimeStatusCreateStatusPage": "", - "distributedUptimeStatusCreateStatusPageAccess": "", - "distributedUptimeStatusCreateStatusPageReady": "", - "distributedUptimeStatusBasicInfoHeader": "", - "distributedUptimeStatusBasicInfoDescription": "", - "distributedUptimeStatusLogoHeader": "", - "distributedUptimeStatusLogoDescription": "", - "distributedUptimeStatusLogoUploadButton": "", - "distributedUptimeStatusStandardMonitorsHeader": "", - "distributedUptimeStatusStandardMonitorsDescription": "", + "testLocale": "testLocale", + "add": "Hinzufügen", + "monitors": "Monitore", + "distributedUptimeStatusCreateStatusPage": "Statusseite", + "distributedUptimeStatusCreateStatusPageAccess": "Zugriff", + "distributedUptimeStatusCreateStatusPageReady": "Wenn deine Statusseite bereit ist, kannst du sie als veröffentlicht markieren.", + "distributedUptimeStatusBasicInfoHeader": "Grundlegende Informationen", + "distributedUptimeStatusBasicInfoDescription": "Definiere den Firmennamen und die Subdomain, auf die deine Statusseite verweist.", + "distributedUptimeStatusLogoHeader": "Logo", + "distributedUptimeStatusLogoDescription": "Lade ein Logo für deine Statusseite hoch", + "distributedUptimeStatusLogoUploadButton": "Logo hochladen", + "distributedUptimeStatusStandardMonitorsHeader": "Standardmonitore", + "distributedUptimeStatusStandardMonitorsDescription": "Füge Standardmonitore zu deiner Statusseite hinzu.", "distributedUptimeStatusCreateYour": "Erstelle Dein", "distributedUptimeStatusEditYour": "Ändere Dein", - "distributedUptimeStatusPublishedLabel": "", + "distributedUptimeStatusPublishedLabel": "Veröffentlicht und für die Öffentlichkeit sichtbar", "distributedUptimeStatusCompanyNameLabel": "Firmenname", - "distributedUptimeStatusPageAddressLabel": "", + "distributedUptimeStatusPageAddressLabel": "Die Adresse deiner Statusseite", "distributedUptimeStatus30Days": "30 Tage", "distributedUptimeStatus60Days": "60 Tage", "distributedUptimeStatus90Days": "90 Tage", - "distributedUptimeStatusPageNotSetUp": "", - "distributedUptimeStatusContactAdmin": "", - "distributedUptimeStatusPageNotPublic": "", - "distributedUptimeStatusPageDeleteDialog": "", - "distributedUptimeStatusPageDeleteConfirm": "", - "distributedUptimeStatusPageDeleteDescription": "", - "distributedUptimeStatusDevices": "", - "distributedUptimeStatusUpt": "", - "distributedUptimeStatusUptBurned": "", - "distributedUptimeStatusUptLogo": "", - "incidentsTableNoIncidents": "", - "incidentsTablePaginationLabel": "", - "incidentsTableMonitorName": "", - "incidentsTableStatus": "", - "incidentsTableDateTime": "", - "incidentsTableStatusCode": "", - "incidentsTableMessage": "", - "incidentsOptionsHeader": "", - "incidentsOptionsHeaderFilterBy": "", - "incidentsOptionsHeaderFilterAll": "", - "incidentsOptionsHeaderFilterDown": "", - "incidentsOptionsHeaderFilterCannotResolve": "", - "incidentsOptionsHeaderShow": "", - "incidentsOptionsHeaderLastHour": "", - "incidentsOptionsHeaderLastDay": "", - "incidentsOptionsHeaderLastWeek": "", - "incidentsOptionsPlaceholderAllServers": "", - "infrastructureCreateYour": "", - "infrastructureCreateGeneralSettingsDescription": "", - "infrastructureServerRequirement": "", - "infrastructureCustomizeAlerts": "", - "infrastructureAlertNotificationDescription": "", - "infrastructureCreateMonitor": "", - "infrastructureProtocol": "", - "infrastructureServerUrlLabel": "", - "infrastructureDisplayNameLabel": "", - "infrastructureAuthorizationSecretLabel": "", - "gb": "", - "mb": "", - "mem": "", - "memoryUsage": "", - "cpu": "", - "cpuUsage": "", - "cpuTemperature": "", - "diskUsage": "", - "used": "", - "total": "", - "cores": "", - "frequency": "", - "status": "", - "cpuPhysical": "", - "cpuLogical": "", - "cpuFrequency": "", - "avgCpuTemperature": "", - "memory": "", - "disk": "", - "uptime": "", - "os": "", - "host": "", - "actions": "", - "integrations": "", - "integrationsPrism": "", - "integrationsSlack": "", - "integrationsSlackInfo": "", - "integrationsDiscord": "", - "integrationsDiscordInfo": "", - "integrationsZapier": "", - "integrationsZapierInfo": "", - "commonSave": "", - "createYour": "", - "createMonitor": "", - "pause": "", - "resume": "", - "editing": "", - "url": "", - "access": "", - "timezone": "", - "features": "", - "administrator": "", - "loginHere": "", - "displayName": "", - "urlMonitor": "", - "portToMonitor": "", - "websiteMonitoring": "", - "websiteMonitoringDescription": "", - "pingMonitoring": "", - "pingMonitoringDescription": "", - "dockerContainerMonitoring": "", - "dockerContainerMonitoringDescription": "", - "portMonitoring": "", - "portMonitoringDescription": "", - "createMaintenanceWindow": "", - "createMaintenance": "", - "editMaintenance": "", - "maintenanceWindowName": "", + "distributedUptimeStatusPageNotSetUp": "Eine Statusseite ist nicht eingerichtet.", + "distributedUptimeStatusContactAdmin": "Bitte kontaktiere deinen Administrator", + "distributedUptimeStatusPageNotPublic": "Diese Statusseite ist nicht öffentlich.", + "distributedUptimeStatusPageDeleteDialog": "Möchtest du diese Statusseite löschen?", + "distributedUptimeStatusPageDeleteConfirm": "Ja, Statusseite löschen", + "distributedUptimeStatusPageDeleteDescription": "Sobald sie gelöscht ist, kann deine Statusseite nicht wiederhergestellt werden.", + "distributedUptimeStatusDevices": "Geräte", + "distributedUptimeStatusUpt": "UPT", + "distributedUptimeStatusUptBurned": "UPT Burned", + "distributedUptimeStatusUptLogo": "Upt Logo", + "incidentsTableNoIncidents": "Keine Ereignisse aufgezeichnet", + "incidentsTablePaginationLabel": "Ereignisse", + "incidentsTableMonitorName": "Monitor Name", + "incidentsTableStatus": "Status", + "incidentsTableDateTime": "Datum & Zeit", + "incidentsTableStatusCode": "Status Code", + "incidentsTableMessage": "Nachricht", + "incidentsOptionsHeader": "Ereignisse für", + "incidentsOptionsHeaderFilterBy": "Gefiltert nach:", + "incidentsOptionsHeaderFilterAll": "Alle", + "incidentsOptionsHeaderFilterDown": "offline", + "incidentsOptionsHeaderFilterCannotResolve": "Kann nicht aufgelöst werden", + "incidentsOptionsHeaderShow": "Anzeigen:", + "incidentsOptionsHeaderLastHour": "Letzte Stunde", + "incidentsOptionsHeaderLastDay": "Letzter Tag", + "incidentsOptionsHeaderLastWeek": "Letzte Woche", + "incidentsOptionsPlaceholderAllServers": "Alle Server", + "infrastructureCreateYour": "Erstelle dein", + "infrastructureCreateGeneralSettingsDescription": "Hier kannst du die URL des Hosts auswählen, zusammen mit dem Namen und dem Autorisierungs-token, um eine Verbindung zum Server-Agenten herzustellen.", + "infrastructureServerRequirement": "Der Server, den du überwachst, muss die folgende Software ausführen:", + "infrastructureCustomizeAlerts": "Benachrichtigungen anpassen", + "infrastructureAlertNotificationDescription": "Sende eine Benachrichtigung an Benutzer, wenn Schwellenwerte einen bestimmten Prozentsatz überschreiten.", + "infrastructureCreateMonitor": "Erstelle Infrastrukturmonitor", + "infrastructureProtocol": "Protokoll", + "infrastructureServerUrlLabel": "Server URL", + "infrastructureDisplayNameLabel": "Anzeigename", + "infrastructureAuthorizationSecretLabel": "Autorisierungs-token", + "gb": "GB", + "mb": "MB", + "mem": "Speicher", + "memoryUsage": "Speicher Nutzung", + "cpu": "CPU", + "cpuUsage": "CPU Nutzung", + "cpuTemperature": "CPU Temperatur", + "diskUsage": "Disk benutzt", + "used": "benutzt", + "total": "Total", + "cores": "Kerne", + "frequency": "Frequenz", + "status": "Status", + "cpuPhysical": "CPU (Physical)", + "cpuLogical": "CPU (Logical)", + "cpuFrequency": "CPU Frequenz", + "avgCpuTemperature": "Durchschn. CPU Termperatur", + "memory": "Speicher", + "disk": "Disk", + "uptime": "Uptime", + "os": "OS", + "host": "Host", + "actions": "Aktionen", + "integrations": "Integrationen", + "integrationsPrism": "Verbinde Prism mit deinem favorisierten Gerät", + "integrationsSlack": "Slack", + "integrationsSlackInfo": "Verbinde dich mit Slack und sieh Ereignisse in einem Kanal", + "integrationsDiscord": "Discord", + "integrationsDiscordInfo": "Verbinde dich mit Discord und sieh Ereignisse direkt in einem Kanal", + "integrationsZapier": "Zapier", + "integrationsZapierInfo": "Sende alle Ereignisse an Zapier und sieh sie dann überall", + "commonSave": "Speichern", + "createYour": "Erstelle Deinen", + "createMonitor": "Erstelle Monitor", + "pause": "Pausieren", + "resume": "Weiter", + "editing": "Bearbeiten...", + "url": "URL", + "access": "Zugriff", + "timezone": "Zeitzone", + "features": "Funktionen", + "administrator": "Administrator?", + "loginHere": "Hier anmelden", + "displayName": "Anzeigename", + "urlMonitor": "zu überwachende URL", + "portToMonitor": "zu überwachender Port", + "websiteMonitoring": "Websiten-Überwachung", + "websiteMonitoringDescription": "Verwende HTTP(s), um deine Website oder API-Endpunkt zu überwachen.", + "pingMonitoring": "Ping-Überwachung", + "pingMonitoringDescription": "Überprüfe, ob dein Server verfügbar ist oder nicht.", + "dockerContainerMonitoring": "Docker-Container-Überwachung", + "dockerContainerMonitoringDescription": "Überprüfe, ob dein Docker-Container läuft oder nicht.", + "portMonitoring": "Port-Überwachung", + "portMonitoringDescription": "Überprüfe, ob dein Port offen ist oder nicht.", + "createMaintenanceWindow": "Erstelle Wartungsfenster", + "createMaintenance": "Erstelle Wartungsfenster", + "editMaintenance": "Ändere Wartungsfenster", + "maintenanceWindowName": "Name des Wartungsfensters", "friendlyNameInput": "", - "friendlyNamePlaceholder": "", - "maintenanceRepeat": "", - "maintenance": "", - "duration": "", - "addMonitors": "", - "window": "", - "cancel": "", - "message": "", - "low": "", - "high": "", - "statusCode": "", - "date&Time": "", - "type": "", - "statusPageName": "", - "publicURL": "", - "repeat": "", - "edit": "", - "createA": "", - "remove": "", + "friendlyNamePlaceholder": "Wartung um __ : __ für ___ Minuten", + "maintenanceRepeat": "Wartungsfenster wiederholen", + "maintenance": "Wartungsfenster", + "duration": "Dauer", + "addMonitors": "Monitor hinzufügen", + "window": "Fenster", + "cancel": "Abbrechen", + "message": "Nachricht", + "low": "niedrig", + "high": "hoch", + "statusCode": "Status Code", + "date&Time": "Datum & Zeit", + "type": "Art", + "statusPageName": "Name der Statusseite", + "publicURL": "Öffentliche URL", + "repeat": "Wiederholen", + "edit": "Ändern", + "createA": "Erstelle ein", + "remove": "Löschen", "maintenanceWindowDescription": "", - "startTime": "", - "timeZoneInfo": "", - "monitorsToApply": "", - "nextWindow": "", - "notFoundButton": "", - "pageSpeedConfigureSettingsDescription": "", - "monitorDisplayName": "", - "whenNewIncident": "", - "notifySMS": "", - "notifyEmails": "", - "seperateEmails": "", - "checkFrequency": "", + "startTime": "Anfangszeit", + "timeZoneInfo": "Alle Daten und Zeiten sind in der Zeitzone GMT+0.", + "monitorsToApply": "Monitore, auf die das Wartungsfenster angewendet werden soll", + "nextWindow": "Nächstes Fenster", + "notFoundButton": "Gehe zum Haupt-Dashboard", + "pageSpeedConfigureSettingsDescription": "Hier kannst du die URL des Hosts auswählen, zusammen mit der Art des Monitors.", + "monitorDisplayName": "Anzeigename des Monitors", + "whenNewIncident": "Wenn es ein neues Ereignis gibt,", + "notifySMS": "Benachrichtige per SMS (kommt bald)", + "notifyEmails": "Benachrichtige auch per E-Mail an mehrere Adressen (kommt bald)", + "seperateEmails": "Du kannst mehrere E-Mails mit einem Komma trennen", + "checkFrequency": "Überprüfungsfrequenz", "matchMethod": "", - "expectedValue": "", - "deleteDialogTitle": "", - "deleteDialogDescription": "", + "expectedValue": "Erwarteter Wert", + "deleteDialogTitle": "Möchtest du diesen Monitor wirklich löschen?", + "deleteDialogDescription": "Sobald dieser Monitor gelöscht ist, kann er nicht wiederhergestellt werden.", "pageSpeedMonitor": "", "shown": "", - "ago": "", + "ago": "vor", "companyName": "", "pageSpeedDetailsPerformanceReport": "", "pageSpeedDetailsPerformanceReportCalculator": "", @@ -283,7 +283,7 @@ "statusPageCreateTabsContentFeaturesDescription": "", "showCharts": "", "showUptimePercentage": "", - "removeLogo": "", + "removeLogo": "Entferne Logo", "statusPageStatus": "", "statusPageStatusContactAdmin": "", "statusPageStatusNotPublic": "", @@ -304,129 +304,131 @@ "errorInvalidFieldId": "", "inviteNoTokenFound": "", "pageSpeedWarning": "", - "pageSpeedLearnMoreLink": "", - "pageSpeedAddApiKey": "", - "update": "", - "invalidFileFormat": "", - "invalidFileSize": "", + "pageSpeedLearnMoreLink": "Hier klicken", + "pageSpeedAddApiKey": "um einen API key einzutragen", + "update": "Update", + "invalidFileFormat": "Nicht unterstützes Dateiformat", + "invalidFileSize": "Dateigröße zu groß!", "ClickUpload": "", - "DragandDrop": "", - "MaxSize": "", - "SupportedFormats": "", - "FirstName": "", - "LastName": "", + "DragandDrop": "drag and drop", + "MaxSize": "Maximalgröße", + "SupportedFormats": "Unterstützte Formate", + "FirstName": "Vorname", + "LastName": "Zuname", "EmailDescriptionText": "", - "YourPhoto": "", + "YourPhoto": "Profilphoto", "PhotoDescriptionText": "", - "save": "", + "save": "Speichern", "DeleteDescriptionText": "", "DeleteAccountWarning": "", - "DeleteWarningTitle": "", + "DeleteWarningTitle": "Account wirklich löschen?", "bulkImport": { "title": "", "selectFileTips": "", "selectFileDescription": "", - "selectFile": "", + "selectFile": "Datei auswählen", "parsingFailed": "", - "uploadSuccess": "", - "validationFailed": "", - "noFileSelected": "", - "fallbackPage": "" + "uploadSuccess": "Monitore erfolgreich erstellt!", + "validationFailed": "Prüfung fehlgeschlagen", + "noFileSelected": "Keine Datei ausgewählt", + "fallbackPage": "", + "invalidFileType": "Ungültiger Dateityp", + "uploadFailed": "Upload fehlgeschlagen" }, - "DeleteAccountTitle": "", - "DeleteAccountButton": "", - "publicLink": "", - "maskedPageSpeedKeyPlaceholder": "", - "reset": "", - "ignoreTLSError": "", - "tlsErrorIgnored": "", - "ignoreTLSErrorDescription": "", - "createNew": "", + "DeleteAccountTitle": "Account löschen", + "DeleteAccountButton": "Account löschen", + "publicLink": "Öffentlicher Link", + "maskedPageSpeedKeyPlaceholder": "*************************************", + "reset": "Reset", + "ignoreTLSError": "TLS/SSL Fehler ignorieren", + "tlsErrorIgnored": "TLS/SSL Fehler ignoriert", + "ignoreTLSErrorDescription": "TLS/SSL Fehler ignorieren und Verfügbarkeit weiter prüfen", + "createNew": "Neu anlegen", "greeting": { - "prepend": "", + "prepend": "Hallo", "append": "", "overview": "" }, "roles": { - "superAdmin": "", - "admin": "", - "teamMember": "", - "demoUser": "" + "superAdmin": "Super admin", + "admin": "Admin", + "teamMember": "Team Mitglied", + "demoUser": "Demo user" }, "teamPanel": { - "teamMembers": "", + "teamMembers": "Team Mitglied", "filter": { - "all": "", - "member": "" + "all": "Alle", + "member": "Mitglied" }, - "inviteTeamMember": "", - "inviteNewTeamMember": "", + "inviteTeamMember": "Team Mirglied einladen", + "inviteNewTeamMember": "Neues Team Mirglied einladen", "inviteDescription": "", - "email": "", - "selectRole": "", - "inviteLink": "", - "cancel": "", - "noMembers": "", - "getToken": "", - "emailToken": "", + "email": "Email", + "selectRole": "Rolle auswählen", + "inviteLink": "Einladungslink", + "cancel": "Abbrechen", + "noMembers": "Es gibt keine Team Mitglieder mit dieser Rolle", + "getToken": "Token holen", + "emailToken": "E-mail token", "table": { - "name": "", - "email": "", - "role": "", - "created": "" + "name": "Name", + "email": "Email", + "role": "Rolle", + "created": "Angelegt" } }, "monitorState": { - "paused": "", + "paused": "Pausiert", "resumed": "", - "active": "" + "active": "Aktiv" }, "menu": { - "uptime": "", + "uptime": "Uptime", "pagespeed": "", "infrastructure": "", "incidents": "", - "statusPages": "", - "maintenance": "", + "statusPages": "Statusseite", + "maintenance": "Wartung", "integrations": "", - "settings": "", - "support": "", - "discussions": "", - "docs": "", - "changelog": "", - "profile": "", - "password": "", - "team": "", - "logOut": "", - "notifications": "", - "logs": "" + "settings": "Einstellungen", + "support": "Support", + "discussions": "Diskussionen", + "docs": "Anleitungen", + "changelog": "Änderungen", + "profile": "Profil", + "password": "Passwort", + "team": "Team", + "logOut": "Abmelden", + "notifications": "Benachrichtigungen", + "logs": "Logs" }, "settingsEmailUser": "", "state": "", - "statusBreadCrumbsStatusPages": "", - "statusBreadCrumbsDetails": "", - "commonSaving": "", + "statusBreadCrumbsStatusPages": "Statusseite", + "statusBreadCrumbsDetails": "Details", + "commonSaving": "Speichere...", "navControls": "", - "incidentsPageTitle": "", + "incidentsPageTitle": "Ereignisse", "passwordPanel": { - "passwordChangedSuccess": "", - "passwordInputIncorrect": "", - "currentPassword": "", - "enterCurrentPassword": "", - "newPassword": "", - "enterNewPassword": "", - "confirmNewPassword": "", - "passwordRequirements": "", - "saving": "" + "passwordChangedSuccess": "Dein Passwort wurde erfolgreich geändert.", + "passwordInputIncorrect": "Die Passworteingabe war nicht korrekt.", + "currentPassword": "Aktuelles Passwort", + "enterCurrentPassword": "Aktuelles Passwort eingeben", + "newPassword": "Neues Passwort", + "enterNewPassword": "Neues Passwort eingeben", + "confirmNewPassword": "Neues Passwort wiederholen", + "passwordRequirements": "Das Passwort muss mindestens 8 Zeichen, einen Großbuchstaben, eine Zahl und ein Sonderzeichen enthalten.", + "saving": "Speichere..." }, - "emailSent": "", - "failedToSendEmail": "", - "settingsTestEmailSuccess": "", - "settingsTestEmailFailed": "", - "settingsTestEmailFailedWithReason": "", - "settingsTestEmailUnknownError": "", + "emailSent": "Email erfolgreich gesendet", + "failedToSendEmail": "Email konnte nicht gesendet werden", + "settingsTestEmailSuccess": "Test-Email erfolgreich gesendet", + "settingsTestEmailFailed": "Test-Email konnte nicht gesendet werden", + "settingsTestEmailFailedWithReason": "Email konnte nicht gesendet werden: {{reason}}", + "settingsTestEmailUnknownError": "Unbekannter Fehler", "statusMsg": { - "paused": "", + "paused": "Monitoring pausiert", "up": "", "down": "", "pending": "" @@ -438,183 +440,188 @@ "port": "" }, "common": { - "appName": "", - "monitoringAgentName": "", + "appName": "Checkmate", + "monitoringAgentName": "Capture", "buttons": { "toggleTheme": "" }, "toasts": { - "networkError": "", - "checkConnection": "", - "unknownError": "" + "networkError": "Netzwerkfehler", + "checkConnection": "Bitte Verbindung prüfen", + "unknownError": "Unbekannter Fehler" } }, "auth": { "common": { "navigation": { - "continue": "", - "back": "" + "continue": "Weiter", + "back": "Zurück" }, "inputs": { "email": { - "label": "", - "placeholder": "", + "label": "Email", + "placeholder": "jordan.ellis@domain.com", "errors": { - "empty": "", + "empty": "Email adresse eingeben um fortzufahren", "invalid": "" } }, "password": { - "label": "", + "label": "Passwort", "rules": { "length": { - "beginning": "", - "highlighted": "" + "beginning": "Muß mindestens", + "highlighted": "8 Zeichen lang" }, "special": { - "beginning": "", - "highlighted": "" + "beginning": "Muß mindestens", + "highlighted": "ein Sonderzeichen enthalten" }, "number": { - "beginning": "", - "highlighted": "" + "beginning": "Muß mindestens", + "highlighted": "eine Zahl enthalten" }, "uppercase": { - "beginning": "", - "highlighted": "" + "beginning": "Muß mindestens", + "highlighted": "einen Großbuchstaben enthalten" }, "lowercase": { - "beginning": "", - "highlighted": "" + "beginning": "Muß mindestens", + "highlighted": "einen Kleinbuchstaben enthalten" }, "match": { "beginning": "", - "highlighted": "" + "highlighted": "muß übereinstimmen" } }, "errors": { - "empty": "", - "length": "", - "uppercase": "", - "lowercase": "", - "number": "", - "special": "", - "incorrect": "" + "empty": "Bitte Passwort eingeben", + "length": "Passwort muss mindestens 8 Zeichen lang sein", + "uppercase": "Passwort muss mindestens einen Großbuchstaben enthalten", + "lowercase": "Passwort muss mindestens einen Kleinbuchstaben enthalten", + "number": "Passwort muss mindestens eine Zahl enthalten", + "special": "Passwort muss mindestens ein Sonderzeichen enthalten", + "incorrect": "Das Passwort stimmt nicht mit dem gespeicherten überein" } }, "passwordConfirm": { - "label": "", - "placeholder": "", + "label": "Passwort bestätigen", + "placeholder": "Passwort nochmal eingeben", "errors": { - "empty": "", - "different": "" + "empty": "Passwort nochmal eingeben", + "different": "Die Passwörter stimmen nicht überein" } }, "firstName": { - "label": "", - "placeholder": "", + "label": "Name", + "placeholder": "Jordan", "errors": { - "empty": "", - "length": "", - "pattern": "" + "empty": "Bitte Name eingeben", + "length": "Name darf nicht mehr als 50 Zeichen lang sein", + "pattern": "Name darf nur aus Buchstaben, Leerstellen, Apostroph und Bindestrichen bestehen" } }, "lastName": { - "label": "", - "placeholder": "", + "label": "Zuname", + "placeholder": "Ellis", "errors": { - "empty": "", - "length": "", - "pattern": "" + "empty": "Bitte Zuname eingeben", + "length": "Zuname darf nicht länger als 50 Zeichen sein", + "pattern": "Zuname darf nur aus Buchstaben, Leerstellen, Apostroph und Bindestrichen bestehen" } } }, "errors": { - "validation": "" + "validation": "Fehler bei der prüfung der Daten" }, "fields": { "password": { "errors": { - "incorrect": "" + "incorrect": "Das Passwort stimmt nicht mit dem gespeicherten überein" + } + }, + "role": { + "errors": { + "min": "" } } } }, "login": { - "heading": "", + "heading": "Anmelden", "subheadings": { - "stepOne": "", - "stepTwo": "" + "stepOne": "Email eingeben", + "stepTwo": "Passwort eingeben" }, "links": { - "forgotPassword": "", - "register": "", - "forgotPasswordLink": "", - "registerLink": "" + "forgotPassword": "Passwort vergessen?", + "register": "Noch keinen Account?", + "forgotPasswordLink": "Passwort zurücksetzen", + "registerLink": "Hier resistrieren" }, "toasts": { - "success": "", - "incorrectPassword": "" + "success": "Willkommen zurück. Erfolgreich angemeldet.", + "incorrectPassword": "Falsches Passwort" }, "errors": { "password": { - "incorrect": "" + "incorrect": "Das Passwort stimmt nicht mit dem gespeicherten überein" } } }, "registration": { "heading": { - "superAdmin": "", - "user": "" + "superAdmin": "Super Admin anlegen", + "user": "Anmelden" }, "subheadings": { "stepOne": "", - "stepTwo": "", - "stepThree": "" + "stepTwo": "Email eingeben", + "stepThree": "Passwort eingeben" }, "description": { - "superAdmin": "", + "superAdmin": "Super admin account anlegen um zu beginnen", "user": "" }, "gettingStartedButton": { - "superAdmin": "", - "user": "" + "superAdmin": "Super admin account anlegen", + "user": "Normalen Benutzer anlegen" }, "termsAndPolicies": "", "links": { - "login": "" + "login": "Du hast bereits einen Benutzer? Anmelden" }, "toasts": { - "success": "" + "success": "Willkommen! Benutzer erfolgreich angelegt" } }, "forgotPassword": { - "heading": "", + "heading": "Passwort vergessen?", "subheadings": { - "stepOne": "", - "stepTwo": "", - "stepThree": "", - "stepFour": "" + "stepOne": "Kein Problem. Wir schicken Dir Infos zum zurücksetzen.", + "stepTwo": "Link zum Password zurücksetzen an geschickt. ", + "stepThree": "Das neue Passwort darf nicht gleich wie frühere Passwörter sein.", + "stepFour": "Das Passwort wurde zurückgesetzt. Unten klicken zum Anmelden." }, "buttons": { - "openEmail": "", - "resetPassword": "" + "openEmail": "Email app öffnen", + "resetPassword": "Passwort zurücksetzen" }, "imageAlts": { - "passwordKey": "", - "email": "", - "lock": "", - "passwordConfirm": "" + "passwordKey": "Password key icon", + "email": "Email icon", + "lock": "Lock icon", + "passwordConfirm": "Password confirm icon" }, "links": { - "login": "", - "resend": "" + "login": "Zurück zum Anmelden", + "resend": "Keine Email bekommen? Klicken um nochmal zu senden" }, "toasts": { - "sent": "", - "emailNotFound": "", - "redirect": "", - "success": "", + "sent": "Anleitung an gesendet.", + "emailNotFound": "Email nicht gefunden.", + "redirect": "Umleitung in ...", + "success": "Passwort erfolgreich zurückgesetzt.", "error": "" } } @@ -622,83 +629,86 @@ "errorPages": { "serverUnreachable": { "toasts": { - "reconnected": "", - "stillUnreachable": "" + "reconnected": "Verbindung zum Server wiederhergestellt.", + "stillUnreachable": "Server immer noch nicht erreichbar. Bitte später erneut versuchen." }, - "alertBox": "", + "alertBox": "Server Verbindungsfehler", "description": "", "retryButton": { - "default": "", - "processing": "" + "default": "Verbindung erneut versuchen", + "processing": "Verbinde..." } } }, "createNotifications": { "title": "", "nameSettings": { - "title": "", + "title": "Name", "description": "", - "nameLabel": "", - "namePlaceholder": "" + "nameLabel": "Name", + "namePlaceholder": "z.B. Slack Benachrichtigung" }, "typeSettings": { - "title": "", + "title": "Typ", "description": "", - "typeLabel": "" + "typeLabel": "Typ" }, "emailSettings": { - "title": "", - "description": "", - "emailLabel": "", - "emailPlaceholder": "" + "title": "Email", + "description": "Empfänger Email adresse", + "emailLabel": "Email addresse", + "emailPlaceholder": "z.B. john@example.com" }, "slackSettings": { - "title": "", - "description": "", - "webhookLabel": "", + "title": "Slack", + "description": "Slack webhook hier konfigurieren", + "webhookLabel": "Slack webhook URL", "webhookPlaceholder": "" }, "pagerdutySettings": { - "title": "", - "description": "", - "integrationKeyLabel": "", - "integrationKeyPlaceholder": "" + "title": "PagerDuty", + "description": "PagerDuty hier konfigurieren", + "integrationKeyLabel": "Integration key", + "integrationKeyPlaceholder": "1234567890" }, "discordSettings": { - "title": "", - "description": "", - "webhookLabel": "", - "webhookPlaceholder": "" + "title": "Discord", + "description": "Discord webhook hier konfigurieren", + "webhookLabel": "Discord Webhook URL", + "webhookPlaceholder": "https://your-server.com/webhook" }, "webhookSettings": { - "title": "", - "description": "", - "webhookLabel": "", - "webhookPlaceholder": "" - } + "title": "Webhook", + "description": "Webhook hier konfigurieren", + "webhookLabel": "Webhook URL", + "webhookPlaceholder": "https://your-server.com/webhook" + }, + "testNotification": "", + "dialogDeleteTitle": "", + "dialogDeleteConfirm": "" }, "notificationConfig": { - "title": "", + "title": "Benachrichtigungen", "description": "" }, "monitorStatus": { "checkingEvery": "", "withCaptureAgent": "", - "up": "", - "down": "", - "paused": "" + "up": "hoch", + "down": "runter", + "paused": "pausiert" }, "advancedMatching": "", - "sendTestNotifications": "", - "testNotificationsDisabled": "", - "selectAll": "", + "sendTestNotifications": "Test Benachrichtigung senden", + "selectAll": "Alle auswählen", "showAdminLoginLink": "", "logsPage": { - "title": "", + "title": "Logs", "description": "", "tabs": { - "queue": "", - "logs": "" + "queue": "Warteschlange", + "logs": "Server logs", + "diagnostics": "" }, "toast": { "fetchLogsSuccess": "" @@ -706,94 +716,98 @@ "logLevelSelect": { "title": "", "values": { - "all": "", - "info": "", - "warn": "", - "error": "", - "debug": "" + "all": "Alle", + "info": "Info", + "warn": "Warnung", + "error": "Fehler", + "debug": "Debug" } } }, "queuePage": { - "title": "", - "refreshButton": "", - "flushButton": "", + "title": "Warteschlange", + "refreshButton": "Auffrischen", + "flushButton": "Warteschlange leeren", "jobTable": { - "title": "", - "idHeader": "", - "urlHeader": "", - "typeHeader": "", - "activeHeader": "", + "title": "Aufgaben in der Warteschlange", + "idHeader": "Monitor ID", + "urlHeader": "URL", + "typeHeader": "Typ", + "activeHeader": "Aktiv", "lockedAtHeader": "", - "runCountHeader": "", - "failCountHeader": "", - "lastRunHeader": "", - "lastFinishedAtHeader": "", - "lastRunTookHeader": "" + "runCountHeader": "Anzahl Läufe", + "failCountHeader": "Anzahl Fehler", + "lastRunHeader": "Letzter Lauf um", + "lastFinishedAtHeader": "Zuletzt abgeschlossen um", + "lastRunTookHeader": "Letzter Lauf dauerte" }, "metricsTable": { "title": "", "metricHeader": "", - "valueHeader": "" + "valueHeader": "Wert" }, "failedJobTable": { - "title": "", - "monitorIdHeader": "", - "monitorUrlHeader": "", - "failCountHeader": "", - "failedAtHeader": "", - "failReasonHeader": "" + "title": "Anzahl Fehler", + "monitorIdHeader": "Monitor ID", + "monitorUrlHeader": "Monitor URL", + "failCountHeader": "Anzahl Fehler", + "failedAtHeader": "Zuletzt fehlgeschlagen um", + "failReasonHeader": "Fehlergrund" } }, "export": { - "title": "", - "success": "", - "failed": "" + "title": "Monitore exportieren", + "success": "Monitore erfolgreich exportiert", + "failed": "Fehler beim exportieren der Monitore" }, "monitorActions": { - "title": "", - "import": "", - "export": "" + "title": "Export/Import", + "import": "Monitore importieren", + "export": "Monitore exportieren", + "deleteSuccess": "", + "deleteFailed": "", + "details": "" }, "settingsPage": { "aboutSettings": { - "labelDevelopedBy": "", - "labelVersion": "", - "title": "" + "labelDevelopedBy": "Entwickelt von Bluewave Labs", + "labelVersion": "Version", + "title": "Über" }, "demoMonitorsSettings": { - "buttonAddMonitors": "", - "description": "", - "title": "" + "buttonAddMonitors": "Demo Monitor hinzufügen", + "description": "Beispiel Monitor zur Demonstration hinzufügen.", + "title": "Demo Monitore" }, "emailSettings": { - "buttonSendTestEmail": "", + "buttonSendTestEmail": "Test-Email senden", "description": "", "descriptionTransport": "", "labelAddress": "", "labelConnectionHost": "", "labelHost": "", - "labelIgnoreTLS": "", - "labelPassword": "", - "labelPasswordSet": "", + "labelIgnoreTLS": "STARTTLS abschalten: TLS nicht nutzen, selbst wenn der Server es unterstützt", + "labelPassword": "Email password - Password für Anmeldung", + "labelPasswordSet": "Passwort erstellt. Reset drücken um es zu ändern.", "labelPool": "", - "labelPort": "", - "labelRejectUnauthorized": "", + "labelPort": "Email port - Port zum verbinden", + "labelRejectUnauthorized": "Ungültige Zertifikate ablehnen. Verbindungen mit self-signed oder nicht vertrauenswürdigen Zertifikaten ablehnen.", "labelRequireTLS": "", - "labelSecure": "", + "labelSecure": "SSL verwenden (empfohlen): Verbindung mit SSL/TLS verschlüsseln", "labelTLSServername": "", "labelUser": "", "linkTransport": "", - "placeholderUser": "", - "title": "" + "placeholderUser": "Leer lassen, wenn nicht benötigt", + "title": "Email", + "toastEmailRequiredFieldsError": "" }, "pageSpeedSettings": { "description": "", - "labelApiKeySet": "", - "labelApiKey": "", - "title": "" + "labelApiKeySet": "API key erstellt. Reset drücken um ihn zu ändern.", + "labelApiKey": "PageSpeed API key", + "title": "Google PageSpeed API key" }, - "saveButtonLabel": "", + "saveButtonLabel": "Speichern", "statsSettings": { "clearAllStatsButton": "", "clearAllStatsDescription": "", @@ -802,35 +816,154 @@ "clearAllStatsDialogTitle": "", "description": "", "labelTTL": "", - "labelTTLOptional": "", + "labelTTLOptional": "0 für unendlich", "title": "" }, "systemResetSettings": { - "buttonRemoveAllMonitors": "", - "description": "", - "dialogConfirm": "", - "dialogDescription": "", - "dialogTitle": "", - "title": "" + "buttonRemoveAllMonitors": "Alle Monitore löschen", + "description": "Alle Monitore löschen", + "dialogConfirm": "Ja, alle Monitore löschen", + "dialogDescription": "Gelöschte Monitore können nicht wiederhergestellt werden.", + "dialogTitle": "Wirklich alle Monitore löschen?", + "title": "System zurücksetzen" }, "timezoneSettings": { "description": "", - "label": "", - "title": "" + "label": "Zeitzone", + "title": "Zeitzone" }, - "title": "", + "title": "Einstellungen", "uiSettings": { "description": "", - "labelLanguage": "", + "labelLanguage": "Sprache", "labelTheme": "", "title": "" }, "urlSettings": { "description": "", "label": "", - "selectDisabled": "", - "selectEnabled": "", + "selectDisabled": "Inaktiv", + "selectEnabled": "Aktiv", "title": "" } - } + }, + "statusPageCreate": { + "buttonSave": "Speichern" + }, + "incidentsOptionsHeaderFilterResolved": "", + "settingsSave": "Speichern", + "statusPageCreateAppearanceTitle": "", + "confirmPassword": "Passwort bestätigen", + "monitorHooks": { + "failureAddDemoMonitors": "Demo Monitor konnte nicht hinzugefügt werden", + "successAddDemoMonitors": "Demo Monitor erfolgreich hinzugefügt" + }, + "settingsAppearance": "", + "settingsDisplayTimezone": "", + "settingsGeneralSettings": "", + "incidentsOptionsHeaderTotalIncidents": "", + "statusPage": { + "deleteSuccess": "", + "deleteFailed": "", + "createSuccess": "", + "updateSuccess": "", + "generalSettings": "", + "contents": "", + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "testNotificationsDisabled": "", + "incidentsTableResolvedAt": "", + "incidentsTableActionResolve": "", + "checkHooks": { + "failureResolveOne": "", + "failureResolveAll": "", + "failureResolveMonitor": "" + }, + "checkFormError": "", + "diagnosticsPage": { + "diagnosticDescription": "", + "statsDescription": "", + "gauges": { + "heapAllocationTitle": "", + "heapAllocationSubtitle": "", + "heapUsageTitle": "", + "heapUsageSubtitle": "", + "heapUtilizationTitle": "", + "heapUtilizationSubtitle": "", + "instantCpuUsageTitle": "", + "instantCpuUsageSubtitle": "" + }, + "stats": { + "eventLoopDelayTitle": "", + "uptimeTitle": "", + "usedHeapSizeTitle": "", + "totalHeapSizeTitle": "", + "osMemoryLimitTitle": "" + } + }, + "pageSpeedLighthouseAPI": "", + "time": { + "threeMinutes": "", + "fiveMinutes": "", + "tenMinutes": "", + "twentyMinutes": "", + "oneHour": "", + "oneDay": "", + "oneWeek": "" + }, + "general": { + "noOptionsFound": "" + }, + "infrastructureMonitor": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "maintenanceWindow": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "pageSpeed": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "uptimeMonitor": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "editUserPage": { + "form": { + "email": "", + "firstName": "", + "lastName": "", + "role": "", + "save": "" + }, + "table": { + "actionHeader": "", + "roleHeader": "" + }, + "title": "", + "toast": { + "successUserUpdate": "", + "validationErrors": "" + } + }, + "incidentsPageActionResolveMonitor": "", + "incidentsPageActionResolveAll": "" } diff --git a/client/src/locales/en.json b/client/src/locales/en.json index b88b1d8a1..230637474 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -154,7 +154,7 @@ "incorrect": "The password you provided does not match our records" } }, - "heading": "Log In", + "heading": "Log in to continue", "links": { "forgotPassword": "Forgot password?", "forgotPasswordLink": "Reset password", @@ -168,7 +168,8 @@ "toasts": { "incorrectPassword": "Incorrect password", "success": "Welcome back! You're successfully logged in." - } + }, + "welcome": "Welcome back to Checkmate!" }, "registration": { "description": { @@ -378,6 +379,7 @@ "dockerContainerMonitoringDescription": "Check whether your Docker container is running or not.", "duration": "Duration", "edit": "Edit", + "editing": "Editing...", "editMaintenance": "Edit maintenance", "editUserPage": { "form": { @@ -397,7 +399,6 @@ "validationErrors": "Validation errors" } }, - "editing": "Editing...", "emailSent": "Email sent successfully", "errorInvalidFieldId": "Invalid field ID provided", "errorInvalidTypeId": "Invalid notification type provided", @@ -453,8 +454,8 @@ "incidentsOptionsHeaderShow": "Show:", "incidentsOptionsHeaderTotalIncidents": "Total Incidents", "incidentsOptionsPlaceholderAllServers": "All servers", - "incidentsPageActionResolveMonitor": "Resolve monitor incidents", "incidentsPageActionResolveAll": "Resolve all incidents", + "incidentsPageActionResolveMonitor": "Resolve monitor incidents", "incidentsPageTitle": "Incidents", "incidentsTableActionResolve": "Resolve", "incidentsTableDateTime": "Date & Time", @@ -476,13 +477,13 @@ "infrastructureEditYour": "Edit your", "infrastructureMonitor": { "fallback": { + "actionButton": "Let's create your first infrastructure monitor!", "checks": [ "Track the performance of your servers", "Identify bottlenecks and optimize usage", "Ensure reliability with real-time monitoring" ], - "title": "An infrastructure monitor is used to:", - "actionButton": "Let's create your first infrastructure monitor!" + "title": "An infrastructure monitor is used to:" } }, "infrastructureMonitorCreated": "Infrastructure monitor created successfully!", @@ -530,19 +531,28 @@ "maintenanceTableActionMenuDialogTitle": "Do you really want to remove this maintenance window?", "maintenanceWindow": { "fallback": { + "actionButton": "Let's create your first maintenance window!", "checks": [ "Mark your maintenance periods", "Eliminate any misunderstandings", "Stop sending alerts in maintenance windows" ], - "title": "A maintenance window is used to:", - "actionButton": "Let's create your first maintenance window!" + "title": "A maintenance window is used to:" } }, "maintenanceWindowDescription": "Your pings won't be sent during this time frame", "maintenanceWindowName": "Maintenance Window Name", "maskedPageSpeedKeyPlaceholder": "*************************************", "matchMethod": "Match Method", + "matchMethodOptions": { + "equal": "Equal", + "equalPlaceholder": "success", + "include": "Include", + "includePlaceholder": "ok", + "regex": "Regex", + "regexPlaceholder": "^(success|ok)$", + "text": "Match Method" + }, "mb": "MB", "mem": "Mem", "memory": "Memory", @@ -582,6 +592,7 @@ "failureAddDemoMonitors": "Failed to add demo monitors", "successAddDemoMonitors": "Successfully added demo monitors" }, + "monitors": "monitors", "monitorState": { "active": "Active", "paused": "Paused", @@ -596,8 +607,29 @@ }, "monitorStatusDown": "Monitor {name} ({url}) is DOWN and not responding", "monitorStatusUp": "Monitor {name} ({url}) is now UP and responding", - "monitors": "monitors", "monitorsToApply": "Monitors to apply maintenance window to", + "monitorType": { + "docker": { + "label": "Container ID", + "namePlaceholder": "My Container", + "placeholder": "abcd1234" + }, + "http": { + "label": "URL to monitor", + "namePlaceholder": "Google", + "placeholder": "google.com" + }, + "ping": { + "label": "IP address to monitor", + "namePlaceholder": "Google", + "placeholder": "1.1.1.1" + }, + "port": { + "label": "URL to monitor", + "namePlaceholder": "Localhost:5173", + "placeholder": "localhost" + } + }, "ms": "ms", "navControls": "Controls", "nextWindow": "Next window", @@ -631,13 +663,13 @@ }, "enableNotifications": "Enable {{platform}} notifications", "fallback": { + "actionButton": "Let's create your first notification channel!", "checks": [ "Alert teams about downtime or performance issues", "Let engineers know when incidents happen", "Keep administrators informed of system changes" ], - "title": "A notification channel is used to:", - "actionButton": "Let's create your first notification channel!" + "title": "A notification channel is used to:" }, "fetch": { "failed": "Failed to fetch notifications", @@ -684,13 +716,13 @@ "os": "OS", "pageSpeed": { "fallback": { + "actionButton": "Let's create your first PageSpeed monitor!", "checks": [ "Report on the user experience of a page", "Help analyze webpage speed", "Give suggestions on how the page can be improved" ], - "title": "A PageSpeed monitor is used to:", - "actionButton": "Let's create your first PageSpeed monitor!" + "title": "A PageSpeed monitor is used to:" } }, "pageSpeedAddApiKey": "to add your API key.", @@ -723,8 +755,8 @@ "queuePage": { "failedJobTable": { "failCountHeader": "Fail count", - "failReasonHeader": "Fail reason", "failedAtHeader": "Last failed at", + "failReasonHeader": "Fail reason", "monitorIdHeader": "Monitor ID", "monitorUrlHeader": "Monitor URL", "title": "Failed jobs" @@ -842,6 +874,10 @@ "title": "Display timezone" }, "title": "Settings", + "globalThresholds": { + "title": "Global Thresholds", + "description": "Configure global CPU, Memory, Disk, and Temperature thresholds. If a value is provided, it will automatically be enabled for monitoring." + }, "uiSettings": { "description": "Switch between light and dark mode, or change user interface language.", "labelLanguage": "Language", @@ -865,8 +901,8 @@ "settingsTestEmailUnknownError": "Unknown error", "showAdminLoginLink": "Show \"Administrator? Login Here\" link on the status page", "showCharts": "Show charts", - "showUptimePercentage": "Show uptime percentage", "shown": "Shown", + "showUptimePercentage": "Show uptime percentage", "starPromptDescription": "See the latest releases and help grow the community on GitHub", "starPromptTitle": "Star Checkmate", "startTime": "Start time", @@ -886,6 +922,15 @@ "createSuccess": "Status page created successfully", "deleteFailed": "Failed to delete status page", "deleteSuccess": "Status page deleted successfully", + "fallback": { + "actionButton": "Let's create your first status page!", + "checks": [ + "Monitor and display the health of your services in real time", + "Track multiple services and share their status", + "Keep users informed about outages and performance" + ], + "title": "A status page is used to:" + }, "generalSettings": "General settings", "updateSuccess": "Status page updated successfully" }, @@ -908,23 +953,6 @@ "statusPageStatusNoPage": "There's no status page here.", "statusPageStatusNotPublic": "This status page is not public.", "statusPageStatusServiceStatus": "Service status", - "statusPage": { - "deleteSuccess": "Status page deleted successfully", - "deleteFailed": "Failed to delete status page", - "createSuccess": "Status page created successfully", - "updateSuccess": "Status page updated successfully", - "generalSettings": "General settings", - "contents": "Contents", - "fallback": { - "checks": [ - "Monitor and display the health of your services in real time", - "Track multiple services and share their status", - "Keep users informed about outages and performance" - ], - "title": "A status page is used to:", - "actionButton": "Let's create your first status page!" - } - }, "submit": "Submit", "teamPanel": { "cancel": "Cancel", @@ -952,22 +980,30 @@ "testLocale": "testLocale", "testNotificationsDisabled": "There are no notifications setup for this monitor. You need to add one by clicking 'Configure' button", "time": { + "fifteenSeconds": "15 seconds", "fiveMinutes": "5 minutes", + "fourMinutes": "4 minutes", "oneDay": "1 day", "oneHour": "1 hour", + "oneMinute": "1 minute", "oneWeek": "1 week", "tenMinutes": "10 minutes", + "thirtySeconds": "30 seconds", "threeMinutes": "3 minutes", - "twentyMinutes": "20 minutes" + "twentyMinutes": "20 minutes", + "twoMinutes": "2 minutes" }, - "timeZoneInfo": "All dates and times are in GMT+0 time zone.", "timezone": "Timezone", + "timeZoneInfo": "All dates and times are in GMT+0 time zone.", "title": "Title", "tlsErrorIgnored": "TLS/SSL errors ignored", "total": "Total", "type": "Type", "update": "Update", "uptime": "Uptime", + "uptimeAdvancedMatching": { + "jsonPath": "JSON Path" + }, "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.", @@ -979,14 +1015,14 @@ }, "uptimeMonitor": { "fallback": { + "actionButton": "Let's create your first uptime monitor!", "checks": [ "Check if websites or servers are online & responsive", "Alert teams about downtime or performance issues", "Monitor HTTP endpoints, pings, containers & ports", "Track historical uptime and reliability trends" ], - "title": "An uptime monitor is used to:", - "actionButton": "Let's create your first uptime monitor!" + "title": "An uptime monitor is used to:" } }, "url": "URL", diff --git a/client/src/locales/es.json b/client/src/locales/es.json new file mode 100644 index 000000000..6ecebadf5 --- /dev/null +++ b/client/src/locales/es.json @@ -0,0 +1,969 @@ +{ + "submit": "Enviar", + "title": "Titulo", + "distributedStatusHeaderText": "Cobertura en tiempo real, para dispositivos reales", + "distributedStatusSubHeaderText": "Potenciado por millones de dispositivos alrededor del mundo, vea el rendimiento del sistema por región global, país o ciudad", + "settingsDisabled": "Deshabilitado", + "settingsSuccessSaved": "Ajustes guardados correctamente", + "settingsFailedToSave": "Fallo al guardar los ajustes", + "settingsStatsCleared": "Estadísticas eliminadas correctamente", + "settingsFailedToClearStats": "Fallo al eliminar las estadísticas", + "settingsMonitorsDeleted": "Todos los monitores eliminados correctamente", + "settingsFailedToDeleteMonitors": "Fallo al eliminar todos los monitores", + "starPromptTitle": "", + "starPromptDescription": "Vea las últimas versiones y ayude a incrementar la comunidad en Github", + "https": "HTTPS", + "http": "HTTP", + "monitor": "monitor", + "aboutus": "Acerca de", + "now": "Ahora", + "delete": "Eliminar", + "configure": "Configurar", + "responseTime": "Tiempo de Respuesta", + "ms": "ms", + "bar": "Barra", + "area": "Área", + "country": "PAIS", + "city": "CIUDAD", + "response": "RESPUESTA", + "monitorStatusUp": "El monitor {name} ({url}) está ahora activo y respondiendo", + "monitorStatusDown": "El monitor {name} ({url}) está inactivo y no está respondiendo", + "webhookSendSuccess": "Notificación webhook enviada correctamente", + "webhookSendError": "Error al enviar notificación webhook a {platform}", + "webhookUnsupportedPlatform": "Plataforma no soportada: {platform}", + "distributedRightCategoryTitle": "Monitor", + "distributedStatusServerMonitors": "Monitores de Servidores", + "distributedStatusServerMonitorsDescription": "Monitoree el estado de los servidores relacionados", + "distributedUptimeCreateSelectURL": "Aquí puede seleccionar la URL del anfitrión, junto con el tipo de monitor.", + "distributedUptimeCreateChecks": "Verificaciones a realizar", + "distributedUptimeCreateChecksDescription": "Usted siempre puede agregar o eliminar verificaciones luego de añadir su sitio.", + "distributedUptimeCreateIncidentNotification": "Notificaciones de incidentes", + "distributedUptimeCreateIncidentDescription": "Cuando ocurra un incidente, notificar a usuarios.", + "distributedUptimeCreateAdvancedSettings": "Ajustes avanzados", + "distributedUptimeDetailsNoMonitorHistory": "No hay historial de verificaciones para este monitor aún.", + "distributedUptimeDetailsStatusHeaderUptime": "Tiempo de funcionamiento:", + "distributedUptimeDetailsStatusHeaderLastUpdate": "Actualizado por última vez", + "notifications": { + "enableNotifications": "Habilitar notificaciones para {{platform}}", + "testNotification": "Notificación de prueba", + "addOrEditNotifications": "Agregar o eliminar notificaciones", + "slack": { + "label": "Slack", + "description": "Para habilitar notificaciones de Slack, cree una aplicación de Slack y habilite los webhooks entrantes. Luego de eso, simplemente introduzca la URL del webhook aquí.", + "webhookLabel": "URL del webhook", + "webhookPlaceholder": "", + "webhookRequired": "" + }, + "discord": { + "label": "Discord", + "description": "Para enviar datos a un canal de Discord desde Checkmate a través de notificaciones de Discord utilizando webhooks, puede utilizar la característica de webhooks entrantes de Discord.", + "webhookLabel": "URL de Webhooks de Discord", + "webhookPlaceholder": "", + "webhookRequired": "" + }, + "telegram": { + "label": "Telegram", + "description": "Para habilitar las notificaciones de Telegram, cree un bot de Telegram utilizando BotFather, un bot oficial para crear y administrar bots de Telegram. Entonces, obtenga una clave API y un ID de chat y escríbalos aquí.", + "tokenLabel": "La clave de su bot", + "tokenPlaceholder": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", + "chatIdLabel": "El ID de su Chat", + "chatIdPlaceholder": "-1001234567890", + "fieldsRequired": "" + }, + "webhook": { + "label": "Webhooks", + "description": "Puede establecer un webhook personalizado para recibir notificaciones cuando ocurra un incidente.", + "urlLabel": "URL del webhook", + "urlPlaceholder": "https://your-server.com/webhook", + "urlRequired": "" + }, + "testNotificationDevelop": "Notificación de prueba 2", + "integrationButton": "", + "testSuccess": "", + "testFailed": "", + "unsupportedType": "", + "networkError": "", + "fallback": { + "title": "", + "checks": [""], + "actionButton": "" + }, + "createButton": "", + "createTitle": "", + "create": { + "success": "", + "failed": "" + }, + "fetch": { + "success": "", + "failed": "" + }, + "delete": { + "success": "", + "failed": "" + }, + "edit": { + "success": "", + "failed": "" + }, + "test": { + "success": "", + "failed": "" + } + }, + "testLocale": "testLocale", + "add": "Agregar", + "monitors": "monitores", + "distributedUptimeStatusCreateStatusPage": "página de estado", + "distributedUptimeStatusCreateStatusPageAccess": "", + "distributedUptimeStatusCreateStatusPageReady": "", + "distributedUptimeStatusBasicInfoHeader": "", + "distributedUptimeStatusBasicInfoDescription": "", + "distributedUptimeStatusLogoHeader": "", + "distributedUptimeStatusLogoDescription": "", + "distributedUptimeStatusLogoUploadButton": "", + "distributedUptimeStatusStandardMonitorsHeader": "", + "distributedUptimeStatusStandardMonitorsDescription": "", + "distributedUptimeStatusCreateYour": "", + "distributedUptimeStatusEditYour": "", + "distributedUptimeStatusPublishedLabel": "", + "distributedUptimeStatusCompanyNameLabel": "", + "distributedUptimeStatusPageAddressLabel": "", + "distributedUptimeStatus30Days": "", + "distributedUptimeStatus60Days": "", + "distributedUptimeStatus90Days": "", + "distributedUptimeStatusPageNotSetUp": "", + "distributedUptimeStatusContactAdmin": "", + "distributedUptimeStatusPageNotPublic": "", + "distributedUptimeStatusPageDeleteDialog": "", + "distributedUptimeStatusPageDeleteConfirm": "", + "distributedUptimeStatusPageDeleteDescription": "", + "distributedUptimeStatusDevices": "", + "distributedUptimeStatusUpt": "", + "distributedUptimeStatusUptBurned": "", + "distributedUptimeStatusUptLogo": "", + "incidentsTableNoIncidents": "", + "incidentsTablePaginationLabel": "", + "incidentsTableMonitorName": "", + "incidentsTableStatus": "", + "incidentsTableDateTime": "", + "incidentsTableStatusCode": "", + "incidentsTableMessage": "", + "incidentsOptionsHeader": "", + "incidentsOptionsHeaderFilterBy": "", + "incidentsOptionsHeaderFilterAll": "", + "incidentsOptionsHeaderFilterDown": "", + "incidentsOptionsHeaderFilterCannotResolve": "", + "incidentsOptionsHeaderShow": "", + "incidentsOptionsHeaderLastHour": "", + "incidentsOptionsHeaderLastDay": "", + "incidentsOptionsHeaderLastWeek": "", + "incidentsOptionsPlaceholderAllServers": "", + "infrastructureCreateYour": "", + "infrastructureCreateGeneralSettingsDescription": "", + "infrastructureServerRequirement": "", + "infrastructureCustomizeAlerts": "", + "infrastructureAlertNotificationDescription": "", + "infrastructureCreateMonitor": "", + "infrastructureProtocol": "", + "infrastructureServerUrlLabel": "", + "infrastructureDisplayNameLabel": "", + "infrastructureAuthorizationSecretLabel": "", + "gb": "", + "mb": "", + "mem": "", + "memoryUsage": "", + "cpu": "", + "cpuUsage": "", + "cpuTemperature": "", + "diskUsage": "", + "used": "", + "total": "", + "cores": "", + "frequency": "", + "status": "", + "cpuPhysical": "", + "cpuLogical": "", + "cpuFrequency": "", + "avgCpuTemperature": "", + "memory": "", + "disk": "", + "uptime": "", + "os": "", + "host": "", + "actions": "", + "integrations": "", + "integrationsPrism": "", + "integrationsSlack": "", + "integrationsSlackInfo": "", + "integrationsDiscord": "", + "integrationsDiscordInfo": "", + "integrationsZapier": "", + "integrationsZapierInfo": "", + "commonSave": "", + "createYour": "", + "createMonitor": "", + "pause": "", + "resume": "", + "editing": "", + "url": "", + "access": "", + "timezone": "", + "features": "", + "administrator": "", + "loginHere": "", + "displayName": "", + "urlMonitor": "", + "portToMonitor": "", + "websiteMonitoring": "", + "websiteMonitoringDescription": "", + "pingMonitoring": "", + "pingMonitoringDescription": "", + "dockerContainerMonitoring": "", + "dockerContainerMonitoringDescription": "", + "portMonitoring": "", + "portMonitoringDescription": "", + "createMaintenanceWindow": "", + "createMaintenance": "", + "editMaintenance": "", + "maintenanceWindowName": "", + "friendlyNameInput": "", + "friendlyNamePlaceholder": "", + "maintenanceRepeat": "", + "maintenance": "", + "duration": "", + "addMonitors": "", + "window": "", + "cancel": "", + "message": "", + "low": "", + "high": "", + "statusCode": "", + "date&Time": "", + "type": "", + "statusPageName": "", + "publicURL": "", + "repeat": "", + "edit": "", + "createA": "", + "remove": "", + "maintenanceWindowDescription": "", + "startTime": "", + "timeZoneInfo": "", + "monitorsToApply": "", + "nextWindow": "", + "notFoundButton": "", + "pageSpeedConfigureSettingsDescription": "", + "monitorDisplayName": "", + "whenNewIncident": "", + "notifySMS": "", + "notifyEmails": "", + "seperateEmails": "", + "checkFrequency": "", + "matchMethod": "", + "expectedValue": "", + "deleteDialogTitle": "", + "deleteDialogDescription": "", + "pageSpeedMonitor": "", + "shown": "", + "ago": "", + "companyName": "", + "pageSpeedDetailsPerformanceReport": "", + "pageSpeedDetailsPerformanceReportCalculator": "", + "checkingEvery": "", + "statusPageCreateSettings": "", + "basicInformation": "", + "statusPageCreateBasicInfoDescription": "", + "statusPageCreateSelectTimeZoneDescription": "", + "statusPageCreateAppearanceDescription": "", + "statusPageCreateSettingsCheckboxLabel": "", + "statusPageCreateBasicInfoStatusPageAddress": "", + "statusPageCreateTabsContent": "", + "statusPageCreateTabsContentDescription": "", + "statusPageCreateTabsContentFeaturesDescription": "", + "showCharts": "", + "showUptimePercentage": "", + "removeLogo": "", + "statusPageStatus": "", + "statusPageStatusContactAdmin": "", + "statusPageStatusNotPublic": "", + "statusPageStatusNoPage": "", + "statusPageStatusServiceStatus": "", + "deleteStatusPage": "", + "deleteStatusPageConfirm": "", + "deleteStatusPageDescription": "", + "uptimeCreate": "", + "uptimeCreateJsonPath": "", + "uptimeCreateJsonPathQuery": "", + "maintenanceTableActionMenuDialogTitle": "", + "infrastructureEditYour": "", + "infrastructureEditMonitor": "", + "infrastructureMonitorCreated": "", + "infrastructureMonitorUpdated": "", + "errorInvalidTypeId": "", + "errorInvalidFieldId": "", + "inviteNoTokenFound": "", + "pageSpeedWarning": "", + "pageSpeedLearnMoreLink": "", + "pageSpeedAddApiKey": "", + "update": "", + "invalidFileFormat": "", + "invalidFileSize": "", + "ClickUpload": "", + "DragandDrop": "", + "MaxSize": "", + "SupportedFormats": "", + "FirstName": "", + "LastName": "", + "EmailDescriptionText": "", + "YourPhoto": "", + "PhotoDescriptionText": "", + "save": "", + "DeleteDescriptionText": "", + "DeleteAccountWarning": "", + "DeleteWarningTitle": "", + "bulkImport": { + "title": "", + "selectFileTips": "", + "selectFileDescription": "", + "selectFile": "", + "parsingFailed": "", + "uploadSuccess": "", + "validationFailed": "", + "noFileSelected": "", + "fallbackPage": "", + "invalidFileType": "", + "uploadFailed": "" + }, + "DeleteAccountTitle": "", + "DeleteAccountButton": "", + "publicLink": "", + "maskedPageSpeedKeyPlaceholder": "", + "reset": "", + "ignoreTLSError": "", + "tlsErrorIgnored": "", + "ignoreTLSErrorDescription": "", + "createNew": "", + "greeting": { + "prepend": "", + "append": "", + "overview": "" + }, + "roles": { + "superAdmin": "", + "admin": "", + "teamMember": "", + "demoUser": "" + }, + "teamPanel": { + "teamMembers": "", + "filter": { + "all": "", + "member": "" + }, + "inviteTeamMember": "", + "inviteNewTeamMember": "", + "inviteDescription": "", + "email": "", + "selectRole": "", + "inviteLink": "", + "cancel": "", + "noMembers": "", + "getToken": "", + "emailToken": "", + "table": { + "name": "", + "email": "", + "role": "", + "created": "" + } + }, + "monitorState": { + "paused": "", + "resumed": "", + "active": "" + }, + "menu": { + "uptime": "", + "pagespeed": "", + "infrastructure": "", + "incidents": "", + "statusPages": "", + "maintenance": "", + "integrations": "", + "settings": "", + "support": "", + "discussions": "", + "docs": "", + "changelog": "", + "profile": "", + "password": "", + "team": "", + "logOut": "", + "notifications": "", + "logs": "" + }, + "settingsEmailUser": "", + "state": "", + "statusBreadCrumbsStatusPages": "", + "statusBreadCrumbsDetails": "", + "commonSaving": "", + "navControls": "", + "incidentsPageTitle": "", + "passwordPanel": { + "passwordChangedSuccess": "", + "passwordInputIncorrect": "", + "currentPassword": "", + "enterCurrentPassword": "", + "newPassword": "", + "enterNewPassword": "", + "confirmNewPassword": "", + "passwordRequirements": "", + "saving": "" + }, + "emailSent": "", + "failedToSendEmail": "", + "settingsTestEmailSuccess": "", + "settingsTestEmailFailed": "", + "settingsTestEmailFailedWithReason": "", + "settingsTestEmailUnknownError": "", + "statusMsg": { + "paused": "", + "up": "", + "down": "", + "pending": "" + }, + "uptimeGeneralInstructions": { + "http": "", + "ping": "", + "docker": "", + "port": "" + }, + "common": { + "appName": "", + "monitoringAgentName": "", + "buttons": { + "toggleTheme": "" + }, + "toasts": { + "networkError": "", + "checkConnection": "", + "unknownError": "" + } + }, + "auth": { + "common": { + "navigation": { + "continue": "", + "back": "" + }, + "inputs": { + "email": { + "label": "", + "placeholder": "", + "errors": { + "empty": "", + "invalid": "" + } + }, + "password": { + "label": "", + "rules": { + "length": { + "beginning": "", + "highlighted": "" + }, + "special": { + "beginning": "", + "highlighted": "" + }, + "number": { + "beginning": "", + "highlighted": "" + }, + "uppercase": { + "beginning": "", + "highlighted": "" + }, + "lowercase": { + "beginning": "", + "highlighted": "" + }, + "match": { + "beginning": "", + "highlighted": "" + } + }, + "errors": { + "empty": "", + "length": "", + "uppercase": "", + "lowercase": "", + "number": "", + "special": "", + "incorrect": "" + } + }, + "passwordConfirm": { + "label": "", + "placeholder": "", + "errors": { + "empty": "", + "different": "" + } + }, + "firstName": { + "label": "", + "placeholder": "", + "errors": { + "empty": "", + "length": "", + "pattern": "" + } + }, + "lastName": { + "label": "", + "placeholder": "", + "errors": { + "empty": "", + "length": "", + "pattern": "" + } + } + }, + "errors": { + "validation": "" + }, + "fields": { + "password": { + "errors": { + "incorrect": "" + } + }, + "role": { + "errors": { + "min": "" + } + } + } + }, + "login": { + "heading": "", + "subheadings": { + "stepOne": "", + "stepTwo": "" + }, + "links": { + "forgotPassword": "", + "register": "", + "forgotPasswordLink": "", + "registerLink": "" + }, + "toasts": { + "success": "", + "incorrectPassword": "" + }, + "errors": { + "password": { + "incorrect": "" + } + } + }, + "registration": { + "heading": { + "superAdmin": "", + "user": "" + }, + "subheadings": { + "stepOne": "", + "stepTwo": "", + "stepThree": "" + }, + "description": { + "superAdmin": "", + "user": "" + }, + "gettingStartedButton": { + "superAdmin": "", + "user": "" + }, + "termsAndPolicies": "", + "links": { + "login": "" + }, + "toasts": { + "success": "" + } + }, + "forgotPassword": { + "heading": "", + "subheadings": { + "stepOne": "", + "stepTwo": "", + "stepThree": "", + "stepFour": "" + }, + "buttons": { + "openEmail": "", + "resetPassword": "" + }, + "imageAlts": { + "passwordKey": "", + "email": "", + "lock": "", + "passwordConfirm": "" + }, + "links": { + "login": "", + "resend": "" + }, + "toasts": { + "sent": "", + "emailNotFound": "", + "redirect": "", + "success": "", + "error": "" + } + } + }, + "errorPages": { + "serverUnreachable": { + "toasts": { + "reconnected": "", + "stillUnreachable": "" + }, + "alertBox": "", + "description": "", + "retryButton": { + "default": "", + "processing": "" + } + } + }, + "createNotifications": { + "title": "", + "nameSettings": { + "title": "", + "description": "", + "nameLabel": "", + "namePlaceholder": "" + }, + "typeSettings": { + "title": "", + "description": "", + "typeLabel": "" + }, + "emailSettings": { + "title": "", + "description": "", + "emailLabel": "", + "emailPlaceholder": "" + }, + "slackSettings": { + "title": "", + "description": "", + "webhookLabel": "", + "webhookPlaceholder": "" + }, + "pagerdutySettings": { + "title": "", + "description": "", + "integrationKeyLabel": "", + "integrationKeyPlaceholder": "" + }, + "discordSettings": { + "title": "", + "description": "", + "webhookLabel": "", + "webhookPlaceholder": "" + }, + "webhookSettings": { + "title": "", + "description": "", + "webhookLabel": "", + "webhookPlaceholder": "" + }, + "testNotification": "", + "dialogDeleteTitle": "", + "dialogDeleteConfirm": "" + }, + "notificationConfig": { + "title": "", + "description": "" + }, + "monitorStatus": { + "checkingEvery": "", + "withCaptureAgent": "", + "up": "", + "down": "", + "paused": "" + }, + "advancedMatching": "", + "sendTestNotifications": "", + "selectAll": "", + "showAdminLoginLink": "", + "logsPage": { + "title": "", + "description": "", + "tabs": { + "queue": "", + "logs": "", + "diagnostics": "" + }, + "toast": { + "fetchLogsSuccess": "" + }, + "logLevelSelect": { + "title": "", + "values": { + "all": "", + "info": "", + "warn": "", + "error": "", + "debug": "" + } + } + }, + "queuePage": { + "title": "", + "refreshButton": "", + "flushButton": "", + "jobTable": { + "title": "", + "idHeader": "", + "urlHeader": "", + "typeHeader": "", + "activeHeader": "", + "lockedAtHeader": "", + "runCountHeader": "", + "failCountHeader": "", + "lastRunHeader": "", + "lastFinishedAtHeader": "", + "lastRunTookHeader": "" + }, + "metricsTable": { + "title": "", + "metricHeader": "", + "valueHeader": "" + }, + "failedJobTable": { + "title": "", + "monitorIdHeader": "", + "monitorUrlHeader": "", + "failCountHeader": "", + "failedAtHeader": "", + "failReasonHeader": "" + } + }, + "export": { + "title": "", + "success": "", + "failed": "" + }, + "monitorActions": { + "title": "", + "import": "", + "export": "", + "deleteSuccess": "", + "deleteFailed": "", + "details": "" + }, + "settingsPage": { + "aboutSettings": { + "labelDevelopedBy": "", + "labelVersion": "", + "title": "" + }, + "demoMonitorsSettings": { + "buttonAddMonitors": "", + "description": "", + "title": "" + }, + "emailSettings": { + "buttonSendTestEmail": "", + "description": "", + "descriptionTransport": "", + "labelAddress": "", + "labelConnectionHost": "", + "labelHost": "", + "labelIgnoreTLS": "", + "labelPassword": "", + "labelPasswordSet": "", + "labelPool": "", + "labelPort": "", + "labelRejectUnauthorized": "", + "labelRequireTLS": "", + "labelSecure": "", + "labelTLSServername": "", + "labelUser": "", + "linkTransport": "", + "placeholderUser": "", + "title": "", + "toastEmailRequiredFieldsError": "" + }, + "pageSpeedSettings": { + "description": "", + "labelApiKeySet": "", + "labelApiKey": "", + "title": "" + }, + "saveButtonLabel": "", + "statsSettings": { + "clearAllStatsButton": "", + "clearAllStatsDescription": "", + "clearAllStatsDialogConfirm": "", + "clearAllStatsDialogDescription": "", + "clearAllStatsDialogTitle": "", + "description": "", + "labelTTL": "", + "labelTTLOptional": "", + "title": "" + }, + "systemResetSettings": { + "buttonRemoveAllMonitors": "", + "description": "", + "dialogConfirm": "", + "dialogDescription": "", + "dialogTitle": "", + "title": "" + }, + "timezoneSettings": { + "description": "", + "label": "", + "title": "" + }, + "title": "", + "uiSettings": { + "description": "", + "labelLanguage": "", + "labelTheme": "", + "title": "" + }, + "urlSettings": { + "description": "", + "label": "", + "selectDisabled": "", + "selectEnabled": "", + "title": "" + } + }, + "statusPageCreate": { + "buttonSave": "" + }, + "incidentsOptionsHeaderFilterResolved": "", + "settingsSave": "", + "statusPageCreateAppearanceTitle": "", + "confirmPassword": "", + "monitorHooks": { + "failureAddDemoMonitors": "", + "successAddDemoMonitors": "" + }, + "settingsAppearance": "", + "settingsDisplayTimezone": "", + "settingsGeneralSettings": "", + "incidentsOptionsHeaderTotalIncidents": "", + "statusPage": { + "deleteSuccess": "", + "deleteFailed": "", + "createSuccess": "", + "updateSuccess": "", + "generalSettings": "", + "contents": "", + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "testNotificationsDisabled": "", + "incidentsTableResolvedAt": "", + "incidentsTableActionResolve": "", + "checkHooks": { + "failureResolveOne": "", + "failureResolveAll": "", + "failureResolveMonitor": "" + }, + "checkFormError": "", + "diagnosticsPage": { + "diagnosticDescription": "", + "statsDescription": "", + "gauges": { + "heapAllocationTitle": "", + "heapAllocationSubtitle": "", + "heapUsageTitle": "", + "heapUsageSubtitle": "", + "heapUtilizationTitle": "", + "heapUtilizationSubtitle": "", + "instantCpuUsageTitle": "", + "instantCpuUsageSubtitle": "" + }, + "stats": { + "eventLoopDelayTitle": "", + "uptimeTitle": "", + "usedHeapSizeTitle": "", + "totalHeapSizeTitle": "", + "osMemoryLimitTitle": "" + } + }, + "pageSpeedLighthouseAPI": "", + "time": { + "threeMinutes": "", + "fiveMinutes": "", + "tenMinutes": "", + "twentyMinutes": "", + "oneHour": "", + "oneDay": "", + "oneWeek": "" + }, + "general": { + "noOptionsFound": "" + }, + "infrastructureMonitor": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "maintenanceWindow": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "pageSpeed": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "uptimeMonitor": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "editUserPage": { + "form": { + "email": "", + "firstName": "", + "lastName": "", + "role": "", + "save": "" + }, + "table": { + "actionHeader": "", + "roleHeader": "" + }, + "title": "", + "toast": { + "successUserUpdate": "", + "validationErrors": "" + } + }, + "incidentsPageActionResolveMonitor": "", + "incidentsPageActionResolveAll": "" +} diff --git a/client/src/locales/fi.json b/client/src/locales/fi.json new file mode 100644 index 000000000..2198e2798 --- /dev/null +++ b/client/src/locales/fi.json @@ -0,0 +1,969 @@ +{ + "submit": "Lähetä", + "title": "Otsikko", + "distributedStatusHeaderText": "Reaaliaikainen kattavuus oikeilla laitteilla", + "distributedStatusSubHeaderText": "Miljoonien laitteiden voimin ympäri maailman näet järjestelmän suorituskyvyn maailman alueittain, maittain tai kaupungeittain", + "settingsDisabled": "Poistettu käytöstä", + "settingsSuccessSaved": "", + "settingsFailedToSave": "", + "settingsStatsCleared": "", + "settingsFailedToClearStats": "", + "settingsMonitorsDeleted": "", + "settingsFailedToDeleteMonitors": "", + "starPromptTitle": "", + "starPromptDescription": "", + "https": "HTTPS", + "http": "HTTP", + "monitor": "Seurain", + "aboutus": "Tietoja meistä", + "now": "Nyt", + "delete": "Poista", + "configure": "Määritä", + "responseTime": "Vasteaika", + "ms": "ms", + "bar": "Palkki", + "area": "Alue", + "country": "MAA", + "city": "KAUPUNKI", + "response": "Vastaus", + "monitorStatusUp": "Valvontakohde {name} ({url}) on nyt toiminnassa ja vastaa", + "monitorStatusDown": "Valvontakohde {name} ({url}) ei ole toiminnassa, eikä vastaa", + "webhookSendSuccess": "Webhook-ilmoitus lähetettiin onnistuneesti", + "webhookSendError": "Virhe lähetettäessä webhook-ilmoitusta palveluun {platform}", + "webhookUnsupportedPlatform": "Ei tuettu palvelu: {platform}", + "distributedRightCategoryTitle": "Valvonta", + "distributedStatusServerMonitors": "Palvelin valvonta", + "distributedStatusServerMonitorsDescription": "Valvo palvelimien tilaa", + "distributedUptimeCreateSelectURL": "Tässä voit valita isäntäkohteen URL-osoitteen ja valvontatyypin.", + "distributedUptimeCreateChecks": "Suoritettavat tarkistukset", + "distributedUptimeCreateChecksDescription": "Voit aina lisätä tai poistaa tarkistuksia sivuston lisäämisen jälkeen.", + "distributedUptimeCreateIncidentNotification": "Häiriöilmoitukset", + "distributedUptimeCreateIncidentDescription": "Jos ilmenee häiriö, ilmoita käyttäjille.", + "distributedUptimeCreateAdvancedSettings": "Lisäasetukset", + "distributedUptimeDetailsNoMonitorHistory": "Tälle valvontakohteelle ei ole vielä tarkistushistoriaa.", + "distributedUptimeDetailsStatusHeaderUptime": "", + "distributedUptimeDetailsStatusHeaderLastUpdate": "Viimeksi päivitetty", + "notifications": { + "enableNotifications": "", + "testNotification": "Testi-ilmoitus", + "addOrEditNotifications": "Lisää tai muokkaa ilmoituksia", + "slack": { + "label": "Slack", + "description": "", + "webhookLabel": "Webhookin URL", + "webhookPlaceholder": "", + "webhookRequired": "" + }, + "discord": { + "label": "Discord", + "description": "", + "webhookLabel": "Discord Webhookin URL-osoite", + "webhookPlaceholder": "https://discord.com/api/webhooks/...", + "webhookRequired": "" + }, + "telegram": { + "label": "Telegram", + "description": "", + "tokenLabel": "", + "tokenPlaceholder": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", + "chatIdLabel": "", + "chatIdPlaceholder": "-1001234567890", + "fieldsRequired": "" + }, + "webhook": { + "label": "Webhookit", + "description": "", + "urlLabel": "Webhookin URL", + "urlPlaceholder": "https://your-server.com/webhook", + "urlRequired": "" + }, + "testNotificationDevelop": "Testi-ilmoitus 2", + "integrationButton": "", + "testSuccess": "Testi ilmoituksen lähetys onnistui!", + "testFailed": "Testi ilmoituksen lähetys epäonnistui", + "unsupportedType": "", + "networkError": "", + "fallback": { + "title": "", + "checks": [""], + "actionButton": "" + }, + "createButton": "", + "createTitle": "", + "create": { + "success": "", + "failed": "" + }, + "fetch": { + "success": "", + "failed": "" + }, + "delete": { + "success": "", + "failed": "" + }, + "edit": { + "success": "", + "failed": "" + }, + "test": { + "success": "", + "failed": "" + } + }, + "testLocale": "", + "add": "Lisää", + "monitors": "Seuraimet", + "distributedUptimeStatusCreateStatusPage": "", + "distributedUptimeStatusCreateStatusPageAccess": "", + "distributedUptimeStatusCreateStatusPageReady": "", + "distributedUptimeStatusBasicInfoHeader": "", + "distributedUptimeStatusBasicInfoDescription": "", + "distributedUptimeStatusLogoHeader": "Logo", + "distributedUptimeStatusLogoDescription": "", + "distributedUptimeStatusLogoUploadButton": "", + "distributedUptimeStatusStandardMonitorsHeader": "", + "distributedUptimeStatusStandardMonitorsDescription": "", + "distributedUptimeStatusCreateYour": "", + "distributedUptimeStatusEditYour": "", + "distributedUptimeStatusPublishedLabel": "", + "distributedUptimeStatusCompanyNameLabel": "Yrityksen nimi", + "distributedUptimeStatusPageAddressLabel": "", + "distributedUptimeStatus30Days": "30 päivää", + "distributedUptimeStatus60Days": "60 päivää", + "distributedUptimeStatus90Days": "90 päivää", + "distributedUptimeStatusPageNotSetUp": "", + "distributedUptimeStatusContactAdmin": "Ota yhteys järjestelmävalvojaasi", + "distributedUptimeStatusPageNotPublic": "", + "distributedUptimeStatusPageDeleteDialog": "", + "distributedUptimeStatusPageDeleteConfirm": "", + "distributedUptimeStatusPageDeleteDescription": "", + "distributedUptimeStatusDevices": "", + "distributedUptimeStatusUpt": "", + "distributedUptimeStatusUptBurned": "", + "distributedUptimeStatusUptLogo": "UPT logo", + "incidentsTableNoIncidents": "", + "incidentsTablePaginationLabel": "", + "incidentsTableMonitorName": "", + "incidentsTableStatus": "Tila", + "incidentsTableDateTime": "Päivämäärä ja aika", + "incidentsTableStatusCode": "", + "incidentsTableMessage": "Viesti", + "incidentsOptionsHeader": "", + "incidentsOptionsHeaderFilterBy": "", + "incidentsOptionsHeaderFilterAll": "Kaikki", + "incidentsOptionsHeaderFilterDown": "Alhaalla", + "incidentsOptionsHeaderFilterCannotResolve": "", + "incidentsOptionsHeaderShow": "Näytä:", + "incidentsOptionsHeaderLastHour": "Viime tunti", + "incidentsOptionsHeaderLastDay": "Viime päivä", + "incidentsOptionsHeaderLastWeek": "Viime viikko", + "incidentsOptionsPlaceholderAllServers": "Kaikki palvelimet", + "infrastructureCreateYour": "", + "infrastructureCreateGeneralSettingsDescription": "", + "infrastructureServerRequirement": "", + "infrastructureCustomizeAlerts": "", + "infrastructureAlertNotificationDescription": "", + "infrastructureCreateMonitor": "", + "infrastructureProtocol": "Protokolla", + "infrastructureServerUrlLabel": "Palvelimen URL-osoite", + "infrastructureDisplayNameLabel": "", + "infrastructureAuthorizationSecretLabel": "", + "gb": "", + "mb": "", + "mem": "", + "memoryUsage": "Muistinkäyttö", + "cpu": "", + "cpuUsage": "", + "cpuTemperature": "", + "diskUsage": "", + "used": "", + "total": "", + "cores": "", + "frequency": "", + "status": "Tila", + "cpuPhysical": "", + "cpuLogical": "", + "cpuFrequency": "", + "avgCpuTemperature": "", + "memory": "Muisti", + "disk": "", + "uptime": "", + "os": "", + "host": "", + "actions": "", + "integrations": "", + "integrationsPrism": "", + "integrationsSlack": "Slack", + "integrationsSlackInfo": "", + "integrationsDiscord": "", + "integrationsDiscordInfo": "", + "integrationsZapier": "Zapier", + "integrationsZapierInfo": "", + "commonSave": "Tallenna", + "createYour": "", + "createMonitor": "", + "pause": "", + "resume": "", + "editing": "", + "url": "", + "access": "", + "timezone": "Aikavyöhyke", + "features": "", + "administrator": "", + "loginHere": "", + "displayName": "", + "urlMonitor": "", + "portToMonitor": "", + "websiteMonitoring": "", + "websiteMonitoringDescription": "", + "pingMonitoring": "", + "pingMonitoringDescription": "", + "dockerContainerMonitoring": "", + "dockerContainerMonitoringDescription": "", + "portMonitoring": "", + "portMonitoringDescription": "", + "createMaintenanceWindow": "", + "createMaintenance": "", + "editMaintenance": "", + "maintenanceWindowName": "", + "friendlyNameInput": "", + "friendlyNamePlaceholder": "", + "maintenanceRepeat": "", + "maintenance": "", + "duration": "", + "addMonitors": "", + "window": "", + "cancel": "Peruuttaa", + "message": "Viesti", + "low": "", + "high": "", + "statusCode": "Tilakoodi", + "date&Time": "Päivämäärä ja aika", + "type": "", + "statusPageName": "", + "publicURL": "Julkinen URL-osoite", + "repeat": "", + "edit": "Muokkaa", + "createA": "", + "remove": "", + "maintenanceWindowDescription": "", + "startTime": "", + "timeZoneInfo": "", + "monitorsToApply": "", + "nextWindow": "", + "notFoundButton": "", + "pageSpeedConfigureSettingsDescription": "", + "monitorDisplayName": "", + "whenNewIncident": "", + "notifySMS": "Ilmoita tekstiviestillä (tulossa pian)", + "notifyEmails": "", + "seperateEmails": "", + "checkFrequency": "", + "matchMethod": "", + "expectedValue": "", + "deleteDialogTitle": "", + "deleteDialogDescription": "", + "pageSpeedMonitor": "", + "shown": "", + "ago": "sitten", + "companyName": "Yrityksen nimi", + "pageSpeedDetailsPerformanceReport": "", + "pageSpeedDetailsPerformanceReportCalculator": "", + "checkingEvery": "", + "statusPageCreateSettings": "", + "basicInformation": "", + "statusPageCreateBasicInfoDescription": "", + "statusPageCreateSelectTimeZoneDescription": "", + "statusPageCreateAppearanceDescription": "", + "statusPageCreateSettingsCheckboxLabel": "", + "statusPageCreateBasicInfoStatusPageAddress": "", + "statusPageCreateTabsContent": "", + "statusPageCreateTabsContentDescription": "", + "statusPageCreateTabsContentFeaturesDescription": "", + "showCharts": "Näytä kaaviot", + "showUptimePercentage": "", + "removeLogo": "Poista logo", + "statusPageStatus": "", + "statusPageStatusContactAdmin": "Ota yhteyttä järjestelmänvalvojaan", + "statusPageStatusNotPublic": "", + "statusPageStatusNoPage": "", + "statusPageStatusServiceStatus": "", + "deleteStatusPage": "", + "deleteStatusPageConfirm": "", + "deleteStatusPageDescription": "", + "uptimeCreate": "", + "uptimeCreateJsonPath": "", + "uptimeCreateJsonPathQuery": "", + "maintenanceTableActionMenuDialogTitle": "", + "infrastructureEditYour": "Muokkaa omaa", + "infrastructureEditMonitor": "", + "infrastructureMonitorCreated": "", + "infrastructureMonitorUpdated": "", + "errorInvalidTypeId": "", + "errorInvalidFieldId": "", + "inviteNoTokenFound": "", + "pageSpeedWarning": "", + "pageSpeedLearnMoreLink": "", + "pageSpeedAddApiKey": "", + "update": "Päivitä", + "invalidFileFormat": "", + "invalidFileSize": "Tiedoston koko on liian suuri!", + "ClickUpload": "", + "DragandDrop": "raahaa ja pudota", + "MaxSize": "Enimmäiskoko", + "SupportedFormats": "Tuetut muodot", + "FirstName": "Etunimi", + "LastName": "Sukunimi", + "EmailDescriptionText": "Tämä on nykyinen sähköpostiosoitteesi — sitä ei voi vaihtaa.", + "YourPhoto": "Profiilikuva", + "PhotoDescriptionText": "", + "save": "Tallenna", + "DeleteDescriptionText": "", + "DeleteAccountWarning": "", + "DeleteWarningTitle": "Haluatko todella poistaa tämän tilin?", + "bulkImport": { + "title": "", + "selectFileTips": "", + "selectFileDescription": "", + "selectFile": "Valitse tiedosto", + "parsingFailed": "", + "uploadSuccess": "", + "validationFailed": "", + "noFileSelected": "Ei valittua tiedostoa", + "fallbackPage": "", + "invalidFileType": "", + "uploadFailed": "" + }, + "DeleteAccountTitle": "Poista tili", + "DeleteAccountButton": "Poista tili", + "publicLink": "Julkinen linkki", + "maskedPageSpeedKeyPlaceholder": "*************************************", + "reset": "Palauta", + "ignoreTLSError": "", + "tlsErrorIgnored": "", + "ignoreTLSErrorDescription": "", + "createNew": "Luo uusi", + "greeting": { + "prepend": "Hei", + "append": "", + "overview": "" + }, + "roles": { + "superAdmin": "Pääylläpitäjä", + "admin": "Ylläpitäjä", + "teamMember": "Tiimin jäsen", + "demoUser": "Demokäyttäjä" + }, + "teamPanel": { + "teamMembers": "Tiimin jäsenet", + "filter": { + "all": "Kaikki", + "member": "Jäsen" + }, + "inviteTeamMember": "", + "inviteNewTeamMember": "", + "inviteDescription": "", + "email": "Sähköposti", + "selectRole": "Valitse rooli", + "inviteLink": "Kutsulinkki", + "cancel": "Peruuttaa", + "noMembers": "", + "getToken": "Hae tunniste", + "emailToken": "Sähköpostitunnus", + "table": { + "name": "Nimi", + "email": "Sähköposti", + "role": "Rooli", + "created": "Luotu" + } + }, + "monitorState": { + "paused": "Tauota", + "resumed": "Jatka", + "active": "" + }, + "menu": { + "uptime": "", + "pagespeed": "", + "infrastructure": "Infrastruktuuri", + "incidents": "", + "statusPages": "Tilasivut", + "maintenance": "Huolto", + "integrations": "", + "settings": "Asetukset", + "support": "Tuki", + "discussions": "Keskustelut", + "docs": "Dokumentit", + "changelog": "Muutosloki", + "profile": "Profiili", + "password": "Salasana", + "team": "Tiimi", + "logOut": "Kirjaudu ulos", + "notifications": "", + "logs": "" + }, + "settingsEmailUser": "", + "state": "Tila", + "statusBreadCrumbsStatusPages": "Tilasivut", + "statusBreadCrumbsDetails": "Tiedot", + "commonSaving": "Tallennetaan...", + "navControls": "Ohjaimet", + "incidentsPageTitle": "Häiriöt", + "passwordPanel": { + "passwordChangedSuccess": "Salasanasi vaihdettiin onnistuneesti.", + "passwordInputIncorrect": "", + "currentPassword": "Nykyinen salasana", + "enterCurrentPassword": "Kirjoita nykyinen salasanasi", + "newPassword": "Uusi salasana", + "enterNewPassword": "Kirjoita uusi salasana", + "confirmNewPassword": "Vahvista uusi salasana", + "passwordRequirements": "Uuden salasanan on oltava vähintään 8 merkkiä pitkä ja sen on sisältävä vähintään yksi iso kirjain, yksi pieni kirjain, yksi numero sekä yksi erikoismerkki.", + "saving": "Tallennetaan..." + }, + "emailSent": "Sähköpostin lähetys onnistui", + "failedToSendEmail": "Sähköpostin lähetys epäonnistui", + "settingsTestEmailSuccess": "Testisähköpostin lähetys onnistui", + "settingsTestEmailFailed": "Testisähköpostin lähetys epäonnistui", + "settingsTestEmailFailedWithReason": "Testisähköpostin lähetys epäonnistui: {{reason}}", + "settingsTestEmailUnknownError": "Tuntematon virhe", + "statusMsg": { + "paused": "Valvonta on keskeytetty.", + "up": "Sivusi on ylhäällä.", + "down": "Sivusi on alhaalla.", + "pending": "Odottaa..." + }, + "uptimeGeneralInstructions": { + "http": "", + "ping": "", + "docker": "", + "port": "" + }, + "common": { + "appName": "", + "monitoringAgentName": "", + "buttons": { + "toggleTheme": "" + }, + "toasts": { + "networkError": "", + "checkConnection": "", + "unknownError": "" + } + }, + "auth": { + "common": { + "navigation": { + "continue": "", + "back": "" + }, + "inputs": { + "email": { + "label": "", + "placeholder": "", + "errors": { + "empty": "", + "invalid": "" + } + }, + "password": { + "label": "", + "rules": { + "length": { + "beginning": "", + "highlighted": "" + }, + "special": { + "beginning": "", + "highlighted": "" + }, + "number": { + "beginning": "", + "highlighted": "" + }, + "uppercase": { + "beginning": "", + "highlighted": "" + }, + "lowercase": { + "beginning": "", + "highlighted": "" + }, + "match": { + "beginning": "", + "highlighted": "" + } + }, + "errors": { + "empty": "", + "length": "", + "uppercase": "", + "lowercase": "", + "number": "", + "special": "", + "incorrect": "" + } + }, + "passwordConfirm": { + "label": "", + "placeholder": "", + "errors": { + "empty": "", + "different": "" + } + }, + "firstName": { + "label": "", + "placeholder": "", + "errors": { + "empty": "", + "length": "", + "pattern": "" + } + }, + "lastName": { + "label": "", + "placeholder": "", + "errors": { + "empty": "", + "length": "", + "pattern": "" + } + } + }, + "errors": { + "validation": "" + }, + "fields": { + "password": { + "errors": { + "incorrect": "" + } + }, + "role": { + "errors": { + "min": "" + } + } + } + }, + "login": { + "heading": "", + "subheadings": { + "stepOne": "", + "stepTwo": "" + }, + "links": { + "forgotPassword": "", + "register": "", + "forgotPasswordLink": "", + "registerLink": "" + }, + "toasts": { + "success": "", + "incorrectPassword": "" + }, + "errors": { + "password": { + "incorrect": "" + } + } + }, + "registration": { + "heading": { + "superAdmin": "", + "user": "" + }, + "subheadings": { + "stepOne": "", + "stepTwo": "", + "stepThree": "" + }, + "description": { + "superAdmin": "", + "user": "" + }, + "gettingStartedButton": { + "superAdmin": "", + "user": "" + }, + "termsAndPolicies": "", + "links": { + "login": "" + }, + "toasts": { + "success": "" + } + }, + "forgotPassword": { + "heading": "", + "subheadings": { + "stepOne": "", + "stepTwo": "", + "stepThree": "", + "stepFour": "" + }, + "buttons": { + "openEmail": "", + "resetPassword": "" + }, + "imageAlts": { + "passwordKey": "", + "email": "", + "lock": "", + "passwordConfirm": "" + }, + "links": { + "login": "", + "resend": "" + }, + "toasts": { + "sent": "", + "emailNotFound": "", + "redirect": "", + "success": "", + "error": "" + } + } + }, + "errorPages": { + "serverUnreachable": { + "toasts": { + "reconnected": "", + "stillUnreachable": "" + }, + "alertBox": "", + "description": "", + "retryButton": { + "default": "", + "processing": "" + } + } + }, + "createNotifications": { + "title": "", + "nameSettings": { + "title": "", + "description": "", + "nameLabel": "", + "namePlaceholder": "" + }, + "typeSettings": { + "title": "", + "description": "", + "typeLabel": "" + }, + "emailSettings": { + "title": "", + "description": "", + "emailLabel": "", + "emailPlaceholder": "" + }, + "slackSettings": { + "title": "", + "description": "", + "webhookLabel": "", + "webhookPlaceholder": "" + }, + "pagerdutySettings": { + "title": "", + "description": "", + "integrationKeyLabel": "", + "integrationKeyPlaceholder": "" + }, + "discordSettings": { + "title": "", + "description": "", + "webhookLabel": "", + "webhookPlaceholder": "" + }, + "webhookSettings": { + "title": "", + "description": "", + "webhookLabel": "", + "webhookPlaceholder": "" + }, + "testNotification": "", + "dialogDeleteTitle": "", + "dialogDeleteConfirm": "" + }, + "notificationConfig": { + "title": "", + "description": "" + }, + "monitorStatus": { + "checkingEvery": "", + "withCaptureAgent": "", + "up": "", + "down": "", + "paused": "" + }, + "advancedMatching": "", + "sendTestNotifications": "", + "selectAll": "", + "showAdminLoginLink": "", + "logsPage": { + "title": "", + "description": "", + "tabs": { + "queue": "", + "logs": "", + "diagnostics": "" + }, + "toast": { + "fetchLogsSuccess": "" + }, + "logLevelSelect": { + "title": "", + "values": { + "all": "", + "info": "", + "warn": "", + "error": "", + "debug": "" + } + } + }, + "queuePage": { + "title": "", + "refreshButton": "", + "flushButton": "", + "jobTable": { + "title": "", + "idHeader": "", + "urlHeader": "", + "typeHeader": "", + "activeHeader": "", + "lockedAtHeader": "", + "runCountHeader": "", + "failCountHeader": "", + "lastRunHeader": "", + "lastFinishedAtHeader": "", + "lastRunTookHeader": "" + }, + "metricsTable": { + "title": "", + "metricHeader": "", + "valueHeader": "" + }, + "failedJobTable": { + "title": "", + "monitorIdHeader": "", + "monitorUrlHeader": "", + "failCountHeader": "", + "failedAtHeader": "", + "failReasonHeader": "" + } + }, + "export": { + "title": "", + "success": "", + "failed": "" + }, + "monitorActions": { + "title": "", + "import": "", + "export": "", + "deleteSuccess": "", + "deleteFailed": "", + "details": "" + }, + "settingsPage": { + "aboutSettings": { + "labelDevelopedBy": "", + "labelVersion": "", + "title": "" + }, + "demoMonitorsSettings": { + "buttonAddMonitors": "", + "description": "", + "title": "" + }, + "emailSettings": { + "buttonSendTestEmail": "", + "description": "", + "descriptionTransport": "", + "labelAddress": "", + "labelConnectionHost": "", + "labelHost": "", + "labelIgnoreTLS": "", + "labelPassword": "", + "labelPasswordSet": "", + "labelPool": "", + "labelPort": "", + "labelRejectUnauthorized": "", + "labelRequireTLS": "", + "labelSecure": "", + "labelTLSServername": "", + "labelUser": "", + "linkTransport": "", + "placeholderUser": "", + "title": "", + "toastEmailRequiredFieldsError": "" + }, + "pageSpeedSettings": { + "description": "", + "labelApiKeySet": "", + "labelApiKey": "", + "title": "" + }, + "saveButtonLabel": "", + "statsSettings": { + "clearAllStatsButton": "", + "clearAllStatsDescription": "", + "clearAllStatsDialogConfirm": "", + "clearAllStatsDialogDescription": "", + "clearAllStatsDialogTitle": "", + "description": "", + "labelTTL": "", + "labelTTLOptional": "", + "title": "" + }, + "systemResetSettings": { + "buttonRemoveAllMonitors": "", + "description": "", + "dialogConfirm": "", + "dialogDescription": "", + "dialogTitle": "", + "title": "" + }, + "timezoneSettings": { + "description": "", + "label": "", + "title": "" + }, + "title": "", + "uiSettings": { + "description": "", + "labelLanguage": "", + "labelTheme": "", + "title": "" + }, + "urlSettings": { + "description": "", + "label": "", + "selectDisabled": "", + "selectEnabled": "", + "title": "" + } + }, + "statusPageCreate": { + "buttonSave": "" + }, + "incidentsOptionsHeaderFilterResolved": "", + "settingsSave": "", + "statusPageCreateAppearanceTitle": "", + "confirmPassword": "", + "monitorHooks": { + "failureAddDemoMonitors": "", + "successAddDemoMonitors": "" + }, + "settingsAppearance": "", + "settingsDisplayTimezone": "", + "settingsGeneralSettings": "", + "incidentsOptionsHeaderTotalIncidents": "", + "statusPage": { + "deleteSuccess": "", + "deleteFailed": "", + "createSuccess": "", + "updateSuccess": "", + "generalSettings": "", + "contents": "", + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "testNotificationsDisabled": "", + "incidentsTableResolvedAt": "", + "incidentsTableActionResolve": "", + "checkHooks": { + "failureResolveOne": "", + "failureResolveAll": "", + "failureResolveMonitor": "" + }, + "checkFormError": "", + "diagnosticsPage": { + "diagnosticDescription": "", + "statsDescription": "", + "gauges": { + "heapAllocationTitle": "", + "heapAllocationSubtitle": "", + "heapUsageTitle": "", + "heapUsageSubtitle": "", + "heapUtilizationTitle": "", + "heapUtilizationSubtitle": "", + "instantCpuUsageTitle": "", + "instantCpuUsageSubtitle": "" + }, + "stats": { + "eventLoopDelayTitle": "", + "uptimeTitle": "", + "usedHeapSizeTitle": "", + "totalHeapSizeTitle": "", + "osMemoryLimitTitle": "" + } + }, + "pageSpeedLighthouseAPI": "", + "time": { + "threeMinutes": "", + "fiveMinutes": "", + "tenMinutes": "", + "twentyMinutes": "", + "oneHour": "", + "oneDay": "", + "oneWeek": "" + }, + "general": { + "noOptionsFound": "" + }, + "infrastructureMonitor": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "maintenanceWindow": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "pageSpeed": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "uptimeMonitor": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "editUserPage": { + "form": { + "email": "", + "firstName": "", + "lastName": "", + "role": "", + "save": "" + }, + "table": { + "actionHeader": "", + "roleHeader": "" + }, + "title": "", + "toast": { + "successUserUpdate": "", + "validationErrors": "" + } + }, + "incidentsPageActionResolveMonitor": "", + "incidentsPageActionResolveAll": "" +} diff --git a/client/src/locales/fr.json b/client/src/locales/fr.json new file mode 100644 index 000000000..c6e263c4d --- /dev/null +++ b/client/src/locales/fr.json @@ -0,0 +1,973 @@ +{ + "submit": "Envoyer", + "title": "Titre", + "distributedStatusHeaderText": "Couverture en temps réel, sur des appareils en temps réel", + "distributedStatusSubHeaderText": "Alimenté par des millions d'appareils à travers le monde, consultez les performances du système par région, pays ou ville", + "settingsDisabled": "Désactivé", + "settingsSuccessSaved": "Les paramètres ont bien été sauvegardés", + "settingsFailedToSave": "Les paramètres n'ont pas pu être sauvegardés", + "settingsStatsCleared": "Les statistiques ont été réinitialisées avec succès", + "settingsFailedToClearStats": "Les statistiques n'ont pas pu être réinitialisées", + "settingsMonitorsDeleted": "Tous les moniteurs ont bien été supprimés", + "settingsFailedToDeleteMonitors": "La suppression de tous les moniteurs a échoué", + "starPromptTitle": "Ajouter Checkmate aux favoris", + "starPromptDescription": "Voir les dernières versions et aider la communauté à grandir sur Github", + "https": "HTTPS", + "http": "HTTP", + "monitor": "Moniteur", + "aboutus": "A propos de nous", + "now": "Maintenant", + "delete": "Supprimer", + "configure": "Configurer", + "responseTime": "Temps de réponse", + "ms": "ms", + "bar": "Graphique", + "area": "Zone", + "country": "PAYS", + "city": "VILLE", + "response": "Réponse", + "monitorStatusUp": "Moniteur {name} ({url}) est désormais EN LIGNE et répond aux requêtes", + "monitorStatusDown": "Moniteur {name} ({url}) est désormais HORS LIGNE et ne répond plus", + "webhookSendSuccess": "La notification webhook a bien été envoyée", + "webhookSendError": "Une erreur s'est produite lors de l'envoi de la notification webhook sur {platform}", + "webhookUnsupportedPlatform": "La plateforme {platform} n'est pas supportée actuellement", + "distributedRightCategoryTitle": "Moniteur", + "distributedStatusServerMonitors": "Monitoring des serveurs", + "distributedStatusServerMonitorsDescription": "Statut des moniteurs relatifs aux serveurs", + "distributedUptimeCreateSelectURL": "Ici vous pouvez sélectionner l'URL de l'hôte et le type de moniteur", + "distributedUptimeCreateChecks": "Vérifications à effectuer", + "distributedUptimeCreateChecksDescription": "Vous pourrez ajouter ou supprimer des vérifications après l'ajout de votre site", + "distributedUptimeCreateIncidentNotification": "Notifications d'incidents", + "distributedUptimeCreateIncidentDescription": "Quand il y a un incident, notifier les utilisateurs", + "distributedUptimeCreateAdvancedSettings": "Paramètres avancés", + "distributedUptimeDetailsNoMonitorHistory": "Il n'y a pas encore d'historique de vérification pour ce moniteur", + "distributedUptimeDetailsStatusHeaderUptime": "Temps en ligne :", + "distributedUptimeDetailsStatusHeaderLastUpdate": "Dernière mise à jour", + "notifications": { + "enableNotifications": "Activer les notifications sur {{platform}}", + "testNotification": "Notification de test", + "addOrEditNotifications": "Ajouter ou éditer des notifications", + "slack": { + "label": "Slack", + "description": "Pour activer les notifications Slack, créez une application Slack et activez les webhooks entrants. Ensuite, indiquer l'URL du webhook ici.", + "webhookLabel": "URL du webhook", + "webhookPlaceholder": "https://hooks.slack.com/services/...", + "webhookRequired": "L'URL du webhook Slack est requise" + }, + "discord": { + "label": "Discord", + "description": "Pour envoyer des données sur un canal Discord depuis Checkmate, utilisez l'intégration Webhook Discord.", + "webhookLabel": "URL du webhook Discord", + "webhookPlaceholder": "https://discord.com/api/webhooks/...", + "webhookRequired": "L'URL du webhook Discord est requise" + }, + "telegram": { + "label": "Telegram", + "description": "Pour activer les notifications Telegram, créez un bot Telegram en utilisant BotFather, le robot officiel pour créer et gérer ses propres robots. Indiquez ensuite votre clé API et l'ID du chat ici.", + "tokenLabel": "Token du robot", + "tokenPlaceholder": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", + "chatIdLabel": "Votre Chat ID", + "chatIdPlaceholder": "-1001234567890", + "fieldsRequired": "Le token Telegram et l'ID du chat sont requis." + }, + "webhook": { + "label": "Webhooks", + "description": "Vous pouvez indiquer un webhook personnalisé pour recevoir des notifications lorsqu'un incident se produit.", + "urlLabel": "URL du webhook", + "urlPlaceholder": "https://votre-serveur.fr/webhook", + "urlRequired": "L'URL du webhook est nécessaire" + }, + "testNotificationDevelop": "Notification de test 2", + "integrationButton": "Intégration de notification", + "testSuccess": "La notification de test a été envoyée avec succès !", + "testFailed": "Échec de l'envoi de la notification de test", + "unsupportedType": "Type de notification non supporté", + "networkError": "Une erreur réseau s'est produite", + "fallback": { + "title": "canal de notification", + "checks": [ + "Alertez les équipes en cas de temps d'arrêt ou de problèmes de performances", + "Informez les ingénieurs lorsque des incidents se produisent", + "Tenez les administrateurs informés des changements apportés au système" + ], + "actionButton": "" + }, + "createButton": "Créer un canal de notification", + "createTitle": "Canal de notification", + "create": { + "success": "La notification a été créée avec succès", + "failed": "Une erreur s'est produite lors de la création de la notification" + }, + "fetch": { + "success": "Les notifications ont été récupérées avec succès", + "failed": "Une erreur s'est produite lors de la récupération des notifications" + }, + "delete": { + "success": "Notification supprimée avec succès", + "failed": "Une erreur s'est produite lors de la suppression de la notification" + }, + "edit": { + "success": "Notification modifiée avec succès", + "failed": "Une erreur s'est produite lors de la modification de la notification" + }, + "test": { + "success": "Notification de test envoyée avec succès", + "failed": "Une erreur s'est produite lors de l'envoi de la notification de test" + } + }, + "testLocale": "testLocale", + "add": "Ajouter", + "monitors": "moniteurs", + "distributedUptimeStatusCreateStatusPage": "page de statut", + "distributedUptimeStatusCreateStatusPageAccess": "Accès", + "distributedUptimeStatusCreateStatusPageReady": "Si votre page de statut est prête, vous pouvez l'indiquer en \"Publiée\".", + "distributedUptimeStatusBasicInfoHeader": "Informations de base", + "distributedUptimeStatusBasicInfoDescription": "Définir le nom de la société et le sous domaine sur lequel votre page de statut pointe.", + "distributedUptimeStatusLogoHeader": "Logo", + "distributedUptimeStatusLogoDescription": "Ajoutez un logo pour votre page de statut", + "distributedUptimeStatusLogoUploadButton": "Téléverser un logo", + "distributedUptimeStatusStandardMonitorsHeader": "Moniteurs standards", + "distributedUptimeStatusStandardMonitorsDescription": "Ajouter un moniteur standard à votre page de statut", + "distributedUptimeStatusCreateYour": "Créer votre", + "distributedUptimeStatusEditYour": "Editer votre", + "distributedUptimeStatusPublishedLabel": "Publiée et visible publiquement", + "distributedUptimeStatusCompanyNameLabel": "Nom de l'entreprise", + "distributedUptimeStatusPageAddressLabel": "Adresse de votre page de statut", + "distributedUptimeStatus30Days": "30 jours", + "distributedUptimeStatus60Days": "60 jours", + "distributedUptimeStatus90Days": "90 jours", + "distributedUptimeStatusPageNotSetUp": "Aucune page de statut n'est mise en place.", + "distributedUptimeStatusContactAdmin": "Veuillez contacter votre administrateur", + "distributedUptimeStatusPageNotPublic": "Cette page de statut n'est pas publique.", + "distributedUptimeStatusPageDeleteDialog": "Voulez-vous supprimer cette page de statut ?", + "distributedUptimeStatusPageDeleteConfirm": "Oui, supprimer la page de statut", + "distributedUptimeStatusPageDeleteDescription": "Supprimer votre page de statut est irréversible.", + "distributedUptimeStatusDevices": "Équipements", + "distributedUptimeStatusUpt": "UPT", + "distributedUptimeStatusUptBurned": "", + "distributedUptimeStatusUptLogo": "Logo Upt", + "incidentsTableNoIncidents": "Aucun incident enregistrée", + "incidentsTablePaginationLabel": "incidents", + "incidentsTableMonitorName": "Nom du moniteur", + "incidentsTableStatus": "Statut", + "incidentsTableDateTime": "Date et heure", + "incidentsTableStatusCode": "Code de statut", + "incidentsTableMessage": "Message", + "incidentsOptionsHeader": "Incidents pour :", + "incidentsOptionsHeaderFilterBy": "Filtrer par :", + "incidentsOptionsHeaderFilterAll": "Tout", + "incidentsOptionsHeaderFilterDown": "Hors ligne", + "incidentsOptionsHeaderFilterCannotResolve": "Impossible de résoudre", + "incidentsOptionsHeaderShow": "Voir:", + "incidentsOptionsHeaderLastHour": "Dernière heure", + "incidentsOptionsHeaderLastDay": "Dernier jour", + "incidentsOptionsHeaderLastWeek": "Dernière semaine", + "incidentsOptionsPlaceholderAllServers": "Tous les serveurs", + "infrastructureCreateYour": "Créer votre", + "infrastructureCreateGeneralSettingsDescription": "Ici, vous pouvez sélectionner l'URL de l'hôte, ainsi que le nom familier et le secret d'autorisation pour vous connecter à l'agent sur le serveur.", + "infrastructureServerRequirement": "Le serveur que vous surveillez doit exécuter le", + "infrastructureCustomizeAlerts": "Personnaliser les alertes", + "infrastructureAlertNotificationDescription": "Envoyer une notification aux utilisateurs lorsque le seuil dépasse un pourcentage spécifié.", + "infrastructureCreateMonitor": "Créer un moniteur d'infrastructure", + "infrastructureProtocol": "Protocole", + "infrastructureServerUrlLabel": "URL du serveur", + "infrastructureDisplayNameLabel": "Nom d'affichage", + "infrastructureAuthorizationSecretLabel": "Secret d'autorisation", + "gb": "GB", + "mb": "MB", + "mem": "Mém", + "memoryUsage": "Utilisation de la mémoire", + "cpu": "CPU", + "cpuUsage": "Utilisation CPU", + "cpuTemperature": "Température CPU", + "diskUsage": "Utilisation du disque", + "used": "Utilisé", + "total": "Total", + "cores": "Coeurs", + "frequency": "Fréquence", + "status": "Statut", + "cpuPhysical": "CPU (Physique)", + "cpuLogical": "CPU (Logique)", + "cpuFrequency": "Fréquence CPU", + "avgCpuTemperature": "Température moyenne du CPU", + "memory": "Mémoire", + "disk": "Disque", + "uptime": "Temps en ligne", + "os": "OS", + "host": "Hôte", + "actions": "Actions", + "integrations": "Intégrations", + "integrationsPrism": "Connecter Prism à votre service préféré.", + "integrationsSlack": "Slack", + "integrationsSlackInfo": "Connecter à Slack et voir les incidents dans un canal", + "integrationsDiscord": "Discord", + "integrationsDiscordInfo": "Connecter à Discord et voir les incidents dans un canal", + "integrationsZapier": "Zapier", + "integrationsZapierInfo": "Envoyer tous les incidents à Zapier", + "commonSave": "Sauvegarder", + "createYour": "Créez votre", + "createMonitor": "Ajouter un moniteur", + "pause": "Mettre en pause", + "resume": "Reprendre", + "editing": "Edition...", + "url": "URL", + "access": "Accès", + "timezone": "Fuseau horaire", + "features": "Fonctionnalités", + "administrator": "Administrateur ?", + "loginHere": "Connexion", + "displayName": "Nom d'affichage", + "urlMonitor": "URL à suivre", + "portToMonitor": "Port du moniteur", + "websiteMonitoring": "Monitoring d'un site internet", + "websiteMonitoringDescription": "Utiliser HTTP(s) pour vérifier votre site ou votre API.", + "pingMonitoring": "Monitoring par ping", + "pingMonitoringDescription": "Vérifier si le serveur est disponible ou pas", + "dockerContainerMonitoring": "Monitoring d'un container Docker", + "dockerContainerMonitoringDescription": "Vérifier si votre container Docker fonctionne ou pas.", + "portMonitoring": "Monitoring d'un port", + "portMonitoringDescription": "Vérifier si votre port est ouvert ou non.", + "createMaintenanceWindow": "Créer une fenêtre de maintenance", + "createMaintenance": "Créer une maintenance", + "editMaintenance": "Modifier la maintenance", + "maintenanceWindowName": "Nom de la fenêtre de maintenance", + "friendlyNameInput": "Nom", + "friendlyNamePlaceholder": "Maintenance à __ : __ pendant ___ minutes", + "maintenanceRepeat": "Répéter la maintenance", + "maintenance": "maintenance", + "duration": "Durée", + "addMonitors": "Ajouter des moniteurs", + "window": "fenêtre", + "cancel": "Annuler", + "message": "Message", + "low": "bas", + "high": "haut", + "statusCode": "Code de statut", + "date&Time": "Date et heure", + "type": "Type", + "statusPageName": "Nom de la page de statut", + "publicURL": "URL publique", + "repeat": "Répéter", + "edit": "Modifier", + "createA": "Créer un", + "remove": "Supprimer", + "maintenanceWindowDescription": "Vos pings ne seront pas envoyés pendant cette fenêtre de temps.", + "startTime": "Date de démarrage", + "timeZoneInfo": "Toutes les dates et heures sont dans le fuseau GMT+0", + "monitorsToApply": "Moniteurs sur lesquels appliquer la fenêtre de maintenance", + "nextWindow": "Prochaine fenêtre de maintenance", + "notFoundButton": "Aller au tableau de bord principal", + "pageSpeedConfigureSettingsDescription": "Vous pouvez sélectionner l'URL de hôte et le type de moniteur.", + "monitorDisplayName": "Nom du moniteur", + "whenNewIncident": "Lors d'un nouvel incident,", + "notifySMS": "Notification par SMS (bientôt)", + "notifyEmails": "Envoyer également une notification par email à plusieurs adresses (bientôt).", + "seperateEmails": "Vous pouvez ajouter plusieurs adresses emails en les séparant par une virgule", + "checkFrequency": "Vérifier la fréquence", + "matchMethod": "Méthode de rapprochement", + "expectedValue": "Valeur attendue", + "deleteDialogTitle": "Voulez-vous vraiment supprimer ce moniteur ?", + "deleteDialogDescription": "Une fois supprimé, le moniteur ne peut pas être récupéré.", + "pageSpeedMonitor": "Moniteur PageSpeed", + "shown": "Visibles", + "ago": "depuis", + "companyName": "Nom de la société", + "pageSpeedDetailsPerformanceReport": "Les valeurs sont estimatives et peuvent varier.", + "pageSpeedDetailsPerformanceReportCalculator": "Voir le calculateur", + "checkingEvery": "Vérification tous les", + "statusPageCreateSettings": "Si votre page de statut est prête, vous pouvez la rendre publique.", + "basicInformation": "Informations de base", + "statusPageCreateBasicInfoDescription": "Définir le nom de la société et le sous-domaine vers lequel pointe votre page de statut.", + "statusPageCreateSelectTimeZoneDescription": "Sélectionnez le fuseau horaire qui sera utilisé pour votre page de statut", + "statusPageCreateAppearanceDescription": "Définir le style par défaut de votre page de statut.", + "statusPageCreateSettingsCheckboxLabel": "Publiée et visible au public", + "statusPageCreateBasicInfoStatusPageAddress": "L'adresse de votre page de statut", + "statusPageCreateTabsContent": "Serveurs de votre page de statut", + "statusPageCreateTabsContentDescription": "Vous pouvez ajouter autant de moniteurs à votre page de statut que vous le souhaitez. Vous pouvez également les réordonner pour améliorer l'expérience utilisateur.", + "statusPageCreateTabsContentFeaturesDescription": "Voir plus de détails sur la page de statut", + "showCharts": "Voir les graphiques", + "showUptimePercentage": "Voir le pourcentage de temps en ligne", + "removeLogo": "Supprimer le logo", + "statusPageStatus": "Aucune page de statut publique n'est déployée", + "statusPageStatusContactAdmin": "Merci de contacter votre administrateur", + "statusPageStatusNotPublic": "Cette page de statut n'est pas piblique", + "statusPageStatusNoPage": "Il n'y a aucune page de statut ici", + "statusPageStatusServiceStatus": "Statut des services", + "deleteStatusPage": "Voulez-vous supprimer cette page de statut ?", + "deleteStatusPageConfirm": "Oui, supprimer la page de statut", + "deleteStatusPageDescription": "Une fois supprimée, votre page de statut ne pourra plus être récupérée.", + "uptimeCreate": "La valeur attendue est utilisée pour comparer le résultat de la réponse et la correspondance détermine le statut du moniteur.", + "uptimeCreateJsonPath": "Cette expression sera évaluée par rapport aux données JSON de la réponse et le résultat sera utilisé pour la comparaison avec la valeur attendue. Voir", + "uptimeCreateJsonPathQuery": "pour la documentation sur le langage de requête.", + "maintenanceTableActionMenuDialogTitle": "Voulez-vous vraiment supprimer cette fenêtre de maintenance ?", + "infrastructureEditYour": "Modifier votre", + "infrastructureEditMonitor": "Sauvegarder le moniteur d'infrastructure", + "infrastructureMonitorCreated": "Le moniteur d'infrastructure a été créé avec succès !", + "infrastructureMonitorUpdated": "Le moniteur d'infrastructure a été modifié avec succès !", + "errorInvalidTypeId": "Le type de notification fourni est invalide", + "errorInvalidFieldId": "Le champ ID fourni est invalide", + "inviteNoTokenFound": "Aucun jeton d'invitation fourni", + "pageSpeedWarning": "Attention : vous n'avez pas encore ajouté de clé API Google PageSpeed. Sans celle-ci, le moniteur PageSpeed ne fonctionnera pas.", + "pageSpeedLearnMoreLink": "Cliquer ici", + "pageSpeedAddApiKey": "pour ajouter votre clé API.", + "update": "Mettre à jour", + "invalidFileFormat": "Format de fichier non supporté !", + "invalidFileSize": "Le fichier est trop volumineux !", + "ClickUpload": "Cliquez pour téléverser", + "DragandDrop": "glisser et déposer", + "MaxSize": "Taille maximum", + "SupportedFormats": "Formats supportés", + "FirstName": "Prénom", + "LastName": "Nom", + "EmailDescriptionText": "Il s'agit de votre adresse email actuelle : elle ne peut pas être changée.", + "YourPhoto": "Photo de profil", + "PhotoDescriptionText": "La photo sera affichée sur votre page de profil.", + "save": "Enregistrer", + "DeleteDescriptionText": "Cela supprimera le compte et toutes les données associées du serveur. Cette opération est irréversible.", + "DeleteAccountWarning": "Supprimer votre compte signifie que vous ne pourrez pas vous reconnecter et que toutes vos données seront supprimés. Ceci est irréversible.", + "DeleteWarningTitle": "Vous supprimez vraiment ce compte ?", + "bulkImport": { + "title": "Import en masse", + "selectFileTips": "Sélectionnez un fichier CSV pour l'upload", + "selectFileDescription": "Vous pouvez télécharger notre ou exemple.", + "selectFile": "Sélectionner un fichier", + "parsingFailed": "L'analyse du fichier a rencontré un problème", + "uploadSuccess": "Moniteurs créés avec succès !", + "validationFailed": "Echec de la validation", + "noFileSelected": "Aucun fichier sélectionné", + "fallbackPage": "Importer un fichier pour ajouter une liste de serveurs en masse", + "invalidFileType": "Type de fichier invalide", + "uploadFailed": "Le téléchargement a échoué" + }, + "DeleteAccountTitle": "Supprimer le compte", + "DeleteAccountButton": "Supprimer le compte", + "publicLink": "Lien public", + "maskedPageSpeedKeyPlaceholder": "*************************************", + "reset": "Réinitialiser", + "ignoreTLSError": "Ignorer les erreurs TLS/SSL", + "tlsErrorIgnored": "Erreurs TLS/SSL ignorées", + "ignoreTLSErrorDescription": "Ignorer les erreurs TLS/SSL et continuer à vérifier l'accessibilité du site web", + "createNew": "Créer un nouveau", + "greeting": { + "prepend": "Salut !", + "append": "L'après-midi est votre libre, rendons-le épique !", + "overview": "Voici un aperçu de vos moniteurs {{type}}." + }, + "roles": { + "superAdmin": "Super admin", + "admin": "Admin", + "teamMember": "Membre de l'équipe", + "demoUser": "Utilisateur de démonstration" + }, + "teamPanel": { + "teamMembers": "Membres de l'équipe", + "filter": { + "all": "Tous", + "member": "Membre" + }, + "inviteTeamMember": "Inviter un membre de l'équipe", + "inviteNewTeamMember": "Inviter un nouveau membre d'équipe", + "inviteDescription": "Lorsque vous ajoutez un nouveau membre à l'équipe, il aura accès à tous les moniteurs.", + "email": "Email", + "selectRole": "Sélectionnez un rôle", + "inviteLink": "Lien d'invitation", + "cancel": "Annuler", + "noMembers": "Il n'y a aucun membre d'équipe avec ce rôle", + "getToken": "Récupérer un token", + "emailToken": "Token de l'email", + "table": { + "name": "Nom", + "email": "Email", + "role": "Rôle", + "created": "Créée" + } + }, + "monitorState": { + "paused": "Pause", + "resumed": "Reprendre", + "active": "Actif" + }, + "menu": { + "uptime": "Temps en ligne", + "pagespeed": "Vitesse", + "infrastructure": "Infrastructure", + "incidents": "Incidents", + "statusPages": "Pages de statut", + "maintenance": "Maintenance", + "integrations": "Intégrations", + "settings": "Paramètres", + "support": "Support", + "discussions": "Discussions", + "docs": "Documentation", + "changelog": "Changelog", + "profile": "Profil", + "password": "Mot de passe", + "team": "Équipe", + "logOut": "Déconnexion", + "notifications": "Notifications", + "logs": "Journaux" + }, + "settingsEmailUser": "Utilisateur SMTP - Utilisateur pour l'authentification, écrase l'adresse email si spécifiée", + "state": "État", + "statusBreadCrumbsStatusPages": "Pages de statut", + "statusBreadCrumbsDetails": "Détails", + "commonSaving": "Sauvegarde...", + "navControls": "Contrôle", + "incidentsPageTitle": "Incidents", + "passwordPanel": { + "passwordChangedSuccess": "Votre mot de passe a été modifié avec succès.", + "passwordInputIncorrect": "La saisie de votre mot de passe est incorrecte.", + "currentPassword": "Mot de passe actuel", + "enterCurrentPassword": "Entrez votre mot de passe actuel", + "newPassword": "Nouveau mot de passe", + "enterNewPassword": "Entrez votre nouveau mot de passe", + "confirmNewPassword": "Confirmer le nouveau mot de passe", + "passwordRequirements": "Le nouveau mot de passe doit contenir au moins 8 caractères et au moins une lettre majuscule, une lettre minuscule, un chiffre et un caractère spécial.", + "saving": "Sauvegarde..." + }, + "emailSent": "Email envoyé avec succès", + "failedToSendEmail": "Une erreur s'est produite lors de l'envoi de l'email", + "settingsTestEmailSuccess": "Email de test envoyé avec succès", + "settingsTestEmailFailed": "Une erreur s'est produite lors de l'envoi de l'email de test", + "settingsTestEmailFailedWithReason": "Une erreur s'est produite lors de l'envoi de l'email de test : {{reason}}", + "settingsTestEmailUnknownError": "Erreur inconnue", + "statusMsg": { + "paused": "Le moniteur est en pause.", + "up": "Votre site est en ligne.", + "down": "Votre site est hors-ligne.", + "pending": "En attente..." + }, + "uptimeGeneralInstructions": { + "http": "Entrez l'URL ou l'IP du moniteur (par exemple https://exemple.fr ou 192.168.1.100) et ajoutez un nom familier qui apparaîtra sur le tableau de bord.", + "ping": "Entrez l'adresse IP ou le nom d'hôte à tester (par exemple, 192.168.1.100 ou exemple.fr) et ajoutez un nom familier qui apparaîtra sur le tableau de bord.", + "docker": "Entrer l'ID Docker du container. Les identifiants Docker doivent être les 64 caractères de l'ID Docker. Vous pouvez utiliser la commande docker inspect pour avoir l'ID complet.", + "port": "Entrez l'URL ou l'adresse IP du serveur, le numéro de port et un nom d'affichage familier qui apparaîtra sur le tableau de bord." + }, + "common": { + "appName": "Checkmate", + "monitoringAgentName": "Capture", + "buttons": { + "toggleTheme": "Afficher le mode clair & sombre" + }, + "toasts": { + "networkError": "Erreur de réseau", + "checkConnection": "Merci de vérifier votre connexion", + "unknownError": "Erreur inconnue" + } + }, + "auth": { + "common": { + "navigation": { + "continue": "Continuer", + "back": "Retour" + }, + "inputs": { + "email": { + "label": "Email", + "placeholder": "jean.dupont@domaine.fr", + "errors": { + "empty": "Pour continuer, merci d'indiquer votre adresse email", + "invalid": "Merci de vérifier la validité de l'adresse email saisie" + } + }, + "password": { + "label": "Mot de passe", + "rules": { + "length": { + "beginning": "Doit contenir au moins", + "highlighted": "Longueur de 8 caractères" + }, + "special": { + "beginning": "Doit contenir au moins", + "highlighted": "un caractère spécial" + }, + "number": { + "beginning": "Doit contenir au moins", + "highlighted": "un nombre" + }, + "uppercase": { + "beginning": "Doit contenir au moins", + "highlighted": "un caractère majuscule" + }, + "lowercase": { + "beginning": "Doit contenir au moins", + "highlighted": "un caractère miniscule" + }, + "match": { + "beginning": "Confirmer le mot de passe et le mot de passe", + "highlighted": "doit valoir" + } + }, + "errors": { + "empty": "Merci d'entrer votre mot de passe", + "length": "Le mot de passe doit faire 8 caractères minimum", + "uppercase": "Le mot de passe doit contenir au minimum une lettre majuscule", + "lowercase": "Le mot de passe doit contenir au minimum une lettre minuscule", + "number": "Le mot de passe doit contenir au minimum un chiffre", + "special": "Le mot de passe doit contenir au minimum un caractère spécial", + "incorrect": "Le mot de passe fourni est erroné" + } + }, + "passwordConfirm": { + "label": "Confirmer le mot de passe", + "placeholder": "Entrez à nouveau le mot de passe pour confirmer", + "errors": { + "empty": "Merci d'entrer votre mot de passe à nouveau pour le confirmer (aide avec la typo)", + "different": "Les mots de passe indiqués ne sont pas identiques, l'un d'entre eux est probablement mal écrit" + } + }, + "firstName": { + "label": "Prénom", + "placeholder": "Jean", + "errors": { + "empty": "Merci d'entrer votre prénom", + "length": "Le prénom doit être inférieur à 50 caractères", + "pattern": "Le prénom ne peut contenir que des lettres, des espaces, des apostrophes ou des tirets" + } + }, + "lastName": { + "label": "Nom", + "placeholder": "Dupont", + "errors": { + "empty": "Merci d'entrer votre nom", + "length": "Le nom doit être inférieur à 50 caractères", + "pattern": "Le nom ne peut contenir que des lettres, des espaces, des apostrophes ou des tirets" + } + } + }, + "errors": { + "validation": "Une erreur s'est produite lors de la validation des données." + }, + "fields": { + "password": { + "errors": { + "incorrect": "Le mot de passe fourni est erroné" + } + }, + "role": { + "errors": { + "min": "" + } + } + } + }, + "login": { + "heading": "Connexion", + "subheadings": { + "stepOne": "Entrez votre adresse email", + "stepTwo": "Entrez votre mot de passe" + }, + "links": { + "forgotPassword": "Mot de passe oublié ?", + "register": "Pas encore de compte ?", + "forgotPasswordLink": "Réinitialiser le mot de passe", + "registerLink": "Inscrivez vous ici" + }, + "toasts": { + "success": "Bon retour ! Vous êtes connecté avec succès.", + "incorrectPassword": "Mot de passe incorrect" + }, + "errors": { + "password": { + "incorrect": "Le mot de passe fourni est erroné" + } + } + }, + "registration": { + "heading": { + "superAdmin": "Créer un super admin", + "user": "Inscription" + }, + "subheadings": { + "stepOne": "Entrez votre informations personnelles", + "stepTwo": "Entrez votre adresse email", + "stepThree": "Créez votre mot de passe" + }, + "description": { + "superAdmin": "Créer le compte \"SuperAdmin\" pour commencer", + "user": "inscription comme utilisateur et demander à l'administrateur principal l'accès aux moniteurs" + }, + "gettingStartedButton": { + "superAdmin": "Créer un compte super admin", + "user": "Inscription comme utilisateur normal" + }, + "termsAndPolicies": "En créant un compte, vous comprenez et acceptez les Conditions d'Utilisation et la Politique de Confidentialité.", + "links": { + "login": "Déjà inscrit ? Connexion" + }, + "toasts": { + "success": "Bienvenue ! Votre compte a été créé avec succès." + } + }, + "forgotPassword": { + "heading": "Mot de passe oublié ?", + "subheadings": { + "stepOne": "Pas d'inquiétude, nous allons vous envoyer des informations pour réinitialiser votre mot de passe.", + "stepTwo": "Nous avons envoyer un lien de réinitialisation du mot de passe à l'email ", + "stepThree": "Votre nouveau mot de passe doit être différent des précédents.", + "stepFour": "Votre mot de passe a été réinitialisé avec succès. Cliquez ci-dessous pour vous connecter." + }, + "buttons": { + "openEmail": "Ouvrir votre application email", + "resetPassword": "Réinitialiser le mot de passe" + }, + "imageAlts": { + "passwordKey": "Icône du mot de passe", + "email": "Icône de l'email", + "lock": "Icône de verrouillage", + "passwordConfirm": "Icône de confirmation du mot de passe" + }, + "links": { + "login": "Retourner à la page de connexion", + "resend": "Vous n'avez pas reçu l'email ? Cliquez ici pour le renvoyer" + }, + "toasts": { + "sent": "Instructions envoyées sur l'adresse email .", + "emailNotFound": "L'email n'a pas été trouvé.", + "redirect": "Redirection dans ...", + "success": "Votre mot de passe", + "error": "Une erreur s'est produite lors de la réinitialisation du mot de passe. Réessayez ultérieurement ou contactez le support." + } + } + }, + "errorPages": { + "serverUnreachable": { + "toasts": { + "reconnected": "Connexion rétablie avec le serveur.", + "stillUnreachable": "Le serveur est toujours inaccessible. Réessayez ultérieurement." + }, + "alertBox": "Erreur de connexion au serveur", + "description": "Nous ne parvenons pas à nous connecter au serveur. Veuillez vérifier votre connexion Internet ou vérifier votre configuration de déploiement si le problème persiste.", + "retryButton": { + "default": "Réessayer la connexion", + "processing": "Connexion..." + } + } + }, + "createNotifications": { + "title": "Créer un canal de notification", + "nameSettings": { + "title": "Nom", + "description": "Une description pour votre intégration.", + "nameLabel": "Nom", + "namePlaceholder": "e.g. notifications Slack" + }, + "typeSettings": { + "title": "Type", + "description": "Sélectionnez le type de canal de notification que vous souhaitez créer.", + "typeLabel": "Type" + }, + "emailSettings": { + "title": "Email", + "description": "Adresse email du destinataire", + "emailLabel": "Adresse email", + "emailPlaceholder": "e.g. jean@exemple.fr" + }, + "slackSettings": { + "title": "Slack", + "description": "Configurez votre webhook Slack ici", + "webhookLabel": "URL du webhook Slack", + "webhookPlaceholder": "https://hooks.slack.com/services/..." + }, + "pagerdutySettings": { + "title": "PagerDuty", + "description": "Configurez l'intégration PagerDuty ici", + "integrationKeyLabel": "Clé d'intégration", + "integrationKeyPlaceholder": "1234567890" + }, + "discordSettings": { + "title": "Discord", + "description": "Configurez votre webhook Discord ici", + "webhookLabel": "URL du webhook Discord", + "webhookPlaceholder": "https://your-server.com/webhook" + }, + "webhookSettings": { + "title": "Webhook", + "description": "Configurez votre webhook ici", + "webhookLabel": "URL du webhook", + "webhookPlaceholder": "https://your-server.com/webhook" + }, + "testNotification": "Notification de test", + "dialogDeleteTitle": "Voulez-vous vraiment supprimer cette notification ?", + "dialogDeleteConfirm": "Supprimer" + }, + "notificationConfig": { + "title": "Notifications", + "description": "Sélectionnez les canaux de notification que vous souhaitez utiliser" + }, + "monitorStatus": { + "checkingEvery": "Vérifier toutes les {{interval}}", + "withCaptureAgent": "avec l'agent Capture {{version}}", + "up": "en ligne", + "down": "hors ligne", + "paused": "en pause" + }, + "advancedMatching": "Filtrage avancé", + "sendTestNotifications": "Envoyer une notification de test", + "selectAll": "Sélectionner tout", + "showAdminLoginLink": "Voir le lien \"Administrateur ? Connectez-vous ici\" sur la page de statut", + "logsPage": { + "title": "Journaux", + "description": "Journaux systèmes - 1000 dernières lignes", + "tabs": { + "queue": "File d'attente des tâches", + "logs": "Journaux des serveurs", + "diagnostics": "Diagnostic" + }, + "toast": { + "fetchLogsSuccess": "Journaux récupérés avec succès" + }, + "logLevelSelect": { + "title": "Niveau de journalisation", + "values": { + "all": "Tout", + "info": "Info", + "warn": "Attention", + "error": "Erreur", + "debug": "Debug" + } + } + }, + "queuePage": { + "title": "File d'attente", + "refreshButton": "Rafraîchir", + "flushButton": "Vider la file d'attente", + "jobTable": { + "title": "Tâches actuellement dans la file d'attente", + "idHeader": "ID du moniteur", + "urlHeader": "URL", + "typeHeader": "Type", + "activeHeader": "Actif", + "lockedAtHeader": "Verrouillé le", + "runCountHeader": "Nombre de tentatives", + "failCountHeader": "Nombre d'échecs", + "lastRunHeader": "Dernière tentative le", + "lastFinishedAtHeader": "Dernière finalisation à", + "lastRunTookHeader": "Dernière exécution effectuée" + }, + "metricsTable": { + "title": "Métriques des files", + "metricHeader": "Métrique", + "valueHeader": "Valeur" + }, + "failedJobTable": { + "title": "Tâches en échec", + "monitorIdHeader": "ID du moniteur", + "monitorUrlHeader": "URL du moniteur", + "failCountHeader": "Nombre de tentatives échouées", + "failedAtHeader": "Dernier échec à", + "failReasonHeader": "Raison de l'échec" + } + }, + "export": { + "title": "Export des moniteurs", + "success": "Les moniteurs ont été exportés avec succès !", + "failed": "Une erreur s'est produite lors de l'export des moniteurs" + }, + "monitorActions": { + "title": "Export/Import", + "import": "Importer des moniteurs", + "export": "Exporter des moniteurs", + "deleteSuccess": "Le moniteur a été supprimé avec succès", + "deleteFailed": "Une erreur s'est produite lors de la suppression du moniteur", + "details": "Détails" + }, + "settingsPage": { + "aboutSettings": { + "labelDevelopedBy": "Développé par Bluewave Labs", + "labelVersion": "Version", + "title": "A propos" + }, + "demoMonitorsSettings": { + "buttonAddMonitors": "Ajouter un moniteur de démonstration", + "description": "Ajouter un moniteur à des fins de démonstration.", + "title": "Moniteurs de démonstration" + }, + "emailSettings": { + "buttonSendTestEmail": "Envoyer un mail de test", + "description": "Configurez les paramètres emails pour le système. Il s'agit du système utilisé pour les notifications & alertes.", + "descriptionTransport": "Ceci créé un transport SMTP pour NodeMailer", + "labelAddress": "Adresse email - utilisée pour la connexion", + "labelConnectionHost": "Hôte SMTP - Nom d'hôte à utiliser dans l'introduction HELO/EHLO", + "labelHost": "Hôte SMTP - Nom d'hôte ou IP sur lequel se connecter", + "labelIgnoreTLS": "Désactiver STARTTLS : ne pas utiliser TLS même si le serveur le supporte", + "labelPassword": "Mot de passe SMTP - Mot de passe requis pour l'authentification", + "labelPasswordSet": "Le mot de passe est renseigné. Réinitialisez le pour le changer.", + "labelPool": "Activer le pool de connexion SMTP afin de réutiliser les connexions existantes pour améliorer la performance", + "labelPort": "Port SMTP - Port auquel se connecter", + "labelRejectUnauthorized": "Rejeter les certificats non valides : rejeter les connexions avec des certificats auto-signés ou non fiables", + "labelRequireTLS": "Forcer STARTTLS : nécessite la mise à niveau TLS, échouer si ce n'est pas supporté", + "labelSecure": "Utiliser le SSL (recommandé) : chiffrer la connexion SSL/TLS", + "labelTLSServername": "Nom du serveur TLS - Nom d'hôte facultatif pour la validation TLS lorsque l'hôte est une adresse IP", + "labelUser": "Utilisateur SMTP - Nom d'utilisateur pour l'authentification, remplace l'adresse email si spécifié", + "linkTransport": "Voir les spécifications ici", + "placeholderUser": "Laisser vide si non requis", + "title": "Email", + "toastEmailRequiredFieldsError": "L'adresse email, l'hôte, le port et le mot de passe sont obligatoires." + }, + "pageSpeedSettings": { + "description": "Entrez votre clé API Google PageSpeed pour activer la surveillance Google PageSpeed. Cliquez sur Réinitialiser pour mettre à jour la clé.", + "labelApiKeySet": "La clé API est générée. Cliquez sur \"Réinitialiser\" pour la modifier.", + "labelApiKey": "Clé API PageSpeed", + "title": "Clé API Google PageSpeed" + }, + "saveButtonLabel": "Sauvegarder", + "statsSettings": { + "clearAllStatsButton": "Supprimer toutes les statistiques", + "clearAllStatsDescription": "Supprimer toutes les statistiques. Cette action est irréversible.", + "clearAllStatsDialogConfirm": "Oui, supprimer toutes les statistiques", + "clearAllStatsDialogDescription": "Une fois supprimés, l'historique et les statistiques du moniteur ne peuvent plus être récupérés.", + "clearAllStatsDialogTitle": "Voulez-vous supprimer toutes les statistiques ?", + "description": "Définissez la durée pendant laquelle vous souhaitez conserver les données historiques. Vous pouvez également effacer toutes les données existantes.", + "labelTTL": "Le nombre de jours dont vous souhaitez garder l'historique.", + "labelTTLOptional": "0 pour une durée infinie", + "title": "Historique du moniteur" + }, + "systemResetSettings": { + "buttonRemoveAllMonitors": "Supprimer tous les moniteurs", + "description": "Supprimer toutes les moniteurs du système.", + "dialogConfirm": "Oui, supprimer tous les moniteurs", + "dialogDescription": "Une fois supprimés, les moniteurs ne peuvent plus être récupérés.", + "dialogTitle": "Voulez-vous supprimer tous les moniteurs ?", + "title": "Réinitialisation du système" + }, + "timezoneSettings": { + "description": "Sélectionnez le fuseau horaire utilisé pour afficher les dates et les heures dans l'application.", + "label": "Fuseau horaire d'affichage", + "title": "Fuseau horaire d'affichage" + }, + "title": "Paramètres", + "uiSettings": { + "description": "Changer entre le mode clair et sombre, ou changer le langage de l'interface utilisateur.", + "labelLanguage": "Langage", + "labelTheme": "Thème", + "title": "Apparence" + }, + "urlSettings": { + "description": "Affichez l'adresse IP ou l'URL du moniteur sur la page de statut publique. Si cette option est désactivée, seul le nom du moniteur sera affiché afin de protéger les informations sensibles.", + "label": "Afficher l'IP/URL sur la page de statut", + "selectDisabled": "Désactivé", + "selectEnabled": "Activé", + "title": "IP/URL du moniteur sur la page de statut" + } + }, + "statusPageCreate": { + "buttonSave": "Sauvegarder" + }, + "incidentsOptionsHeaderFilterResolved": "Résolu", + "settingsSave": "Sauvegarder", + "statusPageCreateAppearanceTitle": "Apparence", + "confirmPassword": "Confirmer le mot de passe", + "monitorHooks": { + "failureAddDemoMonitors": "Une erreur s'est produite lors de l'ajout des moniteurs de démonstration", + "successAddDemoMonitors": "Les moniteurs de démonstration ont bien été ajoutés" + }, + "settingsAppearance": "Apparance", + "settingsDisplayTimezone": "Fuseau horaire d'affichage", + "settingsGeneralSettings": "Paramètres généraux", + "incidentsOptionsHeaderTotalIncidents": "Nombre d'incidents", + "statusPage": { + "deleteSuccess": "La page de statut a été supprimée avec succès", + "deleteFailed": "Une erreur s'est produite lors de l'ajout de la page de statut", + "createSuccess": "La page de statut a été ajoutée avec succès", + "updateSuccess": "La page de statut a été modifiée avec succès", + "generalSettings": "Paramètres généraux", + "contents": "Contenus", + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "testNotificationsDisabled": "Il n'y aucune notification paramétrée pour ce moniteur. Vous pouvez en ajouter une en cliquant sur le bouton 'Configurer'", + "incidentsTableResolvedAt": "Résolu le", + "incidentsTableActionResolve": "Résoudre", + "checkHooks": { + "failureResolveOne": "Une erreur s'est produite lors de la résolution de l'incident.", + "failureResolveAll": "Une erreur s'est produite lors de la résolution des incidents.", + "failureResolveMonitor": "" + }, + "checkFormError": "Merci de vérifier les erreurs du formulaire", + "diagnosticsPage": { + "diagnosticDescription": "Diagnostic du système", + "statsDescription": "Statistiques du système", + "gauges": { + "heapAllocationTitle": "Allocation du tas", + "heapAllocationSubtitle": "% de mémoire disponible", + "heapUsageTitle": "Utilisation du tas", + "heapUsageSubtitle": "% de mémoire disponible", + "heapUtilizationTitle": "Utilisation du tas", + "heapUtilizationSubtitle": "% d'allocation", + "instantCpuUsageTitle": "Utilisation instantanée du CPU", + "instantCpuUsageSubtitle": "% de CPU utilisé" + }, + "stats": { + "eventLoopDelayTitle": "Délai de la boucle d'évènements", + "uptimeTitle": "Temps en ligne", + "usedHeapSizeTitle": "Taille du tas utilisé", + "totalHeapSizeTitle": "Taille total du tas", + "osMemoryLimitTitle": "Limite de mémoire de l'OS" + } + }, + "pageSpeedLighthouseAPI": "Utilisez l'API Lighthouse PageSpeed pour suivre votre site web", + "time": { + "threeMinutes": "3 minutes", + "fiveMinutes": "5 minutes", + "tenMinutes": "10 minutes", + "twentyMinutes": "20 minutes", + "oneHour": "1 heure", + "oneDay": "1 jour", + "oneWeek": "1 semaine" + }, + "general": { + "noOptionsFound": "" + }, + "infrastructureMonitor": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "maintenanceWindow": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "pageSpeed": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "uptimeMonitor": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "editUserPage": { + "form": { + "email": "", + "firstName": "", + "lastName": "", + "role": "", + "save": "" + }, + "table": { + "actionHeader": "", + "roleHeader": "" + }, + "title": "", + "toast": { + "successUserUpdate": "", + "validationErrors": "" + } + }, + "incidentsPageActionResolveMonitor": "", + "incidentsPageActionResolveAll": "" +} diff --git a/client/src/locales/ja.json b/client/src/locales/ja.json index b3b8fc4ce..782fdd9e0 100644 --- a/client/src/locales/ja.json +++ b/client/src/locales/ja.json @@ -1,836 +1,973 @@ { - "submit": "", - "title": "", - "distributedStatusHeaderText": "", - "distributedStatusSubHeaderText": "", - "settingsDisabled": "", - "settingsSuccessSaved": "", - "settingsFailedToSave": "", - "settingsStatsCleared": "", - "settingsFailedToClearStats": "", - "settingsFailedToAddDemoMonitors": "", - "settingsMonitorsDeleted": "", - "settingsFailedToDeleteMonitors": "", - "starPromptTitle": "", - "starPromptDescription": "", - "https": "", - "http": "", - "monitor": "", - "aboutus": "", - "now": "", - "delete": "", - "configure": "", - "responseTime": "", - "ms": "", - "bar": "", - "area": "", - "country": "", - "city": "", - "response": "", - "monitorStatusUp": "", - "monitorStatusDown": "", - "webhookSendSuccess": "", - "webhookSendError": "", - "webhookUnsupportedPlatform": "", - "distributedRightCategoryTitle": "", - "distributedStatusServerMonitors": "", - "distributedStatusServerMonitorsDescription": "", - "distributedUptimeCreateSelectURL": "", - "distributedUptimeCreateChecks": "", - "distributedUptimeCreateChecksDescription": "", - "distributedUptimeCreateIncidentNotification": "", - "distributedUptimeCreateIncidentDescription": "", - "distributedUptimeCreateAdvancedSettings": "", - "distributedUptimeDetailsNoMonitorHistory": "", - "distributedUptimeDetailsStatusHeaderUptime": "", - "distributedUptimeDetailsStatusHeaderLastUpdate": "", + "submit": "送信", + "title": "タイトル", + "distributedStatusHeaderText": "リアルタイム、実デバイス対応", + "distributedStatusSubHeaderText": "世界中の数百万台のデバイスを活用し、グローバル地域、国、都市別にシステムパフォーマンスを表示", + "settingsDisabled": "無効", + "settingsSuccessSaved": "設定が正常に保存されました", + "settingsFailedToSave": "設定の保存に失敗しました", + "settingsStatsCleared": "統計が正常にクリアされました", + "settingsFailedToClearStats": "統計のクリアに失敗しました", + "settingsMonitorsDeleted": "すべてのモニターが正常に削除されました", + "settingsFailedToDeleteMonitors": "すべてのモニターの削除に失敗しました", + "starPromptTitle": "Checkmateにスター", + "starPromptDescription": "GitHubで最新リリースを確認し、コミュニティの成長を支援", + "https": "HTTPS", + "http": "HTTP", + "monitor": "モニター", + "aboutus": "私たちについて", + "now": "今", + "delete": "削除", + "configure": "設定", + "responseTime": "レスポンス時間", + "ms": "ミリ秒", + "bar": "バー", + "area": "エリア", + "country": "国", + "city": "都市", + "response": "レスポンス", + "monitorStatusUp": "モニター {name} ({url}) がアップして応答中です", + "monitorStatusDown": "モニター {name} ({url}) がダウンして応答していません", + "webhookSendSuccess": "Webhook通知が正常に送信されました", + "webhookSendError": "{platform}へのWebhook通知送信エラー", + "webhookUnsupportedPlatform": "サポートされていないプラットフォーム: {platform}", + "distributedRightCategoryTitle": "モニター", + "distributedStatusServerMonitors": "サーバーモニター", + "distributedStatusServerMonitorsDescription": "関連サーバーのステータスを監視", + "distributedUptimeCreateSelectURL": "ここでホストのURL、モニタータイプを選択できます。", + "distributedUptimeCreateChecks": "実行するチェック", + "distributedUptimeCreateChecksDescription": "サイト追加後はいつでもチェックを追加・削除できます。", + "distributedUptimeCreateIncidentNotification": "インシデント通知", + "distributedUptimeCreateIncidentDescription": "インシデント発生時、ユーザーに通知。", + "distributedUptimeCreateAdvancedSettings": "詳細設定", + "distributedUptimeDetailsNoMonitorHistory": "このモニターのチェック履歴はまだありません。", + "distributedUptimeDetailsStatusHeaderUptime": "稼働時間:", + "distributedUptimeDetailsStatusHeaderLastUpdate": "最終更新", "notifications": { - "enableNotifications": "", - "testNotification": "", - "addOrEditNotifications": "", + "enableNotifications": "{{platform}}通知を有効化", + "testNotification": "テスト通知", + "addOrEditNotifications": "通知を追加または編集", "slack": { - "label": "", - "description": "", - "webhookLabel": "", - "webhookPlaceholder": "", - "webhookRequired": "" + "label": "Slack", + "description": "Slack通知を有効にするには、Slackアプリを作成し、incoming webhookを有効にします。その後、ここでWebhook URLを提供するだけです。", + "webhookLabel": "Webhook URL", + "webhookPlaceholder": "https://hooks.slack.com/services/...", + "webhookRequired": "Slack Webhook URLが必要です" }, "discord": { - "label": "", - "description": "", - "webhookLabel": "", - "webhookPlaceholder": "", - "webhookRequired": "" + "label": "Discord", + "description": "Webhookを使用してDiscord通知経由でCheckmateからDiscordチャンネルにデータを送信するには、DiscordのIncoming Webhook機能を使用できます。", + "webhookLabel": "Discord Webhook URL", + "webhookPlaceholder": "https://discord.com/api/webhooks/...", + "webhookRequired": "Discord Webhook URLが必要です" }, "telegram": { - "label": "", - "description": "", - "tokenLabel": "", - "tokenPlaceholder": "", - "chatIdLabel": "", - "chatIdPlaceholder": "", - "fieldsRequired": "" + "label": "Telegram", + "description": "Telegram通知を有効にするには、Telegramボットを作成・管理するための公式ボットであるBotFatherを使用してTelegramボットを作成します。次に、APIトークンとチャットIDを取得してここに記入します。", + "tokenLabel": "ボットトークン", + "tokenPlaceholder": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", + "chatIdLabel": "チャットID", + "chatIdPlaceholder": "-1001234567890", + "fieldsRequired": "TelegramトークンとチャットIDが必要です" }, "webhook": { - "label": "", - "description": "", - "urlLabel": "", - "urlPlaceholder": "", - "urlRequired": "" + "label": "Webhook", + "description": "インシデント発生時に通知を受信するためのカスタムWebhookを設定できます。", + "urlLabel": "Webhook URL", + "urlPlaceholder": "https://your-server.com/webhook", + "urlRequired": "Webhook URLが必要です" }, - "testNotificationDevelop": "", - "integrationButton": "", - "testSuccess": "", - "testFailed": "", - "unsupportedType": "", - "networkError": "", + "testNotificationDevelop": "テスト通知2", + "integrationButton": "通知統合", + "testSuccess": "テスト通知が正常に送信されました!", + "testFailed": "テスト通知の送信に失敗しました", + "unsupportedType": "サポートされていない通知タイプ", + "networkError": "ネットワークエラーが発生しました", "fallback": { - "title": "", - "checks": [""] + "title": "通知チャンネル", + "checks": [ + "ダウンタイムやパフォーマンス問題についてチームにアラート", + "インシデント発生時にエンジニアに通知", + "システム変更について管理者に情報提供" + ], + "actionButton": "" }, - "createButton": "", - "createTitle": "", + "createButton": "通知チャンネルを作成", + "createTitle": "通知チャンネル", "create": { - "success": "", - "failed": "" + "success": "通知が正常に作成されました", + "failed": "通知の作成に失敗しました" }, "fetch": { - "success": "", - "failed": "" + "success": "通知が正常に取得されました", + "failed": "通知の取得に失敗しました" }, "delete": { - "success": "", - "failed": "" + "success": "通知が正常に削除されました", + "failed": "通知の削除に失敗しました" }, "edit": { - "success": "", - "failed": "" + "success": "通知が正常に更新されました", + "failed": "通知の更新に失敗しました" }, "test": { - "success": "", - "failed": "" + "success": "テスト通知が正常に送信されました", + "failed": "テスト通知の送信に失敗しました" } }, - "testLocale": "", - "add": "", - "monitors": "", - "distributedUptimeStatusCreateStatusPage": "", - "distributedUptimeStatusCreateStatusPageAccess": "", - "distributedUptimeStatusCreateStatusPageReady": "", - "distributedUptimeStatusBasicInfoHeader": "", - "distributedUptimeStatusBasicInfoDescription": "", - "distributedUptimeStatusLogoHeader": "", - "distributedUptimeStatusLogoDescription": "", - "distributedUptimeStatusLogoUploadButton": "", - "distributedUptimeStatusStandardMonitorsHeader": "", - "distributedUptimeStatusStandardMonitorsDescription": "", - "distributedUptimeStatusCreateYour": "", - "distributedUptimeStatusEditYour": "", - "distributedUptimeStatusPublishedLabel": "", - "distributedUptimeStatusCompanyNameLabel": "", - "distributedUptimeStatusPageAddressLabel": "", - "distributedUptimeStatus30Days": "", - "distributedUptimeStatus60Days": "", - "distributedUptimeStatus90Days": "", - "distributedUptimeStatusPageNotSetUp": "", - "distributedUptimeStatusContactAdmin": "", - "distributedUptimeStatusPageNotPublic": "", - "distributedUptimeStatusPageDeleteDialog": "", - "distributedUptimeStatusPageDeleteConfirm": "", - "distributedUptimeStatusPageDeleteDescription": "", - "distributedUptimeStatusDevices": "", - "distributedUptimeStatusUpt": "", - "distributedUptimeStatusUptBurned": "", - "distributedUptimeStatusUptLogo": "", - "incidentsTableNoIncidents": "", - "incidentsTablePaginationLabel": "", - "incidentsTableMonitorName": "", - "incidentsTableStatus": "", - "incidentsTableDateTime": "", - "incidentsTableStatusCode": "", - "incidentsTableMessage": "", - "incidentsOptionsHeader": "", - "incidentsOptionsHeaderFilterBy": "", - "incidentsOptionsHeaderFilterAll": "", - "incidentsOptionsHeaderFilterDown": "", - "incidentsOptionsHeaderFilterCannotResolve": "", - "incidentsOptionsHeaderShow": "", - "incidentsOptionsHeaderLastHour": "", - "incidentsOptionsHeaderLastDay": "", - "incidentsOptionsHeaderLastWeek": "", - "incidentsOptionsPlaceholderAllServers": "", - "infrastructureCreateYour": "", - "infrastructureCreateGeneralSettingsDescription": "", - "infrastructureServerRequirement": "", - "infrastructureCustomizeAlerts": "", - "infrastructureAlertNotificationDescription": "", - "infrastructureCreateMonitor": "", - "infrastructureProtocol": "", - "infrastructureServerUrlLabel": "", - "infrastructureDisplayNameLabel": "", - "infrastructureAuthorizationSecretLabel": "", - "gb": "", - "mb": "", - "mem": "", - "memoryUsage": "", - "cpu": "", - "cpuUsage": "", - "cpuTemperature": "", - "diskUsage": "", - "used": "", - "total": "", - "cores": "", - "frequency": "", - "status": "", - "cpuPhysical": "", - "cpuLogical": "", - "cpuFrequency": "", - "avgCpuTemperature": "", - "memory": "", - "disk": "", - "uptime": "", - "os": "", - "host": "", - "actions": "", - "integrations": "", - "integrationsPrism": "", - "integrationsSlack": "", - "integrationsSlackInfo": "", - "integrationsDiscord": "", - "integrationsDiscordInfo": "", - "integrationsZapier": "", - "integrationsZapierInfo": "", - "commonSave": "", - "createYour": "", - "createMonitor": "", - "pause": "", - "resume": "", - "editing": "", - "url": "", - "access": "", - "timezone": "", - "features": "", - "administrator": "", - "loginHere": "", - "displayName": "", - "urlMonitor": "", - "portToMonitor": "", - "websiteMonitoring": "", - "websiteMonitoringDescription": "", - "pingMonitoring": "", - "pingMonitoringDescription": "", - "dockerContainerMonitoring": "", - "dockerContainerMonitoringDescription": "", - "portMonitoring": "", - "portMonitoringDescription": "", - "createMaintenanceWindow": "", - "createMaintenance": "", - "editMaintenance": "", - "maintenanceWindowName": "", - "friendlyNameInput": "", - "friendlyNamePlaceholder": "", - "maintenanceRepeat": "", - "maintenance": "", - "duration": "", - "addMonitors": "", - "window": "", - "cancel": "", - "message": "", - "low": "", - "high": "", - "statusCode": "", - "date&Time": "", - "type": "", - "statusPageName": "", - "publicURL": "", - "repeat": "", - "edit": "", - "createA": "", - "remove": "", - "maintenanceWindowDescription": "", - "startTime": "", - "timeZoneInfo": "", - "monitorsToApply": "", - "nextWindow": "", - "notFoundButton": "", - "pageSpeedConfigureSettingsDescription": "", - "monitorDisplayName": "", - "whenNewIncident": "", - "notifySMS": "", - "notifyEmails": "", - "seperateEmails": "", - "checkFrequency": "", - "matchMethod": "", - "expectedValue": "", - "deleteDialogTitle": "", - "deleteDialogDescription": "", - "pageSpeedMonitor": "", - "shown": "", - "ago": "", - "companyName": "", - "pageSpeedDetailsPerformanceReport": "", - "pageSpeedDetailsPerformanceReportCalculator": "", - "checkingEvery": "", - "statusPageCreateSettings": "", - "basicInformation": "", - "statusPageCreateBasicInfoDescription": "", - "statusPageCreateSelectTimeZoneDescription": "", - "statusPageCreateAppearanceDescription": "", - "statusPageCreateSettingsCheckboxLabel": "", - "statusPageCreateBasicInfoStatusPageAddress": "", - "statusPageCreateTabsContent": "", - "statusPageCreateTabsContentDescription": "", - "statusPageCreateTabsContentFeaturesDescription": "", - "showCharts": "", - "showUptimePercentage": "", - "removeLogo": "", - "statusPageStatus": "", - "statusPageStatusContactAdmin": "", - "statusPageStatusNotPublic": "", - "statusPageStatusNoPage": "", - "statusPageStatusServiceStatus": "", - "deleteStatusPage": "", - "deleteStatusPageConfirm": "", - "deleteStatusPageDescription": "", - "uptimeCreate": "", - "uptimeCreateJsonPath": "", - "uptimeCreateJsonPathQuery": "", - "maintenanceTableActionMenuDialogTitle": "", - "infrastructureEditYour": "", - "infrastructureEditMonitor": "", - "infrastructureMonitorCreated": "", - "infrastructureMonitorUpdated": "", - "errorInvalidTypeId": "", - "errorInvalidFieldId": "", - "inviteNoTokenFound": "", - "pageSpeedWarning": "", - "pageSpeedLearnMoreLink": "", - "pageSpeedAddApiKey": "", - "update": "", - "invalidFileFormat": "", - "invalidFileSize": "", - "ClickUpload": "", - "DragandDrop": "", - "MaxSize": "", - "SupportedFormats": "", - "FirstName": "", - "LastName": "", - "EmailDescriptionText": "", - "YourPhoto": "", - "PhotoDescriptionText": "", - "save": "", - "DeleteDescriptionText": "", - "DeleteAccountWarning": "", - "DeleteWarningTitle": "", + "testLocale": "テストロケール", + "add": "追加", + "monitors": "モニター", + "distributedUptimeStatusCreateStatusPage": "ステータスページ", + "distributedUptimeStatusCreateStatusPageAccess": "アクセス", + "distributedUptimeStatusCreateStatusPageReady": "ステータスページの準備ができたら、公開済みとしてマークできます。", + "distributedUptimeStatusBasicInfoHeader": "基本情報", + "distributedUptimeStatusBasicInfoDescription": "会社名とステータスページが指すサブドメインを定義。", + "distributedUptimeStatusLogoHeader": "ロゴ", + "distributedUptimeStatusLogoDescription": "ステータスページ用のロゴをアップロード", + "distributedUptimeStatusLogoUploadButton": "ロゴをアップロード", + "distributedUptimeStatusStandardMonitorsHeader": "標準モニター", + "distributedUptimeStatusStandardMonitorsDescription": "標準モニターをステータスページに添付。", + "distributedUptimeStatusCreateYour": "作成する", + "distributedUptimeStatusEditYour": "編集する", + "distributedUptimeStatusPublishedLabel": "公開済みで一般に表示", + "distributedUptimeStatusCompanyNameLabel": "会社名", + "distributedUptimeStatusPageAddressLabel": "ステータスページアドレス", + "distributedUptimeStatus30Days": "30日", + "distributedUptimeStatus60Days": "60日", + "distributedUptimeStatus90Days": "90日", + "distributedUptimeStatusPageNotSetUp": "ステータスページが設定されていません。", + "distributedUptimeStatusContactAdmin": "管理者にお問い合わせください", + "distributedUptimeStatusPageNotPublic": "このステータスページは非公開です。", + "distributedUptimeStatusPageDeleteDialog": "このステータスページを削除しますか?", + "distributedUptimeStatusPageDeleteConfirm": "はい、ステータスページを削除", + "distributedUptimeStatusPageDeleteDescription": "削除されると、ステータスページは復元できません。", + "distributedUptimeStatusDevices": "デバイス", + "distributedUptimeStatusUpt": "UPT", + "distributedUptimeStatusUptBurned": "UPT消費", + "distributedUptimeStatusUptLogo": "Uptロゴ", + "incidentsTableNoIncidents": "記録されたインシデントはありません", + "incidentsTablePaginationLabel": "インシデント", + "incidentsTableMonitorName": "モニター名", + "incidentsTableStatus": "ステータス", + "incidentsTableDateTime": "日付と時刻", + "incidentsTableStatusCode": "ステータスコード", + "incidentsTableMessage": "メッセージ", + "incidentsOptionsHeader": "インシデント対象:", + "incidentsOptionsHeaderFilterBy": "フィルター:", + "incidentsOptionsHeaderFilterAll": "すべて", + "incidentsOptionsHeaderFilterDown": "ダウン", + "incidentsOptionsHeaderFilterCannotResolve": "解決不可", + "incidentsOptionsHeaderShow": "表示:", + "incidentsOptionsHeaderLastHour": "最終時間", + "incidentsOptionsHeaderLastDay": "最終日", + "incidentsOptionsHeaderLastWeek": "最終週", + "incidentsOptionsPlaceholderAllServers": "すべてのサーバー", + "infrastructureCreateYour": "作成する", + "infrastructureCreateGeneralSettingsDescription": "ここでホストのURL、フレンドリーネーム、サーバーエージェントに接続するための認証シークレットを選択できます。", + "infrastructureServerRequirement": "監視中のサーバーで実行されている必要があります", + "infrastructureCustomizeAlerts": "アラートをカスタマイズ", + "infrastructureAlertNotificationDescription": "しきい値が指定された割合を超えた場合にユーザーに通知を送信。", + "infrastructureCreateMonitor": "インフラストラクチャモニターを作成", + "infrastructureProtocol": "プロトコル", + "infrastructureServerUrlLabel": "サーバーURL", + "infrastructureDisplayNameLabel": "表示名", + "infrastructureAuthorizationSecretLabel": "認証シークレット", + "gb": "GB", + "mb": "MB", + "mem": "メモリ", + "memoryUsage": "メモリ使用率", + "cpu": "CPU", + "cpuUsage": "CPU使用率", + "cpuTemperature": "CPU温度", + "diskUsage": "ディスク使用率", + "used": "使用済み", + "total": "合計", + "cores": "コア", + "frequency": "頻度", + "status": "ステータス", + "cpuPhysical": "CPU(物理)", + "cpuLogical": "CPU(論理)", + "cpuFrequency": "CPU周波数", + "avgCpuTemperature": "平均CPU温度", + "memory": "メモリ", + "disk": "ディスク", + "uptime": "稼働時間", + "os": "OS", + "host": "ホスト", + "actions": "アクション", + "integrations": "統合", + "integrationsPrism": "Prismをお気に入りのサービスに接続。", + "integrationsSlack": "Slack", + "integrationsSlackInfo": "Slackと接続してチャンネルでインシデントを表示", + "integrationsDiscord": "Discord", + "integrationsDiscordInfo": "Discordと接続してチャンネルでインシデントを直接表示", + "integrationsZapier": "Zapier", + "integrationsZapierInfo": "すべてのインシデントをZapierに送信し、どこでも表示", + "commonSave": "保存", + "createYour": "作成する", + "createMonitor": "モニターを作成", + "pause": "一時停止", + "resume": "再開", + "editing": "編集中...", + "url": "URL", + "access": "アクセス", + "timezone": "タイムゾーン", + "features": "機能", + "administrator": "管理者?", + "loginHere": "こちらでログイン", + "displayName": "表示名", + "urlMonitor": "監視するURL", + "portToMonitor": "監視するポート", + "websiteMonitoring": "ウェブサイト監視", + "websiteMonitoringDescription": "HTTP(s)を使用してウェブサイトやAPIエンドポイントを監視。", + "pingMonitoring": "Ping監視", + "pingMonitoringDescription": "サーバーが利用可能かどうかをチェック。", + "dockerContainerMonitoring": "Dockerコンテナ監視", + "dockerContainerMonitoringDescription": "Dockerコンテナが実行中かどうかをチェック。", + "portMonitoring": "ポート監視", + "portMonitoringDescription": "ポートが開いているかどうかをチェック。", + "createMaintenanceWindow": "メンテナンスウィンドウを作成", + "createMaintenance": "メンテナンスを作成", + "editMaintenance": "メンテナンスを編集", + "maintenanceWindowName": "メンテナンスウィンドウ名", + "friendlyNameInput": "フレンドリーネーム", + "friendlyNamePlaceholder": "__:__で___分間のメンテナンス", + "maintenanceRepeat": "メンテナンス繰り返し", + "maintenance": "メンテナンス", + "duration": "期間", + "addMonitors": "モニターを追加", + "window": "ウィンドウ", + "cancel": "キャンセル", + "message": "メッセージ", + "low": "低", + "high": "高", + "statusCode": "ステータスコード", + "date&Time": "日付と時刻", + "type": "タイプ", + "statusPageName": "ステータスページ名", + "publicURL": "公開URL", + "repeat": "繰り返し", + "edit": "編集", + "createA": "作成する", + "remove": "削除", + "maintenanceWindowDescription": "この時間帯はpingが送信されません", + "startTime": "開始時刻", + "timeZoneInfo": "すべての日付と時刻はGMT+0タイムゾーンです。", + "monitorsToApply": "メンテナンスウィンドウを適用するモニター", + "nextWindow": "次のウィンドウ", + "notFoundButton": "メインダッシュボードに移動", + "pageSpeedConfigureSettingsDescription": "ここでホストのURL、モニタータイプを選択できます。", + "monitorDisplayName": "モニター表示名", + "whenNewIncident": "新しいインシデントが発生した場合、", + "notifySMS": "SMS経由で通知(近日公開)", + "notifyEmails": "複数のアドレスにメールでも通知(近日公開)", + "seperateEmails": "複数のメールはカンマで区切ることができます", + "checkFrequency": "チェック頻度", + "matchMethod": "マッチ方法", + "expectedValue": "期待値", + "deleteDialogTitle": "本当にこのモニターを削除しますか?", + "deleteDialogDescription": "削除されると、このモニターは復元できません。", + "pageSpeedMonitor": "PageSpeedモニター", + "shown": "表示", + "ago": "前", + "companyName": "会社名", + "pageSpeedDetailsPerformanceReport": "値は推定値で変動する可能性があります。", + "pageSpeedDetailsPerformanceReportCalculator": "計算機を見る", + "checkingEvery": "チェック間隔", + "statusPageCreateSettings": "ステータスページの準備ができたら、公開済みとしてマークできます。", + "basicInformation": "基本情報", + "statusPageCreateBasicInfoDescription": "会社名とステータスページが指すサブドメインを定義。", + "statusPageCreateSelectTimeZoneDescription": "ステータスページが表示されるタイムゾーンを選択。", + "statusPageCreateAppearanceDescription": "公開ステータスページのデフォルトの外観を定義。", + "statusPageCreateSettingsCheckboxLabel": "公開済みで一般に表示", + "statusPageCreateBasicInfoStatusPageAddress": "ステータスページアドレス", + "statusPageCreateTabsContent": "ステータスページサーバー", + "statusPageCreateTabsContentDescription": "監視している任意の数のサーバーをステータスページに追加できます。最適な表示体験のために並び替えることもできます。", + "statusPageCreateTabsContentFeaturesDescription": "ステータスページでより多くの詳細を表示", + "showCharts": "チャートを表示", + "showUptimePercentage": "稼働率を表示", + "removeLogo": "ロゴを削除", + "statusPageStatus": "公開ステータスページが設定されていません。", + "statusPageStatusContactAdmin": "管理者にお問い合わせください", + "statusPageStatusNotPublic": "このステータスページは非公開です。", + "statusPageStatusNoPage": "ここにはステータスページがありません。", + "statusPageStatusServiceStatus": "サービスステータス", + "deleteStatusPage": "このステータスページを削除しますか?", + "deleteStatusPageConfirm": "はい、ステータスページを削除", + "deleteStatusPageDescription": "削除されると、ステータスページは復元できません。", + "uptimeCreate": "期待値はレスポンス結果と照合するために使用され、マッチがステータスを決定します。", + "uptimeCreateJsonPath": "この式はレスポンスJSONデータに対して評価され、結果は期待値との照合に使用されます。参照", + "uptimeCreateJsonPathQuery": "クエリ言語ドキュメント。", + "maintenanceTableActionMenuDialogTitle": "本当にこのメンテナンスウィンドウを削除しますか?", + "infrastructureEditYour": "編集する", + "infrastructureEditMonitor": "インフラストラクチャモニターを保存", + "infrastructureMonitorCreated": "インフラストラクチャモニターが正常に作成されました!", + "infrastructureMonitorUpdated": "インフラストラクチャモニターが正常に更新されました!", + "errorInvalidTypeId": "無効な通知タイプが提供されました", + "errorInvalidFieldId": "無効なフィールドIDが提供されました", + "inviteNoTokenFound": "招待トークンが見つかりません", + "pageSpeedWarning": "警告: Google PageSpeed APIキーがまだ追加されていません。これがないとPageSpeedモニターは機能しません。", + "pageSpeedLearnMoreLink": "こちらをクリック", + "pageSpeedAddApiKey": "APIキーを追加する。", + "update": "更新", + "invalidFileFormat": "サポートされていないファイル形式!", + "invalidFileSize": "ファイルサイズが大きすぎます!", + "ClickUpload": "クリックしてアップロード", + "DragandDrop": "ドラッグアンドドロップ", + "MaxSize": "最大サイズ", + "SupportedFormats": "対応フォーマット", + "FirstName": "名", + "LastName": "姓", + "EmailDescriptionText": "こちらは現在ご使用のメールアドレスです — 変更することはできません。", + "YourPhoto": "プロフィール写真", + "PhotoDescriptionText": "この写真はプロフィールページに表示されます。", + "save": "保存", + "DeleteDescriptionText": "これにより、アカウントと関連するすべてのデータがサーバーから削除されます。この操作は元に戻せません。", + "DeleteAccountWarning": "アカウントを削除すると、再度サインインできなくなり、すべてのデータが削除されます。この操作は元に戻せません。", + "DeleteWarningTitle": "本当にこのアカウントを削除しますか?", "bulkImport": { - "title": "", - "selectFileTips": "", - "selectFileDescription": "", - "selectFile": "", - "parsingFailed": "", - "uploadSuccess": "", - "validationFailed": "", - "noFileSelected": "", - "fallbackPage": "" + "title": "一括インポート", + "selectFileTips": "アップロードするCSVファイルを選択", + "selectFileDescription": "またはサンプルをダウンロードできます", + "selectFile": "ファイルを選択", + "parsingFailed": "解析に失敗しました", + "uploadSuccess": "モニターが正常に作成されました!", + "validationFailed": "検証に失敗しました", + "noFileSelected": "ファイルが選択されていません", + "fallbackPage": "サーバーのリストを一括アップロードするためのファイルをインポート", + "invalidFileType": "無効なファイルタイプ", + "uploadFailed": "アップロードに失敗しました" }, - "DeleteAccountTitle": "", - "DeleteAccountButton": "", - "publicLink": "", - "maskedPageSpeedKeyPlaceholder": "", - "reset": "", - "ignoreTLSError": "", - "tlsErrorIgnored": "", - "ignoreTLSErrorDescription": "", - "createNew": "", + "DeleteAccountTitle": "アカウントを削除", + "DeleteAccountButton": "アカウントを削除", + "publicLink": "公開リンク", + "maskedPageSpeedKeyPlaceholder": "*************************************", + "reset": "リセット", + "ignoreTLSError": "TLS/SSLエラーを無視", + "tlsErrorIgnored": "TLS/SSLエラーが無視されました", + "ignoreTLSErrorDescription": "TLS/SSLエラーを無視してウェブサイトの可用性をチェック続行", + "createNew": "新規作成", "greeting": { - "prepend": "", - "append": "", - "overview": "" + "prepend": "こんにちは", + "append": "午後はあなたの遊び場です—壮大なものにしましょう!", + "overview": "{{type}}モニターの概要です。" }, "roles": { - "superAdmin": "", - "admin": "", - "teamMember": "", - "demoUser": "" + "superAdmin": "スーパー管理者", + "admin": "管理者", + "teamMember": "チームメンバー", + "demoUser": "デモユーザー" }, "teamPanel": { - "teamMembers": "", + "teamMembers": "チームメンバー", "filter": { - "all": "", - "member": "" + "all": "すべて", + "member": "メンバー" }, - "inviteTeamMember": "", - "inviteNewTeamMember": "", - "inviteDescription": "", - "email": "", - "selectRole": "", - "inviteLink": "", - "cancel": "", - "noMembers": "", - "getToken": "", - "emailToken": "", + "inviteTeamMember": "チームメンバーを招待", + "inviteNewTeamMember": "新しいチームメンバーを招待", + "inviteDescription": "新しいチームメンバーを追加すると、すべてのモニターへのアクセス権が付与されます。", + "email": "メール", + "selectRole": "役割を選択", + "inviteLink": "招待リンク", + "cancel": "キャンセル", + "noMembers": "この役割のチームメンバーはいません", + "getToken": "トークンを取得", + "emailToken": "メールトークン", "table": { - "name": "", - "email": "", - "role": "", - "created": "" + "name": "名前", + "email": "メール", + "role": "役割", + "created": "作成日" } }, "monitorState": { - "paused": "", - "resumed": "", - "active": "" + "paused": "一時停止", + "resumed": "再開", + "active": "アクティブ" }, "menu": { - "uptime": "", - "pagespeed": "", - "infrastructure": "", - "incidents": "", - "statusPages": "", - "maintenance": "", - "integrations": "", - "settings": "", - "support": "", - "discussions": "", - "docs": "", - "changelog": "", - "profile": "", - "password": "", - "team": "", - "logOut": "", - "notifications": "", - "logs": "" + "uptime": "稼働時間", + "pagespeed": "ページ速度", + "infrastructure": "インフラストラクチャ", + "incidents": "インシデント", + "statusPages": "ステータスページ", + "maintenance": "メンテナンス", + "integrations": "統合", + "settings": "設定", + "support": "サポート", + "discussions": "ディスカッション", + "docs": "ドキュメント", + "changelog": "変更履歴", + "profile": "プロフィール", + "password": "パスワード", + "team": "チーム", + "logOut": "ログアウト", + "notifications": "通知", + "logs": "ログ" }, - "settingsEmailUser": "", - "state": "", - "statusBreadCrumbsStatusPages": "", - "statusBreadCrumbsDetails": "", - "commonSaving": "", - "navControls": "", - "incidentsPageTitle": "", + "settingsEmailUser": "メールユーザー - 認証用ユーザー名、指定されている場合はメールアドレスを上書き", + "state": "状態", + "statusBreadCrumbsStatusPages": "ステータスページ", + "statusBreadCrumbsDetails": "詳細", + "commonSaving": "保存中...", + "navControls": "コントロール", + "incidentsPageTitle": "インシデント", "passwordPanel": { - "passwordChangedSuccess": "", - "passwordInputIncorrect": "", - "currentPassword": "", - "enterCurrentPassword": "", - "newPassword": "", - "enterNewPassword": "", - "confirmNewPassword": "", - "passwordRequirements": "", - "saving": "" + "passwordChangedSuccess": "パスワードが正常に変更されました。", + "passwordInputIncorrect": "パスワード入力が間違っています。", + "currentPassword": "現在のパスワード", + "enterCurrentPassword": "現在のパスワードを入力", + "newPassword": "新しいパスワード", + "enterNewPassword": "新しいパスワードを入力", + "confirmNewPassword": "新しいパスワードの確認", + "passwordRequirements": "新しいパスワードは少なくとも8文字で、少なくとも1つの大文字、1つの小文字、1つの数字、1つの特殊文字を含む必要があります。", + "saving": "保存中..." }, - "emailSent": "", - "failedToSendEmail": "", - "settingsTestEmailSuccess": "", - "settingsTestEmailFailed": "", - "settingsTestEmailFailedWithReason": "", - "settingsTestEmailUnknownError": "", + "emailSent": "メールが正常に送信されました", + "failedToSendEmail": "メールの送信に失敗しました", + "settingsTestEmailSuccess": "テストメールが正常に送信されました", + "settingsTestEmailFailed": "テストメールの送信に失敗しました", + "settingsTestEmailFailedWithReason": "テストメールの送信に失敗しました: {{reason}}", + "settingsTestEmailUnknownError": "不明なエラー", "statusMsg": { - "paused": "", - "up": "", - "down": "", - "pending": "" + "paused": "監視が一時停止されています。", + "up": "サイトが稼働中です。", + "down": "サイトがダウンしています。", + "pending": "保留中..." }, "uptimeGeneralInstructions": { - "http": "", - "ping": "", - "docker": "", - "port": "" + "http": "監視するURLまたはIPを入力(例: https://example.com/ または 192.168.1.100)し、ダッシュボードに表示される明確な表示名を追加。", + "ping": "pingするIPアドレスまたはホスト名を入力(例: 192.168.1.100 または example.com)し、ダッシュボードに表示される明確な表示名を追加。", + "docker": "コンテナのDocker IDを入力。Docker IDは64文字のフルDocker IDである必要があります。docker inspect を実行してフルコンテナIDを取得できます。", + "port": "サーバーのURLまたはIP、ポート番号、ダッシュボードに表示される明確な表示名を入力。" }, "common": { - "appName": "", - "monitoringAgentName": "", + "appName": "Checkmate", + "monitoringAgentName": "Capture", "buttons": { - "toggleTheme": "" + "toggleTheme": "ライト&ダークを切り替え" }, "toasts": { - "networkError": "", - "checkConnection": "", - "unknownError": "" + "networkError": "ネットワークエラー", + "checkConnection": "接続を確認してください", + "unknownError": "不明なエラー" } }, "auth": { "common": { "navigation": { - "continue": "", - "back": "" + "continue": "続行", + "back": "戻る" }, "inputs": { "email": { - "label": "", - "placeholder": "", + "label": "メール", + "placeholder": "jordan.ellis@domain.com", "errors": { - "empty": "", - "invalid": "" + "empty": "続行するには、メールアドレスを入力してください", + "invalid": "入力されたメールアドレスの有効性を再確認してください" } }, "password": { - "label": "", + "label": "パスワード", "rules": { "length": { - "beginning": "", - "highlighted": "" + "beginning": "少なくとも", + "highlighted": "8文字である必要があります" }, "special": { - "beginning": "", - "highlighted": "" + "beginning": "少なくとも", + "highlighted": "1つの特殊文字を含む必要があります" }, "number": { - "beginning": "", - "highlighted": "" + "beginning": "少なくとも", + "highlighted": "1つの数字を含む必要があります" }, "uppercase": { - "beginning": "", - "highlighted": "" + "beginning": "少なくとも", + "highlighted": "1つの大文字を含む必要があります" }, "lowercase": { - "beginning": "", - "highlighted": "" + "beginning": "少なくとも", + "highlighted": "1つの小文字を含む必要があります" }, "match": { - "beginning": "", - "highlighted": "" + "beginning": "パスワードの確認とパスワードは", + "highlighted": "一致する必要があります" } }, "errors": { - "empty": "", - "length": "", - "uppercase": "", - "lowercase": "", - "number": "", - "special": "", - "incorrect": "" + "empty": "パスワードを入力してください", + "length": "パスワードは少なくとも8文字である必要があります", + "uppercase": "パスワードは少なくとも1つの大文字を含む必要があります", + "lowercase": "パスワードは少なくとも1つの小文字を含む必要があります", + "number": "パスワードは少なくとも1つの数字を含む必要があります", + "special": "パスワードは少なくとも1つの特殊文字を含む必要があります", + "incorrect": "入力されたパスワードが記録と一致しません" } }, "passwordConfirm": { - "label": "", - "placeholder": "", + "label": "パスワードの確認", + "placeholder": "確認のためにパスワードを再入力", "errors": { - "empty": "", - "different": "" + "empty": "確認のためにパスワードを再度入力してください(タイプミスを防ぐため)", + "different": "入力されたパスワードが一致しないため、どちらかが間違っています" } }, "firstName": { - "label": "", - "placeholder": "", + "label": "名前", + "placeholder": "太郎", "errors": { - "empty": "", - "length": "", - "pattern": "" + "empty": "名前を入力してください", + "length": "名前は50文字未満である必要があります", + "pattern": "名前は文字、スペース、アポストロフィ、またはハイフンのみを含む必要があります" } }, "lastName": { - "label": "", - "placeholder": "", + "label": "姓", + "placeholder": "田中", "errors": { - "empty": "", - "length": "", - "pattern": "" + "empty": "姓を入力してください", + "length": "姓は50文字未満である必要があります", + "pattern": "姓は文字、スペース、アポストロフィ、またはハイフンのみを含む必要があります" } } }, "errors": { - "validation": "" + "validation": "データの検証エラー。" }, "fields": { "password": { "errors": { - "incorrect": "" + "incorrect": "入力されたパスワードが記録と一致しません" + } + }, + "role": { + "errors": { + "min": "" } } } }, "login": { - "heading": "", + "heading": "ログイン", "subheadings": { - "stepOne": "", - "stepTwo": "" + "stepOne": "メールアドレスを入力", + "stepTwo": "パスワードを入力" }, "links": { - "forgotPassword": "", - "register": "", - "forgotPasswordLink": "", - "registerLink": "" + "forgotPassword": "パスワードを忘れましたか?", + "register": "アカウントをお持ちでないですか?", + "forgotPasswordLink": "パスワードをリセット", + "registerLink": "こちらで登録" }, "toasts": { - "success": "", - "incorrectPassword": "" + "success": "おかえりなさい!正常にログインしました。", + "incorrectPassword": "パスワードが間違っています" }, "errors": { "password": { - "incorrect": "" + "incorrect": "入力されたパスワードが記録と一致しません" } } }, "registration": { "heading": { - "superAdmin": "", - "user": "" + "superAdmin": "スーパー管理者を作成", + "user": "サインアップ" }, "subheadings": { - "stepOne": "", - "stepTwo": "", - "stepThree": "" + "stepOne": "個人情報を入力", + "stepTwo": "メールアドレスを入力", + "stepThree": "パスワードを作成" }, "description": { - "superAdmin": "", - "user": "" + "superAdmin": "スーパー管理者アカウントを作成して開始", + "user": "ユーザーとしてサインアップし、スーパー管理者にモニターへのアクセスを求める" }, "gettingStartedButton": { - "superAdmin": "", - "user": "" + "superAdmin": "スーパー管理者アカウントを作成", + "user": "一般ユーザーでサインアップ" }, - "termsAndPolicies": "", + "termsAndPolicies": "アカウントを作成することで、利用規約とプライバシーポリシーに同意したことになります。", "links": { - "login": "" + "login": "既にアカウントをお持ちですか? ログイン" }, "toasts": { - "success": "" + "success": "ようこそ!アカウントが正常に作成されました。" } }, "forgotPassword": { - "heading": "", + "heading": "パスワードを忘れましたか?", "subheadings": { - "stepOne": "", - "stepTwo": "", - "stepThree": "", - "stepFour": "" + "stepOne": "ご心配なく、リセット手順をお送りします。", + "stepTwo": "パスワードリセットリンクを に送信しました", + "stepThree": "新しいパスワードは以前に使用したパスワードとは異なる必要があります。", + "stepFour": "パスワードが正常にリセットされました。下記をクリックして魔法のようにログインしてください。" }, "buttons": { - "openEmail": "", - "resetPassword": "" + "openEmail": "メールアプリを開く", + "resetPassword": "パスワードをリセット" }, "imageAlts": { - "passwordKey": "", - "email": "", - "lock": "", - "passwordConfirm": "" + "passwordKey": "パスワードキーアイコン", + "email": "メールアイコン", + "lock": "ロックアイコン", + "passwordConfirm": "パスワード確認アイコン" }, "links": { - "login": "", - "resend": "" + "login": "ログインに戻る", + "resend": "メールが届きませんか? クリックして再送信" }, "toasts": { - "sent": "", - "emailNotFound": "", - "redirect": "", - "success": "", - "error": "" + "sent": "手順を に送信しました。", + "emailNotFound": "メールが見つかりません。", + "redirect": "秒後にリダイレクトします...", + "success": "パスワードが正常にリセットされました。", + "error": "パスワードをリセットできません。後でもう一度試すか、サポートにお問い合わせください。" } } }, "errorPages": { "serverUnreachable": { "toasts": { - "reconnected": "", - "stillUnreachable": "" + "reconnected": "サーバーに正常に再接続しました。", + "stillUnreachable": "サーバーにまだ到達できません。後でもう一度お試しください。" }, - "alertBox": "", - "description": "", + "alertBox": "サーバー接続エラー", + "description": "サーバーに接続できません。インターネット接続を確認するか、問題が続く場合はデプロイ設定を確認してください。", "retryButton": { - "default": "", - "processing": "" + "default": "接続を再試行", + "processing": "接続中..." } } }, "createNotifications": { - "title": "", + "title": "通知チャンネルを作成", "nameSettings": { - "title": "", - "description": "", - "nameLabel": "", - "namePlaceholder": "" + "title": "名前", + "description": "統合の説明的な名前。", + "nameLabel": "名前", + "namePlaceholder": "例: Slack通知" }, "typeSettings": { - "title": "", - "description": "", - "typeLabel": "" + "title": "タイプ", + "description": "作成したい通知チャンネルのタイプを選択。", + "typeLabel": "タイプ" }, "emailSettings": { - "title": "", - "description": "", - "emailLabel": "", - "emailPlaceholder": "" + "title": "メール", + "description": "送信先メールアドレス。", + "emailLabel": "メールアドレス", + "emailPlaceholder": "例: john@example.com" }, "slackSettings": { - "title": "", - "description": "", - "webhookLabel": "", - "webhookPlaceholder": "" + "title": "Slack", + "description": "Slack Webhookをここで設定", + "webhookLabel": "Slack Webhook URL", + "webhookPlaceholder": "https://hooks.slack.com/services/..." }, "pagerdutySettings": { - "title": "", - "description": "", - "integrationKeyLabel": "", - "integrationKeyPlaceholder": "" + "title": "PagerDuty", + "description": "PagerDuty統合をここで設定", + "integrationKeyLabel": "統合キー", + "integrationKeyPlaceholder": "1234567890" }, "discordSettings": { - "title": "", - "description": "", - "webhookLabel": "", - "webhookPlaceholder": "" + "title": "Discord", + "description": "Discord Webhookをここで設定", + "webhookLabel": "Discord Webhook URL", + "webhookPlaceholder": "https://your-server.com/webhook" }, "webhookSettings": { - "title": "", - "description": "", - "webhookLabel": "", - "webhookPlaceholder": "" - } + "title": "Webhook", + "description": "Webhookをここで設定", + "webhookLabel": "Webhook URL", + "webhookPlaceholder": "https://your-server.com/webhook" + }, + "testNotification": "テスト通知", + "dialogDeleteTitle": "この通知を削除してもよろしいですか?", + "dialogDeleteConfirm": "削除" }, "notificationConfig": { - "title": "", - "description": "" + "title": "通知", + "description": "使用したい通知チャンネルを選択" }, "monitorStatus": { - "checkingEvery": "", - "withCaptureAgent": "", - "up": "", - "down": "", - "paused": "" + "checkingEvery": "{{interval}}ごとにチェック", + "withCaptureAgent": "Captureエージェント{{version}}で", + "up": "アップ", + "down": "ダウン", + "paused": "一時停止" }, - "advancedMatching": "", - "sendTestNotifications": "", - "testNotificationsDisabled": "", - "selectAll": "", - "showAdminLoginLink": "", + "advancedMatching": "高度なマッチング", + "sendTestNotifications": "テスト通知を送信", + "selectAll": "すべて選択", + "showAdminLoginLink": "ステータスページに「管理者?こちらでログイン」リンクを表示", "logsPage": { - "title": "", - "description": "", + "title": "ログ", + "description": "システムログ - 最新1000行", "tabs": { - "queue": "", - "logs": "" + "queue": "ジョブキュー", + "logs": "サーバーログ", + "diagnostics": "診断" }, "toast": { - "fetchLogsSuccess": "" + "fetchLogsSuccess": "ログが正常に取得されました" }, "logLevelSelect": { - "title": "", + "title": "ログレベル", "values": { - "all": "", - "info": "", - "warn": "", - "error": "", - "debug": "" + "all": "すべて", + "info": "情報", + "warn": "警告", + "error": "エラー", + "debug": "デバッグ" } } }, "queuePage": { - "title": "", - "refreshButton": "", - "flushButton": "", + "title": "キュー", + "refreshButton": "更新", + "flushButton": "キューをフラッシュ", "jobTable": { - "title": "", - "idHeader": "", - "urlHeader": "", - "typeHeader": "", - "activeHeader": "", - "lockedAtHeader": "", - "runCountHeader": "", - "failCountHeader": "", - "lastRunHeader": "", - "lastFinishedAtHeader": "", - "lastRunTookHeader": "" + "title": "現在キューにあるジョブ", + "idHeader": "モニターID", + "urlHeader": "URL", + "typeHeader": "タイプ", + "activeHeader": "アクティブ", + "lockedAtHeader": "ロック日時", + "runCountHeader": "実行回数", + "failCountHeader": "失敗回数", + "lastRunHeader": "最終実行日時", + "lastFinishedAtHeader": "最終完了日時", + "lastRunTookHeader": "最終実行時間" }, "metricsTable": { - "title": "", - "metricHeader": "", - "valueHeader": "" + "title": "キューメトリック", + "metricHeader": "メトリック", + "valueHeader": "値" }, "failedJobTable": { - "title": "", - "monitorIdHeader": "", - "monitorUrlHeader": "", - "failCountHeader": "", - "failedAtHeader": "", - "failReasonHeader": "" + "title": "失敗したジョブ", + "monitorIdHeader": "モニターID", + "monitorUrlHeader": "モニターURL", + "failCountHeader": "失敗回数", + "failedAtHeader": "最終失敗日時", + "failReasonHeader": "失敗理由" } }, "export": { - "title": "", - "success": "", - "failed": "" + "title": "モニターをエクスポート", + "success": "モニターが正常にエクスポートされました!", + "failed": "モニターのエクスポートに失敗しました" }, "monitorActions": { - "title": "", - "import": "", - "export": "" + "title": "エクスポート/インポート", + "import": "モニターをインポート", + "export": "モニターをエクスポート", + "deleteSuccess": "モニターが正常に削除されました", + "deleteFailed": "モニターの削除に失敗しました", + "details": "詳細" }, "settingsPage": { "aboutSettings": { - "labelDevelopedBy": "", - "labelVersion": "", - "title": "" + "labelDevelopedBy": "Bluewave Labsによる開発", + "labelVersion": "バージョン", + "title": "について" }, "demoMonitorsSettings": { - "buttonAddMonitors": "", - "description": "", - "title": "" + "buttonAddMonitors": "デモモニターを追加", + "description": "デモンストレーション目的でサンプルモニターを追加。", + "title": "デモモニター" }, "emailSettings": { - "buttonSendTestEmail": "", - "description": "", - "descriptionTransport": "", - "labelAddress": "", - "labelConnectionHost": "", - "labelHost": "", - "labelIgnoreTLS": "", - "labelPassword": "", - "labelPasswordSet": "", - "labelPool": "", - "labelPort": "", - "labelRejectUnauthorized": "", - "labelRequireTLS": "", - "labelSecure": "", - "labelTLSServername": "", - "labelUser": "", - "linkTransport": "", - "placeholderUser": "", - "title": "" + "buttonSendTestEmail": "テストメールを送信", + "description": "システムのメール設定を構成。これは通知とアラートの送信に使用されます。", + "descriptionTransport": "これはNodeMailer用のSMTPトランスポートを構築します", + "labelAddress": "メールアドレス - 認証に使用", + "labelConnectionHost": "メール接続ホスト - HELO/EHLO挨拶で使用するホスト名", + "labelHost": "メールホスト - 接続するホスト名またはIPアドレス", + "labelIgnoreTLS": "STARTTLSを無効化: サーバーがサポートしていてもTLSを使用しない", + "labelPassword": "メールパスワード - 認証用パスワード", + "labelPasswordSet": "パスワードが設定されています。変更するにはリセットをクリック。", + "labelPool": "接続プーリングを有効化: パフォーマンス向上のため既存接続を再利用", + "labelPort": "メールポート - 接続するポート", + "labelRejectUnauthorized": "無効な証明書を拒否: 自己署名または信頼されていない証明書での接続を拒否", + "labelRequireTLS": "STARTTLSを強制: TLSアップグレードを必須とし、サポートされていない場合は失敗", + "labelSecure": "SSL使用(推奨): SSL/TLSを使用して接続を暗号化", + "labelTLSServername": "TLS Servername - ホストがIPの場合のTLS検証用オプションホスト名", + "labelUser": "メールユーザー - 認証用ユーザー名、指定されている場合はメールアドレスを上書き", + "linkTransport": "仕様はこちらを参照", + "placeholderUser": "不要な場合は空白のまま", + "title": "メール", + "toastEmailRequiredFieldsError": "メールアドレス、ホスト、ポート、パスワードが必要です" }, "pageSpeedSettings": { - "description": "", - "labelApiKeySet": "", - "labelApiKey": "", - "title": "" + "description": "Google PageSpeed APIキーを入力してGoogle PageSpeed監視を有効化。キーを更新するにはリセットをクリック。", + "labelApiKeySet": "APIキーが設定されています。変更するにはリセットをクリック。", + "labelApiKey": "PageSpeed APIキー", + "title": "Google PageSpeed APIキー" }, - "saveButtonLabel": "", + "saveButtonLabel": "保存", "statsSettings": { - "clearAllStatsButton": "", - "clearAllStatsDescription": "", - "clearAllStatsDialogConfirm": "", - "clearAllStatsDialogDescription": "", - "clearAllStatsDialogTitle": "", - "description": "", - "labelTTL": "", - "labelTTLOptional": "", - "title": "" + "clearAllStatsButton": "すべての統計をクリア", + "clearAllStatsDescription": "すべての統計をクリア。この操作は元に戻せません。", + "clearAllStatsDialogConfirm": "はい、すべての統計をクリア", + "clearAllStatsDialogDescription": "削除されると、監視履歴と統計は復元できません。", + "clearAllStatsDialogTitle": "すべての統計をクリアしますか?", + "description": "履歴データを保持する期間を定義。既存のデータをすべてクリアすることもできます。", + "labelTTL": "監視履歴を保持したい日数。", + "labelTTLOptional": "0で無限", + "title": "モニター履歴" }, "systemResetSettings": { - "buttonRemoveAllMonitors": "", - "description": "", - "dialogConfirm": "", - "dialogDescription": "", - "dialogTitle": "", - "title": "" + "buttonRemoveAllMonitors": "すべてのモニターを削除", + "description": "システムからすべてのモニターを削除。", + "dialogConfirm": "はい、すべてのモニターを削除", + "dialogDescription": "削除されると、モニターは復元できません。", + "dialogTitle": "すべてのモニターを削除しますか?", + "title": "システムリセット" }, "timezoneSettings": { - "description": "", - "label": "", - "title": "" + "description": "アプリケーション全体で日付と時刻を表示するために使用するタイムゾーンを選択。", + "label": "表示タイムゾーン", + "title": "表示タイムゾーン" }, - "title": "", + "title": "設定", "uiSettings": { - "description": "", - "labelLanguage": "", - "labelTheme": "", - "title": "" + "description": "ライトモードとダークモードを切り替えるか、ユーザーインターフェース言語を変更。", + "labelLanguage": "言語", + "labelTheme": "テーマモード", + "title": "外観" }, "urlSettings": { - "description": "", - "label": "", - "selectDisabled": "", - "selectEnabled": "", - "title": "" + "description": "公開ステータスページでモニターのIPアドレスまたはURLを表示。無効にすると、機密情報を保護するためモニター名のみが表示されます。", + "label": "ステータスページでIP/URLを表示", + "selectDisabled": "無効", + "selectEnabled": "有効", + "title": "ステータスページのモニターIP/URL" } - } + }, + "statusPageCreate": { + "buttonSave": "保存" + }, + "incidentsOptionsHeaderFilterResolved": "解決済み", + "settingsSave": "保存", + "statusPageCreateAppearanceTitle": "外観", + "confirmPassword": "パスワードの確認", + "monitorHooks": { + "failureAddDemoMonitors": "デモモニターの追加に失敗しました", + "successAddDemoMonitors": "デモモニターが正常に追加されました" + }, + "settingsAppearance": "外観", + "settingsDisplayTimezone": "表示タイムゾーン", + "settingsGeneralSettings": "一般設定", + "incidentsOptionsHeaderTotalIncidents": "総インシデント", + "statusPage": { + "deleteSuccess": "ステータスページが正常に削除されました", + "deleteFailed": "ステータスページの削除に失敗しました", + "createSuccess": "ステータスページが正常に作成されました", + "updateSuccess": "ステータスページが正常に更新されました", + "generalSettings": "一般設定", + "contents": "コンテンツ", + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "testNotificationsDisabled": "このモニターには通知が設定されていません。「設定」ボタンをクリックして追加する必要があります", + "incidentsTableResolvedAt": "解決日時", + "incidentsTableActionResolve": "解決", + "checkHooks": { + "failureResolveOne": "インシデントの解決に失敗しました。", + "failureResolveAll": "すべてのインシデントの解決に失敗しました。", + "failureResolveMonitor": "" + }, + "checkFormError": "フォームでエラーを確認してください。", + "diagnosticsPage": { + "diagnosticDescription": "システム診断", + "statsDescription": "システム統計", + "gauges": { + "heapAllocationTitle": "ヒープ割り当て", + "heapAllocationSubtitle": "利用可能メモリの%", + "heapUsageTitle": "ヒープ使用量", + "heapUsageSubtitle": "利用可能メモリの%", + "heapUtilizationTitle": "ヒープ利用率", + "heapUtilizationSubtitle": "割り当て済みの%", + "instantCpuUsageTitle": "瞬間CPU使用率", + "instantCpuUsageSubtitle": "CPUが使用した1秒の%" + }, + "stats": { + "eventLoopDelayTitle": "イベントループ遅延", + "uptimeTitle": "稼働時間", + "usedHeapSizeTitle": "使用ヒープサイズ", + "totalHeapSizeTitle": "総ヒープサイズ", + "osMemoryLimitTitle": "OSメモリ制限" + } + }, + "pageSpeedLighthouseAPI": "Lighthouse PageSpeed APIを使用してウェブサイトを監視", + "time": { + "threeMinutes": "3分", + "fiveMinutes": "5分", + "tenMinutes": "10分", + "twentyMinutes": "20分", + "oneHour": "1時間", + "oneDay": "1日", + "oneWeek": "1週間" + }, + "general": { + "noOptionsFound": "" + }, + "infrastructureMonitor": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "maintenanceWindow": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "pageSpeed": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "uptimeMonitor": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "editUserPage": { + "form": { + "email": "", + "firstName": "", + "lastName": "", + "role": "", + "save": "" + }, + "table": { + "actionHeader": "", + "roleHeader": "" + }, + "title": "", + "toast": { + "successUserUpdate": "", + "validationErrors": "" + } + }, + "incidentsPageActionResolveMonitor": "", + "incidentsPageActionResolveAll": "" } diff --git a/client/src/locales/pt-BR.json b/client/src/locales/pt-BR.json index f9ccc81b4..2315a723d 100644 --- a/client/src/locales/pt-BR.json +++ b/client/src/locales/pt-BR.json @@ -8,7 +8,6 @@ "settingsFailedToSave": "Falha ao salvar as configurações", "settingsStatsCleared": "Estatísticas limpas com sucesso", "settingsFailedToClearStats": "Falha ao limpar estatísticas", - "settingsFailedToAddDemoMonitors": "Falha ao adicionar monitores de demonstração", "settingsMonitorsDeleted": "Todos os monitores foram excluídos com sucesso", "settingsFailedToDeleteMonitors": "Falha ao excluir todos os monitores", "starPromptTitle": "Star Checkmate", @@ -86,7 +85,8 @@ "networkError": "Ocorreu um erro de rede", "fallback": { "title": "", - "checks": [""] + "checks": [""], + "actionButton": "" }, "createButton": "", "createTitle": "", @@ -331,7 +331,9 @@ "uploadSuccess": "Monitores criados com sucesso!", "validationFailed": "Falha na validação", "noFileSelected": "Nenhum arquivo selecionado", - "fallbackPage": "Importe um arquivo para enviar uma lista de servidores em massa" + "fallbackPage": "Importe um arquivo para enviar uma lista de servidores em massa", + "invalidFileType": "", + "uploadFailed": "" }, "DeleteAccountTitle": "Remover conta", "DeleteAccountButton": "Remover conta", @@ -432,47 +434,47 @@ "pending": "Pendente..." }, "uptimeGeneralInstructions": { - "http": "", - "ping": "", - "docker": "", - "port": "" + "http": "Insira a URL ou IP para monitorar (ex. https://exemplo.com.br/ ou 192.168.1.100) e adicione uma descrição que aparecerá na dashboard.", + "ping": "Insira o endereço IP ou nome de domínio para ping (ex. 192.168.1.100 ou exemplo.com.br) e adicione uma descrição que aparecerá na dashboard.", + "docker": "Insira o Docker Id do seu container. Docker Ids devem ser todos os 64 caracteres. Você pode executar docker inspect para descobrir o Id completo.", + "port": "Insira a URL ou o IP do servidor, aporta e uma descrição que aparecerá na dashboard." }, "common": { - "appName": "", - "monitoringAgentName": "", + "appName": "Checkmate", + "monitoringAgentName": "Capture", "buttons": { - "toggleTheme": "" + "toggleTheme": "Trocar claro e escuro" }, "toasts": { - "networkError": "", - "checkConnection": "", - "unknownError": "" + "networkError": "Erro de rede", + "checkConnection": "Por favor, verifique a conexão", + "unknownError": "Erro desconhecido" } }, "auth": { "common": { "navigation": { - "continue": "", - "back": "" + "continue": "Continuar", + "back": "Voltar" }, "inputs": { "email": { - "label": "", - "placeholder": "", + "label": "Email", + "placeholder": "joao.silva@dominio.com.br", "errors": { - "empty": "", - "invalid": "" + "empty": "Para continuar, insira seu endereço de email", + "invalid": "Por favor, confirme o endereço de email informado" } }, "password": { - "label": "", + "label": "Senha", "rules": { "length": { - "beginning": "", - "highlighted": "" + "beginning": "Deve ter no mínimo", + "highlighted": "8 caracteres de comprimento" }, "special": { - "beginning": "", + "beginning": "Deve conter pelo menos", "highlighted": "" }, "number": { @@ -493,9 +495,9 @@ } }, "errors": { - "empty": "", - "length": "", - "uppercase": "", + "empty": "Por favor, digite sua senha", + "length": "A senha deve ter pelo menos 8 caracteres", + "uppercase": "A senha deve conter pelo menos 1 letra maiúscula", "lowercase": "", "number": "", "special": "", @@ -511,50 +513,55 @@ } }, "firstName": { - "label": "", - "placeholder": "", + "label": "Nome", + "placeholder": "João", "errors": { - "empty": "", - "length": "", - "pattern": "" + "empty": "Por favor, insira seu nome", + "length": "Nome deve ter menos de 50 caracteres", + "pattern": "Nome deve conter apenas letras, espaços, apóstrofos ou hífens" } }, "lastName": { - "label": "", - "placeholder": "", + "label": "Sobrenome", + "placeholder": "Silva", "errors": { - "empty": "", - "length": "", - "pattern": "" + "empty": "Por favor, insira seu sobrenome", + "length": "Sobrenome deve ter menos de 50 caracteres", + "pattern": "Sobrenome deve conter apenas letras, espaços, apóstrofos ou hífens" } } }, "errors": { - "validation": "" + "validation": "Erro de validação de dados." }, "fields": { "password": { "errors": { "incorrect": "" } + }, + "role": { + "errors": { + "min": "" + } } } }, "login": { - "heading": "", + "heading": "Entrar", "subheadings": { - "stepOne": "", - "stepTwo": "" + "stepOne": "Insira seu email", + "stepTwo": "Insira sua senha" }, "links": { - "forgotPassword": "", - "register": "", + "forgotPassword": "Esqueceu sua senha?", + "register": "Não tem uma conta?", "forgotPasswordLink": "", "registerLink": "" }, "toasts": { - "success": "", - "incorrectPassword": "" + "success": "Bem-vindo de volta! Você está autenticado.", + "incorrectPassword": "Senha incorreta" }, "errors": { "password": { @@ -564,8 +571,8 @@ }, "registration": { "heading": { - "superAdmin": "", - "user": "" + "superAdmin": "Crie um usuário administrador", + "user": "Se inscrever" }, "subheadings": { "stepOne": "", @@ -604,11 +611,11 @@ "passwordKey": "", "email": "", "lock": "", - "passwordConfirm": "" + "passwordConfirm": "Ícone de confirmação de senha" }, "links": { - "login": "", - "resend": "" + "login": "Voltar para o login", + "resend": "Não recebeu o email? Clique para reenviar" }, "toasts": { "sent": "", @@ -675,7 +682,10 @@ "description": "", "webhookLabel": "", "webhookPlaceholder": "" - } + }, + "testNotification": "", + "dialogDeleteTitle": "", + "dialogDeleteConfirm": "" }, "notificationConfig": { "title": "", @@ -690,7 +700,6 @@ }, "advancedMatching": "", "sendTestNotifications": "", - "testNotificationsDisabled": "", "selectAll": "", "showAdminLoginLink": "", "logsPage": { @@ -698,7 +707,8 @@ "description": "", "tabs": { "queue": "", - "logs": "" + "logs": "", + "diagnostics": "" }, "toast": { "fetchLogsSuccess": "" @@ -753,7 +763,10 @@ "monitorActions": { "title": "", "import": "", - "export": "" + "export": "", + "deleteSuccess": "", + "deleteFailed": "", + "details": "" }, "settingsPage": { "aboutSettings": { @@ -785,7 +798,8 @@ "labelUser": "", "linkTransport": "", "placeholderUser": "", - "title": "" + "title": "", + "toastEmailRequiredFieldsError": "" }, "pageSpeedSettings": { "description": "", @@ -832,5 +846,124 @@ "selectEnabled": "", "title": "" } - } + }, + "statusPageCreate": { + "buttonSave": "" + }, + "incidentsOptionsHeaderFilterResolved": "", + "settingsSave": "", + "statusPageCreateAppearanceTitle": "", + "confirmPassword": "", + "monitorHooks": { + "failureAddDemoMonitors": "", + "successAddDemoMonitors": "" + }, + "settingsAppearance": "", + "settingsDisplayTimezone": "", + "settingsGeneralSettings": "", + "incidentsOptionsHeaderTotalIncidents": "", + "statusPage": { + "deleteSuccess": "", + "deleteFailed": "", + "createSuccess": "", + "updateSuccess": "", + "generalSettings": "", + "contents": "", + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "testNotificationsDisabled": "", + "incidentsTableResolvedAt": "", + "incidentsTableActionResolve": "", + "checkHooks": { + "failureResolveOne": "", + "failureResolveAll": "", + "failureResolveMonitor": "" + }, + "checkFormError": "", + "diagnosticsPage": { + "diagnosticDescription": "", + "statsDescription": "", + "gauges": { + "heapAllocationTitle": "", + "heapAllocationSubtitle": "", + "heapUsageTitle": "", + "heapUsageSubtitle": "", + "heapUtilizationTitle": "", + "heapUtilizationSubtitle": "", + "instantCpuUsageTitle": "", + "instantCpuUsageSubtitle": "" + }, + "stats": { + "eventLoopDelayTitle": "", + "uptimeTitle": "", + "usedHeapSizeTitle": "", + "totalHeapSizeTitle": "", + "osMemoryLimitTitle": "" + } + }, + "pageSpeedLighthouseAPI": "", + "time": { + "threeMinutes": "", + "fiveMinutes": "", + "tenMinutes": "", + "twentyMinutes": "", + "oneHour": "", + "oneDay": "", + "oneWeek": "" + }, + "general": { + "noOptionsFound": "" + }, + "infrastructureMonitor": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "maintenanceWindow": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "pageSpeed": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "uptimeMonitor": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "editUserPage": { + "form": { + "email": "", + "firstName": "", + "lastName": "", + "role": "", + "save": "" + }, + "table": { + "actionHeader": "", + "roleHeader": "" + }, + "title": "", + "toast": { + "successUserUpdate": "", + "validationErrors": "" + } + }, + "incidentsPageActionResolveMonitor": "", + "incidentsPageActionResolveAll": "" } diff --git a/client/src/locales/ru.json b/client/src/locales/ru.json index e0f66b98e..814e94da1 100644 --- a/client/src/locales/ru.json +++ b/client/src/locales/ru.json @@ -8,10 +8,9 @@ "settingsFailedToSave": "Не удалось сохранить настройки", "settingsStatsCleared": "Статистика успешно очищена", "settingsFailedToClearStats": "Не удалось очистить статистику", - "settingsFailedToAddDemoMonitors": "Не удалось добавить демонстрационные мониторы", "settingsMonitorsDeleted": "Успешно удалены все мониторы", "settingsFailedToDeleteMonitors": "Не удалось удалить все мониторы", - "starPromptTitle": "Star Checkmate", + "starPromptTitle": "", "starPromptDescription": "Ознакомьтесь с последними релизами и помогите развить сообщество на GitHub", "https": "HTTPS", "http": "HTTP", @@ -86,7 +85,8 @@ "networkError": "Произошла сетевая ошибка", "fallback": { "title": "", - "checks": [""] + "checks": [""], + "actionButton": "" }, "createButton": "", "createTitle": "", @@ -331,7 +331,9 @@ "uploadSuccess": "Мониторы успешно созданы!", "validationFailed": "Не удалось выполнить проверку", "noFileSelected": "Файл не выбран", - "fallbackPage": "Импортируйте файл для массовой загрузки списка серверов" + "fallbackPage": "Импортируйте файл для массовой загрузки списка серверов", + "invalidFileType": "", + "uploadFailed": "" }, "DeleteAccountTitle": "Удалить аккаунт", "DeleteAccountButton": "Удалить аккаунт", @@ -537,6 +539,11 @@ "errors": { "incorrect": "" } + }, + "role": { + "errors": { + "min": "" + } } } }, @@ -675,7 +682,10 @@ "description": "", "webhookLabel": "", "webhookPlaceholder": "" - } + }, + "testNotification": "", + "dialogDeleteTitle": "", + "dialogDeleteConfirm": "" }, "notificationConfig": { "title": "", @@ -690,7 +700,6 @@ }, "advancedMatching": "", "sendTestNotifications": "", - "testNotificationsDisabled": "", "selectAll": "", "showAdminLoginLink": "", "logsPage": { @@ -698,7 +707,8 @@ "description": "", "tabs": { "queue": "", - "logs": "" + "logs": "", + "diagnostics": "" }, "toast": { "fetchLogsSuccess": "" @@ -753,7 +763,10 @@ "monitorActions": { "title": "", "import": "", - "export": "" + "export": "", + "deleteSuccess": "", + "deleteFailed": "", + "details": "" }, "settingsPage": { "aboutSettings": { @@ -785,7 +798,8 @@ "labelUser": "", "linkTransport": "", "placeholderUser": "", - "title": "" + "title": "", + "toastEmailRequiredFieldsError": "" }, "pageSpeedSettings": { "description": "", @@ -832,5 +846,124 @@ "selectEnabled": "", "title": "" } - } + }, + "statusPageCreate": { + "buttonSave": "" + }, + "incidentsOptionsHeaderFilterResolved": "", + "settingsSave": "", + "statusPageCreateAppearanceTitle": "", + "confirmPassword": "", + "monitorHooks": { + "failureAddDemoMonitors": "", + "successAddDemoMonitors": "" + }, + "settingsAppearance": "", + "settingsDisplayTimezone": "", + "settingsGeneralSettings": "", + "incidentsOptionsHeaderTotalIncidents": "", + "statusPage": { + "deleteSuccess": "", + "deleteFailed": "", + "createSuccess": "", + "updateSuccess": "", + "generalSettings": "", + "contents": "", + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "testNotificationsDisabled": "", + "incidentsTableResolvedAt": "", + "incidentsTableActionResolve": "", + "checkHooks": { + "failureResolveOne": "", + "failureResolveAll": "", + "failureResolveMonitor": "" + }, + "checkFormError": "", + "diagnosticsPage": { + "diagnosticDescription": "", + "statsDescription": "", + "gauges": { + "heapAllocationTitle": "", + "heapAllocationSubtitle": "", + "heapUsageTitle": "", + "heapUsageSubtitle": "", + "heapUtilizationTitle": "", + "heapUtilizationSubtitle": "", + "instantCpuUsageTitle": "", + "instantCpuUsageSubtitle": "" + }, + "stats": { + "eventLoopDelayTitle": "", + "uptimeTitle": "", + "usedHeapSizeTitle": "", + "totalHeapSizeTitle": "", + "osMemoryLimitTitle": "" + } + }, + "pageSpeedLighthouseAPI": "", + "time": { + "threeMinutes": "", + "fiveMinutes": "", + "tenMinutes": "", + "twentyMinutes": "", + "oneHour": "", + "oneDay": "", + "oneWeek": "" + }, + "general": { + "noOptionsFound": "" + }, + "infrastructureMonitor": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "maintenanceWindow": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "pageSpeed": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "uptimeMonitor": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "editUserPage": { + "form": { + "email": "", + "firstName": "", + "lastName": "", + "role": "", + "save": "" + }, + "table": { + "actionHeader": "", + "roleHeader": "" + }, + "title": "", + "toast": { + "successUserUpdate": "", + "validationErrors": "" + } + }, + "incidentsPageActionResolveMonitor": "", + "incidentsPageActionResolveAll": "" } diff --git a/client/src/locales/tr.json b/client/src/locales/tr.json index 845b24aaf..e9e2d4e29 100644 --- a/client/src/locales/tr.json +++ b/client/src/locales/tr.json @@ -8,7 +8,6 @@ "settingsFailedToSave": "Ayarlar kaydedilemedi", "settingsStatsCleared": "İstatistikler başarıyla temizlendi", "settingsFailedToClearStats": "İstatistikler temizlenemedi", - "settingsFailedToAddDemoMonitors": "Demo monitörler eklenemedi", "settingsMonitorsDeleted": "Tüm monitörler başarıyla silindi", "settingsFailedToDeleteMonitors": "Monitörler silinemedi", "starPromptTitle": "Checkmate yıldızla değerlendirin", @@ -86,7 +85,8 @@ "networkError": "Ağ hatası oluştu", "fallback": { "title": "", - "checks": [""] + "checks": [""], + "actionButton": "" }, "createButton": "Bildirim kanalı oluştur", "createTitle": "Bildirim kanalı", @@ -95,16 +95,16 @@ "failed": "Bildirim kanalı oluşturulamadı" }, "fetch": { - "success": "", - "failed": "" + "success": "Bildirimler başarıyla alındı", + "failed": "Bildirimler alınamadı" }, "delete": { - "success": "", - "failed": "" + "success": "Bildirim başarıyla silindi", + "failed": "Bildirim silinemedi" }, "edit": { - "success": "", - "failed": "" + "success": "Bildirim başarıyla güncellendi", + "failed": "Bildirim güncellenemedi" }, "test": { "success": "Test bildirimi başarıyla gönderildi", @@ -331,7 +331,9 @@ "uploadSuccess": "Monitör başarıyla oluşturuldu!", "validationFailed": "Doğrulama başarısız oldu", "noFileSelected": "Hiçbir dosya seçilmedi", - "fallbackPage": "Toplu olarak sunucu listesini yüklemek için bir dosyayı içe aktarın" + "fallbackPage": "Toplu olarak sunucu listesini yüklemek için bir dosyayı içe aktarın", + "invalidFileType": "", + "uploadFailed": "" }, "DeleteAccountTitle": "Hesabı sil", "DeleteAccountButton": "Hesabı sil", @@ -340,11 +342,11 @@ "reset": "Sıfırla", "ignoreTLSError": "TLS/SSL hatalarını gözardı et", "tlsErrorIgnored": "TLS/SSL hataları gözardı ediliyor", - "ignoreTLSErrorDescription": "", + "ignoreTLSErrorDescription": "TLS/SSL hatalarını gözardı et ve websitesinin ayakta olduğunu denetle", "createNew": "Yeni oluştur", "greeting": { "prepend": "Merhaba", - "append": "", + "append": "Öğleden sonra muhteşem geçsin!", "overview": "" }, "roles": { @@ -365,28 +367,28 @@ "email": "Eposta", "selectRole": "Rolü seçin", "inviteLink": "Davet linki", - "cancel": "", - "noMembers": "", + "cancel": "İptal", + "noMembers": "Bu role sahip hiç üye yok", "getToken": "", "emailToken": "", "table": { - "name": "", - "email": "", - "role": "", - "created": "" + "name": "İsim", + "email": "Eposta", + "role": "Rol", + "created": "Oluşturulma" } }, "monitorState": { - "paused": "", - "resumed": "", - "active": "" + "paused": "Durduruldu", + "resumed": "Yeniden başlatıldı", + "active": "Aktif" }, "menu": { "uptime": "", - "pagespeed": "", + "pagespeed": "Sayfa hızı", "infrastructure": "", - "incidents": "", - "statusPages": "", + "incidents": "Olaylar", + "statusPages": "Durum sayfaları", "maintenance": "Bakım", "integrations": "Entegrasyonlar", "settings": "Ayarlar", @@ -399,7 +401,7 @@ "team": "Ekip", "logOut": "Çıkış yap", "notifications": "Bildirimler", - "logs": "" + "logs": "Günlükler" }, "settingsEmailUser": "E-posta kullanıcısı – Kimlik doğrulama için kullanıcı adı; belirtilirse e-posta adresinin yerine geçer.", "state": "Durum", @@ -512,11 +514,11 @@ }, "firstName": { "label": "", - "placeholder": "", + "placeholder": "Jordan", "errors": { - "empty": "", - "length": "", - "pattern": "" + "empty": "Lütfen adınızı girin", + "length": "İsim 50 karakterden az olmalıdır", + "pattern": "İsim yalnızca harfler, boşluklar, kesme işaretleri veya tireler içerebilir" } }, "lastName": { @@ -525,23 +527,28 @@ "errors": { "empty": "Lütfen soyadınızı girin", "length": "Soyadı en çok 50 karakter olmalıdır", - "pattern": "" + "pattern": "Soyad yalnızca harfler, boşluklar, kesme işaretleri veya tireler içerebilir" } } }, "errors": { - "validation": "" + "validation": "Veriyi işlerken hata oluştu." }, "fields": { "password": { "errors": { "incorrect": "" } + }, + "role": { + "errors": { + "min": "" + } } } }, "login": { - "heading": "", + "heading": "Giriş yap", "subheadings": { "stepOne": "Eposta adresinizi girin", "stepTwo": "Parolanızı girin" @@ -580,7 +587,7 @@ "superAdmin": "Süper yönetici hesabı oluşturun", "user": "" }, - "termsAndPolicies": "", + "termsAndPolicies": "Bir hesap oluşturarak Hizmet Şartları ve Gizlilik Politikası'nı kabul etmiş olursunuz.", "links": { "login": "Bir hesabınız mı var? Giriş yapın" }, @@ -593,8 +600,8 @@ "subheadings": { "stepOne": "Endişelenmeyin, sıfırlamak için gerekli bilgileri göndereceğiz.", "stepTwo": "Parola sıfırlama için gerekli bilgileri hesabına ilettik.", - "stepThree": "", - "stepFour": "" + "stepThree": "Parolanız daha önce kullandığınız paroladan farklı olmalıdır.", + "stepFour": "Parolanız başarıyla sıfırlandı. Buraya tıklayarak giriş yapabilirsiniz." }, "buttons": { "openEmail": "Eposta uygulamasını aç", @@ -612,7 +619,7 @@ }, "toasts": { "sent": "", - "emailNotFound": "", + "emailNotFound": "Eposta bulunamadı.", "redirect": "", "success": "", "error": "" @@ -675,7 +682,10 @@ "description": "", "webhookLabel": "", "webhookPlaceholder": "https://sunucu-adı.com/webhook\n" - } + }, + "testNotification": "Test bildirimi", + "dialogDeleteTitle": "Gerçekten bu bildirimi silmek istiyor musunuz?", + "dialogDeleteConfirm": "Sil" }, "notificationConfig": { "title": "Bildirimler", @@ -688,20 +698,20 @@ "down": "çalışmıyor", "paused": "beklemede" }, - "advancedMatching": "", + "advancedMatching": "Gelişmiş eşleştirme", "sendTestNotifications": "Bildirimleri dene", - "testNotificationsDisabled": "", "selectAll": "Tümünü seç", "showAdminLoginLink": "", "logsPage": { - "title": "", - "description": "", + "title": "Günlükler", + "description": "Sistem günlükleri - son 1000 satır", "tabs": { - "queue": "", - "logs": "" + "queue": "İş kuyruğu", + "logs": "Sunucu günlükleri", + "diagnostics": "Teşhis" }, "toast": { - "fetchLogsSuccess": "" + "fetchLogsSuccess": "Günlükler başarıyla alındı" }, "logLevelSelect": { "title": "", @@ -723,37 +733,40 @@ "idHeader": "", "urlHeader": "", "typeHeader": "", - "activeHeader": "", + "activeHeader": "Aktif", "lockedAtHeader": "", "runCountHeader": "", "failCountHeader": "", "lastRunHeader": "", - "lastFinishedAtHeader": "", + "lastFinishedAtHeader": "Son tamamlama", "lastRunTookHeader": "" }, "metricsTable": { "title": "", - "metricHeader": "", - "valueHeader": "" + "metricHeader": "Metrik", + "valueHeader": "Değer" }, "failedJobTable": { - "title": "", - "monitorIdHeader": "", - "monitorUrlHeader": "", - "failCountHeader": "", + "title": "Tamamlanamayan işler", + "monitorIdHeader": "Monitör ID", + "monitorUrlHeader": "Monitör URL", + "failCountHeader": "Hata sayısı", "failedAtHeader": "", "failReasonHeader": "" } }, "export": { - "title": "", - "success": "", - "failed": "" + "title": "Monitörleri dışarı al", + "success": "Monitörler başarıyla dışarıya alındı", + "failed": "Monitörleri dışarıya alırken bir hata oluştu" }, "monitorActions": { - "title": "", + "title": "Dışarı/İçeri al", "import": "", - "export": "" + "export": "", + "deleteSuccess": "Monitör başarıyla silindi", + "deleteFailed": "Monitör silinemedi", + "details": "Detaylar" }, "settingsPage": { "aboutSettings": { @@ -784,16 +797,17 @@ "labelTLSServername": "", "labelUser": "", "linkTransport": "", - "placeholderUser": "", - "title": "" + "placeholderUser": "Eğer gerekmiyorsa boş bırakabilirsiniz", + "title": "Eposta", + "toastEmailRequiredFieldsError": "" }, "pageSpeedSettings": { - "description": "", - "labelApiKeySet": "", - "labelApiKey": "", - "title": "" + "description": "Google PageSpeed izlemeyi etkinleştirmek için Google PageSpeed API anahtarınızı girin. Anahtarı güncellemek için Sıfırla'ya tıklayın.", + "labelApiKeySet": "API key tanımlandı. Sıfırla düğmesine tıklayarak değiştirebilirsiniz.", + "labelApiKey": "PageSpeed API anahtarı", + "title": "Google PageSpeed API anahtarı" }, - "saveButtonLabel": "", + "saveButtonLabel": "Kaydet", "statsSettings": { "clearAllStatsButton": "", "clearAllStatsDescription": "", @@ -802,35 +816,154 @@ "clearAllStatsDialogTitle": "", "description": "", "labelTTL": "", - "labelTTLOptional": "", - "title": "" + "labelTTLOptional": "Sonsuz için 0", + "title": "İzleme geçmişi" }, "systemResetSettings": { - "buttonRemoveAllMonitors": "", - "description": "", - "dialogConfirm": "", + "buttonRemoveAllMonitors": "Tüm monitörleri sil", + "description": "Sistemden tüm monitörleri sil", + "dialogConfirm": "Evet, tüm monitörleri sil", "dialogDescription": "", - "dialogTitle": "", - "title": "" + "dialogTitle": "Gerçekten tüm monitörleri silmek istiyor musunuz?", + "title": "Sistemi sıfırla" }, "timezoneSettings": { "description": "", - "label": "", - "title": "" + "label": "Görünüm zaman dilimi", + "title": "Görünüm zaman dilimi" }, - "title": "", + "title": "Ayarlar", "uiSettings": { - "description": "", - "labelLanguage": "", - "labelTheme": "", - "title": "" + "description": "Aydınlık ve karanlık mod arasında geçiş yapın ya da kullanıcı arayüzü dilini değiştirin.", + "labelLanguage": "Dil", + "labelTheme": "Tema modu", + "title": "Görünüm" }, "urlSettings": { "description": "", "label": "", - "selectDisabled": "", - "selectEnabled": "", + "selectDisabled": "Kapalı", + "selectEnabled": "Etkin", "title": "" } - } + }, + "statusPageCreate": { + "buttonSave": "" + }, + "incidentsOptionsHeaderFilterResolved": "", + "settingsSave": "", + "statusPageCreateAppearanceTitle": "", + "confirmPassword": "", + "monitorHooks": { + "failureAddDemoMonitors": "", + "successAddDemoMonitors": "" + }, + "settingsAppearance": "", + "settingsDisplayTimezone": "", + "settingsGeneralSettings": "", + "incidentsOptionsHeaderTotalIncidents": "", + "statusPage": { + "deleteSuccess": "Durum sayfası başarıyla silindi", + "deleteFailed": "Durum sayfası silinemedi", + "createSuccess": "Durum sayfası başarıyla oluşturuldu", + "updateSuccess": "Durum sayfası başarıyla güncellendi", + "generalSettings": "Genel ayarlar", + "contents": "İçerikler", + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "testNotificationsDisabled": "Bu izleyici için ayarlanmış bir bildirim yok. 'Yapılandır' düğmesine tıklayarak bir bildirim eklemeniz gerekiyor.", + "incidentsTableResolvedAt": "", + "incidentsTableActionResolve": "Çözümle", + "checkHooks": { + "failureResolveOne": "Olay çözümlenirken bir hata oluştu.", + "failureResolveAll": "Tüm olayları çözümlerken bir hata oluştu.", + "failureResolveMonitor": "" + }, + "checkFormError": "Formda hatalar var. Lütfen kontrol edin.", + "diagnosticsPage": { + "diagnosticDescription": "", + "statsDescription": "", + "gauges": { + "heapAllocationTitle": "", + "heapAllocationSubtitle": "", + "heapUsageTitle": "", + "heapUsageSubtitle": "", + "heapUtilizationTitle": "", + "heapUtilizationSubtitle": "", + "instantCpuUsageTitle": "", + "instantCpuUsageSubtitle": "" + }, + "stats": { + "eventLoopDelayTitle": "", + "uptimeTitle": "", + "usedHeapSizeTitle": "", + "totalHeapSizeTitle": "", + "osMemoryLimitTitle": "" + } + }, + "pageSpeedLighthouseAPI": "", + "time": { + "threeMinutes": "", + "fiveMinutes": "", + "tenMinutes": "", + "twentyMinutes": "", + "oneHour": "1 saat", + "oneDay": "1 gün", + "oneWeek": "1 hafta" + }, + "general": { + "noOptionsFound": "" + }, + "infrastructureMonitor": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "maintenanceWindow": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "pageSpeed": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "uptimeMonitor": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "editUserPage": { + "form": { + "email": "", + "firstName": "", + "lastName": "", + "role": "", + "save": "" + }, + "table": { + "actionHeader": "", + "roleHeader": "" + }, + "title": "", + "toast": { + "successUserUpdate": "", + "validationErrors": "" + } + }, + "incidentsPageActionResolveMonitor": "", + "incidentsPageActionResolveAll": "" } diff --git a/client/src/locales/zh-TW.json b/client/src/locales/zh-TW.json index 0c86b1e16..56f1841d4 100644 --- a/client/src/locales/zh-TW.json +++ b/client/src/locales/zh-TW.json @@ -8,7 +8,6 @@ "settingsFailedToSave": "", "settingsStatsCleared": "", "settingsFailedToClearStats": "", - "settingsFailedToAddDemoMonitors": "", "settingsMonitorsDeleted": "", "settingsFailedToDeleteMonitors": "", "starPromptTitle": "", @@ -86,7 +85,8 @@ "networkError": "", "fallback": { "title": "", - "checks": [""] + "checks": [""], + "actionButton": "" }, "createButton": "", "createTitle": "", @@ -331,7 +331,9 @@ "uploadSuccess": "", "validationFailed": "", "noFileSelected": "", - "fallbackPage": "" + "fallbackPage": "", + "invalidFileType": "", + "uploadFailed": "" }, "DeleteAccountTitle": "", "DeleteAccountButton": "", @@ -537,6 +539,11 @@ "errors": { "incorrect": "" } + }, + "role": { + "errors": { + "min": "" + } } } }, @@ -675,7 +682,10 @@ "description": "", "webhookLabel": "", "webhookPlaceholder": "" - } + }, + "testNotification": "", + "dialogDeleteTitle": "", + "dialogDeleteConfirm": "" }, "notificationConfig": { "title": "", @@ -690,7 +700,6 @@ }, "advancedMatching": "", "sendTestNotifications": "", - "testNotificationsDisabled": "", "selectAll": "", "showAdminLoginLink": "", "logsPage": { @@ -698,7 +707,8 @@ "description": "", "tabs": { "queue": "", - "logs": "" + "logs": "", + "diagnostics": "" }, "toast": { "fetchLogsSuccess": "" @@ -753,7 +763,10 @@ "monitorActions": { "title": "", "import": "", - "export": "" + "export": "", + "deleteSuccess": "", + "deleteFailed": "", + "details": "" }, "settingsPage": { "aboutSettings": { @@ -785,7 +798,8 @@ "labelUser": "", "linkTransport": "", "placeholderUser": "", - "title": "" + "title": "", + "toastEmailRequiredFieldsError": "" }, "pageSpeedSettings": { "description": "", @@ -832,5 +846,124 @@ "selectEnabled": "", "title": "" } - } + }, + "statusPageCreate": { + "buttonSave": "" + }, + "incidentsOptionsHeaderFilterResolved": "", + "settingsSave": "", + "statusPageCreateAppearanceTitle": "", + "confirmPassword": "", + "monitorHooks": { + "failureAddDemoMonitors": "", + "successAddDemoMonitors": "" + }, + "settingsAppearance": "", + "settingsDisplayTimezone": "", + "settingsGeneralSettings": "", + "incidentsOptionsHeaderTotalIncidents": "", + "statusPage": { + "deleteSuccess": "", + "deleteFailed": "", + "createSuccess": "", + "updateSuccess": "", + "generalSettings": "", + "contents": "", + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "testNotificationsDisabled": "", + "incidentsTableResolvedAt": "", + "incidentsTableActionResolve": "", + "checkHooks": { + "failureResolveOne": "", + "failureResolveAll": "", + "failureResolveMonitor": "" + }, + "checkFormError": "", + "diagnosticsPage": { + "diagnosticDescription": "", + "statsDescription": "", + "gauges": { + "heapAllocationTitle": "", + "heapAllocationSubtitle": "", + "heapUsageTitle": "", + "heapUsageSubtitle": "", + "heapUtilizationTitle": "", + "heapUtilizationSubtitle": "", + "instantCpuUsageTitle": "", + "instantCpuUsageSubtitle": "" + }, + "stats": { + "eventLoopDelayTitle": "", + "uptimeTitle": "", + "usedHeapSizeTitle": "", + "totalHeapSizeTitle": "", + "osMemoryLimitTitle": "" + } + }, + "pageSpeedLighthouseAPI": "", + "time": { + "threeMinutes": "", + "fiveMinutes": "", + "tenMinutes": "", + "twentyMinutes": "", + "oneHour": "", + "oneDay": "", + "oneWeek": "" + }, + "general": { + "noOptionsFound": "" + }, + "infrastructureMonitor": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "maintenanceWindow": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "pageSpeed": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "uptimeMonitor": { + "fallback": { + "checks": [""], + "title": "", + "actionButton": "" + } + }, + "editUserPage": { + "form": { + "email": "", + "firstName": "", + "lastName": "", + "role": "", + "save": "" + }, + "table": { + "actionHeader": "", + "roleHeader": "" + }, + "title": "", + "toast": { + "successUserUpdate": "", + "validationErrors": "" + } + }, + "incidentsPageActionResolveMonitor": "", + "incidentsPageActionResolveAll": "" } diff --git a/server/index.js b/server/index.js deleted file mode 100644 index 5c835b9f6..000000000 --- a/server/index.js +++ /dev/null @@ -1,55 +0,0 @@ -import { initializeServices } from "./src/config/services.js"; -import { initializeControllers } from "./src/config/controllers.js"; -import { createApp } from "./src/app.js"; -import { initShutdownListener } from "./shutdown.js"; -import logger from "./utils/logger.js"; -import { fileURLToPath } from "url"; -import path from "path"; -import fs from "fs"; - -import SettingsService from "./src/service/system/settingsService.js"; -import AppSettings from "./src/db/models/AppSettings.js"; - -const SERVICE_NAME = "Server"; - -const startApp = async () => { - // FE path - const __filename = fileURLToPath(import.meta.url); - const __dirname = path.dirname(__filename); - const openApiSpec = JSON.parse(fs.readFileSync(path.join(__dirname, "openapi.json"), "utf8")); - const frontendPath = path.join(__dirname, "public"); - // Create services - const settingsService = new SettingsService(AppSettings); - const appSettings = settingsService.loadSettings(); - - // Initialize services - const services = await initializeServices(appSettings, settingsService); - - // Initialize controllers - const controllers = initializeControllers(services); - - const app = createApp({ - services, - controllers, - appSettings, - frontendPath, - openApiSpec, - }); - - const port = appSettings.port || 52345; - const server = app.listen(port, () => { - logger.info({ message: `Server started on port:${port}` }); - }); - - initShutdownListener(server, services); -}; - -startApp().catch((error) => { - logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "startApp", - stack: error.stack, - }); - process.exit(1); -}); diff --git a/server/package-lock.json b/server/package-lock.json index d2ba13f13..a41a50518 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -13,11 +13,12 @@ "axios": "^1.7.2", "bcryptjs": "3.0.2", "bullmq": "5.41.2", - "compression": "1.8.0", + "compression": "1.8.1", "cors": "^2.8.5", "dockerode": "4.0.6", "dotenv": "^16.4.5", "express": "^4.19.2", + "express-rate-limit": "8.0.1", "handlebars": "^4.7.8", "helmet": "^8.0.0", "ioredis": "^5.4.2", @@ -2022,16 +2023,16 @@ } }, "node_modules/compression": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", - "integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "license": "MIT", "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", "debug": "2.6.9", "negotiator": "~0.6.4", - "on-headers": "~1.0.2", + "on-headers": "~1.1.0", "safe-buffer": "5.2.1", "vary": "~1.1.2" }, @@ -3159,6 +3160,24 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-rate-limit": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.0.1.tgz", + "integrity": "sha512-aZVCnybn7TVmxO4BtlmnvX+nuz8qHW124KKJ8dumsBsmv5ZLxE0pYu7S2nwyRBGHHCAzdmnGyrc5U/rksSPO7Q==", + "license": "MIT", + "dependencies": { + "ip-address": "10.0.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -3901,6 +3920,15 @@ "url": "https://opencollective.com/ioredis" } }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -5705,9 +5733,9 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "license": "MIT", "engines": { "node": ">= 0.8" diff --git a/server/package.json b/server/package.json index d3d7344e9..4768fb45f 100755 --- a/server/package.json +++ b/server/package.json @@ -20,11 +20,12 @@ "axios": "^1.7.2", "bcryptjs": "3.0.2", "bullmq": "5.41.2", - "compression": "1.8.0", + "compression": "1.8.1", "cors": "^2.8.5", "dockerode": "4.0.6", "dotenv": "^16.4.5", "express": "^4.19.2", + "express-rate-limit": "8.0.1", "handlebars": "^4.7.8", "helmet": "^8.0.0", "ioredis": "^5.4.2", diff --git a/server/src/app.js b/server/src/app.js index af53dbf56..6ba1d0d05 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -8,11 +8,13 @@ import languageMiddleware from "./middleware/languageMiddleware.js"; import swaggerUi from "swagger-ui-express"; import { handleErrors } from "./middleware/handleErrors.js"; import { setupRoutes } from "./config/routes.js"; -export const createApp = ({ services, controllers, appSettings, frontendPath, openApiSpec }) => { - const allowedOrigin = appSettings.clientHost; +import { generalApiLimiter } from "./middleware/rateLimiter.js"; + +export const createApp = ({ services, controllers, envSettings, frontendPath, openApiSpec }) => { + const allowedOrigin = envSettings.clientHost; const app = express(); - + app.use(generalApiLimiter); // Static files app.use(express.static(frontendPath)); diff --git a/server/src/config/routes.js b/server/src/config/routes.js index 64b1df474..7056393f9 100644 --- a/server/src/config/routes.js +++ b/server/src/config/routes.js @@ -1,4 +1,5 @@ import { verifyJWT } from "../middleware/verifyJWT.js"; +import { authApiLimiter } from "../middleware/rateLimiter.js"; import AuthRoutes from "../routes/authRoute.js"; import InviteRoutes from "../routes/inviteRoute.js"; @@ -25,7 +26,7 @@ export const setupRoutes = (app, controllers) => { const notificationRoutes = new NotificationRoutes(controllers.notificationController); const diagnosticRoutes = new DiagnosticRoutes(controllers.diagnosticController); - app.use("/api/v1/auth", authRoutes.getRouter()); + app.use("/api/v1/auth", authApiLimiter, authRoutes.getRouter()); app.use("/api/v1/monitors", verifyJWT, monitorRoutes.getRouter()); app.use("/api/v1/settings", verifyJWT, settingsRoutes.getRouter()); app.use("/api/v1/checks", verifyJWT, checkRoutes.getRouter()); diff --git a/server/src/config/services.js b/server/src/config/services.js index 75936c29e..4fb9424cc 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -1,5 +1,4 @@ import ServiceRegistry from "../service/system/serviceRegistry.js"; -import logger from "../utils/logger.js"; import TranslationService from "../service/system/translationService.js"; import StringService from "../service/system/stringService.js"; import MongoDB from "../db/mongo/MongoDB.js"; @@ -9,7 +8,6 @@ import BufferService from "../service/infrastructure/bufferService.js"; import StatusService from "../service/infrastructure/statusService.js"; import NotificationUtils from "../service/infrastructure/notificationUtils.js"; import NotificationService from "../service/infrastructure/notificationService.js"; -import RedisService from "../service/data/redisService.js"; import ErrorService from "../service/infrastructure/errorService.js"; import SuperSimpleQueueHelper from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js"; import SuperSimpleQueue from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js"; @@ -19,7 +17,6 @@ import DiagnosticService from "../service/business/diagnosticService.js"; import InviteService from "../service/business/inviteService.js"; import MaintenanceWindowService from "../service/business/maintenanceWindowService.js"; import MonitorService from "../service/business/monitorService.js"; -import IORedis from "ioredis"; import papaparse from "papaparse"; import axios from "axios"; import ping from "ping"; @@ -35,19 +32,101 @@ import mjml2html from "mjml"; import jwt from "jsonwebtoken"; import crypto from "crypto"; -export const initializeServices = async (appSettings, settingsService) => { +import { fileURLToPath } from "url"; +import { ObjectId } from "mongodb"; + +// DB Modules +import { NormalizeData, NormalizeDataUptimeDetails } from "../utils/dataUtils.js"; +import { GenerateAvatarImage } from "../utils/imageProcessing.js"; +import { ParseBoolean } from "../utils/utils.js"; + +// Models +import Check from "../db/models/Check.js"; +import HardwareCheck from "../db/models/HardwareCheck.js"; +import PageSpeedCheck from "../db/models/PageSpeedCheck.js"; +import Monitor from "../db/models/Monitor.js"; +import User from "../db/models/User.js"; +import InviteToken from "../db/models/InviteToken.js"; +import StatusPage from "../db/models/StatusPage.js"; +import Team from "../db/models/Team.js"; +import MaintenanceWindow from "../db/models/MaintenanceWindow.js"; +import MonitorStats from "../db/models/MonitorStats.js"; +import NetworkCheck from "../db/models/NetworkCheck.js"; +import Notification from "../db/models/Notification.js"; +import RecoveryToken from "../db/models/RecoveryToken.js"; +import AppSettings from "../db/models/AppSettings.js"; + +import InviteModule from "../db/mongo/modules/inviteModule.js"; +import CheckModule from "../db/mongo/modules/checkModule.js"; +import StatusPageModule from "../db/mongo/modules/statusPageModule.js"; +import UserModule from "../db/mongo/modules/userModule.js"; +import HardwareCheckModule from "../db/mongo/modules/hardwareCheckModule.js"; +import MaintenanceWindowModule from "../db/mongo/modules/maintenanceWindowModule.js"; +import MonitorModule from "../db/mongo/modules/monitorModule.js"; +import NetworkCheckModule from "../db/mongo/modules/networkCheckModule.js"; +import NotificationModule from "../db/mongo/modules/notificationModule.js"; +import PageSpeedCheckModule from "../db/mongo/modules/pageSpeedCheckModule.js"; +import RecoveryModule from "../db/mongo/modules/recoveryModule.js"; +import SettingsModule from "../db/mongo/modules/settingsModule.js"; + +export const initializeServices = async ({ logger, envSettings, settingsService }) => { + const serviceRegistry = new ServiceRegistry({ logger }); + ServiceRegistry.instance = serviceRegistry; + const translationService = new TranslationService(logger); await translationService.initialize(); const stringService = new StringService(translationService); // Create DB - const db = new MongoDB({ appSettings }); + const checkModule = new CheckModule({ logger, Check, HardwareCheck, PageSpeedCheck, Monitor, User }); + const inviteModule = new InviteModule({ InviteToken, crypto, stringService }); + const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, stringService }); + const userModule = new UserModule({ User, Team, GenerateAvatarImage, ParseBoolean, stringService }); + const hardwareCheckModule = new HardwareCheckModule({ HardwareCheck, Monitor, logger }); + const maintenanceWindowModule = new MaintenanceWindowModule({ MaintenanceWindow }); + const monitorModule = new MonitorModule({ + Monitor, + MonitorStats, + Check, + PageSpeedCheck, + HardwareCheck, + stringService, + fs, + path, + fileURLToPath, + ObjectId, + NormalizeData, + NormalizeDataUptimeDetails, + }); + const networkCheckModule = new NetworkCheckModule({ NetworkCheck }); + const notificationModule = new NotificationModule({ Notification, Monitor }); + const pageSpeedCheckModule = new PageSpeedCheckModule({ PageSpeedCheck }); + const recoveryModule = new RecoveryModule({ User, RecoveryToken, crypto, stringService }); + const settingsModule = new SettingsModule({ AppSettings }); + + const db = new MongoDB({ + logger, + envSettings, + checkModule, + inviteModule, + statusPageModule, + userModule, + hardwareCheckModule, + maintenanceWindowModule, + monitorModule, + networkCheckModule, + notificationModule, + pageSpeedCheckModule, + recoveryModule, + settingsModule, + }); + await db.connect(); const networkService = new NetworkService(axios, ping, logger, http, Docker, net, stringService, settingsService); const emailService = new EmailService(settingsService, fs, path, compile, mjml2html, nodemailer, logger); - const bufferService = new BufferService({ db, logger }); + const bufferService = new BufferService({ db, logger, envSettings }); const statusService = new StatusService({ db, logger, buffer: bufferService }); const notificationUtils = new NotificationUtils({ @@ -64,7 +143,6 @@ export const initializeServices = async (appSettings, settingsService) => { notificationUtils, }); - const redisService = new RedisService({ Redis: IORedis, logger }); const errorService = new ErrorService(); const superSimpleQueueHelper = new SuperSimpleQueueHelper({ @@ -76,7 +154,7 @@ export const initializeServices = async (appSettings, settingsService) => { }); const superSimpleQueue = await SuperSimpleQueue.create({ - appSettings, + envSettings, db, logger, helper: superSimpleQueueHelper, @@ -135,7 +213,6 @@ export const initializeServices = async (appSettings, settingsService) => { bufferService, statusService, notificationService, - redisService, jobQueue: superSimpleQueue, userService, checkService, diff --git a/server/src/controllers/monitorController.js b/server/src/controllers/monitorController.js index 13890e5ea..a4f391087 100755 --- a/server/src/controllers/monitorController.js +++ b/server/src/controllers/monitorController.js @@ -32,7 +32,7 @@ class MonitorController extends BaseController { } async verifyTeamAccess(teamId, monitorId) { - const monitor = await this.db.getMonitorById(monitorId); + const monitor = await this.db.monitorModule.getMonitorById(monitorId); if (!monitor.teamId.equals(teamId)) { throw this.errorService.createAuthorizationError(); } @@ -150,7 +150,7 @@ class MonitorController extends BaseController { await getCertificateParamValidation.validateAsync(req.params); const { monitorId } = req.params; - const monitor = await this.db.getMonitorById(monitorId); + const monitor = await this.db.monitorModule.getMonitorById(monitorId); const certificate = await fetchMonitorCertificate(sslChecker, monitor); return res.success({ diff --git a/server/src/controllers/notificationController.js b/server/src/controllers/notificationController.js index 081968e3d..e00dd5b6f 100755 --- a/server/src/controllers/notificationController.js +++ b/server/src/controllers/notificationController.js @@ -53,7 +53,7 @@ class NotificationController extends BaseController { body.userId = userId; body.teamId = teamId; - const notification = await this.db.createNotification(body); + const notification = await this.db.notificationModule.createNotification(body); return res.success({ msg: "Notification created successfully", data: notification, @@ -70,7 +70,7 @@ class NotificationController extends BaseController { throw this.errorService.createBadRequestError("Team ID is required"); } - const notifications = await this.db.getNotificationsByTeamId(teamId); + const notifications = await this.db.notificationModule.getNotificationsByTeamId(teamId); return res.success({ msg: "Notifications fetched successfully", @@ -88,12 +88,12 @@ class NotificationController extends BaseController { throw this.errorService.createBadRequestError("Team ID is required"); } - const notification = await this.db.getNotificationById(req.params.id); + const notification = await this.db.notificationModule.getNotificationById(req.params.id); if (!notification.teamId.equals(teamId)) { throw this.errorService.createAuthorizationError(); } - await this.db.deleteNotificationById(req.params.id); + await this.db.notificationModule.deleteNotificationById(req.params.id); return res.success({ msg: "Notification deleted successfully", }); @@ -104,7 +104,7 @@ class NotificationController extends BaseController { getNotificationById = this.asyncHandler( async (req, res) => { - const notification = await this.db.getNotificationById(req.params.id); + const notification = await this.db.notificationModule.getNotificationById(req.params.id); const teamId = req?.user?.teamId; if (!teamId) { @@ -134,13 +134,13 @@ class NotificationController extends BaseController { throw this.errorService.createBadRequestError("Team ID is required"); } - const notification = await this.db.getNotificationById(req.params.id); + const notification = await this.db.notificationModule.getNotificationById(req.params.id); if (!notification.teamId.equals(teamId)) { throw this.errorService.createAuthorizationError(); } - const editedNotification = await this.db.editNotification(req.params.id, req.body); + const editedNotification = await this.db.notificationModule.editNotification(req.params.id, req.body); return res.success({ msg: "Notification updated successfully", data: editedNotification, @@ -158,7 +158,7 @@ class NotificationController extends BaseController { throw this.errorService.createBadRequestError("Team ID is required"); } - const monitor = await this.db.getMonitorById(monitorId); + const monitor = await this.db.monitorModule.getMonitorById(monitorId); if (!monitor.teamId.equals(teamId)) { throw this.errorService.createAuthorizationError(); diff --git a/server/src/controllers/settingsController.js b/server/src/controllers/settingsController.js index c66024025..7da724d23 100755 --- a/server/src/controllers/settingsController.js +++ b/server/src/controllers/settingsController.js @@ -55,7 +55,7 @@ class SettingsController extends BaseController { async (req, res) => { await updateAppSettingsBodyValidation.validateAsync(req.body); - const updatedSettings = await this.db.updateAppSettings(req.body); + const updatedSettings = await this.db.settingsModule.updateAppSettings(req.body); const returnSettings = this.buildAppSettings(updatedSettings); return res.success({ msg: this.stringService.updateAppSettings, diff --git a/server/src/controllers/statusPageController.js b/server/src/controllers/statusPageController.js index 85211c7ff..1cbcb49a2 100755 --- a/server/src/controllers/statusPageController.js +++ b/server/src/controllers/statusPageController.js @@ -19,7 +19,7 @@ class StatusPageController extends BaseController { await imageValidation.validateAsync(req.file); const { _id, teamId } = req.user; - const statusPage = await this.db.createStatusPage({ + const statusPage = await this.db.statusPageModule.createStatusPage({ statusPageData: req.body, image: req.file, userId: _id, @@ -39,7 +39,7 @@ class StatusPageController extends BaseController { await createStatusPageBodyValidation.validateAsync(req.body); await imageValidation.validateAsync(req.file); - const statusPage = await this.db.updateStatusPage(req.body, req.file); + const statusPage = await this.db.statusPageModule.updateStatusPage(req.body, req.file); if (statusPage === null) { throw this.errorService.createNotFoundError(this.stringService.statusPageNotFound); } @@ -54,7 +54,7 @@ class StatusPageController extends BaseController { getStatusPage = this.asyncHandler( async (req, res) => { - const statusPage = await this.db.getStatusPage(); + const statusPage = await this.db.statusPageModule.getStatusPage(); return res.success({ msg: this.stringService.statusPageByUrl, data: statusPage, @@ -69,7 +69,7 @@ class StatusPageController extends BaseController { await getStatusPageParamValidation.validateAsync(req.params); await getStatusPageQueryValidation.validateAsync(req.query); - const statusPage = await this.db.getStatusPageByUrl(req.params.url, req.query.type); + const statusPage = await this.db.statusPageModule.getStatusPageByUrl(req.params.url); return res.success({ msg: this.stringService.statusPageByUrl, data: statusPage, @@ -82,7 +82,7 @@ class StatusPageController extends BaseController { getStatusPagesByTeamId = this.asyncHandler( async (req, res) => { const teamId = req.user.teamId; - const statusPages = await this.db.getStatusPagesByTeamId(teamId); + const statusPages = await this.db.statusPageModule.getStatusPagesByTeamId(teamId); return res.success({ msg: this.stringService.statusPageByTeamId, diff --git a/server/src/db/models/AppSettings.js b/server/src/db/models/AppSettings.js index cd63118e9..2313d638c 100755 --- a/server/src/db/models/AppSettings.js +++ b/server/src/db/models/AppSettings.js @@ -65,6 +65,12 @@ const AppSettingsSchema = mongoose.Schema( type: Number, default: 1, }, + globalThresholds: { + cpu: { type: Number }, + memory: { type: Number }, + disk: { type: Number }, + temperature: { type: Number }, + }, }, { timestamps: true, diff --git a/server/src/db/models/StatusPage.js b/server/src/db/models/StatusPage.js index 92cbab1cd..621749fb8 100755 --- a/server/src/db/models/StatusPage.js +++ b/server/src/db/models/StatusPage.js @@ -74,6 +74,10 @@ const StatusPageSchema = mongoose.Schema( type: Boolean, default: false, }, + customCSS: { + type: String, + default: "", + }, }, { timestamps: true } ); diff --git a/server/src/db/models/User.js b/server/src/db/models/User.js index f180cfb86..9368d98b1 100755 --- a/server/src/db/models/User.js +++ b/server/src/db/models/User.js @@ -103,12 +103,4 @@ UserSchema.methods.comparePassword = async function (submittedPassword) { const User = mongoose.model("User", UserSchema); -User.init().then(() => { - logger.info({ - message: "User model initialized", - service: "UserModel", - method: "init", - }); -}); - export default User; diff --git a/server/src/db/mongo/MongoDB.js b/server/src/db/mongo/MongoDB.js index e063214fd..0c6274320 100755 --- a/server/src/db/mongo/MongoDB.js +++ b/server/src/db/mongo/MongoDB.js @@ -1,89 +1,39 @@ import mongoose from "mongoose"; import AppSettings from "../models/AppSettings.js"; -import logger from "../../utils/logger.js"; - -//**************************************** -// User Operations -//**************************************** - -import * as userModule from "./modules/userModule.js"; - -//**************************************** -// Invite Token Operations -//**************************************** - -import * as inviteModule from "./modules/inviteModule.js"; - -//**************************************** -// Recovery Operations -//**************************************** -import * as recoveryModule from "./modules/recoveryModule.js"; - -//**************************************** -// Monitors -//**************************************** - -import * as monitorModule from "./modules/monitorModule.js"; - -//**************************************** -// Page Speed Checks -//**************************************** - -import * as pageSpeedCheckModule from "./modules/pageSpeedCheckModule.js"; - -//**************************************** -// Hardware Checks -//**************************************** -import * as hardwareCheckModule from "./modules/hardwareCheckModule.js"; - -//**************************************** -// Checks -//**************************************** - -import * as checkModule from "./modules/checkModule.js"; - -//**************************************** -// Maintenance Window -//**************************************** -import * as maintenanceWindowModule from "./modules/maintenanceWindowModule.js"; - -//**************************************** -// Notifications -//**************************************** -import * as notificationModule from "./modules/notificationModule.js"; - -//**************************************** -// AppSettings -//**************************************** -import * as settingsModule from "./modules/settingsModule.js"; - -//**************************************** -// Status Page -//**************************************** -import * as statusPageModule from "./modules/statusPageModule.js"; - -//**************************************** -// Diagnostic -//**************************************** -import * as diagnosticModule from "./modules/diagnosticModule.js"; class MongoDB { static SERVICE_NAME = "MongoDB"; - constructor({ appSettings }) { - this.appSettings = appSettings; - Object.assign(this, userModule); - Object.assign(this, inviteModule); - Object.assign(this, recoveryModule); - Object.assign(this, monitorModule); - Object.assign(this, pageSpeedCheckModule); - Object.assign(this, hardwareCheckModule); - Object.assign(this, checkModule); - Object.assign(this, maintenanceWindowModule); - Object.assign(this, notificationModule); - Object.assign(this, settingsModule); - Object.assign(this, statusPageModule); - Object.assign(this, diagnosticModule); + constructor({ + logger, + envSettings, + checkModule, + inviteModule, + statusPageModule, + userModule, + hardwareCheckModule, + maintenanceWindowModule, + monitorModule, + networkCheckModule, + notificationModule, + pageSpeedCheckModule, + recoveryModule, + settingsModule, + }) { + this.logger = logger; + this.envSettings = envSettings; + this.userModule = userModule; + this.inviteModule = inviteModule; + this.recoveryModule = recoveryModule; + this.pageSpeedCheckModule = pageSpeedCheckModule; + this.hardwareCheckModule = hardwareCheckModule; + this.checkModule = checkModule; + this.maintenanceWindowModule = maintenanceWindowModule; + this.monitorModule = monitorModule; + this.notificationModule = notificationModule; + this.settingsModule = settingsModule; + this.statusPageModule = statusPageModule; + this.networkCheckModule = networkCheckModule; } get serviceName() { @@ -92,9 +42,9 @@ class MongoDB { connect = async () => { try { - const connectionString = this.appSettings.dbConnectionString || "mongodb://localhost:27017/uptime_db"; + const connectionString = this.envSettings.dbConnectionString || "mongodb://localhost:27017/uptime_db"; await mongoose.connect(connectionString); - // If there are no AppSettings, create one + // If there are no AppSettings, create one // TODO why is this here? await AppSettings.findOneAndUpdate( {}, // empty filter to match any document {}, // empty update @@ -110,13 +60,13 @@ class MongoDB { await model.syncIndexes(); } - logger.info({ + this.logger.info({ message: "Connected to MongoDB", service: this.SERVICE_NAME, method: "connect", }); } catch (error) { - logger.error({ + this.logger.error({ message: error.message, service: this.SERVICE_NAME, method: "connect", @@ -128,12 +78,12 @@ class MongoDB { disconnect = async () => { try { - logger.info({ message: "Disconnecting from MongoDB" }); + this.logger.info({ message: "Disconnecting from MongoDB" }); await mongoose.disconnect(); - logger.info({ message: "Disconnected from MongoDB" }); + this.logger.info({ message: "Disconnected from MongoDB" }); return; } catch (error) { - logger.error({ + this.logger.error({ message: error.message, service: this.SERVICE_NAME, method: "disconnect", diff --git a/server/src/db/mongo/modules/checkModule.js b/server/src/db/mongo/modules/checkModule.js index 731d168bf..f806b5bf5 100755 --- a/server/src/db/mongo/modules/checkModule.js +++ b/server/src/db/mongo/modules/checkModule.js @@ -1,9 +1,3 @@ -import Check from "../../models/Check.js"; -import Monitor from "../../models/Monitor.js"; -import HardwareCheck from "../../models/HardwareCheck.js"; -import PageSpeedCheck from "../../models/PageSpeedCheck.js"; -import User from "../../models/User.js"; -import logger from "../../../utils/logger.js"; import { ObjectId } from "mongodb"; import { buildChecksSummaryByTeamIdPipeline } from "./checkModuleQueries.js"; @@ -17,369 +11,296 @@ const dateRangeLookup = { all: undefined, }; -/** - * Create a check for a monitor - * @async - * @param {Object} checkData - * @param {string} checkData.monitorId - * @param {boolean} checkData.status - * @param {number} checkData.responseTime - * @param {number} checkData.statusCode - * @param {string} checkData.message - * @returns {Promise} - * @throws {Error} - */ - -const createCheck = async (checkData) => { - try { - const check = await new Check({ ...checkData }).save(); - return check; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createCheck"; - throw error; +class CheckModule { + constructor({ logger, Check, HardwareCheck, PageSpeedCheck, Monitor, User }) { + this.logger = logger; + this.Check = Check; + this.HardwareCheck = HardwareCheck; + this.PageSpeedCheck = PageSpeedCheck; + this.Monitor = Monitor; + this.User = User; } -}; -const createChecks = async (checks) => { - try { - await Check.insertMany(checks, { ordered: false }); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createCheck"; - throw error; - } -}; - -/** - * Get all checks for a monitor - * @async - * @param {string} monitorId - * @returns {Promise>} - * @throws {Error} - */ -const getChecksByMonitor = async ({ monitorId, type, sortOrder, dateRange, filter, ack, page, rowsPerPage, status }) => { - try { - status = status === "true" ? true : status === "false" ? false : undefined; - page = parseInt(page); - rowsPerPage = parseInt(rowsPerPage); - - const ackStage = ack === "true" ? { ack: true } : { $or: [{ ack: false }, { ack: { $exists: false } }] }; - - // Match - const matchStage = { - monitorId: new ObjectId(monitorId), - ...(typeof status !== "undefined" && { status }), - ...(typeof ack !== "undefined" && ackStage), - ...(dateRangeLookup[dateRange] && { - createdAt: { - $gte: dateRangeLookup[dateRange], - }, - }), - }; - - if (filter !== undefined) { - switch (filter) { - case "all": - break; - case "down": - break; - case "resolve": - matchStage.statusCode = 5000; - break; - default: - logger.warn({ - message: "invalid filter", - service: SERVICE_NAME, - method: "getChecks", - }); - break; - } + createChecks = async (checks) => { + try { + await this.Check.insertMany(checks, { ordered: false }); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createCheck"; + throw error; } + }; - //Sort - sortOrder = sortOrder === "asc" ? 1 : -1; + getChecksByMonitor = async ({ monitorId, type, sortOrder, dateRange, filter, ack, page, rowsPerPage, status }) => { + try { + status = status === "true" ? true : status === "false" ? false : undefined; + page = parseInt(page); + rowsPerPage = parseInt(rowsPerPage); - // Pagination - let skip = 0; - if (page && rowsPerPage) { - skip = page * rowsPerPage; - } + const ackStage = ack === "true" ? { ack: true } : { $or: [{ ack: false }, { ack: { $exists: false } }] }; - const checkModels = { - http: Check, - ping: Check, - docker: Check, - port: Check, - pagespeed: PageSpeedCheck, - hardware: HardwareCheck, - }; - - const Model = checkModels[type]; - - const checks = await Model.aggregate([ - { $match: matchStage }, - { $sort: { createdAt: sortOrder } }, - { - $facet: { - summary: [{ $count: "checksCount" }], - checks: [{ $skip: skip }, { $limit: rowsPerPage }], - }, - }, - { - $project: { - checksCount: { - $ifNull: [{ $arrayElemAt: ["$summary.checksCount", 0] }, 0], + // Match + const matchStage = { + monitorId: new ObjectId(monitorId), + ...(typeof status !== "undefined" && { status }), + ...(typeof ack !== "undefined" && ackStage), + ...(dateRangeLookup[dateRange] && { + createdAt: { + $gte: dateRangeLookup[dateRange], }, - checks: { - $ifNull: ["$checks", []], + }), + }; + + if (filter !== undefined) { + switch (filter) { + case "all": + break; + case "down": + break; + case "resolve": + matchStage.statusCode = 5000; + break; + default: + this.logger.warn({ + message: "invalid filter", + service: SERVICE_NAME, + method: "getChecks", + }); + break; + } + } + + //Sort + sortOrder = sortOrder === "asc" ? 1 : -1; + + // Pagination + let skip = 0; + if (page && rowsPerPage) { + skip = page * rowsPerPage; + } + + const checkModels = { + http: this.Check, + ping: this.Check, + docker: this.Check, + port: this.Check, + pagespeed: this.PageSpeedCheck, + hardware: this.HardwareCheck, + }; + + const Model = checkModels[type]; + + const checks = await Model.aggregate([ + { $match: matchStage }, + { $sort: { createdAt: sortOrder } }, + { + $facet: { + summary: [{ $count: "checksCount" }], + checks: [{ $skip: skip }, { $limit: rowsPerPage }], }, }, - }, - ]); - return checks[0]; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getChecks"; - throw error; - } -}; - -const getChecksByTeam = async ({ sortOrder, dateRange, filter, ack, page, rowsPerPage, teamId }) => { - try { - page = parseInt(page); - rowsPerPage = parseInt(rowsPerPage); - - const ackStage = ack === "true" ? { ack: true } : { $or: [{ ack: false }, { ack: { $exists: false } }] }; - - const matchStage = { - teamId: new ObjectId(teamId), - status: false, - ...(typeof ack !== "undefined" && ackStage), - ...(dateRangeLookup[dateRange] && { - createdAt: { - $gte: dateRangeLookup[dateRange], + { + $project: { + checksCount: { + $ifNull: [{ $arrayElemAt: ["$summary.checksCount", 0] }, 0], + }, + checks: { + $ifNull: ["$checks", []], + }, + }, }, - }), - }; - // Add filter to match stage - if (filter !== undefined) { - switch (filter) { - case "all": - break; - case "down": - break; - case "resolve": - matchStage.statusCode = 5000; - break; - default: - logger.warn({ - message: "invalid filter", - service: SERVICE_NAME, - method: "getChecksByTeam", - }); - break; + ]); + return checks[0]; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getChecks"; + throw error; + } + }; + + getChecksByTeam = async ({ sortOrder, dateRange, filter, ack, page, rowsPerPage, teamId }) => { + try { + page = parseInt(page); + rowsPerPage = parseInt(rowsPerPage); + + const ackStage = ack === "true" ? { ack: true } : { $or: [{ ack: false }, { ack: { $exists: false } }] }; + + const matchStage = { + teamId: new ObjectId(teamId), + status: false, + ...(typeof ack !== "undefined" && ackStage), + ...(dateRangeLookup[dateRange] && { + createdAt: { + $gte: dateRangeLookup[dateRange], + }, + }), + }; + // Add filter to match stage + if (filter !== undefined) { + switch (filter) { + case "all": + break; + case "down": + break; + case "resolve": + matchStage.statusCode = 5000; + break; + default: + this.logger.warn({ + message: "invalid filter", + service: SERVICE_NAME, + method: "getChecksByTeam", + }); + break; + } } + + sortOrder = sortOrder === "asc" ? 1 : -1; + + // pagination + let skip = 0; + if (page && rowsPerPage) { + skip = page * rowsPerPage; + } + + const aggregatePipeline = [ + { $match: matchStage }, + { + $unionWith: { + coll: "hardwarechecks", + pipeline: [{ $match: matchStage }], + }, + }, + { + $unionWith: { + coll: "pagespeedchecks", + pipeline: [{ $match: matchStage }], + }, + }, + + { $sort: { createdAt: sortOrder } }, + { + $facet: { + summary: [{ $count: "checksCount" }], + checks: [{ $skip: skip }, { $limit: rowsPerPage }], + }, + }, + { + $project: { + checksCount: { $arrayElemAt: ["$summary.checksCount", 0] }, + checks: "$checks", + }, + }, + ]; + + const checks = await this.Check.aggregate(aggregatePipeline); + return checks[0]; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getChecksByTeam"; + throw error; + } + }; + + ackCheck = async (checkId, teamId, ack) => { + try { + const updatedCheck = await this.Check.findOneAndUpdate({ _id: checkId, teamId: teamId }, { $set: { ack, ackAt: new Date() } }, { new: true }); + + if (!updatedCheck) { + throw new Error("Check not found"); + } + + return updatedCheck; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "ackCheck"; + throw error; + } + }; + + ackAllChecks = async (monitorId, teamId, ack, path) => { + try { + const updatedChecks = await this.Check.updateMany(path === "monitor" ? { monitorId } : { teamId }, { $set: { ack, ackAt: new Date() } }); + return updatedChecks.modifiedCount; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "ackAllChecks"; + throw error; + } + }; + + getChecksSummaryByTeamId = async ({ teamId }) => { + try { + const matchStage = { + teamId: new ObjectId(teamId), + }; + const checks = await this.Check.aggregate(buildChecksSummaryByTeamIdPipeline({ matchStage })); + return checks[0].summary; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getChecksSummaryByTeamId"; + throw error; + } + }; + deleteChecks = async (monitorId) => { + try { + const result = await this.Check.deleteMany({ monitorId }); + return result.deletedCount; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteChecks"; + throw error; + } + }; + + deleteChecksByTeamId = async (teamId) => { + try { + // Find all monitor IDs for this team (only get _id field for efficiency) + const teamMonitors = await this.Monitor.find({ teamId }, { _id: 1 }); + const monitorIds = teamMonitors.map((monitor) => monitor._id); + + // Delete all checks for these monitors in one operation + const deleteResult = await this.Check.deleteMany({ monitorId: { $in: monitorIds } }); + + return deleteResult.deletedCount; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteChecksByTeamId"; + throw error; + } + }; + + updateChecksTTL = async (teamId, ttl) => { + try { + await this.Check.collection.dropIndex("expiry_1"); + } catch (error) { + this.logger.error({ + message: error.message, + service: SERVICE_NAME, + method: "updateChecksTTL", + stack: error.stack, + }); } - sortOrder = sortOrder === "asc" ? 1 : -1; - - // pagination - let skip = 0; - if (page && rowsPerPage) { - skip = page * rowsPerPage; + try { + await this.Check.collection.createIndex( + { expiry: 1 }, + { expireAfterSeconds: ttl } // TTL in seconds, adjust as necessary + ); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "updateChecksTTL"; + throw error; } - - const aggregatePipeline = [ - { $match: matchStage }, - { - $unionWith: { - coll: "hardwarechecks", - pipeline: [{ $match: matchStage }], - }, - }, - { - $unionWith: { - coll: "pagespeedchecks", - pipeline: [{ $match: matchStage }], - }, - }, - - { $sort: { createdAt: sortOrder } }, - { - $facet: { - summary: [{ $count: "checksCount" }], - checks: [{ $skip: skip }, { $limit: rowsPerPage }], - }, - }, - { - $project: { - checksCount: { $arrayElemAt: ["$summary.checksCount", 0] }, - checks: "$checks", - }, - }, - ]; - - const checks = await Check.aggregate(aggregatePipeline); - return checks[0]; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getChecksByTeam"; - throw error; - } -}; - -/** - * Update the acknowledgment status of a check - * @async - * @param {string} checkId - The ID of the check to update - * @param {string} teamId - The ID of the team - * @param {boolean} ack - The acknowledgment status to set - * @returns {Promise} - * @throws {Error} - */ -const ackCheck = async (checkId, teamId, ack) => { - try { - const updatedCheck = await Check.findOneAndUpdate({ _id: checkId, teamId: teamId }, { $set: { ack, ackAt: new Date() } }, { new: true }); - - if (!updatedCheck) { - throw new Error("Check not found"); + // Update user + try { + await this.User.updateMany({ teamId: teamId }, { checkTTL: ttl }); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "updateChecksTTL"; + throw error; } + }; +} - return updatedCheck; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "ackCheck"; - throw error; - } -}; - -/** - * Update the acknowledgment status of all checks for a monitor or team - * @async - * @param {string} id - The monitor ID or team ID - * @param {boolean} ack - The acknowledgment status to set - * @param {string} path - The path type ('monitor' or 'team') - * @returns {Promise} - * @throws {Error} - */ -const ackAllChecks = async (monitorId, teamId, ack, path) => { - try { - const updatedChecks = await Check.updateMany(path === "monitor" ? { monitorId } : { teamId }, { $set: { ack, ackAt: new Date() } }); - return updatedChecks.modifiedCount; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "ackAllChecks"; - throw error; - } -}; - -/** - * Get checks and summary by team ID - * @async - * @param {string} teamId - * @returns {Promise} - * @throws {Error} - */ -const getChecksSummaryByTeamId = async ({ teamId }) => { - try { - const matchStage = { - teamId: new ObjectId(teamId), - }; - const checks = await Check.aggregate(buildChecksSummaryByTeamIdPipeline({ matchStage })); - return checks[0].summary; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getChecksSummaryByTeamId"; - throw error; - } -}; - -/** - * Delete all checks for a monitor - * @async - * @param {string} monitorId - * @returns {number} - * @throws {Error} - */ - -const deleteChecks = async (monitorId) => { - try { - const result = await Check.deleteMany({ monitorId }); - return result.deletedCount; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteChecks"; - throw error; - } -}; - -/** - * Delete all checks for a team - * @async - * @param {string} monitorId - * @returns {number} - * @throws {Error} - */ - -const deleteChecksByTeamId = async (teamId) => { - try { - // Find all monitor IDs for this team (only get _id field for efficiency) - const teamMonitors = await Monitor.find({ teamId }, { _id: 1 }); - const monitorIds = teamMonitors.map((monitor) => monitor._id); - - // Delete all checks for these monitors in one operation - const deleteResult = await Check.deleteMany({ monitorId: { $in: monitorIds } }); - - return deleteResult.deletedCount; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteChecksByTeamId"; - throw error; - } -}; - -const updateChecksTTL = async (teamId, ttl) => { - try { - await Check.collection.dropIndex("expiry_1"); - } catch (error) { - logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "updateChecksTTL", - stack: error.stack, - }); - } - - try { - await Check.collection.createIndex( - { expiry: 1 }, - { expireAfterSeconds: ttl } // TTL in seconds, adjust as necessary - ); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "updateChecksTTL"; - throw error; - } - // Update user - try { - await User.updateMany({ teamId: teamId }, { checkTTL: ttl }); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "updateChecksTTL"; - throw error; - } -}; - -export { - createCheck, - createChecks, - getChecksByMonitor, - getChecksByTeam, - ackCheck, - ackAllChecks, - getChecksSummaryByTeamId, - deleteChecks, - deleteChecksByTeamId, - updateChecksTTL, -}; +export default CheckModule; diff --git a/server/src/db/mongo/modules/diagnosticModule.js b/server/src/db/mongo/modules/diagnosticModule.js deleted file mode 100755 index 8717720a3..000000000 --- a/server/src/db/mongo/modules/diagnosticModule.js +++ /dev/null @@ -1,48 +0,0 @@ -import Monitor from "../../models/Monitor.js"; -import { ObjectId } from "mongodb"; - -const SERVICE_NAME = "diagnosticModule"; -import { buildMonitorSummaryByTeamIdPipeline, buildMonitorsByTeamIdPipeline, buildFilteredMonitorsByTeamIdPipeline } from "./monitorModuleQueries.js"; - -const getMonitorsByTeamIdExecutionStats = async (req) => { - try { - let { limit, type, page, rowsPerPage, filter, field, order } = req.query; - limit = parseInt(limit); - page = parseInt(page); - rowsPerPage = parseInt(rowsPerPage); - if (field === undefined) { - field = "name"; - order = "asc"; - } - // Build match stage - const matchStage = { teamId: new ObjectId(req.params.teamId) }; - if (type !== undefined) { - matchStage.type = Array.isArray(type) ? { $in: type } : type; - } - - const summary = await Monitor.aggregate(buildMonitorSummaryByTeamIdPipeline({ matchStage })).explain("executionStats"); - - const monitors = await Monitor.aggregate(buildMonitorsByTeamIdPipeline({ matchStage, field, order })).explain("executionStats"); - - const filteredMonitors = await Monitor.aggregate( - buildFilteredMonitorsByTeamIdPipeline({ - matchStage, - filter, - page, - rowsPerPage, - field, - order, - limit, - type, - }) - ).explain("executionStats"); - - return { summary, monitors, filteredMonitors }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorSummaryByTeamIdExecutionStats"; - throw error; - } -}; - -export { getMonitorsByTeamIdExecutionStats }; diff --git a/server/src/db/mongo/modules/hardwareCheckModule.js b/server/src/db/mongo/modules/hardwareCheckModule.js index bfb9b0730..448e72fed 100755 --- a/server/src/db/mongo/modules/hardwareCheckModule.js +++ b/server/src/db/mongo/modules/hardwareCheckModule.js @@ -1,70 +1,22 @@ -import HardwareCheck from "../../models/HardwareCheck.js"; -import Monitor from "../../models/Monitor.js"; -import logger from "../../../utils/logger.js"; - const SERVICE_NAME = "hardwareCheckModule"; -const createHardwareCheck = async (hardwareCheckData) => { - try { - const { monitorId, status } = hardwareCheckData; - const n = (await HardwareCheck.countDocuments({ monitorId })) + 1; - const monitor = await Monitor.findById(monitorId); - if (!monitor) { - logger.error({ - message: "Monitor not found", - service: SERVICE_NAME, - method: "createHardwareCheck", - details: `monitor ID: ${monitorId}`, - }); - return null; +class HardwareCheckModule { + constructor({ HardwareCheck, Monitor, logger }) { + this.HardwareCheck = HardwareCheck; + this.Monitor = Monitor; + this.logger = logger; + } + + createHardwareChecks = async (hardwareChecks) => { + try { + await this.HardwareCheck.insertMany(hardwareChecks, { ordered: false }); + return true; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createHardwareChecks"; + throw error; } + }; +} - let newUptimePercentage; - if (monitor.uptimePercentage === undefined) { - newUptimePercentage = status === true ? 1 : 0; - } else { - newUptimePercentage = (monitor.uptimePercentage * (n - 1) + (status === true ? 1 : 0)) / n; - } - - await Monitor.findOneAndUpdate({ _id: monitorId }, { uptimePercentage: newUptimePercentage }); - - const hardwareCheck = await new HardwareCheck({ - ...hardwareCheckData, - }).save(); - return hardwareCheck; - } catch (error) { - logger.error({ - message: "Error creating hardware check", - service: SERVICE_NAME, - method: "createHardwareCheck", - stack: error.stack, - }); - error.service = SERVICE_NAME; - error.method = "createHardwareCheck"; - throw error; - } -}; - -const createHardwareChecks = async (hardwareChecks) => { - try { - await HardwareCheck.insertMany(hardwareChecks, { ordered: false }); - return true; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createHardwareChecks"; - throw error; - } -}; - -const deleteHardwareChecksByMonitorId = async (monitorId) => { - try { - const result = await HardwareCheck.deleteMany({ monitorId }); - return result.deletedCount; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteHardwareChecksByMonitorId"; - throw error; - } -}; - -export { createHardwareCheck, createHardwareChecks, deleteHardwareChecksByMonitorId }; +export default HardwareCheckModule; diff --git a/server/src/db/mongo/modules/inviteModule.js b/server/src/db/mongo/modules/inviteModule.js index 89d8ad8d7..444e51efd 100755 --- a/server/src/db/mongo/modules/inviteModule.js +++ b/server/src/db/mongo/modules/inviteModule.js @@ -1,89 +1,56 @@ -import InviteToken from "../../models/InviteToken.js"; -import crypto from "crypto"; -import ServiceRegistry from "../../../service/system/serviceRegistry.js"; -import StringService from "../../../service/system/stringService.js"; - const SERVICE_NAME = "inviteModule"; -/** - * Request an invite token for a user. - * - * This function deletes any existing invite tokens for the user's email, - * generates a new token, saves it, and then returns the new token. - * - * @param {Object} userData - The user data. - * @param {string} userData.email - The user's email. - * @param {mongoose.Schema.Types.ObjectId} userData.teamId - The ID of the team. - * @param {Array} userData.role - The user's role(s). - * @param {Date} [userData.expiry=Date.now] - The expiry date of the token. Defaults to the current date and time. - * @returns {Promise} The invite token. - * @throws {Error} If there is an error. - */ -const requestInviteToken = async (userData) => { - try { - await InviteToken.deleteMany({ email: userData.email }); - userData.token = crypto.randomBytes(32).toString("hex"); - let inviteToken = new InviteToken(userData); - await inviteToken.save(); - return inviteToken; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "requestInviteToken"; - throw error; - } -}; -/** - * Retrieves an invite token - * - * This function searches for an invite token in the database and deletes it. - * If the invite token is not found, it throws an error. - * - * @param {string} token - The invite token to search for. - * @returns {Promise} The invite token data. - * @throws {Error} If the invite token is not found or there is another error. - */ -const getInviteToken = async (token) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - try { - const invite = await InviteToken.findOne({ - token, - }); - if (invite === null) { - throw new Error(stringService.authInviteNotFound); +class InviteModule { + constructor({ InviteToken, crypto, stringService }) { + this.InviteToken = InviteToken; + this.crypto = crypto; + this.stringService = stringService; + } + + requestInviteToken = async (userData) => { + try { + await this.InviteToken.deleteMany({ email: userData.email }); + userData.token = this.crypto.randomBytes(32).toString("hex"); + let inviteToken = new this.InviteToken(userData); + await inviteToken.save(); + return inviteToken; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "requestInviteToken"; + throw error; } - return invite; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getInviteToken"; - throw error; - } -}; + }; -/** - * Retrieves and deletes an invite token - * - * This function searches for an invite token in the database and deletes it. - * If the invite token is not found, it throws an error. - * - * @param {string} token - The invite token to search for. - * @returns {Promise} The invite token data. - * @throws {Error} If the invite token is not found or there is another error. - */ -const getInviteTokenAndDelete = async (token) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - try { - const invite = await InviteToken.findOneAndDelete({ - token, - }); - if (invite === null) { - throw new Error(stringService.authInviteNotFound); + getInviteToken = async (token) => { + try { + const invite = await this.InviteToken.findOne({ + token, + }); + if (invite === null) { + throw new Error(this.stringService.authInviteNotFound); + } + return invite; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getInviteToken"; + throw error; } - return invite; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getInviteTokenAndDelete"; - throw error; - } -}; + }; + getInviteTokenAndDelete = async (token) => { + try { + const invite = await this.InviteToken.findOneAndDelete({ + token, + }); + if (invite === null) { + throw new Error(this.stringService.authInviteNotFound); + } + return invite; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getInviteTokenAndDelete"; + throw error; + } + }; +} -export { requestInviteToken, getInviteToken, getInviteTokenAndDelete }; +export default InviteModule; diff --git a/server/src/db/mongo/modules/maintenanceWindowModule.js b/server/src/db/mongo/modules/maintenanceWindowModule.js index f7fea4636..61523c9f0 100755 --- a/server/src/db/mongo/modules/maintenanceWindowModule.js +++ b/server/src/db/mongo/modules/maintenanceWindowModule.js @@ -1,192 +1,106 @@ -import MaintenanceWindow from "../../models/MaintenanceWindow.js"; const SERVICE_NAME = "maintenanceWindowModule"; -/** - * Asynchronously creates a new MaintenanceWindow document and saves it to the database. - * If the maintenance window is a one-time event, the expiry field is set to the same value as the end field. - * @async - * @function createMaintenanceWindow - * @param {Object} maintenanceWindowData - The data for the new MaintenanceWindow document. - * @param {mongoose.Schema.Types.ObjectId} maintenanceWindowData.monitorId - The ID of the monitor. - * @param {Boolean} maintenanceWindowData.active - Indicates whether the maintenance window is active. - * @param {Boolean} maintenanceWindowData.oneTime - Indicates whether the maintenance window is a one-time event. - * @param {Date} maintenanceWindowData.start - The start date and time of the maintenance window. - * @param {Date} maintenanceWindowData.end - The end date and time of the maintenance window. - * @returns {Promise} The saved MaintenanceWindow document. - * @throws {Error} If there is an error saving the document. - */ -const createMaintenanceWindow = async (maintenanceWindowData) => { - try { - const maintenanceWindow = new MaintenanceWindow({ - ...maintenanceWindowData, - }); +class MaintenanceWindowModule { + constructor({ MaintenanceWindow }) { + this.MaintenanceWindow = MaintenanceWindow; + } + createMaintenanceWindow = async (maintenanceWindowData) => { + try { + const maintenanceWindow = new this.MaintenanceWindow({ + ...maintenanceWindowData, + }); - // If the maintenance window is a one time window, set the expiry to the end date - if (maintenanceWindowData.oneTime) { - maintenanceWindow.expiry = maintenanceWindowData.end; + // If the maintenance window is a one time window, set the expiry to the end date + if (maintenanceWindowData.oneTime) { + maintenanceWindow.expiry = maintenanceWindowData.end; + } + const result = await maintenanceWindow.save(); + return result; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createMaintenanceWindow"; + throw error; } - const result = await maintenanceWindow.save(); - return result; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createMaintenanceWindow"; - throw error; - } -}; - -const getMaintenanceWindowById = async ({ id, teamId }) => { - try { - const maintenanceWindow = await MaintenanceWindow.findOne({ - _id: id, - teamId: teamId, - }); - return maintenanceWindow; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMaintenanceWindowById"; - throw error; - } -}; - -/** - * Asynchronously retrieves all MaintenanceWindow documents associated with a specific team ID. - * @async - * @function getMaintenanceWindowByUserId - * @param {String} teamId - The ID of the team. - * @param {Object} query - The request body. - * @returns {Promise>} An array of MaintenanceWindow documents. - * @throws {Error} If there is an error retrieving the documents. - */ -const getMaintenanceWindowsByTeamId = async (teamId, query) => { - try { - let { active, page, rowsPerPage, field, order } = query || {}; - const maintenanceQuery = { teamId }; - - if (active !== undefined) maintenanceQuery.active = active; - - const maintenanceWindowCount = await MaintenanceWindow.countDocuments(maintenanceQuery); - - // Pagination - let skip = 0; - if (page && rowsPerPage) { - skip = page * rowsPerPage; + }; + getMaintenanceWindowById = async ({ id, teamId }) => { + try { + const maintenanceWindow = await this.MaintenanceWindow.findOne({ + _id: id, + teamId: teamId, + }); + return maintenanceWindow; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMaintenanceWindowById"; + throw error; } + }; - // Sorting - let sort = {}; - if (field !== undefined && order !== undefined) { - sort[field] = order === "asc" ? 1 : -1; + getMaintenanceWindowsByTeamId = async (teamId, query) => { + try { + let { active, page, rowsPerPage, field, order } = query || {}; + const maintenanceQuery = { teamId }; + + if (active !== undefined) maintenanceQuery.active = active; + + const maintenanceWindowCount = await this.MaintenanceWindow.countDocuments(maintenanceQuery); + + // Pagination + let skip = 0; + if (page && rowsPerPage) { + skip = page * rowsPerPage; + } + + // Sorting + let sort = {}; + if (field !== undefined && order !== undefined) { + sort[field] = order === "asc" ? 1 : -1; + } + + const maintenanceWindows = await this.MaintenanceWindow.find(maintenanceQuery).skip(skip).limit(rowsPerPage).sort(sort); + + return { maintenanceWindows, maintenanceWindowCount }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMaintenanceWindowByUserId"; + throw error; } + }; + getMaintenanceWindowsByMonitorId = async ({ monitorId, teamId }) => { + try { + const maintenanceWindows = await this.MaintenanceWindow.find({ + monitorId: monitorId, + teamId: teamId, + }); + return maintenanceWindows; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMaintenanceWindowsByMonitorId"; + throw error; + } + }; - const maintenanceWindows = await MaintenanceWindow.find(maintenanceQuery).skip(skip).limit(rowsPerPage).sort(sort); + deleteMaintenanceWindowById = async ({ id, teamId }) => { + try { + const maintenanceWindow = await this.MaintenanceWindow.findOneAndDelete({ _id: id, teamId }); + return maintenanceWindow; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteMaintenanceWindowById"; + throw error; + } + }; - return { maintenanceWindows, maintenanceWindowCount }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMaintenanceWindowByUserId"; - throw error; - } -}; + editMaintenanceWindowById = async ({ id, body }) => { + try { + const editedMaintenanceWindow = await this.MaintenanceWindow.findByIdAndUpdate(id, body, { new: true }); + return editedMaintenanceWindow; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "editMaintenanceWindowById"; + throw error; + } + }; +} -/** - * Asynchronously retrieves all MaintenanceWindow documents associated with a specific monitor ID. - * @async - * @function getMaintenanceWindowsByMonitorId - * @param {mongoose.Schema.Types.ObjectId} monitorId - The ID of the monitor. - * @returns {Promise>} An array of MaintenanceWindow documents. - * @throws {Error} If there is an error retrieving the documents. - */ -const getMaintenanceWindowsByMonitorId = async ({ monitorId, teamId }) => { - try { - const maintenanceWindows = await MaintenanceWindow.find({ - monitorId: monitorId, - teamId: teamId, - }); - return maintenanceWindows; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMaintenanceWindowsByMonitorId"; - throw error; - } -}; - -/** - * Asynchronously deletes a MaintenanceWindow document by its ID. - * @async - * @function deleteMaintenanceWindowById - * @param {mongoose.Schema.Types.ObjectId} maintenanceWindowId - The ID of the MaintenanceWindow document to delete. - * @returns {Promise} The deleted MaintenanceWindow document. - * @throws {Error} If there is an error deleting the document. - */ -const deleteMaintenanceWindowById = async ({ id, teamId }) => { - try { - const maintenanceWindow = await MaintenanceWindow.findOneAndDelete({ _id: id, teamId }); - return maintenanceWindow; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteMaintenanceWindowById"; - throw error; - } -}; - -/** - * Asynchronously deletes all MaintenanceWindow documents associated with a specific monitor ID. - * @async - * @function deleteMaintenanceWindowByMonitorId - * @param {mongoose.Schema.Types.ObjectId} monitorId - The ID of the monitor. - * @returns {Promise} The result of the delete operation. This object contains information about the operation, such as the number of documents deleted. - * @throws {Error} If there is an error deleting the documents. - * @example - */ -const deleteMaintenanceWindowByMonitorId = async (monitorId) => { - try { - const result = await MaintenanceWindow.deleteMany({ monitorId: monitorId }); - return result; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteMaintenanceWindowByMonitorId"; - throw error; - } -}; - -/** - * Asynchronously deletes all MaintenanceWindow documents associated with a specific user ID. - * @async - * @function deleteMaintenanceWindowByUserId - * @param {String} userId - The ID of the user. - * @returns {Promise} The result of the delete operation. This object contains information about the operation, such as the number of documents deleted. - * @throws {Error} If there is an error deleting the documents. - * @example - */ -const deleteMaintenanceWindowByUserId = async (userId) => { - try { - const result = await MaintenanceWindow.deleteMany({ userId: userId }); - return result; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteMaintenanceWindowByUserId"; - throw error; - } -}; - -const editMaintenanceWindowById = async ({ id, body, teamId }) => { - try { - const editedMaintenanceWindow = await MaintenanceWindow.findByIdAndUpdate(id, body, { new: true }); - return editedMaintenanceWindow; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "editMaintenanceWindowById"; - throw error; - } -}; - -export { - createMaintenanceWindow, - getMaintenanceWindowById, - getMaintenanceWindowsByTeamId, - getMaintenanceWindowsByMonitorId, - deleteMaintenanceWindowById, - deleteMaintenanceWindowByMonitorId, - deleteMaintenanceWindowByUserId, - editMaintenanceWindowById, -}; +export default MaintenanceWindowModule; diff --git a/server/src/db/mongo/modules/monitorModule.js b/server/src/db/mongo/modules/monitorModule.js index 6e2e80f49..76f055f94 100755 --- a/server/src/db/mongo/modules/monitorModule.js +++ b/server/src/db/mongo/modules/monitorModule.js @@ -1,16 +1,3 @@ -import Monitor from "../../models/Monitor.js"; -import MonitorStats from "../../models/MonitorStats.js"; -import Check from "../../models/Check.js"; -import PageSpeedCheck from "../../models/PageSpeedCheck.js"; -import HardwareCheck from "../../models/HardwareCheck.js"; -import { NormalizeData, NormalizeDataUptimeDetails } from "../../../utils/dataUtils.js"; -import ServiceRegistry from "../../../service/system/serviceRegistry.js"; -import StringService from "../../../service/system/stringService.js"; -import fs from "fs"; -import path from "path"; -import { fileURLToPath } from "url"; -import { ObjectId } from "mongodb"; - import { buildUptimeDetailsPipeline, buildHardwareDetailsPipeline, @@ -20,476 +7,351 @@ import { buildMonitorsWithChecksByTeamIdPipeline, buildFilteredMonitorsByTeamIdPipeline, } from "./monitorModuleQueries.js"; -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -const demoMonitorsPath = path.resolve(__dirname, "../../../utils/demoMonitors.json"); -const demoMonitors = JSON.parse(fs.readFileSync(demoMonitorsPath, "utf8")); const SERVICE_NAME = "monitorModule"; -const CHECK_MODEL_LOOKUP = { - http: Check, - ping: Check, - docker: Check, - port: Check, - pagespeed: PageSpeedCheck, - hardware: HardwareCheck, -}; +class MonitorModule { + constructor({ + Monitor, + MonitorStats, + Check, + PageSpeedCheck, + HardwareCheck, + stringService, + fs, + path, + fileURLToPath, + ObjectId, + NormalizeData, + NormalizeDataUptimeDetails, + }) { + this.Monitor = Monitor; + this.MonitorStats = MonitorStats; + this.Check = Check; + this.PageSpeedCheck = PageSpeedCheck; + this.HardwareCheck = HardwareCheck; + this.stringService = stringService; + this.fs = fs; + this.path = path; + this.fileURLToPath = fileURLToPath; + this.ObjectId = ObjectId; + this.NormalizeData = NormalizeData; + this.NormalizeDataUptimeDetails = NormalizeDataUptimeDetails; -/** - * Get all monitors - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise>} - * @throws {Error} - */ -const getAllMonitors = async (req, res) => { - try { - const monitors = await Monitor.find(); - return monitors; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getAllMonitors"; - throw error; + this.CHECK_MODEL_LOOKUP = { + http: Check, + ping: Check, + docker: Check, + port: Check, + pagespeed: PageSpeedCheck, + hardware: HardwareCheck, + }; + + const __filename = fileURLToPath(import.meta.url); + const __dirname = path.dirname(__filename); + + this.demoMonitorsPath = path.resolve(__dirname, "../../../utils/demoMonitors.json"); } -}; -/** - * Function to calculate uptime duration based on the most recent check. - * @param {Array} checks Array of check objects. - * @returns {number} Uptime duration in ms. - */ -const calculateUptimeDuration = (checks) => { - if (!checks || checks.length === 0) { - return 0; - } - const latestCheck = new Date(checks[0].createdAt); - let latestDownCheck = 0; - - for (let i = checks.length - 1; i >= 0; i--) { - if (checks[i].status === false) { - latestDownCheck = new Date(checks[i].createdAt); - break; + // Helper + calculateUptimeDuration = (checks) => { + if (!checks || checks.length === 0) { + return 0; } - } + const latestCheck = new Date(checks[0].createdAt); + let latestDownCheck = 0; - // If no down check is found, uptime is from the last check to now - if (latestDownCheck === 0) { - return Date.now() - new Date(checks[checks.length - 1].createdAt); - } + for (let i = checks.length - 1; i >= 0; i--) { + if (checks[i].status === false) { + latestDownCheck = new Date(checks[i].createdAt); + break; + } + } - // Otherwise the uptime is from the last check to the last down check - return latestCheck - latestDownCheck; -}; + // If no down check is found, uptime is from the last check to now + if (latestDownCheck === 0) { + return Date.now() - new Date(checks[checks.length - 1].createdAt); + } -/** - * Helper function to get duration since last check - * @param {Array} checks Array of check objects. - * @returns {number} Timestamp of the most recent check. - */ -const getLastChecked = (checks) => { - if (!checks || checks.length === 0) { - return 0; // Handle case when no checks are available - } - // Data is sorted newest->oldest, so last check is the most recent - return new Date() - new Date(checks[0].createdAt); -}; - -/** - * Helper function to get latestResponseTime - * @param {Array} checks Array of check objects. - * @returns {number} Timestamp of the most recent check. - */ -const getLatestResponseTime = (checks) => { - if (!checks || checks.length === 0) { - return 0; - } - - return checks[0]?.responseTime ?? 0; -}; - -/** - * Helper function to get average response time - * @param {Array} checks Array of check objects. - * @returns {number} Timestamp of the most recent check. - */ -const getAverageResponseTime = (checks) => { - if (!checks || checks.length === 0) { - return 0; - } - - const validChecks = checks.filter((check) => typeof check.responseTime === "number"); - if (validChecks.length === 0) { - return 0; - } - const aggResponseTime = validChecks.reduce((sum, check) => { - return sum + check.responseTime; - }, 0); - return aggResponseTime / validChecks.length; -}; - -/** - * Helper function to get percentage 24h uptime - * @param {Array} checks Array of check objects. - * @returns {number} Timestamp of the most recent check. - */ - -const getUptimePercentage = (checks) => { - if (!checks || checks.length === 0) { - return 0; - } - const upCount = checks.reduce((count, check) => { - return check.status === true ? count + 1 : count; - }, 0); - return (upCount / checks.length) * 100; -}; - -/** - * Helper function to get all incidents - * @param {Array} checks Array of check objects. - * @returns {number} Timestamp of the most recent check. - */ - -const getIncidents = (checks) => { - if (!checks || checks.length === 0) { - return 0; // Handle case when no checks are available - } - return checks.reduce((acc, check) => { - return check.status === false ? (acc += 1) : acc; - }, 0); -}; - -/** - * Get date range parameters - * @param {string} dateRange - 'day' | 'week' | 'month' | 'all' - * @returns {Object} Start and end dates - */ -const getDateRange = (dateRange) => { - const startDates = { - recent: new Date(new Date().setHours(new Date().getHours() - 2)), - day: new Date(new Date().setDate(new Date().getDate() - 1)), - week: new Date(new Date().setDate(new Date().getDate() - 7)), - month: new Date(new Date().setMonth(new Date().getMonth() - 1)), - all: new Date(0), - }; - return { - start: startDates[dateRange], - end: new Date(), - }; -}; - -/** - * Get checks for a monitor - * @param {string} monitorId - Monitor ID - * @param {Object} model - Check model to use - * @param {Object} dateRange - Date range parameters - * @param {number} sortOrder - Sort order (1 for ascending, -1 for descending) - * @returns {Promise} All checks and date-ranged checks - */ -const getMonitorChecks = async (monitorId, model, dateRange, sortOrder) => { - const indexSpec = { - monitorId: 1, - createdAt: sortOrder, // This will be 1 or -1 + // Otherwise the uptime is from the last check to the last down check + return latestCheck - latestDownCheck; }; - const [checksAll, checksForDateRange] = await Promise.all([ - model.find({ monitorId }).sort({ createdAt: sortOrder }).hint(indexSpec).lean(), - model - .find({ - monitorId, - createdAt: { $gte: dateRange.start, $lte: dateRange.end }, - }) - .hint(indexSpec) - .lean(), - ]); + // Helper + getLastChecked = (checks) => { + if (!checks || checks.length === 0) { + return 0; // Handle case when no checks are available + } + // Data is sorted newest->oldest, so last check is the most recent + return new Date() - new Date(checks[0].createdAt); + }; + getLatestResponseTime = (checks) => { + if (!checks || checks.length === 0) { + return 0; + } - return { checksAll, checksForDateRange }; -}; + return checks[0]?.responseTime ?? 0; + }; -/** - * Process checks for display - * @param {Array} checks - Checks to process - * @param {number} numToDisplay - Number of checks to display - * @param {boolean} normalize - Whether to normalize the data - * @returns {Array} Processed checks - */ -const processChecksForDisplay = (normalizeData, checks, numToDisplay, normalize) => { - let processedChecks = checks; - if (numToDisplay && checks.length > numToDisplay) { - const n = Math.ceil(checks.length / numToDisplay); - processedChecks = checks.filter((_, index) => index % n === 0); - } - return normalize ? normalizeData(processedChecks, 1, 100) : processedChecks; -}; + // Helper + getAverageResponseTime = (checks) => { + if (!checks || checks.length === 0) { + return 0; + } -/** - * Get time-grouped checks based on date range - * @param {Array} checks Array of check objects - * @param {string} dateRange 'day' | 'week' | 'month' - * @returns {Object} Grouped checks by time period - */ -const groupChecksByTime = (checks, dateRange) => { - return checks.reduce((acc, check) => { - // Validate the date - const checkDate = new Date(check.createdAt); - if (Number.isNaN(checkDate.getTime()) || checkDate.getTime() === 0) { + const validChecks = checks.filter((check) => typeof check.responseTime === "number"); + if (validChecks.length === 0) { + return 0; + } + const aggResponseTime = validChecks.reduce((sum, check) => { + return sum + check.responseTime; + }, 0); + return aggResponseTime / validChecks.length; + }; + + // Helper + getUptimePercentage = (checks) => { + if (!checks || checks.length === 0) { + return 0; + } + const upCount = checks.reduce((count, check) => { + return check.status === true ? count + 1 : count; + }, 0); + return (upCount / checks.length) * 100; + }; + + // Helper + getIncidents = (checks) => { + if (!checks || checks.length === 0) { + return 0; // Handle case when no checks are available + } + return checks.reduce((acc, check) => { + return check.status === false ? (acc += 1) : acc; + }, 0); + }; + + // Helper + getDateRange = (dateRange) => { + const startDates = { + recent: new Date(new Date().setHours(new Date().getHours() - 2)), + day: new Date(new Date().setDate(new Date().getDate() - 1)), + week: new Date(new Date().setDate(new Date().getDate() - 7)), + month: new Date(new Date().setMonth(new Date().getMonth() - 1)), + all: new Date(0), + }; + return { + start: startDates[dateRange], + end: new Date(), + }; + }; + + //Helper + getMonitorChecks = async (monitorId, model, dateRange, sortOrder) => { + const indexSpec = { + monitorId: 1, + createdAt: sortOrder, // This will be 1 or -1 + }; + + const [checksAll, checksForDateRange] = await Promise.all([ + model.find({ monitorId }).sort({ createdAt: sortOrder }).hint(indexSpec).lean(), + model + .find({ + monitorId, + createdAt: { $gte: dateRange.start, $lte: dateRange.end }, + }) + .hint(indexSpec) + .lean(), + ]); + + return { checksAll, checksForDateRange }; + }; + + // Helper + processChecksForDisplay = (normalizeData, checks, numToDisplay, normalize) => { + let processedChecks = checks; + if (numToDisplay && checks.length > numToDisplay) { + const n = Math.ceil(checks.length / numToDisplay); + processedChecks = checks.filter((_, index) => index % n === 0); + } + return normalize ? normalizeData(processedChecks, 1, 100) : processedChecks; + }; + + // Helper + groupChecksByTime = (checks, dateRange) => { + return checks.reduce((acc, check) => { + // Validate the date + const checkDate = new Date(check.createdAt); + if (Number.isNaN(checkDate.getTime()) || checkDate.getTime() === 0) { + return acc; + } + + const time = dateRange === "day" ? checkDate.setMinutes(0, 0, 0) : checkDate.toISOString().split("T")[0]; + + if (!acc[time]) { + acc[time] = { time, checks: [] }; + } + acc[time].checks.push(check); return acc; - } - - const time = dateRange === "day" ? checkDate.setMinutes(0, 0, 0) : checkDate.toISOString().split("T")[0]; - - if (!acc[time]) { - acc[time] = { time, checks: [] }; - } - acc[time].checks.push(check); - return acc; - }, {}); -}; - -/** - * Calculate aggregate stats for a group of checks - * @param {Object} group Group of checks - * @returns {Object} Stats for the group - */ -const calculateGroupStats = (group) => { - const totalChecks = group.checks.length; - - const checksWithResponseTime = group.checks.filter((check) => typeof check.responseTime === "number" && !Number.isNaN(check.responseTime)); - - return { - time: group.time, - uptimePercentage: getUptimePercentage(group.checks), - totalChecks, - totalIncidents: group.checks.filter((check) => !check.status).length, - avgResponseTime: - checksWithResponseTime.length > 0 - ? checksWithResponseTime.reduce((sum, check) => sum + check.responseTime, 0) / checksWithResponseTime.length - : 0, + }, {}); }; -}; -/** - * Get uptime details by monitor ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const getUptimeDetailsById = async ({ monitorId, dateRange, normalize }) => { - try { - const dates = getDateRange(dateRange); - const formatLookup = { - recent: "%Y-%m-%dT%H:%M:00Z", - day: "%Y-%m-%dT%H:00:00Z", - week: "%Y-%m-%dT%H:00:00Z", - month: "%Y-%m-%dT00:00:00Z", + // Helper + calculateGroupStats = (group) => { + const totalChecks = group.checks.length; + + const checksWithResponseTime = group.checks.filter((check) => typeof check.responseTime === "number" && !Number.isNaN(check.responseTime)); + + return { + time: group.time, + uptimePercentage: this.getUptimePercentage(group.checks), + totalChecks, + totalIncidents: group.checks.filter((check) => !check.status).length, + avgResponseTime: + checksWithResponseTime.length > 0 + ? checksWithResponseTime.reduce((sum, check) => sum + check.responseTime, 0) / checksWithResponseTime.length + : 0, }; + }; - const dateString = formatLookup[dateRange]; - - const results = await Check.aggregate(buildUptimeDetailsPipeline(monitorId, dates, dateString)); - - const monitorData = results[0]; - - monitorData.groupedUpChecks = NormalizeDataUptimeDetails(monitorData.groupedUpChecks, 10, 100); - - monitorData.groupedDownChecks = NormalizeDataUptimeDetails(monitorData.groupedDownChecks, 10, 100); - - const normalizedGroupChecks = NormalizeDataUptimeDetails(monitorData.groupedChecks, 10, 100); - - monitorData.groupedChecks = normalizedGroupChecks; - const monitorStats = await MonitorStats.findOne({ monitorId }); - return { monitorData, monitorStats }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getUptimeDetailsById"; - throw error; - } -}; - -/** - * Get stats by monitor ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const getMonitorStatsById = async ({ monitorId, limit, sortOrder, dateRange, numToDisplay, normalize }) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - try { - // Get monitor, if we can't find it, abort with error - const monitor = await Monitor.findById(monitorId); - if (monitor === null || monitor === undefined) { - throw new Error(stringService.getDbFindMonitorById(monitorId)); - } - - // Get query params - const sort = sortOrder === "asc" ? 1 : -1; - - // Get Checks for monitor in date range requested - const model = CHECK_MODEL_LOOKUP[monitor.type]; - const dates = getDateRange(dateRange); - const { checksAll, checksForDateRange } = await getMonitorChecks(monitorId, model, dates, sort); - - // Build monitor stats - const monitorStats = { - ...monitor.toObject(), - uptimeDuration: calculateUptimeDuration(checksAll), - lastChecked: getLastChecked(checksAll), - latestResponseTime: getLatestResponseTime(checksAll), - periodIncidents: getIncidents(checksForDateRange), - periodTotalChecks: checksForDateRange.length, - checks: processChecksForDisplay(NormalizeData, checksForDateRange, numToDisplay, normalize), - }; - - if (monitor.type === "http" || monitor.type === "ping" || monitor.type === "docker" || monitor.type === "port") { - // HTTP/PING Specific stats - monitorStats.periodAvgResponseTime = getAverageResponseTime(checksForDateRange); - monitorStats.periodUptime = getUptimePercentage(checksForDateRange); - const groupedChecks = groupChecksByTime(checksForDateRange, dateRange); - monitorStats.aggregateData = Object.values(groupedChecks).map(calculateGroupStats); - } - - return monitorStats; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorStatsById"; - throw error; - } -}; - -const getHardwareDetailsById = async ({ monitorId, dateRange }) => { - try { - const monitor = await Monitor.findById(monitorId); - const dates = getDateRange(dateRange); - const formatLookup = { - recent: "%Y-%m-%dT%H:%M:00Z", - day: "%Y-%m-%dT%H:00:00Z", - week: "%Y-%m-%dT%H:00:00Z", - month: "%Y-%m-%dT00:00:00Z", - }; - const dateString = formatLookup[dateRange]; - const hardwareStats = await HardwareCheck.aggregate(buildHardwareDetailsPipeline(monitor, dates, dateString)); - - const monitorStats = { - ...monitor.toObject(), - stats: hardwareStats[0], - }; - return monitorStats; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getHardwareDetailsById"; - throw error; - } -}; - -/** - * Get a monitor by ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const getMonitorById = async (monitorId) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - try { - const monitor = await Monitor.findById(monitorId); - if (monitor === null || monitor === undefined) { - const error = new Error(stringService.getDbFindMonitorById(monitorId)); - error.status = 404; + getAllMonitors = async () => { + try { + const monitors = await this.Monitor.find(); + return monitors; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getAllMonitors"; throw error; } + }; - return monitor; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorById"; - throw error; - } -}; + getMonitorById = async (monitorId) => { + try { + const monitor = await this.Monitor.findById(monitorId); + if (monitor === null || monitor === undefined) { + const error = new Error(this.stringService.getDbFindMonitorById(monitorId)); + error.status = 404; + throw error; + } -const getMonitorsByIds = async (monitorIds) => { - try { - const objectIds = monitorIds.map((id) => new ObjectId(id)); - return await Monitor.find({ _id: { $in: objectIds } }, { _id: 1, teamId: 1 }).lean(); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorsByIds"; - throw error; - } -}; - -const getMonitorsByTeamId = async ({ limit, type, page, rowsPerPage, filter, field, order, teamId }) => { - limit = parseInt(limit); - page = parseInt(page); - rowsPerPage = parseInt(rowsPerPage); - if (field === undefined) { - field = "name"; - order = "asc"; - } - // Build match stage - const matchStage = { teamId: new ObjectId(teamId) }; - if (type !== undefined) { - matchStage.type = Array.isArray(type) ? { $in: type } : type; - } - - const summaryResult = await Monitor.aggregate(buildMonitorSummaryByTeamIdPipeline({ matchStage })); - const summary = summaryResult[0]; - - const monitors = await Monitor.aggregate(buildMonitorsByTeamIdPipeline({ matchStage, field, order })); - - const filteredMonitors = await Monitor.aggregate( - buildFilteredMonitorsByTeamIdPipeline({ - matchStage, - filter, - page, - rowsPerPage, - field, - order, - limit, - type, - }) - ); - - const normalizedFilteredMonitors = filteredMonitors.map((monitor) => { - if (!monitor.checks) { return monitor; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMonitorById"; + throw error; } - monitor.checks = NormalizeData(monitor.checks, 10, 100); - return monitor; - }); + }; - return { summary, monitors, filteredMonitors: normalizedFilteredMonitors }; -}; - -const getMonitorsAndSummaryByTeamId = async ({ type, explain, teamId }) => { - try { - const matchStage = { teamId: new ObjectId(teamId) }; - if (type !== undefined) { - matchStage.type = Array.isArray(type) ? { $in: type } : type; + getMonitorsByIds = async (monitorIds) => { + try { + const objectIds = monitorIds.map((id) => new this.ObjectId(id)); + return await this.Monitor.find({ _id: { $in: objectIds } }, { _id: 1, teamId: 1 }).lean(); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMonitorsByIds"; + throw error; } + }; + getUptimeDetailsById = async ({ monitorId, dateRange }) => { + try { + const dates = this.getDateRange(dateRange); + const formatLookup = { + recent: "%Y-%m-%dT%H:%M:00Z", + day: "%Y-%m-%dT%H:00:00Z", + week: "%Y-%m-%dT%H:00:00Z", + month: "%Y-%m-%dT00:00:00Z", + }; - if (explain === true) { - return Monitor.aggregate(buildMonitorsAndSummaryByTeamIdPipeline({ matchStage })).explain("executionStats"); + const dateString = formatLookup[dateRange]; + + const results = await this.Check.aggregate(buildUptimeDetailsPipeline(monitorId, dates, dateString)); + + const monitorData = results[0]; + + monitorData.groupedUpChecks = this.NormalizeDataUptimeDetails(monitorData.groupedUpChecks, 10, 100); + + monitorData.groupedDownChecks = this.NormalizeDataUptimeDetails(monitorData.groupedDownChecks, 10, 100); + + const normalizedGroupChecks = this.NormalizeDataUptimeDetails(monitorData.groupedChecks, 10, 100); + + monitorData.groupedChecks = normalizedGroupChecks; + const monitorStats = await this.MonitorStats.findOne({ monitorId }); + return { monitorData, monitorStats }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getUptimeDetailsById"; + throw error; } + }; - const queryResult = await Monitor.aggregate(buildMonitorsAndSummaryByTeamIdPipeline({ matchStage })); - const { monitors, summary } = queryResult?.[0] ?? {}; - return { monitors, summary }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorsAndSummaryByTeamId"; - throw error; - } -}; + getMonitorStatsById = async ({ monitorId, sortOrder, dateRange, numToDisplay, normalize }) => { + try { + // Get monitor, if we can't find it, abort with error + const monitor = await this.Monitor.findById(monitorId); + if (monitor === null || monitor === undefined) { + throw new Error(this.stringService.getDbFindMonitorById(monitorId)); + } -const getMonitorsWithChecksByTeamId = async ({ limit, type, page, rowsPerPage, filter, field, order, teamId, explain }) => { - try { + // Get query params + const sort = sortOrder === "asc" ? 1 : -1; + + // Get Checks for monitor in date range requested + const model = this.CHECK_MODEL_LOOKUP[monitor.type]; + const dates = this.getDateRange(dateRange); + const { checksAll, checksForDateRange } = await this.getMonitorChecks(monitorId, model, dates, sort); + + // Build monitor stats + const monitorStats = { + ...monitor.toObject(), + uptimeDuration: this.calculateUptimeDuration(checksAll), + lastChecked: this.getLastChecked(checksAll), + latestResponseTime: this.getLatestResponseTime(checksAll), + periodIncidents: this.getIncidents(checksForDateRange), + periodTotalChecks: checksForDateRange.length, + checks: this.processChecksForDisplay(this.NormalizeData, checksForDateRange, numToDisplay, normalize), + }; + + if (monitor.type === "http" || monitor.type === "ping" || monitor.type === "docker" || monitor.type === "port") { + // HTTP/PING Specific stats + monitorStats.periodAvgResponseTime = this.getAverageResponseTime(checksForDateRange); + monitorStats.periodUptime = this.getUptimePercentage(checksForDateRange); + const groupedChecks = this.groupChecksByTime(checksForDateRange, dateRange); + monitorStats.aggregateData = Object.values(groupedChecks).map(this.calculateGroupStats); + } + + return monitorStats; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMonitorStatsById"; + throw error; + } + }; + + getHardwareDetailsById = async ({ monitorId, dateRange }) => { + try { + const monitor = await this.Monitor.findById(monitorId); + const dates = this.getDateRange(dateRange); + const formatLookup = { + recent: "%Y-%m-%dT%H:%M:00Z", + day: "%Y-%m-%dT%H:00:00Z", + week: "%Y-%m-%dT%H:00:00Z", + month: "%Y-%m-%dT00:00:00Z", + }; + const dateString = formatLookup[dateRange]; + const hardwareStats = await this.HardwareCheck.aggregate(buildHardwareDetailsPipeline(monitor, dates, dateString)); + + const monitorStats = { + ...monitor.toObject(), + stats: hardwareStats[0], + }; + return monitorStats; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getHardwareDetailsById"; + throw error; + } + }; + + getMonitorsByTeamId = async ({ limit, type, page, rowsPerPage, filter, field, order, teamId }) => { limit = parseInt(limit); page = parseInt(page); rowsPerPage = parseInt(rowsPerPage); @@ -498,28 +360,18 @@ const getMonitorsWithChecksByTeamId = async ({ limit, type, page, rowsPerPage, f order = "asc"; } // Build match stage - const matchStage = { teamId: new ObjectId(teamId) }; + const matchStage = { teamId: new this.ObjectId(teamId) }; if (type !== undefined) { matchStage.type = Array.isArray(type) ? { $in: type } : type; } - if (explain === true) { - return Monitor.aggregate( - buildMonitorsWithChecksByTeamIdPipeline({ - matchStage, - filter, - page, - rowsPerPage, - field, - order, - limit, - type, - }) - ).explain("executionStats"); - } + const summaryResult = await this.Monitor.aggregate(buildMonitorSummaryByTeamIdPipeline({ matchStage })); + const summary = summaryResult[0]; - const queryResult = await Monitor.aggregate( - buildMonitorsWithChecksByTeamIdPipeline({ + const monitors = await this.Monitor.aggregate(buildMonitorsByTeamIdPipeline({ matchStage, field, order })); + + const filteredMonitors = await this.Monitor.aggregate( + buildFilteredMonitorsByTeamIdPipeline({ matchStage, filter, page, @@ -530,237 +382,220 @@ const getMonitorsWithChecksByTeamId = async ({ limit, type, page, rowsPerPage, f type, }) ); - const monitors = queryResult[0]?.monitors; - const count = queryResult[0]?.count; - const normalizedFilteredMonitors = monitors.map((monitor) => { + + const normalizedFilteredMonitors = filteredMonitors.map((monitor) => { if (!monitor.checks) { return monitor; } - monitor.checks = NormalizeData(monitor.checks, 10, 100); + monitor.checks = this.NormalizeData(monitor.checks, 10, 100); return monitor; }); - return { count, monitors: normalizedFilteredMonitors }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorsWithChecksByTeamId"; - throw error; - } -}; -/** - * Create a monitor - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const createMonitor = async ({ body, teamId, userId }) => { - try { - const monitor = new Monitor({ ...body, teamId, userId }); - const saved = await monitor.save(); - return saved; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createMonitor"; - throw error; - } -}; + return { summary, monitors, filteredMonitors: normalizedFilteredMonitors }; + }; -/** - * Create bulk monitors - * @async - * @param {Express.Request} req - * @returns {Promise} - * @throws {Error} - */ -const createBulkMonitors = async (req) => { - try { - const monitors = req.map((item) => new Monitor({ ...item, notifications: undefined })); - await Monitor.bulkSave(monitors); - return monitors; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createBulkMonitors"; - throw error; - } -}; + getMonitorsAndSummaryByTeamId = async ({ type, explain, teamId }) => { + try { + const matchStage = { teamId: new this.ObjectId(teamId) }; + if (type !== undefined) { + matchStage.type = Array.isArray(type) ? { $in: type } : type; + } -/** - * Delete a monitor by ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const deleteMonitor = async ({ teamId, monitorId }) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - try { - const deletedMonitor = await Monitor.findOneAndDelete({ _id: monitorId, teamId }); + if (explain === true) { + return this.Monitor.aggregate(buildMonitorsAndSummaryByTeamIdPipeline({ matchStage })).explain("executionStats"); + } - if (!deletedMonitor) { - throw new Error(stringService.getDbFindMonitorById(monitorId)); + const queryResult = await this.Monitor.aggregate(buildMonitorsAndSummaryByTeamIdPipeline({ matchStage })); + const { monitors, summary } = queryResult?.[0] ?? {}; + return { monitors, summary }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMonitorsAndSummaryByTeamId"; + throw error; } + }; - return deletedMonitor; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteMonitor"; - throw error; - } -}; + getMonitorsWithChecksByTeamId = async ({ limit, type, page, rowsPerPage, filter, field, order, teamId, explain }) => { + try { + limit = parseInt(limit); + page = parseInt(page); + rowsPerPage = parseInt(rowsPerPage); + if (field === undefined) { + field = "name"; + order = "asc"; + } + // Build match stage + const matchStage = { teamId: new this.ObjectId(teamId) }; + if (type !== undefined) { + matchStage.type = Array.isArray(type) ? { $in: type } : type; + } -/** - * DELETE ALL MONITORS (TEMP) - */ + if (explain === true) { + return this.Monitor.aggregate( + buildMonitorsWithChecksByTeamIdPipeline({ + matchStage, + filter, + page, + rowsPerPage, + field, + order, + limit, + type, + }) + ).explain("executionStats"); + } -const deleteAllMonitors = async (teamId) => { - try { - const monitors = await Monitor.find({ teamId }); - const { deletedCount } = await Monitor.deleteMany({ teamId }); + const queryResult = await this.Monitor.aggregate( + buildMonitorsWithChecksByTeamIdPipeline({ + matchStage, + filter, + page, + rowsPerPage, + field, + order, + limit, + type, + }) + ); + const monitors = queryResult[0]?.monitors; + const count = queryResult[0]?.count; + const normalizedFilteredMonitors = monitors.map((monitor) => { + if (!monitor.checks) { + return monitor; + } + monitor.checks = this.NormalizeData(monitor.checks, 10, 100); + return monitor; + }); + return { count, monitors: normalizedFilteredMonitors }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMonitorsWithChecksByTeamId"; + throw error; + } + }; + createMonitor = async ({ body, teamId, userId }) => { + try { + const monitor = new this.Monitor({ ...body, teamId, userId }); + const saved = await monitor.save(); + return saved; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createMonitor"; + throw error; + } + }; - return { monitors, deletedCount }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteAllMonitors"; - throw error; - } -}; + createBulkMonitors = async (req) => { + try { + const monitors = req.map((item) => new this.Monitor({ ...item, notifications: undefined })); + await this.Monitor.bulkSave(monitors); + return monitors; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createBulkMonitors"; + throw error; + } + }; -/** - * Delete all monitors associated with a user ID - * @async - * @param {string} userId - The ID of the user whose monitors are to be deleted. - * @returns {Promise} A promise that resolves when the operation is complete. - */ -const deleteMonitorsByUserId = async (userId) => { - try { - const result = await Monitor.deleteMany({ userId: userId }); - return result; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteMonitorsByUserId"; - throw error; - } -}; + deleteMonitor = async ({ teamId, monitorId }) => { + try { + const deletedMonitor = await this.Monitor.findOneAndDelete({ _id: monitorId, teamId }); -/** - * Edit a monitor by ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const editMonitor = async ({ monitorId, body }) => { - try { - const editedMonitor = await Monitor.findByIdAndUpdate(monitorId, body, { - new: true, - }); - return editedMonitor; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "editMonitor"; - throw error; - } -}; + if (!deletedMonitor) { + throw new Error(this.stringService.getDbFindMonitorById(monitorId)); + } -const addDemoMonitors = async (userId, teamId) => { - try { - const demoMonitorsToInsert = demoMonitors.map((monitor) => { - return { - userId, - teamId, - name: monitor.name, - description: monitor.name, - type: "http", - url: monitor.url, - interval: 60000, - }; - }); - const insertedMonitors = await Monitor.insertMany(demoMonitorsToInsert); - return insertedMonitors; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "addDemoMonitors"; - throw error; - } -}; + return deletedMonitor; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteMonitor"; + throw error; + } + }; -const pauseMonitor = async ({ monitorId }) => { - try { - const monitor = await Monitor.findOneAndUpdate( - { _id: monitorId }, - [ - { - $set: { - isActive: { $not: "$isActive" }, - status: "$$REMOVE", + deleteAllMonitors = async (teamId) => { + try { + const monitors = await this.Monitor.find({ teamId }); + const { deletedCount } = await this.Monitor.deleteMany({ teamId }); + + return { monitors, deletedCount }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteAllMonitors"; + throw error; + } + }; + + deleteMonitorsByUserId = async (userId) => { + try { + const result = await this.Monitor.deleteMany({ userId: userId }); + return result; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteMonitorsByUserId"; + throw error; + } + }; + + editMonitor = async ({ monitorId, body }) => { + try { + const editedMonitor = await this.Monitor.findByIdAndUpdate(monitorId, body, { + new: true, + }); + return editedMonitor; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "editMonitor"; + throw error; + } + }; + + addDemoMonitors = async (userId, teamId) => { + try { + const demoMonitors = JSON.parse(this.fs.readFileSync(this.demoMonitorsPath, "utf8")); + + const demoMonitorsToInsert = demoMonitors.map((monitor) => { + return { + userId, + teamId, + name: monitor.name, + description: monitor.name, + type: "http", + url: monitor.url, + interval: 60000, + }; + }); + const insertedMonitors = await this.Monitor.insertMany(demoMonitorsToInsert); + return insertedMonitors; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "addDemoMonitors"; + throw error; + } + }; + + pauseMonitor = async ({ monitorId }) => { + try { + const monitor = await this.Monitor.findOneAndUpdate( + { _id: monitorId }, + [ + { + $set: { + isActive: { $not: "$isActive" }, + status: "$$REMOVE", + }, }, - }, - ], - { new: true } - ); + ], + { new: true } + ); - return monitor; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "pauseMonitor"; - throw error; - } -}; + return monitor; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "pauseMonitor"; + throw error; + } + }; +} -export { - getAllMonitors, - getMonitorStatsById, - getMonitorById, - getMonitorsByIds, - getMonitorsByTeamId, - getMonitorsAndSummaryByTeamId, - getMonitorsWithChecksByTeamId, - getUptimeDetailsById, - createMonitor, - createBulkMonitors, - deleteMonitor, - deleteAllMonitors, - deleteMonitorsByUserId, - editMonitor, - addDemoMonitors, - getHardwareDetailsById, - pauseMonitor, -}; - -// Helper functions -export { - calculateUptimeDuration, - getLastChecked, - getLatestResponseTime, - getAverageResponseTime, - getUptimePercentage, - getIncidents, - getDateRange, - getMonitorChecks, - processChecksForDisplay, - groupChecksByTime, - calculateGroupStats, -}; - -// limit 25 -// page 1 -// rowsPerPage 25 -// filter undefined -// field name -// order asc -// skip 25 -// sort { name: 1 } -// filteredMonitors [] - -// limit 25 -// page NaN -// rowsPerPage 25 -// filter undefined -// field name -// order asc -// skip 0 -// sort { name: 1 } +export default MonitorModule; diff --git a/server/src/db/mongo/modules/networkCheckModule.js b/server/src/db/mongo/modules/networkCheckModule.js index 12a86e73b..917e68440 100644 --- a/server/src/db/mongo/modules/networkCheckModule.js +++ b/server/src/db/mongo/modules/networkCheckModule.js @@ -1,44 +1,30 @@ -import NetworkCheck from "../../models/NetworkCheck.js"; - const SERVICE_NAME = "networkCheckModule"; -/** - * Creates and saves a new network check document to the database. - * @async - * @param {object} networkCheckData - The data for the new network check. This should conform to the NetworkCheckSchema. - * @param {string} networkCheckData.monitorId - The ID of the monitor associated with this check. - * @returns {Promise} A promise that resolves to the newly created network check document. - * @throws {Error} Throws an error if the database operation fails. - */ -const createNetworkCheck = async (networkCheckData) => { - try { - const networkCheck = await new NetworkCheck(networkCheckData); - await networkCheck.save(); - return networkCheck; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createNetworkCheck"; - throw error; +class NetworkCheckModule { + constructor({ NetworkCheck }) { + this.NetworkCheck = NetworkCheck; } -}; + createNetworkCheck = async (networkCheckData) => { + try { + const networkCheck = await new this.NetworkCheck(networkCheckData); + await networkCheck.save(); + return networkCheck; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createNetworkCheck"; + throw error; + } + }; + getNetworkChecksByMonitorId = async (monitorId, limit = 100) => { + try { + const networkChecks = await this.NetworkCheck.find({ monitorId }).sort({ createdAt: -1 }).limit(limit); + return networkChecks; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getNetworkChecksByMonitorId"; + throw error; + } + }; +} -/** - * Retrieves a list of network checks for a specific monitor, sorted by most recent. - * @async - * @param {string} monitorId - The ID of the monitor to retrieve checks for. - * @param {number} [limit=100] - The maximum number of checks to return. Defaults to 100. - * @returns {Promise>} A promise that resolves to an array of network check documents. - * @throws {Error} Throws an error if the database operation fails. - */ -const getNetworkChecksByMonitorId = async (monitorId, limit = 100) => { - try { - const networkChecks = await NetworkCheck.find({ monitorId }).sort({ createdAt: -1 }).limit(limit); - return networkChecks; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getNetworkChecksByMonitorId"; - throw error; - } -}; - -export { createNetworkCheck, getNetworkChecksByMonitorId }; +export default NetworkCheckModule; diff --git a/server/src/db/mongo/modules/notificationModule.js b/server/src/db/mongo/modules/notificationModule.js index 2348a6321..3c6edb54a 100755 --- a/server/src/db/mongo/modules/notificationModule.js +++ b/server/src/db/mongo/modules/notificationModule.js @@ -1,125 +1,100 @@ -import Notification from "../../models/Notification.js"; -import Monitor from "../../models/Monitor.js"; +// import Notification from "../../models/Notification.js"; +// import Monitor from "../../models/Monitor.js"; const SERVICE_NAME = "notificationModule"; -/** - * Creates a new notification. - * @param {Object} notificationData - The data for the new notification. - * @param {mongoose.Types.ObjectId} notificationData.monitorId - The ID of the monitor. - * @param {string} notificationData.type - The type of the notification (e.g., "email", "sms"). - * @param {string} [notificationData.address] - The address for the notification (if applicable). - * @param {string} [notificationData.phone] - The phone number for the notification (if applicable). - * @returns {Promise} The created notification. - * @throws Will throw an error if the notification cannot be created. - */ -const createNotification = async (notificationData) => { - try { - const notification = await new Notification({ ...notificationData }).save(); - return notification; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createNotification"; - throw error; - } -}; -const getNotificationsByTeamId = async (teamId) => { - try { - const notifications = await Notification.find({ teamId }); - return notifications; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getNotificationsByTeamId"; - throw error; +class NotificationModule { + constructor({ Notification, Monitor }) { + this.Notification = Notification; + this.Monitor = Monitor; } -}; - -const getNotificationsByIds = async (notificationIds) => { - try { - const notifications = await Notification.find({ _id: { $in: notificationIds } }); - return notifications; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getNotificationsByIds"; - throw error; - } -}; - -/** - * Retrieves notifications by monitor ID. - * @param {mongoose.Types.ObjectId} monitorId - The ID of the monitor. - * @returns {Promise>} An array of notifications. - * @throws Will throw an error if the notifications cannot be retrieved. - */ -const getNotificationsByMonitorId = async (monitorId) => { - try { - const notifications = await Notification.find({ monitorId }); - return notifications; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getNotificationsByMonitorId"; - throw error; - } -}; - -const deleteNotificationsByMonitorId = async (monitorId) => { - try { - const result = await Notification.deleteMany({ monitorId }); - return result.deletedCount; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteNotificationsByMonitorId"; - throw error; - } -}; - -const deleteNotificationById = async (id) => { - try { - const notification = await Notification.findById(id); - if (!notification) { - throw new Error("Notification not found"); + createNotification = async (notificationData) => { + try { + const notification = await new this.Notification({ ...notificationData }).save(); + return notification; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createNotification"; + throw error; } + }; + getNotificationsByTeamId = async (teamId) => { + try { + const notifications = await this.Notification.find({ teamId }); + return notifications; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getNotificationsByTeamId"; + throw error; + } + }; + getNotificationsByIds = async (notificationIds) => { + try { + const notifications = await this.Notification.find({ _id: { $in: notificationIds } }); + return notifications; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getNotificationsByIds"; + throw error; + } + }; + getNotificationsByMonitorId = async (monitorId) => { + try { + const notifications = await this.Notification.find({ monitorId }); + return notifications; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getNotificationsByMonitorId"; + throw error; + } + }; + deleteNotificationsByMonitorId = async (monitorId) => { + try { + const result = await this.Notification.deleteMany({ monitorId }); + return result.deletedCount; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteNotificationsByMonitorId"; + throw error; + } + }; + deleteNotificationById = async (id) => { + try { + const notification = await this.Notification.findById(id); + if (!notification) { + throw new Error("Notification not found"); + } - const result = await Notification.findByIdAndDelete(id); - await Monitor.updateMany({ notifications: id }, { $pull: { notifications: id } }); - return result; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteNotificationById"; - throw error; - } -}; + const result = await this.Notification.findByIdAndDelete(id); + await this.Monitor.updateMany({ notifications: id }, { $pull: { notifications: id } }); + return result; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteNotificationById"; + throw error; + } + }; + getNotificationById = async (id) => { + try { + const notification = await this.Notification.findById(id); + return notification; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getNotificationById"; + throw error; + } + }; + editNotification = async (id, notificationData) => { + try { + const notification = await this.Notification.findByIdAndUpdate(id, notificationData, { + new: true, + }); + return notification; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "editNotification"; + throw error; + } + }; +} -const getNotificationById = async (id) => { - try { - const notification = await Notification.findById(id); - return notification; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getNotificationById"; - throw error; - } -}; - -const editNotification = async (id, notificationData) => { - try { - const notification = await Notification.findByIdAndUpdate(id, notificationData, { - new: true, - }); - return notification; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "editNotification"; - throw error; - } -}; - -export { - createNotification, - getNotificationsByTeamId, - getNotificationsByIds, - getNotificationsByMonitorId, - deleteNotificationsByMonitorId, - deleteNotificationById, - getNotificationById, - editNotification, -}; +export default NotificationModule; diff --git a/server/src/db/mongo/modules/pageSpeedCheckModule.js b/server/src/db/mongo/modules/pageSpeedCheckModule.js index 9169a0836..b2a3eb71d 100755 --- a/server/src/db/mongo/modules/pageSpeedCheckModule.js +++ b/server/src/db/mongo/modules/pageSpeedCheckModule.js @@ -1,57 +1,31 @@ -import PageSpeedCheck from "../../models/PageSpeedCheck.js"; +// import PageSpeedCheck from "../../models/PageSpeedCheck.js"; const SERVICE_NAME = "pageSpeedCheckModule"; -/** - * Create a PageSpeed check for a monitor - * @async - * @param {Object} pageSpeedCheckData - * @param {string} pageSpeedCheckData.monitorId - * @param {number} pageSpeedCheckData.accessibility - * @param {number} pageSpeedCheckData.bestPractices - * @param {number} pageSpeedCheckData.seo - * @param {number} pageSpeedCheckData.performance - * @returns {Promise} - * @throws {Error} - */ -const createPageSpeedCheck = async (pageSpeedCheckData) => { - try { - const pageSpeedCheck = await new PageSpeedCheck({ - ...pageSpeedCheckData, - }).save(); - return pageSpeedCheck; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createPageSpeedCheck"; - throw error; - } -}; -const createPageSpeedChecks = async (pageSpeedChecks) => { - try { - await PageSpeedCheck.insertMany(pageSpeedChecks, { ordered: false }); - return true; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createPageSpeedCheck"; - throw error; - } -}; -/** - * Delete all PageSpeed checks for a monitor - * @async - * @param {string} monitorId - * @returns {number} - * @throws {Error} - */ - -const deletePageSpeedChecksByMonitorId = async (monitorId) => { - try { - const result = await PageSpeedCheck.deleteMany({ monitorId }); - return result.deletedCount; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deletePageSpeedChecksByMonitorId"; - throw error; +class PageSpeedCheckModule { + constructor({ PageSpeedCheck }) { + this.PageSpeedCheck = PageSpeedCheck; } -}; -export { createPageSpeedCheck, createPageSpeedChecks, deletePageSpeedChecksByMonitorId }; + createPageSpeedChecks = async (pageSpeedChecks) => { + try { + await this.PageSpeedCheck.insertMany(pageSpeedChecks, { ordered: false }); + return true; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createPageSpeedCheck"; + throw error; + } + }; + + deletePageSpeedChecksByMonitorId = async (monitorId) => { + try { + const result = await this.PageSpeedCheck.deleteMany({ monitorId }); + return result.deletedCount; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deletePageSpeedChecksByMonitorId"; + throw error; + } + }; +} +export default PageSpeedCheckModule; diff --git a/server/src/db/mongo/modules/recoveryModule.js b/server/src/db/mongo/modules/recoveryModule.js index 2678f49c4..cc22306a3 100755 --- a/server/src/db/mongo/modules/recoveryModule.js +++ b/server/src/db/mongo/modules/recoveryModule.js @@ -1,86 +1,80 @@ -import UserModel from "../../models/User.js"; -import RecoveryToken from "../../models/RecoveryToken.js"; -import crypto from "crypto"; -import serviceRegistry from "../../../service/system/serviceRegistry.js"; -import StringService from "../../../service/system/stringService.js"; - const SERVICE_NAME = "recoveryModule"; -/** - * Request a recovery token - * @async - * @param {string} email - * @returns {Promise} - * @throws {Error} - */ -const requestRecoveryToken = async (email) => { - try { - // Delete any existing tokens - await RecoveryToken.deleteMany({ email }); - let recoveryToken = new RecoveryToken({ - email, - token: crypto.randomBytes(32).toString("hex"), - }); - await recoveryToken.save(); - return recoveryToken; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "requestRecoveryToken"; - throw error; +class RecoveryModule { + constructor({ User, RecoveryToken, crypto, stringService }) { + this.User = User; + this.RecoveryToken = RecoveryToken; + this.crypto = crypto; + this.stringService = stringService; } -}; -const validateRecoveryToken = async (candidateToken) => { - const stringService = serviceRegistry.get(StringService.SERVICE_NAME); - try { - const recoveryToken = await RecoveryToken.findOne({ - token: candidateToken, - }); - if (recoveryToken !== null) { + requestRecoveryToken = async (email) => { + try { + // Delete any existing tokens + await this.RecoveryToken.deleteMany({ email }); + let recoveryToken = new this.RecoveryToken({ + email, + token: this.crypto.randomBytes(32).toString("hex"), + }); + await recoveryToken.save(); return recoveryToken; - } else { - throw new Error(stringService.dbTokenNotFound); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "requestRecoveryToken"; + throw error; } - } catch (error) { - error.service = SERVICE_NAME; - error.method = "validateRecoveryToken"; - throw error; - } -}; - -const resetPassword = async (password, candidateToken) => { - const stringService = serviceRegistry.get(StringService.SERVICE_NAME); - try { - const newPassword = password; - - // Validate token again - const recoveryToken = await validateRecoveryToken(candidateToken); - const user = await UserModel.findOne({ email: recoveryToken.email }); - - if (user === null) { - throw new Error(stringService.dbUserNotFound); + }; + validateRecoveryToken = async (candidateToken) => { + try { + const recoveryToken = await this.RecoveryToken.findOne({ + token: candidateToken, + }); + if (recoveryToken !== null) { + return recoveryToken; + } else { + throw new Error(this.stringService.dbTokenNotFound); + } + } catch (error) { + error.service = SERVICE_NAME; + error.method = "validateRecoveryToken"; + throw error; } + }; - const match = await user.comparePassword(newPassword); - if (match === true) { - throw new Error(stringService.dbResetPasswordBadMatch); + resetPassword = async (password, candidateToken) => { + try { + const newPassword = password; + + // Validate token again + const recoveryToken = await this.validateRecoveryToken(candidateToken); + const user = await this.User.findOne({ email: recoveryToken.email }); + + if (user === null) { + throw new Error(this.stringService.dbUserNotFound); + } + + const match = await user.comparePassword(newPassword); + + if (match === true) { + throw new Error("Password cannot be the same as the old password"); + } + + user.password = newPassword; + await user.save(); + await this.RecoveryToken.deleteMany({ email: recoveryToken.email }); + // Fetch the user again without the password + const userWithoutPassword = await this.User.findOne({ + email: recoveryToken.email, + }) + .select("-password") + .select("-profileImage"); + return userWithoutPassword; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "resetPassword"; + throw error; } + }; +} - user.password = newPassword; - await user.save(); - await RecoveryToken.deleteMany({ email: recoveryToken.email }); - // Fetch the user again without the password - const userWithoutPassword = await UserModel.findOne({ - email: recoveryToken.email, - }) - .select("-password") - .select("-profileImage"); - return userWithoutPassword; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "resetPassword"; - throw error; - } -}; - -export { requestRecoveryToken, validateRecoveryToken, resetPassword }; +export default RecoveryModule; diff --git a/server/src/db/mongo/modules/settingsModule.js b/server/src/db/mongo/modules/settingsModule.js index df325d2fa..37b851bf2 100755 --- a/server/src/db/mongo/modules/settingsModule.js +++ b/server/src/db/mongo/modules/settingsModule.js @@ -1,41 +1,36 @@ -import AppSettings from "../../models/AppSettings.js"; +// import AppSettings from "../../models/AppSettings.js"; const SERVICE_NAME = "SettingsModule"; -const getAppSettings = async () => { - try { - const settings = AppSettings.findOne(); - return settings; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getSettings"; - throw error; +class SettingsModule { + constructor({ AppSettings }) { + this.AppSettings = AppSettings; } -}; -const updateAppSettings = async (newSettings) => { - try { - const update = { $set: { ...newSettings } }; + updateAppSettings = async (newSettings) => { + try { + const update = { $set: { ...newSettings } }; - if (newSettings.pagespeedApiKey === "") { - update.$unset = { pagespeedApiKey: "" }; - delete update.$set.pagespeedApiKey; + if (newSettings.pagespeedApiKey === "") { + update.$unset = { pagespeedApiKey: "" }; + delete update.$set.pagespeedApiKey; + } + + if (newSettings.systemEmailPassword === "") { + update.$unset = { systemEmailPassword: "" }; + delete update.$set.systemEmailPassword; + } + + await this.AppSettings.findOneAndUpdate({}, update, { + upsert: true, + }); + const settings = await this.AppSettings.findOne().select("-__v -_id -createdAt -updatedAt -singleton").lean(); + return settings; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "updateAppSettings"; + throw error; } + }; +} - if (newSettings.systemEmailPassword === "") { - update.$unset = { systemEmailPassword: "" }; - delete update.$set.systemEmailPassword; - } - - await AppSettings.findOneAndUpdate({}, update, { - upsert: true, - }); - const settings = await AppSettings.findOne().select("-__v -_id -createdAt -updatedAt -singleton").lean(); - return settings; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "updateAppSettings"; - throw error; - } -}; - -export { getAppSettings, updateAppSettings }; +export default SettingsModule; diff --git a/server/src/db/mongo/modules/statusPageModule.js b/server/src/db/mongo/modules/statusPageModule.js index e814c20f5..10710d0f7 100755 --- a/server/src/db/mongo/modules/statusPageModule.js +++ b/server/src/db/mongo/modules/statusPageModule.js @@ -1,292 +1,287 @@ -import StatusPage from "../../models/StatusPage.js"; -import { NormalizeData } from "../../../utils/dataUtils.js"; -import ServiceRegistry from "../../../service/system/serviceRegistry.js"; -import StringService from "../../../service/system/stringService.js"; +// import StatusPage from "../../models/StatusPage.js"; +// import { NormalizeData } from "../../../utils/dataUtils.js"; +// import ServiceRegistry from "../../../service/system/serviceRegistry.js"; +// import StringService from "../../../service/system/stringService.js"; const SERVICE_NAME = "statusPageModule"; -const createStatusPage = async ({ statusPageData, image, userId, teamId }) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - - try { - const statusPage = new StatusPage({ - ...statusPageData, - userId, - teamId, - }); - if (image) { - statusPage.logo = { - data: image.buffer, - contentType: image.mimetype, - }; - } - await statusPage.save(); - return statusPage; - } catch (error) { - if (error?.code === 11000) { - // Handle duplicate URL errors - error.status = 400; - error.message = stringService.statusPageUrlNotUnique; - } - error.service = SERVICE_NAME; - error.method = "createStatusPage"; - throw error; +class StatusPageModule { + constructor({ StatusPage, NormalizeData, stringService }) { + this.StatusPage = StatusPage; + this.NormalizeData = NormalizeData; + this.stringService = stringService; } -}; -const updateStatusPage = async (statusPageData, image) => { - try { - if (image) { - statusPageData.logo = { - data: image.buffer, - contentType: image.mimetype, - }; - } else { - statusPageData.logo = null; - } - - if (statusPageData.deleteSubmonitors === "true") { - statusPageData.subMonitors = []; - } - const statusPage = await StatusPage.findOneAndUpdate({ url: statusPageData.url }, statusPageData, { - new: true, - }); - - return statusPage; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "updateStatusPage"; - throw error; - } -}; - -const getStatusPageByUrl = async (url, type) => { - // TODO This is deprecated, can remove and have controller call getStatusPage - try { - return getStatusPage(url); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getStatusPageByUrl"; - throw error; - } -}; - -const getStatusPagesByTeamId = async (teamId) => { - try { - const statusPages = await StatusPage.find({ teamId }); - return statusPages; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getStatusPagesByTeamId"; - throw error; - } -}; - -const getStatusPage = async (url) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - - try { - const preliminaryStatusPage = await StatusPage.findOne({ url }); - if (!preliminaryStatusPage) { - const error = new Error(stringService.statusPageNotFound); - error.status = 404; + createStatusPage = async ({ statusPageData, image, userId, teamId }) => { + try { + const statusPage = new this.StatusPage({ + ...statusPageData, + userId, + teamId, + }); + if (image) { + statusPage.logo = { + data: image.buffer, + contentType: image.mimetype, + }; + } + await statusPage.save(); + return statusPage; + } catch (error) { + if (error?.code === 11000) { + // Handle duplicate URL errors + error.status = 400; + error.message = this.stringService.statusPageUrlNotUnique; + } + error.service = SERVICE_NAME; + error.method = "createStatusPage"; throw error; } + }; - if (!preliminaryStatusPage.monitors || preliminaryStatusPage.monitors.length === 0) { - const { _id, color, companyName, isPublished, logo, originalMonitors, showCharts, showUptimePercentage, timezone, showAdminLoginLink, url } = - preliminaryStatusPage; - return { - statusPage: { - _id, - color, - companyName, - isPublished, - logo, - originalMonitors, - showCharts, - showUptimePercentage, - timezone, - showAdminLoginLink, - url, - }, - monitors: [], - }; + updateStatusPage = async (statusPageData, image) => { + try { + if (image) { + statusPageData.logo = { + data: image.buffer, + contentType: image.mimetype, + }; + } else { + statusPageData.logo = null; + } + + if (statusPageData.deleteSubmonitors === "true") { + statusPageData.subMonitors = []; + } + const statusPage = await this.StatusPage.findOneAndUpdate({ url: statusPageData.url }, statusPageData, { + new: true, + }); + + return statusPage; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "updateStatusPage"; + throw error; } + }; - const statusPageQuery = await StatusPage.aggregate([ - { $match: { url: url } }, - { - $set: { - originalMonitors: "$monitors", - }, - }, - { - $lookup: { - from: "monitors", - localField: "monitors", - foreignField: "_id", - as: "monitors", - }, - }, - { - $unwind: { - path: "$monitors", - preserveNullAndEmptyArrays: true, - }, - }, - { - $lookup: { - from: "maintenancewindows", - let: { monitorId: "$monitors._id" }, - pipeline: [{ $match: { $expr: { $eq: ["$monitorId", "$$monitorId"] } } }], - as: "monitors.maintenanceWindows", - }, - }, - { - $lookup: { - from: "checks", - let: { monitorId: "$monitors._id" }, - pipeline: [ - { - $match: { - $expr: { $eq: ["$monitorId", "$$monitorId"] }, - }, - }, - { $sort: { createdAt: -1 } }, - { $limit: 25 }, - ], - as: "monitors.checks", - }, - }, - { - $addFields: { - "monitors.orderIndex": { - $indexOfArray: ["$originalMonitors", "$monitors._id"], - }, - "monitors.isMaintenance": { - $reduce: { - input: "$monitors.maintenanceWindows", - initialValue: false, - in: { - $or: [ - "$$value", - { - $and: [{ $eq: ["$$this.active", true] }, { $lte: ["$$this.start", "$$NOW"] }, { $gte: ["$$this.end", "$$NOW"] }], - }, - ], - }, - }, - }, - }, - }, - { $match: { "monitors.orderIndex": { $ne: -1 } } }, - { $sort: { "monitors.orderIndex": 1 } }, + getStatusPageByUrl = async (url) => { + // TODO This is deprecated, can remove and have controller call getStatusPage + try { + return this.getStatusPage(url); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getStatusPageByUrl"; + throw error; + } + }; - { - $group: { - _id: "$_id", - statusPage: { $first: "$$ROOT" }, - monitors: { $push: "$monitors" }, - }, - }, - { - $project: { + getStatusPagesByTeamId = async (teamId) => { + try { + const statusPages = await this.StatusPage.find({ teamId }); + return statusPages; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getStatusPagesByTeamId"; + throw error; + } + }; + + getStatusPage = async (url) => { + try { + const preliminaryStatusPage = await this.StatusPage.findOne({ url }); + if (!preliminaryStatusPage) { + const error = new Error(this.stringService.statusPageNotFound); + error.status = 404; + throw error; + } + + if (!preliminaryStatusPage.monitors || preliminaryStatusPage.monitors.length === 0) { + const { _id, color, companyName, isPublished, logo, originalMonitors, showCharts, showUptimePercentage, timezone, showAdminLoginLink, url } = + preliminaryStatusPage; + return { statusPage: { - _id: 1, - color: 1, - companyName: 1, - isPublished: 1, - logo: 1, - originalMonitors: 1, - showCharts: 1, - showUptimePercentage: 1, - timezone: 1, - showAdminLoginLink: 1, - url: 1, + _id, + color, + companyName, + isPublished, + logo, + originalMonitors, + showCharts, + showUptimePercentage, + timezone, + showAdminLoginLink, + url, }, - monitors: { - _id: 1, - userId: 1, - teamId: 1, - name: 1, - description: 1, - status: 1, - type: 1, - ignoreTlsErrors: 1, - jsonPath: 1, - expectedValue: 1, - matchMethod: 1, - url: 1, - port: 1, - isActive: 1, - interval: 1, - uptimePercentage: 1, - notifications: 1, - secret: 1, - thresholds: 1, - alertThreshold: 1, - cpuAlertThreshold: 1, - memoryAlertThreshold: 1, - diskAlertThreshold: 1, - tempAlertThreshold: 1, - checks: 1, - isMaintenance: 1, - createdAt: 1, - updatedAt: 1, + monitors: [], + }; + } + + const statusPageQuery = await this.StatusPage.aggregate([ + { $match: { url: url } }, + { + $set: { + originalMonitors: "$monitors", }, }, - }, - ]); - if (!statusPageQuery.length) { - const error = new Error(stringService.statusPageNotFound); - error.status = 404; + { + $lookup: { + from: "monitors", + localField: "monitors", + foreignField: "_id", + as: "monitors", + }, + }, + { + $unwind: { + path: "$monitors", + preserveNullAndEmptyArrays: true, + }, + }, + { + $lookup: { + from: "maintenancewindows", + let: { monitorId: "$monitors._id" }, + pipeline: [{ $match: { $expr: { $eq: ["$monitorId", "$$monitorId"] } } }], + as: "monitors.maintenanceWindows", + }, + }, + { + $lookup: { + from: "checks", + let: { monitorId: "$monitors._id" }, + pipeline: [ + { + $match: { + $expr: { $eq: ["$monitorId", "$$monitorId"] }, + }, + }, + { $sort: { createdAt: -1 } }, + { $limit: 25 }, + ], + as: "monitors.checks", + }, + }, + { + $addFields: { + "monitors.orderIndex": { + $indexOfArray: ["$originalMonitors", "$monitors._id"], + }, + "monitors.isMaintenance": { + $reduce: { + input: "$monitors.maintenanceWindows", + initialValue: false, + in: { + $or: [ + "$$value", + { + $and: [{ $eq: ["$$this.active", true] }, { $lte: ["$$this.start", "$$NOW"] }, { $gte: ["$$this.end", "$$NOW"] }], + }, + ], + }, + }, + }, + }, + }, + { $match: { "monitors.orderIndex": { $ne: -1 } } }, + { $sort: { "monitors.orderIndex": 1 } }, + + { + $group: { + _id: "$_id", + statusPage: { $first: "$$ROOT" }, + monitors: { $push: "$monitors" }, + }, + }, + { + $project: { + statusPage: { + _id: 1, + color: 1, + companyName: 1, + isPublished: 1, + logo: 1, + originalMonitors: 1, + showCharts: 1, + showUptimePercentage: 1, + timezone: 1, + showAdminLoginLink: 1, + url: 1, + }, + monitors: { + _id: 1, + userId: 1, + teamId: 1, + name: 1, + description: 1, + status: 1, + type: 1, + ignoreTlsErrors: 1, + jsonPath: 1, + expectedValue: 1, + matchMethod: 1, + url: 1, + port: 1, + isActive: 1, + interval: 1, + uptimePercentage: 1, + notifications: 1, + secret: 1, + thresholds: 1, + alertThreshold: 1, + cpuAlertThreshold: 1, + memoryAlertThreshold: 1, + diskAlertThreshold: 1, + tempAlertThreshold: 1, + checks: 1, + isMaintenance: 1, + createdAt: 1, + updatedAt: 1, + }, + }, + }, + ]); + if (!statusPageQuery.length) { + const error = new Error(this.stringService.statusPageNotFound); + error.status = 404; + throw error; + } + + const { statusPage, monitors } = statusPageQuery[0]; + + const normalizedMonitors = monitors.map((monitor) => { + return { + ...monitor, + checks: this.NormalizeData(monitor.checks, 10, 100), + }; + }); + + return { statusPage, monitors: normalizedMonitors }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getStatusPageByUrl"; throw error; } + }; - const { statusPage, monitors } = statusPageQuery[0]; + deleteStatusPage = async (url) => { + try { + await this.StatusPage.deleteOne({ url }); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteStatusPage"; + throw error; + } + }; + deleteStatusPagesByMonitorId = async (monitorId) => { + try { + await this.StatusPage.deleteMany({ monitors: { $in: [monitorId] } }); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteStatusPageByMonitorId"; + throw error; + } + }; +} - const normalizedMonitors = monitors.map((monitor) => { - return { - ...monitor, - checks: NormalizeData(monitor.checks, 10, 100), - }; - }); - - return { statusPage, monitors: normalizedMonitors }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getStatusPageByUrl"; - throw error; - } -}; - -const deleteStatusPage = async (url) => { - try { - await StatusPage.deleteOne({ url }); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteStatusPage"; - throw error; - } -}; - -const deleteStatusPagesByMonitorId = async (monitorId) => { - try { - await StatusPage.deleteMany({ monitors: { $in: [monitorId] } }); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteStatusPageByMonitorId"; - throw error; - } -}; - -export { - createStatusPage, - updateStatusPage, - getStatusPagesByTeamId, - getStatusPage, - getStatusPageByUrl, - deleteStatusPage, - deleteStatusPagesByMonitorId, -}; +export default StatusPageModule; diff --git a/server/src/db/mongo/modules/userModule.js b/server/src/db/mongo/modules/userModule.js index 974f2d2ed..83b3b15f9 100755 --- a/server/src/db/mongo/modules/userModule.js +++ b/server/src/db/mongo/modules/userModule.js @@ -1,261 +1,171 @@ -import UserModel from "../../models/User.js"; -import TeamModel from "../../models/Team.js"; -import { GenerateAvatarImage } from "../../../utils/imageProcessing.js"; - -const DUPLICATE_KEY_CODE = 11000; // MongoDB error code for duplicate key -import { ParseBoolean } from "../../../utils/utils.js"; -import ServiceRegistry from "../../../service/system/serviceRegistry.js"; -import StringService from "../../../service/system/stringService.js"; const SERVICE_NAME = "userModule"; +const DUPLICATE_KEY_CODE = 11000; // MongoDB error code for duplicate key -const checkSuperadmin = async () => { - const superAdmin = await UserModel.findOne({ role: "superadmin" }); - if (superAdmin !== null) { - return true; +class UserModule { + constructor({ User, Team, GenerateAvatarImage, ParseBoolean, stringService }) { + this.User = User; + this.Team = Team; + this.GenerateAvatarImage = GenerateAvatarImage; + this.ParseBoolean = ParseBoolean; + this.stringService = stringService; } - return false; -}; -/** - * Insert a User - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const insertUser = async (userData, imageFile, generateAvatarImage = GenerateAvatarImage) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - try { - if (imageFile) { - // 1. Save the full size image - userData.profileImage = { - data: imageFile.buffer, - contentType: imageFile.mimetype, - }; + checkSuperadmin = async () => { + try { + const superAdmin = await this.User.findOne({ role: "superadmin" }); + if (superAdmin !== null) { + return true; + } + return false; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "checkSuperadmin"; + throw error; + } + }; - // 2. Get the avatar sized image - const avatar = await generateAvatarImage(imageFile); - userData.avatarImage = avatar; + insertUser = async (userData, imageFile) => { + try { + if (imageFile) { + // 1. Save the full size image + userData.profileImage = { + data: imageFile.buffer, + contentType: imageFile.mimetype, + }; + + // 2. Get the avatar sized image + const avatar = await this.GenerateAvatarImage(imageFile); + userData.avatarImage = avatar; + } + + // Handle creating team if superadmin + if (userData.role.includes("superadmin")) { + const team = new this.Team({ + email: userData.email, + }); + userData.teamId = team._id; + userData.checkTTL = 60 * 60 * 24 * 30; + await team.save(); + } + + const newUser = new this.User(userData); + await newUser.save(); + return await this.User.findOne({ _id: newUser._id }).select("-password").select("-profileImage"); // .select() doesn't work with create, need to save then find + } catch (error) { + if (error.code === DUPLICATE_KEY_CODE) { + error.message = this.stringService.dbUserExists; + } + error.service = SERVICE_NAME; + error.method = "insertUser"; + throw error; + } + }; + getUserByEmail = async (email) => { + try { + // Need the password to be able to compare, removed .select() + // We can strip the hash before returning the user + const user = await this.User.findOne({ email: email }).select("-profileImage"); + if (!user) { + throw new Error(this.stringService.dbUserNotFound); + } + return user; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getUserByEmail"; + throw error; + } + }; + + updateUser = async ({ userId, user, file }) => { + if (!userId) { + throw new Error("No user in request"); } - // Handle creating team if superadmin - if (userData.role.includes("superadmin")) { - const team = new TeamModel({ - email: userData.email, - }); - userData.teamId = team._id; - userData.checkTTL = 60 * 60 * 24 * 30; - await team.save(); + try { + const candidateUser = { ...user }; + + if (this.ParseBoolean(candidateUser.deleteProfileImage) === true) { + candidateUser.profileImage = null; + candidateUser.avatarImage = null; + } else if (file) { + // 1. Save the full size image + candidateUser.profileImage = { + data: file.buffer, + contentType: file.mimetype, + }; + + // 2. Get the avatar sized image + const avatar = await this.GenerateAvatarImage(file); + candidateUser.avatarImage = avatar; + } + + const updatedUser = await this.User.findByIdAndUpdate( + userId, + candidateUser, + { new: true } // Returns updated user instead of pre-update user + ) + .select("-password") + .select("-profileImage"); + return updatedUser; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "updateUser"; + throw error; } - - const newUser = new UserModel(userData); - await newUser.save(); - return await UserModel.findOne({ _id: newUser._id }).select("-password").select("-profileImage"); // .select() doesn't work with create, need to save then find - } catch (error) { - if (error.code === DUPLICATE_KEY_CODE) { - error.message = stringService.dbUserExists; + }; + deleteUser = async (userId) => { + try { + const deletedUser = await this.User.findByIdAndDelete(userId); + if (!deletedUser) { + throw new Error(this.stringService.dbUserNotFound); + } + return deletedUser; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteUser"; + throw error; } - error.service = SERVICE_NAME; - error.method = "insertUser"; - throw error; - } -}; + }; -/** - * Get User by Email - * Gets a user by Email. Not sure if we'll ever need this except for login. - * If not needed except for login, we can move password comparison here - * Throws error if user not found - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const getUserByEmail = async (email) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - - try { - // Need the password to be able to compare, removed .select() - // We can strip the hash before returning the user - const user = await UserModel.findOne({ email: email }).select("-profileImage"); - if (!user) { - throw new Error(stringService.dbUserNotFound); + getAllUsers = async () => { + try { + const users = await this.User.find().select("-password").select("-profileImage"); + return users; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getAllUsers"; + throw error; } - return user; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getUserByEmail"; - throw error; - } -}; + }; -/** - * Update a user by ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ + getUserById = async (roles, userId) => { + try { + if (!roles.includes("superadmin")) { + throw new Error("User is not a superadmin"); + } -const updateUser = async ({ userId, user, file }) => { - if (!userId) { - throw new Error("No user in request"); - } + const user = await this.User.findById(userId).select("-password").select("-profileImage"); + if (!user) { + throw new Error("User not found"); + } - try { - const candidateUser = { ...user }; - - if (ParseBoolean(candidateUser.deleteProfileImage) === true) { - candidateUser.profileImage = null; - candidateUser.avatarImage = null; - } else if (file) { - // 1. Save the full size image - candidateUser.profileImage = { - data: file.buffer, - contentType: file.mimetype, - }; - - // 2. Get the avatar sized image - const avatar = await GenerateAvatarImage(file); - candidateUser.avatarImage = avatar; + return user; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getUserById"; + throw error; } + }; - // ****************************************** - // End handling profile image - // ****************************************** - - const updatedUser = await UserModel.findByIdAndUpdate( - userId, - candidateUser, - { new: true } // Returns updated user instead of pre-update user - ) - .select("-password") - .select("-profileImage"); - return updatedUser; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "updateUser"; - throw error; - } -}; - -/** - * Delete a user by ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const deleteUser = async (userId) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - - try { - const deletedUser = await UserModel.findByIdAndDelete(userId); - if (!deletedUser) { - throw new Error(stringService.dbUserNotFound); + editUserById = async (userId, user) => { + try { + await this.User.findByIdAndUpdate(userId, user, { new: true }).select("-password").select("-profileImage"); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "editUserById"; + throw error; } - return deletedUser; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteUser"; - throw error; - } -}; + }; +} -/** - * Delete a user by ID - * @async - * @param {string} teamId - * @returns {void} - * @throws {Error} - */ -const deleteTeam = async (teamId) => { - try { - await TeamModel.findByIdAndDelete(teamId); - return true; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteTeam"; - throw error; - } -}; - -const deleteAllOtherUsers = async () => { - try { - await UserModel.deleteMany({ role: { $ne: "superadmin" } }); - return true; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteAllOtherUsers"; - throw error; - } -}; - -const getAllUsers = async () => { - try { - const users = await UserModel.find().select("-password").select("-profileImage"); - return users; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getAllUsers"; - throw error; - } -}; - -const logoutUser = async (userId) => { - try { - await UserModel.updateOne({ _id: userId }, { $unset: { authToken: null } }); - return true; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "logoutUser"; - throw error; - } -}; - -const getUserById = async (roles, userId) => { - try { - if (!roles.includes("superadmin")) { - throw new Error("User is not a superadmin"); - } - - const user = await UserModel.findById(userId).select("-password").select("-profileImage"); - if (!user) { - throw new Error("User not found"); - } - - return user; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getUserById"; - throw error; - } -}; - -const editUserById = async (userId, user) => { - try { - await UserModel.findByIdAndUpdate(userId, user, { new: true }).select("-password").select("-profileImage"); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "editUserById"; - throw error; - } -}; - -export { - checkSuperadmin, - insertUser, - getUserByEmail, - updateUser, - deleteUser, - deleteTeam, - deleteAllOtherUsers, - getAllUsers, - logoutUser, - getUserById, - editUserById, -}; +export default UserModule; diff --git a/server/src/db/mongo/utils/seedDb.js b/server/src/db/mongo/utils/seedDb.js index 64f23178c..224744a51 100755 --- a/server/src/db/mongo/utils/seedDb.js +++ b/server/src/db/mongo/utils/seedDb.js @@ -1,6 +1,6 @@ import Monitor from "../../models/Monitor.js"; import Check from "../../models/Check.js"; -import logger from "../../../utils/logger.js"; +import { logger } from "../../../utils/logger.js"; const generateRandomUrl = () => { const domains = ["example.com", "test.org", "demo.net", "sample.io", "mock.dev"]; diff --git a/server/src/index.js b/server/src/index.js index a34a517ca..6b12eb25d 100755 --- a/server/src/index.js +++ b/server/src/index.js @@ -2,15 +2,16 @@ import { initializeServices } from "./config/services.js"; import { initializeControllers } from "./config/controllers.js"; import { createApp } from "./app.js"; import { initShutdownListener } from "./shutdown.js"; -import logger from "./utils/logger.js"; import { fileURLToPath } from "url"; import path from "path"; import fs from "fs"; +import Logger from "./utils/logger.js"; import SettingsService from "./service/system/settingsService.js"; import AppSettings from "./db/models/AppSettings.js"; const SERVICE_NAME = "Server"; +let logger; const startApp = async () => { // FE path @@ -18,12 +19,16 @@ const startApp = async () => { const __dirname = path.dirname(__filename); const openApiSpec = JSON.parse(fs.readFileSync(path.join(__dirname, "../openapi.json"), "utf8")); const frontendPath = path.join(__dirname, "public"); + // Create services const settingsService = new SettingsService(AppSettings); - const appSettings = settingsService.loadSettings(); + const envSettings = settingsService.loadSettings(); + + // Create logger + logger = new Logger({ envSettings }); // Initialize services - const services = await initializeServices(appSettings, settingsService); + const services = await initializeServices({ logger, envSettings, settingsService }); // Initialize controllers const controllers = initializeControllers(services); @@ -31,12 +36,12 @@ const startApp = async () => { const app = createApp({ services, controllers, - appSettings, + envSettings, frontendPath, openApiSpec, }); - const port = appSettings.port || 52345; + const port = envSettings.port || 52345; const server = app.listen(port, () => { logger.info({ message: `Server started on port:${port}` }); }); diff --git a/server/src/middleware/handleErrors.js b/server/src/middleware/handleErrors.js index 782d37ad9..b0ded5ab7 100755 --- a/server/src/middleware/handleErrors.js +++ b/server/src/middleware/handleErrors.js @@ -1,4 +1,4 @@ -import logger from "../utils/logger.js"; +import { logger } from "../utils/logger.js"; import ServiceRegistry from "../service/system/serviceRegistry.js"; import StringService from "../service/system/stringService.js"; diff --git a/server/src/middleware/languageMiddleware.js b/server/src/middleware/languageMiddleware.js index effd49cc7..6293dc837 100755 --- a/server/src/middleware/languageMiddleware.js +++ b/server/src/middleware/languageMiddleware.js @@ -1,4 +1,4 @@ -import logger from "../utils/logger.js"; +import { logger } from "../utils/logger.js"; const languageMiddleware = (stringService, translationService) => async (req, res, next) => { try { diff --git a/server/src/middleware/rateLimiter.js b/server/src/middleware/rateLimiter.js new file mode 100644 index 000000000..70b080d23 --- /dev/null +++ b/server/src/middleware/rateLimiter.js @@ -0,0 +1,17 @@ +import rateLimit from "express-rate-limit"; + +export const generalApiLimiter = rateLimit({ + window: 60 * 1000, + limit: 600, + standardHeaders: true, + legacyHeaders: false, + ipv6Subnet: 64, +}); + +export const authApiLimiter = rateLimit({ + window: 60 * 1000, + limit: 15, + standardHeaders: true, + legacyHeaders: false, + ipv6Subnet: 64, +}); diff --git a/server/src/middleware/verifyOwnership.js b/server/src/middleware/verifyOwnership.js index 890c40b24..4431b0c26 100755 --- a/server/src/middleware/verifyOwnership.js +++ b/server/src/middleware/verifyOwnership.js @@ -1,4 +1,4 @@ -import logger from "../utils/logger.js"; +import { logger } from "../utils/logger.js"; import ServiceRegistry from "../service/system/serviceRegistry.js"; import StringService from "../service/system/stringService.js"; import { ObjectId } from "mongodb"; diff --git a/server/src/service/business/checkService.js b/server/src/service/business/checkService.js index 262d8e863..7966fdbb4 100644 --- a/server/src/service/business/checkService.js +++ b/server/src/service/business/checkService.js @@ -23,7 +23,7 @@ class CheckService { throw this.errorService.createBadRequestError("No team ID in request"); } - const monitor = await this.db.getMonitorById(monitorId); + const monitor = await this.db.monitorModule.getMonitorById(monitorId); if (!monitor) { throw this.errorService.createNotFoundError("Monitor not found"); @@ -34,7 +34,7 @@ class CheckService { } let { type, sortOrder, dateRange, filter, ack, page, rowsPerPage, status } = query; - const result = await this.db.getChecksByMonitor({ + const result = await this.db.checkModule.getChecksByMonitor({ monitorId, type, sortOrder, @@ -55,7 +55,7 @@ class CheckService { throw this.errorService.createBadRequestError("No team ID in request"); } - const checkData = await this.db.getChecksByTeam({ + const checkData = await this.db.checkModule.getChecksByTeam({ sortOrder, dateRange, filter, @@ -72,7 +72,7 @@ class CheckService { throw this.errorService.createBadRequestError("No team ID in request"); } - const summary = await this.db.getChecksSummaryByTeamId({ teamId }); + const summary = await this.db.checkModule.getChecksSummaryByTeamId({ teamId }); return summary; }; @@ -85,7 +85,7 @@ class CheckService { throw this.errorService.createBadRequestError("No team ID in request"); } - const updatedCheck = await this.db.ackCheck(checkId, teamId, ack); + const updatedCheck = await this.db.checkModule.ackCheck(checkId, teamId, ack); return updatedCheck; }; @@ -95,7 +95,7 @@ class CheckService { throw this.errorService.createBadRequestError("No monitor ID in request"); } - const monitor = await this.db.getMonitorById(monitorId); + const monitor = await this.db.monitorModule.getMonitorById(monitorId); if (!monitor) { throw this.errorService.createNotFoundError("Monitor not found"); } @@ -105,7 +105,7 @@ class CheckService { } } - const updatedChecks = await this.db.ackAllChecks(monitorId, teamId, ack, path); + const updatedChecks = await this.db.checkModule.ackAllChecks(monitorId, teamId, ack, path); return updatedChecks; }; @@ -118,7 +118,7 @@ class CheckService { throw this.errorService.createBadRequestError("No team ID in request"); } - const monitor = await this.db.getMonitorById(monitorId); + const monitor = await this.db.monitorModule.getMonitorById(monitorId); if (!monitor) { throw this.errorService.createNotFoundError("Monitor not found"); @@ -128,7 +128,7 @@ class CheckService { throw this.errorService.createAuthorizationError(); } - const deletedCount = await this.db.deleteChecks(monitorId); + const deletedCount = await this.db.checkModule.deleteChecks(monitorId); return deletedCount; }; deleteChecksByTeamId = async ({ teamId }) => { @@ -136,14 +136,14 @@ class CheckService { throw this.errorService.createBadRequestError("No team ID in request"); } - const deletedCount = await this.db.deleteChecksByTeamId(teamId); + const deletedCount = await this.db.checkModule.deleteChecksByTeamId(teamId); return deletedCount; }; updateChecksTTL = async ({ teamId, ttl }) => { const SECONDS_PER_DAY = 86400; const newTTL = parseInt(ttl, 10) * SECONDS_PER_DAY; - await this.db.updateChecksTTL(teamId, newTTL); + await this.db.checkModule.updateChecksTTL(teamId, newTTL); }; } diff --git a/server/src/service/business/inviteService.js b/server/src/service/business/inviteService.js index 161045971..369ed195a 100644 --- a/server/src/service/business/inviteService.js +++ b/server/src/service/business/inviteService.js @@ -17,12 +17,12 @@ class InviteService { getInviteToken = async ({ invite, teamId }) => { invite.teamId = teamId; - const inviteToken = await this.db.requestInviteToken(invite); + const inviteToken = await this.db.inviteModule.requestInviteToken(invite); return inviteToken; }; sendInviteEmail = async ({ inviteRequest, firstName }) => { - const inviteToken = await this.db.requestInviteToken({ ...inviteRequest }); + const inviteToken = await this.db.inviteModule.requestInviteToken({ ...inviteRequest }); const { clientHost } = this.settingsService.getSettings(); const html = await this.emailService.buildEmail("employeeActivationTemplate", { @@ -36,7 +36,7 @@ class InviteService { }; verifyInviteToken = async ({ inviteToken }) => { - const invite = await this.db.getInviteToken(inviteToken); + const invite = await this.db.inviteModule.getInviteToken(inviteToken); return invite; }; } diff --git a/server/src/service/business/maintenanceWindowService.js b/server/src/service/business/maintenanceWindowService.js index ec0fa5345..9d52702e9 100644 --- a/server/src/service/business/maintenanceWindowService.js +++ b/server/src/service/business/maintenanceWindowService.js @@ -16,7 +16,7 @@ class MaintenanceWindowService { createMaintenanceWindow = async ({ teamId, body }) => { const monitorIds = body.monitors; - const monitors = await this.db.getMonitorsByIds(monitorIds); + const monitors = await this.db.monitorModule.getMonitorsByIds(monitorIds); const unauthorizedMonitors = monitors.filter((monitor) => !monitor.teamId.equals(teamId)); @@ -25,7 +25,7 @@ class MaintenanceWindowService { } const dbTransactions = monitorIds.map((monitorId) => { - return this.db.createMaintenanceWindow({ + return this.db.maintenanceWindowModule.createMaintenanceWindow({ teamId, monitorId, name: body.name, @@ -39,26 +39,26 @@ class MaintenanceWindowService { }; getMaintenanceWindowById = async ({ id, teamId }) => { - const maintenanceWindow = await this.db.getMaintenanceWindowById({ id, teamId }); + const maintenanceWindow = await this.db.maintenanceWindowModule.getMaintenanceWindowById({ id, teamId }); return maintenanceWindow; }; getMaintenanceWindowsByTeamId = async ({ teamId, query }) => { - const maintenanceWindows = await this.db.getMaintenanceWindowsByTeamId(teamId, query); + const maintenanceWindows = await this.db.maintenanceWindowModule.getMaintenanceWindowsByTeamId(teamId, query); return maintenanceWindows; }; getMaintenanceWindowsByMonitorId = async ({ monitorId, teamId }) => { - const maintenanceWindows = await this.db.getMaintenanceWindowsByMonitorId({ monitorId, teamId }); + const maintenanceWindows = await this.db.maintenanceWindowModule.getMaintenanceWindowsByMonitorId({ monitorId, teamId }); return maintenanceWindows; }; deleteMaintenanceWindow = async ({ id, teamId }) => { - await this.db.deleteMaintenanceWindowById({ id, teamId }); + await this.db.maintenanceWindowModule.deleteMaintenanceWindowById({ id, teamId }); }; editMaintenanceWindow = async ({ id, teamId, body }) => { - const editedMaintenanceWindow = await this.db.editMaintenanceWindowById({ id, body, teamId }); + const editedMaintenanceWindow = await this.db.maintenanceWindowModule.editMaintenanceWindowById({ id, body, teamId }); return editedMaintenanceWindow; }; } diff --git a/server/src/service/business/monitorService.js b/server/src/service/business/monitorService.js index d307e1837..32f7d1ebe 100644 --- a/server/src/service/business/monitorService.js +++ b/server/src/service/business/monitorService.js @@ -20,20 +20,20 @@ class MonitorService { } verifyTeamAccess = async ({ teamId, monitorId }) => { - const monitor = await this.db.getMonitorById(monitorId); + const monitor = await this.db.monitorModule.getMonitorById(monitorId); if (!monitor?.teamId?.equals(teamId)) { throw this.errorService.createAuthorizationError(); } }; getAllMonitors = async () => { - const monitors = await this.db.getAllMonitors(); + const monitors = await this.db.monitorModule.getAllMonitors(); return monitors; }; getUptimeDetailsById = async ({ teamId, monitorId, dateRange, normalize }) => { await this.verifyTeamAccess({ teamId, monitorId }); - const data = await this.db.getUptimeDetailsById({ + const data = await this.db.monitorModule.getUptimeDetailsById({ monitorId, dateRange, normalize, @@ -44,7 +44,7 @@ class MonitorService { getMonitorStatsById = async ({ teamId, monitorId, limit, sortOrder, dateRange, numToDisplay, normalize }) => { await this.verifyTeamAccess({ teamId, monitorId }); - const monitorStats = await this.db.getMonitorStatsById({ + const monitorStats = await this.db.monitorModule.getMonitorStatsById({ monitorId, limit, sortOrder, @@ -58,20 +58,20 @@ class MonitorService { getHardwareDetailsById = async ({ teamId, monitorId, dateRange }) => { await this.verifyTeamAccess({ teamId, monitorId }); - const monitor = await this.db.getHardwareDetailsById({ monitorId, dateRange }); + const monitor = await this.db.monitorModule.getHardwareDetailsById({ monitorId, dateRange }); return monitor; }; getMonitorById = async ({ teamId, monitorId }) => { await this.verifyTeamAccess({ teamId, monitorId }); - const monitor = await this.db.getMonitorById(monitorId); + const monitor = await this.db.monitorModule.getMonitorById(monitorId); return monitor; }; createMonitor = async ({ teamId, userId, body }) => { - const monitor = await this.db.createMonitor({ + const monitor = await this.db.monitorModule.createMonitor({ body, teamId, userId, @@ -122,7 +122,7 @@ class MonitorService { await createMonitorsBodyValidation.validateAsync(enrichedData); - const monitors = await this.db.createBulkMonitors(enrichedData); + const monitors = await this.db.monitorModule.createBulkMonitors(enrichedData); await Promise.all( monitors.map(async (monitor) => { @@ -141,21 +141,21 @@ class MonitorService { deleteMonitor = async ({ teamId, monitorId }) => { await this.verifyTeamAccess({ teamId, monitorId }); - const monitor = await this.db.deleteMonitor({ teamId, monitorId }); + const monitor = await this.db.monitorModule.deleteMonitor({ teamId, monitorId }); await this.jobQueue.deleteJob(monitor); - await this.db.deleteStatusPagesByMonitorId(monitor._id); + await this.db.statusPageModule.deleteStatusPagesByMonitorId(monitor._id); return monitor; }; deleteAllMonitors = async ({ teamId }) => { - const { monitors, deletedCount } = await this.db.deleteAllMonitors(teamId); + const { monitors, deletedCount } = await this.db.monitorModule.deleteAllMonitors(teamId); await Promise.all( monitors.map(async (monitor) => { try { await this.jobQueue.deleteJob(monitor); - await this.db.deleteChecks(monitor._id); - await this.db.deletePageSpeedChecksByMonitorId(monitor._id); - await this.db.deleteNotificationsByMonitorId(monitor._id); + await this.db.checkModule.deleteChecks(monitor._id); + await this.db.pageSpeedCheckModule.deletePageSpeedChecksByMonitorId(monitor._id); + await this.db.notificationsModule.deleteNotificationsByMonitorId(monitor._id); } catch (error) { this.logger.warn({ message: `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`, @@ -171,19 +171,19 @@ class MonitorService { editMonitor = async ({ teamId, monitorId, body }) => { await this.verifyTeamAccess({ teamId, monitorId }); - const editedMonitor = await this.db.editMonitor({ monitorId, body }); + const editedMonitor = await this.db.monitorModule.editMonitor({ monitorId, body }); await this.jobQueue.updateJob(editedMonitor); }; pauseMonitor = async ({ teamId, monitorId }) => { await this.verifyTeamAccess({ teamId, monitorId }); - const monitor = await this.db.pauseMonitor({ monitorId }); + const monitor = await this.db.monitorModule.pauseMonitor({ monitorId }); monitor.isActive === true ? await this.jobQueue.resumeJob(monitor._id, monitor) : await this.jobQueue.pauseJob(monitor); return monitor; }; addDemoMonitors = async ({ userId, teamId }) => { - const demoMonitors = await this.db.addDemoMonitors(userId, teamId); + const demoMonitors = await this.db.monitorModuleaddDemoMonitors(userId, teamId); await Promise.all(demoMonitors.map((monitor) => this.jobQueue.addJob(monitor._id, monitor))); return demoMonitors; }; @@ -203,7 +203,7 @@ class MonitorService { }; getMonitorsByTeamId = async ({ teamId, limit, type, page, rowsPerPage, filter, field, order }) => { - const monitors = await this.db.getMonitorsByTeamId({ + const monitors = await this.db.monitorModule.getMonitorsByTeamId({ limit, type, page, @@ -217,7 +217,7 @@ class MonitorService { }; getMonitorsAndSummaryByTeamId = async ({ teamId, type, explain }) => { - const result = await this.db.getMonitorsAndSummaryByTeamId({ + const result = await this.db.monitorModule.getMonitorsAndSummaryByTeamId({ type, explain, teamId, @@ -226,7 +226,7 @@ class MonitorService { }; getMonitorsWithChecksByTeamId = async ({ teamId, limit, type, page, rowsPerPage, filter, field, order, explain }) => { - const result = await this.db.getMonitorsWithChecksByTeamId({ + const result = await this.db.monitorModule.getMonitorsWithChecksByTeamId({ limit, type, page, @@ -241,7 +241,7 @@ class MonitorService { }; exportMonitorsToCSV = async ({ teamId }) => { - const monitors = await this.db.getMonitorsByTeamId({ teamId }); + const monitors = await this.db.monitorModule.getMonitorsByTeamId({ teamId }); if (!monitors || monitors.length === 0) { throw this.errorService.createNotFoundError("No monitors to export"); diff --git a/server/src/service/business/userService.js b/server/src/service/business/userService.js index d0d1a1205..0a965bced 100644 --- a/server/src/service/business/userService.js +++ b/server/src/service/business/userService.js @@ -29,18 +29,18 @@ class UserService { registerUser = async (user, file) => { // Create a new user // If superAdmin exists, a token should be attached to all further register requests - const superAdminExists = await this.db.checkSuperadmin(); + const superAdminExists = await this.db.userModule.checkSuperadmin(); if (superAdminExists) { - const invitedUser = await this.db.getInviteTokenAndDelete(user.inviteToken); + const invitedUser = await this.db.inviteModule.getInviteTokenAndDelete(user.inviteToken); user.role = invitedUser.role; user.teamId = invitedUser.teamId; } else { // This is the first account, create JWT secret to use if one is not supplied by env - const jwtSecret = crypto.randomBytes(64).toString("hex"); - await this.db.updateAppSettings({ jwtSecret }); + const jwtSecret = this.crypto.randomBytes(64).toString("hex"); + await this.db.settingsModule.updateAppSettings({ jwtSecret }); } - const newUser = await this.db.insertUser({ ...user }, file); + const newUser = await this.db.userModule.insertUser({ ...user }, file); this.logger.debug({ message: "New user created", @@ -83,7 +83,7 @@ class UserService { loginUser = async (email, password) => { // Check if user exists - const user = await this.db.getUserByEmail(email); + const user = await this.db.userModule.getUserByEmail(email); // Compare password const match = await user.comparePassword(password); if (match !== true) { @@ -110,7 +110,7 @@ class UserService { // Add user email to body for DB operation updates.email = currentUser.email; // Get user - const user = await this.db.getUserByEmail(currentUser.email); + const user = await this.db.userModule.getUserByEmail(currentUser.email); // Compare passwords const match = await user.comparePassword(updates?.password); // If not a match, throw a 403 @@ -122,18 +122,18 @@ class UserService { updates.password = updates.newPassword; } - const updatedUser = await this.db.updateUser({ userId: currentUser?._id, user: updates, file: file }); + const updatedUser = await this.db.userModule.updateUser({ userId: currentUser?._id, user: updates, file: file }); return updatedUser; }; checkSuperadminExists = async () => { - const superAdminExists = await this.db.checkSuperadmin(); + const superAdminExists = await this.db.userModule.checkSuperadmin(); return superAdminExists; }; requestRecovery = async (email) => { - const user = await this.db.getUserByEmail(email); - const recoveryToken = await this.db.requestRecoveryToken(email); + const user = await this.db.userModule.getUserByEmail(email); + const recoveryToken = await this.db.recoveryModule.requestRecoveryToken(email); const name = user.firstName; const { clientHost } = this.settingsService.getSettings(); const url = `${clientHost}/set-new-password/${recoveryToken.token}`; @@ -148,11 +148,11 @@ class UserService { }; validateRecovery = async (recoveryToken) => { - await this.db.validateRecoveryToken(recoveryToken); + await this.db.recoveryModule.validateRecoveryToken(recoveryToken); }; resetPassword = async (password, recoveryToken) => { - const user = await this.db.resetPassword(password, recoveryToken); + const user = await this.db.recoveryModule.resetPassword(password, recoveryToken); const appSettings = await this.settingsService.getSettings(); const token = this.issueToken(user._doc, appSettings); return { user, token }; @@ -181,7 +181,7 @@ class UserService { } // 1. Find all the monitors associated with the team ID if superadmin - const result = await this.db.getMonitorsByTeamId({ + const result = await this.db.monitorModule.getMonitorsByTeamId({ teamId: teamId, }); @@ -195,21 +195,21 @@ class UserService { )); } // 6. Delete the user by id - await this.db.deleteUser(userId); + await this.db.userModule.deleteUser(userId); }; getAllUsers = async () => { - const users = await this.db.getAllUsers(); + const users = await this.db.userModule.getAllUsers(); return users; }; getUserById = async (roles, userId) => { - const user = await this.db.getUserById(roles, userId); + const user = await this.db.userModule.getUserById(roles, userId); return user; }; editUserById = async (userId, user) => { - await this.db.editUserById(userId, user); + await this.db.userModule.editUserById(userId, user); }; } export default UserService; diff --git a/server/src/service/data/redisService.js b/server/src/service/data/redisService.js deleted file mode 100644 index 3bcfe73b8..000000000 --- a/server/src/service/data/redisService.js +++ /dev/null @@ -1,64 +0,0 @@ -const SERVICE_NAME = "RedisService"; - -class RedisService { - static SERVICE_NAME = SERVICE_NAME; - - constructor({ Redis, logger }) { - this.Redis = Redis; - this.connections = new Set(); - this.logger = logger; - } - - get serviceName() { - return RedisService.SERVICE_NAME; - } - - getNewConnection(options = {}) { - const connection = new this.Redis(process.env.REDIS_URL, { - retryStrategy: (times) => { - return null; - }, - ...options, - }); - this.connections.add(connection); - return connection; - } - - async closeAllConnections() { - const closePromises = Array.from(this.connections).map((conn) => - conn.quit().catch((err) => { - this.logger.error({ - message: "Error closing Redis connection", - service: SERVICE_NAME, - method: "closeAllConnections", - details: { error: err }, - }); - }) - ); - - await Promise.all(closePromises); - this.connections.clear(); - this.logger.info({ - message: "All Redis connections closed", - service: SERVICE_NAME, - method: "closeAllConnections", - }); - } - - async flushRedis() { - this.logger.info({ - message: "Flushing Redis", - service: SERVICE_NAME, - method: "flushRedis", - }); - const flushPromises = Array.from(this.connections).map((conn) => conn.flushall()); - await Promise.all(flushPromises); - this.logger.info({ - message: "Redis flushed", - service: SERVICE_NAME, - method: "flushRedis", - }); - } -} - -export default RedisService; diff --git a/server/src/service/infrastructure/JobQueue/JobQueue.js b/server/src/service/infrastructure/JobQueue/JobQueue.js deleted file mode 100644 index 88f0a3cac..000000000 --- a/server/src/service/infrastructure/JobQueue/JobQueue.js +++ /dev/null @@ -1,321 +0,0 @@ -const QUEUE_NAMES = ["uptime", "pagespeed", "hardware"]; -const SERVICE_NAME = "JobQueue"; -const HEALTH_CHECK_INTERVAL = 10 * 60 * 1000; // 10 minutes -const QUEUE_LOOKUP = { - hardware: "hardware", - http: "uptime", - ping: "uptime", - port: "uptime", - docker: "uptime", - pagespeed: "pagespeed", -}; -const getSchedulerId = (monitor) => `scheduler:${monitor.type}:${monitor._id}`; - -class JobQueue { - static SERVICE_NAME = SERVICE_NAME; - - constructor({ db, jobQueueHelper, logger, stringService }) { - this.db = db; - this.jobQueueHelper = jobQueueHelper; - this.stringService = stringService; - this.logger = logger; - this.queues = {}; - this.workers = []; - } - - get serviceName() { - return JobQueue.SERVICE_NAME; - } - - static async create({ db, jobQueueHelper, logger, stringService }) { - const instance = new JobQueue({ db, jobQueueHelper, logger, stringService }); - await instance.init(); - return instance; - } - - async init() { - try { - await this.initQueues(); - await this.initWorkers(); - const monitors = await this.db.getAllMonitors(); - await Promise.all( - monitors - .filter((monitor) => monitor.isActive) - .map(async (monitor) => { - try { - await this.addJob(monitor._id, monitor); - } catch (error) { - this.logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "initJobQueue", - stack: error.stack, - }); - } - }) - ); - this.healthCheckInterval = setInterval(async () => { - try { - const queueHealthChecks = await this.checkQueueHealth(); - const queueIsStuck = queueHealthChecks.some((healthCheck) => healthCheck.stuck); - if (queueIsStuck) { - this.logger.warn({ - message: "Queue is stuck", - service: SERVICE_NAME, - method: "periodicHealthCheck", - details: queueHealthChecks, - }); - await this.flushQueues(); - } - } catch (error) { - this.logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "periodicHealthCheck", - stack: error.stack, - }); - } - }, HEALTH_CHECK_INTERVAL); - } catch (error) { - this.logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "initJobQueue", - stack: error.stack, - }); - } - } - - async initQueues() { - const readyPromises = []; - - for (const queueName of QUEUE_NAMES) { - const q = this.jobQueueHelper.createQueue(queueName); - this.queues[queueName] = q; - readyPromises.push(q.waitUntilReady()); - } - await Promise.all(readyPromises); - this.logger.info({ - message: "Queues ready", - service: SERVICE_NAME, - method: "initQueues", - }); - } - - async initWorkers() { - const workerReadyPromises = []; - - for (const queueName of QUEUE_NAMES) { - const worker = this.jobQueueHelper.createWorker(queueName, this.queues[queueName]); - this.workers.push(worker); - workerReadyPromises.push(worker.waitUntilReady()); - } - await Promise.all(workerReadyPromises); - this.logger.info({ - message: "Workers ready", - service: SERVICE_NAME, - method: "initWorkers", - }); - } - - pauseJob = async (monitor) => { - this.deleteJob(monitor); - }; - - resumeJob = async (monitor) => { - this.addJob(monitor._id, monitor); - }; - - async addJob(jobName, monitor) { - this.logger.info({ - message: `Adding job ${monitor?.url ?? "No URL"}`, - service: SERVICE_NAME, - method: "addJob", - }); - - const queueName = QUEUE_LOOKUP[monitor.type]; - const queue = this.queues[queueName]; - if (typeof queue === "undefined") { - throw new Error(`Queue for ${monitor.type} not found`); - } - const jobTemplate = { - name: jobName, - data: monitor, - opts: { - attempts: 1, - backoff: { - type: "exponential", - delay: 1000, - }, - removeOnComplete: true, - removeOnFail: false, - timeout: 1 * 60 * 1000, - }, - }; - - const schedulerId = getSchedulerId(monitor); - await queue.upsertJobScheduler(schedulerId, { every: monitor?.interval ?? 60000 }, jobTemplate); - } - - async deleteJob(monitor) { - try { - const queue = this.queues[QUEUE_LOOKUP[monitor.type]]; - const schedulerId = getSchedulerId(monitor); - const wasDeleted = await queue.removeJobScheduler(schedulerId); - - if (wasDeleted === true) { - this.logger.info({ - message: this.stringService.jobQueueDeleteJob, - service: SERVICE_NAME, - method: "deleteJob", - details: `Deleted job ${monitor._id}`, - }); - return true; - } else { - this.logger.error({ - message: this.stringService.jobQueueDeleteJob, - service: SERVICE_NAME, - method: "deleteJob", - details: `Failed to delete job ${monitor._id}`, - }); - return false; - } - } catch (error) { - error.service === undefined ? (error.service = SERVICE_NAME) : null; - error.method === undefined ? (error.method = "deleteJob") : null; - throw error; - } - } - - async updateJob(monitor) { - await this.deleteJob(monitor); - await this.addJob(monitor._id, monitor); - } - - async getJobs() { - try { - let stats = {}; - await Promise.all( - QUEUE_NAMES.map(async (name) => { - const queue = this.queues[name]; - const jobs = await queue.getJobs(); - const ret = await Promise.all( - jobs.map(async (job) => { - const state = await job.getState(); - return { url: job.data.url, state, progress: job.progress }; - }) - ); - stats[name] = { jobs: ret }; - }) - ); - return stats; - } catch (error) { - error.service === undefined ? (error.service = SERVICE_NAME) : null; - error.method === undefined ? (error.method = "getJobStats") : null; - throw error; - } - } - - async getMetrics() { - try { - let metrics = {}; - - await Promise.all( - QUEUE_NAMES.map(async (name) => { - const queue = this.queues[name]; - const [waiting, active, failed, delayed, repeatableJobs] = await Promise.all([ - queue.getWaitingCount(), - queue.getActiveCount(), - queue.getFailedCount(), - queue.getDelayedCount(), - queue.getRepeatableJobs(), - ]); - - metrics[name] = { - waiting, - active, - failed, - delayed, - repeatableJobs: repeatableJobs.length, - }; - }) - ); - - return metrics; - } catch (error) { - this.logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "getMetrics", - stack: error.stack, - }); - } - } - - async checkQueueHealth() { - const res = []; - for (const queueName of QUEUE_NAMES) { - const q = this.queues[queueName]; - await q.waitUntilReady(); - - const lastJobProcessedTime = q.lastJobProcessedTime; - const currentTime = Date.now(); - const timeDiff = currentTime - lastJobProcessedTime; - - // Check for jobs - const jobCounts = await q.getJobCounts(); - const hasJobs = Object.values(jobCounts).some((count) => count > 0); - - res.push({ - queueName, - timeSinceLastJob: timeDiff, - stuck: hasJobs && timeDiff > 10000, - jobCounts, - }); - } - return res; - } - - async flushQueues() { - try { - this.logger.warn({ - message: "Flushing queues", - method: "flushQueues", - service: SERVICE_NAME, - }); - for (const worker of this.workers) { - await worker.close(); - } - this.workers = []; - - for (const queue of Object.values(this.queues)) { - await queue.obliterate(); - } - this.queue = {}; - await this.init(); - return true; - } catch (error) { - this.logger.warn({ - message: `${error.message} - Flushing redis manually`, - service: SERVICE_NAME, - method: "flushQueues", - }); - return await this.jobQueueHelper.flushRedis(); - } - } - - async shutdown() { - if (this.healthCheckInterval) { - clearInterval(this.healthCheckInterval); - this.healthCheckInterval = null; - } - for (const worker of this.workers) { - await worker.close(); - } - for (const queue of Object.values(this.queues)) { - await queue.obliterate(); - } - } -} - -export default JobQueue; diff --git a/server/src/service/infrastructure/JobQueue/JobQueueHelper.js b/server/src/service/infrastructure/JobQueue/JobQueueHelper.js deleted file mode 100644 index c746edae1..000000000 --- a/server/src/service/infrastructure/JobQueue/JobQueueHelper.js +++ /dev/null @@ -1,308 +0,0 @@ -const SERVICE_NAME = "JobQueueHelper"; - -class JobQueueHelper { - static SERVICE_NAME = SERVICE_NAME; - - constructor({ redisService, Queue, Worker, logger, db, networkService, statusService, notificationService }) { - this.db = db; - this.redisService = redisService; - this.Queue = Queue; - this.Worker = Worker; - this.logger = logger; - this.networkService = networkService; - this.statusService = statusService; - this.notificationService = notificationService; - } - - get serviceName() { - return JobQueueHelper.SERVICE_NAME; - } - - createQueue(queueName) { - const connection = this.redisService.getNewConnection(); - const q = new this.Queue(queueName, { - connection, - }); - q.lastJobProcessedTime = Date.now(); - q.on("cleaned", (jobs, type) => { - this.logger.debug({ - message: `Queue ${queueName} is cleaned with jobs: ${jobs} and type: ${type}`, - service: SERVICE_NAME, - method: "createQueue:cleaned", - }); - }); - q.on("error", (err) => { - this.logger.error({ - message: `Queue ${queueName} is error with msg: ${err}`, - service: SERVICE_NAME, - method: "createQueue:error", - }); - }); - q.on("ioredis:close", () => { - this.logger.debug({ - message: `Queue ${queueName} is ioredis:close`, - service: SERVICE_NAME, - method: "createQueue:ioredis:close", - }); - }); - q.on("paused", () => { - this.logger.debug({ - message: `Queue ${queueName} is paused`, - service: SERVICE_NAME, - method: "createQueue:paused", - }); - }); - q.on("progress", (job, progress) => { - this.logger.debug({ - message: `Queue ${queueName} is progress with msg: ${progress}`, - service: SERVICE_NAME, - method: "createQueue:progress", - }); - }); - q.on("removed", (job) => { - this.logger.debug({ - message: `Queue ${queueName} is removed with msg: ${job}`, - service: SERVICE_NAME, - method: "createQueue:removed", - }); - }); - q.on("resumed", () => { - this.logger.debug({ - message: `Queue ${queueName} is resumed`, - service: SERVICE_NAME, - method: "createQueue:resumed", - }); - }); - q.on("waiting", () => { - this.logger.debug({ - message: `Queue ${queueName} is waiting`, - service: SERVICE_NAME, - method: "createQueue:waiting", - }); - }); - return q; - } - - createWorker(queueName, queue) { - const connection = this.redisService.getNewConnection({ - maxRetriesPerRequest: null, - }); - const worker = new this.Worker(queueName, this.createJobHandler(queue), { - connection, - concurrency: 50, - }); - worker.on("active", (job) => { - this.logger.debug({ - message: `Worker ${queueName} is active`, - service: SERVICE_NAME, - method: "createWorker:active", - }); - }); - - worker.on("closed", () => { - this.logger.debug({ - message: `Worker ${queueName} is closed`, - service: SERVICE_NAME, - method: "createWorker:closed", - }); - }); - - worker.on("closing", (msg) => { - this.logger.debug({ - message: `Worker ${queueName} is closing with msg: ${msg}`, - service: SERVICE_NAME, - method: "createWorker:closing", - }); - }); - - worker.on("completed", (job) => { - this.logger.debug({ - message: `Worker ${queueName} is completed`, - service: SERVICE_NAME, - method: "createWorker:completed", - }); - }); - - worker.on("drained", () => { - this.logger.debug({ - message: `Worker ${queueName} is drained`, - service: SERVICE_NAME, - method: "createWorker:drained", - }); - }); - - worker.on("error", (failedReason) => { - this.logger.error({ - message: `Worker ${queueName} is error with msg: ${failedReason}`, - service: SERVICE_NAME, - method: "createWorker:error", - }); - }); - - worker.on("failed", (job, error, prev) => { - this.logger.error({ - message: `Worker ${queueName} is failed with msg: ${error.message}`, - service: error?.service ?? SERVICE_NAME, - method: error?.method ?? "createWorker:failed", - stack: error?.stack, - }); - }); - - worker.on("ioredis:close", () => { - this.logger.debug({ - message: `Worker ${queueName} is ioredis:close`, - service: SERVICE_NAME, - method: "createWorker:ioredis:close", - }); - }); - - worker.on("paused", () => { - this.logger.debug({ - message: `Worker ${queueName} is paused`, - service: SERVICE_NAME, - method: "createWorker:paused", - }); - }); - - worker.on("progress", (job, progress) => { - this.logger.debug({ - message: `Worker ${queueName} is progress with msg: ${progress}`, - service: SERVICE_NAME, - method: "createWorker:progress", - }); - }); - - worker.on("ready", () => { - this.logger.debug({ - message: `Worker ${queueName} is ready`, - service: SERVICE_NAME, - method: "createWorker:ready", - }); - }); - - worker.on("resumed", () => { - this.logger.debug({ - message: `Worker ${queueName} is resumed`, - service: SERVICE_NAME, - method: "createWorker:resumed", - }); - }); - - worker.on("stalled", () => { - this.logger.warn({ - message: `Worker ${queueName} is stalled`, - service: SERVICE_NAME, - method: "createWorker:stalled", - }); - }); - return worker; - } - - async isInMaintenanceWindow(monitorId) { - const maintenanceWindows = await this.db.getMaintenanceWindowsByMonitorId(monitorId); - // Check for active maintenance window: - const maintenanceWindowIsActive = maintenanceWindows.reduce((acc, window) => { - if (window.active) { - const start = new Date(window.start); - const end = new Date(window.end); - const now = new Date(); - const repeatInterval = window.repeat || 0; - - // If start is < now and end > now, we're in maintenance - if (start <= now && end >= now) return true; - - // If maintenance window was set in the past with a repeat, - // we need to advance start and end to see if we are in range - - while (start < now && repeatInterval !== 0) { - start.setTime(start.getTime() + repeatInterval); - end.setTime(end.getTime() + repeatInterval); - if (start <= now && end >= now) { - return true; - } - } - return false; - } - return acc; - }, false); - return maintenanceWindowIsActive; - } - - createJobHandler(q) { - return async (job) => { - try { - // Update the last job processed time for this queue - q.lastJobProcessedTime = Date.now(); - // Get all maintenance windows for this monitor - await job.updateProgress(0); - const monitorId = job.data._id; - const maintenanceWindowActive = await this.isInMaintenanceWindow(monitorId); - // If a maintenance window is active, we're done - - if (maintenanceWindowActive) { - await job.updateProgress(100); - this.logger.info({ - message: `Monitor ${monitorId} is in maintenance window`, - service: SERVICE_NAME, - method: "createWorker", - }); - return false; - } - - // Get the current status - await job.updateProgress(30); - const monitor = job.data; - const networkResponse = await this.networkService.getStatus(monitor); - - // If the network response is not found, we're done - if (!networkResponse) { - await job.updateProgress(100); - return false; - } - - // Handle status change - await job.updateProgress(60); - const { monitor: updatedMonitor, statusChanged, prevStatus } = await this.statusService.updateStatus(networkResponse); - // Handle notifications - await job.updateProgress(80); - this.notificationService - .handleNotifications({ - ...networkResponse, - monitor: updatedMonitor, - prevStatus, - statusChanged, - }) - .catch((error) => { - this.logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "createJobHandler", - details: `Error sending notifications for job ${job.id}: ${error.message}`, - stack: error.stack, - }); - }); - await job.updateProgress(100); - return true; - } catch (error) { - await job.updateProgress(100); - throw error; - } - }; - } - async flushRedis() { - try { - const connection = this.redisService.getNewConnection(); - const flushResult = await connection.flushall(); - return flushResult; - } catch (error) { - this.logger.warn({ - message: error.message, - service: SERVICE_NAME, - method: "flushRedis", - }); - return false; - } - } -} - -export default JobQueueHelper; diff --git a/server/src/service/infrastructure/PulseQueue/PulseQueue.js b/server/src/service/infrastructure/PulseQueue/PulseQueue.js deleted file mode 100644 index d822b371d..000000000 --- a/server/src/service/infrastructure/PulseQueue/PulseQueue.js +++ /dev/null @@ -1,213 +0,0 @@ -import { Pulse } from "@pulsecron/pulse"; - -const SERVICE_NAME = "JobQueue"; -class PulseQueue { - static SERVICE_NAME = SERVICE_NAME; - - constructor({ appSettings, db, pulseQueueHelper, logger }) { - this.db = db; - this.appSettings = appSettings; - this.pulseQueueHelper = pulseQueueHelper; - this.logger = logger; - } - - get serviceName() { - return PulseQueue.SERVICE_NAME; - } - - static async create({ appSettings, db, pulseQueueHelper, logger }) { - const instance = new PulseQueue({ appSettings, db, pulseQueueHelper, logger }); - await instance.init(); - return instance; - } - - // **************************************** - // Core methods - // **************************************** - init = async () => { - try { - const mongoConnectionString = this.appSettings.dbConnectionString || "mongodb://localhost:27017/uptime_db"; - this.pulse = new Pulse({ db: { address: mongoConnectionString } }); - await this.pulse.start(); - this.pulse.define("monitor-job", this.pulseQueueHelper.getMonitorJob(), {}); - - const monitors = await this.db.getAllMonitors(); - for (const monitor of monitors) { - await this.addJob(monitor._id, monitor); - } - return true; - } catch (error) { - this.logger.error({ - message: "Failed to initialize PulseQueue", - service: SERVICE_NAME, - method: "init", - details: error, - }); - return false; - } - }; - - addJob = async (monitorId, monitor) => { - this.logger.debug({ - message: `Adding job ${monitor?.url ?? "No URL"}`, - service: SERVICE_NAME, - method: "addJob", - }); - const intervalInSeconds = monitor.interval / 1000; - const job = this.pulse.create("monitor-job", { - monitor, - }); - job.unique({ "data.monitor._id": monitor._id }); - job.attrs.jobId = monitorId.toString(); - job.repeatEvery(`${intervalInSeconds} seconds`); - if (monitor.isActive === false) { - job.disable(); - } - await job.save(); - }; - - deleteJob = async (monitor) => { - this.logger.debug({ - message: `Deleting job ${monitor?.url ?? "No URL"}`, - service: SERVICE_NAME, - method: "deleteJob", - }); - await this.pulse.cancel({ - "data.monitor._id": monitor._id, - }); - }; - - pauseJob = async (monitor) => { - const result = await this.pulse.disable({ - "data.monitor._id": monitor._id, - }); - - if (result.length < 1) { - throw new Error("Failed to pause monitor"); - } - - this.logger.debug({ - message: `Paused monitor ${monitor._id}`, - service: SERVICE_NAME, - method: "pauseJob", - }); - }; - - resumeJob = async (monitor) => { - const result = await this.pulse.enable({ - "data.monitor._id": monitor._id, - }); - - if (result.length < 1) { - throw new Error("Failed to resume monitor"); - } - - this.logger.debug({ - message: `Resumed monitor ${monitor._id}`, - service: SERVICE_NAME, - method: "resumeJob", - }); - }; - - updateJob = async (monitor) => { - const jobs = await this.pulse.jobs({ - "data.monitor._id": monitor._id, - }); - - const job = jobs[0]; - if (!job) { - throw new Error("Job not found"); - } - - const intervalInSeconds = monitor.interval / 1000; - job.repeatEvery(`${intervalInSeconds} seconds`); - job.attrs.data.monitor = monitor; - await job.save(); - }; - - shutdown = async () => { - this.logger.info({ - message: "Shutting down JobQueue", - service: SERVICE_NAME, - method: "shutdown", - }); - await this.pulse.stop(); - }; - - // **************************************** - // Diagnostic methods - // **************************************** - - getMetrics = async () => { - const jobs = await this.pulse.jobs(); - const metrics = jobs.reduce( - (acc, job) => { - acc.totalRuns += job.attrs.runCount || 0; - acc.totalFailures += job.attrs.failCount || 0; - acc.jobs++; - if (job.attrs.failCount > 0 && job.attrs.failedAt >= job.attrs.lastFinishedAt) { - acc.failingJobs++; - } - if (job.attrs.lockedAt) { - acc.activeJobs++; - } - if (job.attrs.failCount > 0) { - acc.jobsWithFailures.push({ - monitorId: job.attrs.data.monitor._id, - monitorUrl: job.attrs.data.monitor.url, - monitorType: job.attrs.data.monitor.type, - failedAt: job.attrs.failedAt, - failCount: job.attrs.failCount, - failReason: job.attrs.failReason, - }); - } - return acc; - }, - { - jobs: 0, - activeJobs: 0, - failingJobs: 0, - jobsWithFailures: [], - totalRuns: 0, - totalFailures: 0, - } - ); - return metrics; - }; - - getJobs = async () => { - const jobs = await this.pulse.jobs(); - return jobs.map((job) => { - return { - monitorId: job.attrs.data.monitor._id, - monitorUrl: job.attrs.data.monitor.url, - monitorType: job.attrs.data.monitor.type, - active: !job.attrs.disabled, - lockedAt: job.attrs.lockedAt, - runCount: job.attrs.runCount || 0, - failCount: job.attrs.failCount || 0, - failReason: job.attrs.failReason, - lastRunAt: job.attrs.lastRunAt, - lastFinishedAt: job.attrs.lastFinishedAt, - lastRunTook: job.attrs.lockedAt ? null : job.attrs.lastFinishedAt - job.attrs.lastRunAt, - lastFailedAt: job.attrs.failedAt, - }; - }); - }; - - flushQueues = async () => { - const cancelRes = await this.pulse.cancel(); - await this.pulse.stop(); - const initRes = await this.init(); - return { - flushedJobs: cancelRes, - initSuccess: initRes, - }; - }; - - obliterate = async () => { - await this.flushQueues(); - }; -} - -export default PulseQueue; diff --git a/server/src/service/infrastructure/PulseQueue/PulseQueueHelper.js b/server/src/service/infrastructure/PulseQueue/PulseQueueHelper.js deleted file mode 100644 index a5ec34d01..000000000 --- a/server/src/service/infrastructure/PulseQueue/PulseQueueHelper.js +++ /dev/null @@ -1,104 +0,0 @@ -const SERVICE_NAME = "PulseQueueHelper"; - -class PulseQueueHelper { - static SERVICE_NAME = SERVICE_NAME; - - constructor({ db, logger, networkService, statusService, notificationService }) { - this.db = db; - this.logger = logger; - this.networkService = networkService; - this.statusService = statusService; - this.notificationService = notificationService; - } - - get serviceName() { - return PulseQueueHelper.SERVICE_NAME; - } - - getMonitorJob = () => { - return async (job) => { - try { - const monitor = job.attrs.data.monitor; - const monitorId = job.attrs.data.monitor._id; - if (!monitorId) { - throw new Error("No monitor id"); - } - - const maintenanceWindowActive = await this.isInMaintenanceWindow(monitorId); - if (maintenanceWindowActive) { - this.logger.info({ - message: `Monitor ${monitorId} is in maintenance window`, - service: SERVICE_NAME, - method: "getMonitorJob", - }); - return; - } - - const networkResponse = await this.networkService.getStatus(monitor); - - if (!networkResponse) { - throw new Error("No network response"); - } - - const { monitor: updatedMonitor, statusChanged, prevStatus } = await this.statusService.updateStatus(networkResponse); - - this.notificationService - .handleNotifications({ - ...networkResponse, - monitor: updatedMonitor, - prevStatus, - statusChanged, - }) - .catch((error) => { - this.logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "getMonitorJob", - details: `Error sending notifications for job ${job.id}: ${error.message}`, - stack: error.stack, - }); - }); - } catch (error) { - this.logger.warn({ - message: error.message, - service: error.service || SERVICE_NAME, - method: error.method || "getMonitorJob", - stack: error.stack, - }); - throw error; - } - }; - }; - - async isInMaintenanceWindow(monitorId) { - const maintenanceWindows = await this.db.getMaintenanceWindowsByMonitorId(monitorId); - // Check for active maintenance window: - const maintenanceWindowIsActive = maintenanceWindows.reduce((acc, window) => { - if (window.active) { - const start = new Date(window.start); - const end = new Date(window.end); - const now = new Date(); - const repeatInterval = window.repeat || 0; - - // If start is < now and end > now, we're in maintenance - if (start <= now && end >= now) return true; - - // If maintenance window was set in the past with a repeat, - // we need to advance start and end to see if we are in range - - while (start < now && repeatInterval !== 0) { - start.setTime(start.getTime() + repeatInterval); - end.setTime(end.getTime() + repeatInterval); - if (start <= now && end >= now) { - return true; - } - } - return false; - } - return acc; - }, false); - return maintenanceWindowIsActive; - } -} - -export default PulseQueueHelper; diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js index e9021bf97..60bcb0754 100644 --- a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js +++ b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js @@ -4,8 +4,8 @@ const SERVICE_NAME = "JobQueue"; class SuperSimpleQueue { static SERVICE_NAME = SERVICE_NAME; - constructor({ appSettings, db, logger, helper }) { - this.appSettings = appSettings; + constructor({ envSettings, db, logger, helper }) { + this.envSettings = envSettings; this.db = db; this.logger = logger; this.helper = helper; @@ -15,8 +15,8 @@ class SuperSimpleQueue { return SuperSimpleQueue.SERVICE_NAME; } - static async create({ appSettings, db, logger, helper }) { - const instance = new SuperSimpleQueue({ appSettings, db, logger, helper }); + static async create({ envSettings, db, logger, helper }) { + const instance = new SuperSimpleQueue({ envSettings, db, logger, helper }); await instance.init(); return instance; } @@ -28,12 +28,12 @@ class SuperSimpleQueue { // storeType: "redis", logLevel: "debug", debug: true, - dbUri: this.appSettings.dbConnectionString, + // dbUri: this.envSettings.dbConnectionString, }); this.scheduler.start(); this.scheduler.addTemplate("monitor-job", this.helper.getMonitorJob()); - const monitors = await this.db.getAllMonitors(); + const monitors = await this.db.monitorModule.getAllMonitors(); for (const monitor of monitors) { await this.addJob(monitor._id, monitor); } diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js index 639e5183b..ca2e5f642 100644 --- a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js +++ b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js @@ -67,7 +67,7 @@ class SuperSimpleQueueHelper { }; async isInMaintenanceWindow(monitorId) { - const maintenanceWindows = await this.db.getMaintenanceWindowsByMonitorId(monitorId); + const maintenanceWindows = await this.db.maintenanceWindowModule.getMaintenanceWindowsByMonitorId(monitorId); // Check for active maintenance window: const maintenanceWindowIsActive = maintenanceWindows.reduce((acc, window) => { if (window.active) { diff --git a/server/src/service/infrastructure/bufferService.js b/server/src/service/infrastructure/bufferService.js index 439b251f6..46da6bef3 100755 --- a/server/src/service/infrastructure/bufferService.js +++ b/server/src/service/infrastructure/bufferService.js @@ -1,5 +1,4 @@ const SERVICE_NAME = "BufferService"; -const BUFFER_TIMEOUT = process.env.NODE_ENV === "development" ? 5000 : 1000 * 60 * 1; // 1 minute const TYPE_MAP = { http: "checks", ping: "checks", @@ -11,7 +10,8 @@ const TYPE_MAP = { class BufferService { static SERVICE_NAME = SERVICE_NAME; - constructor({ db, logger }) { + constructor({ db, logger, envSettings }) { + this.BUFFER_TIMEOUT = envSettings.nodeEnv === "development" ? 5000 : 1000 * 60 * 1; // 1 minute this.db = db; this.logger = logger; this.SERVICE_NAME = SERVICE_NAME; @@ -21,14 +21,14 @@ class BufferService { hardwareChecks: [], }; this.OPERATION_MAP = { - checks: this.db.createChecks, - pagespeedChecks: this.db.createPageSpeedChecks, - hardwareChecks: this.db.createHardwareChecks, + checks: this.db.checkModule.createChecks, + pagespeedChecks: this.db.pageSpeedCheckModule.createPageSpeedChecks, + hardwareChecks: this.db.hardwareCheckModule.createHardwareChecks, }; this.scheduleNextFlush(); this.logger.info({ - message: `Buffer service initialized, flushing every ${BUFFER_TIMEOUT / 1000}s`, + message: `Buffer service initialized, flushing every ${this.BUFFER_TIMEOUT / 1000}s`, service: this.SERVICE_NAME, method: "constructor", }); @@ -66,7 +66,7 @@ class BufferService { // Schedule the next flush only after the current one completes this.scheduleNextFlush(); } - }, BUFFER_TIMEOUT); + }, this.BUFFER_TIMEOUT); } async flushBuffers() { let items = 0; diff --git a/server/src/service/infrastructure/notificationService.js b/server/src/service/infrastructure/notificationService.js index 6af4fd20a..8c34519f3 100644 --- a/server/src/service/infrastructure/notificationService.js +++ b/server/src/service/infrastructure/notificationService.js @@ -81,7 +81,7 @@ class NotificationService { } async notifyAll({ notificationIDs, subject, html, content }) { - const notifications = await this.db.getNotificationsByIds(notificationIDs); + const notifications = await this.db.notificationModule.getNotificationsByIds(notificationIDs); // Map each notification to a test promise const promises = notifications.map(async (notification) => { diff --git a/server/src/service/infrastructure/statusService.js b/server/src/service/infrastructure/statusService.js index ebe0944d5..72916a0ce 100755 --- a/server/src/service/infrastructure/statusService.js +++ b/server/src/service/infrastructure/statusService.js @@ -122,7 +122,7 @@ class StatusService { this.insertCheck(networkResponse); try { const { monitorId, status, code } = networkResponse; - const monitor = await this.db.getMonitorById(monitorId); + const monitor = await this.db.monitorModule.getMonitorById(monitorId); // Update running stats this.updateRunningStats({ monitor, networkResponse }); diff --git a/server/src/service/system/serviceRegistry.js b/server/src/service/system/serviceRegistry.js index 6aa3f94ef..4e3e28464 100755 --- a/server/src/service/system/serviceRegistry.js +++ b/server/src/service/system/serviceRegistry.js @@ -1,17 +1,20 @@ const SERVICE_NAME = "ServiceRegistry"; -import logger from "../../utils/logger.js"; + class ServiceRegistry { static SERVICE_NAME = SERVICE_NAME; - constructor() { + + constructor({ logger }) { this.services = {}; + this.logger = logger; } get serviceName() { return ServiceRegistry.SERVICE_NAME; } + // Instance methods register(name, service) { - logger.info({ + this.logger.info({ message: `Registering service ${name}`, service: SERVICE_NAME, method: "register", @@ -21,7 +24,7 @@ class ServiceRegistry { get(name) { if (!this.services[name]) { - logger.error({ + this.logger.error({ message: `Service ${name} is not registered`, service: SERVICE_NAME, method: "get", @@ -34,6 +37,27 @@ class ServiceRegistry { listServices() { return Object.keys(this.services); } + + static get(name) { + if (!ServiceRegistry.instance) { + throw new Error("ServiceRegistry not initialized"); + } + return ServiceRegistry.instance.get(name); + } + + static register(name, service) { + if (!ServiceRegistry.instance) { + throw new Error("ServiceRegistry not initialized"); + } + return ServiceRegistry.instance.register(name, service); + } + + static listServices() { + if (!ServiceRegistry.instance) { + throw new Error("ServiceRegistry not initialized"); + } + return ServiceRegistry.instance.listServices(); + } } -export default new ServiceRegistry(); +export default ServiceRegistry; diff --git a/server/src/service/system/settingsService.js b/server/src/service/system/settingsService.js index 4f5e18abb..70d8d192c 100755 --- a/server/src/service/system/settingsService.js +++ b/server/src/service/system/settingsService.js @@ -1,22 +1,14 @@ const SERVICE_NAME = "SettingsService"; const envConfig = { - nodeEnv: process.env.NODE_ENV, - logLevel: process.env.LOG_LEVEL, - systemEmailHost: process.env.SYSTEM_EMAIL_HOST, - systemEmailPort: process.env.SYSTEM_EMAIL_PORT, - systemEmailUser: process.env.SYSTEM_EMAIL_USER, - systemEmailAddress: process.env.SYSTEM_EMAIL_ADDRESS, - systemEmailPassword: process.env.SYSTEM_EMAIL_PASSWORD, jwtSecret: process.env.JWT_SECRET, jwtTTL: process.env.TOKEN_TTL, + systemEmailHost: process.env.SYSTEM_EMAIL_HOST, + nodeEnv: process.env.NODE_ENV, + logLevel: process.env.LOG_LEVEL, clientHost: process.env.CLIENT_HOST, dbConnectionString: process.env.DB_CONNECTION_STRING, - redisUrl: process.env.REDIS_URL, - callbackUrl: process.env.CALLBACK_URL, port: process.env.PORT, - pagespeedApiKey: process.env.PAGESPEED_API_KEY, - uprockApiKey: process.env.UPROCK_API_KEY, }; /** * SettingsService @@ -46,13 +38,7 @@ class SettingsService { loadSettings() { return this.settings; } - /** - * Reload settings by calling loadSettings. - * @returns {Promise} The reloaded settings. - */ - reloadSettings() { - return this.loadSettings(); - } + /** * Get the current settings. * @returns {Object} The current settings. diff --git a/server/src/shutdown.js b/server/src/shutdown.js index b9b2b53c5..27eb0f21d 100644 --- a/server/src/shutdown.js +++ b/server/src/shutdown.js @@ -1,4 +1,4 @@ -import logger from "./utils/logger.js"; +import { logger } from "./utils/logger.js"; export const initShutdownListener = (server, services) => { const SERVICE_NAME = "Server"; diff --git a/server/src/utils/logger.js b/server/src/utils/logger.js index a94ad6be2..0917862fa 100755 --- a/server/src/utils/logger.js +++ b/server/src/utils/logger.js @@ -6,7 +6,8 @@ const SERVICE_NAME = "Logger"; class Logger { static SERVICE_NAME = SERVICE_NAME; - constructor() { + constructor({ envSettings }) { + this.envSettings = envSettings; this.logCache = []; this.maxCacheSize = 1000; const consoleFormat = format.printf(({ level, message, service, method, details, timestamp, stack }) => { @@ -45,7 +46,7 @@ class Logger { return msg; }); - const logLevel = process.env.LOG_LEVEL || "info"; + const logLevel = this.envSettings.logLevel || "info"; this.logger = createLogger({ level: logLevel, @@ -145,7 +146,8 @@ class Logger { } } -const logger = new Logger(); -export { Logger }; +export default Logger; -export default logger; +// Legacy logger +const logger = new Logger({ envSettings: { logLevel: "debug" } }); +export { logger }; diff --git a/server/src/validation/joi.js b/server/src/validation/joi.js index 744b0ec74..f2c15205b 100755 --- a/server/src/validation/joi.js +++ b/server/src/validation/joi.js @@ -416,6 +416,15 @@ const updateAppSettingsBodyValidation = joi.object({ systemEmailIgnoreTLS: joi.boolean(), systemEmailRequireTLS: joi.boolean(), systemEmailRejectUnauthorized: joi.boolean(), + + globalThresholds: joi + .object({ + cpu: joi.number().min(1).max(100).allow("").optional(), + memory: joi.number().min(1).max(100).allow("").optional(), + disk: joi.number().min(1).max(100).allow("").optional(), + temperature: joi.number().min(1).max(150).allow("").optional(), + }) + .optional(), }); //****************************************