diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 000000000..d4b59627d
Binary files /dev/null and b/.DS_Store differ
diff --git a/Client/package-lock.json b/Client/package-lock.json
index 556caf1bc..febb13eb1 100644
--- a/Client/package-lock.json
+++ b/Client/package-lock.json
@@ -11,10 +11,10 @@
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@fontsource/roboto": "^5.0.13",
- "@hello-pangea/dnd": "^17.0.0",
- "@mui/icons-material": "6.4.3",
- "@mui/lab": "6.0.0-beta.26",
- "@mui/material": "6.4.3",
+ "@hello-pangea/dnd": "^18.0.0",
+ "@mui/icons-material": "6.4.4",
+ "@mui/lab": "6.0.0-beta.27",
+ "@mui/material": "6.4.4",
"@mui/x-charts": "^7.5.1",
"@mui/x-data-grid": "7.26.0",
"@mui/x-date-pickers": "7.26.0",
@@ -285,9 +285,9 @@
}
},
"node_modules/@babel/runtime": {
- "version": "7.26.0",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
- "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
+ "version": "7.26.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.7.tgz",
+ "integrity": "sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==",
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.14.0"
@@ -1002,21 +1002,20 @@
}
},
"node_modules/@hello-pangea/dnd": {
- "version": "17.0.0",
- "resolved": "https://registry.npmjs.org/@hello-pangea/dnd/-/dnd-17.0.0.tgz",
- "integrity": "sha512-LDDPOix/5N0j5QZxubiW9T0M0+1PR0rTDWeZF5pu1Tz91UQnuVK4qQ/EjY83Qm2QeX0eM8qDXANfDh3VVqtR4Q==",
+ "version": "18.0.1",
+ "resolved": "https://registry.npmjs.org/@hello-pangea/dnd/-/dnd-18.0.1.tgz",
+ "integrity": "sha512-xojVWG8s/TGrKT1fC8K2tIWeejJYTAeJuj36zM//yEm/ZrnZUSFGS15BpO+jGZT1ybWvyXmeDJwPYb4dhWlbZQ==",
+ "license": "Apache-2.0",
"dependencies": {
- "@babel/runtime": "^7.25.6",
+ "@babel/runtime": "^7.26.7",
"css-box-model": "^1.2.1",
- "memoize-one": "^6.0.0",
"raf-schd": "^4.0.3",
- "react-redux": "^9.1.2",
- "redux": "^5.0.1",
- "use-memo-one": "^1.1.3"
+ "react-redux": "^9.2.0",
+ "redux": "^5.0.1"
},
"peerDependencies": {
- "react": "^18.0.0",
- "react-dom": "^18.0.0"
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
}
},
"node_modules/@humanwhocodes/config-array": {
@@ -1215,9 +1214,9 @@
}
},
"node_modules/@mui/core-downloads-tracker": {
- "version": "6.4.3",
- "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.3.tgz",
- "integrity": "sha512-hlyOzo2ObarllAOeT1ZSAusADE5NZNencUeIvXrdQ1Na+FL1lcznhbxfV5He1KqGiuR8Az3xtCUcYKwMVGFdzg==",
+ "version": "6.4.4",
+ "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.4.tgz",
+ "integrity": "sha512-r+J0EditrekkTtO2CnCBCOGpNaDYwJqz8lH4rj6o/anDcskZFJodBlG8aCJkS8DL/CF/9EHS+Gz53EbmYEnQbw==",
"license": "MIT",
"funding": {
"type": "opencollective",
@@ -1225,9 +1224,9 @@
}
},
"node_modules/@mui/icons-material": {
- "version": "6.4.3",
- "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.3.tgz",
- "integrity": "sha512-3IY9LpjkwIJVgL/SkZQKKCUcumdHdQEsJaIavvsQze2QEztBt0HJ17naToN0DBBdhKdtwX5xXrfD6ZFUeWWk8g==",
+ "version": "6.4.4",
+ "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.4.tgz",
+ "integrity": "sha512-uF1chGaoFmYdRUomK6f8kgJfWosk9A3HXWiVD0vQm+2mE7f25eTQ1E8RRO11LXpnUBqu8Rbv/uGlpnjT/u1Ksg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0"
@@ -1240,7 +1239,7 @@
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
- "@mui/material": "^6.4.3",
+ "@mui/material": "^6.4.4",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
@@ -1251,9 +1250,9 @@
}
},
"node_modules/@mui/lab": {
- "version": "6.0.0-beta.26",
- "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-6.0.0-beta.26.tgz",
- "integrity": "sha512-auu2dXp6jslzW4Cp0tfYWv0xO9FmuwROsjyWcB9wPlAsEoWhh5N1FW8dqESDwaSKqFz5LwV+Y2vsYjYsYX9aOw==",
+ "version": "6.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-6.0.0-beta.27.tgz",
+ "integrity": "sha512-weLxPsCs2wJKgWKf46shXHE+x7qlf5VxMK3P+4HsWasMakV/uTmxsoT7PG3QCvakGQ2TdpZtQLE2umJKC0mvKQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0",
@@ -1274,7 +1273,7 @@
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
- "@mui/material": "^6.4.3",
+ "@mui/material": "^6.4.4",
"@mui/material-pigment-css": "^6.4.3",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
@@ -1296,13 +1295,13 @@
}
},
"node_modules/@mui/material": {
- "version": "6.4.3",
- "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.3.tgz",
- "integrity": "sha512-ubtQjplbWneIEU8Y+4b2VA0CDBlyH5I3AmVFGmsLyDe/bf0ubxav5t11c8Afem6rkSFWPlZA2DilxmGka1xiKQ==",
+ "version": "6.4.4",
+ "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.4.tgz",
+ "integrity": "sha512-ISVPrIsPQsxnwvS40C4u03AuNSPigFeS2+n1qpuEZ94hDsdMi19dQM2JcC9CHEhXecSIQjP1RTyY0mPiSpSrFQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0",
- "@mui/core-downloads-tracker": "^6.4.3",
+ "@mui/core-downloads-tracker": "^6.4.4",
"@mui/system": "^6.4.3",
"@mui/types": "^7.2.21",
"@mui/utils": "^6.4.3",
@@ -5333,11 +5332,6 @@
"node": ">= 0.4"
}
},
- "node_modules/memoize-one": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
- "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
- },
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -6886,14 +6880,6 @@
"punycode": "^2.1.0"
}
},
- "node_modules/use-memo-one": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz",
- "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==",
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
- }
- },
"node_modules/use-sync-external-store": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
diff --git a/Client/package.json b/Client/package.json
index d4c26e1c5..7ecc398c0 100644
--- a/Client/package.json
+++ b/Client/package.json
@@ -14,10 +14,10 @@
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@fontsource/roboto": "^5.0.13",
- "@hello-pangea/dnd": "^17.0.0",
- "@mui/icons-material": "6.4.3",
- "@mui/lab": "6.0.0-beta.26",
- "@mui/material": "6.4.3",
+ "@hello-pangea/dnd": "^18.0.0",
+ "@mui/icons-material": "6.4.4",
+ "@mui/lab": "6.0.0-beta.27",
+ "@mui/material": "6.4.4",
"@mui/x-charts": "^7.5.1",
"@mui/x-data-grid": "7.26.0",
"@mui/x-date-pickers": "7.26.0",
diff --git a/Client/src/Components/LanguageSelector.jsx b/Client/src/Components/LanguageSelector.jsx
index 7a809065f..ca8e9207c 100644
--- a/Client/src/Components/LanguageSelector.jsx
+++ b/Client/src/Components/LanguageSelector.jsx
@@ -1,17 +1,16 @@
-import { useState } from "react";
import { useTranslation } from "react-i18next";
import { Box, MenuItem, Select, Stack } from "@mui/material";
import { useTheme } from "@emotion/react";
import "flag-icons/css/flag-icons.min.css";
+import { useSelector } from "react-redux";
const LanguageSelector = () => {
const { i18n } = useTranslation();
const theme = useTheme();
- const [language, setLanguage] = useState(i18n.language || "gb");
+ const { language } = useSelector((state) => state.ui);
const handleChange = (event) => {
const newLang = event.target.value;
- setLanguage(newLang);
i18n.changeLanguage(newLang);
};
diff --git a/Client/src/Components/Skeletons/FullPage/index.jsx b/Client/src/Components/Skeletons/FullPage/index.jsx
new file mode 100644
index 000000000..233fc3c06
--- /dev/null
+++ b/Client/src/Components/Skeletons/FullPage/index.jsx
@@ -0,0 +1,14 @@
+import { Stack, Skeleton } from "@mui/material";
+
+export const SkeletonLayout = () => {
+ return (
+
+
+
+ );
+};
+
+export default SkeletonLayout;
diff --git a/Client/src/Features/UI/uiSlice.js b/Client/src/Features/UI/uiSlice.js
index 4565c6c5d..5731307ec 100644
--- a/Client/src/Features/UI/uiSlice.js
+++ b/Client/src/Features/UI/uiSlice.js
@@ -23,6 +23,7 @@ const initialState = {
greeting: { index: 0, lastUpdate: null },
timezone: "America/Toronto",
distributedUptimeEnabled: false,
+ language: "gb",
};
const uiSlice = createSlice({
@@ -51,6 +52,9 @@ const uiSlice = createSlice({
setTimezone(state, action) {
state.timezone = action.payload.timezone;
},
+ setLanguage(state, action) {
+ state.language = action.payload;
+ },
},
});
@@ -62,4 +66,5 @@ export const {
setGreeting,
setTimezone,
setDistributedUptimeEnabled,
+ setLanguage,
} = uiSlice.actions;
diff --git a/Client/src/Pages/DistributedUptimeStatus/Status/index.jsx b/Client/src/Pages/DistributedUptimeStatus/Status/index.jsx
index 89f97a4d0..ef77b3032 100644
--- a/Client/src/Pages/DistributedUptimeStatus/Status/index.jsx
+++ b/Client/src/Pages/DistributedUptimeStatus/Status/index.jsx
@@ -75,9 +75,9 @@ const DistributedUptimeStatus = () => {
marginY={theme.spacing(4)}
color={theme.palette.primary.contrastTextTertiary}
>
- A public status page is not set up.
+ A status page is not set up.
- Please contact to your administrator
+ Please contact your administrator
);
diff --git a/Client/src/Pages/Maintenance/MaintenanceTable/index.jsx b/Client/src/Pages/Maintenance/MaintenanceTable/index.jsx
index 9bdce5887..ea8a1d7a3 100644
--- a/Client/src/Pages/Maintenance/MaintenanceTable/index.jsx
+++ b/Client/src/Pages/Maintenance/MaintenanceTable/index.jsx
@@ -34,7 +34,6 @@ const MaintenanceTable = ({
updateCallback,
}) => {
const { rowsPerPage } = useSelector((state) => state.ui.maintenance);
- console.log(rowsPerPage);
const dispatch = useDispatch();
const handleChangePage = (event, newPage) => {
@@ -175,8 +174,6 @@ const MaintenanceTable = ({
setSort({ field, order });
};
- console.log(handleChangePage);
-
return (
<>
{
if (typeof error === "undefined") {
const success = await createStatusPage({ form });
if (success) {
- createToast({ body: "Status page created successfully" });
+ createToast({
+ body: isCreate
+ ? "Status page created successfully"
+ : "Status page updated successfully",
+ });
navigate(`/status/uptime/${form.url}`);
}
return;
diff --git a/Client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx b/Client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx
new file mode 100644
index 000000000..746d10f42
--- /dev/null
+++ b/Client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx
@@ -0,0 +1,85 @@
+import DataTable from "../../../../../Components/Table";
+import { useTheme } from "@emotion/react";
+import { useNavigate } from "react-router-dom";
+import { ColoredLabel } from "../../../../../Components/Label";
+import ArrowOutwardIcon from "@mui/icons-material/ArrowOutward";
+import { Stack, Typography } from "@mui/material";
+const StatusPagesTable = ({ data }) => {
+ const theme = useTheme();
+ const navigate = useNavigate();
+ const headers = [
+ {
+ id: "name",
+ content: "Status page name",
+ render: (row) => {
+ return row.companyName;
+ },
+ },
+ {
+ id: "url",
+ content: "URL",
+ render: (row) => {
+ return (
+
+ {`/${row.url}`}
+
+
+ );
+ },
+ },
+ {
+ id: "type",
+ content: "Type",
+ render: (row) => {
+ return row.type;
+ },
+ },
+ {
+ id: "status",
+ content: "Status",
+ render: (row) => {
+ return (
+
+ );
+ },
+ },
+ ];
+
+ const handleRowClick = (statusPage) => {
+ if (statusPage.type === "distributed") {
+ navigate(`/status/distributed/${statusPage.url}`);
+ } else if (statusPage.type === "uptime") {
+ navigate(`/status/uptime/${statusPage.url}`);
+ }
+ };
+
+ return (
+ {
+ handleRowClick(row);
+ },
+ }}
+ headers={headers}
+ data={data}
+ />
+ );
+};
+
+export default StatusPagesTable;
diff --git a/Client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx b/Client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx
index 7565f129a..26eec8104 100644
--- a/Client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx
+++ b/Client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx
@@ -20,7 +20,9 @@ const useStatusPagesFetch = () => {
setStatusPages(res?.data?.data);
} catch (error) {
setNetworkError(true);
- createToast(error.message, "error");
+ createToast({
+ body: error.message,
+ });
} finally {
setIsLoading(false);
}
diff --git a/Client/src/Pages/StatusPage/StatusPages/index.jsx b/Client/src/Pages/StatusPage/StatusPages/index.jsx
index cf5b83289..9027467a4 100644
--- a/Client/src/Pages/StatusPage/StatusPages/index.jsx
+++ b/Client/src/Pages/StatusPage/StatusPages/index.jsx
@@ -4,11 +4,12 @@ import Breadcrumbs from "../../../Components/Breadcrumbs";
import Fallback from "../../../Components/Fallback";
import MonitorCreateHeader from "../../../Components/MonitorCreateHeader";
import GenericFallback from "../../../Components/GenericFallback";
+import StatusPagesTable from "./Components/StatusPagesTable";
+import SkeletonLayout from "../../../Components/Skeletons/FullPage";
// Utils
import { useTheme } from "@emotion/react";
import { useStatusPagesFetch } from "./Hooks/useStatusPagesFetch";
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
-import { useNavigate } from "react-router";
const BREADCRUMBS = [{ name: `Status Pages`, path: "" }];
const StatusPages = () => {
@@ -16,29 +17,9 @@ const StatusPages = () => {
const theme = useTheme();
const isAdmin = useIsAdmin();
const [isLoading, networkError, statusPages] = useStatusPagesFetch();
- const navigate = useNavigate();
- // Handlers
- const handleStatusPageClick = (statusPage) => {
- if (statusPage.type === "distributed") {
- navigate(`/status/distributed/${statusPage.url}`);
- } else if (statusPage.type === "uptime") {
- navigate(`/status/uptime/${statusPage.url}`);
- }
- };
-
- if (!isLoading && typeof statusPages === "undefined") {
- return (
-
- );
+ if (isLoading) {
+ return ;
}
if (networkError === true) {
@@ -55,6 +36,21 @@ const StatusPages = () => {
);
}
+
+ if (!isLoading && typeof statusPages !== "undefined" && statusPages.length === 0) {
+ return (
+
+ );
+ }
return (
@@ -63,19 +59,7 @@ const StatusPages = () => {
isAdmin={isAdmin}
path="/status/uptime/create"
/>
- {statusPages?.map((statusPage) => {
- return (
- handleStatusPageClick(statusPage)}
- sx={{ cursor: "pointer" }}
- >
- Company Name: {statusPage.companyName}
- Status page URL: {statusPage.url}
- Type: {statusPage.type}
-
- );
- })}
+
);
};
diff --git a/Client/src/Pages/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx b/Client/src/Pages/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx
index e94af8441..776b66a6f 100644
--- a/Client/src/Pages/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx
+++ b/Client/src/Pages/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx
@@ -85,9 +85,9 @@ const ResponseGaugeChart = ({ avgResponseTime }) => {
{responseTime} ms
diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js
index ebb726a2c..515ae5930 100644
--- a/Client/src/Utils/NetworkService.js
+++ b/Client/src/Utils/NetworkService.js
@@ -1015,11 +1015,12 @@ class NetworkService {
async createStatusPage(config) {
const { authToken, user, form, isCreate } = config;
+
const fd = new FormData();
fd.append("teamId", user.teamId);
fd.append("userId", user._id);
fd.append("type", form.type);
- form.isPublished && fd.append("isPublished", form.isPublished);
+ form.isPublished !== undefined && fd.append("isPublished", form.isPublished);
form.companyName && fd.append("companyName", form.companyName);
form.url && fd.append("url", form.url);
form.timezone && fd.append("timezone", form.timezone);
diff --git a/Client/src/Utils/i18n.js b/Client/src/Utils/i18n.js
index f7528f4b5..6243a2599 100644
--- a/Client/src/Utils/i18n.js
+++ b/Client/src/Utils/i18n.js
@@ -1,5 +1,7 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
+import { setLanguage } from "../Features/UI/uiSlice";
+import store from "../store";
const primaryLanguage = "gb";
@@ -13,11 +15,12 @@ Object.keys(translations).forEach((path) => {
};
});
-const savedLanguage = localStorage.getItem("language") || primaryLanguage;
+const savedLanguage = store.getState()?.ui?.language;
+const initialLanguage = savedLanguage || primaryLanguage;
i18n.use(initReactI18next).init({
resources,
- lng: savedLanguage,
+ lng: initialLanguage,
fallbackLng: primaryLanguage,
debug: import.meta.env.MODE === "development",
ns: ["translation"],
@@ -28,7 +31,7 @@ i18n.use(initReactI18next).init({
});
i18n.on("languageChanged", (lng) => {
- localStorage.setItem("language", lng);
+ store.dispatch(setLanguage(lng));
});
export default i18n;
diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js
index 0ffa612f3..94952e3c7 100644
--- a/Server/controllers/authController.js
+++ b/Server/controllers/authController.js
@@ -8,7 +8,6 @@ import {
newPasswordValidation,
} from "../validation/joi.js";
import logger from "../utils/logger.js";
-import { errorMessages, successMessages } from "../utils/messages.js";
import jwt from "jsonwebtoken";
import { getTokenFromHeaders, tokenType } from "../utils/utils.js";
import crypto from "crypto";
@@ -16,11 +15,12 @@ import { handleValidationError, handleError } from "./controllerUtils.js";
const SERVICE_NAME = "authController";
class AuthController {
- constructor(db, settingsService, emailService, jobQueue) {
+ constructor(db, settingsService, emailService, jobQueue, stringService) {
this.db = db;
this.settingsService = settingsService;
this.emailService = emailService;
this.jobQueue = jobQueue;
+ this.stringService = stringService;
}
/**
@@ -85,7 +85,7 @@ class AuthController {
const newUser = await this.db.insertUser({ ...req.body }, req.file);
logger.info({
- message: successMessages.AUTH_CREATE_USER,
+ message: this.stringService.authCreateUser,
service: SERVICE_NAME,
details: newUser._id,
});
@@ -116,7 +116,7 @@ class AuthController {
});
res.success({
- msg: successMessages.AUTH_CREATE_USER,
+ msg: this.stringService.authCreateUser,
data: { user: newUser, token: token, refreshToken: refreshToken },
});
} catch (error) {
@@ -153,7 +153,7 @@ class AuthController {
// Compare password
const match = await user.comparePassword(password);
if (match !== true) {
- const error = new Error(errorMessages.AUTH_INCORRECT_PASSWORD);
+ const error = new Error(this.stringService.authIncorrectPassword);
error.status = 401;
next(error);
return;
@@ -176,7 +176,7 @@ class AuthController {
userWithoutPassword.avatarImage = user.avatarImage;
return res.success({
- msg: successMessages.AUTH_LOGIN_USER,
+ msg: this.stringService.authLoginUser,
data: {
user: userWithoutPassword,
token: token,
@@ -200,13 +200,14 @@ class AuthController {
* @throws {Error} If there is an error during the process such as any of the token is not received
*/
refreshAuthToken = async (req, res, next) => {
+
try {
// check for refreshToken
const refreshToken = req.headers["x-refresh-token"];
if (!refreshToken) {
// No refresh token provided
- const error = new Error(errorMessages.NO_REFRESH_TOKEN);
+ const error = new Error(this.stringService.noRefreshToken);
error.status = 401;
error.service = SERVICE_NAME;
error.method = "refreshAuthToken";
@@ -221,8 +222,8 @@ class AuthController {
// Invalid or expired refresh token, trigger logout
const errorMessage =
refreshErr.name === "TokenExpiredError"
- ? errorMessages.EXPIRED_REFRESH_TOKEN
- : errorMessages.INVALID_REFRESH_TOKEN;
+ ? this.stringService.expiredAuthToken
+ : this.stringService.invalidAuthToken;
const error = new Error(errorMessage);
error.status = 401;
error.service = SERVICE_NAME;
@@ -243,7 +244,7 @@ class AuthController {
);
return res.success({
- msg: successMessages.AUTH_TOKEN_REFRESHED,
+ msg: this.stringService.authTokenRefreshed,
data: { user: payloadData, token: newAuthToken, refreshToken: refreshToken },
});
} catch (error) {
@@ -265,6 +266,7 @@ class AuthController {
* @throws {Error} If there is an error during the process, especially if there is a validation error (422), the user is unauthorized (401), or the password is incorrect (403).
*/
editUser = async (req, res, next) => {
+
try {
await editUserParamValidation.validateAsync(req.params);
await editUserBodyValidation.validateAsync(req.body);
@@ -276,7 +278,7 @@ class AuthController {
// TODO is this neccessary any longer? Verify ownership middleware should handle this
if (req.params.userId !== req.user._id.toString()) {
- const error = new Error(errorMessages.AUTH_UNAUTHORIZED);
+ const error = new Error(this.stringService.unauthorized);
error.status = 401;
error.service = SERVICE_NAME;
next(error);
@@ -300,7 +302,7 @@ class AuthController {
// If not a match, throw a 403
// 403 instead of 401 to avoid triggering axios interceptor
if (!match) {
- const error = new Error(errorMessages.AUTH_INCORRECT_PASSWORD);
+ const error = new Error(this.stringService.authIncorrectPassword);
error.status = 403;
next(error);
return;
@@ -311,7 +313,7 @@ class AuthController {
const updatedUser = await this.db.updateUser(req, res);
res.success({
- msg: successMessages.AUTH_UPDATE_USER,
+ msg: this.stringService.authUpdateUser,
data: updatedUser,
});
} catch (error) {
@@ -333,7 +335,7 @@ class AuthController {
const superAdminExists = await this.db.checkSuperadmin(req, res);
return res.success({
- msg: successMessages.AUTH_ADMIN_EXISTS,
+ msg: this.stringService.authAdminExists,
data: superAdminExists,
});
} catch (error) {
@@ -379,7 +381,7 @@ class AuthController {
);
return res.success({
- msg: successMessages.AUTH_CREATE_RECOVERY_TOKEN,
+ msg: this.stringService.authCreateRecoveryToken,
data: msgId,
});
} catch (error) {
@@ -410,7 +412,7 @@ class AuthController {
await this.db.validateRecoveryToken(req, res);
return res.success({
- msg: successMessages.AUTH_VERIFY_RECOVERY_TOKEN,
+ msg: this.stringService.authVerifyRecoveryToken,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "validateRecoveryTokenController"));
@@ -443,7 +445,7 @@ class AuthController {
const token = this.issueToken(user._doc, tokenType.ACCESS_TOKEN, appSettings);
return res.success({
- msg: successMessages.AUTH_RESET_PASSWORD,
+ msg: this.stringService.authResetPassword,
data: { user, token },
});
} catch (error) {
@@ -497,7 +499,7 @@ class AuthController {
await this.db.deleteUser(user._id);
return res.success({
- msg: successMessages.AUTH_DELETE_USER,
+ msg: this.stringService.authDeleteUser,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "deleteUserController"));
@@ -509,7 +511,7 @@ class AuthController {
const allUsers = await this.db.getAllUsers(req, res);
return res.success({
- msg: successMessages.AUTH_GET_ALL_USERS,
+ msg: this.stringService.authGetAllUsers,
data: allUsers,
});
} catch (error) {
diff --git a/Server/controllers/checkController.js b/Server/controllers/checkController.js
index ae6b96dbd..2addd7359 100644
--- a/Server/controllers/checkController.js
+++ b/Server/controllers/checkController.js
@@ -9,7 +9,6 @@ import {
deleteChecksByTeamIdParamValidation,
updateChecksTTLBodyValidation,
} from "../validation/joi.js";
-import { successMessages } from "../utils/messages.js";
import jwt from "jsonwebtoken";
import { getTokenFromHeaders } from "../utils/utils.js";
import { handleValidationError, handleError } from "./controllerUtils.js";
@@ -17,9 +16,10 @@ import { handleValidationError, handleError } from "./controllerUtils.js";
const SERVICE_NAME = "checkController";
class CheckController {
- constructor(db, settingsService) {
+ constructor(db, settingsService, stringService) {
this.db = db;
this.settingsService = settingsService;
+ this.stringService = stringService;
}
createCheck = async (req, res, next) => {
@@ -36,7 +36,7 @@ class CheckController {
const check = await this.db.createCheck(checkData);
return res.success({
- msg: successMessages.CHECK_CREATE,
+ msg: this.stringService.checkCreate,
data: check,
});
} catch (error) {
@@ -57,7 +57,7 @@ class CheckController {
const result = await this.db.getChecksByMonitor(req);
return res.success({
- msg: successMessages.CHECK_GET,
+ msg: this.stringService.checkGet,
data: result,
});
} catch (error) {
@@ -77,7 +77,7 @@ class CheckController {
const checkData = await this.db.getChecksByTeam(req);
return res.success({
- msg: successMessages.CHECK_GET,
+ msg: this.stringService.checkGet,
data: checkData,
});
} catch (error) {
@@ -97,7 +97,7 @@ class CheckController {
const deletedCount = await this.db.deleteChecks(req.params.monitorId);
return res.success({
- msg: successMessages.CHECK_DELETE,
+ msg: this.stringService.checkDelete,
data: { deletedCount },
});
} catch (error) {
@@ -117,7 +117,7 @@ class CheckController {
const deletedCount = await this.db.deleteChecksByTeamId(req.params.teamId);
return res.success({
- msg: successMessages.CHECK_DELETE,
+ msg: this.stringService.checkDelete,
data: { deletedCount },
});
} catch (error) {
@@ -144,7 +144,7 @@ class CheckController {
await this.db.updateChecksTTL(teamId, ttl);
return res.success({
- msg: successMessages.CHECK_UPDATE_TTL,
+ msg: this.stringService.checkUpdateTTL,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "updateTTL"));
diff --git a/Server/controllers/inviteController.js b/Server/controllers/inviteController.js
index a40f43a32..40d045432 100644
--- a/Server/controllers/inviteController.js
+++ b/Server/controllers/inviteController.js
@@ -7,14 +7,15 @@ import logger from "../utils/logger.js";
import jwt from "jsonwebtoken";
import { handleError, handleValidationError } from "./controllerUtils.js";
import { getTokenFromHeaders } from "../utils/utils.js";
-import { successMessages } from "../utils/messages.js";
+
const SERVICE_NAME = "inviteController";
class InviteController {
- constructor(db, settingsService, emailService) {
+ constructor(db, settingsService, emailService, stringService) {
this.db = db;
this.settingsService = settingsService;
this.emailService = emailService;
+ this.stringService = stringService;
}
/**
@@ -66,7 +67,7 @@ class InviteController {
});
return res.success({
- msg: successMessages.INVITE_ISSUED,
+ msg: this.stringService.inviteIssued,
data: inviteToken,
});
} catch (error) {
@@ -86,7 +87,7 @@ class InviteController {
const invite = await this.db.getInviteToken(req.body.token);
return res.success({
- msg: successMessages.INVITE_VERIFIED,
+ msg: this.stringService.inviteVerified,
data: invite,
});
} catch (error) {
diff --git a/Server/controllers/maintenanceWindowController.js b/Server/controllers/maintenanceWindowController.js
index b301ba94c..a11f957ed 100644
--- a/Server/controllers/maintenanceWindowController.js
+++ b/Server/controllers/maintenanceWindowController.js
@@ -9,14 +9,15 @@ import {
} from "../validation/joi.js";
import jwt from "jsonwebtoken";
import { getTokenFromHeaders } from "../utils/utils.js";
-import { successMessages } from "../utils/messages.js";
import { handleValidationError, handleError } from "./controllerUtils.js";
+
const SERVICE_NAME = "maintenanceWindowController";
class MaintenanceWindowController {
- constructor(db, settingsService) {
+ constructor(db, settingsService, stringService) {
this.db = db;
this.settingsService = settingsService;
+ this.stringService = stringService;
}
createMaintenanceWindows = async (req, res, next) => {
@@ -45,7 +46,7 @@ class MaintenanceWindowController {
await Promise.all(dbTransactions);
return res.success({
- msg: successMessages.MAINTENANCE_WINDOW_CREATE,
+ msg: this.stringService.maintenanceWindowCreate,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "createMaintenanceWindow"));
@@ -63,7 +64,7 @@ class MaintenanceWindowController {
const maintenanceWindow = await this.db.getMaintenanceWindowById(req.params.id);
return res.success({
- msg: successMessages.MAINTENANCE_WINDOW_GET_BY_ID,
+ msg: this.stringService.maintenanceWindowGetById,
data: maintenanceWindow,
});
} catch (error) {
@@ -89,7 +90,7 @@ class MaintenanceWindowController {
);
return res.success({
- msg: successMessages.MAINTENANCE_WINDOW_GET_BY_TEAM,
+ msg: this.stringService.maintenanceWindowGetByTeam,
data: maintenanceWindows,
});
} catch (error) {
@@ -111,7 +112,7 @@ class MaintenanceWindowController {
);
return res.success({
- msg: successMessages.MAINTENANCE_WINDOW_GET_BY_USER,
+ msg: this.stringService.maintenanceWindowGetByUser,
data: maintenanceWindows,
});
} catch (error) {
@@ -129,7 +130,7 @@ class MaintenanceWindowController {
try {
await this.db.deleteMaintenanceWindowById(req.params.id);
return res.success({
- msg: successMessages.MAINTENANCE_WINDOW_DELETE,
+ msg: this.stringService.maintenanceWindowDelete,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "deleteMaintenanceWindow"));
@@ -150,7 +151,7 @@ class MaintenanceWindowController {
req.body
);
return res.success({
- msg: successMessages.MAINTENANCE_WINDOW_EDIT,
+ msg: this.stringService.maintenanceWindowEdit,
data: editedMaintenanceWindow,
});
} catch (error) {
diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js
index ccd8ec26f..f65113cd7 100644
--- a/Server/controllers/monitorController.js
+++ b/Server/controllers/monitorController.js
@@ -14,7 +14,6 @@ import {
getHardwareDetailsByIdQueryValidation,
} from "../validation/joi.js";
import sslChecker from "ssl-checker";
-import { successMessages } from "../utils/messages.js";
import jwt from "jsonwebtoken";
import { getTokenFromHeaders } from "../utils/utils.js";
import logger from "../utils/logger.js";
@@ -24,10 +23,11 @@ import seedDb from "../db/mongo/utils/seedDb.js";
const SERVICE_NAME = "monitorController";
class MonitorController {
- constructor(db, settingsService, jobQueue) {
+ constructor(db, settingsService, jobQueue, stringService) {
this.db = db;
this.settingsService = settingsService;
this.jobQueue = jobQueue;
+ this.stringService = stringService;
}
/**
@@ -43,7 +43,7 @@ class MonitorController {
try {
const monitors = await this.db.getAllMonitors();
return res.success({
- msg: successMessages.MONITOR_GET_ALL,
+ msg: this.stringService.monitorGetAll,
data: monitors,
});
} catch (error) {
@@ -64,7 +64,7 @@ class MonitorController {
try {
const monitors = await this.db.getAllMonitorsWithUptimeStats();
return res.success({
- msg: successMessages.MONITOR_GET_ALL,
+ msg: this.stringService.monitorGetAll,
data: monitors,
});
} catch (error) {
@@ -76,7 +76,7 @@ class MonitorController {
try {
const monitor = await this.db.getUptimeDetailsById(req);
return res.success({
- msg: successMessages.MONITOR_GET_BY_ID,
+ msg: this.stringService.monitorGetById,
data: monitor,
});
} catch (error) {
@@ -105,7 +105,7 @@ class MonitorController {
try {
const monitorStats = await this.db.getMonitorStatsById(req);
return res.success({
- msg: successMessages.MONITOR_STATS_BY_ID,
+ msg: this.stringService.monitorStatsById,
data: monitorStats,
});
} catch (error) {
@@ -133,7 +133,7 @@ class MonitorController {
try {
const monitor = await this.db.getHardwareDetailsById(req);
return res.success({
- msg: successMessages.MONITOR_GET_BY_ID,
+ msg: this.stringService.monitorGetById,
data: monitor,
});
} catch (error) {
@@ -154,7 +154,7 @@ class MonitorController {
const certificate = await fetchMonitorCertificate(sslChecker, monitor);
return res.success({
- msg: successMessages.MONITOR_CERTIFICATE,
+ msg: this.stringService.monitorCertificate,
data: {
certificateDate: new Date(certificate.validTo),
},
@@ -187,7 +187,7 @@ class MonitorController {
try {
const monitor = await this.db.getMonitorById(req.params.monitorId);
return res.success({
- msg: successMessages.MONITOR_GET_BY_ID,
+ msg: this.stringService.monitorGetById,
data: monitor,
});
} catch (error) {
@@ -231,7 +231,7 @@ class MonitorController {
// Add monitor to job queue
this.jobQueue.addJob(monitor._id, monitor);
return res.success({
- msg: successMessages.MONITOR_CREATE,
+ msg: this.stringService.monitorCreate,
data: monitor,
});
} catch (error) {
@@ -309,7 +309,7 @@ class MonitorController {
stack: error.stack,
});
}
- return res.success({ msg: successMessages.MONITOR_DELETE });
+ return res.success({ msg: this.stringService.monitorDelete });
} catch (error) {
next(handleError(error, SERVICE_NAME, "deleteMonitor"));
}
@@ -390,10 +390,10 @@ class MonitorController {
await Promise.all(
notifications &&
- notifications.map(async (notification) => {
- notification.monitorId = editedMonitor._id;
- await this.db.createNotification(notification);
- })
+ notifications.map(async (notification) => {
+ notification.monitorId = editedMonitor._id;
+ await this.db.createNotification(notification);
+ })
);
// Delete the old job(editedMonitor has the same ID as the old monitor)
@@ -401,7 +401,7 @@ class MonitorController {
// Add the new job back to the queue
await this.jobQueue.addJob(editedMonitor._id, editedMonitor);
return res.success({
- msg: successMessages.MONITOR_EDIT,
+ msg: this.stringService.monitorEdit,
data: editedMonitor,
});
} catch (error) {
@@ -438,8 +438,8 @@ class MonitorController {
monitor.save();
return res.success({
msg: monitor.isActive
- ? successMessages.MONITOR_RESUME
- : successMessages.MONITOR_PAUSE,
+ ? this.stringService.monitorResume
+ : this.stringService.monitorPause,
data: monitor,
});
} catch (error) {
@@ -469,7 +469,7 @@ class MonitorController {
);
return res.success({
- msg: successMessages.MONITOR_DEMO_ADDED,
+ msg: this.stringService.monitorDemoAdded,
data: demoMonitors.length,
});
} catch (error) {
@@ -488,7 +488,7 @@ class MonitorController {
try {
const monitors = await this.db.getMonitorsByTeamId(req);
return res.success({
- msg: successMessages.MONITOR_GET_BY_TEAM_ID,
+ msg: this.stringService.monitorGetByTeamId,
data: monitors,
});
} catch (error) {
diff --git a/Server/controllers/queueController.js b/Server/controllers/queueController.js
index f62b9fdeb..330ade532 100644
--- a/Server/controllers/queueController.js
+++ b/Server/controllers/queueController.js
@@ -1,18 +1,18 @@
import { handleError } from "./controllerUtils.js";
-import { successMessages } from "../utils/messages.js";
const SERVICE_NAME = "JobQueueController";
class JobQueueController {
- constructor(jobQueue) {
+ constructor(jobQueue, stringService) {
this.jobQueue = jobQueue;
+ this.stringService = stringService;
}
getMetrics = async (req, res, next) => {
try {
const metrics = await this.jobQueue.getMetrics();
res.success({
- msg: successMessages.QUEUE_GET_METRICS,
+ msg: this.stringService.queueGetMetrics,
data: metrics,
});
} catch (error) {
@@ -25,7 +25,7 @@ class JobQueueController {
try {
const jobs = await this.jobQueue.getJobStats();
return res.success({
- msg: successMessages.QUEUE_GET_METRICS,
+ msg: this.stringService.queueGetMetrics,
data: jobs,
});
} catch (error) {
@@ -38,7 +38,7 @@ class JobQueueController {
try {
await this.jobQueue.addJob(Math.random().toString(36).substring(7));
return res.success({
- msg: successMessages.QUEUE_ADD_JOB,
+ msg: this.stringService.queueAddJob,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "addJob"));
@@ -50,7 +50,7 @@ class JobQueueController {
try {
await this.jobQueue.obliterate();
return res.success({
- msg: successMessages.QUEUE_OBLITERATE,
+ msg: this.stringService.queueObliterate,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "obliterateQueue"));
diff --git a/Server/controllers/settingsController.js b/Server/controllers/settingsController.js
index 496ed057a..54643dfbb 100644
--- a/Server/controllers/settingsController.js
+++ b/Server/controllers/settingsController.js
@@ -1,12 +1,13 @@
-import { successMessages } from "../utils/messages.js";
import { updateAppSettingsBodyValidation } from "../validation/joi.js";
import { handleValidationError, handleError } from "./controllerUtils.js";
+
const SERVICE_NAME = "SettingsController";
class SettingsController {
- constructor(db, settingsService) {
+ constructor(db, settingsService, stringService) {
this.db = db;
this.settingsService = settingsService;
+ this.stringService = stringService;
}
getAppSettings = async (req, res, next) => {
@@ -14,7 +15,7 @@ class SettingsController {
const settings = { ...(await this.settingsService.getSettings()) };
delete settings.jwtSecret;
return res.success({
- msg: successMessages.GET_APP_SETTINGS,
+ msg: this.stringService.getAppSettings,
data: settings,
});
} catch (error) {
@@ -35,7 +36,7 @@ class SettingsController {
const updatedSettings = { ...(await this.settingsService.reloadSettings()) };
delete updatedSettings.jwtSecret;
return res.success({
- msg: successMessages.UPDATE_APP_SETTINGS,
+ msg: this.stringService.updateAppSettings,
data: updatedSettings,
});
} catch (error) {
diff --git a/Server/controllers/statusPageController.js b/Server/controllers/statusPageController.js
index 1a69c92ec..eb3e69484 100644
--- a/Server/controllers/statusPageController.js
+++ b/Server/controllers/statusPageController.js
@@ -5,13 +5,13 @@ import {
getStatusPageQueryValidation,
imageValidation,
} from "../validation/joi.js";
-import { successMessages, errorMessages } from "../utils/messages.js";
const SERVICE_NAME = "statusPageController";
class StatusPageController {
- constructor(db) {
+ constructor(db, stringService) {
this.db = db;
+ this.stringService = stringService;
}
createStatusPage = async (req, res, next) => {
@@ -26,7 +26,7 @@ class StatusPageController {
try {
const statusPage = await this.db.createStatusPage(req.body, req.file);
return res.success({
- msg: successMessages.STATUS_PAGE_CREATE,
+ msg: this.stringService.statusPageCreate,
data: statusPage,
});
} catch (error) {
@@ -46,12 +46,12 @@ class StatusPageController {
try {
const statusPage = await this.db.updateStatusPage(req.body, req.file);
if (statusPage === null) {
- const error = new Error(errorMessages.STATUS_PAGE_NOT_FOUND);
+ const error = new Error(this.stringService.statusPageNotFound);
error.status = 404;
throw error;
}
return res.success({
- msg: successMessages.STATUS_PAGE_UPDATE,
+ msg: this.stringService.statusPageUpdate,
data: statusPage,
});
} catch (error) {
@@ -63,7 +63,7 @@ class StatusPageController {
try {
const statusPage = await this.db.getStatusPage();
return res.success({
- msg: successMessages.STATUS_PAGE,
+ msg: this.stringService.statusPageByUrl,
data: statusPage,
});
} catch (error) {
@@ -83,7 +83,7 @@ class StatusPageController {
try {
const statusPage = await this.db.getStatusPageByUrl(req.params.url, req.query.type);
return res.success({
- msg: successMessages.STATUS_PAGE_BY_URL,
+ msg: this.stringService.statusPageByUrl,
data: statusPage,
});
} catch (error) {
@@ -95,8 +95,9 @@ class StatusPageController {
try {
const teamId = req.params.teamId;
const statusPages = await this.db.getStatusPagesByTeamId(teamId);
+
return res.success({
- msg: successMessages.STATUS_PAGE_BY_TEAM_ID,
+ msg: this.stringService.statusPageByTeamId,
data: statusPages,
});
} catch (error) {
@@ -108,7 +109,7 @@ class StatusPageController {
try {
await this.db.deleteStatusPage(req.params.url);
return res.success({
- msg: successMessages.STATUS_PAGE_DELETE,
+ msg: this.stringService.statusPageDelete,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "deleteStatusPage"));
diff --git a/Server/db/models/Monitor.js b/Server/db/models/Monitor.js
index 90cbb1d62..eb5f67c1b 100644
--- a/Server/db/models/Monitor.js
+++ b/Server/db/models/Monitor.js
@@ -38,6 +38,20 @@ const MonitorSchema = mongoose.Schema(
"distributed_http",
],
},
+ jsonPath: {
+ type: String,
+ },
+ expectedValue: {
+ type: String,
+ },
+ matchMethod: {
+ type: String,
+ enum: [
+ "equal",
+ "include",
+ "regex",
+ ],
+ },
url: {
type: String,
required: true,
diff --git a/Server/db/mongo/modules/inviteModule.js b/Server/db/mongo/modules/inviteModule.js
index 0cbf0ab04..f5c960697 100644
--- a/Server/db/mongo/modules/inviteModule.js
+++ b/Server/db/mongo/modules/inviteModule.js
@@ -1,6 +1,7 @@
import InviteToken from "../../models/InviteToken.js";
import crypto from "crypto";
-import { errorMessages } from "../../../utils/messages.js";
+import ServiceRegistry from "../../../service/serviceRegistry.js";
+import StringService from "../../../service/stringService.js";
const SERVICE_NAME = "inviteModule";
/**
@@ -42,12 +43,13 @@ const requestInviteToken = async (userData) => {
* @throws {Error} If the invite token is not found or there is another error.
*/
const getInviteToken = async (token) => {
+ const stringService = ServiceRegistry.get(StringService.SERVICE_NAME);
try {
const invite = await InviteToken.findOne({
token,
});
if (invite === null) {
- throw new Error(errorMessages.AUTH_INVITE_NOT_FOUND);
+ throw new Error(stringService.authInviteNotFound);
}
return invite;
} catch (error) {
@@ -68,12 +70,13 @@ const getInviteToken = async (token) => {
* @throws {Error} If the invite token is not found or there is another error.
*/
const getInviteTokenAndDelete = async (token) => {
+ const stringService = ServiceRegistry.get(StringService.SERVICE_NAME);
try {
const invite = await InviteToken.findOneAndDelete({
token,
});
if (invite === null) {
- throw new Error(errorMessages.AUTH_INVITE_NOT_FOUND);
+ throw new Error(stringService.authInviteNotFound);
}
return invite;
} catch (error) {
diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js
index 9a8e98f4e..e1db09f0a 100644
--- a/Server/db/mongo/modules/monitorModule.js
+++ b/Server/db/mongo/modules/monitorModule.js
@@ -3,9 +3,10 @@ import Check from "../../models/Check.js";
import PageSpeedCheck from "../../models/PageSpeedCheck.js";
import HardwareCheck from "../../models/HardwareCheck.js";
import DistributedUptimeCheck from "../../models/DistributedUptimeCheck.js";
-import { errorMessages } from "../../../utils/messages.js";
import Notification from "../../models/Notification.js";
import { NormalizeData, NormalizeDataUptimeDetails } from "../../../utils/dataUtils.js";
+import ServiceRegistry from "../../../service/serviceRegistry.js";
+import StringService from "../../../service/stringService.js";
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
@@ -312,7 +313,7 @@ const calculateGroupStats = (group) => {
avgResponseTime:
checksWithResponseTime.length > 0
? checksWithResponseTime.reduce((sum, check) => sum + check.responseTime, 0) /
- checksWithResponseTime.length
+ checksWithResponseTime.length
: 0,
};
};
@@ -326,11 +327,12 @@ const calculateGroupStats = (group) => {
* @throws {Error}
*/
const getUptimeDetailsById = async (req) => {
+ const stringService = ServiceRegistry.get(StringService.SERVICE_NAME);
try {
const { monitorId } = req.params;
const monitor = await Monitor.findById(monitorId);
if (monitor === null || monitor === undefined) {
- throw new Error(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId));
+ throw new Error(stringService.dbFindMonitorById(monitorId));
}
const { dateRange, normalize } = req.query;
@@ -373,7 +375,7 @@ const getDistributedUptimeDetailsById = async (req) => {
const { monitorId } = req.params;
const monitor = await Monitor.findById(monitorId);
if (monitor === null || monitor === undefined) {
- throw new Error(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId));
+ throw new Error(this.stringService.dbFindMonitorById(monitorId));
}
const { dateRange, normalize } = req.query;
@@ -419,13 +421,14 @@ const getDistributedUptimeDetailsById = async (req) => {
* @throws {Error}
*/
const getMonitorStatsById = async (req) => {
+ const stringService = ServiceRegistry.get(StringService.SERVICE_NAME);
try {
const { monitorId } = req.params;
// Get monitor, if we can't find it, abort with error
const monitor = await Monitor.findById(monitorId);
if (monitor === null || monitor === undefined) {
- throw new Error(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId));
+ throw new Error(stringService.getDbFindMonitorById(monitorId));
}
// Get query params
@@ -516,10 +519,11 @@ const getHardwareDetailsById = async (req) => {
* @throws {Error}
*/
const getMonitorById = async (monitorId) => {
+ const stringService = ServiceRegistry.get(StringService.SERVICE_NAME);
try {
const monitor = await Monitor.findById(monitorId);
if (monitor === null || monitor === undefined) {
- const error = new Error(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId));
+ const error = new Error(stringService.getDbFindMonitorById(monitorId));
error.status = 404;
throw error;
}
@@ -601,98 +605,98 @@ const getMonitorsByTeamId = async (req) => {
filteredMonitors: [
...(filter !== undefined
? [
- {
- $match: {
- $or: [
- { name: { $regex: filter, $options: "i" } },
- { url: { $regex: filter, $options: "i" } },
- ],
- },
+ {
+ $match: {
+ $or: [
+ { name: { $regex: filter, $options: "i" } },
+ { url: { $regex: filter, $options: "i" } },
+ ],
},
- ]
+ },
+ ]
: []),
{ $sort: sort },
{ $skip: skip },
...(rowsPerPage ? [{ $limit: rowsPerPage }] : []),
...(limit
? [
- {
- $lookup: {
- from: "checks",
- let: { monitorId: "$_id" },
- pipeline: [
- {
- $match: {
- $expr: { $eq: ["$monitorId", "$$monitorId"] },
- },
+ {
+ $lookup: {
+ from: "checks",
+ let: { monitorId: "$_id" },
+ pipeline: [
+ {
+ $match: {
+ $expr: { $eq: ["$monitorId", "$$monitorId"] },
},
- { $sort: { createdAt: -1 } },
- ...(limit ? [{ $limit: limit }] : []),
- ],
- as: "standardchecks",
- },
+ },
+ { $sort: { createdAt: -1 } },
+ ...(limit ? [{ $limit: limit }] : []),
+ ],
+ as: "standardchecks",
},
- ]
+ },
+ ]
: []),
...(limit
? [
- {
- $lookup: {
- from: "pagespeedchecks",
- let: { monitorId: "$_id" },
- pipeline: [
- {
- $match: {
- $expr: { $eq: ["$monitorId", "$$monitorId"] },
- },
+ {
+ $lookup: {
+ from: "pagespeedchecks",
+ let: { monitorId: "$_id" },
+ pipeline: [
+ {
+ $match: {
+ $expr: { $eq: ["$monitorId", "$$monitorId"] },
},
- { $sort: { createdAt: -1 } },
- ...(limit ? [{ $limit: limit }] : []),
- ],
- as: "pagespeedchecks",
- },
+ },
+ { $sort: { createdAt: -1 } },
+ ...(limit ? [{ $limit: limit }] : []),
+ ],
+ as: "pagespeedchecks",
},
- ]
+ },
+ ]
: []),
...(limit
? [
- {
- $lookup: {
- from: "hardwarechecks",
- let: { monitorId: "$_id" },
- pipeline: [
- {
- $match: {
- $expr: { $eq: ["$monitorId", "$$monitorId"] },
- },
+ {
+ $lookup: {
+ from: "hardwarechecks",
+ let: { monitorId: "$_id" },
+ pipeline: [
+ {
+ $match: {
+ $expr: { $eq: ["$monitorId", "$$monitorId"] },
},
- { $sort: { createdAt: -1 } },
- ...(limit ? [{ $limit: limit }] : []),
- ],
- as: "hardwarechecks",
- },
+ },
+ { $sort: { createdAt: -1 } },
+ ...(limit ? [{ $limit: limit }] : []),
+ ],
+ as: "hardwarechecks",
},
- ]
+ },
+ ]
: []),
...(limit
? [
- {
- $lookup: {
- from: "distributeduptimechecks",
- let: { monitorId: "$_id" },
- pipeline: [
- {
- $match: {
- $expr: { $eq: ["$monitorId", "$$monitorId"] },
- },
+ {
+ $lookup: {
+ from: "distributeduptimechecks",
+ let: { monitorId: "$_id" },
+ pipeline: [
+ {
+ $match: {
+ $expr: { $eq: ["$monitorId", "$$monitorId"] },
},
- { $sort: { createdAt: -1 } },
- ...(limit ? [{ $limit: limit }] : []),
- ],
- as: "distributeduptimechecks",
- },
+ },
+ { $sort: { createdAt: -1 } },
+ ...(limit ? [{ $limit: limit }] : []),
+ ],
+ as: "distributeduptimechecks",
},
- ]
+ },
+ ]
: []),
{
@@ -783,11 +787,13 @@ const createMonitor = async (req, res) => {
* @throws {Error}
*/
const deleteMonitor = async (req, res) => {
+ const stringService = ServiceRegistry.get(StringService.SERVICE_NAME);
+
const monitorId = req.params.monitorId;
try {
const monitor = await Monitor.findByIdAndDelete(monitorId);
if (!monitor) {
- throw new Error(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId));
+ throw new Error(stringService.getDbFindMonitorById(monitorId));
}
return monitor;
} catch (error) {
diff --git a/Server/db/mongo/modules/recoveryModule.js b/Server/db/mongo/modules/recoveryModule.js
index 40dd66c56..3b39e847c 100644
--- a/Server/db/mongo/modules/recoveryModule.js
+++ b/Server/db/mongo/modules/recoveryModule.js
@@ -1,7 +1,8 @@
import UserModel from "../../models/User.js";
import RecoveryToken from "../../models/RecoveryToken.js";
import crypto from "crypto";
-import { errorMessages } from "../../../utils/messages.js";
+import serviceRegistry from "../../../service/serviceRegistry.js";
+import StringService from "../../../service/stringService.js";
const SERVICE_NAME = "recoveryModule";
@@ -31,6 +32,7 @@ const requestRecoveryToken = async (req, res) => {
};
const validateRecoveryToken = async (req, res) => {
+ const stringService = serviceRegistry.get(StringService.SERVICE_NAME);
try {
const candidateToken = req.body.recoveryToken;
const recoveryToken = await RecoveryToken.findOne({
@@ -39,7 +41,7 @@ const validateRecoveryToken = async (req, res) => {
if (recoveryToken !== null) {
return recoveryToken;
} else {
- throw new Error(errorMessages.DB_TOKEN_NOT_FOUND);
+ throw new Error(stringService.dbTokenNotFound);
}
} catch (error) {
error.service = SERVICE_NAME;
@@ -49,6 +51,7 @@ const validateRecoveryToken = async (req, res) => {
};
const resetPassword = async (req, res) => {
+ const stringService = serviceRegistry.get(StringService.SERVICE_NAME);
try {
const newPassword = req.body.password;
@@ -57,12 +60,12 @@ const resetPassword = async (req, res) => {
const user = await UserModel.findOne({ email: recoveryToken.email });
if (user === null) {
- throw new Error(errorMessages.DB_USER_NOT_FOUND);
+ throw new Error(stringService.dbUserNotFound);
}
const match = await user.comparePassword(newPassword);
if (match === true) {
- throw new Error(errorMessages.DB_RESET_PASSWORD_BAD_MATCH);
+ throw new Error(stringService.dbResetPasswordBadMatch);
}
user.password = newPassword;
diff --git a/Server/db/mongo/modules/statusPageModule.js b/Server/db/mongo/modules/statusPageModule.js
index 8687f06ed..aa32e49f5 100644
--- a/Server/db/mongo/modules/statusPageModule.js
+++ b/Server/db/mongo/modules/statusPageModule.js
@@ -1,10 +1,13 @@
import StatusPage from "../../models/StatusPage.js";
-import { errorMessages } from "../../../utils/messages.js";
import { NormalizeData } from "../../../utils/dataUtils.js";
+import ServiceRegistry from "../../../service/serviceRegistry.js";
+import StringService from "../../../service/stringService.js";
const SERVICE_NAME = "statusPageModule";
const createStatusPage = async (statusPageData, image) => {
+ const stringService = ServiceRegistry.get(StringService.SERVICE_NAME);
+
try {
const statusPage = new StatusPage({ ...statusPageData });
if (image) {
@@ -19,7 +22,7 @@ const createStatusPage = async (statusPageData, image) => {
if (error?.code === 11000) {
// Handle duplicate URL errors
error.status = 400;
- error.message = errorMessages.STATUS_PAGE_URL_NOT_UNIQUE;
+ error.message = stringService.statusPageUrlNotUnique;
}
error.service = SERVICE_NAME;
error.method = "createStatusPage";
@@ -67,13 +70,10 @@ const getStatusPageByUrl = async (url, type) => {
};
const getStatusPagesByTeamId = async (teamId) => {
+ const stringService = ServiceRegistry.get(StringService.SERVICE_NAME);
+
try {
const statusPages = await StatusPage.find({ teamId });
- if (statusPages.length === 0) {
- const error = new Error(errorMessages.STATUS_PAGE_NOT_FOUND);
- error.status = 404;
- throw error;
- }
return statusPages;
} catch (error) {
error.service = SERVICE_NAME;
@@ -83,6 +83,8 @@ const getStatusPagesByTeamId = async (teamId) => {
};
const getStatusPage = async (url) => {
+ const stringService = ServiceRegistry.get(StringService.SERVICE_NAME);
+
try {
const statusPageQuery = await StatusPage.aggregate([
{ $match: { url: url } },
@@ -156,7 +158,7 @@ const getStatusPage = async (url) => {
},
]);
if (!statusPageQuery.length) {
- const error = new Error(errorMessages.STATUS_PAGE_NOT_FOUND);
+ const error = new Error(stringService.statusPageNotFound);
error.status = 404;
throw error;
}
diff --git a/Server/db/mongo/modules/userModule.js b/Server/db/mongo/modules/userModule.js
index 5b951ff64..4069be8c6 100644
--- a/Server/db/mongo/modules/userModule.js
+++ b/Server/db/mongo/modules/userModule.js
@@ -1,10 +1,11 @@
import UserModel from "../../models/User.js";
import TeamModel from "../../models/Team.js";
-import { errorMessages } from "../../../utils/messages.js";
import { GenerateAvatarImage } from "../../../utils/imageProcessing.js";
const DUPLICATE_KEY_CODE = 11000; // MongoDB error code for duplicate key
import { ParseBoolean } from "../../../utils/utils.js";
+import ServiceRegistry from "../../../service/serviceRegistry.js";
+import StringService from "../../../service/stringService.js";
const SERVICE_NAME = "userModule";
/**
@@ -20,6 +21,7 @@ const insertUser = async (
imageFile,
generateAvatarImage = GenerateAvatarImage
) => {
+ const stringService = ServiceRegistry.get(StringService.SERVICE_NAME);
try {
if (imageFile) {
// 1. Save the full size image
@@ -50,7 +52,7 @@ const insertUser = async (
.select("-profileImage"); // .select() doesn't work with create, need to save then find
} catch (error) {
if (error.code === DUPLICATE_KEY_CODE) {
- error.message = errorMessages.DB_USER_EXISTS;
+ error.message = stringService.dbUserExists;
}
error.service = SERVICE_NAME;
error.method = "insertUser";
@@ -70,12 +72,14 @@ const insertUser = async (
* @throws {Error}
*/
const getUserByEmail = async (email) => {
+ const stringService = ServiceRegistry.get(StringService.SERVICE_NAME);
+
try {
// Need the password to be able to compare, removed .select()
// We can strip the hash before returning the user
const user = await UserModel.findOne({ email: email }).select("-profileImage");
if (!user) {
- throw new Error(errorMessages.DB_USER_NOT_FOUND);
+ throw new Error(stringService.dbUserNotFound);
}
return user;
} catch (error) {
@@ -150,10 +154,12 @@ const updateUser = async (
* @throws {Error}
*/
const deleteUser = async (userId) => {
+ const stringService = ServiceRegistry.get(StringService.SERVICE_NAME);
+
try {
const deletedUser = await UserModel.findByIdAndDelete(userId);
if (!deletedUser) {
- throw new Error(errorMessages.DB_USER_NOT_FOUND);
+ throw new Error(stringService.dbUserNotFound);
}
return deletedUser;
} catch (error) {
diff --git a/Server/index.js b/Server/index.js
index 8fb58481c..090498de2 100644
--- a/Server/index.js
+++ b/Server/index.js
@@ -78,6 +78,10 @@ import MongoDB from "./db/mongo/MongoDB.js";
import IORedis from "ioredis";
+import TranslationService from './service/translationService.js';
+import languageMiddleware from './middleware/languageMiddleware.js';
+import StringService from './service/stringService.js';
+
const SERVICE_NAME = "Server";
const SHUTDOWN_TIMEOUT = 1000;
let isShuttingDown = false;
@@ -160,11 +164,17 @@ const startApp = async () => {
}
}
+ // Create and Register Primary services
+ const translationService = new TranslationService(logger);
+ const stringService = new StringService(translationService);
+ ServiceRegistry.register(StringService.SERVICE_NAME, stringService);
+
// Create DB
const db = new MongoDB();
await db.connect();
// Create services
+ const networkService = new NetworkService(axios, ping, logger, http, Docker, net, stringService);
const settingsService = new SettingsService(AppSettings);
await settingsService.loadSettings();
const emailService = new EmailService(
@@ -176,16 +186,17 @@ const startApp = async () => {
nodemailer,
logger
);
- const networkService = new NetworkService(axios, ping, logger, http, Docker, net);
const statusService = new StatusService(db, logger);
const notificationService = new NotificationService(emailService, db, logger, networkService);
+
const jobQueue = new JobQueue(
db,
statusService,
networkService,
notificationService,
settingsService,
+ stringService,
logger,
Queue,
Worker
@@ -199,6 +210,10 @@ const startApp = async () => {
ServiceRegistry.register(NetworkService.SERVICE_NAME, networkService);
ServiceRegistry.register(StatusService.SERVICE_NAME, statusService);
ServiceRegistry.register(NotificationService.SERVICE_NAME, notificationService);
+ ServiceRegistry.register(TranslationService.SERVICE_NAME, translationService);
+
+ await translationService.initialize();
+
server = app.listen(PORT, () => {
logger.info({ message: `server started on port:${PORT}` });
});
@@ -212,40 +227,50 @@ const startApp = async () => {
ServiceRegistry.get(MongoDB.SERVICE_NAME),
ServiceRegistry.get(SettingsService.SERVICE_NAME),
ServiceRegistry.get(EmailService.SERVICE_NAME),
- ServiceRegistry.get(JobQueue.SERVICE_NAME)
+ ServiceRegistry.get(JobQueue.SERVICE_NAME),
+ ServiceRegistry.get(StringService.SERVICE_NAME)
);
const monitorController = new MonitorController(
ServiceRegistry.get(MongoDB.SERVICE_NAME),
ServiceRegistry.get(SettingsService.SERVICE_NAME),
- ServiceRegistry.get(JobQueue.SERVICE_NAME)
+ ServiceRegistry.get(JobQueue.SERVICE_NAME),
+ ServiceRegistry.get(StringService.SERVICE_NAME)
);
const settingsController = new SettingsController(
ServiceRegistry.get(MongoDB.SERVICE_NAME),
- ServiceRegistry.get(SettingsService.SERVICE_NAME)
+ ServiceRegistry.get(SettingsService.SERVICE_NAME),
+ ServiceRegistry.get(StringService.SERVICE_NAME)
);
const checkController = new CheckController(
ServiceRegistry.get(MongoDB.SERVICE_NAME),
- ServiceRegistry.get(SettingsService.SERVICE_NAME)
+ ServiceRegistry.get(SettingsService.SERVICE_NAME),
+ ServiceRegistry.get(StringService.SERVICE_NAME)
);
const inviteController = new InviteController(
ServiceRegistry.get(MongoDB.SERVICE_NAME),
ServiceRegistry.get(SettingsService.SERVICE_NAME),
- ServiceRegistry.get(EmailService.SERVICE_NAME)
+ ServiceRegistry.get(EmailService.SERVICE_NAME),
+ ServiceRegistry.get(StringService.SERVICE_NAME)
);
const maintenanceWindowController = new MaintenanceWindowController(
ServiceRegistry.get(MongoDB.SERVICE_NAME),
- ServiceRegistry.get(SettingsService.SERVICE_NAME)
+ ServiceRegistry.get(SettingsService.SERVICE_NAME),
+ ServiceRegistry.get(StringService.SERVICE_NAME)
);
- const queueController = new QueueController(ServiceRegistry.get(JobQueue.SERVICE_NAME));
+ const queueController = new QueueController(
+ ServiceRegistry.get(JobQueue.SERVICE_NAME),
+ ServiceRegistry.get(StringService.SERVICE_NAME)
+ );
const statusPageController = new StatusPageController(
- ServiceRegistry.get(MongoDB.SERVICE_NAME)
+ ServiceRegistry.get(MongoDB.SERVICE_NAME),
+ ServiceRegistry.get(StringService.SERVICE_NAME)
);
const notificationController = new NotificationController(
@@ -278,12 +303,10 @@ const startApp = async () => {
// Init job queue
await jobQueue.initJobQueue();
// Middleware
- app.use(
- cors()
- //We will add configuration later
- );
+ app.use(cors());
app.use(express.json());
app.use(helmet());
+ app.use(languageMiddleware(stringService, translationService));
// Swagger UI
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(openApiSpec));
diff --git a/Server/locales/en.json b/Server/locales/en.json
new file mode 100644
index 000000000..8657deb73
--- /dev/null
+++ b/Server/locales/en.json
@@ -0,0 +1,154 @@
+{
+ "dontHaveAccount": "Don't have account",
+ "email": "E-mail",
+ "forgotPassword": "Forgot Password",
+ "password": "password",
+ "signUp": "Sign up",
+ "submit": "Submit",
+ "title": "Title",
+ "continue": "Continue",
+ "enterEmail": "Enter your email",
+ "authLoginTitle": "Log In",
+ "authLoginEnterPassword": "Enter your password",
+ "commonPassword": "Password",
+ "commonBack": "Back",
+ "authForgotPasswordTitle": "Forgot password?",
+ "authForgotPasswordResetPassword": "Reset password",
+ "createPassword": "Create your password",
+ "createAPassword": "Create a password",
+ "authRegisterAlreadyHaveAccount": "Already have an account?",
+ "commonAppName": "BlueWave Uptime",
+ "authLoginEnterEmail": "Enter your email",
+ "authRegisterTitle": "Create an account",
+ "authRegisterStepOneTitle": "Create your account",
+ "authRegisterStepOneDescription": "Enter your details to get started",
+ "authRegisterStepTwoTitle": "Set up your profile",
+ "authRegisterStepTwoDescription": "Tell us more about yourself",
+ "authRegisterStepThreeTitle": "Almost done!",
+ "authRegisterStepThreeDescription": "Review your information",
+ "authForgotPasswordDescription": "No worries, we'll send you reset instructions.",
+ "authForgotPasswordSendInstructions": "Send instructions",
+ "authForgotPasswordBackTo": "Back to",
+ "authCheckEmailTitle": "Check your email",
+ "authCheckEmailDescription": "We sent a password reset link to {{email}}",
+ "authCheckEmailResendEmail": "Resend email",
+ "authCheckEmailBackTo": "Back to",
+ "goBackTo": "Go back to",
+ "authCheckEmailDidntReceiveEmail": "Didn't receive the email?",
+ "authCheckEmailClickToResend": "Click to resend",
+ "authSetNewPasswordTitle": "Set new password",
+ "authSetNewPasswordDescription": "Your new password must be different from previously used passwords.",
+ "authSetNewPasswordNewPassword": "New password",
+ "authSetNewPasswordConfirmPassword": "Confirm password",
+ "confirmPassword": "Confirm your password",
+ "authSetNewPasswordResetPassword": "Reset password",
+ "authSetNewPasswordBackTo": "Back to",
+ "authPasswordMustBeAtLeast": "Must be at least",
+ "authPasswordCharactersLong": "8 characters long",
+ "authPasswordMustContainAtLeast": "Must contain at least",
+ "authPasswordSpecialCharacter": "one special character",
+ "authPasswordOneNumber": "one number",
+ "authPasswordUpperCharacter": "one upper character",
+ "authPasswordLowerCharacter": "one lower character",
+ "authPasswordConfirmAndPassword": "Confirm password and password",
+ "authPasswordMustMatch": "must match",
+ "friendlyError": "Something went wrong...",
+ "unknownError": "An unknown error occurred",
+ "unauthorized": "Unauthorized access",
+ "authAdminExists": "Admin already exists",
+ "authInviteNotFound": "Invite not found",
+ "unknownService": "Unknown service",
+ "noAuthToken": "No auth token provided",
+ "invalidAuthToken": "Invalid auth token",
+ "expiredAuthToken": "Token expired",
+ "noRefreshToken": "No refresh token provided",
+ "invalidRefreshToken": "Invalid refresh token",
+ "expiredRefreshToken": "Refresh token expired",
+ "requestNewAccessToken": "Request new access token",
+ "invalidPayload": "Invalid payload",
+ "verifyOwnerNotFound": "Document not found",
+ "verifyOwnerUnauthorized": "Unauthorized access",
+ "insufficientPermissions": "Insufficient permissions",
+ "dbUserExists": "User already exists",
+ "dbUserNotFound": "User not found",
+ "dbTokenNotFound": "Token not found",
+ "dbResetPasswordBadMatch": "New password must be different from old password",
+ "dbFindMonitorById": "Monitor with id ${monitorId} not found",
+ "dbDeleteChecks": "No checks found for monitor with id ${monitorId}",
+ "authIncorrectPassword": "Incorrect password",
+ "authUnauthorized": "Unauthorized access",
+ "monitorGetById": "Monitor not found",
+ "monitorGetByUserId": "No monitors found for user",
+ "jobQueueWorkerClose": "Error closing worker",
+ "jobQueueDeleteJob": "Job not found in queue",
+ "jobQueueObliterate": "Error obliterating queue",
+ "pingCannotResolve": "No response",
+ "statusPageNotFound": "Status page not found",
+ "statusPageUrlNotUnique": "Status page url must be unique",
+ "dockerFail": "Failed to fetch Docker container information",
+ "dockerNotFound": "Docker container not found",
+ "portFail": "Failed to connect to port",
+ "alertCreate": "Alert created successfully",
+ "alertGetByUser": "Got alerts successfully",
+ "alertGetByMonitor": "Got alerts by Monitor successfully",
+ "alertGetById": "Got alert by Id successfully",
+ "alertEdit": "Alert edited successfully",
+ "alertDelete": "Alert deleted successfully",
+ "authCreateUser": "User created successfully",
+ "authLoginUser": "User logged in successfully",
+ "authLogoutUser": "User logged out successfully",
+ "authUpdateUser": "User updated successfully",
+ "authCreateRecoveryToken": "Recovery token created successfully",
+ "authVerifyRecoveryToken": "Recovery token verified successfully",
+ "authResetPassword": "Password reset successfully",
+ "authAdminCheck": "Admin check completed successfully",
+ "authDeleteUser": "User deleted successfully",
+ "authTokenRefreshed": "Auth token is refreshed",
+ "authGetAllUsers": "Got all users successfully",
+ "inviteIssued": "Invite sent successfully",
+ "inviteVerified": "Invite verified successfully",
+ "checkCreate": "Check created successfully",
+ "checkGet": "Got checks successfully",
+ "checkDelete": "Checks deleted successfully",
+ "checkUpdateTtl": "Checks TTL updated successfully",
+ "monitorGetAll": "Got all monitors successfully",
+ "monitorStatsById": "Got monitor stats by Id successfully",
+ "monitorGetByIdSuccess": "Got monitor by Id successfully",
+ "monitorGetByTeamId": "Got monitors by Team Id successfully",
+ "monitorGetByUserIdSuccess": "Got monitor for ${userId} successfully",
+ "monitorCreate": "Monitor created successfully",
+ "monitorDelete": "Monitor deleted successfully",
+ "monitorEdit": "Monitor edited successfully",
+ "monitorCertificate": "Got monitor certificate successfully",
+ "monitorDemoAdded": "Successfully added demo monitors",
+ "queueGetMetrics": "Got metrics successfully",
+ "queueAddJob": "Job added successfully",
+ "queueObliterate": "Queue obliterated",
+ "jobQueueDeleteJobSuccess": "Job removed successfully",
+ "jobQueuePauseJob": "Job paused successfully",
+ "jobQueueResumeJob": "Job resumed successfully",
+ "maintenanceWindowGetById": "Got Maintenance Window by Id successfully",
+ "maintenanceWindowCreate": "Maintenance Window created successfully",
+ "maintenanceWindowGetByTeam": "Got Maintenance Windows by Team successfully",
+ "maintenanceWindowDelete": "Maintenance Window deleted successfully",
+ "maintenanceWindowEdit": "Maintenance Window edited successfully",
+ "pingSuccess": "Success",
+ "getAppSettings": "Got app settings successfully",
+ "updateAppSettings": "Updated app settings successfully",
+ "statusPageByUrl": "Got status page by url successfully",
+ "statusPageCreate": "Status page created successfully",
+ "newTermsAdded": "New terms added to POEditor",
+ "dockerSuccess": "Docker container status fetched successfully",
+ "portSuccess": "Port connected successfully",
+ "monitorPause": "Monitor paused successfully",
+ "monitorResume": "Monitor resumed successfully",
+ "statusPageDelete": "Status page deleted successfully",
+ "statusPageUpdate": "Status page updated successfully",
+ "statusPageByTeamId": "Got status pages by team id successfully",
+ "httpNetworkError": "Network error",
+ "httpNotJson": "Response data is not json",
+ "httpJsonPathError": "Failed to parse json data",
+ "httpEmptyResult": "Result is empty",
+ "httpMatchSuccess": "Response data match successfully",
+ "httpMatchFail": "Failed to match response data"
+}
\ No newline at end of file
diff --git a/Server/locales/en.json.bak b/Server/locales/en.json.bak
new file mode 100644
index 000000000..eaef58eef
--- /dev/null
+++ b/Server/locales/en.json.bak
@@ -0,0 +1,147 @@
+{
+ "dontHaveAccount": "Don't have account",
+ "email": "E-mail",
+ "forgotPassword": "Forgot Password",
+ "password": "password",
+ "signUp": "Sign up",
+ "submit": "Submit",
+ "title": "Title",
+ "continue": "Continue",
+ "enterEmail": "Enter your email",
+ "authLoginTitle": "Log In",
+ "authLoginEnterPassword": "Enter your password",
+ "commonPassword": "Password",
+ "commonBack": "Back",
+ "authForgotPasswordTitle": "Forgot password?",
+ "authForgotPasswordResetPassword": "Reset password",
+ "createPassword": "Create your password",
+ "createAPassword": "Create a password",
+ "authRegisterAlreadyHaveAccount": "Already have an account?",
+ "commonAppName": "BlueWave Uptime",
+ "authLoginEnterEmail": "Enter your email",
+ "authRegisterTitle": "Create an account",
+ "authRegisterStepOneTitle": "Create your account",
+ "authRegisterStepOneDescription": "Enter your details to get started",
+ "authRegisterStepTwoTitle": "Set up your profile",
+ "authRegisterStepTwoDescription": "Tell us more about yourself",
+ "authRegisterStepThreeTitle": "Almost done!",
+ "authRegisterStepThreeDescription": "Review your information",
+ "authForgotPasswordDescription": "No worries, we'll send you reset instructions.",
+ "authForgotPasswordSendInstructions": "Send instructions",
+ "authForgotPasswordBackTo": "Back to",
+ "authCheckEmailTitle": "Check your email",
+ "authCheckEmailDescription": "We sent a password reset link to {{email}}",
+ "authCheckEmailResendEmail": "Resend email",
+ "authCheckEmailBackTo": "Back to",
+ "goBackTo": "Go back to",
+ "authCheckEmailDidntReceiveEmail": "Didn't receive the email?",
+ "authCheckEmailClickToResend": "Click to resend",
+ "authSetNewPasswordTitle": "Set new password",
+ "authSetNewPasswordDescription": "Your new password must be different from previously used passwords.",
+ "authSetNewPasswordNewPassword": "New password",
+ "authSetNewPasswordConfirmPassword": "Confirm password",
+ "confirmPassword": "Confirm your password",
+ "authSetNewPasswordResetPassword": "Reset password",
+ "authSetNewPasswordBackTo": "Back to",
+ "authPasswordMustBeAtLeast": "Must be at least",
+ "authPasswordCharactersLong": "8 characters long",
+ "authPasswordMustContainAtLeast": "Must contain at least",
+ "authPasswordSpecialCharacter": "one special character",
+ "authPasswordOneNumber": "one number",
+ "authPasswordUpperCharacter": "one upper character",
+ "authPasswordLowerCharacter": "one lower character",
+ "authPasswordConfirmAndPassword": "Confirm password and password",
+ "authPasswordMustMatch": "must match",
+ "friendlyError": "Something went wrong...",
+ "unknownError": "An unknown error occurred",
+ "unauthorized": "Unauthorized access",
+ "authAdminExists": "Admin already exists",
+ "authInviteNotFound": "Invite not found",
+ "unknownService": "Unknown service",
+ "noAuthToken": "No auth token provided",
+ "invalidAuthToken": "Invalid auth token",
+ "expiredAuthToken": "Token expired",
+ "noRefreshToken": "No refresh token provided",
+ "invalidRefreshToken": "Invalid refresh token",
+ "expiredRefreshToken": "Refresh token expired",
+ "requestNewAccessToken": "Request new access token",
+ "invalidPayload": "Invalid payload",
+ "verifyOwnerNotFound": "Document not found",
+ "verifyOwnerUnauthorized": "Unauthorized access",
+ "insufficientPermissions": "Insufficient permissions",
+ "dbUserExists": "User already exists",
+ "dbUserNotFound": "User not found",
+ "dbTokenNotFound": "Token not found",
+ "dbResetPasswordBadMatch": "New password must be different from old password",
+ "dbFindMonitorById": "Monitor with id ${monitorId} not found",
+ "dbDeleteChecks": "No checks found for monitor with id ${monitorId}",
+ "authIncorrectPassword": "Incorrect password",
+ "authUnauthorized": "Unauthorized access",
+ "monitorGetById": "Monitor not found",
+ "monitorGetByUserId": "No monitors found for user",
+ "jobQueueWorkerClose": "Error closing worker",
+ "jobQueueDeleteJob": "Job not found in queue",
+ "jobQueueObliterate": "Error obliterating queue",
+ "pingCannotResolve": "No response",
+ "statusPageNotFound": "Status page not found",
+ "statusPageUrlNotUnique": "Status page url must be unique",
+ "dockerFail": "Failed to fetch Docker container information",
+ "dockerNotFound": "Docker container not found",
+ "portFail": "Failed to connect to port",
+ "alertCreate": "Alert created successfully",
+ "alertGetByUser": "Got alerts successfully",
+ "alertGetByMonitor": "Got alerts by Monitor successfully",
+ "alertGetById": "Got alert by Id successfully",
+ "alertEdit": "Alert edited successfully",
+ "alertDelete": "Alert deleted successfully",
+ "authCreateUser": "User created successfully",
+ "authLoginUser": "User logged in successfully",
+ "authLogoutUser": "User logged out successfully",
+ "authUpdateUser": "User updated successfully",
+ "authCreateRecoveryToken": "Recovery token created successfully",
+ "authVerifyRecoveryToken": "Recovery token verified successfully",
+ "authResetPassword": "Password reset successfully",
+ "authAdminCheck": "Admin check completed successfully",
+ "authDeleteUser": "User deleted successfully",
+ "authTokenRefreshed": "Auth token is refreshed",
+ "authGetAllUsers": "Got all users successfully",
+ "inviteIssued": "Invite sent successfully",
+ "inviteVerified": "Invite verified successfully",
+ "checkCreate": "Check created successfully",
+ "checkGet": "Got checks successfully",
+ "checkDelete": "Checks deleted successfully",
+ "checkUpdateTtl": "Checks TTL updated successfully",
+ "monitorGetAll": "Got all monitors successfully",
+ "monitorStatsById": "Got monitor stats by Id successfully",
+ "monitorGetByIdSuccess": "Got monitor by Id successfully",
+ "monitorGetByTeamId": "Got monitors by Team Id successfully",
+ "monitorGetByUserIdSuccess": "Got monitor for ${userId} successfully",
+ "monitorCreate": "Monitor created successfully",
+ "monitorDelete": "Monitor deleted successfully",
+ "monitorEdit": "Monitor edited successfully",
+ "monitorCertificate": "Got monitor certificate successfully",
+ "monitorDemoAdded": "Successfully added demo monitors",
+ "queueGetMetrics": "Got metrics successfully",
+ "queueAddJob": "Job added successfully",
+ "queueObliterate": "Queue obliterated",
+ "jobQueueDeleteJobSuccess": "Job removed successfully",
+ "jobQueuePauseJob": "Job paused successfully",
+ "jobQueueResumeJob": "Job resumed successfully",
+ "maintenanceWindowGetById": "Got Maintenance Window by Id successfully",
+ "maintenanceWindowCreate": "Maintenance Window created successfully",
+ "maintenanceWindowGetByTeam": "Got Maintenance Windows by Team successfully",
+ "maintenanceWindowDelete": "Maintenance Window deleted successfully",
+ "maintenanceWindowEdit": "Maintenance Window edited successfully",
+ "pingSuccess": "Success",
+ "getAppSettings": "Got app settings successfully",
+ "updateAppSettings": "Updated app settings successfully",
+ "statusPageByUrl": "Got status page by url successfully",
+ "statusPageCreate": "Status page created successfully",
+ "newTermsAdded": "New terms added to POEditor",
+ "dockerSuccess": "Docker container status fetched successfully",
+ "portSuccess": "Port connected successfully",
+ "monitorPause": "Monitor paused successfully",
+ "monitorResume": "Monitor resumed successfully",
+ "statusPageDelete": "Status page deleted successfully",
+ "statusPageUpdate": "Status page updated successfully"
+}
diff --git a/Server/middleware/handleErrors.js b/Server/middleware/handleErrors.js
index fa9af4c0b..b64cda897 100644
--- a/Server/middleware/handleErrors.js
+++ b/Server/middleware/handleErrors.js
@@ -1,10 +1,12 @@
import logger from "../utils/logger.js";
-import { errorMessages } from "../utils/messages.js";
+import ServiceRegistry from "../service/serviceRegistry.js";
+import StringService from "../service/stringService.js";
const handleErrors = (error, req, res, next) => {
const status = error.status || 500;
- const message = error.message || errorMessages.FRIENDLY_ERROR;
- const service = error.service || errorMessages.UNKNOWN_SERVICE;
+ const stringService = ServiceRegistry.get(StringService.SERVICE_NAME);
+ const message = error.message || stringService.friendlyError;
+ const service = error.service || stringService.unknownService;
logger.error({
message: message,
service: service,
diff --git a/Server/middleware/isAllowed.js b/Server/middleware/isAllowed.js
index bd75b00ad..03a6b9d16 100644
--- a/Server/middleware/isAllowed.js
+++ b/Server/middleware/isAllowed.js
@@ -2,17 +2,17 @@ import jwt from "jsonwebtoken";
const TOKEN_PREFIX = "Bearer ";
const SERVICE_NAME = "allowedRoles";
import ServiceRegistry from "../service/serviceRegistry.js";
+import StringService from "../service/stringService.js";
import SettingsService from "../service/settingsService.js";
-import { errorMessages } from "../utils/messages.js";
const isAllowed = (allowedRoles) => {
return (req, res, next) => {
const token = req.headers["authorization"];
-
+ const stringService = ServiceRegistry.get(StringService.SERVICE_NAME);
// If no token is pressent, return an error
if (!token) {
- const error = new Error(errorMessages.NO_AUTH_TOKEN);
+ const error = new Error(stringService.noAuthToken);
error.status = 401;
error.service = SERVICE_NAME;
next(error);
@@ -21,7 +21,7 @@ const isAllowed = (allowedRoles) => {
// If the token is improperly formatted, return an error
if (!token.startsWith(TOKEN_PREFIX)) {
- const error = new Error(errorMessages.INVALID_AUTH_TOKEN);
+ const error = new Error(stringService.invalidAuthToken);
error.status = 400;
error.service = SERVICE_NAME;
next(error);
@@ -41,7 +41,7 @@ const isAllowed = (allowedRoles) => {
next();
return;
} else {
- const error = new Error(errorMessages.INSUFFICIENT_PERMISSIONS);
+ const error = new Error(stringService.insufficientPermissions);
error.status = 401;
error.service = SERVICE_NAME;
next(error);
diff --git a/Server/middleware/languageMiddleware.js b/Server/middleware/languageMiddleware.js
new file mode 100644
index 000000000..11c2f2aec
--- /dev/null
+++ b/Server/middleware/languageMiddleware.js
@@ -0,0 +1,11 @@
+const languageMiddleware = (stringService, translationService) => (req, res, next) => {
+ const acceptLanguage = req.headers['accept-language'] || 'en';
+ const language = acceptLanguage.split(',')[0].slice(0, 2).toLowerCase();
+
+ translationService.setLanguage(language);
+ stringService.setLanguage(language);
+
+ next();
+};
+
+export default languageMiddleware;
\ No newline at end of file
diff --git a/Server/middleware/verifyJWT.js b/Server/middleware/verifyJWT.js
index 87fa53bd6..9ee42e809 100644
--- a/Server/middleware/verifyJWT.js
+++ b/Server/middleware/verifyJWT.js
@@ -1,7 +1,7 @@
import jwt from "jsonwebtoken";
-import { errorMessages } from "../utils/messages.js";
import ServiceRegistry from "../service/serviceRegistry.js";
import SettingsService from "../service/settingsService.js";
+import StringService from "../service/stringService.js";
const SERVICE_NAME = "verifyJWT";
const TOKEN_PREFIX = "Bearer ";
@@ -14,10 +14,11 @@ const TOKEN_PREFIX = "Bearer ";
* @returns {express.Response}
*/
const verifyJWT = (req, res, next) => {
+ const stringService = ServiceRegistry.get(StringService.SERVICE_NAME);
const token = req.headers["authorization"];
// Make sure a token is provided
if (!token) {
- const error = new Error(errorMessages.NO_AUTH_TOKEN);
+ const error = new Error(stringService.noAuthToken);
error.status = 401;
error.service = SERVICE_NAME;
next(error);
@@ -25,7 +26,7 @@ const verifyJWT = (req, res, next) => {
}
// Make sure it is properly formatted
if (!token.startsWith(TOKEN_PREFIX)) {
- const error = new Error(errorMessages.INVALID_AUTH_TOKEN); // Instantiate a new Error object for improperly formatted token
+ const error = new Error(stringService.invalidAuthToken); // Instantiate a new Error object for improperly formatted token
error.status = 400;
error.service = SERVICE_NAME;
error.method = "verifyJWT";
@@ -43,7 +44,7 @@ const verifyJWT = (req, res, next) => {
handleExpiredJwtToken(req, res, next);
} else {
// Invalid token (signature or token altered or other issue)
- const errorMessage = errorMessages.INVALID_AUTH_TOKEN;
+ const errorMessage = stringService.invalidAuthToken;
return res.status(401).json({ success: false, msg: errorMessage });
}
} else {
@@ -55,12 +56,13 @@ const verifyJWT = (req, res, next) => {
};
function handleExpiredJwtToken(req, res, next) {
+ const stringService = ServiceRegistry.get(StringService.SERVICE_NAME);
// check for refreshToken
const refreshToken = req.headers["x-refresh-token"];
if (!refreshToken) {
// No refresh token provided
- const error = new Error(errorMessages.NO_REFRESH_TOKEN);
+ const error = new Error(stringService.noRefreshToken);
error.status = 401;
error.service = SERVICE_NAME;
error.method = "handleExpiredJwtToken";
@@ -76,8 +78,8 @@ function handleExpiredJwtToken(req, res, next) {
// Invalid or expired refresh token, trigger logout
const errorMessage =
refreshErr.name === "TokenExpiredError"
- ? errorMessages.EXPIRED_REFRESH_TOKEN
- : errorMessages.INVALID_REFRESH_TOKEN;
+ ? stringService.expiredRefreshToken
+ : stringService.invalidRefreshToken;
const error = new Error(errorMessage);
error.status = 401;
error.service = SERVICE_NAME;
@@ -87,7 +89,7 @@ function handleExpiredJwtToken(req, res, next) {
// Refresh token is valid and unexpired, request for new access token
res.status(403).json({
success: false,
- msg: errorMessages.REQUEST_NEW_ACCESS_TOKEN,
+ msg: stringService.requestNewAccessToken,
});
});
}
diff --git a/Server/middleware/verifyOwnership.js b/Server/middleware/verifyOwnership.js
index d812dd543..ca2476f54 100644
--- a/Server/middleware/verifyOwnership.js
+++ b/Server/middleware/verifyOwnership.js
@@ -1,8 +1,10 @@
import logger from "../utils/logger.js";
-import { errorMessages } from "../utils/messages.js";
+import ServiceRegistry from "../service/serviceRegistry.js";
+import StringService from "../service/stringService.js";
const SERVICE_NAME = "verifyOwnership";
const verifyOwnership = (Model, paramName) => {
+ const stringService = ServiceRegistry.get(StringService.SERVICE_NAME);
return async (req, res, next) => {
const userId = req.user._id;
const documentId = req.params[paramName];
@@ -11,11 +13,11 @@ const verifyOwnership = (Model, paramName) => {
//If the document is not found, return a 404 error
if (!doc) {
logger.error({
- message: errorMessages.VERIFY_OWNER_NOT_FOUND,
+ message: stringService.verifyOwnerNotFound,
service: SERVICE_NAME,
method: "verifyOwnership",
});
- const error = new Error(errorMessages.VERIFY_OWNER_NOT_FOUND);
+ const error = new Error(stringService.verifyOwnerNotFound);
error.status = 404;
throw error;
}
@@ -23,7 +25,7 @@ const verifyOwnership = (Model, paramName) => {
// Special case for User model, as it will not have a `userId` field as other docs will
if (Model.modelName === "User") {
if (userId.toString() !== doc._id.toString()) {
- const error = new Error(errorMessages.VERIFY_OWNER_UNAUTHORIZED);
+ const error = new Error(stringService.verifyOwnerUnauthorized);
error.status = 403;
throw error;
}
@@ -33,7 +35,7 @@ const verifyOwnership = (Model, paramName) => {
// If the userID does not match the document's userID, return a 403 error
if (userId.toString() !== doc.userId.toString()) {
- const error = new Error(errorMessages.VERIFY_OWNER_UNAUTHORIZED);
+ const error = new Error(stringService.verifyOwnerUnauthorized);
error.status = 403;
throw error;
}
diff --git a/Server/middleware/verifySuperAdmin.js b/Server/middleware/verifySuperAdmin.js
index bf4c780a7..98c336fad 100644
--- a/Server/middleware/verifySuperAdmin.js
+++ b/Server/middleware/verifySuperAdmin.js
@@ -2,9 +2,9 @@ const jwt = require("jsonwebtoken");
const logger = require("../utils/logger");
const SERVICE_NAME = "verifyAdmin";
const TOKEN_PREFIX = "Bearer ";
-const { errorMessages } = require("../utils/messages");
import ServiceRegistry from "../service/serviceRegistry.js";
import SettingsService from "../service/settingsService.js";
+import StringService from "../service/stringService.js";
/**
* Verifies the JWT token
* @function
@@ -14,10 +14,11 @@ import SettingsService from "../service/settingsService.js";
* @returns {express.Response}
*/
const verifySuperAdmin = (req, res, next) => {
+ const stringService = ServiceRegistry.get(StringService.SERVICE_NAME);
const token = req.headers["authorization"];
// Make sure a token is provided
if (!token) {
- const error = new Error(errorMessages.NO_AUTH_TOKEN);
+ const error = new Error(stringService.noAuthToken);
error.status = 401;
error.service = SERVICE_NAME;
next(error);
@@ -25,7 +26,7 @@ const verifySuperAdmin = (req, res, next) => {
}
// Make sure it is properly formatted
if (!token.startsWith(TOKEN_PREFIX)) {
- const error = new Error(errorMessages.INVALID_AUTH_TOKEN); // Instantiate a new Error object for improperly formatted token
+ const error = new Error(stringService.invalidAuthToken); // Instantiate a new Error object for improperly formatted token
error.status = 400;
error.service = SERVICE_NAME;
error.method = "verifySuperAdmin";
@@ -44,21 +45,21 @@ const verifySuperAdmin = (req, res, next) => {
service: SERVICE_NAME,
method: "verifySuperAdmin",
stack: err.stack,
- details: errorMessages.INVALID_AUTH_TOKEN,
+ details: stringService.invalidAuthToken,
});
return res
.status(401)
- .json({ success: false, msg: errorMessages.INVALID_AUTH_TOKEN });
+ .json({ success: false, msg: stringService.invalidAuthToken });
}
if (decoded.role.includes("superadmin") === false) {
logger.error({
- message: errorMessages.INVALID_AUTH_TOKEN,
+ message: stringService.invalidAuthToken,
service: SERVICE_NAME,
method: "verifySuperAdmin",
stack: err.stack,
});
- return res.status(401).json({ success: false, msg: errorMessages.UNAUTHORIZED });
+ return res.status(401).json({ success: false, msg: stringService.unauthorized });
}
next();
});
diff --git a/Server/nodemon.json b/Server/nodemon.json
new file mode 100644
index 000000000..b43c80fc1
--- /dev/null
+++ b/Server/nodemon.json
@@ -0,0 +1,12 @@
+{
+ "ignore": [
+ "locales/*",
+ "*.log",
+ "node_modules/*"
+ ],
+ "watch": [
+ "*.js",
+ "*.json"
+ ],
+ "ext": "js,json"
+}
\ No newline at end of file
diff --git a/Server/package-lock.json b/Server/package-lock.json
index d7cb00d6c..76cb601a3 100644
--- a/Server/package-lock.json
+++ b/Server/package-lock.json
@@ -11,7 +11,7 @@
"dependencies": {
"axios": "^1.7.2",
"bcrypt": "5.1.1",
- "bullmq": "5.40.2",
+ "bullmq": "5.40.3",
"cors": "^2.8.5",
"dockerode": "4.0.4",
"dotenv": "^16.4.5",
@@ -19,6 +19,7 @@
"handlebars": "^4.7.8",
"helmet": "^8.0.0",
"ioredis": "^5.4.2",
+ "jmespath": "^0.16.0",
"joi": "^17.13.1",
"jsonwebtoken": "9.0.2",
"mailersend": "^2.2.0",
@@ -1748,9 +1749,9 @@
}
},
"node_modules/bullmq": {
- "version": "5.40.2",
- "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.40.2.tgz",
- "integrity": "sha512-Cn4NUpwGAF4WnuXR2kTZCTAUEUHajSCn/IqiDG9ry1kVvAwwwg1Ati3J5HN2uZjqD5PBfNDXYnsc2+0PzakDwg==",
+ "version": "5.40.3",
+ "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.40.3.tgz",
+ "integrity": "sha512-ZHGHilK/A8wo5HHlPbwyOWMCEMRsyEmwFubEu98Xb7IyELyJCDn2hXYQVemG8dqFovkgIxXmerf4y3OHcmwmOQ==",
"license": "MIT",
"dependencies": {
"cron-parser": "^4.9.0",
@@ -3071,9 +3072,9 @@
}
},
"node_modules/eslint": {
- "version": "9.20.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.20.0.tgz",
- "integrity": "sha512-aL4F8167Hg4IvsW89ejnpTwx+B/UQRzJPGgbIOl+4XqffWsahVVsLEWoZvnrVuwpWmnRd7XeXmQI1zlKcFDteA==",
+ "version": "9.20.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.20.1.tgz",
+ "integrity": "sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3868,10 +3869,11 @@
}
},
"node_modules/globals": {
- "version": "15.14.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz",
- "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==",
+ "version": "15.15.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz",
+ "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=18"
},
@@ -4441,6 +4443,15 @@
"@pkgjs/parseargs": "^0.11.0"
}
},
+ "node_modules/jmespath": {
+ "version": "0.16.0",
+ "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz",
+ "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">= 0.6.0"
+ }
+ },
"node_modules/joi": {
"version": "17.13.3",
"resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz",
diff --git a/Server/package.json b/Server/package.json
index 6695a444a..3020ac0e9 100644
--- a/Server/package.json
+++ b/Server/package.json
@@ -18,7 +18,7 @@
"dependencies": {
"axios": "^1.7.2",
"bcrypt": "5.1.1",
- "bullmq": "5.40.2",
+ "bullmq": "5.40.3",
"cors": "^2.8.5",
"dockerode": "4.0.4",
"dotenv": "^16.4.5",
@@ -26,6 +26,7 @@
"handlebars": "^4.7.8",
"helmet": "^8.0.0",
"ioredis": "^5.4.2",
+ "jmespath": "^0.16.0",
"joi": "^17.13.1",
"jsonwebtoken": "9.0.2",
"mailersend": "^2.2.0",
diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js
index f7c70f0a7..be36383d2 100644
--- a/Server/service/jobQueue.js
+++ b/Server/service/jobQueue.js
@@ -12,7 +12,7 @@ const QUEUE_LOOKUP = {
};
const getSchedulerId = (monitor) => `scheduler:${monitor.type}:${monitor._id}`;
-import { successMessages, errorMessages } from "../utils/messages.js";
+
class NewJobQueue {
static SERVICE_NAME = SERVICE_NAME;
@@ -22,6 +22,7 @@ class NewJobQueue {
networkService,
notificationService,
settingsService,
+ stringService,
logger,
Queue,
Worker
@@ -44,6 +45,7 @@ class NewJobQueue {
this.settingsService = settingsService;
this.logger = logger;
this.Worker = Worker;
+ this.stringService = stringService;
QUEUE_NAMES.forEach((name) => {
this.queues[name] = new Queue(name, { connection });
@@ -455,7 +457,7 @@ class NewJobQueue {
if (wasDeleted === true) {
this.logger.info({
- message: successMessages.JOB_QUEUE_DELETE_JOB,
+ message: this.stringService.jobQueueDeleteJob,
service: SERVICE_NAME,
method: "deleteJob",
details: `Deleted job ${monitor._id}`,
@@ -464,7 +466,7 @@ class NewJobQueue {
await this.scaleWorkers(workerStats, queue);
} else {
this.logger.error({
- message: errorMessages.JOB_QUEUE_DELETE_JOB,
+ message: this.stringService.jobQueueDeleteJob,
service: SERVICE_NAME,
method: "deleteJob",
details: `Failed to delete job ${monitor._id}`,
@@ -587,7 +589,7 @@ class NewJobQueue {
const metrics = await this.getMetrics();
this.logger.info({
- message: successMessages.JOB_QUEUE_OBLITERATE,
+ message: this.stringService.jobQueueObliterate,
service: SERVICE_NAME,
method: "obliterate",
details: metrics,
diff --git a/Server/service/networkService.js b/Server/service/networkService.js
index fd6fff522..c76712de7 100644
--- a/Server/service/networkService.js
+++ b/Server/service/networkService.js
@@ -1,4 +1,4 @@
-import { errorMessages, successMessages } from "../utils/messages.js";
+import jmespath from 'jmespath';
const SERVICE_NAME = "NetworkService";
const UPROCK_ENDPOINT = "https://api.uprock.com/checkmate/push";
@@ -13,7 +13,8 @@ const UPROCK_ENDPOINT = "https://api.uprock.com/checkmate/push";
*/
class NetworkService {
static SERVICE_NAME = SERVICE_NAME;
- constructor(axios, ping, logger, http, Docker, net) {
+
+ constructor(axios, ping, logger, http, Docker, net, stringService) {
this.TYPE_PING = "ping";
this.TYPE_HTTP = "http";
this.TYPE_PAGESPEED = "pagespeed";
@@ -30,6 +31,7 @@ class NetworkService {
this.http = http;
this.Docker = Docker;
this.net = net;
+ this.stringService = stringService;
}
/**
@@ -87,13 +89,13 @@ class NetworkService {
if (error) {
pingResponse.status = false;
pingResponse.code = this.PING_ERROR;
- pingResponse.message = errorMessages.PING_CANNOT_RESOLVE;
+ pingResponse.message = "No response";
return pingResponse;
}
pingResponse.code = 200;
pingResponse.status = response.alive;
- pingResponse.message = successMessages.PING_SUCCESS;
+ pingResponse.message = "Success";
return pingResponse;
} catch (error) {
error.service = this.SERVICE_NAME;
@@ -121,20 +123,19 @@ class NetworkService {
*/
async requestHttp(job) {
try {
- const url = job.data.url;
+ const { url, secret, _id, name, teamId, type, jsonPath, matchMethod, expectedValue } = job.data;
const config = {};
- job.data.secret !== undefined &&
- (config.headers = { Authorization: `Bearer ${job.data.secret}` });
+ secret !== undefined && (config.headers = { Authorization: `Bearer ${secret}` });
const { response, responseTime, error } = await this.timeRequest(() =>
this.axios.get(url, config)
);
const httpResponse = {
- monitorId: job.data._id,
- teamId: job.data.teamId,
- type: job.data.type,
+ monitorId: _id,
+ teamId,
+ type,
responseTime,
payload: response?.data,
};
@@ -143,12 +144,62 @@ class NetworkService {
const code = error.response?.status || this.NETWORK_ERROR;
httpResponse.code = code;
httpResponse.status = false;
- httpResponse.message = this.http.STATUS_CODES[code] || "Network Error";
+ httpResponse.message = this.http.STATUS_CODES[code] || this.stringService.httpNetworkError;
return httpResponse;
}
- httpResponse.status = true;
+
httpResponse.code = response.status;
- httpResponse.message = this.http.STATUS_CODES[response.status];
+
+ if (!expectedValue) {
+ // not configure expected value, return
+ httpResponse.status = true;
+ httpResponse.message = this.http.STATUS_CODES[response.status];
+ return httpResponse;
+ }
+
+ // validate if response data match expected value
+ let result = response?.data;
+
+ this.logger.info({
+ service: this.SERVICE_NAME,
+ method: "requestHttp",
+ message: `Job: [${name}](${_id}) match result with expected value`,
+ details: { expectedValue, result, jsonPath, matchMethod }
+ });
+
+ if (jsonPath) {
+ const contentType = response.headers['content-type'];
+
+ const isJson = contentType?.includes('application/json');
+ if (!isJson) {
+ httpResponse.status = false;
+ httpResponse.message = this.stringService.httpNotJson;
+ return httpResponse;
+ }
+
+ try {
+ result = jmespath.search(result, jsonPath);
+ } catch (error) {
+ httpResponse.status = false;
+ httpResponse.message = this.stringService.httpJsonPathError;
+ return httpResponse;
+ }
+ }
+
+ if (result === null || result === undefined) {
+ httpResponse.status = false;
+ httpResponse.message = this.stringService.httpEmptyResult;
+ return httpResponse;
+ }
+
+ let match;
+ result = typeof result === "object" ? JSON.stringify(result) : result.toString();
+ 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;
@@ -240,7 +291,7 @@ class NetworkService {
const containers = await docker.listContainers({ all: true });
const containerExists = containers.some((c) => c.Id.startsWith(job.data.url));
if (!containerExists) {
- throw new Error(errorMessages.DOCKER_NOT_FOUND);
+ throw new Error(this.stringService.dockerNotFound);
}
const container = docker.getContainer(job.data.url);
@@ -257,12 +308,12 @@ class NetworkService {
if (error) {
dockerResponse.status = false;
dockerResponse.code = error.statusCode || this.NETWORK_ERROR;
- dockerResponse.message = error.reason || errorMessages.DOCKER_FAIL;
+ dockerResponse.message = error.reason || "Failed to fetch Docker container information";
return dockerResponse;
}
dockerResponse.status = response?.State?.Status === "running" ? true : false;
dockerResponse.code = 200;
- dockerResponse.message = successMessages.DOCKER_SUCCESS;
+ dockerResponse.message = "Docker container status fetched successfully";
return dockerResponse;
} catch (error) {
error.service = this.SERVICE_NAME;
@@ -310,13 +361,13 @@ class NetworkService {
if (error) {
portResponse.status = false;
portResponse.code = this.NETWORK_ERROR;
- portResponse.message = errorMessages.PORT_FAIL;
+ portResponse.message = this.stringService.portFail;
return portResponse;
}
portResponse.status = response.success;
portResponse.code = 200;
- portResponse.message = successMessages.PORT_SUCCESS;
+ portResponse.message = this.stringService.portSuccess;
return portResponse;
} catch (error) {
error.service = this.SERVICE_NAME;
diff --git a/Server/service/stringService.js b/Server/service/stringService.js
new file mode 100644
index 000000000..1ef208d6c
--- /dev/null
+++ b/Server/service/stringService.js
@@ -0,0 +1,358 @@
+class StringService {
+ static SERVICE_NAME = "StringService";
+
+ constructor(translationService) {
+ if (StringService.instance) {
+ return StringService.instance;
+ }
+
+ this.translationService = translationService;
+ this._language = 'en'; // default language
+ StringService.instance = this;
+ }
+
+ setLanguage(language) {
+ this._language = language;
+ }
+
+ get language() {
+ return this._language;
+ }
+
+ // Auth Messages
+ get dontHaveAccount() {
+ return this.translationService.getTranslation('dontHaveAccount');
+ }
+
+ get email() {
+ return this.translationService.getTranslation('email');
+ }
+
+ get forgotPassword() {
+ return this.translationService.getTranslation('forgotPassword');
+ }
+
+ get password() {
+ return this.translationService.getTranslation('password');
+ }
+
+ get signUp() {
+ return this.translationService.getTranslation('signUp');
+ }
+
+ get submit() {
+ return this.translationService.getTranslation('submit');
+ }
+
+ get title() {
+ return this.translationService.getTranslation('title');
+ }
+
+ get continue() {
+ return this.translationService.getTranslation('continue');
+ }
+
+ get enterEmail() {
+ return this.translationService.getTranslation('enterEmail');
+ }
+
+ get authLoginTitle() {
+ return this.translationService.getTranslation('authLoginTitle');
+ }
+
+ get authLoginEnterPassword() {
+ return this.translationService.getTranslation('authLoginEnterPassword');
+ }
+
+ get commonPassword() {
+ return this.translationService.getTranslation('commonPassword');
+ }
+
+ get commonBack() {
+ return this.translationService.getTranslation('commonBack');
+ }
+
+ get authForgotPasswordTitle() {
+ return this.translationService.getTranslation('authForgotPasswordTitle');
+ }
+
+ get authForgotPasswordResetPassword() {
+ return this.translationService.getTranslation('authForgotPasswordResetPassword');
+ }
+
+ get createPassword() {
+ return this.translationService.getTranslation('createPassword');
+ }
+
+ get createAPassword() {
+ return this.translationService.getTranslation('createAPassword');
+ }
+
+ get authRegisterAlreadyHaveAccount() {
+ return this.translationService.getTranslation('authRegisterAlreadyHaveAccount');
+ }
+
+ get commonAppName() {
+ return this.translationService.getTranslation('commonAppName');
+ }
+
+ get authLoginEnterEmail() {
+ return this.translationService.getTranslation('authLoginEnterEmail');
+ }
+
+ get authRegisterTitle() {
+ return this.translationService.getTranslation('authRegisterTitle');
+ }
+
+ get monitorGetAll() {
+ return this.translationService.getTranslation('monitorGetAll');
+ }
+
+ get monitorGetById() {
+ return this.translationService.getTranslation('monitorGetById');
+ }
+
+ get monitorCreate() {
+ return this.translationService.getTranslation('monitorCreate');
+ }
+
+ get monitorEdit() {
+ return this.translationService.getTranslation('monitorEdit');
+ }
+
+ get monitorDelete() {
+ return this.translationService.getTranslation('monitorDelete');
+ }
+
+ get monitorPause() {
+ return this.translationService.getTranslation('monitorPause');
+ }
+
+ get monitorResume() {
+ return this.translationService.getTranslation('monitorResume');
+ }
+
+ get monitorDemoAdded() {
+ return this.translationService.getTranslation('monitorDemoAdded');
+ }
+
+ get monitorStatsById() {
+ return this.translationService.getTranslation('monitorStatsById');
+ }
+
+ get monitorCertificate() {
+ return this.translationService.getTranslation('monitorCertificate');
+ }
+
+ // Maintenance Window Messages
+ get maintenanceWindowCreate() {
+ return this.translationService.getTranslation('maintenanceWindowCreate');
+ }
+
+ get maintenanceWindowGetById() {
+ return this.translationService.getTranslation('maintenanceWindowGetById');
+ }
+
+ get maintenanceWindowGetByTeam() {
+ return this.translationService.getTranslation('maintenanceWindowGetByTeam');
+ }
+
+ get maintenanceWindowDelete() {
+ return this.translationService.getTranslation('maintenanceWindowDelete');
+ }
+
+ get maintenanceWindowEdit() {
+ return this.translationService.getTranslation('maintenanceWindowEdit');
+ }
+
+ // Error Messages
+ get unknownError() {
+ return this.translationService.getTranslation('unknownError');
+ }
+
+ get friendlyError() {
+ return this.translationService.getTranslation('friendlyError');
+ }
+
+ get authIncorrectPassword() {
+ return this.translationService.getTranslation('authIncorrectPassword');
+ }
+
+ get unauthorized() {
+ return this.translationService.getTranslation('unauthorized');
+ }
+
+ get authAdminExists() {
+ return this.translationService.getTranslation('authAdminExists');
+ }
+
+ get authInviteNotFound() {
+ return this.translationService.getTranslation('authInviteNotFound');
+ }
+
+ get unknownService() {
+ return this.translationService.getTranslation('unknownService');
+ }
+
+ get noAuthToken() {
+ return this.translationService.getTranslation('noAuthToken');
+ }
+
+ get invalidAuthToken() {
+ return this.translationService.getTranslation('invalidAuthToken');
+ }
+
+ get expiredAuthToken() {
+ return this.translationService.getTranslation('expiredAuthToken');
+ }
+
+ // Queue Messages
+ get queueGetMetrics() {
+ return this.translationService.getTranslation('queueGetMetrics');
+ }
+
+ get queueAddJob() {
+ return this.translationService.getTranslation('queueAddJob');
+ }
+
+ get queueObliterate() {
+ return this.translationService.getTranslation('queueObliterate');
+ }
+
+ // Job Queue Messages
+ get jobQueueDeleteJobSuccess() {
+ return this.translationService.getTranslation('jobQueueDeleteJobSuccess');
+ }
+
+ get jobQueuePauseJob() {
+ return this.translationService.getTranslation('jobQueuePauseJob');
+ }
+
+ get jobQueueResumeJob() {
+ return this.translationService.getTranslation('jobQueueResumeJob');
+ }
+
+ // Status Page Messages
+ get statusPageByUrl() {
+ return this.translationService.getTranslation('statusPageByUrl');
+ }
+
+ get statusPageCreate() {
+ return this.translationService.getTranslation('statusPageCreate');
+ }
+
+ get statusPageDelete() {
+ return this.translationService.getTranslation('statusPageDelete');
+ }
+
+ get statusPageUpdate() {
+ return this.translationService.getTranslation('statusPageUpdate');
+ }
+
+ get statusPageNotFound() {
+ return this.translationService.getTranslation('statusPageNotFound');
+ }
+
+ get statusPageByTeamId() {
+ return this.translationService.getTranslation('statusPageByTeamId');
+ }
+
+ get statusPageUrlNotUnique() {
+ return this.translationService.getTranslation('statusPageUrlNotUnique');
+ }
+
+ // Docker Messages
+ get dockerFail() {
+ return this.translationService.getTranslation('dockerFail');
+ }
+
+ get dockerNotFound() {
+ return this.translationService.getTranslation('dockerNotFound');
+ }
+
+ get dockerSuccess() {
+ return this.translationService.getTranslation('dockerSuccess');
+ }
+
+ // Port Messages
+ get portFail() {
+ return this.translationService.getTranslation('portFail');
+ }
+
+ get portSuccess() {
+ return this.translationService.getTranslation('portSuccess');
+ }
+
+ // Alert Messages
+ get alertCreate() {
+ return this.translationService.getTranslation('alertCreate');
+ }
+
+ get alertGetByUser() {
+ return this.translationService.getTranslation('alertGetByUser');
+ }
+
+ get alertGetByMonitor() {
+ return this.translationService.getTranslation('alertGetByMonitor');
+ }
+
+ get alertGetById() {
+ return this.translationService.getTranslation('alertGetById');
+ }
+
+ get alertEdit() {
+ return this.translationService.getTranslation('alertEdit');
+ }
+
+ get alertDelete() {
+ return this.translationService.getTranslation('alertDelete');
+ }
+
+ getDeletedCount(count) {
+ return this.translationService.getTranslation('deletedCount')
+ .replace('{count}', count);
+ }
+
+ get pingSuccess() {
+ return this.translationService.getTranslation('pingSuccess');
+ }
+
+ get getAppSettings() {
+ return this.translationService.getTranslation('getAppSettings');
+ }
+
+ get httpNetworkError() {
+ return this.translationService.getTranslation('httpNetworkError');
+ }
+
+ get httpNotJson() {
+ return this.translationService.getTranslation('httpNotJson');
+ }
+
+ get httpJsonPathError() {
+ return this.translationService.getTranslation('httpJsonPathError');
+ }
+
+ get httpEmptyResult() {
+ return this.translationService.getTranslation('httpEmptyResult');
+ }
+
+ get httpMatchSuccess() {
+ return this.translationService.getTranslation('httpMatchSuccess');
+ }
+
+ get httpMatchFail() {
+ return this.translationService.getTranslation('httpMatchFail');
+ }
+
+ get updateAppSettings() {
+ return this.translationService.getTranslation('updateAppSettings');
+ }
+
+ getDbFindMonitorById(monitorId) {
+ return this.translationService.getTranslation('dbFindMonitorById')
+ .replace('${monitorId}', monitorId);
+ }
+}
+
+export default StringService;
\ No newline at end of file
diff --git a/Server/service/translationService.js b/Server/service/translationService.js
new file mode 100644
index 000000000..64c62bff2
--- /dev/null
+++ b/Server/service/translationService.js
@@ -0,0 +1,90 @@
+import fs from 'fs';
+import path from 'path';
+
+class TranslationService {
+ static SERVICE_NAME = 'TranslationService';
+
+ constructor(logger) {
+ this.logger = logger;
+ this.translations = {};
+ this._language = 'en';
+ this.localesDir = path.join(process.cwd(), 'locales');
+ }
+
+ setLanguage(language) {
+ this._language = language;
+ }
+
+ get language() {
+ return this._language;
+ }
+
+ async initialize() {
+ try {
+ await this.loadFromFiles();
+
+ } catch (error) {
+ this.logger.error({
+ message: error.message,
+ service: 'TranslationService',
+ method: 'initialize',
+ stack: error.stack
+ });
+ }
+ }
+
+ async loadFromFiles() {
+ try {
+ if (!fs.existsSync(this.localesDir)) {
+ return false;
+ }
+
+ const files = fs.readdirSync(this.localesDir).filter(file => file.endsWith('.json'));
+
+ if (files.length === 0) {
+ return false;
+ }
+
+ for (const file of files) {
+ const language = file.replace('.json', '');
+ const filePath = path.join(this.localesDir, file);
+ const content = fs.readFileSync(filePath, 'utf8');
+ this.translations[language] = JSON.parse(content);
+ }
+
+ this.logger.info({
+ message: 'Translations loaded from files successfully',
+ service: 'TranslationService',
+ method: 'loadFromFiles'
+ });
+
+ return true;
+ } catch (error) {
+ this.logger.error({
+ message: error.message,
+ service: 'TranslationService',
+ method: 'loadFromFiles',
+ stack: error.stack
+ });
+ return false;
+ }
+ }
+
+ getTranslation(key) {
+ let language = this._language;
+
+ try {
+ return this.translations[language]?.[key] || this.translations['en']?.[key] || key;
+ } catch (error) {
+ this.logger.error({
+ message: error.message,
+ service: 'TranslationService',
+ method: 'getTranslation',
+ stack: error.stack
+ });
+ return key;
+ }
+ }
+}
+
+export default TranslationService;
\ No newline at end of file
diff --git a/Server/utils/messages.js b/Server/utils/messages.js
deleted file mode 100644
index 38b1bf9d1..000000000
--- a/Server/utils/messages.js
+++ /dev/null
@@ -1,150 +0,0 @@
-const errorMessages = {
- // General Errors:
- FRIENDLY_ERROR: "Something went wrong...",
- UNKNOWN_ERROR: "An unknown error occurred",
-
- // Auth Controller
- UNAUTHORIZED: "Unauthorized access",
- AUTH_ADMIN_EXISTS: "Admin already exists",
- AUTH_INVITE_NOT_FOUND: "Invite not found",
-
- //Error handling middleware
- UNKNOWN_SERVICE: "Unknown service",
- NO_AUTH_TOKEN: "No auth token provided",
- INVALID_AUTH_TOKEN: "Invalid auth token",
- EXPIRED_AUTH_TOKEN: "Token expired",
- NO_REFRESH_TOKEN: "No refresh token provided",
- INVALID_REFRESH_TOKEN: "Invalid refresh token",
- EXPIRED_REFRESH_TOKEN: "Refresh token expired",
- REQUEST_NEW_ACCESS_TOKEN: "Request new access token",
-
- //Payload
- INVALID_PAYLOAD: "Invalid payload",
-
- //Ownership Middleware
- VERIFY_OWNER_NOT_FOUND: "Document not found",
- VERIFY_OWNER_UNAUTHORIZED: "Unauthorized access",
-
- //Permissions Middleware
- INSUFFICIENT_PERMISSIONS: "Insufficient permissions",
-
- //DB Errors
- DB_USER_EXISTS: "User already exists",
- DB_USER_NOT_FOUND: "User not found",
- DB_TOKEN_NOT_FOUND: "Token not found",
- DB_RESET_PASSWORD_BAD_MATCH: "New password must be different from old password",
- DB_FIND_MONITOR_BY_ID: (monitorId) => `Monitor with id ${monitorId} not found`,
- DB_DELETE_CHECKS: (monitorId) => `No checks found for monitor with id ${monitorId}`,
-
- //Auth errors
- AUTH_INCORRECT_PASSWORD: "Incorrect password",
- AUTH_UNAUTHORIZED: "Unauthorized access",
-
- // Monitor Errors
- MONITOR_GET_BY_ID: "Monitor not found",
- MONITOR_GET_BY_USER_ID: "No monitors found for user",
-
- // Job Queue Errors
- JOB_QUEUE_WORKER_CLOSE: "Error closing worker",
- JOB_QUEUE_DELETE_JOB: "Job not found in queue",
- JOB_QUEUE_OBLITERATE: "Error obliterating queue",
-
- // PING Operations
- PING_CANNOT_RESOLVE: "No response",
-
- // Status Page Errors
- STATUS_PAGE_NOT_FOUND: "Status page not found",
- STATUS_PAGE_URL_NOT_UNIQUE: "Status page url must be unique",
-
- // Docker
- DOCKER_FAIL: "Failed to fetch Docker container information",
- DOCKER_NOT_FOUND: "Docker container not found",
-
- // Port
- PORT_FAIL: "Failed to connect to port",
-};
-
-const successMessages = {
- //Alert Controller
- ALERT_CREATE: "Alert created successfully",
- ALERT_GET_BY_USER: "Got alerts successfully",
- ALERT_GET_BY_MONITOR: "Got alerts by Monitor successfully",
- ALERT_GET_BY_ID: "Got alert by Id successfully",
- ALERT_EDIT: "Alert edited successfully",
- ALERT_DELETE: "Alert deleted successfully",
-
- // Auth Controller
- AUTH_CREATE_USER: "User created successfully",
- AUTH_LOGIN_USER: "User logged in successfully",
- AUTH_LOGOUT_USER: "User logged out successfully",
- AUTH_UPDATE_USER: "User updated successfully",
- AUTH_CREATE_RECOVERY_TOKEN: "Recovery token created successfully",
- AUTH_VERIFY_RECOVERY_TOKEN: "Recovery token verified successfully",
- AUTH_RESET_PASSWORD: "Password reset successfully",
- AUTH_ADMIN_CHECK: "Admin check completed successfully",
- AUTH_DELETE_USER: "User deleted successfully",
- AUTH_TOKEN_REFRESHED: "Auth token is refreshed",
- AUTH_GET_ALL_USERS: "Got all users successfully",
-
- // Invite Controller
- INVITE_ISSUED: "Invite sent successfully",
- INVITE_VERIFIED: "Invite verified successfully",
-
- // Check Controller
- CHECK_CREATE: "Check created successfully",
- CHECK_GET: "Got checks successfully",
- CHECK_DELETE: "Checks deleted successfully",
- CHECK_UPDATE_TTL: "Checks TTL updated successfully",
-
- //Monitor Controller
- MONITOR_GET_ALL: "Got all monitors successfully",
- MONITOR_STATS_BY_ID: "Got monitor stats by Id successfully",
- MONITOR_GET_BY_ID: "Got monitor by Id successfully",
- MONITOR_GET_BY_TEAM_ID: "Got monitors by Team Id successfully",
- MONITOR_GET_BY_USER_ID: (userId) => `Got monitor for ${userId} successfully"`,
- MONITOR_CREATE: "Monitor created successfully",
- MONITOR_DELETE: "Monitor deleted successfully",
- MONITOR_EDIT: "Monitor edited successfully",
- MONITOR_CERTIFICATE: "Got monitor certificate successfully",
- MONITOR_DEMO_ADDED: "Successfully added demo monitors",
-
- // Queue Controller
- QUEUE_GET_METRICS: "Got metrics successfully",
- QUEUE_ADD_JOB: "Job added successfully",
- QUEUE_OBLITERATE: "Queue obliterated",
-
- //Job Queue
- JOB_QUEUE_DELETE_JOB: "Job removed successfully",
- JOB_QUEUE_OBLITERATE: "Queue OBLITERATED!!!",
- JOB_QUEUE_PAUSE_JOB: "Job paused successfully",
- JOB_QUEUE_RESUME_JOB: "Job resumed successfully",
-
- //Maintenance Window Controller
- MAINTENANCE_WINDOW_GET_BY_ID: "Got Maintenance Window by Id successfully",
- MAINTENANCE_WINDOW_CREATE: "Maintenance Window created successfully",
- MAINTENANCE_WINDOW_GET_BY_TEAM: "Got Maintenance Windows by Team successfully",
- MAINTENANCE_WINDOW_DELETE: "Maintenance Window deleted successfully",
- MAINTENANCE_WINDOW_EDIT: "Maintenance Window edited successfully",
-
- //Ping Operations
- PING_SUCCESS: "Success",
-
- // App Settings
- GET_APP_SETTINGS: "Got app settings successfully",
- UPDATE_APP_SETTINGS: "Updated app settings successfully",
-
- // Status Page
- STATUS_PAGE_BY_URL: "Got status page by url successfully",
- STATUS_PAGE: "Got status page successfully",
- STATUS_PAGE_CREATE: "Status page created successfully",
- STATUS_PAGE_DELETE: "Status page deleted successfully",
- STATUS_PAGE_UPDATE: "Status page updated successfully",
- STATUS_PAGE_BY_TEAM_ID: "Got status pages by team id successfully",
- // Docker
- DOCKER_SUCCESS: "Docker container status fetched successfully",
-
- // Port
- PORT_SUCCESS: "Port connected successfully",
-};
-
-export { errorMessages, successMessages };
diff --git a/Server/validation/joi.js b/Server/validation/joi.js
index 42c41d8a5..b60022fc5 100644
--- a/Server/validation/joi.js
+++ b/Server/validation/joi.js
@@ -194,6 +194,9 @@ const createMonitorBodyValidation = joi.object({
}),
notifications: joi.array().items(joi.object()),
secret: joi.string(),
+ jsonPath: joi.string(),
+ expectedValue: joi.string(),
+ matchMethod: joi.string(),
});
const editMonitorBodyValidation = joi.object({
@@ -202,6 +205,9 @@ const editMonitorBodyValidation = joi.object({
interval: joi.number(),
notifications: joi.array().items(joi.object()),
secret: joi.string(),
+ jsonPath: joi.string(),
+ expectedValue: joi.string(),
+ matchMethod: joi.string(),
});
const pauseMonitorParamValidation = joi.object({