Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f971cfb02 | ||
|
|
32c811ec5a | ||
|
|
4febc56c30 | ||
|
|
f22ee4a68b | ||
|
|
87bec0399f | ||
|
|
1f29255dd1 | ||
|
|
4a11638182 | ||
|
|
10743372f8 | ||
|
|
d4b11bbb76 | ||
|
|
ced2417c98 | ||
|
|
27415aeca6 | ||
|
|
9bd2e336a6 | ||
|
|
d5778ec173 | ||
|
|
74e80f086a | ||
|
|
b1c6191fa1 | ||
|
|
8e0245c844 | ||
|
|
613fbf8c39 | ||
|
|
1788cda5c2 | ||
|
|
155adf549e | ||
|
|
5463542532 | ||
|
|
f5a25bc0e5 | ||
|
|
993339d0d1 | ||
|
|
bc4fe897cf | ||
|
|
c773bdaabd | ||
|
|
9f9a2be1d8 | ||
|
|
1e4b0c2f20 | ||
|
|
56025f6d56 | ||
|
|
5188b90f32 | ||
|
|
7178f537a8 | ||
|
|
45ac589975 | ||
|
|
9925e10ffc | ||
|
|
e3fde8d564 | ||
|
|
8aee01e008 | ||
|
|
cd203ff067 | ||
|
|
254cb6d80f | ||
|
|
9b3ad54d0a | ||
|
|
4ae3bb5780 | ||
|
|
b8aa1ef765 | ||
|
|
5e5daef49a | ||
|
|
88c478c533 | ||
|
|
1ecec115e4 | ||
|
|
8ff597eedb | ||
|
|
69f74488e5 | ||
|
|
ded9a27f42 | ||
|
|
43284ae625 | ||
|
|
f7b1ef9741 | ||
|
|
a6d445818f | ||
|
|
122a84b9e2 | ||
|
|
9716e64c93 | ||
|
|
eb8cf1b6da | ||
|
|
c633a9ad8d | ||
|
|
923eda1307 | ||
|
|
997342a31c |
No files matched your search
@@ -1,7 +1,6 @@
|
|||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Background from "../../assets/Images/background-grid.svg?react";
|
import Background from "../../assets/Images/background-grid.svg?react";
|
||||||
import SkeletonDark from "../../assets/Images/create-placeholder-dark.svg?react";
|
|
||||||
import OutputAnimation from "../../assets/Animations/output.gif";
|
import OutputAnimation from "../../assets/Animations/output.gif";
|
||||||
import DarkmodeOutput from "../../assets/Animations/darkmodeOutput.gif";
|
import DarkmodeOutput from "../../assets/Animations/darkmodeOutput.gif";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
@@ -13,7 +12,7 @@ const FallbackBackground = () => {
|
|||||||
<Box
|
<Box
|
||||||
component="img"
|
component="img"
|
||||||
src={mode === "light" ? OutputAnimation : DarkmodeOutput}
|
src={mode === "light" ? OutputAnimation : DarkmodeOutput}
|
||||||
Background="transparent"
|
background="transparent"
|
||||||
alt="Loading animation"
|
alt="Loading animation"
|
||||||
sx={{
|
sx={{
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ const StarPrompt = ({ repoUrl = "https://github.com/bluewave-labs/checkmate" })
|
|||||||
<Typography
|
<Typography
|
||||||
variant="body1"
|
variant="body1"
|
||||||
sx={{
|
sx={{
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.primary.contrastTextTertiary,
|
||||||
fontSize: "0.938rem",
|
fontSize: "0.938rem",
|
||||||
lineHeight: 1.5,
|
lineHeight: 1.5,
|
||||||
mb: 1,
|
mb: 1,
|
||||||
|
|||||||
@@ -369,6 +369,8 @@ const useUpdateMonitor = () => {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const updatedFields = {
|
const updatedFields = {
|
||||||
name: monitor.name,
|
name: monitor.name,
|
||||||
|
statusWindowSize: monitor.statusWindowSize,
|
||||||
|
statusWindowThreshold: monitor.statusWindowThreshold,
|
||||||
description: monitor.description,
|
description: monitor.description,
|
||||||
interval: monitor.interval,
|
interval: monitor.interval,
|
||||||
notifications: monitor.notifications,
|
notifications: monitor.notifications,
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import ConfigBox from "../../../../Components/ConfigBox";
|
||||||
|
import { Box, Stack, Typography } from "@mui/material";
|
||||||
|
import { CustomThreshold } from "../Components/CustomThreshold";
|
||||||
|
import { capitalizeFirstLetter } from "../../../../Utils/stringUtils";
|
||||||
|
import { useTheme } from "@emotion/react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
const CustomAlertsSection = ({
|
||||||
|
errors,
|
||||||
|
onChange,
|
||||||
|
infrastructureMonitor,
|
||||||
|
handleCheckboxChange,
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const METRICS = ["cpu", "memory", "disk", "temperature"];
|
||||||
|
const METRIC_PREFIX = "usage_";
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<ConfigBox>
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("infrastructureCustomizeAlerts")}
|
||||||
|
</Typography>
|
||||||
|
<Typography component="p">
|
||||||
|
{t("infrastructureAlertNotificationDescription")}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Stack gap={theme.spacing(6)}>
|
||||||
|
{METRICS.map((metric) => {
|
||||||
|
return (
|
||||||
|
<CustomThreshold
|
||||||
|
key={metric}
|
||||||
|
infrastructureMonitor={infrastructureMonitor}
|
||||||
|
errors={errors}
|
||||||
|
checkboxId={metric}
|
||||||
|
checkboxName={metric}
|
||||||
|
checkboxLabel={
|
||||||
|
metric !== "cpu" ? capitalizeFirstLetter(metric) : metric.toUpperCase()
|
||||||
|
}
|
||||||
|
onCheckboxChange={handleCheckboxChange}
|
||||||
|
isChecked={infrastructureMonitor[metric]}
|
||||||
|
fieldId={METRIC_PREFIX + metric}
|
||||||
|
fieldName={METRIC_PREFIX + metric}
|
||||||
|
fieldValue={String(infrastructureMonitor[METRIC_PREFIX + metric])}
|
||||||
|
onFieldChange={onChange}
|
||||||
|
alertUnit={metric == "temperature" ? "°C" : "%"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{hasAlertError(errors) && (
|
||||||
|
<Typography
|
||||||
|
component="span"
|
||||||
|
className="input-error"
|
||||||
|
color={theme.palette.error.main}
|
||||||
|
mt={theme.spacing(2)}
|
||||||
|
sx={{
|
||||||
|
opacity: 0.8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{getAlertError(errors)}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</ConfigBox>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
CustomAlertsSection.propTypes = {
|
||||||
|
errors: PropTypes.object.isRequired,
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
infrastructureMonitor: PropTypes.object.isRequired,
|
||||||
|
handleCheckboxChange: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustomAlertsSection;
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Box, Button } from "@mui/material";
|
||||||
|
import { useTheme } from "@emotion/react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline";
|
||||||
|
import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded";
|
||||||
|
import Dialog from "../../../../Components/Dialog";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
|
const MonitorActionButtons = ({ monitor, isBusy, handlePause, handleRemove }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
alignSelf="flex-end"
|
||||||
|
ml="auto"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={handlePause}
|
||||||
|
loading={isBusy}
|
||||||
|
variant="contained"
|
||||||
|
color="secondary"
|
||||||
|
sx={{
|
||||||
|
pl: theme.spacing(4),
|
||||||
|
pr: theme.spacing(6),
|
||||||
|
"& svg": {
|
||||||
|
mr: theme.spacing(2),
|
||||||
|
"& path": {
|
||||||
|
stroke: theme.palette.primary.contrastTextTertiary,
|
||||||
|
strokeWidth: 0.1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{monitor?.isActive ? (
|
||||||
|
<>
|
||||||
|
<PauseCircleOutlineIcon />
|
||||||
|
{t("pause")}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<PlayCircleOutlineRoundedIcon />
|
||||||
|
{t("resume")}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
loading={isBusy}
|
||||||
|
variant="contained"
|
||||||
|
color="error"
|
||||||
|
onClick={() => setIsOpen(true)}
|
||||||
|
sx={{ ml: theme.spacing(6) }}
|
||||||
|
>
|
||||||
|
{t("remove")}
|
||||||
|
</Button>
|
||||||
|
<Dialog
|
||||||
|
open={isOpen}
|
||||||
|
theme={theme}
|
||||||
|
title={t("deleteDialogTitle")}
|
||||||
|
description={t("deleteDialogDescription")}
|
||||||
|
onCancel={() => setIsOpen(false)}
|
||||||
|
confirmationButtonLabel={t("delete")}
|
||||||
|
onConfirm={handleRemove}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
MonitorActionButtons.propTypes = {
|
||||||
|
monitor: PropTypes.object.isRequired,
|
||||||
|
isBusy: PropTypes.bool.isRequired,
|
||||||
|
handlePause: PropTypes.func.isRequired,
|
||||||
|
handleRemove: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MonitorActionButtons;
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { Box, Stack, Tooltip, Typography } from "@mui/material";
|
||||||
|
import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils";
|
||||||
|
import { useTheme } from "@emotion/react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import PulseDot from "../../../../Components/Animated/PulseDot";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
const MonitorStatusHeader = ({ monitor, infrastructureMonitor }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { statusColor, pagespeedStatusMsg, determineState } = useMonitorUtils();
|
||||||
|
return (
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
alignItems="center"
|
||||||
|
height="fit-content"
|
||||||
|
gap={theme.spacing(2)}
|
||||||
|
>
|
||||||
|
<Tooltip
|
||||||
|
title={pagespeedStatusMsg[determineState(monitor)]}
|
||||||
|
disableInteractive
|
||||||
|
slotProps={{
|
||||||
|
popper: {
|
||||||
|
modifiers: [
|
||||||
|
{
|
||||||
|
name: "offset",
|
||||||
|
options: { offset: [0, -8] },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<PulseDot color={statusColor[determineState(monitor)]} />
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="monitorUrl"
|
||||||
|
>
|
||||||
|
{infrastructureMonitor.url?.replace(/^https?:\/\//, "") || "..."}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
position="relative"
|
||||||
|
variant="body2"
|
||||||
|
ml={theme.spacing(6)}
|
||||||
|
mt={theme.spacing(1)}
|
||||||
|
sx={{
|
||||||
|
"&:before": {
|
||||||
|
position: "absolute",
|
||||||
|
content: `""`,
|
||||||
|
width: theme.spacing(2),
|
||||||
|
height: theme.spacing(2),
|
||||||
|
borderRadius: "50%",
|
||||||
|
backgroundColor: theme.palette.primary.contrastTextTertiary,
|
||||||
|
opacity: 0.8,
|
||||||
|
left: theme.spacing(-5),
|
||||||
|
top: "50%",
|
||||||
|
transform: "translateY(-50%)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("editing")}
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
MonitorStatusHeader.propTypes = {
|
||||||
|
monitor: PropTypes.object.isRequired,
|
||||||
|
infrastructureMonitor: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MonitorStatusHeader;
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
const useInfrastructureMonitorForm = () => {
|
||||||
|
const [infrastructureMonitor, setInfrastructureMonitor] = useState({
|
||||||
|
url: "",
|
||||||
|
name: "",
|
||||||
|
notifications: [],
|
||||||
|
notify_email: false,
|
||||||
|
interval: 0.25,
|
||||||
|
cpu: false,
|
||||||
|
usage_cpu: "",
|
||||||
|
memory: false,
|
||||||
|
usage_memory: "",
|
||||||
|
disk: false,
|
||||||
|
usage_disk: "",
|
||||||
|
temperature: false,
|
||||||
|
usage_temperature: "",
|
||||||
|
secret: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const onChangeForm = (name, value) => {
|
||||||
|
setInfrastructureMonitor({
|
||||||
|
...infrastructureMonitor,
|
||||||
|
[name]: value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const handleCheckboxChange = (event) => {
|
||||||
|
setInfrastructureMonitor({
|
||||||
|
...infrastructureMonitor,
|
||||||
|
[event.target.name]: event.target.checked,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const initializeInfrastructureMonitorForCreate = (globalSettings) => {
|
||||||
|
const gt = globalSettings?.data?.settings?.globalThresholds || {};
|
||||||
|
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: "",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const initializeInfrastructureMonitorForUpdate = (monitor) => {
|
||||||
|
const MS_PER_MINUTE = 60000;
|
||||||
|
const { thresholds = {} } = monitor;
|
||||||
|
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 || "",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
infrastructureMonitor,
|
||||||
|
setInfrastructureMonitor,
|
||||||
|
onChangeForm,
|
||||||
|
handleCheckboxChange,
|
||||||
|
initializeInfrastructureMonitorForCreate,
|
||||||
|
initializeInfrastructureMonitorForUpdate,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useInfrastructureMonitorForm;
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import { useCreateMonitor, useUpdateMonitor } from "../../../../Hooks/monitorHooks";
|
||||||
|
const useInfrastructureSubmit = () => {
|
||||||
|
const [createMonitor, isCreating] = useCreateMonitor();
|
||||||
|
const [updateMonitor, isUpdating] = useUpdateMonitor();
|
||||||
|
const buildForm = (infrastructureMonitor, https) => {
|
||||||
|
const MS_PER_MINUTE = 60000;
|
||||||
|
|
||||||
|
let form = {
|
||||||
|
url: `http${https ? "s" : ""}://` + infrastructureMonitor.url,
|
||||||
|
name:
|
||||||
|
infrastructureMonitor.name === ""
|
||||||
|
? infrastructureMonitor.url
|
||||||
|
: infrastructureMonitor.name,
|
||||||
|
interval: infrastructureMonitor.interval * MS_PER_MINUTE,
|
||||||
|
cpu: infrastructureMonitor.cpu,
|
||||||
|
...(infrastructureMonitor.cpu
|
||||||
|
? { usage_cpu: infrastructureMonitor.usage_cpu }
|
||||||
|
: {}),
|
||||||
|
memory: infrastructureMonitor.memory,
|
||||||
|
...(infrastructureMonitor.memory
|
||||||
|
? { usage_memory: infrastructureMonitor.usage_memory }
|
||||||
|
: {}),
|
||||||
|
disk: infrastructureMonitor.disk,
|
||||||
|
...(infrastructureMonitor.disk
|
||||||
|
? { usage_disk: infrastructureMonitor.usage_disk }
|
||||||
|
: {}),
|
||||||
|
temperature: infrastructureMonitor.temperature,
|
||||||
|
...(infrastructureMonitor.temperature
|
||||||
|
? { usage_temperature: infrastructureMonitor.usage_temperature }
|
||||||
|
: {}),
|
||||||
|
secret: infrastructureMonitor.secret,
|
||||||
|
};
|
||||||
|
return form;
|
||||||
|
};
|
||||||
|
const submitInfrastructureForm = async (
|
||||||
|
infrastructureMonitor,
|
||||||
|
form,
|
||||||
|
isCreate,
|
||||||
|
monitorId
|
||||||
|
) => {
|
||||||
|
const {
|
||||||
|
cpu,
|
||||||
|
usage_cpu,
|
||||||
|
memory,
|
||||||
|
usage_memory,
|
||||||
|
disk,
|
||||||
|
usage_disk,
|
||||||
|
temperature,
|
||||||
|
usage_temperature,
|
||||||
|
...rest
|
||||||
|
} = form;
|
||||||
|
|
||||||
|
const thresholds = {
|
||||||
|
...(cpu ? { usage_cpu: usage_cpu / 100 } : {}),
|
||||||
|
...(memory ? { usage_memory: usage_memory / 100 } : {}),
|
||||||
|
...(disk ? { usage_disk: usage_disk / 100 } : {}),
|
||||||
|
...(temperature ? { usage_temperature: usage_temperature / 100 } : {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const finalForm = {
|
||||||
|
...(isCreate ? {} : { _id: monitorId }),
|
||||||
|
...rest,
|
||||||
|
description: form.name,
|
||||||
|
type: "hardware",
|
||||||
|
notifications: infrastructureMonitor.notifications,
|
||||||
|
thresholds,
|
||||||
|
};
|
||||||
|
// Handle create or update
|
||||||
|
isCreate
|
||||||
|
? await createMonitor({ monitor: finalForm, redirect: "/infrastructure" })
|
||||||
|
: await updateMonitor({ monitor: finalForm, redirect: "/infrastructure" });
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
buildForm,
|
||||||
|
submitInfrastructureForm,
|
||||||
|
isCreating,
|
||||||
|
isUpdating,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export default useInfrastructureSubmit;
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { infrastructureMonitorValidation } from "../../../../Validation/validation";
|
||||||
|
import { createToast } from "../../../../Utils/toastUtils";
|
||||||
|
const useValidateInfrastructureForm = () => {
|
||||||
|
const [errors, setErrors] = useState({});
|
||||||
|
|
||||||
|
const validateField = (name, value) => {
|
||||||
|
const { error } = infrastructureMonitorValidation.validate(
|
||||||
|
{ [name]: value },
|
||||||
|
{ abortEarly: false }
|
||||||
|
);
|
||||||
|
setErrors((prev) => ({
|
||||||
|
...prev,
|
||||||
|
...(error ? { [name]: error.details[0].message } : { [name]: undefined }),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateForm = (form) => {
|
||||||
|
const { error } = infrastructureMonitorValidation.validate(form, {
|
||||||
|
abortEarly: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
const newErrors = {};
|
||||||
|
error.details.forEach((err) => {
|
||||||
|
newErrors[err.path[0]] = err.message;
|
||||||
|
});
|
||||||
|
console.log(newErrors);
|
||||||
|
setErrors(newErrors);
|
||||||
|
createToast({ body: "Please check the form for errors." });
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
return { errors, validateField, validateForm };
|
||||||
|
};
|
||||||
|
export default useValidateInfrastructureForm;
|
||||||
@@ -1,40 +1,33 @@
|
|||||||
//Components
|
//Components
|
||||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||||
import ConfigBox from "../../../Components/ConfigBox";
|
import ConfigBox from "../../../Components/ConfigBox";
|
||||||
import Dialog from "../../../Components/Dialog";
|
|
||||||
import FieldWrapper from "../../../Components/Inputs/FieldWrapper";
|
import FieldWrapper from "../../../Components/Inputs/FieldWrapper";
|
||||||
import Link from "../../../Components/Link";
|
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 Select from "../../../Components/Inputs/Select";
|
||||||
import TextInput from "../../../Components/Inputs/TextInput";
|
import TextInput from "../../../Components/Inputs/TextInput";
|
||||||
import { Box, Stack, Tooltip, Typography, Button, ButtonGroup } from "@mui/material";
|
import { Box, Stack, Typography, Button, ButtonGroup } from "@mui/material";
|
||||||
import { CustomThreshold } from "./Components/CustomThreshold";
|
|
||||||
import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments";
|
import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments";
|
||||||
import { createToast } from "../../../Utils/toastUtils";
|
import MonitorStatusHeader from "./Components/MonitorStatusHeader";
|
||||||
|
import MonitorActionButtons from "./Components/MonitorActionButtons";
|
||||||
|
import CustomAlertsSection from "./Components/CustomAlertsSection";
|
||||||
// Utils
|
// Utils
|
||||||
import NotificationsConfig from "../../../Components/NotificationConfig";
|
import NotificationsConfig from "../../../Components/NotificationConfig";
|
||||||
import { capitalizeFirstLetter } from "../../../Utils/stringUtils";
|
|
||||||
import { infrastructureMonitorValidation } from "../../../Validation/validation";
|
|
||||||
import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
|
import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
|
||||||
import { useMonitorUtils } from "../../../Hooks/useMonitorUtils";
|
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
useCreateMonitor,
|
|
||||||
useDeleteMonitor,
|
useDeleteMonitor,
|
||||||
useFetchGlobalSettings,
|
useFetchGlobalSettings,
|
||||||
useFetchHardwareMonitorById,
|
useFetchHardwareMonitorById,
|
||||||
usePauseMonitor,
|
usePauseMonitor,
|
||||||
useUpdateMonitor,
|
|
||||||
} from "../../../Hooks/monitorHooks";
|
} from "../../../Hooks/monitorHooks";
|
||||||
|
import useInfrastructureMonitorForm from "./hooks/useInfrastructureMonitorForm";
|
||||||
|
import useValidateInfrastructureForm from "./hooks/useValidateInfrastructureForm";
|
||||||
|
import useInfrastructureSubmit from "./hooks/useInfrastructureSubmit";
|
||||||
|
|
||||||
const CreateInfrastructureMonitor = () => {
|
const CreateInfrastructureMonitor = () => {
|
||||||
const { user } = useSelector((state) => state.auth);
|
|
||||||
const { monitorId } = useParams();
|
const { monitorId } = useParams();
|
||||||
const isCreate = typeof monitorId === "undefined";
|
const isCreate = typeof monitorId === "undefined";
|
||||||
|
|
||||||
@@ -42,39 +35,29 @@ const CreateInfrastructureMonitor = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// State
|
// State
|
||||||
const [errors, setErrors] = useState({});
|
|
||||||
const [https, setHttps] = useState(false);
|
const [https, setHttps] = useState(false);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const [updateTrigger, setUpdateTrigger] = useState(false);
|
const [updateTrigger, setUpdateTrigger] = useState(false);
|
||||||
const [infrastructureMonitor, setInfrastructureMonitor] = useState({
|
|
||||||
url: "",
|
|
||||||
name: "",
|
|
||||||
notifications: [],
|
|
||||||
notify_email: false,
|
|
||||||
interval: 0.25,
|
|
||||||
cpu: false,
|
|
||||||
usage_cpu: "",
|
|
||||||
memory: false,
|
|
||||||
usage_memory: "",
|
|
||||||
disk: false,
|
|
||||||
usage_disk: "",
|
|
||||||
temperature: false,
|
|
||||||
usage_temperature: "",
|
|
||||||
secret: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fetch monitor details if editing
|
// Fetch monitor details if editing
|
||||||
const { statusColor, pagespeedStatusMsg, determineState } = useMonitorUtils();
|
|
||||||
const [monitor, isLoading] = useFetchHardwareMonitorById({
|
const [monitor, isLoading] = useFetchHardwareMonitorById({
|
||||||
monitorId,
|
monitorId,
|
||||||
updateTrigger,
|
updateTrigger,
|
||||||
});
|
});
|
||||||
const [createMonitor, isCreating] = useCreateMonitor();
|
|
||||||
const [deleteMonitor, isDeleting] = useDeleteMonitor();
|
const [deleteMonitor, isDeleting] = useDeleteMonitor();
|
||||||
const [globalSettings, globalSettingsLoading] = useFetchGlobalSettings();
|
const [globalSettings, globalSettingsLoading] = useFetchGlobalSettings();
|
||||||
const [notifications, notificationsAreLoading] = useGetNotificationsByTeamId();
|
const [notifications, notificationsAreLoading] = useGetNotificationsByTeamId();
|
||||||
const [pauseMonitor, isPausing] = usePauseMonitor();
|
const [pauseMonitor, isPausing] = usePauseMonitor();
|
||||||
const [updateMonitor, isUpdating] = useUpdateMonitor();
|
const {
|
||||||
|
infrastructureMonitor,
|
||||||
|
setInfrastructureMonitor,
|
||||||
|
onChangeForm,
|
||||||
|
handleCheckboxChange,
|
||||||
|
initializeInfrastructureMonitorForCreate,
|
||||||
|
initializeInfrastructureMonitorForUpdate,
|
||||||
|
} = useInfrastructureMonitorForm();
|
||||||
|
const { errors, validateField, validateForm } = useValidateInfrastructureForm();
|
||||||
|
const { buildForm, submitInfrastructureForm, isCreating, isUpdating } =
|
||||||
|
useInfrastructureSubmit();
|
||||||
|
|
||||||
const FREQUENCIES = [
|
const FREQUENCIES = [
|
||||||
{ _id: 0.25, name: t("time.fifteenSeconds") },
|
{ _id: 0.25, name: t("time.fifteenSeconds") },
|
||||||
@@ -93,157 +76,27 @@ const CreateInfrastructureMonitor = () => {
|
|||||||
{ name: "Configure", path: `/infrastructure/configure/${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
|
// Populate form fields if editing
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isCreate) {
|
if (isCreate) {
|
||||||
if (globalSettingsLoading) return;
|
if (globalSettingsLoading) return;
|
||||||
|
|
||||||
const gt = globalSettings?.data?.settings?.globalThresholds || {};
|
|
||||||
|
|
||||||
setHttps(false);
|
setHttps(false);
|
||||||
|
initializeInfrastructureMonitorForCreate(globalSettings);
|
||||||
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) {
|
} else if (monitor) {
|
||||||
const { thresholds = {} } = monitor;
|
|
||||||
|
|
||||||
setHttps(monitor.url.startsWith("https"));
|
setHttps(monitor.url.startsWith("https"));
|
||||||
|
initializeInfrastructureMonitorForUpdate(monitor);
|
||||||
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]);
|
}, [isCreate, monitor, globalSettings, globalSettingsLoading]);
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
const onSubmit = async (event) => {
|
const onSubmit = async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
const form = buildForm(infrastructureMonitor, https);
|
||||||
// Build the form
|
const error = validateForm(form);
|
||||||
let form = {
|
|
||||||
url: `http${https ? "s" : ""}://` + infrastructureMonitor.url,
|
|
||||||
name:
|
|
||||||
infrastructureMonitor.name === ""
|
|
||||||
? infrastructureMonitor.url
|
|
||||||
: infrastructureMonitor.name,
|
|
||||||
interval: infrastructureMonitor.interval * MS_PER_MINUTE,
|
|
||||||
cpu: infrastructureMonitor.cpu,
|
|
||||||
...(infrastructureMonitor.cpu
|
|
||||||
? { usage_cpu: infrastructureMonitor.usage_cpu }
|
|
||||||
: {}),
|
|
||||||
memory: infrastructureMonitor.memory,
|
|
||||||
...(infrastructureMonitor.memory
|
|
||||||
? { usage_memory: infrastructureMonitor.usage_memory }
|
|
||||||
: {}),
|
|
||||||
disk: infrastructureMonitor.disk,
|
|
||||||
...(infrastructureMonitor.disk
|
|
||||||
? { usage_disk: infrastructureMonitor.usage_disk }
|
|
||||||
: {}),
|
|
||||||
temperature: infrastructureMonitor.temperature,
|
|
||||||
...(infrastructureMonitor.temperature
|
|
||||||
? { usage_temperature: infrastructureMonitor.usage_temperature }
|
|
||||||
: {}),
|
|
||||||
secret: infrastructureMonitor.secret,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { error } = infrastructureMonitorValidation.validate(form, {
|
|
||||||
abortEarly: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
const newErrors = {};
|
|
||||||
error.details.forEach((err) => {
|
|
||||||
newErrors[err.path[0]] = err.message;
|
|
||||||
});
|
|
||||||
console.log(newErrors);
|
|
||||||
setErrors(newErrors);
|
|
||||||
createToast({ body: "Please check the form for errors." });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
submitInfrastructureForm(infrastructureMonitor, form, isCreate, monitorId);
|
||||||
// Build the thresholds for the form
|
|
||||||
const {
|
|
||||||
cpu,
|
|
||||||
usage_cpu,
|
|
||||||
memory,
|
|
||||||
usage_memory,
|
|
||||||
disk,
|
|
||||||
usage_disk,
|
|
||||||
temperature,
|
|
||||||
usage_temperature,
|
|
||||||
...rest
|
|
||||||
} = form;
|
|
||||||
|
|
||||||
const thresholds = {
|
|
||||||
...(cpu ? { usage_cpu: usage_cpu / 100 } : {}),
|
|
||||||
...(memory ? { usage_memory: usage_memory / 100 } : {}),
|
|
||||||
...(disk ? { usage_disk: usage_disk / 100 } : {}),
|
|
||||||
...(temperature ? { usage_temperature: usage_temperature / 100 } : {}),
|
|
||||||
};
|
|
||||||
|
|
||||||
form = {
|
|
||||||
...(isCreate ? {} : { _id: monitorId }),
|
|
||||||
...rest,
|
|
||||||
description: form.name,
|
|
||||||
type: "hardware",
|
|
||||||
notifications: infrastructureMonitor.notifications,
|
|
||||||
thresholds,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle create or update
|
|
||||||
isCreate
|
|
||||||
? await createMonitor({ monitor: form, redirect: "/infrastructure" })
|
|
||||||
: await updateMonitor({ monitor: form, redirect: "/infrastructure" });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const triggerUpdate = () => {
|
const triggerUpdate = () => {
|
||||||
@@ -252,28 +105,8 @@ const CreateInfrastructureMonitor = () => {
|
|||||||
|
|
||||||
const onChange = (event) => {
|
const onChange = (event) => {
|
||||||
const { value, name } = event.target;
|
const { value, name } = event.target;
|
||||||
setInfrastructureMonitor({
|
onChangeForm(name, value);
|
||||||
...infrastructureMonitor,
|
validateField(name, value);
|
||||||
[name]: value,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { error } = infrastructureMonitorValidation.validate(
|
|
||||||
{ [name]: value },
|
|
||||||
{ abortEarly: false }
|
|
||||||
);
|
|
||||||
setErrors((prev) => ({
|
|
||||||
...prev,
|
|
||||||
...(error ? { [name]: error.details[0].message } : { [name]: undefined }),
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCheckboxChange = (event) => {
|
|
||||||
const { name } = event.target;
|
|
||||||
const { checked } = event.target;
|
|
||||||
setInfrastructureMonitor({
|
|
||||||
...infrastructureMonitor,
|
|
||||||
[name]: checked,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePause = async () => {
|
const handlePause = async () => {
|
||||||
@@ -336,105 +169,19 @@ const CreateInfrastructureMonitor = () => {
|
|||||||
)}
|
)}
|
||||||
</Typography>
|
</Typography>
|
||||||
{!isCreate && (
|
{!isCreate && (
|
||||||
<Stack
|
<MonitorStatusHeader
|
||||||
direction="row"
|
monitor={monitor}
|
||||||
alignItems="center"
|
infrastructureMonitor={infrastructureMonitor}
|
||||||
height="fit-content"
|
/>
|
||||||
gap={theme.spacing(2)}
|
|
||||||
>
|
|
||||||
<Tooltip
|
|
||||||
title={pagespeedStatusMsg[determineState(monitor)]}
|
|
||||||
disableInteractive
|
|
||||||
slotProps={{
|
|
||||||
popper: {
|
|
||||||
modifiers: [
|
|
||||||
{
|
|
||||||
name: "offset",
|
|
||||||
options: { offset: [0, -8] },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box>
|
|
||||||
<PulseDot color={statusColor[determineState(monitor)]} />
|
|
||||||
</Box>
|
|
||||||
</Tooltip>
|
|
||||||
<Typography
|
|
||||||
component="h2"
|
|
||||||
variant="monitorUrl"
|
|
||||||
>
|
|
||||||
{infrastructureMonitor.url?.replace(/^https?:\/\//, "") || "..."}
|
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
position="relative"
|
|
||||||
variant="body2"
|
|
||||||
ml={theme.spacing(6)}
|
|
||||||
mt={theme.spacing(1)}
|
|
||||||
sx={{
|
|
||||||
"&:before": {
|
|
||||||
position: "absolute",
|
|
||||||
content: `""`,
|
|
||||||
width: theme.spacing(2),
|
|
||||||
height: theme.spacing(2),
|
|
||||||
borderRadius: "50%",
|
|
||||||
backgroundColor: theme.palette.primary.contrastTextTertiary,
|
|
||||||
opacity: 0.8,
|
|
||||||
left: theme.spacing(-5),
|
|
||||||
top: "50%",
|
|
||||||
transform: "translateY(-50%)",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("editing")}
|
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
{!isCreate && (
|
{!isCreate && (
|
||||||
<Box
|
<MonitorActionButtons
|
||||||
alignSelf="flex-end"
|
monitor={monitor}
|
||||||
ml="auto"
|
isBusy={isBusy}
|
||||||
>
|
handlePause={handlePause}
|
||||||
<Button
|
handleRemove={handleRemove}
|
||||||
onClick={handlePause}
|
/>
|
||||||
loading={isBusy}
|
|
||||||
variant="contained"
|
|
||||||
color="secondary"
|
|
||||||
sx={{
|
|
||||||
pl: theme.spacing(4),
|
|
||||||
pr: theme.spacing(6),
|
|
||||||
"& svg": {
|
|
||||||
mr: theme.spacing(2),
|
|
||||||
"& path": {
|
|
||||||
stroke: theme.palette.primary.contrastTextTertiary,
|
|
||||||
strokeWidth: 0.1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{monitor?.isActive ? (
|
|
||||||
<>
|
|
||||||
<PauseCircleOutlineIcon />
|
|
||||||
{t("pause")}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<PlayCircleOutlineRoundedIcon />
|
|
||||||
{t("resume")}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
loading={isBusy}
|
|
||||||
variant="contained"
|
|
||||||
color="error"
|
|
||||||
onClick={() => setIsOpen(true)}
|
|
||||||
sx={{ ml: theme.spacing(6) }}
|
|
||||||
>
|
|
||||||
{t("remove")}
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
@@ -534,58 +281,12 @@ const CreateInfrastructureMonitor = () => {
|
|||||||
setNotifications={infrastructureMonitor.notifications}
|
setNotifications={infrastructureMonitor.notifications}
|
||||||
/>
|
/>
|
||||||
</ConfigBox>
|
</ConfigBox>
|
||||||
<ConfigBox>
|
<CustomAlertsSection
|
||||||
<Box>
|
errors={errors}
|
||||||
<Typography
|
onChange={onChange}
|
||||||
component="h2"
|
infrastructureMonitor={infrastructureMonitor}
|
||||||
variant="h2"
|
handleCheckboxChange={handleCheckboxChange}
|
||||||
>
|
/>
|
||||||
{t("infrastructureCustomizeAlerts")}
|
|
||||||
</Typography>
|
|
||||||
<Typography component="p">
|
|
||||||
{t("infrastructureAlertNotificationDescription")}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
<Stack gap={theme.spacing(6)}>
|
|
||||||
{METRICS.map((metric) => {
|
|
||||||
return (
|
|
||||||
<CustomThreshold
|
|
||||||
key={metric}
|
|
||||||
infrastructureMonitor={infrastructureMonitor}
|
|
||||||
errors={errors}
|
|
||||||
checkboxId={metric}
|
|
||||||
checkboxName={metric}
|
|
||||||
checkboxLabel={
|
|
||||||
metric !== "cpu"
|
|
||||||
? capitalizeFirstLetter(metric)
|
|
||||||
: metric.toUpperCase()
|
|
||||||
}
|
|
||||||
onCheckboxChange={handleCheckboxChange}
|
|
||||||
isChecked={infrastructureMonitor[metric]}
|
|
||||||
fieldId={METRIC_PREFIX + metric}
|
|
||||||
fieldName={METRIC_PREFIX + metric}
|
|
||||||
fieldValue={String(infrastructureMonitor[METRIC_PREFIX + metric])}
|
|
||||||
onFieldChange={onChange}
|
|
||||||
alertUnit={metric == "temperature" ? "°C" : "%"}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{/* Error text */}
|
|
||||||
{hasAlertError(errors) && (
|
|
||||||
<Typography
|
|
||||||
component="span"
|
|
||||||
className="input-error"
|
|
||||||
color={theme.palette.error.main}
|
|
||||||
mt={theme.spacing(2)}
|
|
||||||
sx={{
|
|
||||||
opacity: 0.8,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{getAlertError(errors)}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</ConfigBox>
|
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography
|
<Typography
|
||||||
@@ -620,17 +321,6 @@ const CreateInfrastructureMonitor = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
{!isCreate && (
|
|
||||||
<Dialog
|
|
||||||
open={isOpen}
|
|
||||||
theme={theme}
|
|
||||||
title={t("deleteDialogTitle")}
|
|
||||||
description={t("deleteDialogDescription")}
|
|
||||||
onCancel={() => setIsOpen(false)}
|
|
||||||
confirmationButtonLabel={t("delete")}
|
|
||||||
onConfirm={handleRemove}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { FormControl, InputLabel, Select, MenuItem, Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
|
import Select from "../../../../../Components/Inputs/Select";
|
||||||
import NetworkStatBoxes from "./NetworkStatBoxes";
|
import NetworkStatBoxes from "./NetworkStatBoxes";
|
||||||
import NetworkCharts from "./NetworkCharts";
|
import NetworkCharts from "./NetworkCharts";
|
||||||
import MonitorTimeFrameHeader from "../../../../../Components/MonitorTimeFrameHeader";
|
import MonitorTimeFrameHeader from "../../../../../Components/MonitorTimeFrameHeader";
|
||||||
@@ -63,27 +64,17 @@ const Network = ({ net, checks, isLoading, dateRange, setDateRange }) => {
|
|||||||
gap={theme.spacing(4)}
|
gap={theme.spacing(4)}
|
||||||
>
|
>
|
||||||
{availableInterfaces.length > 0 && (
|
{availableInterfaces.length > 0 && (
|
||||||
<FormControl
|
<Select
|
||||||
variant="outlined"
|
name="networkInterface"
|
||||||
size="small"
|
label={t("networkInterface")}
|
||||||
|
value={selectedInterface}
|
||||||
|
onChange={(e) => setSelectedInterface(e.target.value)}
|
||||||
|
items={availableInterfaces.map((interfaceName) => ({
|
||||||
|
_id: interfaceName,
|
||||||
|
name: interfaceName,
|
||||||
|
}))}
|
||||||
sx={{ minWidth: 200 }}
|
sx={{ minWidth: 200 }}
|
||||||
>
|
/>
|
||||||
<InputLabel>{t("networkInterface")}</InputLabel>
|
|
||||||
<Select
|
|
||||||
value={selectedInterface}
|
|
||||||
onChange={(e) => setSelectedInterface(e.target.value)}
|
|
||||||
label={t("networkInterface")}
|
|
||||||
>
|
|
||||||
{availableInterfaces.map((interfaceName) => (
|
|
||||||
<MenuItem
|
|
||||||
key={interfaceName}
|
|
||||||
value={interfaceName}
|
|
||||||
>
|
|
||||||
{interfaceName}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
)}
|
||||||
<MonitorTimeFrameHeader
|
<MonitorTimeFrameHeader
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
|||||||
@@ -51,34 +51,14 @@ const Logs = () => {
|
|||||||
];
|
];
|
||||||
return (
|
return (
|
||||||
<Stack gap={theme.spacing(4)}>
|
<Stack gap={theme.spacing(4)}>
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: "sticky",
|
|
||||||
top: theme.spacing(17),
|
|
||||||
backdropFilter: "blur(10px)",
|
|
||||||
paddingY: theme.spacing(5),
|
|
||||||
paddingLeft: theme.spacing(6),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography variant="h2">{t("logsPage.description")}</Typography>
|
|
||||||
</Box>
|
|
||||||
<Divider
|
|
||||||
color={theme.palette.accent.main}
|
|
||||||
sx={{
|
|
||||||
position: "sticky",
|
|
||||||
top: theme.spacing(33),
|
|
||||||
backdropFilter: "blur(10px)",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Stack
|
<Stack
|
||||||
direction="row"
|
direction="row"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
gap={theme.spacing(4)}
|
gap={theme.spacing(4)}
|
||||||
mt={theme.spacing(10)}
|
|
||||||
sx={{
|
sx={{
|
||||||
position: "sticky",
|
position: "sticky",
|
||||||
top: theme.spacing(34),
|
top: theme.spacing(17),
|
||||||
backdropFilter: "blur(10px)",
|
backdropFilter: "blur(10px)",
|
||||||
paddingTop: theme.spacing(4),
|
paddingTop: theme.spacing(4),
|
||||||
paddingLeft: theme.spacing(6),
|
paddingLeft: theme.spacing(6),
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ const QueueDetails = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap={theme.spacing(4)}>
|
<Stack gap={theme.spacing(4)}>
|
||||||
<Typography variant="h2">{t("queuePage.metricsTable.title")}</Typography>
|
|
||||||
<Divider color={theme.palette.accent.main} />
|
|
||||||
<Stack
|
<Stack
|
||||||
gap={theme.spacing(20)}
|
gap={theme.spacing(20)}
|
||||||
mt={theme.spacing(10)}
|
mt={theme.spacing(10)}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const Logs = () => {
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
// Local state
|
// Local state
|
||||||
const [value, setValue] = useState(2);
|
const [value, setValue] = useState(0);
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
const handleChange = (event, newValue) => {
|
const handleChange = (event, newValue) => {
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import Select from "../../../../../Components/Inputs/Select";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
const ConfigSelect = ({ configSelection, valueSelect, onChange, ...props }) => {
|
||||||
|
const getValueById = (config, id) => {
|
||||||
|
const item = config.find((config) => config._id === id);
|
||||||
|
return item ? (item.value ? item.value : item.name) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getIdByValue = (config, name) => {
|
||||||
|
const item = config.find((config) => {
|
||||||
|
if (config.value) {
|
||||||
|
return config.value === name;
|
||||||
|
} else {
|
||||||
|
return config.name === name;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return item ? item._id : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
value={getIdByValue(configSelection, valueSelect)}
|
||||||
|
onChange={(event) => {
|
||||||
|
const newValue = getValueById(configSelection, event.target.value);
|
||||||
|
onChange(newValue);
|
||||||
|
}}
|
||||||
|
items={configSelection}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ConfigSelect.propTypes = {
|
||||||
|
configSelection: PropTypes.array.isRequired,
|
||||||
|
valueSelect: PropTypes.string.isRequired,
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
export default ConfigSelect;
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { networkService } from "../../../../main";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import {
|
||||||
|
MS_PER_SECOND,
|
||||||
|
MS_PER_MINUTE,
|
||||||
|
MS_PER_HOUR,
|
||||||
|
MS_PER_DAY,
|
||||||
|
MS_PER_WEEK,
|
||||||
|
} from "../../../../Utils/timeUtils";
|
||||||
|
|
||||||
|
const useMaintenanceActions = () => {
|
||||||
|
const MS_LOOKUP = {
|
||||||
|
seconds: MS_PER_SECOND,
|
||||||
|
minutes: MS_PER_MINUTE,
|
||||||
|
hours: MS_PER_HOUR,
|
||||||
|
days: MS_PER_DAY,
|
||||||
|
weeks: MS_PER_WEEK,
|
||||||
|
};
|
||||||
|
const REPEAT_LOOKUP = {
|
||||||
|
none: 0,
|
||||||
|
daily: MS_PER_DAY,
|
||||||
|
weekly: MS_PER_DAY * 7,
|
||||||
|
};
|
||||||
|
const handleSubmitForm = async (maintenanceWindowId, form) => {
|
||||||
|
const start = dayjs(form.startDate)
|
||||||
|
.set("hour", form.startTime.hour())
|
||||||
|
.set("minute", form.startTime.minute());
|
||||||
|
|
||||||
|
const MS_MULTIPLIER = MS_LOOKUP[form.durationUnit];
|
||||||
|
const durationInMs = form.duration * MS_MULTIPLIER;
|
||||||
|
const end = start.add(durationInMs);
|
||||||
|
|
||||||
|
const repeat = REPEAT_LOOKUP[form.repeat];
|
||||||
|
|
||||||
|
const submit = {
|
||||||
|
monitors: form.monitors.map((monitor) => monitor._id),
|
||||||
|
name: form.name,
|
||||||
|
start: start.toISOString(),
|
||||||
|
end: end.toISOString(),
|
||||||
|
repeat,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (repeat === 0) {
|
||||||
|
submit.expiry = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestConfig = { maintenanceWindow: submit };
|
||||||
|
|
||||||
|
if (maintenanceWindowId !== undefined) {
|
||||||
|
requestConfig.maintenanceWindowId = maintenanceWindowId;
|
||||||
|
}
|
||||||
|
const request =
|
||||||
|
maintenanceWindowId === undefined
|
||||||
|
? networkService.createMaintenanceWindow(requestConfig)
|
||||||
|
: networkService.editMaintenanceWindow(requestConfig);
|
||||||
|
return request;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleSubmitForm,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useMaintenanceActions;
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { networkService } from "../../../../main";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import {
|
||||||
|
MS_PER_SECOND,
|
||||||
|
MS_PER_MINUTE,
|
||||||
|
MS_PER_HOUR,
|
||||||
|
MS_PER_DAY,
|
||||||
|
MS_PER_WEEK,
|
||||||
|
} from "../../../../Utils/timeUtils";
|
||||||
|
const useMaintenanceData = () => {
|
||||||
|
const REVERSE_REPEAT_LOOKUP = {
|
||||||
|
0: "none",
|
||||||
|
[MS_PER_DAY]: "daily",
|
||||||
|
[MS_PER_WEEK]: "weekly",
|
||||||
|
};
|
||||||
|
const getDurationAndUnit = (durationInMs) => {
|
||||||
|
if (durationInMs % MS_PER_DAY === 0) {
|
||||||
|
return {
|
||||||
|
duration: (durationInMs / MS_PER_DAY).toString(),
|
||||||
|
durationUnit: "days",
|
||||||
|
};
|
||||||
|
} else if (durationInMs % MS_PER_HOUR === 0) {
|
||||||
|
return {
|
||||||
|
duration: (durationInMs / MS_PER_HOUR).toString(),
|
||||||
|
durationUnit: "hours",
|
||||||
|
};
|
||||||
|
} else if (durationInMs % MS_PER_MINUTE === 0) {
|
||||||
|
return {
|
||||||
|
duration: (durationInMs / MS_PER_MINUTE).toString(),
|
||||||
|
durationUnit: "minutes",
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
duration: (durationInMs / MS_PER_SECOND).toString(),
|
||||||
|
durationUnit: "seconds",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const fetchMonitorsMaintenance = async () => {
|
||||||
|
const response = await networkService.getMonitorsByTeamId({
|
||||||
|
limit: null,
|
||||||
|
types: ["http", "ping", "pagespeed", "port"],
|
||||||
|
});
|
||||||
|
const fetchedMonitors = response.data.data.monitors;
|
||||||
|
return fetchedMonitors;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initializeMaintenanceForEdit = async (maintenanceWindowId, monitorList) => {
|
||||||
|
const res = await networkService.getMaintenanceWindowById({
|
||||||
|
maintenanceWindowId: maintenanceWindowId,
|
||||||
|
});
|
||||||
|
const maintenanceWindow = res.data.data;
|
||||||
|
const { name, start, end, repeat, monitorId } = maintenanceWindow;
|
||||||
|
const startTime = dayjs(start);
|
||||||
|
const endTime = dayjs(end);
|
||||||
|
const durationInMs = endTime.diff(startTime, "milliseconds").toString();
|
||||||
|
const { duration, durationUnit } = getDurationAndUnit(durationInMs);
|
||||||
|
const monitor = monitorList.find((monitor) => monitor._id === monitorId);
|
||||||
|
const maintenanceWindowInformation = {
|
||||||
|
name,
|
||||||
|
repeat: REVERSE_REPEAT_LOOKUP[repeat],
|
||||||
|
startDate: startTime,
|
||||||
|
startTime,
|
||||||
|
duration,
|
||||||
|
durationUnit,
|
||||||
|
monitors: monitor ? [monitor] : [],
|
||||||
|
};
|
||||||
|
return maintenanceWindowInformation;
|
||||||
|
};
|
||||||
|
|
||||||
|
return { fetchMonitorsMaintenance, initializeMaintenanceForEdit };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useMaintenanceData;
|
||||||
@@ -10,71 +10,21 @@ import { MobileTimePicker } from "@mui/x-date-pickers/MobileTimePicker";
|
|||||||
import { maintenanceWindowValidation } from "../../../Validation/validation";
|
import { maintenanceWindowValidation } from "../../../Validation/validation";
|
||||||
import { createToast } from "../../../Utils/toastUtils";
|
import { createToast } from "../../../Utils/toastUtils";
|
||||||
import MonitorList from "./Components/MonitorList";
|
import MonitorList from "./Components/MonitorList";
|
||||||
import Checkbox from "../../../Components/Inputs/Checkbox";
|
import ConfigSelect from "./Components/ConfigSelect";
|
||||||
|
import useMaintenanceData from "./hooks/useMaintenanceData";
|
||||||
|
import useMaintenanceActions from "./hooks/useMaintenanceActions";
|
||||||
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
import Select from "../../../Components/Inputs/Select";
|
|
||||||
import TextInput from "../../../Components/Inputs/TextInput";
|
import TextInput from "../../../Components/Inputs/TextInput";
|
||||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||||
import CalendarIcon from "../../../assets/icons/calendar.svg?react";
|
import CalendarIcon from "../../../assets/icons/calendar.svg?react";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import Search from "../../../Components/Inputs/Search";
|
import Search from "../../../Components/Inputs/Search";
|
||||||
import { networkService } from "../../../main";
|
|
||||||
import { logger } from "../../../Utils/Logger";
|
import { logger } from "../../../Utils/Logger";
|
||||||
import {
|
|
||||||
MS_PER_SECOND,
|
|
||||||
MS_PER_MINUTE,
|
|
||||||
MS_PER_HOUR,
|
|
||||||
MS_PER_DAY,
|
|
||||||
MS_PER_WEEK,
|
|
||||||
} from "../../../Utils/timeUtils";
|
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { buildErrors, hasValidationErrors } from "../../../Validation/error";
|
import { buildErrors, hasValidationErrors } from "../../../Validation/error";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import dayjs from "dayjs";
|
||||||
const getDurationAndUnit = (durationInMs) => {
|
|
||||||
if (durationInMs % MS_PER_DAY === 0) {
|
|
||||||
return {
|
|
||||||
duration: (durationInMs / MS_PER_DAY).toString(),
|
|
||||||
durationUnit: "days",
|
|
||||||
};
|
|
||||||
} else if (durationInMs % MS_PER_HOUR === 0) {
|
|
||||||
return {
|
|
||||||
duration: (durationInMs / MS_PER_HOUR).toString(),
|
|
||||||
durationUnit: "hours",
|
|
||||||
};
|
|
||||||
} else if (durationInMs % MS_PER_MINUTE === 0) {
|
|
||||||
return {
|
|
||||||
duration: (durationInMs / MS_PER_MINUTE).toString(),
|
|
||||||
durationUnit: "minutes",
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
duration: (durationInMs / MS_PER_SECOND).toString(),
|
|
||||||
durationUnit: "seconds",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const MS_LOOKUP = {
|
|
||||||
seconds: MS_PER_SECOND,
|
|
||||||
minutes: MS_PER_MINUTE,
|
|
||||||
hours: MS_PER_HOUR,
|
|
||||||
days: MS_PER_DAY,
|
|
||||||
weeks: MS_PER_WEEK,
|
|
||||||
};
|
|
||||||
|
|
||||||
const REPEAT_LOOKUP = {
|
|
||||||
none: 0,
|
|
||||||
daily: MS_PER_DAY,
|
|
||||||
weekly: MS_PER_DAY * 7,
|
|
||||||
};
|
|
||||||
|
|
||||||
const REVERSE_REPEAT_LOOKUP = {
|
|
||||||
0: "none",
|
|
||||||
[MS_PER_DAY]: "daily",
|
|
||||||
[MS_PER_WEEK]: "weekly",
|
|
||||||
};
|
|
||||||
|
|
||||||
const repeatConfig = [
|
const repeatConfig = [
|
||||||
{ _id: 0, name: "Don't repeat", value: "none" },
|
{ _id: 0, name: "Don't repeat", value: "none" },
|
||||||
@@ -96,24 +46,10 @@ const durationConfig = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const getValueById = (config, id) => {
|
|
||||||
const item = config.find((config) => config._id === id);
|
|
||||||
return item ? (item.value ? item.value : item.name) : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getIdByValue = (config, name) => {
|
|
||||||
const item = config.find((config) => {
|
|
||||||
if (config.value) {
|
|
||||||
return config.value === name;
|
|
||||||
} else {
|
|
||||||
return config.name === name;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return item ? item._id : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const CreateMaintenance = () => {
|
const CreateMaintenance = () => {
|
||||||
const { maintenanceWindowId } = useParams();
|
const { maintenanceWindowId } = useParams();
|
||||||
|
const { handleSubmitForm } = useMaintenanceActions();
|
||||||
|
const { fetchMonitorsMaintenance, initializeMaintenanceForEdit } = useMaintenanceData();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -121,6 +57,7 @@ const CreateMaintenance = () => {
|
|||||||
const [monitors, setMonitors] = useState([]);
|
const [monitors, setMonitors] = useState([]);
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [errors, setErrors] = useState({});
|
||||||
const [form, setForm] = useState({
|
const [form, setForm] = useState({
|
||||||
repeat: "none",
|
repeat: "none",
|
||||||
startDate: dayjs(),
|
startDate: dayjs(),
|
||||||
@@ -130,43 +67,35 @@ const CreateMaintenance = () => {
|
|||||||
name: "",
|
name: "",
|
||||||
monitors: [],
|
monitors: [],
|
||||||
});
|
});
|
||||||
const [errors, setErrors] = useState({});
|
const handleFormChange = (key, value) => {
|
||||||
|
setForm((prev) => ({ ...prev, [key]: value }));
|
||||||
|
const { error } = maintenanceWindowValidation.validate(
|
||||||
|
{ [key]: value },
|
||||||
|
{ abortEarly: false }
|
||||||
|
);
|
||||||
|
setErrors((prev) => {
|
||||||
|
return buildErrors(prev, key, error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchMonitors = async () => {
|
const fetchMonitors = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await networkService.getMonitorsByTeamId({
|
const fetchedMonitors = await fetchMonitorsMaintenance();
|
||||||
limit: null,
|
setMonitors(fetchedMonitors);
|
||||||
types: ["http", "ping", "pagespeed", "port"],
|
|
||||||
});
|
|
||||||
const monitors = response.data.data.monitors;
|
|
||||||
setMonitors(monitors);
|
|
||||||
|
|
||||||
if (maintenanceWindowId === undefined) {
|
if (maintenanceWindowId === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const maintenanceWindowInformation = await initializeMaintenanceForEdit(
|
||||||
const res = await networkService.getMaintenanceWindowById({
|
maintenanceWindowId,
|
||||||
maintenanceWindowId: maintenanceWindowId,
|
fetchedMonitors
|
||||||
});
|
);
|
||||||
const maintenanceWindow = res.data.data;
|
setForm((prev) => ({
|
||||||
const { name, start, end, repeat, monitorId } = maintenanceWindow;
|
...prev,
|
||||||
const startTime = dayjs(start);
|
...maintenanceWindowInformation,
|
||||||
const endTime = dayjs(end);
|
}));
|
||||||
const durationInMs = endTime.diff(startTime, "milliseconds").toString();
|
|
||||||
const { duration, durationUnit } = getDurationAndUnit(durationInMs);
|
|
||||||
const monitor = monitors.find((monitor) => monitor._id === monitorId);
|
|
||||||
setForm({
|
|
||||||
...form,
|
|
||||||
name,
|
|
||||||
repeat: REVERSE_REPEAT_LOOKUP[repeat],
|
|
||||||
startDate: startTime,
|
|
||||||
startTime,
|
|
||||||
duration,
|
|
||||||
durationUnit,
|
|
||||||
monitors: monitor ? [monitor] : [],
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
createToast({ body: "Failed to fetch data" });
|
createToast({ body: "Failed to fetch data" });
|
||||||
logger.error("Failed to fetch monitors", error);
|
logger.error("Failed to fetch monitors", error);
|
||||||
@@ -177,90 +106,9 @@ const CreateMaintenance = () => {
|
|||||||
fetchMonitors();
|
fetchMonitors();
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
const handleSearch = (value) => {
|
|
||||||
setSearch(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSelectMonitors = (monitors) => {
|
|
||||||
setForm({ ...form, monitors });
|
|
||||||
const { error } = maintenanceWindowValidation.validate(
|
|
||||||
{ monitors },
|
|
||||||
{ abortEarly: false }
|
|
||||||
);
|
|
||||||
setErrors((prev) => {
|
|
||||||
return buildErrors(prev, "monitors", error);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFormChange = (key, value) => {
|
|
||||||
setForm({ ...form, [key]: value });
|
|
||||||
const { error } = maintenanceWindowValidation.validate(
|
|
||||||
{ [key]: value },
|
|
||||||
{ abortEarly: false }
|
|
||||||
);
|
|
||||||
setErrors((prev) => {
|
|
||||||
return buildErrors(prev, key, error);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTimeChange = (key, newTime) => {
|
|
||||||
setForm({ ...form, [key]: newTime });
|
|
||||||
const { error } = maintenanceWindowValidation.validate(
|
|
||||||
{ [key]: newTime },
|
|
||||||
{ abortEarly: false }
|
|
||||||
);
|
|
||||||
setErrors((prev) => {
|
|
||||||
return buildErrors(prev, key, error);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMonitorsChange = (selected) => {
|
|
||||||
setForm((prev) => ({ ...prev, monitors: selected }));
|
|
||||||
const { error } = maintenanceWindowValidation.validate(
|
|
||||||
{ monitors: selected },
|
|
||||||
{ abortEarly: false }
|
|
||||||
);
|
|
||||||
setErrors((prev) => {
|
|
||||||
return buildErrors(prev, "monitors", error);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (hasValidationErrors(form, maintenanceWindowValidation, setErrors)) return;
|
if (hasValidationErrors(form, maintenanceWindowValidation, setErrors)) return;
|
||||||
// Build timestamp for maintenance window from startDate and startTime
|
const request = handleSubmitForm(maintenanceWindowId, form);
|
||||||
const start = dayjs(form.startDate)
|
|
||||||
.set("hour", form.startTime.hour())
|
|
||||||
.set("minute", form.startTime.minute());
|
|
||||||
// Build end timestamp for maintenance window
|
|
||||||
const MS_MULTIPLIER = MS_LOOKUP[form.durationUnit];
|
|
||||||
const durationInMs = form.duration * MS_MULTIPLIER;
|
|
||||||
const end = start.add(durationInMs);
|
|
||||||
|
|
||||||
// Get repeat value in milliseconds
|
|
||||||
const repeat = REPEAT_LOOKUP[form.repeat];
|
|
||||||
|
|
||||||
const submit = {
|
|
||||||
monitors: form.monitors.map((monitor) => monitor._id),
|
|
||||||
name: form.name,
|
|
||||||
start: start.toISOString(),
|
|
||||||
end: end.toISOString(),
|
|
||||||
repeat,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (repeat === 0) {
|
|
||||||
submit.expiry = end;
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestConfig = { maintenanceWindow: submit };
|
|
||||||
|
|
||||||
if (maintenanceWindowId !== undefined) {
|
|
||||||
requestConfig.maintenanceWindowId = maintenanceWindowId;
|
|
||||||
}
|
|
||||||
const request =
|
|
||||||
maintenanceWindowId === undefined
|
|
||||||
? networkService.createMaintenanceWindow(requestConfig)
|
|
||||||
: networkService.editMaintenanceWindow(requestConfig);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
await request;
|
await request;
|
||||||
@@ -353,18 +201,13 @@ const CreateMaintenance = () => {
|
|||||||
error={errors["name"] ? true : false}
|
error={errors["name"] ? true : false}
|
||||||
helperText={errors["name"]}
|
helperText={errors["name"]}
|
||||||
/>
|
/>
|
||||||
<Select
|
<ConfigSelect
|
||||||
id="repeat"
|
id="repeat"
|
||||||
name="maintenance-repeat"
|
name="maintenance-repeat"
|
||||||
label={t("maintenanceRepeat")}
|
label={t("maintenanceRepeat")}
|
||||||
value={getIdByValue(repeatConfig, form.repeat)}
|
valueSelect={form.repeat}
|
||||||
onChange={(event) => {
|
configSelection={repeatConfig}
|
||||||
handleFormChange(
|
onChange={(value) => handleFormChange("repeat", value)}
|
||||||
"repeat",
|
|
||||||
getValueById(repeatConfig, event.target.value)
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
items={repeatConfig}
|
|
||||||
/>
|
/>
|
||||||
<Stack>
|
<Stack>
|
||||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||||
@@ -437,7 +280,7 @@ const CreateMaintenance = () => {
|
|||||||
}}
|
}}
|
||||||
sx={{}}
|
sx={{}}
|
||||||
onChange={(newDate) => {
|
onChange={(newDate) => {
|
||||||
handleTimeChange("startDate", newDate);
|
handleFormChange("startDate", newDate);
|
||||||
}}
|
}}
|
||||||
error={errors["startDate"]}
|
error={errors["startDate"]}
|
||||||
/>
|
/>
|
||||||
@@ -453,7 +296,7 @@ const CreateMaintenance = () => {
|
|||||||
>
|
>
|
||||||
{t("startTime")}
|
{t("startTime")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography>{t("timeZoneInfo")}</Typography>
|
<Typography> {t("timeZoneInfo")} </Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Stack gap={theme.spacing(15)}>
|
<Stack gap={theme.spacing(15)}>
|
||||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||||
@@ -462,7 +305,7 @@ const CreateMaintenance = () => {
|
|||||||
label={t("startTime")}
|
label={t("startTime")}
|
||||||
value={form.startTime}
|
value={form.startTime}
|
||||||
onChange={(newTime) => {
|
onChange={(newTime) => {
|
||||||
handleTimeChange("startTime", newTime);
|
handleFormChange("startTime", newTime);
|
||||||
}}
|
}}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
nextIconButton: { sx: { ml: theme.spacing(2) } },
|
nextIconButton: { sx: { ml: theme.spacing(2) } },
|
||||||
@@ -508,16 +351,11 @@ const CreateMaintenance = () => {
|
|||||||
error={errors["duration"] ? true : false}
|
error={errors["duration"] ? true : false}
|
||||||
helperText={errors["duration"]}
|
helperText={errors["duration"]}
|
||||||
/>
|
/>
|
||||||
<Select
|
<ConfigSelect
|
||||||
id="durationUnit"
|
id="durationUnit"
|
||||||
value={getIdByValue(durationConfig, form.durationUnit)}
|
valueSelect={form.durationUnit}
|
||||||
items={durationConfig}
|
configSelection={durationConfig}
|
||||||
onChange={(event) => {
|
onChange={(value) => handleFormChange("durationUnit", value)}
|
||||||
handleFormChange(
|
|
||||||
"durationUnit",
|
|
||||||
getValueById(durationConfig, event.target.value)
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
error={errors["durationUnit"]}
|
error={errors["durationUnit"]}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -545,7 +383,9 @@ const CreateMaintenance = () => {
|
|||||||
inputValue={search}
|
inputValue={search}
|
||||||
value={form.monitors}
|
value={form.monitors}
|
||||||
handleInputChange={setSearch}
|
handleInputChange={setSearch}
|
||||||
handleChange={handleMonitorsChange}
|
handleChange={(selected) => {
|
||||||
|
handleFormChange("monitors", selected);
|
||||||
|
}}
|
||||||
error={errors["monitors"]}
|
error={errors["monitors"]}
|
||||||
/>
|
/>
|
||||||
<MonitorList
|
<MonitorList
|
||||||
|
|||||||
@@ -89,8 +89,6 @@ const CreateNotifications = () => {
|
|||||||
error.details.forEach((err) => {
|
error.details.forEach((err) => {
|
||||||
newErrors[err.path[0]] = err.message;
|
newErrors[err.path[0]] = err.message;
|
||||||
});
|
});
|
||||||
console.log(JSON.stringify(newErrors));
|
|
||||||
console.log(JSON.stringify(form, null, 2));
|
|
||||||
createToast({ body: Object.values(newErrors)[0] });
|
createToast({ body: Object.values(newErrors)[0] });
|
||||||
setErrors(newErrors);
|
setErrors(newErrors);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const NOTIFICATION_TYPES = [
|
|||||||
export const TITLE_MAP = {
|
export const TITLE_MAP = {
|
||||||
email: "createNotifications.emailSettings.title",
|
email: "createNotifications.emailSettings.title",
|
||||||
slack: "createNotifications.slackSettings.title",
|
slack: "createNotifications.slackSettings.title",
|
||||||
pager_duty: "createNotifications.pagerDutySettings.title",
|
pager_duty: "createNotifications.pagerdutySettings.title",
|
||||||
webhook: "createNotifications.webhookSettings.title",
|
webhook: "createNotifications.webhookSettings.title",
|
||||||
discord: "createNotifications.discordSettings.title",
|
discord: "createNotifications.discordSettings.title",
|
||||||
};
|
};
|
||||||
@@ -17,7 +17,7 @@ export const TITLE_MAP = {
|
|||||||
export const DESCRIPTION_MAP = {
|
export const DESCRIPTION_MAP = {
|
||||||
email: "createNotifications.emailSettings.description",
|
email: "createNotifications.emailSettings.description",
|
||||||
slack: "createNotifications.slackSettings.description",
|
slack: "createNotifications.slackSettings.description",
|
||||||
pager_duty: "createNotifications.pagerDutySettings.description",
|
pager_duty: "createNotifications.pagerdutySettings.description",
|
||||||
webhook: "createNotifications.webhookSettings.description",
|
webhook: "createNotifications.webhookSettings.description",
|
||||||
discord: "createNotifications.discordSettings.description",
|
discord: "createNotifications.discordSettings.description",
|
||||||
};
|
};
|
||||||
@@ -25,7 +25,7 @@ export const DESCRIPTION_MAP = {
|
|||||||
export const LABEL_MAP = {
|
export const LABEL_MAP = {
|
||||||
email: "createNotifications.emailSettings.emailLabel",
|
email: "createNotifications.emailSettings.emailLabel",
|
||||||
slack: "createNotifications.slackSettings.webhookLabel",
|
slack: "createNotifications.slackSettings.webhookLabel",
|
||||||
pager_duty: "createNotifications.pagerDutySettings.integrationKeyLabel",
|
pager_duty: "createNotifications.pagerdutySettings.integrationKeyLabel",
|
||||||
webhook: "createNotifications.webhookSettings.webhookLabel",
|
webhook: "createNotifications.webhookSettings.webhookLabel",
|
||||||
discord: "createNotifications.discordSettings.webhookLabel",
|
discord: "createNotifications.discordSettings.webhookLabel",
|
||||||
};
|
};
|
||||||
@@ -33,7 +33,7 @@ export const LABEL_MAP = {
|
|||||||
export const PLACEHOLDER_MAP = {
|
export const PLACEHOLDER_MAP = {
|
||||||
email: "createNotifications.emailSettings.emailPlaceholder",
|
email: "createNotifications.emailSettings.emailPlaceholder",
|
||||||
slack: "createNotifications.slackSettings.webhookPlaceholder",
|
slack: "createNotifications.slackSettings.webhookPlaceholder",
|
||||||
pager_duty: "createNotifications.pagerDutySettings.integrationKeyPlaceholder",
|
pager_duty: "createNotifications.pagerdutySettings.integrationKeyPlaceholder",
|
||||||
webhook: "createNotifications.webhookSettings.webhookPlaceholder",
|
webhook: "createNotifications.webhookSettings.webhookPlaceholder",
|
||||||
discord: "createNotifications.discordSettings.webhookPlaceholder",
|
discord: "createNotifications.discordSettings.webhookPlaceholder",
|
||||||
};
|
};
|
||||||
@@ -152,7 +152,7 @@ const PageSpeedSetup = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { error } = monitorValidation.validate(
|
const { error } = monitorValidation.validate(
|
||||||
{ [name]: value },
|
{ [name]: value, type: monitor.type },
|
||||||
{ abortEarly: false }
|
{ abortEarly: false }
|
||||||
);
|
);
|
||||||
setErrors((prev) => ({
|
setErrors((prev) => ({
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const PieChartLegend = ({ audits }) => {
|
|||||||
: theme.palette.tertiary.main;
|
: theme.palette.tertiary.main;
|
||||||
|
|
||||||
// Find the position where the number ends and the unit begins
|
// Find the position where the number ends and the unit begins
|
||||||
const match = audit.displayValue.match(/(\d+\.?\d*)\s*([a-zA-Z]+)/);
|
const match = audit?.displayValue?.match(/(\d+\.?\d*)\s*([a-zA-Z]+)/);
|
||||||
let value;
|
let value;
|
||||||
let unit;
|
let unit;
|
||||||
if (match) {
|
if (match) {
|
||||||
|
|||||||
@@ -44,20 +44,6 @@ import {
|
|||||||
useFetchMonitorGames,
|
useFetchMonitorGames,
|
||||||
} from "../../../Hooks/monitorHooks";
|
} from "../../../Hooks/monitorHooks";
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
* Create page renders monitor creation or configuration views.
|
||||||
* @component
|
* @component
|
||||||
@@ -69,6 +55,8 @@ const UptimeCreate = ({ isClone = false }) => {
|
|||||||
// States
|
// States
|
||||||
const [monitor, setMonitor] = useState({
|
const [monitor, setMonitor] = useState({
|
||||||
type: "http",
|
type: "http",
|
||||||
|
statusWindowSize: 5,
|
||||||
|
statusWindowThreshold: 60,
|
||||||
matchMethod: "equal",
|
matchMethod: "equal",
|
||||||
expectedValue: "",
|
expectedValue: "",
|
||||||
jsonPath: "",
|
jsonPath: "",
|
||||||
@@ -191,7 +179,10 @@ const UptimeCreate = ({ isClone = false }) => {
|
|||||||
? `http${https ? "s" : ""}://` + monitor.url
|
? `http${https ? "s" : ""}://` + monitor.url
|
||||||
: monitor.url,
|
: monitor.url,
|
||||||
name: monitor.name || monitor.url.substring(0, 50),
|
name: monitor.name || monitor.url.substring(0, 50),
|
||||||
|
statusWindowSize: monitor.statusWindowSize,
|
||||||
|
statusWindowThreshold: monitor.statusWindowThreshold,
|
||||||
type: monitor.type,
|
type: monitor.type,
|
||||||
|
|
||||||
port:
|
port:
|
||||||
monitor.type === "port" || monitor.type === "game" ? monitor.port : undefined,
|
monitor.type === "port" || monitor.type === "game" ? monitor.port : undefined,
|
||||||
interval: monitor.interval,
|
interval: monitor.interval,
|
||||||
@@ -206,6 +197,8 @@ const UptimeCreate = ({ isClone = false }) => {
|
|||||||
_id: monitor._id,
|
_id: monitor._id,
|
||||||
url: monitor.url,
|
url: monitor.url,
|
||||||
name: monitor.name || monitor.url.substring(0, 50),
|
name: monitor.name || monitor.url.substring(0, 50),
|
||||||
|
statusWindowSize: monitor.statusWindowSize,
|
||||||
|
statusWindowThreshold: monitor.statusWindowThreshold,
|
||||||
type: monitor.type,
|
type: monitor.type,
|
||||||
matchMethod: monitor.matchMethod,
|
matchMethod: monitor.matchMethod,
|
||||||
expectedValue: monitor.expectedValue,
|
expectedValue: monitor.expectedValue,
|
||||||
@@ -299,7 +292,7 @@ const UptimeCreate = ({ isClone = false }) => {
|
|||||||
|
|
||||||
const isBusy = isLoading || isCreating || isDeleting || isUpdating || isPausing;
|
const isBusy = isLoading || isCreating || isDeleting || isUpdating || isPausing;
|
||||||
const displayInterval = monitor?.interval / MS_PER_MINUTE || 1;
|
const displayInterval = monitor?.interval / MS_PER_MINUTE || 1;
|
||||||
const parsedUrl = parseUrl(monitor?.url);
|
const parsedUrl = monitor?.url;
|
||||||
const protocol = parsedUrl?.protocol?.replace(":", "") || "";
|
const protocol = parsedUrl?.protocol?.replace(":", "") || "";
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -616,6 +609,39 @@ const UptimeCreate = ({ isClone = false }) => {
|
|||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</ConfigBox>
|
</ConfigBox>
|
||||||
|
<ConfigBox>
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("createMonitorPage.incidentConfigTitle")}
|
||||||
|
</Typography>
|
||||||
|
<Typography component="p">
|
||||||
|
{t("createMonitorPage.incidentConfigDescription")}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Stack gap={theme.spacing(20)}>
|
||||||
|
<TextInput
|
||||||
|
name="statusWindowSize"
|
||||||
|
label={t("createMonitorPage.incidentConfigStatusWindowLabel")}
|
||||||
|
type="number"
|
||||||
|
value={monitor.statusWindowSize}
|
||||||
|
onChange={onChange}
|
||||||
|
error={errors["statusWindowSize"] ? true : false}
|
||||||
|
helperText={errors["statusWindowSize"]}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
name="statusWindowThreshold"
|
||||||
|
label={t("createMonitorPage.incidentConfigStatusWindowThresholdLabel")}
|
||||||
|
type="number"
|
||||||
|
value={monitor.statusWindowThreshold}
|
||||||
|
onChange={onChange}
|
||||||
|
error={errors["statusWindowThreshold"] ? true : false}
|
||||||
|
helperText={errors["statusWindowThreshold"]}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</ConfigBox>
|
||||||
<ConfigBox>
|
<ConfigBox>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography
|
<Typography
|
||||||
|
|||||||
@@ -564,6 +564,10 @@ const baseTheme = (palette) => ({
|
|||||||
"& .MuiSelect-icon": {
|
"& .MuiSelect-icon": {
|
||||||
color: theme.palette.primary.contrastTextSecondary, // Dropdown + color
|
color: theme.palette.primary.contrastTextSecondary, // Dropdown + color
|
||||||
},
|
},
|
||||||
|
"& .MuiSelect-select": {
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: theme.palette.primary.main, // Background on hover
|
backgroundColor: theme.palette.primary.main, // Background on hover
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -115,6 +115,16 @@ const monitorValidation = joi.object({
|
|||||||
_id: joi.string(),
|
_id: joi.string(),
|
||||||
userId: joi.string(),
|
userId: joi.string(),
|
||||||
teamId: joi.string(),
|
teamId: joi.string(),
|
||||||
|
statusWindowSize: joi.number().min(1).max(20).default(5).messages({
|
||||||
|
"number.base": "Status window size must be a number.",
|
||||||
|
"number.min": "Status window size must be at least 1.",
|
||||||
|
"number.max": "Status window size must be at most 20.",
|
||||||
|
}),
|
||||||
|
statusWindowThreshold: joi.number().min(1).max(100).default(60).messages({
|
||||||
|
"number.base": "Incident percentage must be a number.",
|
||||||
|
"number.min": "Incident percentage must be at least 1.",
|
||||||
|
"number.max": "Incident percentage must be at most 100.",
|
||||||
|
}),
|
||||||
url: joi.when("type", {
|
url: joi.when("type", {
|
||||||
is: "docker",
|
is: "docker",
|
||||||
then: joi
|
then: joi
|
||||||
@@ -150,6 +160,11 @@ const monitorValidation = joi.object({
|
|||||||
// can be replaced by a shortest alternative
|
// can be replaced by a shortest alternative
|
||||||
// (?![-_])(?:[-\\w\\u00a1-\\uffff]{0,63}[^-_]\\.)+
|
// (?![-_])(?:[-\\w\\u00a1-\\uffff]{0,63}[^-_]\\.)+
|
||||||
"(?:" +
|
"(?:" +
|
||||||
|
// Single hostname without dots (like localhost)
|
||||||
|
"[a-z0-9\\u00a1-\\uffff][a-z0-9\\u00a1-\\uffff_-]{0,62}" +
|
||||||
|
"|" +
|
||||||
|
// Domain with dots
|
||||||
|
"(?:" +
|
||||||
"(?:" +
|
"(?:" +
|
||||||
"[a-z0-9\\u00a1-\\uffff]" +
|
"[a-z0-9\\u00a1-\\uffff]" +
|
||||||
"[a-z0-9\\u00a1-\\uffff_-]{0,62}" +
|
"[a-z0-9\\u00a1-\\uffff_-]{0,62}" +
|
||||||
@@ -159,6 +174,7 @@ const monitorValidation = joi.object({
|
|||||||
// TLD identifier name, may end with dot
|
// TLD identifier name, may end with dot
|
||||||
"(?:[a-z\\u00a1-\\uffff]{2,}\\.?)" +
|
"(?:[a-z\\u00a1-\\uffff]{2,}\\.?)" +
|
||||||
")" +
|
")" +
|
||||||
|
")" +
|
||||||
// port number (optional)
|
// port number (optional)
|
||||||
"(?::\\d{2,5})?" +
|
"(?::\\d{2,5})?" +
|
||||||
// resource path (optional)
|
// resource path (optional)
|
||||||
@@ -439,21 +455,35 @@ const notificationValidation = joi.object({
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
address: joi.when("type", {
|
address: joi.when("type", {
|
||||||
is: "email",
|
switch: [
|
||||||
then: joi
|
{
|
||||||
.string()
|
is: "email",
|
||||||
.email({ tlds: { allow: false } })
|
then: joi
|
||||||
.required()
|
.string()
|
||||||
.messages({
|
.email({ tlds: { allow: false } })
|
||||||
"string.empty": "E-mail address cannot be empty",
|
.required()
|
||||||
"any.required": "E-mail address is required",
|
.messages({
|
||||||
"string.email": "Please enter a valid e-mail address",
|
"string.empty": "E-mail address cannot be empty",
|
||||||
}),
|
"any.required": "E-mail address is required",
|
||||||
otherwise: joi.string().uri().required().messages({
|
"string.email": "Please enter a valid e-mail address",
|
||||||
"string.empty": "Webhook URL cannot be empty",
|
}),
|
||||||
"any.required": "Webhook URL is required",
|
},
|
||||||
"string.uri": "Please enter a valid Webhook URL",
|
{
|
||||||
}),
|
is: "pager_duty",
|
||||||
|
then: joi.string().required().messages({
|
||||||
|
"string.empty": "PagerDuty routing key cannot be empty",
|
||||||
|
"any.required": "PagerDuty routing key is required",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
is: joi.valid("webhook", "slack", "discord"),
|
||||||
|
then: joi.string().uri().required().messages({
|
||||||
|
"string.empty": "Webhook URL cannot be empty",
|
||||||
|
"any.required": "Webhook URL is required",
|
||||||
|
"string.uri": "Please enter a valid Webhook URL",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+55
-49
@@ -1,18 +1,4 @@
|
|||||||
{
|
{
|
||||||
"ClickUpload": "Click to upload",
|
|
||||||
"DeleteAccountButton": "Remove account",
|
|
||||||
"DeleteAccountTitle": "Remove account",
|
|
||||||
"DeleteAccountWarning": "Removing your account means you won't be able to sign in again and all your data will be removed. This isn't reversible.",
|
|
||||||
"DeleteDescriptionText": "This will remove the account and all associated data from the server. This isn't reversible.",
|
|
||||||
"DeleteWarningTitle": "Really remove this account?",
|
|
||||||
"DragandDrop": "drag and drop",
|
|
||||||
"EmailDescriptionText": "This is your current email address — it cannot be changed.",
|
|
||||||
"FirstName": "First name",
|
|
||||||
"LastName": "Last name",
|
|
||||||
"MaxSize": "Maximum Size",
|
|
||||||
"PhotoDescriptionText": "This photo will be displayed in your profile page.",
|
|
||||||
"SupportedFormats": "Supported formats",
|
|
||||||
"YourPhoto": "Profile photo",
|
|
||||||
"aboutus": "About Us",
|
"aboutus": "About Us",
|
||||||
"access": "Access",
|
"access": "Access",
|
||||||
"actions": "Actions",
|
"actions": "Actions",
|
||||||
@@ -202,13 +188,6 @@
|
|||||||
"avgCpuTemperature": "Average CPU Temperature",
|
"avgCpuTemperature": "Average CPU Temperature",
|
||||||
"bar": "Bar",
|
"bar": "Bar",
|
||||||
"basicInformation": "Basic Information",
|
"basicInformation": "Basic Information",
|
||||||
"bytesPerSecond": "Bytes per second",
|
|
||||||
"bytesReceived": "Bytes Received",
|
|
||||||
"bytesSent": "Bytes Sent",
|
|
||||||
"dataReceived": "Data Received",
|
|
||||||
"dataSent": "Data Sent",
|
|
||||||
"dataRate": "Data Rate",
|
|
||||||
"rate": "Rate",
|
|
||||||
"bulkImport": {
|
"bulkImport": {
|
||||||
"fallbackPage": "Import a file to upload a list of servers in bulk",
|
"fallbackPage": "Import a file to upload a list of servers in bulk",
|
||||||
"invalidFileType": "Invalid file type",
|
"invalidFileType": "Invalid file type",
|
||||||
@@ -222,17 +201,21 @@
|
|||||||
"uploadSuccess": "Monitors created successfully!",
|
"uploadSuccess": "Monitors created successfully!",
|
||||||
"validationFailed": "Validation failed"
|
"validationFailed": "Validation failed"
|
||||||
},
|
},
|
||||||
|
"bytesPerSecond": "Bytes per second",
|
||||||
|
"bytesReceived": "Bytes Received",
|
||||||
|
"bytesSent": "Bytes Sent",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"checkFormError": "Please check the form for errors.",
|
"checkFormError": "Please check the form for errors.",
|
||||||
"checkFrequency": "Check frequency",
|
"checkFrequency": "Check frequency",
|
||||||
"chooseGame": "Choose game",
|
|
||||||
"checkHooks": {
|
"checkHooks": {
|
||||||
"failureResolveAll": "Failed to resolve all incidents.",
|
"failureResolveAll": "Failed to resolve all incidents.",
|
||||||
"failureResolveMonitor": "Failed to resolve monitor incidents.",
|
"failureResolveMonitor": "Failed to resolve monitor incidents.",
|
||||||
"failureResolveOne": "Failed to resolve incident."
|
"failureResolveOne": "Failed to resolve incident."
|
||||||
},
|
},
|
||||||
"checkingEvery": "Checking every",
|
"checkingEvery": "Checking every",
|
||||||
|
"chooseGame": "Choose game",
|
||||||
"city": "CITY",
|
"city": "CITY",
|
||||||
|
"ClickUpload": "Click to upload",
|
||||||
"common": {
|
"common": {
|
||||||
"appName": "Checkmate",
|
"appName": "Checkmate",
|
||||||
"buttons": {
|
"buttons": {
|
||||||
@@ -262,6 +245,12 @@
|
|||||||
"createMaintenance": "Create maintenance",
|
"createMaintenance": "Create maintenance",
|
||||||
"createMaintenanceWindow": "Create maintenance window",
|
"createMaintenanceWindow": "Create maintenance window",
|
||||||
"createMonitor": "Create monitor",
|
"createMonitor": "Create monitor",
|
||||||
|
"createMonitorPage": {
|
||||||
|
"incidentConfigDescription": "A sliding window is used to determine when a monitor goes down. The status of a monitor will only change when the percentage of checks in the sliding window meet the specified value.",
|
||||||
|
"incidentConfigStatusWindowLabel": "How many checks should be in the sliding window?",
|
||||||
|
"incidentConfigStatusWindowThresholdLabel": "What percentage of checks in the sliding window fail/succeed before monitor status changes?",
|
||||||
|
"incidentConfigTitle": "Incidents"
|
||||||
|
},
|
||||||
"createNew": "Create new",
|
"createNew": "Create new",
|
||||||
"createNotifications": {
|
"createNotifications": {
|
||||||
"dialogDeleteConfirm": "Delete",
|
"dialogDeleteConfirm": "Delete",
|
||||||
@@ -311,13 +300,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"createYour": "Create your",
|
"createYour": "Create your",
|
||||||
|
"dataRate": "Data Rate",
|
||||||
|
"dataReceived": "Data Received",
|
||||||
|
"dataSent": "Data Sent",
|
||||||
"date&Time": "Date & Time",
|
"date&Time": "Date & Time",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
|
"DeleteAccountButton": "Remove account",
|
||||||
|
"DeleteAccountTitle": "Remove account",
|
||||||
|
"DeleteAccountWarning": "Removing your account means you won't be able to sign in again and all your data will be removed. This isn't reversible.",
|
||||||
|
"DeleteDescriptionText": "This will remove the account and all associated data from the server. This isn't reversible.",
|
||||||
"deleteDialogDescription": "Once deleted, this monitor cannot be retrieved.",
|
"deleteDialogDescription": "Once deleted, this monitor cannot be retrieved.",
|
||||||
"deleteDialogTitle": "Do you really want to delete this monitor?",
|
"deleteDialogTitle": "Do you really want to delete this monitor?",
|
||||||
"deleteStatusPage": "Do you want to delete this status page?",
|
"deleteStatusPage": "Do you want to delete this status page?",
|
||||||
"deleteStatusPageConfirm": "Yes, delete status page",
|
"deleteStatusPageConfirm": "Yes, delete status page",
|
||||||
"deleteStatusPageDescription": "Once deleted, your status page cannot be retrieved.",
|
"deleteStatusPageDescription": "Once deleted, your status page cannot be retrieved.",
|
||||||
|
"DeleteWarningTitle": "Really remove this account?",
|
||||||
|
"details": "Details",
|
||||||
"diagnosticsPage": {
|
"diagnosticsPage": {
|
||||||
"diagnosticDescription": "System diagnostics",
|
"diagnosticDescription": "System diagnostics",
|
||||||
"gauges": {
|
"gauges": {
|
||||||
@@ -341,15 +339,6 @@
|
|||||||
},
|
},
|
||||||
"disk": "Disk",
|
"disk": "Disk",
|
||||||
"diskUsage": "Disk Usage",
|
"diskUsage": "Disk Usage",
|
||||||
"drops": "Drops",
|
|
||||||
"errors": "Errors",
|
|
||||||
"errorsIn": "Errors In",
|
|
||||||
"errorsOut": "Errors Out",
|
|
||||||
"networkErrors": "Network Errors",
|
|
||||||
"networkDrops": "Network Drops",
|
|
||||||
"networkInterface": "Network Interface",
|
|
||||||
"selectInterface": "Select Interface",
|
|
||||||
"details": "Details",
|
|
||||||
"displayName": "Display name",
|
"displayName": "Display name",
|
||||||
"distributedRightCategoryTitle": "Monitor",
|
"distributedRightCategoryTitle": "Monitor",
|
||||||
"distributedStatusHeaderText": "Real-time, real-device coverage",
|
"distributedStatusHeaderText": "Real-time, real-device coverage",
|
||||||
@@ -395,6 +384,8 @@
|
|||||||
"distributedUptimeStatusUptLogo": "Upt Logo",
|
"distributedUptimeStatusUptLogo": "Upt Logo",
|
||||||
"dockerContainerMonitoring": "Docker container monitoring",
|
"dockerContainerMonitoring": "Docker container monitoring",
|
||||||
"dockerContainerMonitoringDescription": "Check whether your Docker container is running or not.",
|
"dockerContainerMonitoringDescription": "Check whether your Docker container is running or not.",
|
||||||
|
"DragandDrop": "drag and drop",
|
||||||
|
"drops": "Drops",
|
||||||
"duration": "Duration",
|
"duration": "Duration",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"editing": "Editing...",
|
"editing": "Editing...",
|
||||||
@@ -417,6 +408,7 @@
|
|||||||
"validationErrors": "Validation errors"
|
"validationErrors": "Validation errors"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"EmailDescriptionText": "This is your current email address — it cannot be changed.",
|
||||||
"emailSent": "Email sent successfully",
|
"emailSent": "Email sent successfully",
|
||||||
"errorInvalidFieldId": "Invalid field ID provided",
|
"errorInvalidFieldId": "Invalid field ID provided",
|
||||||
"errorInvalidTypeId": "Invalid notification type provided",
|
"errorInvalidTypeId": "Invalid notification type provided",
|
||||||
@@ -434,6 +426,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"errors": "Errors",
|
||||||
|
"errorsIn": "Errors In",
|
||||||
|
"errorsOut": "Errors Out",
|
||||||
"expectedValue": "Expected value",
|
"expectedValue": "Expected value",
|
||||||
"export": {
|
"export": {
|
||||||
"failed": "Failed to export monitors",
|
"failed": "Failed to export monitors",
|
||||||
@@ -442,9 +437,12 @@
|
|||||||
},
|
},
|
||||||
"failedToSendEmail": "Failed to send email",
|
"failedToSendEmail": "Failed to send email",
|
||||||
"features": "Features",
|
"features": "Features",
|
||||||
|
"FirstName": "First name",
|
||||||
"frequency": "Frequency",
|
"frequency": "Frequency",
|
||||||
"friendlyNameInput": "Friendly name",
|
"friendlyNameInput": "Friendly name",
|
||||||
"friendlyNamePlaceholder": "Maintenance at __ : __ for ___ minutes",
|
"friendlyNamePlaceholder": "Maintenance at __ : __ for ___ minutes",
|
||||||
|
"gameServerMonitoring": "Game Server Monitoring",
|
||||||
|
"gameServerMonitoringDescription": "Check whether your game server is running or not",
|
||||||
"gb": "GB",
|
"gb": "GB",
|
||||||
"general": {
|
"general": {
|
||||||
"noOptionsFound": "No {{unit}} found"
|
"noOptionsFound": "No {{unit}} found"
|
||||||
@@ -520,6 +518,7 @@
|
|||||||
"invalidFileFormat": "Unsupported file format!",
|
"invalidFileFormat": "Unsupported file format!",
|
||||||
"invalidFileSize": "File size is too large!",
|
"invalidFileSize": "File size is too large!",
|
||||||
"inviteNoTokenFound": "No invite token found",
|
"inviteNoTokenFound": "No invite token found",
|
||||||
|
"LastName": "Last name",
|
||||||
"loginHere": "Login here",
|
"loginHere": "Login here",
|
||||||
"logsPage": {
|
"logsPage": {
|
||||||
"description": "System logs - last 1000 lines",
|
"description": "System logs - last 1000 lines",
|
||||||
@@ -571,6 +570,7 @@
|
|||||||
"regexPlaceholder": "^(success|ok)$",
|
"regexPlaceholder": "^(success|ok)$",
|
||||||
"text": "Match Method"
|
"text": "Match Method"
|
||||||
},
|
},
|
||||||
|
"MaxSize": "Maximum Size",
|
||||||
"mb": "MB",
|
"mb": "MB",
|
||||||
"mem": "Mem",
|
"mem": "Mem",
|
||||||
"memory": "Memory",
|
"memory": "Memory",
|
||||||
@@ -632,6 +632,11 @@
|
|||||||
"namePlaceholder": "My Container",
|
"namePlaceholder": "My Container",
|
||||||
"placeholder": "abcd1234"
|
"placeholder": "abcd1234"
|
||||||
},
|
},
|
||||||
|
"game": {
|
||||||
|
"label": "URL to monitor",
|
||||||
|
"namePlaceholder": "localhost:5173",
|
||||||
|
"placeholder": "localhost"
|
||||||
|
},
|
||||||
"http": {
|
"http": {
|
||||||
"label": "URL to monitor",
|
"label": "URL to monitor",
|
||||||
"namePlaceholder": "Google",
|
"namePlaceholder": "Google",
|
||||||
@@ -646,16 +651,14 @@
|
|||||||
"label": "URL to monitor",
|
"label": "URL to monitor",
|
||||||
"namePlaceholder": "Localhost:5173",
|
"namePlaceholder": "Localhost:5173",
|
||||||
"placeholder": "localhost"
|
"placeholder": "localhost"
|
||||||
},
|
|
||||||
"game": {
|
|
||||||
"label": "URL to monitor",
|
|
||||||
"namePlaceholder": "localhost:5173",
|
|
||||||
"placeholder": "localhost"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ms": "ms",
|
"ms": "ms",
|
||||||
"navControls": "Controls",
|
"navControls": "Controls",
|
||||||
"network": "Network",
|
"network": "Network",
|
||||||
|
"networkDrops": "Network Drops",
|
||||||
|
"networkErrors": "Network Errors",
|
||||||
|
"networkInterface": "Network Interface",
|
||||||
"nextWindow": "Next window",
|
"nextWindow": "Next window",
|
||||||
"noNetworkStatsAvailable": "No network stats available.",
|
"noNetworkStatsAvailable": "No network stats available.",
|
||||||
"notFoundButton": "Go to the main dashboard",
|
"notFoundButton": "Go to the main dashboard",
|
||||||
@@ -739,6 +742,10 @@
|
|||||||
"notifySMS": "Notify via SMS (coming soon)",
|
"notifySMS": "Notify via SMS (coming soon)",
|
||||||
"now": "Now",
|
"now": "Now",
|
||||||
"os": "OS",
|
"os": "OS",
|
||||||
|
"packetsPerSecond": "Packets per second",
|
||||||
|
"packetsReceived": "Packets Received",
|
||||||
|
"packetsReceivedRate": "Packets Received Rate",
|
||||||
|
"packetsSent": "Packets Sent",
|
||||||
"pageSpeed": {
|
"pageSpeed": {
|
||||||
"fallback": {
|
"fallback": {
|
||||||
"actionButton": "Let's create your first PageSpeed monitor!",
|
"actionButton": "Let's create your first PageSpeed monitor!",
|
||||||
@@ -769,17 +776,12 @@
|
|||||||
"passwordRequirements": "New password must contain at least 8 characters and must have at least one uppercase letter, one lowercase letter, one number and one special character.",
|
"passwordRequirements": "New password must contain at least 8 characters and must have at least one uppercase letter, one lowercase letter, one number and one special character.",
|
||||||
"saving": "Saving..."
|
"saving": "Saving..."
|
||||||
},
|
},
|
||||||
"packetsPerSecond": "Packets per second",
|
|
||||||
"packetsReceived": "Packets Received",
|
|
||||||
"packetsReceivedRate": "Packets Received Rate",
|
|
||||||
"packetsSent": "Packets Sent",
|
|
||||||
"pause": "Pause",
|
"pause": "Pause",
|
||||||
|
"PhotoDescriptionText": "This photo will be displayed in your profile page.",
|
||||||
"pingMonitoring": "Ping monitoring",
|
"pingMonitoring": "Ping monitoring",
|
||||||
"pingMonitoringDescription": "Check whether your server is available or not.",
|
"pingMonitoringDescription": "Check whether your server is available or not.",
|
||||||
"portMonitoring": "Port monitoring",
|
"portMonitoring": "Port monitoring",
|
||||||
"portMonitoringDescription": "Check whether your port is open or not.",
|
"portMonitoringDescription": "Check whether your port is open or not.",
|
||||||
"gameServerMonitoring": "Game Server Monitoring",
|
|
||||||
"gameServerMonitoringDescription": "Check whether your game server is running or not",
|
|
||||||
"portToMonitor": "Port to monitor",
|
"portToMonitor": "Port to monitor",
|
||||||
"publicLink": "Public link",
|
"publicLink": "Public link",
|
||||||
"publicURL": "Public URL",
|
"publicURL": "Public URL",
|
||||||
@@ -814,6 +816,7 @@
|
|||||||
"refreshButton": "Refresh",
|
"refreshButton": "Refresh",
|
||||||
"title": "Queue"
|
"title": "Queue"
|
||||||
},
|
},
|
||||||
|
"rate": "Rate",
|
||||||
"remove": "Remove",
|
"remove": "Remove",
|
||||||
"removeLogo": "Remove Logo",
|
"removeLogo": "Remove Logo",
|
||||||
"repeat": "Repeat",
|
"repeat": "Repeat",
|
||||||
@@ -829,6 +832,7 @@
|
|||||||
},
|
},
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"selectAll": "Select all",
|
"selectAll": "Select all",
|
||||||
|
"selectInterface": "Select Interface",
|
||||||
"sendTestNotifications": "Send test notifications",
|
"sendTestNotifications": "Send test notifications",
|
||||||
"seperateEmails": "You can separate multiple emails with a comma",
|
"seperateEmails": "You can separate multiple emails with a comma",
|
||||||
"settingsAppearance": "Appearance",
|
"settingsAppearance": "Appearance",
|
||||||
@@ -873,6 +877,10 @@
|
|||||||
"title": "Email",
|
"title": "Email",
|
||||||
"toastEmailRequiredFieldsError": "Email address, host, port and password are required"
|
"toastEmailRequiredFieldsError": "Email address, host, port and password are required"
|
||||||
},
|
},
|
||||||
|
"globalThresholds": {
|
||||||
|
"description": "Configure global CPU, Memory, Disk, and Temperature thresholds. If a value is provided, it will automatically be enabled for monitoring.",
|
||||||
|
"title": "Global Thresholds"
|
||||||
|
},
|
||||||
"pageSpeedSettings": {
|
"pageSpeedSettings": {
|
||||||
"description": "Enter your Google PageSpeed API key to enable Google PageSpeed monitoring. Click Reset to update the key.",
|
"description": "Enter your Google PageSpeed API key to enable Google PageSpeed monitoring. Click Reset to update the key.",
|
||||||
"labelApiKey": "PageSpeed API key",
|
"labelApiKey": "PageSpeed API key",
|
||||||
@@ -905,10 +913,6 @@
|
|||||||
"title": "Display timezone"
|
"title": "Display timezone"
|
||||||
},
|
},
|
||||||
"title": "Settings",
|
"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": {
|
"uiSettings": {
|
||||||
"description": "Switch between light and dark mode, or change user interface language.",
|
"description": "Switch between light and dark mode, or change user interface language.",
|
||||||
"labelLanguage": "Language",
|
"labelLanguage": "Language",
|
||||||
@@ -985,6 +989,7 @@
|
|||||||
"statusPageStatusNotPublic": "This status page is not public.",
|
"statusPageStatusNotPublic": "This status page is not public.",
|
||||||
"statusPageStatusServiceStatus": "Service status",
|
"statusPageStatusServiceStatus": "Service status",
|
||||||
"submit": "Submit",
|
"submit": "Submit",
|
||||||
|
"SupportedFormats": "Supported formats",
|
||||||
"teamPanel": {
|
"teamPanel": {
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
@@ -1040,10 +1045,10 @@
|
|||||||
"uptimeCreateJsonPathQuery": "for query language documentation.",
|
"uptimeCreateJsonPathQuery": "for query language documentation.",
|
||||||
"uptimeGeneralInstructions": {
|
"uptimeGeneralInstructions": {
|
||||||
"docker": "Enter the Docker ID of your container. Docker IDs must be the full 64 char Docker ID. You can run docker inspect <short_id> to get the full container ID.",
|
"docker": "Enter the Docker ID of your container. Docker IDs must be the full 64 char Docker ID. You can run docker inspect <short_id> to get the full container ID.",
|
||||||
|
"game": "Enter the IP address or hostname and the port number to ping (e.g., 192.168.1.100 or example.com) and choose game type.",
|
||||||
"http": "Enter the URL or IP to monitor (e.g., https://example.com/ or 192.168.1.100) and add a clear display name that appears on the dashboard.",
|
"http": "Enter the URL or IP to monitor (e.g., https://example.com/ or 192.168.1.100) and add a clear display name that appears on the dashboard.",
|
||||||
"ping": "Enter the IP address or hostname to ping (e.g., 192.168.1.100 or example.com) and add a clear display name that appears on the dashboard.",
|
"ping": "Enter the IP address or hostname to ping (e.g., 192.168.1.100 or example.com) and add a clear display name that appears on the dashboard.",
|
||||||
"port": "Enter the URL or IP of the server, the port number and a clear display name that appears on the dashboard.",
|
"port": "Enter the URL or IP of the server, the port number and a clear display name that appears on the dashboard."
|
||||||
"game": "Enter the IP address or hostname and the port number to ping (e.g., 192.168.1.100 or example.com) and choose game type."
|
|
||||||
},
|
},
|
||||||
"uptimeMonitor": {
|
"uptimeMonitor": {
|
||||||
"fallback": {
|
"fallback": {
|
||||||
@@ -1066,5 +1071,6 @@
|
|||||||
"websiteMonitoring": "Website monitoring",
|
"websiteMonitoring": "Website monitoring",
|
||||||
"websiteMonitoringDescription": "Use HTTP(s) to monitor your website or API endpoint.",
|
"websiteMonitoringDescription": "Use HTTP(s) to monitor your website or API endpoint.",
|
||||||
"whenNewIncident": "When there is a new incident,",
|
"whenNewIncident": "When there is a new incident,",
|
||||||
"window": "window"
|
"window": "window",
|
||||||
|
"YourPhoto": "Profile photo"
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,7 @@ import { execSync } from "child_process";
|
|||||||
|
|
||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({ mode }) => {
|
||||||
const env = loadEnv(mode, process.cwd(), "");
|
const env = loadEnv(mode, process.cwd(), "");
|
||||||
let version = "3.0-beta";
|
let version = "3.1-beta";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
base: "/",
|
base: "/",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ cd "$(dirname "$0")"
|
|||||||
cd ../..
|
cd ../..
|
||||||
|
|
||||||
# Define service names and their corresponding Dockerfiles in parallel arrays
|
# Define service names and their corresponding Dockerfiles in parallel arrays
|
||||||
services=("mono_mongo" "mono_redis" "mono_server")
|
services=("mono_mongo" "mono_server")
|
||||||
dockerfiles=(
|
dockerfiles=(
|
||||||
"./docker/dist-mono/mongoDB.Dockerfile"
|
"./docker/dist-mono/mongoDB.Dockerfile"
|
||||||
"./docker/dist-mono/server.Dockerfile"
|
"./docker/dist-mono/server.Dockerfile"
|
||||||
|
|||||||
Generated
+155
-35
@@ -21,6 +21,7 @@
|
|||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"express-rate-limit": "8.0.1",
|
"express-rate-limit": "8.0.1",
|
||||||
"gamedig": "^5.3.1",
|
"gamedig": "^5.3.1",
|
||||||
|
"got": "14.4.7",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
"helmet": "^8.0.0",
|
"helmet": "^8.0.0",
|
||||||
"ioredis": "^5.4.2",
|
"ioredis": "^5.4.2",
|
||||||
@@ -1137,6 +1138,12 @@
|
|||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/@sec-ant/readable-stream": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@sideway/address": {
|
"node_modules/@sideway/address": {
|
||||||
"version": "4.1.5",
|
"version": "4.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
|
||||||
@@ -1159,12 +1166,12 @@
|
|||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
"node_modules/@sindresorhus/is": {
|
"node_modules/@sindresorhus/is": {
|
||||||
"version": "5.6.0",
|
"version": "7.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.0.2.tgz",
|
||||||
"integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==",
|
"integrity": "sha512-d9xRovfKNz1SKieM0qJdO+PQonjnnIfSNWfHYnBSJ9hkjm0ZPw6HlxscDXYstp3z+7V2GOFHc+J0CYrYTjqCJw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.16"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sindresorhus/is?sponsor=1"
|
"url": "https://github.com/sindresorhus/is?sponsor=1"
|
||||||
@@ -1821,21 +1828,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cacheable-request": {
|
"node_modules/cacheable-request": {
|
||||||
"version": "10.2.14",
|
"version": "12.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz",
|
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-12.0.1.tgz",
|
||||||
"integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==",
|
"integrity": "sha512-Yo9wGIQUaAfIbk+qY0X4cDQgCosecfBe3V9NSyeY4qPC2SAkbCS4Xj79VP8WOzitpJUZKc/wsRCYF5ariDIwkg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/http-cache-semantics": "^4.0.2",
|
"@types/http-cache-semantics": "^4.0.4",
|
||||||
"get-stream": "^6.0.1",
|
"get-stream": "^9.0.1",
|
||||||
"http-cache-semantics": "^4.1.1",
|
"http-cache-semantics": "^4.1.1",
|
||||||
"keyv": "^4.5.3",
|
"keyv": "^4.5.4",
|
||||||
"mimic-response": "^4.0.0",
|
"mimic-response": "^4.0.0",
|
||||||
"normalize-url": "^8.0.0",
|
"normalize-url": "^8.0.1",
|
||||||
"responselike": "^3.0.0"
|
"responselike": "^3.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.16"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/call-bind-apply-helpers": {
|
"node_modules/call-bind-apply-helpers": {
|
||||||
@@ -3750,12 +3757,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/form-data-encoder": {
|
"node_modules/form-data-encoder": {
|
||||||
"version": "2.1.4",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.1.0.tgz",
|
||||||
"integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==",
|
"integrity": "sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 14.17"
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/formdata-polyfill": {
|
"node_modules/formdata-polyfill": {
|
||||||
@@ -3840,6 +3847,82 @@
|
|||||||
"node": ">=16.20.0"
|
"node": ">=16.20.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/gamedig/node_modules/@sindresorhus/is": {
|
||||||
|
"version": "5.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz",
|
||||||
|
"integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sindresorhus/is?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/gamedig/node_modules/cacheable-request": {
|
||||||
|
"version": "10.2.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz",
|
||||||
|
"integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/http-cache-semantics": "^4.0.2",
|
||||||
|
"get-stream": "^6.0.1",
|
||||||
|
"http-cache-semantics": "^4.1.1",
|
||||||
|
"keyv": "^4.5.3",
|
||||||
|
"mimic-response": "^4.0.0",
|
||||||
|
"normalize-url": "^8.0.0",
|
||||||
|
"responselike": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/gamedig/node_modules/form-data-encoder": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/gamedig/node_modules/get-stream": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/gamedig/node_modules/got": {
|
||||||
|
"version": "13.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz",
|
||||||
|
"integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@sindresorhus/is": "^5.2.0",
|
||||||
|
"@szmarczak/http-timer": "^5.0.1",
|
||||||
|
"cacheable-lookup": "^7.0.0",
|
||||||
|
"cacheable-request": "^10.2.8",
|
||||||
|
"decompress-response": "^6.0.0",
|
||||||
|
"form-data-encoder": "^2.1.2",
|
||||||
|
"get-stream": "^6.0.1",
|
||||||
|
"http2-wrapper": "^2.1.10",
|
||||||
|
"lowercase-keys": "^3.0.0",
|
||||||
|
"p-cancelable": "^3.0.0",
|
||||||
|
"responselike": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sindresorhus/got?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/gamedig/node_modules/iconv-lite": {
|
"node_modules/gamedig/node_modules/iconv-lite": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
@@ -3852,6 +3935,15 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/gamedig/node_modules/p-cancelable": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.20"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/gaxios": {
|
"node_modules/gaxios": {
|
||||||
"version": "6.7.1",
|
"version": "6.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz",
|
||||||
@@ -3930,12 +4022,28 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/get-stream": {
|
"node_modules/get-stream": {
|
||||||
"version": "6.0.1",
|
"version": "9.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz",
|
||||||
"integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
|
"integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@sec-ant/readable-stream": "^0.4.1",
|
||||||
|
"is-stream": "^4.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/get-stream/node_modules/is-stream": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
@@ -4024,28 +4132,40 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/got": {
|
"node_modules/got": {
|
||||||
"version": "13.0.0",
|
"version": "14.4.7",
|
||||||
"resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/got/-/got-14.4.7.tgz",
|
||||||
"integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==",
|
"integrity": "sha512-DI8zV1231tqiGzOiOzQWDhsBmncFW7oQDH6Zgy6pDPrqJuVZMtoSgPLLsBZQj8Jg4JFfwoOsDA8NGtLQLnIx2g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sindresorhus/is": "^5.2.0",
|
"@sindresorhus/is": "^7.0.1",
|
||||||
"@szmarczak/http-timer": "^5.0.1",
|
"@szmarczak/http-timer": "^5.0.1",
|
||||||
"cacheable-lookup": "^7.0.0",
|
"cacheable-lookup": "^7.0.0",
|
||||||
"cacheable-request": "^10.2.8",
|
"cacheable-request": "^12.0.1",
|
||||||
"decompress-response": "^6.0.0",
|
"decompress-response": "^6.0.0",
|
||||||
"form-data-encoder": "^2.1.2",
|
"form-data-encoder": "^4.0.2",
|
||||||
"get-stream": "^6.0.1",
|
"http2-wrapper": "^2.2.1",
|
||||||
"http2-wrapper": "^2.1.10",
|
|
||||||
"lowercase-keys": "^3.0.0",
|
"lowercase-keys": "^3.0.0",
|
||||||
"p-cancelable": "^3.0.0",
|
"p-cancelable": "^4.0.1",
|
||||||
"responselike": "^3.0.0"
|
"responselike": "^3.0.0",
|
||||||
|
"type-fest": "^4.26.1"
|
||||||
},
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sindresorhus/got?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/got/node_modules/type-fest": {
|
||||||
|
"version": "4.41.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
|
||||||
|
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
|
||||||
|
"license": "(MIT OR CC0-1.0)",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sindresorhus/got?sponsor=1"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/handlebars": {
|
"node_modules/handlebars": {
|
||||||
@@ -6350,12 +6470,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/p-cancelable": {
|
"node_modules/p-cancelable": {
|
||||||
"version": "3.0.0",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-4.0.1.tgz",
|
||||||
"integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==",
|
"integrity": "sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.20"
|
"node": ">=14.16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/p-limit": {
|
"node_modules/p-limit": {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"express-rate-limit": "8.0.1",
|
"express-rate-limit": "8.0.1",
|
||||||
"gamedig": "^5.3.1",
|
"gamedig": "^5.3.1",
|
||||||
|
"got": "14.4.7",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
"helmet": "^8.0.0",
|
"helmet": "^8.0.0",
|
||||||
"ioredis": "^5.4.2",
|
"ioredis": "^5.4.2",
|
||||||
|
|||||||
@@ -19,8 +19,10 @@ import MaintenanceWindowService from "../service/business/maintenanceWindowServi
|
|||||||
import MonitorService from "../service/business/monitorService.js";
|
import MonitorService from "../service/business/monitorService.js";
|
||||||
import papaparse from "papaparse";
|
import papaparse from "papaparse";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import got from "got";
|
||||||
import ping from "ping";
|
import ping from "ping";
|
||||||
import http from "http";
|
import http from "http";
|
||||||
|
import https from "https";
|
||||||
import Docker from "dockerode";
|
import Docker from "dockerode";
|
||||||
import net from "net";
|
import net from "net";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
@@ -32,6 +34,8 @@ import mjml2html from "mjml";
|
|||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import { games } from "gamedig";
|
import { games } from "gamedig";
|
||||||
|
import jmespath from "jmespath";
|
||||||
|
import { GameDig } from "gamedig";
|
||||||
|
|
||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
import { ObjectId } from "mongodb";
|
import { ObjectId } from "mongodb";
|
||||||
@@ -43,8 +47,6 @@ import { ParseBoolean } from "../utils/utils.js";
|
|||||||
|
|
||||||
// Models
|
// Models
|
||||||
import Check from "../db/models/Check.js";
|
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 Monitor from "../db/models/Monitor.js";
|
||||||
import User from "../db/models/User.js";
|
import User from "../db/models/User.js";
|
||||||
import InviteToken from "../db/models/InviteToken.js";
|
import InviteToken from "../db/models/InviteToken.js";
|
||||||
@@ -52,7 +54,6 @@ import StatusPage from "../db/models/StatusPage.js";
|
|||||||
import Team from "../db/models/Team.js";
|
import Team from "../db/models/Team.js";
|
||||||
import MaintenanceWindow from "../db/models/MaintenanceWindow.js";
|
import MaintenanceWindow from "../db/models/MaintenanceWindow.js";
|
||||||
import MonitorStats from "../db/models/MonitorStats.js";
|
import MonitorStats from "../db/models/MonitorStats.js";
|
||||||
import NetworkCheck from "../db/models/NetworkCheck.js";
|
|
||||||
import Notification from "../db/models/Notification.js";
|
import Notification from "../db/models/Notification.js";
|
||||||
import RecoveryToken from "../db/models/RecoveryToken.js";
|
import RecoveryToken from "../db/models/RecoveryToken.js";
|
||||||
import AppSettings from "../db/models/AppSettings.js";
|
import AppSettings from "../db/models/AppSettings.js";
|
||||||
@@ -61,12 +62,9 @@ import InviteModule from "../db/mongo/modules/inviteModule.js";
|
|||||||
import CheckModule from "../db/mongo/modules/checkModule.js";
|
import CheckModule from "../db/mongo/modules/checkModule.js";
|
||||||
import StatusPageModule from "../db/mongo/modules/statusPageModule.js";
|
import StatusPageModule from "../db/mongo/modules/statusPageModule.js";
|
||||||
import UserModule from "../db/mongo/modules/userModule.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 MaintenanceWindowModule from "../db/mongo/modules/maintenanceWindowModule.js";
|
||||||
import MonitorModule from "../db/mongo/modules/monitorModule.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 NotificationModule from "../db/mongo/modules/notificationModule.js";
|
||||||
import PageSpeedCheckModule from "../db/mongo/modules/pageSpeedCheckModule.js";
|
|
||||||
import RecoveryModule from "../db/mongo/modules/recoveryModule.js";
|
import RecoveryModule from "../db/mongo/modules/recoveryModule.js";
|
||||||
import SettingsModule from "../db/mongo/modules/settingsModule.js";
|
import SettingsModule from "../db/mongo/modules/settingsModule.js";
|
||||||
|
|
||||||
@@ -80,18 +78,15 @@ export const initializeServices = async ({ logger, envSettings, settingsService
|
|||||||
const stringService = new StringService(translationService);
|
const stringService = new StringService(translationService);
|
||||||
|
|
||||||
// Create DB
|
// Create DB
|
||||||
const checkModule = new CheckModule({ logger, Check, HardwareCheck, PageSpeedCheck, Monitor, User });
|
const checkModule = new CheckModule({ logger, Check, Monitor, User });
|
||||||
const inviteModule = new InviteModule({ InviteToken, crypto, stringService });
|
const inviteModule = new InviteModule({ InviteToken, crypto, stringService });
|
||||||
const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, stringService });
|
const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, stringService });
|
||||||
const userModule = new UserModule({ User, Team, GenerateAvatarImage, ParseBoolean, stringService });
|
const userModule = new UserModule({ User, Team, GenerateAvatarImage, ParseBoolean, stringService });
|
||||||
const hardwareCheckModule = new HardwareCheckModule({ HardwareCheck, Monitor, logger });
|
|
||||||
const maintenanceWindowModule = new MaintenanceWindowModule({ MaintenanceWindow });
|
const maintenanceWindowModule = new MaintenanceWindowModule({ MaintenanceWindow });
|
||||||
const monitorModule = new MonitorModule({
|
const monitorModule = new MonitorModule({
|
||||||
Monitor,
|
Monitor,
|
||||||
MonitorStats,
|
MonitorStats,
|
||||||
Check,
|
Check,
|
||||||
PageSpeedCheck,
|
|
||||||
HardwareCheck,
|
|
||||||
stringService,
|
stringService,
|
||||||
fs,
|
fs,
|
||||||
path,
|
path,
|
||||||
@@ -100,9 +95,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService
|
|||||||
NormalizeData,
|
NormalizeData,
|
||||||
NormalizeDataUptimeDetails,
|
NormalizeDataUptimeDetails,
|
||||||
});
|
});
|
||||||
const networkCheckModule = new NetworkCheckModule({ NetworkCheck });
|
|
||||||
const notificationModule = new NotificationModule({ Notification, Monitor });
|
const notificationModule = new NotificationModule({ Notification, Monitor });
|
||||||
const pageSpeedCheckModule = new PageSpeedCheckModule({ PageSpeedCheck });
|
|
||||||
const recoveryModule = new RecoveryModule({ User, RecoveryToken, crypto, stringService });
|
const recoveryModule = new RecoveryModule({ User, RecoveryToken, crypto, stringService });
|
||||||
const settingsModule = new SettingsModule({ AppSettings });
|
const settingsModule = new SettingsModule({ AppSettings });
|
||||||
|
|
||||||
@@ -113,19 +106,29 @@ export const initializeServices = async ({ logger, envSettings, settingsService
|
|||||||
inviteModule,
|
inviteModule,
|
||||||
statusPageModule,
|
statusPageModule,
|
||||||
userModule,
|
userModule,
|
||||||
hardwareCheckModule,
|
|
||||||
maintenanceWindowModule,
|
maintenanceWindowModule,
|
||||||
monitorModule,
|
monitorModule,
|
||||||
networkCheckModule,
|
|
||||||
notificationModule,
|
notificationModule,
|
||||||
pageSpeedCheckModule,
|
|
||||||
recoveryModule,
|
recoveryModule,
|
||||||
settingsModule,
|
settingsModule,
|
||||||
});
|
});
|
||||||
|
|
||||||
await db.connect();
|
await db.connect();
|
||||||
|
|
||||||
const networkService = new NetworkService(axios, ping, logger, http, Docker, net, stringService, settingsService);
|
const networkService = new NetworkService({
|
||||||
|
axios,
|
||||||
|
got,
|
||||||
|
https,
|
||||||
|
jmespath,
|
||||||
|
GameDig,
|
||||||
|
ping,
|
||||||
|
logger,
|
||||||
|
http,
|
||||||
|
Docker,
|
||||||
|
net,
|
||||||
|
stringService,
|
||||||
|
settingsService,
|
||||||
|
});
|
||||||
const emailService = new EmailService(settingsService, fs, path, compile, mjml2html, nodemailer, logger);
|
const emailService = new EmailService(settingsService, fs, path, compile, mjml2html, nodemailer, logger);
|
||||||
const bufferService = new BufferService({ db, logger, envSettings });
|
const bufferService = new BufferService({ db, logger, envSettings });
|
||||||
const statusService = new StatusService({ db, logger, buffer: bufferService });
|
const statusService = new StatusService({ db, logger, buffer: bufferService });
|
||||||
|
|||||||
+166
-89
@@ -1,99 +1,176 @@
|
|||||||
import mongoose from "mongoose";
|
import mongoose from "mongoose";
|
||||||
|
|
||||||
const BaseCheckSchema = mongoose.Schema({
|
const cpuSchema = mongoose.Schema({
|
||||||
/**
|
physical_core: { type: Number, default: 0 },
|
||||||
* Reference to the associated Monitor document.
|
logical_core: { type: Number, default: 0 },
|
||||||
*
|
frequency: { type: Number, default: 0 },
|
||||||
* @type {mongoose.Schema.Types.ObjectId}
|
temperature: { type: [Number], default: [] },
|
||||||
*/
|
free_percent: { type: Number, default: 0 },
|
||||||
monitorId: {
|
usage_percent: { type: Number, default: 0 },
|
||||||
type: mongoose.Schema.Types.ObjectId,
|
|
||||||
ref: "Monitor",
|
|
||||||
immutable: true,
|
|
||||||
index: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
teamId: {
|
|
||||||
type: mongoose.Schema.Types.ObjectId,
|
|
||||||
ref: "Team",
|
|
||||||
immutable: true,
|
|
||||||
index: true,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Status of the check (true for up, false for down).
|
|
||||||
*
|
|
||||||
* @type {Boolean}
|
|
||||||
*/
|
|
||||||
status: {
|
|
||||||
type: Boolean,
|
|
||||||
index: true,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Response time of the check in milliseconds.
|
|
||||||
*
|
|
||||||
* @type {Number}
|
|
||||||
*/
|
|
||||||
responseTime: {
|
|
||||||
type: Number,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* HTTP status code received during the check.
|
|
||||||
*
|
|
||||||
* @type {Number}
|
|
||||||
*/
|
|
||||||
statusCode: {
|
|
||||||
type: Number,
|
|
||||||
index: true,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Message or description of the check result.
|
|
||||||
*
|
|
||||||
* @type {String}
|
|
||||||
*/
|
|
||||||
message: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Expiry date of the check, auto-calculated to expire after 30 days.
|
|
||||||
*
|
|
||||||
* @type {Date}
|
|
||||||
*/
|
|
||||||
|
|
||||||
expiry: {
|
|
||||||
type: Date,
|
|
||||||
default: Date.now,
|
|
||||||
expires: 60 * 60 * 24 * 30, // 30 days
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Acknowledgment of the check.
|
|
||||||
*
|
|
||||||
* @type {Boolean}
|
|
||||||
*/
|
|
||||||
ack: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Resolution date of the check (when the check was resolved).
|
|
||||||
*
|
|
||||||
* @type {Date}
|
|
||||||
*/
|
|
||||||
ackAt: {
|
|
||||||
type: Date,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
const memorySchema = mongoose.Schema({
|
||||||
* Check Schema for MongoDB collection.
|
total_bytes: { type: Number, default: 0 },
|
||||||
*
|
available_bytes: { type: Number, default: 0 },
|
||||||
* Represents a check associated with a monitor, storing information
|
used_bytes: { type: Number, default: 0 },
|
||||||
* about the status and response of a particular check event.
|
usage_percent: { type: Number, default: 0 },
|
||||||
*/
|
});
|
||||||
const CheckSchema = mongoose.Schema({ ...BaseCheckSchema.obj }, { timestamps: true });
|
|
||||||
|
const diskSchema = mongoose.Schema({
|
||||||
|
read_speed_bytes: { type: Number, default: 0 },
|
||||||
|
write_speed_bytes: { type: Number, default: 0 },
|
||||||
|
total_bytes: { type: Number, default: 0 },
|
||||||
|
free_bytes: { type: Number, default: 0 },
|
||||||
|
usage_percent: { type: Number, default: 0 },
|
||||||
|
});
|
||||||
|
|
||||||
|
const hostSchema = mongoose.Schema({
|
||||||
|
os: { type: String, default: "" },
|
||||||
|
platform: { type: String, default: "" },
|
||||||
|
kernel_version: { type: String, default: "" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const errorSchema = mongoose.Schema({
|
||||||
|
metric: { type: [String], default: [] },
|
||||||
|
err: { type: String, default: "" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const captureSchema = mongoose.Schema({
|
||||||
|
version: { type: String, default: "" },
|
||||||
|
mode: { type: String, default: "" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const networkInterfaceSchema = mongoose.Schema({
|
||||||
|
name: { type: String },
|
||||||
|
bytes_sent: { type: Number, default: 0 },
|
||||||
|
bytes_recv: { type: Number, default: 0 },
|
||||||
|
packets_sent: { type: Number, default: 0 },
|
||||||
|
packets_recv: { type: Number, default: 0 },
|
||||||
|
err_in: { type: Number, default: 0 },
|
||||||
|
err_out: { type: Number, default: 0 },
|
||||||
|
drop_in: { type: Number, default: 0 },
|
||||||
|
drop_out: { type: Number, default: 0 },
|
||||||
|
fifo_in: { type: Number, default: 0 },
|
||||||
|
fifo_out: { type: Number, default: 0 },
|
||||||
|
});
|
||||||
|
|
||||||
|
const CheckSchema = new mongoose.Schema(
|
||||||
|
{
|
||||||
|
// Common fields
|
||||||
|
monitorId: {
|
||||||
|
type: mongoose.Schema.Types.ObjectId,
|
||||||
|
ref: "Monitor",
|
||||||
|
immutable: true,
|
||||||
|
index: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
teamId: {
|
||||||
|
type: mongoose.Schema.Types.ObjectId,
|
||||||
|
ref: "Team",
|
||||||
|
immutable: true,
|
||||||
|
index: true,
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
enum: ["http", "hardware", "pagespeed", "distributed"],
|
||||||
|
required: true,
|
||||||
|
index: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
status: {
|
||||||
|
type: Boolean,
|
||||||
|
index: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
responseTime: {
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
|
||||||
|
timings: {
|
||||||
|
type: Object,
|
||||||
|
default: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
statusCode: {
|
||||||
|
type: Number,
|
||||||
|
index: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
message: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
expiry: {
|
||||||
|
type: Date,
|
||||||
|
default: Date.now,
|
||||||
|
expires: 60 * 60 * 24 * 30, // 30 days
|
||||||
|
},
|
||||||
|
|
||||||
|
ack: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
ackAt: {
|
||||||
|
type: Date,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Hardware fields
|
||||||
|
cpu: {
|
||||||
|
type: cpuSchema,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
memory: {
|
||||||
|
type: memorySchema,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
disk: {
|
||||||
|
type: [diskSchema],
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
host: {
|
||||||
|
type: hostSchema,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
type: [errorSchema],
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
|
||||||
|
capture: {
|
||||||
|
type: captureSchema,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
|
||||||
|
net: {
|
||||||
|
type: [networkInterfaceSchema],
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
|
||||||
|
// PageSpeed fields
|
||||||
|
accessibility: {
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
bestPractices: {
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
seo: {
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
performance: {
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
audits: {
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ timestamps: true }
|
||||||
|
);
|
||||||
|
|
||||||
CheckSchema.index({ updatedAt: 1 });
|
CheckSchema.index({ updatedAt: 1 });
|
||||||
CheckSchema.index({ monitorId: 1, updatedAt: 1 });
|
CheckSchema.index({ monitorId: 1, updatedAt: 1 });
|
||||||
CheckSchema.index({ monitorId: 1, updatedAt: -1 });
|
CheckSchema.index({ monitorId: 1, updatedAt: -1 });
|
||||||
CheckSchema.index({ teamId: 1, updatedAt: -1 });
|
CheckSchema.index({ teamId: 1, updatedAt: -1 });
|
||||||
|
|
||||||
export default mongoose.model("Check", CheckSchema);
|
export default mongoose.model("Check", CheckSchema);
|
||||||
export { BaseCheckSchema };
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
import mongoose from "mongoose";
|
|
||||||
import { BaseCheckSchema } from "./Check.js";
|
|
||||||
const cpuSchema = mongoose.Schema({
|
|
||||||
physical_core: { type: Number, default: 0 },
|
|
||||||
logical_core: { type: Number, default: 0 },
|
|
||||||
frequency: { type: Number, default: 0 },
|
|
||||||
temperature: { type: [Number], default: [] },
|
|
||||||
free_percent: { type: Number, default: 0 },
|
|
||||||
usage_percent: { type: Number, default: 0 },
|
|
||||||
});
|
|
||||||
|
|
||||||
const memorySchema = mongoose.Schema({
|
|
||||||
total_bytes: { type: Number, default: 0 },
|
|
||||||
available_bytes: { type: Number, default: 0 },
|
|
||||||
used_bytes: { type: Number, default: 0 },
|
|
||||||
usage_percent: { type: Number, default: 0 },
|
|
||||||
});
|
|
||||||
|
|
||||||
const diskSchema = mongoose.Schema({
|
|
||||||
read_speed_bytes: { type: Number, default: 0 },
|
|
||||||
write_speed_bytes: { type: Number, default: 0 },
|
|
||||||
total_bytes: { type: Number, default: 0 },
|
|
||||||
free_bytes: { type: Number, default: 0 },
|
|
||||||
usage_percent: { type: Number, default: 0 },
|
|
||||||
});
|
|
||||||
|
|
||||||
const hostSchema = mongoose.Schema({
|
|
||||||
os: { type: String, default: "" },
|
|
||||||
platform: { type: String, default: "" },
|
|
||||||
kernel_version: { type: String, default: "" },
|
|
||||||
});
|
|
||||||
|
|
||||||
const errorSchema = mongoose.Schema({
|
|
||||||
metric: { type: [String], default: [] },
|
|
||||||
err: { type: String, default: "" },
|
|
||||||
});
|
|
||||||
|
|
||||||
const captureSchema = mongoose.Schema({
|
|
||||||
version: { type: String, default: "" },
|
|
||||||
mode: { type: String, default: "" },
|
|
||||||
});
|
|
||||||
|
|
||||||
const networkInterfaceSchema = mongoose.Schema({
|
|
||||||
name: { type: String },
|
|
||||||
bytes_sent: { type: Number, default: 0 },
|
|
||||||
bytes_recv: { type: Number, default: 0 },
|
|
||||||
packets_sent: { type: Number, default: 0 },
|
|
||||||
packets_recv: { type: Number, default: 0 },
|
|
||||||
err_in: { type: Number, default: 0 },
|
|
||||||
err_out: { type: Number, default: 0 },
|
|
||||||
drop_in: { type: Number, default: 0 },
|
|
||||||
drop_out: { type: Number, default: 0 },
|
|
||||||
fifo_in: { type: Number, default: 0 },
|
|
||||||
fifo_out: { type: Number, default: 0 },
|
|
||||||
});
|
|
||||||
|
|
||||||
const HardwareCheckSchema = mongoose.Schema(
|
|
||||||
{
|
|
||||||
...BaseCheckSchema.obj,
|
|
||||||
cpu: {
|
|
||||||
type: cpuSchema,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
memory: {
|
|
||||||
type: memorySchema,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
disk: {
|
|
||||||
type: [diskSchema],
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
host: {
|
|
||||||
type: hostSchema,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
|
|
||||||
errors: {
|
|
||||||
type: [errorSchema],
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
|
|
||||||
capture: {
|
|
||||||
type: captureSchema,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
|
|
||||||
net: {
|
|
||||||
type: [networkInterfaceSchema],
|
|
||||||
default: () => [],
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ timestamps: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
HardwareCheckSchema.index({ createdAt: 1 });
|
|
||||||
HardwareCheckSchema.index({ monitorId: 1, createdAt: 1 });
|
|
||||||
HardwareCheckSchema.index({ monitorId: 1, createdAt: -1 });
|
|
||||||
|
|
||||||
export default mongoose.model("HardwareCheck", HardwareCheckSchema);
|
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
import mongoose from "mongoose";
|
import mongoose from "mongoose";
|
||||||
import HardwareCheck from "./HardwareCheck.js";
|
|
||||||
import PageSpeedCheck from "./PageSpeedCheck.js";
|
|
||||||
import Check from "./Check.js";
|
import Check from "./Check.js";
|
||||||
import MonitorStats from "./MonitorStats.js";
|
import MonitorStats from "./MonitorStats.js";
|
||||||
import StatusPage from "./StatusPage.js";
|
import StatusPage from "./StatusPage.js";
|
||||||
@@ -30,6 +28,18 @@ const MonitorSchema = mongoose.Schema(
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: undefined,
|
default: undefined,
|
||||||
},
|
},
|
||||||
|
statusWindow: {
|
||||||
|
type: [Boolean],
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
statusWindowSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 5,
|
||||||
|
},
|
||||||
|
statusWindowThreshold: {
|
||||||
|
type: Number,
|
||||||
|
default: 0.6,
|
||||||
|
},
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
@@ -133,13 +143,7 @@ MonitorSchema.pre("findOneAndDelete", async function (next) {
|
|||||||
throw new Error("Monitor not found");
|
throw new Error("Monitor not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doc?.type === "pagespeed") {
|
await Check.deleteMany({ monitorId: doc._id });
|
||||||
await PageSpeedCheck.deleteMany({ monitorId: doc._id });
|
|
||||||
} else if (doc?.type === "hardware") {
|
|
||||||
await HardwareCheck.deleteMany({ monitorId: doc._id });
|
|
||||||
} else {
|
|
||||||
await Check.deleteMany({ monitorId: doc._id });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deal with status pages
|
// Deal with status pages
|
||||||
await StatusPage.updateMany({ monitors: doc?._id }, { $pull: { monitors: doc?._id } });
|
await StatusPage.updateMany({ monitors: doc?._id }, { $pull: { monitors: doc?._id } });
|
||||||
@@ -156,13 +160,7 @@ MonitorSchema.pre("deleteMany", async function (next) {
|
|||||||
const monitors = await this.model.find(filter).select(["_id", "type"]).lean();
|
const monitors = await this.model.find(filter).select(["_id", "type"]).lean();
|
||||||
|
|
||||||
for (const monitor of monitors) {
|
for (const monitor of monitors) {
|
||||||
if (monitor.type === "pagespeed") {
|
await Check.deleteMany({ monitorId: monitor._id });
|
||||||
await PageSpeedCheck.deleteMany({ monitorId: monitor._id });
|
|
||||||
} else if (monitor.type === "hardware") {
|
|
||||||
await HardwareCheck.deleteMany({ monitorId: monitor._id });
|
|
||||||
} else {
|
|
||||||
await Check.deleteMany({ monitorId: monitor._id });
|
|
||||||
}
|
|
||||||
await StatusPage.updateMany({ monitors: monitor._id }, { $pull: { monitors: monitor._id } });
|
await StatusPage.updateMany({ monitors: monitor._id }, { $pull: { monitors: monitor._id } });
|
||||||
await MonitorStats.deleteMany({ monitorId: monitor._id.toString() });
|
await MonitorStats.deleteMany({ monitorId: monitor._id.toString() });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,10 +40,6 @@ const MonitorStatsSchema = new mongoose.Schema(
|
|||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
uptBurnt: {
|
|
||||||
type: mongoose.Schema.Types.Decimal128,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{ timestamps: true }
|
{ timestamps: true }
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
import mongoose from "mongoose";
|
|
||||||
import { BaseCheckSchema } from "./Check.js";
|
|
||||||
|
|
||||||
const networkInterfaceSchema = mongoose.Schema({
|
|
||||||
name: { type: String, required: true },
|
|
||||||
bytes_sent: { type: Number, default: 0 },
|
|
||||||
bytes_recv: { type: Number, default: 0 },
|
|
||||||
packets_sent: { type: Number, default: 0 },
|
|
||||||
packets_recv: { type: Number, default: 0 },
|
|
||||||
err_in: { type: Number, default: 0 },
|
|
||||||
err_out: { type: Number, default: 0 },
|
|
||||||
drop_in: { type: Number, default: 0 },
|
|
||||||
drop_out: { type: Number, default: 0 },
|
|
||||||
fifo_in: { type: Number, default: 0 },
|
|
||||||
fifo_out: { type: Number, default: 0 },
|
|
||||||
});
|
|
||||||
|
|
||||||
const captureSchema = mongoose.Schema({
|
|
||||||
version: { type: String, default: "" },
|
|
||||||
mode: { type: String, default: "" },
|
|
||||||
});
|
|
||||||
|
|
||||||
const NetworkCheckSchema = mongoose.Schema(
|
|
||||||
{
|
|
||||||
...BaseCheckSchema.obj,
|
|
||||||
data: {
|
|
||||||
type: [networkInterfaceSchema],
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
capture: {
|
|
||||||
type: captureSchema,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
errors: {
|
|
||||||
type: mongoose.Schema.Types.Mixed,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ timestamps: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
NetworkCheckSchema.index({ createdAt: 1 });
|
|
||||||
NetworkCheckSchema.index({ monitorId: 1, createdAt: 1 });
|
|
||||||
NetworkCheckSchema.index({ monitorId: 1, createdAt: -1 });
|
|
||||||
|
|
||||||
export default mongoose.model("NetworkCheck", NetworkCheckSchema);
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
import mongoose from "mongoose";
|
|
||||||
import { BaseCheckSchema } from "./Check.js";
|
|
||||||
const AuditSchema = mongoose.Schema({
|
|
||||||
id: { type: String, required: true },
|
|
||||||
title: { type: String, required: true },
|
|
||||||
description: { type: String, required: true },
|
|
||||||
score: { type: Number, required: true },
|
|
||||||
scoreDisplayMode: { type: String, required: true },
|
|
||||||
displayValue: { type: String, required: true },
|
|
||||||
numericValue: { type: Number, required: true },
|
|
||||||
numericUnit: { type: String, required: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
const AuditsSchema = mongoose.Schema({
|
|
||||||
cls: {
|
|
||||||
type: AuditSchema,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
si: {
|
|
||||||
type: AuditSchema,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
fcp: {
|
|
||||||
type: AuditSchema,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
lcp: {
|
|
||||||
type: AuditSchema,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
tbt: {
|
|
||||||
type: AuditSchema,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mongoose schema for storing metrics from Google Lighthouse.
|
|
||||||
* @typedef {Object} PageSpeedCheck
|
|
||||||
* @property {mongoose.Schema.Types.ObjectId} monitorId - Reference to the Monitor model.
|
|
||||||
* @property {number} accessibility - Accessibility score.
|
|
||||||
* @property {number} bestPractices - Best practices score.
|
|
||||||
* @property {number} seo - SEO score.
|
|
||||||
* @property {number} performance - Performance score.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const PageSpeedCheck = mongoose.Schema(
|
|
||||||
{
|
|
||||||
...BaseCheckSchema.obj,
|
|
||||||
accessibility: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
bestPractices: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
seo: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
performance: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
audits: {
|
|
||||||
type: AuditsSchema,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ timestamps: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mongoose model for storing metrics from Google Lighthouse.
|
|
||||||
* @typedef {mongoose.Model<PageSpeedCheck>} LighthouseMetricsModel
|
|
||||||
*/
|
|
||||||
|
|
||||||
PageSpeedCheck.index({ createdAt: 1 });
|
|
||||||
PageSpeedCheck.index({ monitorId: 1, createdAt: 1 });
|
|
||||||
PageSpeedCheck.index({ monitorId: 1, createdAt: -1 });
|
|
||||||
|
|
||||||
export default mongoose.model("PageSpeedCheck", PageSpeedCheck);
|
|
||||||
@@ -31,7 +31,7 @@ class CheckModule {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
getChecksByMonitor = async ({ monitorId, type, sortOrder, dateRange, filter, ack, page, rowsPerPage, status }) => {
|
getChecksByMonitor = async ({ monitorId, sortOrder, dateRange, filter, ack, page, rowsPerPage, status }) => {
|
||||||
try {
|
try {
|
||||||
status = status === "true" ? true : status === "false" ? false : undefined;
|
status = status === "true" ? true : status === "false" ? false : undefined;
|
||||||
page = parseInt(page);
|
page = parseInt(page);
|
||||||
@@ -79,19 +79,7 @@ class CheckModule {
|
|||||||
skip = page * rowsPerPage;
|
skip = page * rowsPerPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkModels = {
|
const checks = await this.Check.aggregate([
|
||||||
http: this.Check,
|
|
||||||
ping: this.Check,
|
|
||||||
docker: this.Check,
|
|
||||||
port: this.Check,
|
|
||||||
pagespeed: this.PageSpeedCheck,
|
|
||||||
hardware: this.HardwareCheck,
|
|
||||||
game: this.Check,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Model = checkModels[type];
|
|
||||||
|
|
||||||
const checks = await Model.aggregate([
|
|
||||||
{ $match: matchStage },
|
{ $match: matchStage },
|
||||||
{ $sort: { createdAt: sortOrder } },
|
{ $sort: { createdAt: sortOrder } },
|
||||||
{
|
{
|
||||||
@@ -166,18 +154,6 @@ class CheckModule {
|
|||||||
|
|
||||||
const aggregatePipeline = [
|
const aggregatePipeline = [
|
||||||
{ $match: matchStage },
|
{ $match: matchStage },
|
||||||
{
|
|
||||||
$unionWith: {
|
|
||||||
coll: "hardwarechecks",
|
|
||||||
pipeline: [{ $match: matchStage }],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$unionWith: {
|
|
||||||
coll: "pagespeedchecks",
|
|
||||||
pipeline: [{ $match: matchStage }],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
{ $sort: { createdAt: sortOrder } },
|
{ $sort: { createdAt: sortOrder } },
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
const SERVICE_NAME = "hardwareCheckModule";
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default HardwareCheckModule;
|
|
||||||
@@ -11,25 +11,10 @@ import {
|
|||||||
const SERVICE_NAME = "monitorModule";
|
const SERVICE_NAME = "monitorModule";
|
||||||
|
|
||||||
class MonitorModule {
|
class MonitorModule {
|
||||||
constructor({
|
constructor({ Monitor, MonitorStats, Check, stringService, fs, path, fileURLToPath, ObjectId, NormalizeData, NormalizeDataUptimeDetails }) {
|
||||||
Monitor,
|
|
||||||
MonitorStats,
|
|
||||||
Check,
|
|
||||||
PageSpeedCheck,
|
|
||||||
HardwareCheck,
|
|
||||||
stringService,
|
|
||||||
fs,
|
|
||||||
path,
|
|
||||||
fileURLToPath,
|
|
||||||
ObjectId,
|
|
||||||
NormalizeData,
|
|
||||||
NormalizeDataUptimeDetails,
|
|
||||||
}) {
|
|
||||||
this.Monitor = Monitor;
|
this.Monitor = Monitor;
|
||||||
this.MonitorStats = MonitorStats;
|
this.MonitorStats = MonitorStats;
|
||||||
this.Check = Check;
|
this.Check = Check;
|
||||||
this.PageSpeedCheck = PageSpeedCheck;
|
|
||||||
this.HardwareCheck = HardwareCheck;
|
|
||||||
this.stringService = stringService;
|
this.stringService = stringService;
|
||||||
this.fs = fs;
|
this.fs = fs;
|
||||||
this.path = path;
|
this.path = path;
|
||||||
@@ -38,15 +23,6 @@ class MonitorModule {
|
|||||||
this.NormalizeData = NormalizeData;
|
this.NormalizeData = NormalizeData;
|
||||||
this.NormalizeDataUptimeDetails = NormalizeDataUptimeDetails;
|
this.NormalizeDataUptimeDetails = NormalizeDataUptimeDetails;
|
||||||
|
|
||||||
this.CHECK_MODEL_LOOKUP = {
|
|
||||||
http: Check,
|
|
||||||
ping: Check,
|
|
||||||
docker: Check,
|
|
||||||
port: Check,
|
|
||||||
pagespeed: PageSpeedCheck,
|
|
||||||
hardware: HardwareCheck,
|
|
||||||
};
|
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
@@ -146,19 +122,18 @@ class MonitorModule {
|
|||||||
};
|
};
|
||||||
|
|
||||||
//Helper
|
//Helper
|
||||||
getMonitorChecks = async (monitorId, model, dateRange, sortOrder) => {
|
getMonitorChecks = async (monitorId, dateRange, sortOrder) => {
|
||||||
const indexSpec = {
|
const indexSpec = {
|
||||||
monitorId: 1,
|
monitorId: 1,
|
||||||
createdAt: sortOrder, // This will be 1 or -1
|
updatedAt: sortOrder, // This will be 1 or -1
|
||||||
};
|
};
|
||||||
|
|
||||||
const [checksAll, checksForDateRange] = await Promise.all([
|
const [checksAll, checksForDateRange] = await Promise.all([
|
||||||
model.find({ monitorId }).sort({ createdAt: sortOrder }).hint(indexSpec).lean(),
|
this.Check.find({ monitorId }).sort({ createdAt: sortOrder }).hint(indexSpec).lean(),
|
||||||
model
|
this.Check.find({
|
||||||
.find({
|
monitorId,
|
||||||
monitorId,
|
createdAt: { $gte: dateRange.start, $lte: dateRange.end },
|
||||||
createdAt: { $gte: dateRange.start, $lte: dateRange.end },
|
})
|
||||||
})
|
|
||||||
.hint(indexSpec)
|
.hint(indexSpec)
|
||||||
.lean(),
|
.lean(),
|
||||||
]);
|
]);
|
||||||
@@ -295,9 +270,8 @@ class MonitorModule {
|
|||||||
const sort = sortOrder === "asc" ? 1 : -1;
|
const sort = sortOrder === "asc" ? 1 : -1;
|
||||||
|
|
||||||
// Get Checks for monitor in date range requested
|
// Get Checks for monitor in date range requested
|
||||||
const model = this.CHECK_MODEL_LOOKUP[monitor.type];
|
|
||||||
const dates = this.getDateRange(dateRange);
|
const dates = this.getDateRange(dateRange);
|
||||||
const { checksAll, checksForDateRange } = await this.getMonitorChecks(monitorId, model, dates, sort);
|
const { checksAll, checksForDateRange } = await this.getMonitorChecks(monitorId, dates, sort);
|
||||||
|
|
||||||
// Build monitor stats
|
// Build monitor stats
|
||||||
const monitorStats = {
|
const monitorStats = {
|
||||||
@@ -339,7 +313,7 @@ class MonitorModule {
|
|||||||
};
|
};
|
||||||
const dateString = formatLookup[dateRange];
|
const dateString = formatLookup[dateRange];
|
||||||
|
|
||||||
const hardwareStats = await this.HardwareCheck.aggregate(buildHardwareDetailsPipeline(monitor, dates, dateString));
|
const hardwareStats = await this.Check.aggregate(buildHardwareDetailsPipeline(monitor, dates, dateString));
|
||||||
|
|
||||||
const stats = hardwareStats[0];
|
const stats = hardwareStats[0];
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ const buildUptimeDetailsPipeline = (monitorId, dates, dateString) => {
|
|||||||
{
|
{
|
||||||
$match: {
|
$match: {
|
||||||
monitorId: new ObjectId(monitorId),
|
monitorId: new ObjectId(monitorId),
|
||||||
createdAt: { $gte: dates.start, $lte: dates.end },
|
updatedAt: { $gte: dates.start, $lte: dates.end },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
$sort: {
|
$sort: {
|
||||||
createdAt: 1,
|
updatedAt: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -173,6 +173,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
|
|||||||
{
|
{
|
||||||
$match: {
|
$match: {
|
||||||
monitorId: monitor._id,
|
monitorId: monitor._id,
|
||||||
|
type: "hardware",
|
||||||
createdAt: { $gte: dates.start, $lte: dates.end },
|
createdAt: { $gte: dates.start, $lte: dates.end },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -218,14 +219,14 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
|
|||||||
{
|
{
|
||||||
$project: {
|
$project: {
|
||||||
diskCount: {
|
diskCount: {
|
||||||
$size: "$disk",
|
$size: { $ifNull: ["$disk", []] },
|
||||||
},
|
},
|
||||||
netCount: { $size: "$net" },
|
netCount: { $size: { $ifNull: ["$net", []] } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
$lookup: {
|
$lookup: {
|
||||||
from: "hardwarechecks",
|
from: "checks",
|
||||||
let: {
|
let: {
|
||||||
diskCount: "$diskCount",
|
diskCount: "$diskCount",
|
||||||
netCount: "$netCount",
|
netCount: "$netCount",
|
||||||
@@ -381,7 +382,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
|
|||||||
},
|
},
|
||||||
net: {
|
net: {
|
||||||
$map: {
|
$map: {
|
||||||
input: { $range: [0, { $size: { $arrayElemAt: ["$net", 0] } }] },
|
input: { $range: [0, { $size: { $ifNull: [{ $arrayElemAt: ["$net", 0] }, []] } }] },
|
||||||
as: "netIndex",
|
as: "netIndex",
|
||||||
in: {
|
in: {
|
||||||
name: {
|
name: {
|
||||||
@@ -409,7 +410,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
|
|||||||
$arrayElemAt: [
|
$arrayElemAt: [
|
||||||
{
|
{
|
||||||
$map: {
|
$map: {
|
||||||
input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] },
|
input: {
|
||||||
|
$arrayElemAt: [{ $ifNull: ["$net", []] }, { $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1] }],
|
||||||
|
},
|
||||||
as: "iface",
|
as: "iface",
|
||||||
in: "$$iface.bytes_sent",
|
in: "$$iface.bytes_sent",
|
||||||
},
|
},
|
||||||
@@ -418,7 +421,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
tFirst: { $arrayElemAt: ["$updatedAts", 0] },
|
tFirst: { $arrayElemAt: ["$updatedAts", 0] },
|
||||||
tLast: { $arrayElemAt: ["$updatedAts", { $subtract: [{ $size: "$updatedAts" }, 1] }] },
|
tLast: {
|
||||||
|
$arrayElemAt: [{ $ifNull: ["$updatedAts", []] }, { $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1] }],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
in: {
|
in: {
|
||||||
$cond: [
|
$cond: [
|
||||||
@@ -444,7 +449,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
|
|||||||
$arrayElemAt: [
|
$arrayElemAt: [
|
||||||
{
|
{
|
||||||
$map: {
|
$map: {
|
||||||
input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] },
|
input: {
|
||||||
|
$arrayElemAt: [{ $ifNull: ["$net", []] }, { $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1] }],
|
||||||
|
},
|
||||||
as: "iface",
|
as: "iface",
|
||||||
in: "$$iface.bytes_recv",
|
in: "$$iface.bytes_recv",
|
||||||
},
|
},
|
||||||
@@ -453,7 +460,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
tFirst: { $arrayElemAt: ["$updatedAts", 0] },
|
tFirst: { $arrayElemAt: ["$updatedAts", 0] },
|
||||||
tLast: { $arrayElemAt: ["$updatedAts", { $subtract: [{ $size: "$updatedAts" }, 1] }] },
|
tLast: {
|
||||||
|
$arrayElemAt: [{ $ifNull: ["$updatedAts", []] }, { $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1] }],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
in: {
|
in: {
|
||||||
$cond: [
|
$cond: [
|
||||||
@@ -479,7 +488,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
|
|||||||
$arrayElemAt: [
|
$arrayElemAt: [
|
||||||
{
|
{
|
||||||
$map: {
|
$map: {
|
||||||
input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] },
|
input: {
|
||||||
|
$arrayElemAt: [{ $ifNull: ["$net", []] }, { $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1] }],
|
||||||
|
},
|
||||||
as: "iface",
|
as: "iface",
|
||||||
in: "$$iface.packets_sent",
|
in: "$$iface.packets_sent",
|
||||||
},
|
},
|
||||||
@@ -488,7 +499,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
tFirst: { $arrayElemAt: ["$updatedAts", 0] },
|
tFirst: { $arrayElemAt: ["$updatedAts", 0] },
|
||||||
tLast: { $arrayElemAt: ["$updatedAts", { $subtract: [{ $size: "$updatedAts" }, 1] }] },
|
tLast: {
|
||||||
|
$arrayElemAt: [{ $ifNull: ["$updatedAts", []] }, { $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1] }],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
in: {
|
in: {
|
||||||
$cond: [
|
$cond: [
|
||||||
@@ -522,9 +535,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
|
|||||||
$map: {
|
$map: {
|
||||||
input: {
|
input: {
|
||||||
$arrayElemAt: [
|
$arrayElemAt: [
|
||||||
"$net",
|
{ $ifNull: ["$net", []] },
|
||||||
{
|
{
|
||||||
$subtract: [{ $size: "$net" }, 1],
|
$subtract: [{ $size: { $ifNull: ["$net", []] } }, 1],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -538,9 +551,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
|
|||||||
tFirst: { $arrayElemAt: ["$updatedAts", 0] },
|
tFirst: { $arrayElemAt: ["$updatedAts", 0] },
|
||||||
tLast: {
|
tLast: {
|
||||||
$arrayElemAt: [
|
$arrayElemAt: [
|
||||||
"$updatedAts",
|
{ $ifNull: ["$updatedAts", []] },
|
||||||
{
|
{
|
||||||
$subtract: [{ $size: "$updatedAts" }, 1],
|
$subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -566,7 +579,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
|
|||||||
$arrayElemAt: [
|
$arrayElemAt: [
|
||||||
{
|
{
|
||||||
$map: {
|
$map: {
|
||||||
input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] },
|
input: {
|
||||||
|
$arrayElemAt: [{ $ifNull: ["$net", []] }, { $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1] }],
|
||||||
|
},
|
||||||
as: "iface",
|
as: "iface",
|
||||||
in: "$$iface.err_in",
|
in: "$$iface.err_in",
|
||||||
},
|
},
|
||||||
@@ -575,7 +590,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
tFirst: { $arrayElemAt: ["$updatedAts", 0] },
|
tFirst: { $arrayElemAt: ["$updatedAts", 0] },
|
||||||
tLast: { $arrayElemAt: ["$updatedAts", { $subtract: [{ $size: "$updatedAts" }, 1] }] },
|
tLast: {
|
||||||
|
$arrayElemAt: [{ $ifNull: ["$updatedAts", []] }, { $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1] }],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
in: {
|
in: {
|
||||||
$cond: [
|
$cond: [
|
||||||
@@ -609,9 +626,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
|
|||||||
$map: {
|
$map: {
|
||||||
input: {
|
input: {
|
||||||
$arrayElemAt: [
|
$arrayElemAt: [
|
||||||
"$net",
|
{ $ifNull: ["$net", []] },
|
||||||
{
|
{
|
||||||
$subtract: [{ $size: "$net" }, 1],
|
$subtract: [{ $size: { $ifNull: ["$net", []] } }, 1],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -625,9 +642,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
|
|||||||
tFirst: { $arrayElemAt: ["$updatedAts", 0] },
|
tFirst: { $arrayElemAt: ["$updatedAts", 0] },
|
||||||
tLast: {
|
tLast: {
|
||||||
$arrayElemAt: [
|
$arrayElemAt: [
|
||||||
"$updatedAts",
|
{ $ifNull: ["$updatedAts", []] },
|
||||||
{
|
{
|
||||||
$subtract: [{ $size: "$updatedAts" }, 1],
|
$subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -664,9 +681,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
|
|||||||
$map: {
|
$map: {
|
||||||
input: {
|
input: {
|
||||||
$arrayElemAt: [
|
$arrayElemAt: [
|
||||||
"$net",
|
{ $ifNull: ["$net", []] },
|
||||||
{
|
{
|
||||||
$subtract: [{ $size: "$net" }, 1],
|
$subtract: [{ $size: { $ifNull: ["$net", []] } }, 1],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -680,9 +697,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
|
|||||||
tFirst: { $arrayElemAt: ["$updatedAts", 0] },
|
tFirst: { $arrayElemAt: ["$updatedAts", 0] },
|
||||||
tLast: {
|
tLast: {
|
||||||
$arrayElemAt: [
|
$arrayElemAt: [
|
||||||
"$updatedAts",
|
{ $ifNull: ["$updatedAts", []] },
|
||||||
{
|
{
|
||||||
$subtract: [{ $size: "$updatedAts" }, 1],
|
$subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -719,9 +736,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
|
|||||||
$map: {
|
$map: {
|
||||||
input: {
|
input: {
|
||||||
$arrayElemAt: [
|
$arrayElemAt: [
|
||||||
"$net",
|
{ $ifNull: ["$net", []] },
|
||||||
{
|
{
|
||||||
$subtract: [{ $size: "$net" }, 1],
|
$subtract: [{ $size: { $ifNull: ["$net", []] } }, 1],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -735,9 +752,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
|
|||||||
tFirst: { $arrayElemAt: ["$updatedAts", 0] },
|
tFirst: { $arrayElemAt: ["$updatedAts", 0] },
|
||||||
tLast: {
|
tLast: {
|
||||||
$arrayElemAt: [
|
$arrayElemAt: [
|
||||||
"$updatedAts",
|
{ $ifNull: ["$updatedAts", []] },
|
||||||
{
|
{
|
||||||
$subtract: [{ $size: "$updatedAts" }, 1],
|
$subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -970,12 +987,7 @@ const buildMonitorsWithChecksByTeamIdPipeline = ({ matchStage, filter, page, row
|
|||||||
|
|
||||||
// Add checks
|
// Add checks
|
||||||
if (limit) {
|
if (limit) {
|
||||||
let checksCollection = "checks";
|
const checksCollection = "checks";
|
||||||
if (type === "pagespeed") {
|
|
||||||
checksCollection = "pagespeedchecks";
|
|
||||||
} else if (type === "hardware") {
|
|
||||||
checksCollection = "hardwarechecks";
|
|
||||||
}
|
|
||||||
monitorsPipeline.push({
|
monitorsPipeline.push({
|
||||||
$lookup: {
|
$lookup: {
|
||||||
from: checksCollection,
|
from: checksCollection,
|
||||||
@@ -1040,12 +1052,8 @@ const buildFilteredMonitorsByTeamIdPipeline = ({ matchStage, filter, page, rowsP
|
|||||||
|
|
||||||
// Add checks
|
// Add checks
|
||||||
if (limit) {
|
if (limit) {
|
||||||
let checksCollection = "checks";
|
const checksCollection = "checks";
|
||||||
if (type === "pagespeed") {
|
|
||||||
checksCollection = "pagespeedchecks";
|
|
||||||
} else if (type === "hardware") {
|
|
||||||
checksCollection = "hardwarechecks";
|
|
||||||
}
|
|
||||||
pipeline.push({
|
pipeline.push({
|
||||||
$lookup: {
|
$lookup: {
|
||||||
from: checksCollection,
|
from: checksCollection,
|
||||||
@@ -1159,46 +1167,6 @@ const buildGetMonitorsByTeamIdPipeline = (req) => {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
...(limit
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
$lookup: {
|
|
||||||
from: "pagespeedchecks",
|
|
||||||
let: { monitorId: "$_id" },
|
|
||||||
pipeline: [
|
|
||||||
{
|
|
||||||
$match: {
|
|
||||||
$expr: { $eq: ["$monitorId", "$$monitorId"] },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ $sort: { createdAt: -1 } },
|
|
||||||
...(limit ? [{ $limit: limit }] : []),
|
|
||||||
],
|
|
||||||
as: "pagespeedchecks",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
...(limit
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
$lookup: {
|
|
||||||
from: "hardwarechecks",
|
|
||||||
let: { monitorId: "$_id" },
|
|
||||||
pipeline: [
|
|
||||||
{
|
|
||||||
$match: {
|
|
||||||
$expr: { $eq: ["$monitorId", "$$monitorId"] },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ $sort: { createdAt: -1 } },
|
|
||||||
...(limit ? [{ $limit: limit }] : []),
|
|
||||||
],
|
|
||||||
as: "hardwarechecks",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
|
|
||||||
{
|
{
|
||||||
$addFields: {
|
$addFields: {
|
||||||
@@ -1209,14 +1177,6 @@ const buildGetMonitorsByTeamIdPipeline = (req) => {
|
|||||||
case: { $in: ["$type", ["http", "ping", "docker", "port", "game"]] },
|
case: { $in: ["$type", ["http", "ping", "docker", "port", "game"]] },
|
||||||
then: "$standardchecks",
|
then: "$standardchecks",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
case: { $eq: ["$type", "pagespeed"] },
|
|
||||||
then: "$pagespeedchecks",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
case: { $eq: ["$type", "hardware"] },
|
|
||||||
then: "$hardwarechecks",
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
default: [],
|
default: [],
|
||||||
},
|
},
|
||||||
@@ -1226,8 +1186,6 @@ const buildGetMonitorsByTeamIdPipeline = (req) => {
|
|||||||
{
|
{
|
||||||
$project: {
|
$project: {
|
||||||
standardchecks: 0,
|
standardchecks: 0,
|
||||||
pagespeedchecks: 0,
|
|
||||||
hardwarechecks: 0,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
const SERVICE_NAME = "networkCheckModule";
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NetworkCheckModule;
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
// import PageSpeedCheck from "../../models/PageSpeedCheck.js";
|
|
||||||
const SERVICE_NAME = "pageSpeedCheckModule";
|
|
||||||
|
|
||||||
class PageSpeedCheckModule {
|
|
||||||
constructor({ PageSpeedCheck }) {
|
|
||||||
this.PageSpeedCheck = PageSpeedCheck;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
+1
-1
@@ -18,7 +18,7 @@ const startApp = async () => {
|
|||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
const openApiSpec = JSON.parse(fs.readFileSync(path.join(__dirname, "../openapi.json"), "utf8"));
|
const openApiSpec = JSON.parse(fs.readFileSync(path.join(__dirname, "../openapi.json"), "utf8"));
|
||||||
const frontendPath = path.join(__dirname, "public");
|
const frontendPath = path.join(__dirname, "..", "public");
|
||||||
|
|
||||||
// Create services
|
// Create services
|
||||||
const settingsService = new SettingsService(AppSettings);
|
const settingsService = new SettingsService(AppSettings);
|
||||||
|
|||||||
@@ -33,10 +33,9 @@ class CheckService {
|
|||||||
throw this.errorService.createAuthorizationError();
|
throw this.errorService.createAuthorizationError();
|
||||||
}
|
}
|
||||||
|
|
||||||
let { type, sortOrder, dateRange, filter, ack, page, rowsPerPage, status } = query;
|
let { sortOrder, dateRange, filter, ack, page, rowsPerPage, status } = query;
|
||||||
const result = await this.db.checkModule.getChecksByMonitor({
|
const result = await this.db.checkModule.getChecksByMonitor({
|
||||||
monitorId,
|
monitorId,
|
||||||
type,
|
|
||||||
sortOrder,
|
sortOrder,
|
||||||
dateRange,
|
dateRange,
|
||||||
filter,
|
filter,
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ class SuperSimpleQueue {
|
|||||||
id: monitorId.toString(),
|
id: monitorId.toString(),
|
||||||
template: "monitor-job",
|
template: "monitor-job",
|
||||||
repeat: monitor.interval,
|
repeat: monitor.interval,
|
||||||
|
active: monitor.isActive,
|
||||||
data: monitor.toObject(),
|
data: monitor.toObject(),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,16 @@ const SERVICE_NAME = "JobQueueHelper";
|
|||||||
|
|
||||||
class SuperSimpleQueueHelper {
|
class SuperSimpleQueueHelper {
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{
|
||||||
|
* db: import("../database").Database,
|
||||||
|
* logger: import("../logger").Logger,
|
||||||
|
* networkService: import("../networkService").NetworkService,
|
||||||
|
* statusService: import("../statusService").StatusService,
|
||||||
|
* notificationService: import("../notificationService").NotificationService
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
constructor({ db, logger, networkService, statusService, notificationService }) {
|
constructor({ db, logger, networkService, statusService, notificationService }) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
@@ -31,7 +41,8 @@ class SuperSimpleQueueHelper {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const networkResponse = await this.networkService.getStatus(monitor);
|
const networkResponse = await this.networkService.requestStatus(monitor);
|
||||||
|
|
||||||
if (!networkResponse) {
|
if (!networkResponse) {
|
||||||
throw new Error("No network response");
|
throw new Error("No network response");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,13 @@
|
|||||||
const SERVICE_NAME = "BufferService";
|
const SERVICE_NAME = "BufferService";
|
||||||
const TYPE_MAP = {
|
|
||||||
http: "checks",
|
|
||||||
ping: "checks",
|
|
||||||
port: "checks",
|
|
||||||
docker: "checks",
|
|
||||||
pagespeed: "pagespeedChecks",
|
|
||||||
hardware: "hardwareChecks",
|
|
||||||
game: "checks",
|
|
||||||
};
|
|
||||||
|
|
||||||
class BufferService {
|
class BufferService {
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
constructor({ db, logger, envSettings }) {
|
constructor({ db, logger, envSettings }) {
|
||||||
this.BUFFER_TIMEOUT = envSettings.nodeEnv === "development" ? 5000 : 1000 * 60 * 1; // 1 minute
|
this.BUFFER_TIMEOUT = envSettings.nodeEnv === "development" ? 1000 : 1000 * 60 * 1; // 1 minute
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.SERVICE_NAME = SERVICE_NAME;
|
this.SERVICE_NAME = SERVICE_NAME;
|
||||||
this.buffers = {
|
this.buffer = [];
|
||||||
checks: [],
|
|
||||||
pagespeedChecks: [],
|
|
||||||
hardwareChecks: [],
|
|
||||||
};
|
|
||||||
this.OPERATION_MAP = {
|
|
||||||
checks: this.db.checkModule.createChecks,
|
|
||||||
pagespeedChecks: this.db.pageSpeedCheckModule.createPageSpeedChecks,
|
|
||||||
hardwareChecks: this.db.hardwareCheckModule.createHardwareChecks,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.scheduleNextFlush();
|
this.scheduleNextFlush();
|
||||||
this.logger.info({
|
this.logger.info({
|
||||||
message: `Buffer service initialized, flushing every ${this.BUFFER_TIMEOUT / 1000}s`,
|
message: `Buffer service initialized, flushing every ${this.BUFFER_TIMEOUT / 1000}s`,
|
||||||
@@ -39,9 +20,9 @@ class BufferService {
|
|||||||
return BufferService.SERVICE_NAME;
|
return BufferService.SERVICE_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
addToBuffer({ check, type }) {
|
addToBuffer({ check }) {
|
||||||
try {
|
try {
|
||||||
this.buffers[TYPE_MAP[type]].push(check);
|
this.buffer.push(check);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error({
|
this.logger.error({
|
||||||
message: error.message,
|
message: error.message,
|
||||||
@@ -55,7 +36,7 @@ class BufferService {
|
|||||||
scheduleNextFlush() {
|
scheduleNextFlush() {
|
||||||
this.bufferTimer = setTimeout(async () => {
|
this.bufferTimer = setTimeout(async () => {
|
||||||
try {
|
try {
|
||||||
await this.flushBuffers();
|
await this.flushBuffer();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error({
|
this.logger.error({
|
||||||
message: `Error in flush cycle: ${error.message}`,
|
message: `Error in flush cycle: ${error.message}`,
|
||||||
@@ -69,35 +50,24 @@ class BufferService {
|
|||||||
}
|
}
|
||||||
}, this.BUFFER_TIMEOUT);
|
}, this.BUFFER_TIMEOUT);
|
||||||
}
|
}
|
||||||
async flushBuffers() {
|
async flushBuffer() {
|
||||||
let items = 0;
|
let items = this.buffer.length;
|
||||||
for (const [bufferName, buffer] of Object.entries(this.buffers)) {
|
|
||||||
items += buffer.length;
|
try {
|
||||||
const operation = this.OPERATION_MAP[bufferName];
|
await this.db.checkModule.createChecks(this.buffer);
|
||||||
if (!operation) {
|
} catch (error) {
|
||||||
this.logger.error({
|
this.logger.error({
|
||||||
message: `No operation found for ${bufferName}`,
|
message: error.message,
|
||||||
service: this.SERVICE_NAME,
|
service: this.SERVICE_NAME,
|
||||||
method: "flushBuffers",
|
method: "flushBuffer",
|
||||||
});
|
stack: error.stack,
|
||||||
continue;
|
});
|
||||||
}
|
|
||||||
try {
|
|
||||||
await operation(buffer);
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error({
|
|
||||||
message: error.message,
|
|
||||||
service: this.SERVICE_NAME,
|
|
||||||
method: "flushBuffers",
|
|
||||||
stack: error.stack,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.buffers[bufferName] = [];
|
|
||||||
}
|
}
|
||||||
|
this.buffer = [];
|
||||||
this.logger.debug({
|
this.logger.debug({
|
||||||
message: `Flushed ${items} items`,
|
message: `Flushed ${items} items`,
|
||||||
service: this.SERVICE_NAME,
|
service: this.SERVICE_NAME,
|
||||||
method: "flushBuffers",
|
method: "flushBuffer",
|
||||||
});
|
});
|
||||||
items = 0;
|
items = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Executable → Regular
+184
-309
@@ -1,23 +1,9 @@
|
|||||||
import jmespath from "jmespath";
|
|
||||||
import https from "https";
|
|
||||||
import { GameDig } from "gamedig";
|
|
||||||
|
|
||||||
const SERVICE_NAME = "NetworkService";
|
const SERVICE_NAME = "NetworkService";
|
||||||
const UPROCK_ENDPOINT = "https://api.uprock.com/checkmate/push";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new NetworkService instance.
|
|
||||||
*
|
|
||||||
* @param {Object} axios - The axios instance for HTTP requests.
|
|
||||||
* @param {Object} ping - The ping utility for network checks.
|
|
||||||
* @param {Object} logger - The logger instance for logging.
|
|
||||||
* @param {Object} http - The HTTP utility for network operations.
|
|
||||||
* @param {Object} net - The net utility for network operations.
|
|
||||||
*/
|
|
||||||
class NetworkService {
|
class NetworkService {
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
|
|
||||||
constructor(axios, ping, logger, http, Docker, net, stringService, settingsService) {
|
constructor({ axios, got, https, jmespath, GameDig, ping, logger, http, Docker, net, stringService, settingsService }) {
|
||||||
this.TYPE_PING = "ping";
|
this.TYPE_PING = "ping";
|
||||||
this.TYPE_HTTP = "http";
|
this.TYPE_HTTP = "http";
|
||||||
this.TYPE_PAGESPEED = "pagespeed";
|
this.TYPE_PAGESPEED = "pagespeed";
|
||||||
@@ -29,6 +15,10 @@ class NetworkService {
|
|||||||
this.NETWORK_ERROR = 5000;
|
this.NETWORK_ERROR = 5000;
|
||||||
this.PING_ERROR = 5001;
|
this.PING_ERROR = 5001;
|
||||||
this.axios = axios;
|
this.axios = axios;
|
||||||
|
this.got = got;
|
||||||
|
this.https = https;
|
||||||
|
this.jmespath = jmespath;
|
||||||
|
this.GameDig = GameDig;
|
||||||
this.ping = ping;
|
this.ping = ping;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.http = http;
|
this.http = http;
|
||||||
@@ -38,151 +28,134 @@ class NetworkService {
|
|||||||
this.settingsService = settingsService;
|
this.settingsService = settingsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
get serviceName() {
|
// Helper functions
|
||||||
return NetworkService.SERVICE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Times the execution of an asynchronous operation.
|
|
||||||
*
|
|
||||||
* @param {Function} operation - The asynchronous operation to be timed.
|
|
||||||
* @returns {Promise<Object>} An object containing the response, response time, and optionally an error.
|
|
||||||
* @property {Object|null} response - The response from the operation, or null if an error occurred.
|
|
||||||
* @property {number} responseTime - The time taken for the operation to complete, in milliseconds.
|
|
||||||
* @property {Error} [error] - The error object if an error occurred during the operation.
|
|
||||||
*/
|
|
||||||
async timeRequest(operation) {
|
async timeRequest(operation) {
|
||||||
const startTime = Date.now();
|
const start = process.hrtime.bigint();
|
||||||
try {
|
try {
|
||||||
const response = await operation();
|
const response = await operation();
|
||||||
const endTime = Date.now();
|
const elapsedMs = Math.round(Number(process.hrtime.bigint() - start) / 1_000_000);
|
||||||
const responseTime = endTime - startTime;
|
return { response, responseTime: elapsedMs };
|
||||||
return { response, responseTime };
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const endTime = Date.now();
|
const elapsedMs = Math.round(Number(process.hrtime.bigint() - start) / 1_000_000);
|
||||||
const responseTime = endTime - startTime;
|
return { response: null, responseTime: elapsedMs, error };
|
||||||
return { response: null, responseTime, error };
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main entry point
|
||||||
|
async requestStatus(monitor) {
|
||||||
|
const type = monitor?.type || "unknown";
|
||||||
|
switch (type) {
|
||||||
|
case this.TYPE_PING:
|
||||||
|
return await this.requestPing(monitor);
|
||||||
|
case this.TYPE_HTTP:
|
||||||
|
return await this.requestHttp(monitor);
|
||||||
|
case this.TYPE_PAGESPEED:
|
||||||
|
return await this.requestPageSpeed(monitor);
|
||||||
|
case this.TYPE_HARDWARE:
|
||||||
|
return await this.requestHardware(monitor);
|
||||||
|
case this.TYPE_DOCKER:
|
||||||
|
return await this.requestDocker(monitor);
|
||||||
|
case this.TYPE_PORT:
|
||||||
|
return await this.requestPort(monitor);
|
||||||
|
case this.TYPE_GAME:
|
||||||
|
return await this.requestGame(monitor);
|
||||||
|
default:
|
||||||
|
return await this.handleUnsupportedType(type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs a ping check to a specified host to verify its availability.
|
|
||||||
* @async
|
|
||||||
* @param {Object} monitor - The monitor configuration object
|
|
||||||
* @param {string} monitor.url - The host URL to ping
|
|
||||||
* @param {string} monitor._id - The unique identifier of the monitor
|
|
||||||
* @returns {Promise<Object>} A promise that resolves to a ping response object
|
|
||||||
* @returns {string} pingResponse.monitorId - The ID of the monitor
|
|
||||||
* @returns {string} pingResponse.type - The type of monitor (always "ping")
|
|
||||||
* @returns {number} pingResponse.responseTime - The time taken for the ping
|
|
||||||
* @returns {Object} pingResponse.payload - The raw ping response data
|
|
||||||
* @returns {boolean} pingResponse.status - Whether the host is alive (true) or not (false)
|
|
||||||
* @returns {number} pingResponse.code - Status code (200 for success, PING_ERROR for failure)
|
|
||||||
* @returns {string} pingResponse.message - Success or failure message
|
|
||||||
* @throws {Error} If there's an error during the ping operation
|
|
||||||
*/
|
|
||||||
async requestPing(monitor) {
|
async requestPing(monitor) {
|
||||||
try {
|
try {
|
||||||
const url = monitor.url;
|
if (!monitor?.url) {
|
||||||
const { response, responseTime, error } = await this.timeRequest(() => this.ping.promise.probe(url));
|
throw new Error("Monitor URL is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { response, responseTime, error } = await this.timeRequest(() => this.ping.promise.probe(monitor.url));
|
||||||
|
|
||||||
|
if (!response) {
|
||||||
|
throw new Error("Ping failed - no result returned");
|
||||||
|
}
|
||||||
|
|
||||||
const pingResponse = {
|
const pingResponse = {
|
||||||
monitorId: monitor._id,
|
monitorId: monitor._id,
|
||||||
type: "ping",
|
type: "ping",
|
||||||
|
status: response.alive,
|
||||||
|
code: 200,
|
||||||
responseTime,
|
responseTime,
|
||||||
|
message: "Success",
|
||||||
payload: response,
|
payload: response,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
pingResponse.status = false;
|
pingResponse.status = false;
|
||||||
pingResponse.code = this.PING_ERROR;
|
pingResponse.code = 200;
|
||||||
pingResponse.message = "No response";
|
pingResponse.message = "Ping failed";
|
||||||
return pingResponse;
|
return pingResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
pingResponse.code = 200;
|
|
||||||
pingResponse.status = response.alive;
|
|
||||||
pingResponse.message = "Success";
|
|
||||||
return pingResponse;
|
return pingResponse;
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
error.service = this.SERVICE_NAME;
|
err.service = this.SERVICE_NAME;
|
||||||
error.method = "requestPing";
|
err.method = "requestPing";
|
||||||
throw error;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs an HTTP GET request to a specified URL with optional validation of response data.
|
|
||||||
* @async
|
|
||||||
* @param {Object} monitor - The monitor configuration object
|
|
||||||
* @param {string} monitor.url - The URL to make the HTTP request to
|
|
||||||
* @param {string} [monitor.secret] - Optional Bearer token for authentication
|
|
||||||
* @param {string} monitor._id - The unique identifier of the monitor
|
|
||||||
* @param {string} monitor.name - The name of the monitor
|
|
||||||
* @param {string} monitor.teamId - The team ID associated with the monitor
|
|
||||||
* @param {string} monitor.type - The type of monitor
|
|
||||||
* @param {boolean} [monitor.ignoreTlsErrors] - Whether to ignore TLS certificate errors
|
|
||||||
* @param {string} [monitor.jsonPath] - Optional JMESPath expression to extract data from JSON response
|
|
||||||
* @param {string} [monitor.matchMethod] - Method to match response data ('include', 'regex', or exact match)
|
|
||||||
* @param {string} [monitor.expectedValue] - Expected value to match against response data
|
|
||||||
* @returns {Promise<Object>} A promise that resolves to an HTTP response object
|
|
||||||
* @returns {string} httpResponse.monitorId - The ID of the monitor
|
|
||||||
* @returns {string} httpResponse.teamId - The team ID
|
|
||||||
* @returns {string} httpResponse.type - The type of monitor
|
|
||||||
* @returns {number} httpResponse.responseTime - The time taken for the request
|
|
||||||
* @returns {Object} httpResponse.payload - The response data
|
|
||||||
* @returns {boolean} httpResponse.status - Whether the request was successful and matched expected value (if specified)
|
|
||||||
* @returns {number} httpResponse.code - HTTP status code or NETWORK_ERROR
|
|
||||||
* @returns {string} httpResponse.message - Success or failure message
|
|
||||||
* @throws {Error} If there's an error during the HTTP request or data validation
|
|
||||||
*/
|
|
||||||
async requestHttp(monitor) {
|
async requestHttp(monitor) {
|
||||||
|
const { url, secret, _id, name, teamId, type, ignoreTlsErrors, jsonPath, matchMethod, expectedValue } = monitor;
|
||||||
|
const httpResponse = {
|
||||||
|
monitorId: _id,
|
||||||
|
teamId: teamId,
|
||||||
|
type,
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { url, secret, _id, name, teamId, type, ignoreTlsErrors, jsonPath, matchMethod, expectedValue } = monitor;
|
if (!url) {
|
||||||
const config = {};
|
throw new Error("Monitor URL is required");
|
||||||
|
|
||||||
secret !== undefined && (config.headers = { Authorization: `Bearer ${secret}` });
|
|
||||||
|
|
||||||
if (ignoreTlsErrors === true) {
|
|
||||||
config.httpsAgent = new https.Agent({
|
|
||||||
rejectUnauthorized: false,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
const config = {
|
||||||
const { response, responseTime, error } = await this.timeRequest(() => this.axios.get(url, config));
|
headers: secret ? { Authorization: `Bearer ${secret}` } : undefined,
|
||||||
|
|
||||||
const httpResponse = {
|
|
||||||
monitorId: _id,
|
|
||||||
teamId,
|
|
||||||
type,
|
|
||||||
responseTime,
|
|
||||||
payload: response?.data,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (error) {
|
if (ignoreTlsErrors) {
|
||||||
const code = error.response?.status || this.NETWORK_ERROR;
|
config.agent = {
|
||||||
httpResponse.code = code;
|
https: new this.https.Agent({
|
||||||
httpResponse.status = false;
|
rejectUnauthorized: false,
|
||||||
httpResponse.message = this.http.STATUS_CODES[code] || this.stringService.httpNetworkError;
|
}),
|
||||||
return httpResponse;
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
httpResponse.code = response.status;
|
const response = await this.got(url, config);
|
||||||
|
|
||||||
|
let payload;
|
||||||
|
const contentType = response.headers["content-type"];
|
||||||
|
|
||||||
|
if (contentType && contentType.includes("application/json")) {
|
||||||
|
try {
|
||||||
|
payload = JSON.parse(response.body);
|
||||||
|
} catch {
|
||||||
|
payload = response.body;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
payload = response.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
httpResponse.code = response.statusCode;
|
||||||
|
httpResponse.status = response.ok;
|
||||||
|
httpResponse.message = response.statusMessage;
|
||||||
|
httpResponse.responseTime = response.timings.phases.total || 0;
|
||||||
|
httpResponse.payload = payload;
|
||||||
|
httpResponse.timings = response.timings || {};
|
||||||
|
|
||||||
if (!expectedValue) {
|
if (!expectedValue) {
|
||||||
// not configure expected value, return
|
|
||||||
httpResponse.status = true;
|
|
||||||
httpResponse.message = this.http.STATUS_CODES[response.status];
|
|
||||||
return httpResponse;
|
return httpResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate if response data match expected value
|
|
||||||
let result = response?.data;
|
|
||||||
|
|
||||||
this.logger.info({
|
this.logger.info({
|
||||||
service: this.SERVICE_NAME,
|
service: this.SERVICE_NAME,
|
||||||
method: "requestHttp",
|
method: "requestHttp",
|
||||||
message: `Job: [${name}](${_id}) match result with expected value`,
|
message: `Job: [${name}](${_id}) match result with expected value`,
|
||||||
details: { expectedValue, result, jsonPath, matchMethod },
|
details: { expectedValue, payload, jsonPath, matchMethod },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (jsonPath) {
|
if (jsonPath) {
|
||||||
@@ -196,106 +169,75 @@ class NetworkService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
result = jmespath.search(result, jsonPath);
|
this.jmespath.search(payload, jsonPath);
|
||||||
} catch (error) {
|
} catch {
|
||||||
httpResponse.status = false;
|
httpResponse.status = false;
|
||||||
httpResponse.message = this.stringService.httpJsonPathError;
|
httpResponse.message = this.stringService.httpJsonPathError;
|
||||||
return httpResponse;
|
return httpResponse;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return httpResponse;
|
||||||
if (result === null || result === undefined) {
|
} catch (err) {
|
||||||
|
if (err.name === "HTTPError" || err.name === "RequestError") {
|
||||||
|
httpResponse.code = err?.response?.statusCode || this.NETWORK_ERROR;
|
||||||
httpResponse.status = false;
|
httpResponse.status = false;
|
||||||
httpResponse.message = this.stringService.httpEmptyResult;
|
httpResponse.message = err?.response?.statusCode || err.message;
|
||||||
|
httpResponse.responseTime = err?.timings?.phases?.total || 0;
|
||||||
|
httpResponse.payload = null;
|
||||||
|
httpResponse.timings = err?.timings || {};
|
||||||
return httpResponse;
|
return httpResponse;
|
||||||
}
|
}
|
||||||
|
err.service = this.SERVICE_NAME;
|
||||||
let match;
|
err.method = "requestHttp";
|
||||||
result = typeof result === "object" ? JSON.stringify(result) : result.toString();
|
throw err;
|
||||||
if (matchMethod === "include") match = result.includes(expectedValue);
|
|
||||||
else if (matchMethod === "regex") match = new RegExp(expectedValue).test(result);
|
|
||||||
else match = result === expectedValue;
|
|
||||||
|
|
||||||
httpResponse.status = match;
|
|
||||||
httpResponse.message = match ? this.stringService.httpMatchSuccess : this.stringService.httpMatchFail;
|
|
||||||
return httpResponse;
|
|
||||||
} catch (error) {
|
|
||||||
error.service = this.SERVICE_NAME;
|
|
||||||
error.method = "requestHttp";
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async requestPageSpeed(monitor) {
|
||||||
* Checks the performance of a webpage using Google's PageSpeed Insights API.
|
|
||||||
* @async
|
|
||||||
* @param {Object} monitor - The monitor configuration object
|
|
||||||
* @param {string} monitor.url - The URL of the webpage to analyze
|
|
||||||
* @returns {Promise<Object|undefined>} A promise that resolves to a pagespeed response object or undefined if API key is missing
|
|
||||||
* @returns {string} response.monitorId - The ID of the monitor
|
|
||||||
* @returns {string} response.type - The type of monitor
|
|
||||||
* @returns {number} response.responseTime - The time taken for the analysis
|
|
||||||
* @returns {boolean} response.status - Whether the analysis was successful
|
|
||||||
* @returns {number} response.code - HTTP status code from the PageSpeed API
|
|
||||||
* @returns {string} response.message - Success or failure message
|
|
||||||
* @returns {Object} response.payload - The PageSpeed analysis results
|
|
||||||
* @throws {Error} If there's an error during the PageSpeed analysis
|
|
||||||
*/
|
|
||||||
async requestPagespeed(monitor) {
|
|
||||||
try {
|
try {
|
||||||
const url = monitor.url;
|
const url = monitor.url;
|
||||||
const updatedMonitor = { ...monitor };
|
if (!url) {
|
||||||
let pagespeedUrl = `https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${url}&category=seo&category=accessibility&category=best-practices&category=performance`;
|
throw new Error("Monitor URL is required");
|
||||||
|
}
|
||||||
|
let pageSpeedUrl = `https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${url}&category=seo&category=accessibility&category=best-practices&category=performance`;
|
||||||
const dbSettings = await this.settingsService.getDBSettings();
|
const dbSettings = await this.settingsService.getDBSettings();
|
||||||
if (dbSettings?.pagespeedApiKey) {
|
if (dbSettings?.pagespeedApiKey) {
|
||||||
pagespeedUrl += `&key=${dbSettings.pagespeedApiKey}`;
|
pageSpeedUrl += `&key=${dbSettings.pagespeedApiKey}`;
|
||||||
} else {
|
} else {
|
||||||
this.logger.warn({
|
this.logger.warn({
|
||||||
message: "Pagespeed API key not found, job not executed",
|
message: "PageSpeed API key not found, job not executed",
|
||||||
service: this.SERVICE_NAME,
|
service: this.SERVICE_NAME,
|
||||||
method: "requestPagespeed",
|
method: "requestPagespeed",
|
||||||
details: { url },
|
details: { url },
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
updatedMonitor.url = pagespeedUrl;
|
return await this.requestHttp({
|
||||||
return await this.requestHttp(updatedMonitor);
|
...monitor,
|
||||||
} catch (error) {
|
url: pageSpeedUrl,
|
||||||
error.service = this.SERVICE_NAME;
|
});
|
||||||
error.method = "requestPagespeed";
|
} catch (err) {
|
||||||
throw error;
|
err.service = this.SERVICE_NAME;
|
||||||
|
err.method = "requestPageSpeed";
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async requestHardware(monitor) {
|
async requestHardware(monitor) {
|
||||||
try {
|
try {
|
||||||
return await this.requestHttp(monitor);
|
return await this.requestHttp(monitor);
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
error.service = this.SERVICE_NAME;
|
err.service = this.SERVICE_NAME;
|
||||||
error.method = "requestHardware";
|
err.method = "requestHardware";
|
||||||
throw error;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks the status of a Docker container by its ID.
|
|
||||||
* @async
|
|
||||||
* @param {Object} monitor - The monitor configuration object
|
|
||||||
* @param {string} monitor.url - The Docker container ID to check
|
|
||||||
* @param {string} monitor._id - The unique identifier of the monitor
|
|
||||||
* @param {string} monitor.type - The type of monitor
|
|
||||||
* @returns {Promise<Object>} A promise that resolves to a docker response object
|
|
||||||
* @returns {string} dockerResponse.monitorId - The ID of the monitor
|
|
||||||
* @returns {string} dockerResponse.type - The type of monitor
|
|
||||||
* @returns {number} dockerResponse.responseTime - The time taken for the container inspection
|
|
||||||
* @returns {boolean} dockerResponse.status - Whether the container is running (true) or not (false)
|
|
||||||
* @returns {number} dockerResponse.code - HTTP-like status code (200 for success, NETWORK_ERROR for failure)
|
|
||||||
* @returns {string} dockerResponse.message - Success or failure message
|
|
||||||
* @throws {Error} If the container is not found or if there's an error inspecting the container
|
|
||||||
*/
|
|
||||||
async requestDocker(monitor) {
|
async requestDocker(monitor) {
|
||||||
try {
|
try {
|
||||||
|
if (!monitor.url) {
|
||||||
|
throw new Error("Monitor URL is required");
|
||||||
|
}
|
||||||
|
|
||||||
const docker = new this.Docker({
|
const docker = new this.Docker({
|
||||||
socketPath: "/var/run/docker.sock",
|
socketPath: "/var/run/docker.sock",
|
||||||
handleError: true, // Enable error handling
|
handleError: true, // Enable error handling
|
||||||
@@ -306,14 +248,17 @@ class NetworkService {
|
|||||||
if (!containerExists) {
|
if (!containerExists) {
|
||||||
throw new Error(this.stringService.dockerNotFound);
|
throw new Error(this.stringService.dockerNotFound);
|
||||||
}
|
}
|
||||||
const container = docker.getContainer(monitor.url);
|
|
||||||
|
|
||||||
|
const container = docker.getContainer(monitor.url);
|
||||||
const { response, responseTime, error } = await this.timeRequest(() => container.inspect());
|
const { response, responseTime, error } = await this.timeRequest(() => container.inspect());
|
||||||
|
|
||||||
const dockerResponse = {
|
const dockerResponse = {
|
||||||
monitorId: monitor._id,
|
monitorId: monitor._id,
|
||||||
type: monitor.type,
|
type: monitor.type,
|
||||||
responseTime,
|
responseTime,
|
||||||
|
status: response?.State?.Status === "running" ? true : false,
|
||||||
|
code: 200,
|
||||||
|
message: "Docker container status fetched successfully",
|
||||||
};
|
};
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -322,34 +267,15 @@ class NetworkService {
|
|||||||
dockerResponse.message = error.reason || "Failed to fetch Docker container information";
|
dockerResponse.message = error.reason || "Failed to fetch Docker container information";
|
||||||
return dockerResponse;
|
return dockerResponse;
|
||||||
}
|
}
|
||||||
dockerResponse.status = response?.State?.Status === "running" ? true : false;
|
|
||||||
dockerResponse.code = 200;
|
|
||||||
dockerResponse.message = "Docker container status fetched successfully";
|
|
||||||
return dockerResponse;
|
return dockerResponse;
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
error.service = this.SERVICE_NAME;
|
err.service = this.SERVICE_NAME;
|
||||||
error.method = "requestDocker";
|
err.method = "requestDocker";
|
||||||
throw error;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to establish a TCP connection to a specified host and port.
|
|
||||||
* @async
|
|
||||||
* @param {Object} monitor - The monitor configuration object
|
|
||||||
* @param {string} monitor.url - The host URL to connect to
|
|
||||||
* @param {number} monitor.port - The port number to connect to
|
|
||||||
* @param {string} monitor._id - The unique identifier of the monitor
|
|
||||||
* @param {string} monitor.type - The type of monitor
|
|
||||||
* @returns {Promise<Object>} A promise that resolves to a port response object
|
|
||||||
* @returns {string} portResponse.monitorId - The ID of the monitor
|
|
||||||
* @returns {string} portResponse.type - The type of monitor
|
|
||||||
* @returns {number} portResponse.responseTime - The time taken for the connection attempt
|
|
||||||
* @returns {boolean} portResponse.status - Whether the connection was successful
|
|
||||||
* @returns {number} portResponse.code - HTTP-like status code (200 for success, NETWORK_ERROR for failure)
|
|
||||||
* @returns {string} portResponse.message - Success or failure message
|
|
||||||
* @throws {Error} If the connection times out or encounters an error
|
|
||||||
*/
|
|
||||||
async requestPort(monitor) {
|
async requestPort(monitor) {
|
||||||
try {
|
try {
|
||||||
const { url, port } = monitor;
|
const { url, port } = monitor;
|
||||||
@@ -381,21 +307,21 @@ class NetworkService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const portResponse = {
|
const portResponse = {
|
||||||
|
code: 200,
|
||||||
|
status: response.success,
|
||||||
|
message: this.stringService.portSuccess,
|
||||||
monitorId: monitor._id,
|
monitorId: monitor._id,
|
||||||
type: monitor.type,
|
type: monitor.type,
|
||||||
responseTime,
|
responseTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
portResponse.status = false;
|
|
||||||
portResponse.code = this.NETWORK_ERROR;
|
portResponse.code = this.NETWORK_ERROR;
|
||||||
|
portResponse.status = false;
|
||||||
portResponse.message = this.stringService.portFail;
|
portResponse.message = this.stringService.portFail;
|
||||||
return portResponse;
|
return portResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
portResponse.status = response.success;
|
|
||||||
portResponse.code = 200;
|
|
||||||
portResponse.message = this.stringService.portSuccess;
|
|
||||||
return portResponse;
|
return portResponse;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
error.service = this.SERVICE_NAME;
|
error.service = this.SERVICE_NAME;
|
||||||
@@ -404,19 +330,55 @@ class NetworkService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async requestGame(monitor) {
|
||||||
* Handles unsupported job types by throwing an error with details.
|
try {
|
||||||
*
|
const { url, port, gameId } = monitor;
|
||||||
* @param {string} type - The unsupported job type that was provided
|
|
||||||
* @throws {Error} An error with service name, method name and unsupported type message
|
const gameResponse = {
|
||||||
*/
|
code: 200,
|
||||||
handleUnsupportedType(type) {
|
status: true,
|
||||||
|
message: "Success",
|
||||||
|
monitorId: monitor._id,
|
||||||
|
type: "game",
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = await this.GameDig.query({
|
||||||
|
type: gameId,
|
||||||
|
host: url,
|
||||||
|
port: port,
|
||||||
|
}).catch((error) => {
|
||||||
|
this.logger.warn({
|
||||||
|
message: error.message,
|
||||||
|
service: this.SERVICE_NAME,
|
||||||
|
method: "requestGame",
|
||||||
|
details: { url, port, gameId },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!state) {
|
||||||
|
gameResponse.code = this.NETWORK_ERROR;
|
||||||
|
gameResponse.status = false;
|
||||||
|
gameResponse.message = "No response";
|
||||||
|
return gameResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
gameResponse.responseTime = state.ping;
|
||||||
|
gameResponse.payload = state;
|
||||||
|
return gameResponse;
|
||||||
|
} catch (error) {
|
||||||
|
error.service = this.SERVICE_NAME;
|
||||||
|
error.method = "requestPing";
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async handleUnsupportedType(type) {
|
||||||
const err = new Error(`Unsupported type: ${type}`);
|
const err = new Error(`Unsupported type: ${type}`);
|
||||||
err.service = this.SERVICE_NAME;
|
err.service = this.SERVICE_NAME;
|
||||||
err.method = "getStatus";
|
err.method = "getStatus";
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Other network requests unrelated to monitoring:
|
||||||
async requestWebhook(type, url, body) {
|
async requestWebhook(type, url, body) {
|
||||||
try {
|
try {
|
||||||
const response = await this.axios.post(url, body, {
|
const response = await this.axios.post(url, body, {
|
||||||
@@ -471,93 +433,6 @@ class NetworkService {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Requests the status of a game monitor.
|
|
||||||
*
|
|
||||||
* @param {Object} monitor - The monitor object to request the status for.
|
|
||||||
* @returns {Promise<Object>} The response from the game status request.
|
|
||||||
* @throws {Error} Throws an error if the request fails or if the monitor is not configured correctly.
|
|
||||||
* @property {string} monitorId - The ID of the monitor.
|
|
||||||
* @property {string} type - The type of the monitor (should be "game").
|
|
||||||
* @property {number} responseTime - The time taken for the request.
|
|
||||||
* @property {Object|null} payload - The game state response or null if the request failed.
|
|
||||||
* @property {boolean} status - Indicates if the request was successful (true) or not (false).
|
|
||||||
* @property {number} code - The status code of the request (200 for success, NETWORK_ERROR for failure).
|
|
||||||
* @property {string} message - A message indicating the result of the request.
|
|
||||||
*/
|
|
||||||
async requestGame(monitor) {
|
|
||||||
try {
|
|
||||||
const { url, port, gameId } = monitor;
|
|
||||||
|
|
||||||
const gameResponse = {
|
|
||||||
code: 200,
|
|
||||||
status: true,
|
|
||||||
message: "Success",
|
|
||||||
monitorId: monitor._id,
|
|
||||||
type: "game",
|
|
||||||
};
|
|
||||||
|
|
||||||
const state = await GameDig.query({
|
|
||||||
type: gameId,
|
|
||||||
host: url,
|
|
||||||
port: port,
|
|
||||||
}).catch((error) => {
|
|
||||||
this.logger.warn({
|
|
||||||
message: error.message,
|
|
||||||
service: this.SERVICE_NAME,
|
|
||||||
method: "requestGame",
|
|
||||||
details: { url, port, gameId },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!state) {
|
|
||||||
gameResponse.status = false;
|
|
||||||
gameResponse.code = this.NETWORK_ERROR;
|
|
||||||
gameResponse.message = "No response";
|
|
||||||
return gameResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
gameResponse.responseTime = state.ping;
|
|
||||||
gameResponse.payload = state;
|
|
||||||
return gameResponse;
|
|
||||||
} catch (error) {
|
|
||||||
error.service = this.SERVICE_NAME;
|
|
||||||
error.method = "requestPing";
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the status of a job based on its type and returns the appropriate response.
|
|
||||||
*
|
|
||||||
* @param {Object} job - The job object containing the data for the status request.
|
|
||||||
* @param {Object} job.data - The data object within the job.
|
|
||||||
* @param {string} job.data.type - The type of the job (e.g., "ping", "http", "pagespeed", "hardware").
|
|
||||||
* @returns {Promise<Object>} The response object from the appropriate request method.
|
|
||||||
* @throws {Error} Throws an error if the job type is unsupported.
|
|
||||||
*/
|
|
||||||
async getStatus(monitor) {
|
|
||||||
const type = monitor.type ?? "unknown";
|
|
||||||
switch (type) {
|
|
||||||
case this.TYPE_PING:
|
|
||||||
return await this.requestPing(monitor);
|
|
||||||
case this.TYPE_HTTP:
|
|
||||||
return await this.requestHttp(monitor);
|
|
||||||
case this.TYPE_PAGESPEED:
|
|
||||||
return await this.requestPagespeed(monitor);
|
|
||||||
case this.TYPE_HARDWARE:
|
|
||||||
return await this.requestHardware(monitor);
|
|
||||||
case this.TYPE_DOCKER:
|
|
||||||
return await this.requestDocker(monitor);
|
|
||||||
case this.TYPE_PORT:
|
|
||||||
return await this.requestPort(monitor);
|
|
||||||
case this.TYPE_GAME:
|
|
||||||
return await this.requestGame(monitor);
|
|
||||||
default:
|
|
||||||
return this.handleUnsupportedType(type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NetworkService;
|
export default NetworkService;
|
||||||
@@ -1,17 +1,14 @@
|
|||||||
import MonitorStats from "../../db/models/MonitorStats.js";
|
import MonitorStats from "../../db/models/MonitorStats.js";
|
||||||
import { safelyParseFloat } from "../../utils/dataUtils.js";
|
|
||||||
const SERVICE_NAME = "StatusService";
|
const SERVICE_NAME = "StatusService";
|
||||||
|
|
||||||
class StatusService {
|
class StatusService {
|
||||||
static SERVICE_NAME = SERVICE_NAME;
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of StatusService.
|
* @param {{
|
||||||
*
|
* buffer: import("./bufferService.js").BufferService
|
||||||
* @param {Object} db - The database instance.
|
* }}
|
||||||
* @param {Object} logger - The logger instance.
|
*/ constructor({ db, logger, buffer }) {
|
||||||
*/
|
|
||||||
constructor({ db, logger, buffer }) {
|
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.buffer = buffer;
|
this.buffer = buffer;
|
||||||
@@ -24,7 +21,7 @@ class StatusService {
|
|||||||
async updateRunningStats({ monitor, networkResponse }) {
|
async updateRunningStats({ monitor, networkResponse }) {
|
||||||
try {
|
try {
|
||||||
const monitorId = monitor._id;
|
const monitorId = monitor._id;
|
||||||
const { responseTime, status, upt_burnt } = networkResponse;
|
const { responseTime, status } = networkResponse;
|
||||||
// Get stats
|
// Get stats
|
||||||
let stats = await MonitorStats.findOne({ monitorId });
|
let stats = await MonitorStats.findOne({ monitorId });
|
||||||
if (!stats) {
|
if (!stats) {
|
||||||
@@ -36,8 +33,6 @@ class StatusService {
|
|||||||
totalDownChecks: 0,
|
totalDownChecks: 0,
|
||||||
uptimePercentage: 0,
|
uptimePercentage: 0,
|
||||||
lastCheck: null,
|
lastCheck: null,
|
||||||
timeSInceLastCheck: 0,
|
|
||||||
uptBurnt: 0,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,12 +77,6 @@ class StatusService {
|
|||||||
// latest check
|
// latest check
|
||||||
stats.lastCheckTimestamp = new Date().getTime();
|
stats.lastCheckTimestamp = new Date().getTime();
|
||||||
|
|
||||||
// UPT burned
|
|
||||||
if (typeof upt_burnt !== "undefined" && upt_burnt !== null) {
|
|
||||||
const currentUptBurnt = safelyParseFloat(stats.uptBurnt);
|
|
||||||
const newUptBurnt = safelyParseFloat(upt_burnt);
|
|
||||||
stats.uptBurnt = currentUptBurnt + newUptBurnt;
|
|
||||||
}
|
|
||||||
await stats.save();
|
await stats.save();
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -119,7 +108,8 @@ class StatusService {
|
|||||||
* @returns {boolean} returnObject.prevStatus - The previous status of the monitor
|
* @returns {boolean} returnObject.prevStatus - The previous status of the monitor
|
||||||
*/
|
*/
|
||||||
updateStatus = async (networkResponse) => {
|
updateStatus = async (networkResponse) => {
|
||||||
this.insertCheck(networkResponse);
|
const check = this.buildCheck(networkResponse);
|
||||||
|
await this.insertCheck(check);
|
||||||
try {
|
try {
|
||||||
const { monitorId, status, code } = networkResponse;
|
const { monitorId, status, code } = networkResponse;
|
||||||
const monitor = await this.db.monitorModule.getMonitorById(monitorId);
|
const monitor = await this.db.monitorModule.getMonitorById(monitorId);
|
||||||
@@ -127,31 +117,67 @@ class StatusService {
|
|||||||
// Update running stats
|
// Update running stats
|
||||||
this.updateRunningStats({ monitor, networkResponse });
|
this.updateRunningStats({ monitor, networkResponse });
|
||||||
|
|
||||||
// No change in monitor status, return early
|
// If the status window size has changed, empty
|
||||||
if (monitor.status === status)
|
while (monitor.statusWindow.length > monitor.statusWindowSize) {
|
||||||
|
monitor.statusWindow.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update status sliding window
|
||||||
|
monitor.statusWindow.push(status);
|
||||||
|
if (monitor.statusWindow.length > monitor.statusWindowSize) {
|
||||||
|
monitor.statusWindow.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (monitor.status === undefined || monitor.status === null) {
|
||||||
|
monitor.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newStatus = monitor.status;
|
||||||
|
let statusChanged = false;
|
||||||
|
const prevStatus = monitor.status;
|
||||||
|
|
||||||
|
// Return early if not enough data points
|
||||||
|
if (monitor.statusWindow.length < monitor.statusWindowSize) {
|
||||||
|
await monitor.save();
|
||||||
return {
|
return {
|
||||||
monitor,
|
monitor,
|
||||||
statusChanged: false,
|
statusChanged: false,
|
||||||
prevStatus: monitor.status,
|
prevStatus,
|
||||||
code,
|
code,
|
||||||
timestamp: new Date().getTime(),
|
timestamp: Date.now(),
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Monitor status changed, save prev status and update monitor
|
// Check if threshold has been met
|
||||||
this.logger.info({
|
const failures = monitor.statusWindow.filter((s) => s === false).length;
|
||||||
service: this.SERVICE_NAME,
|
const failureRate = (failures / monitor.statusWindow.length) * 100;
|
||||||
message: `${monitor.name} went from ${this.getStatusString(monitor.status)} to ${this.getStatusString(status)}`,
|
|
||||||
prevStatus: monitor.status,
|
|
||||||
newStatus: status,
|
|
||||||
});
|
|
||||||
|
|
||||||
const prevStatus = monitor.status;
|
// If threshold has been met and the monitor is not already down, mark down:
|
||||||
monitor.status = status;
|
if (failureRate >= monitor.statusWindowThreshold && monitor.status !== false) {
|
||||||
|
newStatus = false;
|
||||||
|
statusChanged = true;
|
||||||
|
}
|
||||||
|
// If the failure rate is below the threshold and the monitor is down, recover:
|
||||||
|
else if (failureRate <= monitor.statusWindowThreshold && monitor.status === false) {
|
||||||
|
newStatus = true;
|
||||||
|
statusChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statusChanged) {
|
||||||
|
this.logger.info({
|
||||||
|
service: this.SERVICE_NAME,
|
||||||
|
message: `${monitor.name} went from ${this.getStatusString(prevStatus)} to ${this.getStatusString(newStatus)}`,
|
||||||
|
prevStatus,
|
||||||
|
newStatus,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
monitor.status = newStatus;
|
||||||
await monitor.save();
|
await monitor.save();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
monitor,
|
monitor,
|
||||||
statusChanged: true,
|
statusChanged,
|
||||||
prevStatus,
|
prevStatus,
|
||||||
code,
|
code,
|
||||||
timestamp: new Date().getTime(),
|
timestamp: new Date().getTime(),
|
||||||
@@ -192,14 +218,17 @@ class StatusService {
|
|||||||
conn_took,
|
conn_took,
|
||||||
connect_took,
|
connect_took,
|
||||||
tls_took,
|
tls_took,
|
||||||
|
timings,
|
||||||
} = networkResponse;
|
} = networkResponse;
|
||||||
|
|
||||||
const check = {
|
const check = {
|
||||||
monitorId,
|
monitorId,
|
||||||
teamId,
|
teamId,
|
||||||
|
type,
|
||||||
status,
|
status,
|
||||||
statusCode: code,
|
statusCode: code,
|
||||||
responseTime,
|
responseTime,
|
||||||
|
timings: timings || {},
|
||||||
message,
|
message,
|
||||||
first_byte_took,
|
first_byte_took,
|
||||||
body_read_took,
|
body_read_took,
|
||||||
@@ -262,25 +291,24 @@ class StatusService {
|
|||||||
* @param {Object} networkResponse.payload - The payload of the response.
|
* @param {Object} networkResponse.payload - The payload of the response.
|
||||||
* @returns {Promise<void>} A promise that resolves when the check is inserted.
|
* @returns {Promise<void>} A promise that resolves when the check is inserted.
|
||||||
*/
|
*/
|
||||||
insertCheck = async (networkResponse) => {
|
insertCheck = async (check) => {
|
||||||
try {
|
try {
|
||||||
const check = this.buildCheck(networkResponse);
|
|
||||||
if (typeof check === "undefined") {
|
if (typeof check === "undefined") {
|
||||||
this.logger.warn({
|
this.logger.warn({
|
||||||
message: "Failed to build check",
|
message: "Failed to build check",
|
||||||
service: this.SERVICE_NAME,
|
service: this.SERVICE_NAME,
|
||||||
method: "insertCheck",
|
method: "insertCheck",
|
||||||
details: networkResponse,
|
|
||||||
});
|
});
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
this.buffer.addToBuffer({ check, type: networkResponse.type });
|
this.buffer.addToBuffer({ check });
|
||||||
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error({
|
this.logger.error({
|
||||||
message: error.message,
|
message: error.message,
|
||||||
service: error.service || this.SERVICE_NAME,
|
service: error.service || this.SERVICE_NAME,
|
||||||
method: error.method || "insertCheck",
|
method: error.method || "insertCheck",
|
||||||
details: error.details || `Error inserting check for monitor: ${networkResponse?.monitorId}`,
|
details: error.details || `Error inserting check for monitor: ${check?.monitorId}`,
|
||||||
stack: error.stack,
|
stack: error.stack,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,6 +155,8 @@ const createMonitorBodyValidation = joi.object({
|
|||||||
name: joi.string().required(),
|
name: joi.string().required(),
|
||||||
description: joi.string().required(),
|
description: joi.string().required(),
|
||||||
type: joi.string().required(),
|
type: joi.string().required(),
|
||||||
|
statusWindowSize: joi.number().min(1).max(20).default(5),
|
||||||
|
statusWindowThreshold: joi.number().min(1).max(100).default(60),
|
||||||
url: joi.string().required(),
|
url: joi.string().required(),
|
||||||
ignoreTlsErrors: joi.boolean().default(false),
|
ignoreTlsErrors: joi.boolean().default(false),
|
||||||
port: joi.number(),
|
port: joi.number(),
|
||||||
@@ -183,6 +185,8 @@ const createMonitorsBodyValidation = joi.array().items(
|
|||||||
|
|
||||||
const editMonitorBodyValidation = joi.object({
|
const editMonitorBodyValidation = joi.object({
|
||||||
name: joi.string(),
|
name: joi.string(),
|
||||||
|
statusWindowSize: joi.number().min(1).max(20).default(5),
|
||||||
|
statusWindowThreshold: joi.number().min(1).max(100).default(60),
|
||||||
description: joi.string(),
|
description: joi.string(),
|
||||||
interval: joi.number(),
|
interval: joi.number(),
|
||||||
notifications: joi.array().items(joi.string()),
|
notifications: joi.array().items(joi.string()),
|
||||||
|
|||||||
Reference in new issue
Block a user