Merge branch 'develop' of https://github.com/remememe/Checkmate into develop

This commit is contained in:
remememe committed 2025-05-18 14:30:09 +02:00
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:
+9
View File
@@ -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).
+3 -3
View File
@@ -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];
};
+28
View File
@@ -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,
};
});
+1 -1
View File
@@ -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>
+12 -4
View File
@@ -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}
+14 -8
View File
@@ -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.",
}),
+6 -5
View File
@@ -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.",
+5 -4
View File
@@ -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
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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,
+8
View File
@@ -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,
+11 -4
View File
@@ -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"
+10 -1
View File
@@ -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({
+20 -1
View File
@@ -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;
+1 -3
View File
@@ -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(
+3
View File
@@ -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")
+1
View File
@@ -432,6 +432,7 @@ const updateAppSettingsBodyValidation = joi.object({
systemEmailAddress: joi.string().allow(""),
systemEmailPassword: joi.string().allow(""),
systemEmailUser: joi.string().allow(""),
systemEmailConnectionHost: joi.string().allow(""),
});
//****************************************