Merge branch 'develop' of https://github.com/remememe/Checkmate into develop
This commit is contained in:
commit
c22a0cea99
26 files changed
+326
-94
No files matched your search
@@ -11,6 +11,9 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@@ -36,6 +39,9 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
|
||||
@@ -123,9 +123,15 @@ If one or more of these are missing, we may ask you to update your pull request
|
||||
|
||||
## Branching model
|
||||
|
||||
|
||||
- Code contributions should go to the `develop` branch.
|
||||
- `master` is used for stable releases.
|
||||
- Use descriptive branch names, like `fix/login-error` or `feat/add-alerts`.
|
||||
- Make sure that you are using the latest version.
|
||||
- Make sure you run the code locally. The Checkmate [documentation](https://docs.checkmate.so) covers it.
|
||||
- Find out if the functionality is already covered, maybe by an individual configuration.
|
||||
- Perform a [search](/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
|
||||
- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library.
|
||||
|
||||
---
|
||||
|
||||
@@ -133,8 +139,11 @@ If one or more of these are missing, we may ask you to update your pull request
|
||||
|
||||
Thanks for making Checkmate better. We mean it. Whether it’s your first pull request or your 50th, we’re excited to build with you.
|
||||
|
||||
|
||||
PS: feel free to introduce yourself on [Discord](https://discord.gg/YOUR-DISCORD-LINK) and say hi.
|
||||
|
||||
-- Checkmate team
|
||||
|
||||
Also make sure you read the [document about how to make a good pull request](/PULLREQUESTS.md).
|
||||
|
||||
|
||||
@@ -30,11 +30,11 @@ You can see the latest build of [Checkmate](https://checkmate-demo.bluewavelabs.
|
||||
|
||||
## 🔗 User's guide
|
||||
|
||||
Usage instructions can be found [here](https://docs.checkmate.so/). It's still WIP and some of the information there might be outdated as we continuously add features weekly. Rest assured, we are doing our best! :)
|
||||
Usage instructions can be found [here](https://docs.checkmate.so/checkmate-2.1). It's still WIP and some of the information there might be outdated as we continuously add features weekly. Rest assured, we are doing our best! :)
|
||||
|
||||
## 🛠️ Installation
|
||||
|
||||
See installation instructions in [Checkmate documentation portal](https://docs.checkmate.so/quickstart). Alternatively, you can also use [Coolify](https://coolify.io/) or [Elestio](https://elest.io/open-source/checkmate) for a one-click Docker deployment. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions.
|
||||
See installation instructions in [Checkmate documentation portal](https://docs.checkmate.so/checkmate-2.1/users-guide/quickstart). Alternatively, you can also use [Coolify](https://coolify.io/) or [Elestio](https://elest.io/open-source/checkmate) for a one-click Docker deployment. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions.
|
||||
|
||||
## 🏁 Translations
|
||||
|
||||
@@ -118,7 +118,7 @@ Here's how you can contribute:
|
||||
|
||||
0. Star this repo :)
|
||||
1. Check [Contributor's guideline](https://github.com/bluewave-labs/Checkmate/blob/develop/CONTRIBUTING.md). First timers are encouraged to check `good-first-issue` tag.
|
||||
2. Check [project structure](https://docs.checkmate.so/developers-guide/general-project-structure) and [high level overview](https://bluewavelabs.gitbook.io/checkmate/developers-guide/high-level-overview).
|
||||
2. Check [project structure](https://docs.checkmate.so/checkmate-2.1/developers-guide/general-project-structure) and [high level overview](https://bluewavelabs.gitbook.io/checkmate/developers-guide/high-level-overview).
|
||||
3. Read a detailed structure of [Checkmate](https://deepwiki.com/bluewave-labs/Checkmate) if you would like to deep dive into the architecture.
|
||||
4. Open an issue if you believe you've encountered a bug.
|
||||
5. Check for good-first-issue's if you are a newcomer.
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Status from "./status";
|
||||
import Skeleton from "./skeleton";
|
||||
import Button from "@mui/material/Button";
|
||||
import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined";
|
||||
import PauseOutlinedIcon from "@mui/icons-material/PauseOutlined";
|
||||
import PlayArrowOutlinedIcon from "@mui/icons-material/PlayArrowOutlined";
|
||||
|
||||
// Utils
|
||||
import PropTypes from "prop-types";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { usePauseMonitor } from "../../Hooks/useMonitorControls";
|
||||
|
||||
/**
|
||||
* MonitorDetailsControlHeader component displays the control header for monitor details.
|
||||
* It includes status display, pause/resume button, and a configure button for admins.
|
||||
*
|
||||
* @component
|
||||
* @param {Object} props - Component props
|
||||
* @param {string} props.path - The base path for navigation
|
||||
* @param {boolean} [props.isLoading=false] - Flag indicating if the data is loading
|
||||
* @param {boolean} [props.isAdmin=false] - Flag indicating if the user is an admin
|
||||
* @param {Object} props.monitor - The monitor object containing details
|
||||
* @param {Function} props.triggerUpdate - Function to trigger an update
|
||||
* @returns {JSX.Element} The rendered component
|
||||
*/
|
||||
const MonitorDetailsControlHeader = ({
|
||||
path,
|
||||
isLoading = false,
|
||||
isAdmin = false,
|
||||
monitor,
|
||||
triggerUpdate,
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const [pauseMonitor, isPausing, error] = usePauseMonitor({
|
||||
monitorId: monitor?._id,
|
||||
triggerUpdate,
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
return <Skeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Status monitor={monitor} />
|
||||
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
loading={isPausing}
|
||||
startIcon={
|
||||
monitor?.isActive ? <PauseOutlinedIcon /> : <PlayArrowOutlinedIcon />
|
||||
}
|
||||
onClick={() => {
|
||||
pauseMonitor();
|
||||
}}
|
||||
>
|
||||
{monitor?.isActive ? "Pause" : "Resume"}
|
||||
</Button>
|
||||
|
||||
{isAdmin && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
startIcon={<SettingsOutlinedIcon />}
|
||||
onClick={() => navigate(`/${path}/configure/${monitor._id}`)}
|
||||
>
|
||||
Configure
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
MonitorDetailsControlHeader.propTypes = {
|
||||
path: PropTypes.string,
|
||||
isLoading: PropTypes.bool,
|
||||
isAdmin: PropTypes.bool,
|
||||
monitor: PropTypes.object,
|
||||
triggerUpdate: PropTypes.func,
|
||||
};
|
||||
|
||||
export default MonitorDetailsControlHeader;
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Stack, Skeleton } from "@mui/material";
|
||||
|
||||
const SkeletonLayout = () => {
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Skeleton
|
||||
height={40}
|
||||
variant="rounded"
|
||||
width="15%"
|
||||
/>
|
||||
<Skeleton
|
||||
height={40}
|
||||
variant="rounded"
|
||||
width="15%"
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default SkeletonLayout;
|
||||
@@ -0,0 +1,56 @@
|
||||
// Components
|
||||
import Stack from "@mui/material/Stack";
|
||||
import PulseDot from "../../Components/Animated/PulseDot";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Dot from "../../Components/Dot";
|
||||
// Utils
|
||||
import { formatDurationRounded } from "../../Utils/timeUtils";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import useUtils from "../../Pages/Uptime/Monitors/Hooks/useUtils";
|
||||
|
||||
/**
|
||||
* Status component displays the status information of a monitor.
|
||||
* It includes the monitor's name, URL, and check interval.
|
||||
*
|
||||
* @component
|
||||
* @param {Object} props - Component props
|
||||
* @param {Object} props.monitor - The monitor object containing details
|
||||
* @param {string} props.monitor.name - The name of the monitor
|
||||
* @param {string} props.monitor.url - The URL of the monitor
|
||||
* @param {number} props.monitor.interval - The interval at which the monitor checks
|
||||
* @returns {JSX.Element} The rendered component
|
||||
*/
|
||||
const Status = ({ monitor }) => {
|
||||
const theme = useTheme();
|
||||
const { statusColor, determineState } = useUtils();
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Typography variant="h1">{monitor?.name}</Typography>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems={"center"}
|
||||
gap={theme.spacing(4)}
|
||||
>
|
||||
<PulseDot color={statusColor[determineState(monitor)]} />
|
||||
<Typography
|
||||
variant="h2"
|
||||
style={{ fontFamily: "monospace", fontWeight: "bolder" }}
|
||||
>
|
||||
{monitor?.url?.replace(/^https?:\/\//, "") || "..."}
|
||||
</Typography>
|
||||
<Dot />
|
||||
<Typography>
|
||||
Checking every {formatDurationRounded(monitor?.interval)}.
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
Status.propTypes = {
|
||||
monitor: PropTypes.object,
|
||||
};
|
||||
|
||||
export default Status;
|
||||
@@ -3,13 +3,12 @@ import { networkService } from "../main";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { createToast } from "../Utils/toastUtils";
|
||||
|
||||
export const useFetchUptimeMonitorDetails = ({ monitorId, dateRange }) => {
|
||||
export const useFetchUptimeMonitorDetails = ({ monitorId, dateRange, trigger }) => {
|
||||
const [networkError, setNetworkError] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [monitor, setMonitor] = useState(undefined);
|
||||
const [monitorStats, setMonitorStats] = useState(undefined);
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMonitors = async () => {
|
||||
try {
|
||||
@@ -29,7 +28,7 @@ export const useFetchUptimeMonitorDetails = ({ monitorId, dateRange }) => {
|
||||
}
|
||||
};
|
||||
fetchMonitors();
|
||||
}, [dateRange, monitorId, navigate]);
|
||||
}, [dateRange, monitorId, navigate, trigger]);
|
||||
return [monitor, monitorStats, isLoading, networkError];
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { useState } from "react";
|
||||
import { networkService } from "../main";
|
||||
import { createToast } from "../Utils/toastUtils";
|
||||
|
||||
const usePauseMonitor = ({ monitorId, triggerUpdate }) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState(undefined);
|
||||
const pauseMonitor = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const res = await networkService.pauseMonitorById({ monitorId });
|
||||
createToast({
|
||||
body: res.data.data.isActive
|
||||
? "Monitor resumed successfully"
|
||||
: "Monitor paused successfully",
|
||||
});
|
||||
triggerUpdate();
|
||||
} catch (error) {
|
||||
setError(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return [pauseMonitor, isLoading, error];
|
||||
};
|
||||
|
||||
export { usePauseMonitor };
|
||||
@@ -98,9 +98,6 @@ const MonitorsTable = ({ shouldRender, monitors, isAdmin, handleActionMenuDelete
|
||||
const mem = (monitor?.checks[0]?.memory.usage_percent ?? 0) * 100;
|
||||
const disk = (monitor?.checks[0]?.disk[0]?.usage_percent ?? 0) * 100;
|
||||
const status = determineState(monitor);
|
||||
const uptimePercentage = ((monitor?.uptimePercentage ?? 0) * 100)
|
||||
.toFixed(2)
|
||||
.toString();
|
||||
const percentageColor =
|
||||
monitor.uptimePercentage < 0.25
|
||||
? theme.palette.error.main
|
||||
@@ -117,7 +114,6 @@ const MonitorsTable = ({ shouldRender, monitors, isAdmin, handleActionMenuDelete
|
||||
mem,
|
||||
disk,
|
||||
status,
|
||||
uptimePercentage,
|
||||
percentageColor,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ const SettingsAbout = () => {
|
||||
<Typography component="h1">{t("settingsAbout")}</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography component="h2">Checkmate {2.0}</Typography>
|
||||
<Typography component="h2">Checkmate {2.1}</Typography>
|
||||
<Typography sx={{ mt: theme.spacing(2), mb: theme.spacing(6), opacity: 0.6 }}>
|
||||
{t("settingsDevelopedBy")}
|
||||
</Typography>
|
||||
|
||||
@@ -122,6 +122,15 @@ const SettingsEmail = ({
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
<Box>
|
||||
<Typography>{t("settingsEmailConnectionHost")}</Typography>
|
||||
<TextInput
|
||||
name="systemEmailConnectionHost"
|
||||
placeholder="bluewavelabs.ca"
|
||||
value={settingsData?.settings?.systemEmailConnectionHost ?? ""}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Button
|
||||
variant="contained"
|
||||
|
||||
@@ -158,23 +158,6 @@ const Configure = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handlePause = async () => {
|
||||
try {
|
||||
const action = await dispatch(pauseUptimeMonitor({ monitorId }));
|
||||
if (pauseUptimeMonitor.fulfilled.match(action)) {
|
||||
const monitor = action.payload.data;
|
||||
setMonitor(monitor);
|
||||
const state = action?.payload?.data.isActive === false ? "paused" : "resumed";
|
||||
createToast({ body: `Monitor ${state} successfully.` });
|
||||
} else if (pauseUptimeMonitor.rejected.match(action)) {
|
||||
throw new Error(action.error.message);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error pausing monitor: " + monitorId);
|
||||
createToast({ body: "Failed to pause monitor" });
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
const action = await dispatch(updateUptimeMonitor({ monitor: monitor }));
|
||||
@@ -329,38 +312,6 @@ const Configure = () => {
|
||||
ml: "auto",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
loading={isLoading}
|
||||
sx={{
|
||||
pl: theme.spacing(4),
|
||||
pr: theme.spacing(6),
|
||||
mr: theme.spacing(6),
|
||||
"& svg": {
|
||||
mr: theme.spacing(2),
|
||||
width: 22,
|
||||
height: 22,
|
||||
"& path": {
|
||||
stroke: theme.palette.primary.contrastTextTertiary,
|
||||
strokeWidth: 1.7,
|
||||
},
|
||||
},
|
||||
}}
|
||||
onClick={handlePause}
|
||||
>
|
||||
{monitor?.isActive ? (
|
||||
<>
|
||||
<PauseIcon />
|
||||
{t("pause")}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ResumeIcon />
|
||||
{t("resume")}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
loading={isLoading}
|
||||
variant="contained"
|
||||
|
||||
@@ -75,7 +75,7 @@ const ChartBoxes = ({
|
||||
<Typography component="span">
|
||||
{hoveredUptimeData !== null
|
||||
? Math.floor(hoveredUptimeData?.originalAvgResponseTime ?? 0)
|
||||
: Math.floor(monitorData?.groupedUptimePercentage ?? 0 * 100)}
|
||||
: Math.floor((monitorData?.groupedUptimePercentage ?? 0) * 100)}
|
||||
<Typography component="span">
|
||||
{hoveredUptimeData !== null ? " ms" : " %"}
|
||||
</Typography>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Components
|
||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||
import MonitorDetailsControlHeader from "../../../Components/MonitorDetailsControlHeader";
|
||||
import MonitorStatusHeader from "../../../Components/MonitorStatusHeader";
|
||||
import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader";
|
||||
import ChartBoxes from "./Components/ChartBoxes";
|
||||
@@ -36,9 +37,9 @@ const UptimeDetails = () => {
|
||||
|
||||
// Local state
|
||||
const [dateRange, setDateRange] = useState("recent");
|
||||
|
||||
const [page, setPage] = useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(5);
|
||||
const [trigger, setTrigger] = useState(false);
|
||||
|
||||
// Utils
|
||||
const dateFormat =
|
||||
@@ -52,6 +53,7 @@ const UptimeDetails = () => {
|
||||
useFetchUptimeMonitorDetails({
|
||||
monitorId,
|
||||
dateRange,
|
||||
trigger,
|
||||
});
|
||||
|
||||
const monitor = monitorData?.monitor;
|
||||
@@ -73,6 +75,10 @@ const UptimeDetails = () => {
|
||||
});
|
||||
|
||||
// Handlers
|
||||
const triggerUpdate = () => {
|
||||
setTrigger(!trigger);
|
||||
};
|
||||
|
||||
const handlePageChange = (_, newPage) => {
|
||||
setPage(newPage);
|
||||
};
|
||||
@@ -101,11 +107,12 @@ const UptimeDetails = () => {
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<MonitorStatusHeader
|
||||
<MonitorDetailsControlHeader
|
||||
path={"uptime"}
|
||||
isAdmin={isAdmin}
|
||||
shouldRender={!monitorIsLoading}
|
||||
isLoading={monitorIsLoading}
|
||||
monitor={monitor}
|
||||
triggerUpdate={triggerUpdate}
|
||||
/>
|
||||
<GenericFallback>
|
||||
<Typography>{t("distributedUptimeDetailsNoMonitorHistory")}</Typography>
|
||||
@@ -117,11 +124,12 @@ const UptimeDetails = () => {
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<MonitorStatusHeader
|
||||
<MonitorDetailsControlHeader
|
||||
path={"uptime"}
|
||||
isAdmin={isAdmin}
|
||||
isLoading={monitorIsLoading}
|
||||
monitor={monitor}
|
||||
triggerUpdate={triggerUpdate}
|
||||
/>
|
||||
<UptimeStatusBoxes
|
||||
isLoading={monitorIsLoading}
|
||||
|
||||
@@ -109,7 +109,11 @@ const monitorValidation = joi.object({
|
||||
then: joi
|
||||
.string()
|
||||
.trim()
|
||||
.regex(/^[a-z0-9]{64}$/),
|
||||
.regex(/^[a-z0-9]{64}$/)
|
||||
.messages({
|
||||
"string.empty": "This field is required.",
|
||||
"string.pattern.base": "Please enter a valid 64-character Docker container ID.",
|
||||
}),
|
||||
otherwise: joi
|
||||
.string()
|
||||
.trim()
|
||||
@@ -156,13 +160,12 @@ const monitorValidation = joi.object({
|
||||
}
|
||||
|
||||
return value;
|
||||
})
|
||||
.messages({
|
||||
"string.empty": "This field is required.",
|
||||
"string.uri": "The URL you provided is not valid.",
|
||||
"string.invalidUrl": "Please enter a valid URL with optional port",
|
||||
}),
|
||||
})
|
||||
.messages({
|
||||
"string.empty": "This field is required.",
|
||||
"string.uri": "The URL you provided is not valid.",
|
||||
"string.invalidUrl": "Please enter a valid URL with optional port",
|
||||
"string.pattern.base": "Please enter a valid container ID.",
|
||||
}),
|
||||
port: joi
|
||||
.number()
|
||||
@@ -171,7 +174,7 @@ const monitorValidation = joi.object({
|
||||
.max(65535)
|
||||
.when("type", {
|
||||
is: "port",
|
||||
then: joi.required().messages({
|
||||
then: joi.number().messages({
|
||||
"number.base": "Port must be a number.",
|
||||
"number.min": "Port must be at least 1.",
|
||||
"number.max": "Port must be at most 65535.",
|
||||
@@ -265,6 +268,7 @@ const statusPageValidation = joi.object({
|
||||
showUptimePercentage: joi.boolean(),
|
||||
showCharts: joi.boolean(),
|
||||
});
|
||||
|
||||
const settingsValidation = joi.object({
|
||||
checkTTL: joi.number().required().messages({
|
||||
"string.empty": "Please enter a value",
|
||||
@@ -278,6 +282,7 @@ const settingsValidation = joi.object({
|
||||
systemEmailAddress: joi.string().allow(""),
|
||||
systemEmailPassword: joi.string().allow(""),
|
||||
systemEmailUser: joi.string().allow(""),
|
||||
systemEmailConnectionHost: joi.string().allow(""),
|
||||
});
|
||||
|
||||
const dayjsValidator = (value, helpers) => {
|
||||
@@ -307,6 +312,7 @@ const advancedSettingsValidation = joi.object({
|
||||
systemEmailPort: joi.number().allow(null, ""),
|
||||
systemEmailAddress: joi.string().allow(""),
|
||||
systemEmailPassword: joi.string().allow(""),
|
||||
systemEmailConnectionHost: joi.string().allow(""),
|
||||
jwtTTLNum: joi.number().messages({
|
||||
"number.base": "JWT TTL is required.",
|
||||
}),
|
||||
|
||||
@@ -114,12 +114,13 @@
|
||||
"settingsFailedToDeleteMonitors": "Failed to delete all monitors",
|
||||
"settingsEmail": "Email settings",
|
||||
"settingsEmailDescription": "Configure email settings",
|
||||
"settingsEmailHost": "Email host",
|
||||
"settingsEmailPort": "Email port",
|
||||
"settingsEmailAddress": "Email address",
|
||||
"settingsEmailPassword": "Email password",
|
||||
"settingsEmailUser": "Email user",
|
||||
"settingsEmailHost": "Email host - Hostname or IP address of the SMTP server",
|
||||
"settingsEmailPort": "Email port - Port to connect to",
|
||||
"settingsEmailAddress": "Email address - Used for authentication",
|
||||
"settingsEmailPassword": "Email password - Password for authentication",
|
||||
"settingsEmailUser": "Email user - Username for authentication, overrides email address if specified",
|
||||
"settingsEmailFieldResetLabel": "Password is set. Click Reset to change it.",
|
||||
"settingsEmailConnectionHost": "Email connection host - Hostname to use in the HELO/EHLO greeting",
|
||||
"backendUnreachable": "Server Connection Error",
|
||||
"backendUnreachableMessage": "We're unable to connect to the server. Please check your internet connection or verify your deployment configuration if the problem persists.",
|
||||
"backendUnreachableError": "Cannot connect to the server. Please try again later.",
|
||||
|
||||
Vendored
+5
-4
@@ -11,11 +11,12 @@ declare -A services=(
|
||||
["bluewaveuptime/uptime_server"]="./docker/dist/server.Dockerfile"
|
||||
)
|
||||
|
||||
# Loop through each service and build the corresponding image
|
||||
for service in "${!services[@]}"; do
|
||||
docker build -f "${services[$service]}" -t "$service" .
|
||||
|
||||
# Check if the build succeeded
|
||||
docker buildx build \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
-f "${services[$service]}" \
|
||||
-t "$service" \
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error building $service image. Exiting..."
|
||||
exit 1
|
||||
|
||||
@@ -595,8 +595,8 @@ class MonitorController {
|
||||
{ new: true }
|
||||
);
|
||||
monitor.isActive === true
|
||||
? await this.jobQueue.deleteJob(monitor)
|
||||
: await this.jobQueue.addJob(monitor._id, monitor);
|
||||
? await this.jobQueue.addJob(monitor._id, monitor)
|
||||
: await this.jobQueue.deleteJob(monitor);
|
||||
|
||||
return res.success({
|
||||
msg: monitor.isActive
|
||||
|
||||
@@ -13,6 +13,7 @@ class SettingsController {
|
||||
getAppSettings = async (req, res, next) => {
|
||||
const dbSettings = await this.settingsService.getDBSettings();
|
||||
const sanitizedSettings = { ...dbSettings };
|
||||
delete sanitizedSettings.version;
|
||||
|
||||
const returnSettings = {
|
||||
pagespeedKeySet: false,
|
||||
@@ -27,7 +28,6 @@ class SettingsController {
|
||||
returnSettings.emailPasswordSet = true;
|
||||
delete sanitizedSettings.systemEmailPassword;
|
||||
}
|
||||
|
||||
returnSettings.settings = sanitizedSettings;
|
||||
return res.success({
|
||||
msg: this.stringService.getAppSettings,
|
||||
|
||||
@@ -28,12 +28,20 @@ const AppSettingsSchema = mongoose.Schema(
|
||||
systemEmailUser: {
|
||||
type: String,
|
||||
},
|
||||
systemEmailConnectionHost: {
|
||||
type: String,
|
||||
default: "localhost",
|
||||
},
|
||||
singleton: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
unique: true,
|
||||
default: true,
|
||||
},
|
||||
version: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
|
||||
Generated
+11
-4
@@ -26,8 +26,9 @@
|
||||
"mailersend": "^2.2.0",
|
||||
"mjml": "^5.0.0-alpha.4",
|
||||
"mongoose": "^8.3.3",
|
||||
"multer": "1.4.5-lts.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"nodemailer": "^6.9.14",
|
||||
"papaparse": "^5.5.2",
|
||||
"ping": "0.4.4",
|
||||
"sharp": "0.33.5",
|
||||
"ssl-checker": "2.0.10",
|
||||
@@ -5499,6 +5500,12 @@
|
||||
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
|
||||
"license": "BlueOak-1.0.0"
|
||||
},
|
||||
"node_modules/papaparse": {
|
||||
"version": "5.5.2",
|
||||
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.2.tgz",
|
||||
"integrity": "sha512-PZXg8UuAc4PcVwLosEEDYjPyfWnTEhOrUfdv+3Bx+NuAb+5NhDmXzg5fHWmdCh1mP5p7JAZfFr3IMQfcntNAdA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
@@ -7290,9 +7297,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "6.21.1",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz",
|
||||
"integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==",
|
||||
"version": "6.21.3",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
|
||||
"integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
|
||||
@@ -80,7 +80,16 @@ class BufferService {
|
||||
});
|
||||
continue;
|
||||
}
|
||||
await operation(buffer);
|
||||
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.logger.info({
|
||||
|
||||
@@ -11,6 +11,7 @@ const SERVICE_NAME = "EmailService";
|
||||
*/
|
||||
class EmailService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
/**
|
||||
* Constructs an instance of the EmailService, initializing template loaders and the email transporter.
|
||||
* @param {Object} settingsService - The settings service to get email configuration.
|
||||
@@ -96,9 +97,10 @@ class EmailService {
|
||||
systemEmailUser,
|
||||
systemEmailAddress,
|
||||
systemEmailPassword,
|
||||
systemEmailConnectionHost,
|
||||
} = await this.settingsService.getDBSettings();
|
||||
|
||||
const emailConfig = {
|
||||
const baseEmailConfig = {
|
||||
host: systemEmailHost,
|
||||
port: systemEmailPort,
|
||||
secure: true,
|
||||
@@ -109,6 +111,22 @@ class EmailService {
|
||||
connectionTimeout: 5000,
|
||||
};
|
||||
|
||||
const isSmtps = Number(systemEmailPort) === 465;
|
||||
|
||||
const emailConfig = !isSmtps
|
||||
? {
|
||||
...baseEmailConfig,
|
||||
name: systemEmailConnectionHost || "localhost",
|
||||
secure: false,
|
||||
pool: true,
|
||||
tls: { rejectUnauthorized: false },
|
||||
}
|
||||
: baseEmailConfig;
|
||||
|
||||
if (!isSmtps) {
|
||||
delete emailConfig.auth;
|
||||
}
|
||||
|
||||
this.transporter = this.nodemailer.createTransport(emailConfig);
|
||||
|
||||
const buildHtml = async (template, context) => {
|
||||
@@ -149,4 +167,5 @@ class EmailService {
|
||||
return info?.messageId;
|
||||
};
|
||||
}
|
||||
|
||||
export default EmailService;
|
||||
@@ -257,9 +257,7 @@ class NotificationService {
|
||||
const alerts = {
|
||||
cpu: cpuThreshold !== -1 && cpuUsage > cpuThreshold ? true : false,
|
||||
memory: memoryThreshold !== -1 && memoryUsage > memoryThreshold ? true : false,
|
||||
disk: disk.some((d) => diskThreshold !== -1 && d.usage_percent > diskThreshold)
|
||||
? true
|
||||
: false,
|
||||
disk: disk?.some(d => diskThreshold !== -1 && typeof d?.usage_percent === "number" && d?.usage_percent > diskThreshold) ?? false,
|
||||
};
|
||||
|
||||
const notifications = await this.db.getNotificationsByMonitorId(
|
||||
|
||||
@@ -59,6 +59,9 @@ class SettingsService {
|
||||
}
|
||||
|
||||
async getDBSettings() {
|
||||
// Remove any old settings
|
||||
await this.appSettings.deleteMany({ version: { $exists: false } });
|
||||
|
||||
let settings = await this.appSettings
|
||||
.findOne({ singleton: true })
|
||||
.select("-__v -_id -createdAt -updatedAt -singleton")
|
||||
|
||||
@@ -432,6 +432,7 @@ const updateAppSettingsBodyValidation = joi.object({
|
||||
systemEmailAddress: joi.string().allow(""),
|
||||
systemEmailPassword: joi.string().allow(""),
|
||||
systemEmailUser: joi.string().allow(""),
|
||||
systemEmailConnectionHost: joi.string().allow(""),
|
||||
});
|
||||
|
||||
//****************************************
|
||||
|
||||
Reference in new issue
Block a user