Compare commits

..
41 changed files with 208 additions and 1518 deletions

No files matched your search

-1
View File
@@ -1 +0,0 @@
release_notes: false
+1 -1
View File
@@ -7,7 +7,7 @@ import { URLSearchParams } from "url";
const API_TOKEN = process.env.POEDITOR_API;
const PROJECT_ID = process.env.POEDITOR_PROJECT_ID;
const LANGUAGES = (
process.env.LANGUAGES || "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja,zh-cn"
process.env.LANGUAGES || "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja"
).split(",");
const EXPORT_FORMAT = process.env.EXPORT_FORMAT || "key_value_json";
+2 -2
View File
@@ -16,7 +16,7 @@ jobs:
- name: Install client dependencies
working-directory: client
run: npm install
run: npm ci
- name: Check client formatting
working-directory: client
@@ -34,7 +34,7 @@ jobs:
- name: Install server dependencies
working-directory: server
run: npm install
run: npm ci
- name: Check server formatting
working-directory: server
+1 -1
View File
@@ -7,7 +7,7 @@ on:
languages:
description: "Languages to synchronize (comma separated, e.g.: tr,en,es)"
required: false
default: "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja,zh-cn"
default: "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja"
format:
description: "Export format (key_value_json or json)"
required: false
+11 -8
View File
@@ -21,7 +21,7 @@
"dayjs": "1.11.13",
"flag-icons": "7.3.2",
"html2canvas": "^1.4.1",
"i18next": "25.4.2",
"i18next": "^24.2.2",
"joi": "17.13.3",
"mui-color-input": "^6.0.0",
"react": "18.3.1",
@@ -46,6 +46,9 @@
"eslint-plugin-react-refresh": "^0.4.6",
"prettier": "^3.3.3",
"vite": "6.3.5"
},
"optionalDependencies": {
"@rollup/rollup-linux-arm64-musl": "4.41.0"
}
},
"node_modules/@ampproject/remapping": {
@@ -280,9 +283,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz",
"integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==",
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz",
"integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -4069,9 +4072,9 @@
}
},
"node_modules/i18next": {
"version": "25.4.2",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.4.2.tgz",
"integrity": "sha512-gD4T25a6ovNXsfXY1TwHXXXLnD/K2t99jyYMCSimSCBnBRJVQr5j+VAaU83RJCPzrTGhVQ6dqIga66xO2rtd5g==",
"version": "24.2.3",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-24.2.3.tgz",
"integrity": "sha512-lfbf80OzkocvX7nmZtu7nSTNbrTYR52sLWxPtlXX1zAhVw8WEnFk4puUkCR4B1dNQwbSpEHHHemcZu//7EcB7A==",
"funding": [
{
"type": "individual",
@@ -4088,7 +4091,7 @@
],
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.27.6"
"@babel/runtime": "^7.26.10"
},
"peerDependencies": {
"typescript": "^5"
+4 -1
View File
@@ -26,7 +26,7 @@
"dayjs": "1.11.13",
"flag-icons": "7.3.2",
"html2canvas": "^1.4.1",
"i18next": "25.4.2",
"i18next": "^24.2.2",
"joi": "17.13.3",
"mui-color-input": "^6.0.0",
"react": "18.3.1",
@@ -59,5 +59,8 @@
"eslint-plugin-react-refresh": "^0.4.6",
"prettier": "^3.3.3",
"vite": "6.3.5"
},
"optionalDependencies": {
"@rollup/rollup-linux-arm64-musl": "4.41.0"
}
}
@@ -1,7 +1,7 @@
import { useTheme } from "@emotion/react";
import { Box, Stack, Tooltip, Typography } from "@mui/material";
import { formatDateWithTz } from "../../../Utils/timeUtils";
import { useEffect, useState, forwardRef } from "react";
import { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import PropTypes from "prop-types";
@@ -17,32 +17,26 @@ import PropTypes from "prop-types";
* @returns {JSX.Element} The Bar component.
*/
const Bar = forwardRef(
({ width, height, backgroundColor, borderRadius, children, ...otherProps }, ref) => {
const theme = useTheme();
const Bar = ({ width, height, backgroundColor, borderRadius, children }) => {
const theme = useTheme();
return (
<Box
ref={ref}
position="relative"
width={width}
height={height}
backgroundColor={backgroundColor}
sx={{
borderRadius: borderRadius || theme.spacing(1.5),
}}
{...otherProps}
>
{children}
</Box>
);
}
);
Bar.displayName = "Bar";
return (
<Box
position="relative"
width={width}
height={height}
backgroundColor={backgroundColor}
sx={{
borderRadius: borderRadius || theme.spacing(1.5),
}}
>
{children}
</Box>
);
};
Bar.propTypes = {
width: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
width: PropTypes.string.isRequired,
height: PropTypes.string.isRequired,
backgroundColor: PropTypes.string.isRequired,
borderRadius: PropTypes.string,
+1 -1
View File
@@ -65,7 +65,7 @@ const Check = ({ text, noHighlightText, variant = "info", outlined = false }) =>
};
Check.propTypes = {
text: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
text: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
noHighlightText: PropTypes.string,
variant: PropTypes.oneOf(["info", "error", "success"]),
outlined: PropTypes.bool,
@@ -2,7 +2,7 @@ import { useId } from "react";
import PropTypes from "prop-types";
import { Modal, Stack, Typography } from "@mui/material";
const GenericDialog = ({ title, description, open, onClose, theme, children, width }) => {
const GenericDialog = ({ title, description, open, onClose, theme, children }) => {
const titleId = useId();
const descriptionId = useId();
const ariaDescribedBy = description?.length > 0 ? descriptionId : "";
@@ -16,7 +16,6 @@ const GenericDialog = ({ title, description, open, onClose, theme, children, wid
>
<Stack
gap={theme.spacing(2)}
width={width}
sx={{
position: "absolute",
top: "50%",
@@ -40,7 +39,6 @@ const GenericDialog = ({ title, description, open, onClose, theme, children, wid
fontSize={16}
color={theme.palette.primary.contrastText}
fontWeight={600}
marginBottom={theme.spacing(4)}
>
{title}
</Typography>
@@ -48,7 +46,6 @@ const GenericDialog = ({ title, description, open, onClose, theme, children, wid
<Typography
id={descriptionId}
color={theme.palette.primary.contrastTextTertiary}
marginBottom={theme.spacing(4)}
>
{description}
</Typography>
@@ -67,7 +64,6 @@ GenericDialog.propTypes = {
theme: PropTypes.object.isRequired,
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node])
.isRequired,
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
};
export { GenericDialog };
+12 -31
View File
@@ -14,25 +14,25 @@ const useGetInviteToken = () => {
const clearToken = () => {
setToken(undefined);
};
const fetchToken = async (email, role) => {
const response = await networkService.requestInvitationToken({ email, role });
const token = response?.data?.data?.token;
if (typeof token === "undefined") {
throw new Error(t("inviteNoTokenFound"));
}
return token;
};
const getInviteToken = async ({ email, role }) => {
try {
setIsLoading(true);
const token = await fetchToken(email, role);
const response = await networkService.requestInvitationToken({
email,
role,
});
const token = response?.data?.data?.token;
if (typeof token === "undefined") {
throw new Error(t("inviteNoTokenFound"));
}
let inviteLink = token;
if (typeof CLIENT_HOST !== "undefined") {
inviteLink = `${CLIENT_HOST}/register/${token}`;
}
setToken(inviteLink);
return token;
} catch (error) {
setError(error);
} finally {
@@ -40,26 +40,7 @@ const useGetInviteToken = () => {
}
};
const addTeamMember = async (formData, role) => {
try {
setIsLoading(true);
const token = await fetchToken(formData.email, role);
const toSubmit = {
...formData,
inviteToken: token,
};
delete toSubmit.confirm;
const responseRegister = await networkService.registerUser(toSubmit);
return responseRegister;
} catch (error) {
setError(error);
throw error;
} finally {
setIsLoading(false);
}
};
return [getInviteToken, clearToken, isLoading, error, token, addTeamMember];
return [getInviteToken, clearToken, isLoading, error, token];
};
export { useGetInviteToken };
@@ -1,68 +0,0 @@
import { useState } from "react";
import Button from "@mui/material/Button";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import { useTheme } from "@emotion/react";
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
import { useTranslation } from "react-i18next";
import Proptypes from "prop-types";
const AddMemberMenu = ({ handleInviteOpen, handleIsRegisterOpen }) => {
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);
const { t } = useTranslation();
const theme = useTheme();
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return (
<>
<Button
variant="contained"
color="accent"
endIcon={<ArrowDropDownIcon sx={{ color: theme.palette.secondary.light }} />}
onClick={handleClick}
>
{t("teamPanel.addTeamMember.addMemberMenu")}
</Button>
<Menu
anchorEl={anchorEl}
open={open}
onClose={handleClose}
sx={{
"& .MuiPaper-root": {
minWidth: anchorEl?.offsetWidth || "auto",
},
}}
>
<MenuItem
onClick={() => {
handleClose();
handleInviteOpen();
}}
>
{t("teamPanel.inviteTeamMember")}
</MenuItem>
<MenuItem
onClick={() => {
handleClose();
handleIsRegisterOpen(true);
}}
>
{t("teamPanel.register")}
</MenuItem>
</Menu>
</>
);
};
AddMemberMenu.propTypes = {
handleInviteOpen: Proptypes.func.isRequired,
handleIsRegisterOpen: Proptypes.func.isRequired,
};
export default AddMemberMenu;
@@ -1,48 +0,0 @@
import { useState } from "react";
import { newOrChangedCredentials } from "../../../../../Validation/validation";
import { useTranslation } from "react-i18next";
const useAddTeamMember = () => {
const { t } = useTranslation();
const [errors, setErrors] = useState({});
const clearErrors = () => setErrors({});
const validateFields = (name, value, formData) => {
const { error } = newOrChangedCredentials.validate(
{ [name]: value },
{ abortEarly: false, context: { password: formData.password } }
);
setErrors((prev) => ({
...prev,
[name]: error?.details?.[0]?.message || "",
}));
};
const validateForm = (formData, role) => {
const { error } = newOrChangedCredentials.validate(formData, {
abortEarly: false,
context: { password: formData.password },
});
const formErrors = {};
if (error) {
for (const err of error.details) {
formErrors[err.path[0]] = err.message;
}
}
if (!role[0] || role.length === 0) {
formErrors.role = t(
"teamPanel.registerTeamMember.auth.common.inputs.role.errors.empty"
);
}
if (Object.keys(formErrors).length > 0) {
setErrors(formErrors);
return false;
}
setErrors({});
return true;
};
return { errors, setErrors, clearErrors, validateFields, validateForm };
};
export default useAddTeamMember;
@@ -1,213 +0,0 @@
import { Button, Stack } from "@mui/material";
import { GenericDialog } from "../../../../Components/Dialog/genericDialog";
import TextInput from "../../../../Components/Inputs/TextInput";
import Select from "../../../../Components/Inputs/Select";
import { useGetInviteToken } from "../../../../Hooks/inviteHooks";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
import { createToast } from "../../../../Utils/toastUtils";
import { useState } from "react";
import PasswordTooltip from "../../../Auth/components/PasswordTooltip";
import useAddTeamMember from "./hooks/useAddTeamMember";
import usePasswordFeedback from "../../../Auth/hooks/usePasswordFeedback";
import { PasswordEndAdornment } from "../../../../Components/Inputs/TextInput/Adornments";
import PropTypes from "prop-types";
const INITIAL_FORM_STATE = {
firstName: "",
lastName: "",
email: "",
password: "",
confirm: "",
teamId: "",
};
const INITIAL_ROLE_STATE = ["user"];
const AddTeamMember = ({ handleIsRegisterOpen, isRegisterOpen, onMemberAdded }) => {
const theme = useTheme();
const { t } = useTranslation();
const { errors, setErrors, clearErrors, validateFields, validateForm } =
useAddTeamMember();
const { feedback, handlePasswordFeedback } = usePasswordFeedback();
const [getInviteToken, clearToken, isLoading, error, token, addTeamMember] =
useGetInviteToken();
const [form, setForm] = useState(INITIAL_FORM_STATE);
const [role, setRole] = useState(INITIAL_ROLE_STATE);
const [isLoadingSubmit, setIsLoadingSubmit] = useState(false);
const closeAddMemberModal = () => {
handleIsRegisterOpen(false);
setForm(INITIAL_FORM_STATE);
setRole(INITIAL_ROLE_STATE);
clearErrors();
clearToken();
};
const onChange = (e) => {
let { name, value } = e.target;
if (name === "email") value = value.toLowerCase();
const updatedForm = { ...form, [name]: value };
validateFields(name, value, updatedForm);
setForm(updatedForm);
if (name === "password" || name === "confirm") {
handlePasswordFeedback(updatedForm, name, value, form, errors, setErrors);
}
};
const onsubmitAddMember = async (event) => {
event.preventDefault();
if (!validateForm(form, role)) return;
try {
setIsLoadingSubmit(true);
await addTeamMember(form, role);
createToast({
body: t("teamPanel.registerToast.success"),
});
onMemberAdded();
closeAddMemberModal();
} catch (error) {
const errorMsg = error.response?.data?.msg || error.message || "unknownError";
createToast({
type: "error",
body: t(errorMsg),
});
} finally {
setIsLoadingSubmit(false);
}
};
const tErr = (key) => (key ? t([`teamPanel.registerTeamMember.${key}`, key]) : "");
return (
<>
<GenericDialog
title={t("teamPanel.addTeamMember.title")}
description={t("teamPanel.addTeamMember.description")}
open={isRegisterOpen}
onClose={closeAddMemberModal}
theme={theme}
width={{ sm: "55%", md: "50%", lg: "40%", xl: "30%" }}
>
<TextInput
name="firstName"
label={t("auth.common.inputs.firstName.label")}
isRequired={true}
gap={theme.spacing(4)}
placeholder={t("auth.common.inputs.firstName.placeholder")}
value={form.firstName}
onChange={onChange}
error={errors.firstName ? true : false}
helperText={errors.firstName ? tErr(errors.firstName) : null}
sx={{ mb: errors.firstName ? theme.spacing(15) : theme.spacing(5) }}
/>
<TextInput
name="lastName"
label={t("auth.common.inputs.lastName.label")}
isRequired={true}
gap={theme.spacing(4)}
placeholder={t("auth.common.inputs.lastName.placeholder")}
value={form.lastName}
onChange={onChange}
error={errors.lastName ? true : false}
helperText={errors.lastName ? tErr(errors.lastName) : null}
sx={{ mb: errors.lastName ? theme.spacing(15) : theme.spacing(5) }}
/>
<TextInput
type="email"
label={t("auth.common.inputs.email.label")}
name="email"
gap={theme.spacing(4)}
isRequired={true}
id="input-team-member"
placeholder={t("teamPanel.email")}
value={form.email}
onChange={onChange}
error={errors.email ? true : false}
helperText={errors.email ? tErr(errors.email) : null}
sx={{ mb: errors.email ? theme.spacing(15) : theme.spacing(5) }}
/>
<Select
label={t("teamPanel.role")}
id="team-member-role"
name="role"
required={true}
placeholder={t("teamPanel.selectRole")}
isHidden={true}
value={role[0] || ""}
sx={{ mb: theme.spacing(5) }}
onChange={(event) => setRole([event.target.value])}
items={[
{ _id: "admin", name: t("roles.admin") },
{ _id: "user", name: t("roles.teamMember") },
]}
/>
<PasswordTooltip
feedback={feedback}
form={form}
>
<TextInput
type="password"
id="register-password-input"
name="password"
label={t("auth.common.inputs.password.label")}
isRequired={true}
placeholder="••••••••••"
value={form.password}
onChange={onChange}
error={errors.password && errors.password[0] ? true : false}
endAdornment={<PasswordEndAdornment />}
sx={{ mb: theme.spacing(5) }}
/>
</PasswordTooltip>
<TextInput
type="password"
id="register-confirm-input"
name="confirm"
label={t("auth.common.inputs.passwordConfirm.label")}
gap={theme.spacing(4)}
isRequired={true}
placeholder={t("auth.common.inputs.passwordConfirm.placeholder")}
autoComplete="current-password"
value={form.confirm}
onChange={onChange}
error={errors.confirm && errors.confirm[0] ? true : false}
endAdornment={<PasswordEndAdornment />}
sx={{ mb: theme.spacing(5) }}
/>
<Stack
direction="row"
spacing={theme.spacing(10)}
mt={theme.spacing(8)}
justifyContent="flex-end"
>
<Button
variant="contained"
color="error"
onClick={closeAddMemberModal}
disabled={isLoadingSubmit}
>
{t("teamPanel.cancel")}
</Button>
<Button
variant="contained"
color="accent"
onClick={onsubmitAddMember}
disabled={isLoadingSubmit}
>
{t("teamPanel.addTeamMember.addButton")}
</Button>
</Stack>
</GenericDialog>
</>
);
};
AddTeamMember.propTypes = {
handleIsRegisterOpen: PropTypes.func.isRequired,
isRegisterOpen: PropTypes.bool.isRequired,
onMemberAdded: PropTypes.func.isRequired,
};
export default AddTeamMember;
@@ -9,12 +9,10 @@ import { networkService } from "../../../main";
import { createToast } from "../../../Utils/toastUtils";
import Select from "../../../Components/Inputs/Select";
import { GenericDialog } from "../../../Components/Dialog/genericDialog";
import AddTeamMember from "../components/AddTeamMember";
import DataTable from "../../../Components/Table";
import { useGetInviteToken } from "../../../Hooks/inviteHooks";
import { useNavigate } from "react-router-dom";
import { useIsSuperAdmin } from "../../../Hooks/useIsAdmin";
import AddMemberMenu from "./AddMemberMenu";
/**
* TeamPanel component manages the organization and team members,
* providing functionalities like renaming the organization, managing team members,
@@ -67,10 +65,7 @@ const TeamPanel = () => {
render: (row) => row.role,
},
];
const [refreshTrigger, setRefreshTrigger] = useState(false);
const refreshTeamList = () => {
setRefreshTrigger((prev) => !prev);
};
useEffect(() => {
const fetchTeam = async () => {
try {
@@ -84,7 +79,7 @@ const TeamPanel = () => {
};
fetchTeam();
}, [refreshTrigger]);
}, []);
useEffect(() => {
const ROLE_MAP = {
@@ -114,10 +109,6 @@ const TeamPanel = () => {
setIsDisabled(Object.keys(errors).length !== 0 || toInvite.email === "");
}, [errors, toInvite.email]);
const [isOpen, setIsOpen] = useState(false);
const [isRegisterOpen, setIsRegisterOpen] = useState(false);
const handleIsRegisterOpen = (open) => {
setIsRegisterOpen(open);
};
const handleChange = (event) => {
const { value } = event.target;
@@ -147,6 +138,7 @@ const TeamPanel = () => {
const handleGetToken = async () => {
await getInviteToken({ email: toInvite.email, role: toInvite.role });
};
const handleInviteMember = async () => {
if (!toInvite.email) {
setErrors((prev) => ({ ...prev, email: "Email is required." }));
@@ -247,16 +239,13 @@ const TeamPanel = () => {
</Button>
</ButtonGroup>
</Stack>
<AddTeamMember
handleIsRegisterOpen={handleIsRegisterOpen}
isRegisterOpen={isRegisterOpen}
onMemberAdded={refreshTeamList}
/>
<AddMemberMenu
handleInviteOpen={() => setIsOpen(true)}
handleIsRegisterOpen={handleIsRegisterOpen}
/>
<Button
variant="contained"
color="accent"
onClick={() => setIsOpen(true)}
>
{t("teamPanel.inviteTeamMember")}
</Button>
</Stack>
<DataTable
@@ -275,6 +264,7 @@ const TeamPanel = () => {
}}
/>
</Stack>
<GenericDialog
title={t("teamPanel.inviteNewTeamMember")}
description={t("teamPanel.inviteDescription")}
@@ -93,14 +93,14 @@ const PasswordTooltip = ({ feedback, form, children }) => {
},
}}
>
<span style={{ display: "inline-block", width: "100%" }}>{children}</span>
{children}
</Tooltip>
);
};
PasswordTooltip.propTypes = {
feedback: PropTypes.shape({
length: PropTypes.string,
length: PropTypes.string.isRequired,
special: PropTypes.string,
number: PropTypes.string,
uppercase: PropTypes.string,
@@ -1,71 +0,0 @@
import { useState } from "react";
import { newOrChangedCredentials } from "../../../Validation/validation";
const usePasswordFeedback = () => {
const [feedback, setFeedback] = useState({});
const getFeedbackStatus = (form, errors, field, criteria) => {
const fieldErrors = errors?.[field];
const isFieldEmpty = form?.[field]?.length === 0;
const hasError = fieldErrors?.includes(criteria) || fieldErrors?.includes("empty");
const isCorrect = !isFieldEmpty && !hasError;
if (isCorrect) {
return "success";
} else if (hasError) {
return "error";
} else {
return "info";
}
};
const handlePasswordFeedback = (updatedForm, name, value, form, errors, setErrors) => {
const validateValue = { [name]: value };
const validateOptions = { abortEarly: false, context: { password: form.password } };
if (name === "password" && form.confirm.length > 0) {
validateValue.confirm = form.confirm;
validateOptions.context = { password: value };
} else if (name === "confirm") {
validateValue.password = form.password;
}
const { error } = newOrChangedCredentials.validate(validateValue, validateOptions);
const pwdErrors = error?.details.map((error) => ({
path: error.path[0],
type: error.type,
}));
const errorsByPath =
pwdErrors &&
pwdErrors.reduce((acc, { path, type }) => {
if (!acc[path]) {
acc[path] = [];
}
acc[path].push(type);
return acc;
}, {});
const oldErrors = { ...errors };
if (name === "password") {
oldErrors.password = undefined;
} else if (name === "confirm") {
oldErrors.confirm = undefined;
}
const newErrors = { ...oldErrors, ...errorsByPath };
setErrors(newErrors);
const newFeedback = {
length: getFeedbackStatus(updatedForm, errorsByPath, "password", "string.min"),
special: getFeedbackStatus(updatedForm, errorsByPath, "password", "special"),
number: getFeedbackStatus(updatedForm, errorsByPath, "password", "number"),
uppercase: getFeedbackStatus(updatedForm, errorsByPath, "password", "uppercase"),
lowercase: getFeedbackStatus(updatedForm, errorsByPath, "password", "lowercase"),
confirm: getFeedbackStatus(updatedForm, errorsByPath, "confirm", "different"),
};
setFeedback(newFeedback);
};
return { feedback, handlePasswordFeedback, getFeedbackStatus };
};
export default usePasswordFeedback;
@@ -11,12 +11,7 @@ import PropTypes from "prop-types";
import { useSelector } from "react-redux";
const MonitorsList = ({
isLoading = false,
shouldRender = true,
monitors = [],
statusPage = {},
}) => {
const MonitorsList = ({ isLoading = false, shouldRender = true, monitors = [] }) => {
const theme = useTheme();
const { determineState } = useMonitorUtils();
@@ -45,12 +40,10 @@ const MonitorsList = ({
alignItems="center"
gap={theme.spacing(20)}
>
{statusPage.showCharts !== false && (
<Box flex={9}>
<StatusPageBarChart checks={monitor?.checks?.slice().reverse()} />
</Box>
)}
<Box flex={statusPage.showCharts !== false ? 1 : 10}>
<Box flex={9}>
<StatusPageBarChart checks={monitor?.checks?.slice().reverse()} />
</Box>
<Box flex={1}>
<StatusLabel
status={status}
text={status}
@@ -67,7 +60,6 @@ const MonitorsList = ({
MonitorsList.propTypes = {
monitors: PropTypes.array.isRequired,
statusPage: PropTypes.object,
};
export default MonitorsList;
+1 -4
View File
@@ -154,10 +154,7 @@ const PublicStatus = () => {
/>
<Typography variant="h2">{t("statusPageStatusServiceStatus")}</Typography>
<StatusBar monitors={monitors} />
<MonitorsList
monitors={monitors}
statusPage={statusPage}
/>
<MonitorsList monitors={monitors} />
{link}
</Stack>
);
+1 -1
View File
@@ -223,7 +223,7 @@ const Routes = () => {
<Route
exact
path="/register/:token"
element={<AuthRegister superAdminExists={true} />}
element={<AuthRegister />}
/>
<Route
-45
View File
@@ -360,12 +360,6 @@
"demoUser": "Demo user"
},
"teamPanel": {
"addTeamMember": {
"addMemberMenu": "Add Team Member",
"title": "Register new team member",
"description": "Create a new user and share the credentials with them. This method gives the member immediate access to all monitors.",
"addButton": "Add Member"
},
"teamMembers": "Team members",
"filter": {
"all": "All",
@@ -381,45 +375,6 @@
"noMembers": "There are no team members with this role",
"getToken": "Get token",
"emailToken": "E-mail token",
"register": "Register a team member",
"registerToast": {
"success": "User created, share credentials with the member securely.",
"dbUserExists": "User already exists.",
"unknownError": "Unknown error occurred."
},
"registerTeamMember": {
"title": "Register team member",
"auth": {
"common": {
"inputs": {
"firstName": {
"errors": {
"empty": "Please enter a name",
"pattern": "Name must contain only letters, spaces, apostrophes, or hyphens"
}
},
"lastName": {
"errors": {
"empty": "Please enter a surname",
"pattern": "Surname must contain only letters, spaces, apostrophes, or hyphens"
}
},
"email": {
"errors": {
"empty": "To continue, please enter an email address",
"invalid": "Please recheck validity of entered email address"
}
},
"role": {
"errors": {
"empty": "Role is required"
}
}
}
}
}
},
"role": "Role",
"table": {
"name": "Name",
"email": "Email",
+1 -1
View File
@@ -5,7 +5,7 @@ import { execSync } from "child_process";
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), "");
let version = "3.1.7";
let version = "3.1.4";
return {
base: "/",
+10 -1
View File
@@ -1,9 +1,18 @@
FROM node:20-slim AS build
FROM node:20-alpine AS build
ENV NODE_OPTIONS="--max-old-space-size=4096"
WORKDIR /app
RUN apk add --no-cache \
python3 \
make g++ \
gcc \
libc-dev \
linux-headers \
libusb-dev \
eudev-dev
COPY ./client/package*.json ./
RUN npm install
+1 -6
View File
@@ -1,9 +1,4 @@
FROM node:20-slim
# Install ping
RUN apt-get update \
&& apt-get install -y iputils-ping \
&& rm -rf /var/lib/apt/lists/*
FROM node:20-alpine
WORKDIR /app
+11 -1
View File
@@ -1,9 +1,19 @@
FROM node:20-slim AS build
FROM node:20-alpine AS build
ENV NODE_OPTIONS="--max-old-space-size=4096"
WORKDIR /app
RUN apk add --no-cache \
python3 \
make g++ \
gcc \
libc-dev \
linux-headers \
libusb-dev \
eudev-dev
COPY ./client/package*.json ./
RUN npm install
+1 -6
View File
@@ -1,9 +1,4 @@
FROM node:20-slim
# Install ping
RUN apt-get update \
&& apt-get install -y iputils-ping \
&& rm -rf /var/lib/apt/lists/*
FROM node:20-alpine
WORKDIR /app
+1 -2
View File
@@ -2,7 +2,6 @@ services:
server:
image: ghcr.io/bluewave-labs/checkmate-backend-mono-multiarch:latest
restart: always
pull_policy: always
ports:
- "52345:52345"
environment:
@@ -17,7 +16,7 @@ services:
mongodb:
image: mongo:4.4.18
restart: always
command: ["mongod", "--quiet", "--bind_ip_all"]
command: ["mongod", "--quiet", --bind_ip_all"]
ports:
- "27017:27017"
volumes:
+7 -24
View File
@@ -1,42 +1,25 @@
# ---------------------
# Frontend build stage
# ---------------------
FROM node:24-slim AS frontend-build
# Install ping
RUN apt-get update \
&& apt-get install -y iputils-ping \
&& rm -rf /var/lib/apt/lists/*
FROM node:20-alpine AS frontend-build
WORKDIR /app/client
COPY client/package.json ./
RUN npm install
RUN npm install esbuild@0.25.5 --build-from-source
COPY client/package*.json ./
RUN npm ci
COPY client ./
RUN npm run build
# ---------------------
# Backend stage
# ---------------------
FROM node:24-slim AS backend
FROM node:20-alpine AS backend
WORKDIR /app/server
COPY server/package.json ./
RUN npm install
COPY server ./
COPY --from=frontend-build /app/client/dist ./public
RUN npm ci
RUN chmod +x ./scripts/inject-vars.sh
EXPOSE 52345
CMD ["sh", "-c", "./scripts/inject-vars.sh && node ./src/index.js"]
CMD ./scripts/inject-vars.sh && node ./src/index.js
+2 -7
View File
@@ -1,9 +1,4 @@
FROM node:20-slim AS frontend-build
# Install ping
RUN apt-get update \
&& apt-get install -y iputils-ping \
&& rm -rf /var/lib/apt/lists/*
FROM node:20-alpine AS frontend-build
WORKDIR /app/client
@@ -13,7 +8,7 @@ RUN npm install
COPY client ./
RUN npm run build
FROM node:20-slim AS app
FROM node:20-alpine AS app
WORKDIR /app/server
+10 -1
View File
@@ -1,9 +1,18 @@
FROM node:20-slim AS build
FROM node:20-alpine AS build
ENV NODE_OPTIONS="--max-old-space-size=4096"
WORKDIR /app
RUN apk add --no-cache \
python3 \
make g++ \
gcc \
libc-dev \
linux-headers \
libusb-dev \
eudev-dev
COPY ./client/package*.json ./
RUN npm install
+1 -6
View File
@@ -1,9 +1,4 @@
FROM node:20-slim
# Install ping
RUN apt-get update \
&& apt-get install -y iputils-ping \
&& rm -rf /var/lib/apt/lists/*
FROM node:20-alpine
WORKDIR /app
+10 -1
View File
@@ -1,9 +1,18 @@
FROM node:20-slim AS build
FROM node:20-alpine AS build
ENV NODE_OPTIONS="--max-old-space-size=4096"
WORKDIR /app
RUN apk add --no-cache \
python3 \
make g++ \
gcc \
libc-dev \
linux-headers \
libusb-dev \
eudev-dev
COPY ./client/package*.json ./
RUN npm install
+1 -6
View File
@@ -1,9 +1,4 @@
FROM node:20-slim
# Install ping
RUN apt-get update \
&& apt-get install -y iputils-ping \
&& rm -rf /var/lib/apt/lists/*
FROM node:20-alpine
ENV NODE_OPTIONS="--max-old-space-size=2048"
+11 -1
View File
@@ -1,9 +1,19 @@
FROM node:20-slim AS build
FROM node:20-alpine AS build
ENV NODE_OPTIONS="--max-old-space-size=4096"
WORKDIR /app
RUN apk add --no-cache \
python3 \
make g++ \
gcc \
libc-dev \
linux-headers \
libusb-dev \
eudev-dev
COPY ./client/package*.json ./
RUN npm install
+1 -6
View File
@@ -1,9 +1,4 @@
FROM node:20-slim
# Install ping
RUN apt-get update \
&& apt-get install -y iputils-ping \
&& rm -rf /var/lib/apt/lists/*
FROM node:20-alpine
ENV NODE_OPTIONS="--max-old-space-size=2048"
+42 -448
View File
@@ -39,7 +39,7 @@
"ping": "0.4.4",
"sharp": "0.33.5",
"ssl-checker": "2.0.10",
"super-simple-scheduler": "1.4.1",
"super-simple-scheduler": "1.4.0",
"swagger-ui-express": "5.0.1",
"winston": "^3.13.0"
},
@@ -248,20 +248,10 @@
"kuler": "^2.0.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz",
"integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==",
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.8.0.tgz",
"integrity": "sha512-MJQFqrZgcW0UNYLGOuQpey/oTN59vyWwplvCGZztn1cKz9agZPPYpJB7h2OMmuu7VLqkvEjN8feFZJmxNF9D+Q==",
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
"integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -469,19 +459,33 @@
}
},
"node_modules/@humanfs/node": {
"version": "0.16.7",
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
"integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
"version": "0.16.6",
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
"integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@humanfs/core": "^0.19.1",
"@humanwhocodes/retry": "^0.4.0"
"@humanwhocodes/retry": "^0.3.0"
},
"engines": {
"node": ">=18.18.0"
}
},
"node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
"integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=18.18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/@humanwhocodes/module-importer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
@@ -510,130 +514,6 @@
"url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
"integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.0.4"
}
},
"node_modules/@img/sharp-darwin-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
"integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.0.4"
}
},
"node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
"integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
"integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
"integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
"cpu": [
"arm"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
"integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
"integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
"cpu": [
"s390x"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
@@ -650,104 +530,6 @@
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
"integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
"integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-linux-arm": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
"integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
"cpu": [
"arm"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.0.5"
}
},
"node_modules/@img/sharp-linux-arm64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
"integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.0.4"
}
},
"node_modules/@img/sharp-linux-s390x": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
"integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
"cpu": [
"s390x"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.0.4"
}
},
"node_modules/@img/sharp-linux-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
@@ -770,107 +552,6 @@
"@img/sharp-libvips-linux-x64": "1.0.4"
}
},
"node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
"integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
}
},
"node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
"integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.0.4"
}
},
"node_modules/@img/sharp-wasm32": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
"integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
"cpu": [
"wasm32"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"dependencies": {
"@emnapi/runtime": "^1.2.0"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-ia32": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
"integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
"cpu": [
"ia32"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
"integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
"cpu": [
"x64"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@ioredis/commands": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.3.1.tgz",
@@ -951,58 +632,6 @@
"sparse-bitfield": "^3.0.3"
}
},
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz",
"integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz",
"integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz",
"integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz",
"integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz",
@@ -1016,19 +645,6 @@
"linux"
]
},
"node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz",
"integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -1270,12 +886,6 @@
"undici-types": "~7.10.0"
}
},
"node_modules/@types/relateurl": {
"version": "0.2.33",
"resolved": "https://registry.npmjs.org/@types/relateurl/-/relateurl-0.2.33.tgz",
"integrity": "sha512-bTQCKsVbIdzLqZhLkF5fcJQreE4y1ro4DIyVrlDNSCJRRwHhB8Z+4zXXa8jN6eDvc2HbRsEYgbvrnGvi54EpSw==",
"license": "MIT"
},
"node_modules/@types/triple-beam": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
@@ -1905,9 +1515,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001739",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz",
"integrity": "sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA==",
"version": "1.0.30001737",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz",
"integrity": "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==",
"funding": [
{
"type": "opencollective",
@@ -3020,9 +2630,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.213",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.213.tgz",
"integrity": "sha512-xr9eRzSLNa4neDO0xVFrkXu3vyIzG4Ay08dApecw42Z1NbmCt+keEpXdvlYGVe0wtvY5dhW0Ay0lY0IOfsCg0Q==",
"version": "1.5.211",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.211.tgz",
"integrity": "sha512-IGBvimJkotaLzFnwIVgW9/UD/AOJ2tByUmeOrtqBfACSbAw5b1G0XpvdaieKyc7ULmbwXVx+4e4Be8pOPBrYkw==",
"license": "ISC"
},
"node_modules/emitter-component": {
@@ -3831,20 +3441,6 @@
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"license": "MIT"
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -4295,12 +3891,11 @@
"license": "MIT"
},
"node_modules/htmlnano": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.3.tgz",
"integrity": "sha512-mzTUHhxdfDw80X36rv5qFvjvor7r0uCwXI5mzo8CW61tImbe5Jpvl3JzPAetVh54wUYVuoa8x3qw8LFn8B3gHQ==",
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.2.tgz",
"integrity": "sha512-8Fst+0bhAfU362S6oHVb4wtJj/UYEFr0qiCLAEi8zioqmp1JYBQx5crZAADlFVX0Ly/6s/IQz6G7PL9/hgoJaQ==",
"license": "MIT",
"dependencies": {
"@types/relateurl": "^0.2.33",
"cosmiconfig": "^9.0.0",
"posthtml": "^0.16.5"
},
@@ -6206,14 +5801,13 @@
}
},
"node_modules/nise/node_modules/path-to-regexp": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
"integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
"integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
"dev": true,
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
"engines": {
"node": ">=16"
}
},
"node_modules/node-abort-controller": {
@@ -8271,9 +7865,9 @@
}
},
"node_modules/super-simple-scheduler": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.4.1.tgz",
"integrity": "sha512-jKVwxdRBXOHrUstA2pvO4jBZIA3CUinKiQ6k5C2Wn5tscoV3srppPxW0zG3VNKTCaGxo3yPn6Zaf3IDVBcAwRA==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.4.0.tgz",
"integrity": "sha512-DO8HTrrx+23Cz2RPCuniPZ9IhHx2lgtF8ZJiBV4JfUhqDqxuJ2leQ+l3gZ+vniy4DfpMki7s6bZwM3u8norQEA==",
"license": "MIT",
"dependencies": {
"uuid": "11.1.0",
@@ -8345,9 +7939,9 @@
}
},
"node_modules/swagger-ui-dist": {
"version": "5.28.1",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.28.1.tgz",
"integrity": "sha512-IvPrtNi8MvjiuDgoSmPYgg27Lvu38fnLD1OSd8Y103xXsPAqezVNnNeHnVCZ/d+CMXJblflGaIyHxAYIF3O71w==",
"version": "5.28.0",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.28.0.tgz",
"integrity": "sha512-I9ibQtr77BPzT28WFWMVktzQOtWzoSS2J99L0Att8gDar1atl1YTRI7NUFSr4kj8VvWICgylanYHIoHjITc7iA==",
"license": "Apache-2.0",
"dependencies": {
"@scarf/scarf": "=1.4.0"
+1 -1
View File
@@ -46,7 +46,7 @@
"ping": "0.4.4",
"sharp": "0.33.5",
"ssl-checker": "2.0.10",
"super-simple-scheduler": "1.4.1",
"super-simple-scheduler": "1.4.0",
"swagger-ui-express": "5.0.1",
"winston": "^3.13.0"
},
+1 -1
View File
@@ -94,7 +94,7 @@ class AuthController extends BaseController {
await registrationBodyValidation.validateAsync(req.body);
const { user, token } = await this.userService.registerUser(req.body, req.file);
res.success({
msg: this.stringService.authCreateUser,
msg: this.stringService.get("authCreateUser"),
data: { user, token },
});
},
@@ -26,7 +26,7 @@ class StatusPageController extends BaseController {
teamId,
});
return res.success({
msg: this.stringService.statusPageCreate,
msg: this.stringService.get("statusPageCreate"),
data: statusPage,
});
},
@@ -41,10 +41,10 @@ class StatusPageController extends BaseController {
const statusPage = await this.db.statusPageModule.updateStatusPage(req.body, req.file);
if (statusPage === null) {
throw this.errorService.createNotFoundError(this.stringService.statusPageNotFound);
throw this.errorService.createNotFoundError(this.stringService.get("statusPageNotFound"));
}
return res.success({
msg: this.stringService.statusPageUpdate,
msg: this.stringService.get("statusPageUpdate"),
data: statusPage,
});
},
@@ -56,7 +56,7 @@ class StatusPageController extends BaseController {
async (req, res) => {
const statusPage = await this.db.statusPageModule.getStatusPage();
return res.success({
msg: this.stringService.statusPageByUrl,
msg: this.stringService.get("statusPageByUrl"),
data: statusPage,
});
},
@@ -71,7 +71,7 @@ class StatusPageController extends BaseController {
const statusPage = await this.db.statusPageModule.getStatusPageByUrl(req.params.url);
return res.success({
msg: this.stringService.statusPageByUrl,
msg: this.stringService.get("statusPageByUrl"),
data: statusPage,
});
},
@@ -85,7 +85,7 @@ class StatusPageController extends BaseController {
const statusPages = await this.db.statusPageModule.getStatusPagesByTeamId(teamId);
return res.success({
msg: this.stringService.statusPageByTeamId,
msg: this.stringService.get("statusPageByTeamId"),
data: statusPages,
});
},
@@ -97,7 +97,7 @@ class StatusPageController extends BaseController {
async (req, res) => {
await this.db.statusPageModule.deleteStatusPage(req.params.url);
return res.success({
msg: this.stringService.statusPageDelete,
msg: this.stringService.get("statusPageDelete"),
});
},
SERVICE_NAME,
@@ -162,14 +162,6 @@ class StatusPageModule {
as: "monitors.checks",
},
},
{
$lookup: {
from: "monitorstats",
localField: "monitors._id",
foreignField: "monitorId",
as: "monitors.stats",
},
},
{
$addFields: {
"monitors.orderIndex": {
@@ -189,9 +181,6 @@ class StatusPageModule {
},
},
},
"monitors.uptimePercentage": {
$arrayElemAt: ["$monitors.stats.uptimePercentage", 0],
},
},
},
{ $match: { "monitors.orderIndex": { $ne: -1 } } },
@@ -75,9 +75,6 @@ class NetworkService {
const { response, responseTime, error } = await this.timeRequest(() => this.ping.promise.probe(monitor.url));
if (!response) {
if (error) {
throw error;
}
throw new Error("Ping failed - no result returned");
}
+18 -417
View File
@@ -25,423 +25,24 @@ class StringService {
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 monitorGetByIdSuccess() {
return this.translationService.getTranslation("monitorGetByIdSuccess");
}
get monitorCreate() {
return this.translationService.getTranslation("monitorCreate");
}
get bulkMonitorsCreate() {
return this.translationService.getTranslation("bulkMonitorsCreate");
}
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");
}
// Webhook Messages
get webhookUnsupportedPlatform() {
return this.translationService.getTranslation("webhookUnsupportedPlatform");
}
get webhookSendError() {
return this.translationService.getTranslation("webhookSendError");
}
get webhookSendSuccess() {
return this.translationService.getTranslation("webhookSendSuccess");
}
get telegramRequiresBotTokenAndChatId() {
return this.translationService.getTranslation("telegramRequiresBotTokenAndChatId");
}
get webhookUrlRequired() {
return this.translationService.getTranslation("webhookUrlRequired");
}
get platformRequired() {
return this.translationService.getTranslation("platformRequired");
}
get testNotificationFailed() {
return this.translationService.getTranslation("testNotificationFailed");
}
get monitorUpAlert() {
return this.translationService.getTranslation("monitorUpAlert");
}
get monitorDownAlert() {
return this.translationService.getTranslation("monitorDownAlert");
}
getWebhookUnsupportedPlatform(platform) {
return this.translationService.getTranslation("webhookUnsupportedPlatform").replace("{platform}", platform);
}
getWebhookSendError(platform) {
return this.translationService.getTranslation("webhookSendError").replace("{platform}", platform);
}
getMonitorStatus(name, status, url) {
const translationKey = status === true ? "monitorStatusUp" : "monitorStatusDown";
return this.translationService.getTranslation(translationKey).replace("{name}", name).replace("{url}", url);
}
// 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 queueGetJobs() {
return this.translationService.getTranslation("queueGetJobs");
}
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");
}
get insufficientPermissions() {
return this.translationService.getTranslation("insufficientPermissions");
}
getDbFindMonitorById(monitorId) {
return this.translationService.getTranslation("dbFindMonitorById").replace("${monitorId}", monitorId);
}
get dbUserExists() {
return this.translationService.getTranslation("dbUserExists");
}
get testEmailSubject() {
return this.translationService.getTranslation("testEmailSubject");
}
get verifyOwnerNotFound() {
return this.translationService.getTranslation("verifyOwnerNotFound");
}
get verifyOwnerUnauthorized() {
return this.translationService.getTranslation("verifyOwnerUnauthorized");
}
get dbUserNotFound() {
return this.translationService.getTranslation("dbUserNotFound");
/**
* Get translation by key with optional parameters
* @param {string} key - Translation key
* @param {Object} params - Optional parameters to replace in the translation
* @returns {string} Translated string with replaced parameters
*/
get(key, params = {}) {
let translation = this.translationService.getTranslation(key);
// Replace parameters in the translation
Object.keys(params).forEach(paramKey => {
const paramValue = params[paramKey];
// Support both {param} and ${param} syntax
translation = translation.replace(new RegExp(`\\{${paramKey}\\}`, 'g'), paramValue);
translation = translation.replace(new RegExp(`\\$\\{${paramKey}\\}`, 'g'), paramValue);
});
return translation;
}
}