Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a924227805 | ||
|
|
e4e75632c5 | ||
|
|
ddb7c24832 | ||
|
|
2144d3f8c0 | ||
|
|
4a824dec81 | ||
|
|
b35fee1be8 | ||
|
|
1b167af12b | ||
|
|
f2962e34ae | ||
|
|
386db5029a | ||
|
|
0e6e54a124 | ||
|
|
c666cb3879 | ||
|
|
4039517683 | ||
|
|
00fc7b8038 | ||
|
|
81d283ec8d | ||
|
|
105100e349 | ||
|
|
4598690dbf | ||
|
|
f68341c315 | ||
|
|
e8a8cea909 | ||
|
|
bf53f107e3 | ||
|
|
00de974528 | ||
|
|
0445398e94 | ||
|
|
272e2ba955 | ||
|
|
0a2adc2d92 | ||
|
|
7008fc1770 | ||
|
|
041a261540 | ||
|
|
1008d819f7 | ||
|
|
fb84a79a04 | ||
|
|
fcd6786149 | ||
|
|
45bfdbdce0 | ||
|
|
1537c2141f | ||
|
|
ea968ebf3c | ||
|
|
3ba1a52644 | ||
|
|
a015cc6706 | ||
|
|
5bad362793 | ||
|
|
5520756edd | ||
|
|
ba954a1d6e | ||
|
|
2944ff298c | ||
|
|
80d121e87a | ||
|
|
8a7b852974 | ||
|
|
ed8352d857 | ||
|
|
41f64ca9fe | ||
|
|
738e49dc92 | ||
|
|
68a16257c6 | ||
|
|
5dcbae55f4 | ||
|
|
e0d963256c | ||
|
|
2bff4bbd15 | ||
|
|
a0eba89f34 | ||
|
|
4e78de9da2 | ||
|
|
85f00eb7e8 | ||
|
|
8e13855b66 | ||
|
|
06f49e24c2 | ||
|
|
e598ae45f7 | ||
|
|
ad7f469e06 | ||
|
|
c8d3e3b70c | ||
|
|
cb57ff5cc2 | ||
|
|
72f070fbcb | ||
|
|
fb0a86d37c | ||
|
|
36402dfefa | ||
|
|
c61874dbdd | ||
|
|
20848f28d5 | ||
|
|
5d23966f00 | ||
|
|
294cc66e4a | ||
|
|
0fd2e7032a | ||
|
|
340b85043e | ||
|
|
1821ae3c2b | ||
|
|
c536cb2afa | ||
|
|
3f79b4c2f6 | ||
|
|
137ba9185e | ||
|
|
2f6a0913dc | ||
|
|
2c7336d440 | ||
|
|
5a022ff3f6 | ||
|
|
fe4855500e | ||
|
|
ce8f5cbe0c | ||
|
|
f01030a47a | ||
|
|
3cbd907c09 | ||
|
|
8fae69b7a4 | ||
|
|
e45dd4240e | ||
|
|
fd951bfdd5 | ||
|
|
27d613b594 | ||
|
|
7c8ab64e34 | ||
|
|
ec7855bd02 | ||
|
|
d401eefd6b |
No files matched your search
@@ -0,0 +1 @@
|
|||||||
|
release_notes: false
|
||||||
@@ -7,7 +7,7 @@ import { URLSearchParams } from "url";
|
|||||||
const API_TOKEN = process.env.POEDITOR_API;
|
const API_TOKEN = process.env.POEDITOR_API;
|
||||||
const PROJECT_ID = process.env.POEDITOR_PROJECT_ID;
|
const PROJECT_ID = process.env.POEDITOR_PROJECT_ID;
|
||||||
const LANGUAGES = (
|
const LANGUAGES = (
|
||||||
process.env.LANGUAGES || "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja"
|
process.env.LANGUAGES || "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja,zh-cn,th"
|
||||||
).split(",");
|
).split(",");
|
||||||
const EXPORT_FORMAT = process.env.EXPORT_FORMAT || "key_value_json";
|
const EXPORT_FORMAT = process.env.EXPORT_FORMAT || "key_value_json";
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install client dependencies
|
- name: Install client dependencies
|
||||||
working-directory: client
|
working-directory: client
|
||||||
run: npm ci
|
run: npm install
|
||||||
|
|
||||||
- name: Check client formatting
|
- name: Check client formatting
|
||||||
working-directory: client
|
working-directory: client
|
||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install server dependencies
|
- name: Install server dependencies
|
||||||
working-directory: server
|
working-directory: server
|
||||||
run: npm ci
|
run: npm install
|
||||||
|
|
||||||
- name: Check server formatting
|
- name: Check server formatting
|
||||||
working-directory: server
|
working-directory: server
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ on:
|
|||||||
languages:
|
languages:
|
||||||
description: "Languages to synchronize (comma separated, e.g.: tr,en,es)"
|
description: "Languages to synchronize (comma separated, e.g.: tr,en,es)"
|
||||||
required: false
|
required: false
|
||||||
default: "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja"
|
default: "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja,zh-cn,th"
|
||||||
format:
|
format:
|
||||||
description: "Export format (key_value_json or json)"
|
description: "Export format (key_value_json or json)"
|
||||||
required: false
|
required: false
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ Usage instructions can be found [here](https://docs.checkmate.so/checkmate-2.1).
|
|||||||
|
|
||||||
See installation instructions in [Checkmate documentation portal](https://docs.checkmate.so/checkmate-2.1/users-guide/quickstart).
|
See installation instructions in [Checkmate documentation portal](https://docs.checkmate.so/checkmate-2.1/users-guide/quickstart).
|
||||||
|
|
||||||
Alternatively, you can also use [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md) or [Pikapods](https://www.pikapods.com/) to quickly spin off a Checkmate instance. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions.
|
Alternatively, you can also use [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (South Africa) or [Pikapods](https://www.pikapods.com/) to quickly spin off a Checkmate instance. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions.
|
||||||
|
|
||||||
### Using a Custom CA
|
### Using a Custom CA
|
||||||
|
|
||||||
|
|||||||
Generated
+22
-8
@@ -45,7 +45,7 @@
|
|||||||
"eslint-plugin-react-hooks": "^5.0.0",
|
"eslint-plugin-react-hooks": "^5.0.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.6",
|
"eslint-plugin-react-refresh": "^0.4.6",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"vite": "6.3.5"
|
"vite": "6.3.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ampproject/remapping": {
|
"node_modules/@ampproject/remapping": {
|
||||||
@@ -2422,13 +2422,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.9.0",
|
"version": "1.12.2",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
|
||||||
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
|
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.6",
|
"follow-redirects": "^1.15.6",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.4",
|
||||||
"proxy-from-env": "^1.1.0"
|
"proxy-from-env": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3756,6 +3756,20 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"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": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
@@ -6375,9 +6389,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "6.3.5",
|
"version": "6.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz",
|
||||||
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
|
"integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
|
|||||||
+1
-1
@@ -58,6 +58,6 @@
|
|||||||
"eslint-plugin-react-hooks": "^5.0.0",
|
"eslint-plugin-react-hooks": "^5.0.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.6",
|
"eslint-plugin-react-refresh": "^0.4.6",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"vite": "6.3.5"
|
"vite": "6.3.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import { Box, Stack, Tooltip, Typography } from "@mui/material";
|
import { Box, Stack, Tooltip, Typography } from "@mui/material";
|
||||||
import { formatDateWithTz } from "../../../Utils/timeUtils";
|
import { formatDateWithTz } from "../../../Utils/timeUtils";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState, forwardRef } from "react";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
@@ -17,26 +17,32 @@ import PropTypes from "prop-types";
|
|||||||
* @returns {JSX.Element} The Bar component.
|
* @returns {JSX.Element} The Bar component.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const Bar = ({ width, height, backgroundColor, borderRadius, children }) => {
|
const Bar = forwardRef(
|
||||||
const theme = useTheme();
|
({ width, height, backgroundColor, borderRadius, children, ...otherProps }, ref) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
position="relative"
|
ref={ref}
|
||||||
width={width}
|
position="relative"
|
||||||
height={height}
|
width={width}
|
||||||
backgroundColor={backgroundColor}
|
height={height}
|
||||||
sx={{
|
backgroundColor={backgroundColor}
|
||||||
borderRadius: borderRadius || theme.spacing(1.5),
|
sx={{
|
||||||
}}
|
borderRadius: borderRadius || theme.spacing(1.5),
|
||||||
>
|
}}
|
||||||
{children}
|
{...otherProps}
|
||||||
</Box>
|
>
|
||||||
);
|
{children}
|
||||||
};
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Bar.displayName = "Bar";
|
||||||
|
|
||||||
Bar.propTypes = {
|
Bar.propTypes = {
|
||||||
width: PropTypes.string.isRequired,
|
width: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
|
||||||
height: PropTypes.string.isRequired,
|
height: PropTypes.string.isRequired,
|
||||||
backgroundColor: PropTypes.string.isRequired,
|
backgroundColor: PropTypes.string.isRequired,
|
||||||
borderRadius: PropTypes.string,
|
borderRadius: PropTypes.string,
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ const Check = ({ text, noHighlightText, variant = "info", outlined = false }) =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
Check.propTypes = {
|
Check.propTypes = {
|
||||||
text: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
|
text: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
|
||||||
noHighlightText: PropTypes.string,
|
noHighlightText: PropTypes.string,
|
||||||
variant: PropTypes.oneOf(["info", "error", "success"]),
|
variant: PropTypes.oneOf(["info", "error", "success"]),
|
||||||
outlined: PropTypes.bool,
|
outlined: PropTypes.bool,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useId } from "react";
|
|||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { Modal, Stack, Typography } from "@mui/material";
|
import { Modal, Stack, Typography } from "@mui/material";
|
||||||
|
|
||||||
const GenericDialog = ({ title, description, open, onClose, theme, children }) => {
|
const GenericDialog = ({ title, description, open, onClose, theme, children, width }) => {
|
||||||
const titleId = useId();
|
const titleId = useId();
|
||||||
const descriptionId = useId();
|
const descriptionId = useId();
|
||||||
const ariaDescribedBy = description?.length > 0 ? descriptionId : "";
|
const ariaDescribedBy = description?.length > 0 ? descriptionId : "";
|
||||||
@@ -16,6 +16,7 @@ const GenericDialog = ({ title, description, open, onClose, theme, children }) =
|
|||||||
>
|
>
|
||||||
<Stack
|
<Stack
|
||||||
gap={theme.spacing(2)}
|
gap={theme.spacing(2)}
|
||||||
|
width={width}
|
||||||
sx={{
|
sx={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: "50%",
|
top: "50%",
|
||||||
@@ -39,6 +40,7 @@ const GenericDialog = ({ title, description, open, onClose, theme, children }) =
|
|||||||
fontSize={16}
|
fontSize={16}
|
||||||
color={theme.palette.primary.contrastText}
|
color={theme.palette.primary.contrastText}
|
||||||
fontWeight={600}
|
fontWeight={600}
|
||||||
|
marginBottom={theme.spacing(4)}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -46,6 +48,7 @@ const GenericDialog = ({ title, description, open, onClose, theme, children }) =
|
|||||||
<Typography
|
<Typography
|
||||||
id={descriptionId}
|
id={descriptionId}
|
||||||
color={theme.palette.primary.contrastTextTertiary}
|
color={theme.palette.primary.contrastTextTertiary}
|
||||||
|
marginBottom={theme.spacing(4)}
|
||||||
>
|
>
|
||||||
{description}
|
{description}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -64,6 +67,7 @@ GenericDialog.propTypes = {
|
|||||||
theme: PropTypes.object.isRequired,
|
theme: PropTypes.object.isRequired,
|
||||||
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node])
|
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node])
|
||||||
.isRequired,
|
.isRequired,
|
||||||
|
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
|
||||||
};
|
};
|
||||||
|
|
||||||
export { GenericDialog };
|
export { GenericDialog };
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import GenericFallback from "../GenericFallback";
|
||||||
|
import Fallback from "../Fallback";
|
||||||
|
import { Typography } from "@mui/material";
|
||||||
|
import { useTheme } from "@emotion/react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useIsAdmin } from "../../Hooks/useIsAdmin";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
const PageStateWrapper = ({
|
||||||
|
networkError,
|
||||||
|
isLoading,
|
||||||
|
items,
|
||||||
|
type,
|
||||||
|
fallbackLink,
|
||||||
|
fallbackChildren,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const isAdmin = useIsAdmin();
|
||||||
|
const isEmpty = items === null || (Array.isArray(items) && items.length === 0);
|
||||||
|
const hasInitialized = items !== undefined && !(isLoading && isEmpty);
|
||||||
|
const showEmpty = !isLoading && isEmpty;
|
||||||
|
if (networkError) {
|
||||||
|
return (
|
||||||
|
<GenericFallback>
|
||||||
|
<Typography
|
||||||
|
variant="h1"
|
||||||
|
marginY={theme.spacing(4)}
|
||||||
|
color={theme.palette.primary.contrastTextTertiary}
|
||||||
|
>
|
||||||
|
{t("common.toasts.networkError")}
|
||||||
|
</Typography>
|
||||||
|
<Typography>{t("common.toasts.checkConnection")}</Typography>
|
||||||
|
</GenericFallback>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!hasInitialized) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (showEmpty) {
|
||||||
|
return (
|
||||||
|
<Fallback
|
||||||
|
type={type}
|
||||||
|
title={t(`${type}.fallback.title`)}
|
||||||
|
checks={t(`${type}.fallback.checks`, { returnObjects: true })}
|
||||||
|
link={fallbackLink}
|
||||||
|
isAdmin={isAdmin}
|
||||||
|
>
|
||||||
|
{fallbackChildren}
|
||||||
|
</Fallback>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <>{children}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
PageStateWrapper.propTypes = {
|
||||||
|
networkError: PropTypes.any,
|
||||||
|
isLoading: PropTypes.bool,
|
||||||
|
items: PropTypes.oneOfType([PropTypes.array, PropTypes.oneOf([null])]),
|
||||||
|
type: PropTypes.string,
|
||||||
|
fallbackLink: PropTypes.string,
|
||||||
|
fallbackChildren: PropTypes.node,
|
||||||
|
children: PropTypes.node,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PageStateWrapper;
|
||||||
@@ -14,25 +14,25 @@ const useGetInviteToken = () => {
|
|||||||
const clearToken = () => {
|
const clearToken = () => {
|
||||||
setToken(undefined);
|
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 }) => {
|
const getInviteToken = async ({ email, role }) => {
|
||||||
try {
|
try {
|
||||||
const response = await networkService.requestInvitationToken({
|
setIsLoading(true);
|
||||||
email,
|
const token = await fetchToken(email, role);
|
||||||
role,
|
|
||||||
});
|
|
||||||
const token = response?.data?.data?.token;
|
|
||||||
if (typeof token === "undefined") {
|
|
||||||
throw new Error(t("inviteNoTokenFound"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let inviteLink = token;
|
let inviteLink = token;
|
||||||
|
|
||||||
if (typeof CLIENT_HOST !== "undefined") {
|
if (typeof CLIENT_HOST !== "undefined") {
|
||||||
inviteLink = `${CLIENT_HOST}/register/${token}`;
|
inviteLink = `${CLIENT_HOST}/register/${token}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
setToken(inviteLink);
|
setToken(inviteLink);
|
||||||
|
return token;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(error);
|
setError(error);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -40,7 +40,26 @@ const useGetInviteToken = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return [getInviteToken, clearToken, isLoading, error, token];
|
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];
|
||||||
};
|
};
|
||||||
|
|
||||||
export { useGetInviteToken };
|
export { useGetInviteToken };
|
||||||
@@ -34,7 +34,7 @@ const useCreateNotification = () => {
|
|||||||
|
|
||||||
const useGetNotificationsByTeamId = (updateTrigger) => {
|
const useGetNotificationsByTeamId = (updateTrigger) => {
|
||||||
const [notifications, setNotifications] = useState([]);
|
const [notifications, setNotifications] = useState([]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
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;
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
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;
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
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,10 +9,12 @@ import { networkService } from "../../../main";
|
|||||||
import { createToast } from "../../../Utils/toastUtils";
|
import { createToast } from "../../../Utils/toastUtils";
|
||||||
import Select from "../../../Components/Inputs/Select";
|
import Select from "../../../Components/Inputs/Select";
|
||||||
import { GenericDialog } from "../../../Components/Dialog/genericDialog";
|
import { GenericDialog } from "../../../Components/Dialog/genericDialog";
|
||||||
|
import AddTeamMember from "../components/AddTeamMember";
|
||||||
import DataTable from "../../../Components/Table";
|
import DataTable from "../../../Components/Table";
|
||||||
import { useGetInviteToken } from "../../../Hooks/inviteHooks";
|
import { useGetInviteToken } from "../../../Hooks/inviteHooks";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useIsSuperAdmin } from "../../../Hooks/useIsAdmin";
|
import { useIsSuperAdmin } from "../../../Hooks/useIsAdmin";
|
||||||
|
import AddMemberMenu from "./AddMemberMenu";
|
||||||
/**
|
/**
|
||||||
* TeamPanel component manages the organization and team members,
|
* TeamPanel component manages the organization and team members,
|
||||||
* providing functionalities like renaming the organization, managing team members,
|
* providing functionalities like renaming the organization, managing team members,
|
||||||
@@ -65,7 +67,10 @@ const TeamPanel = () => {
|
|||||||
render: (row) => row.role,
|
render: (row) => row.role,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
const [refreshTrigger, setRefreshTrigger] = useState(false);
|
||||||
|
const refreshTeamList = () => {
|
||||||
|
setRefreshTrigger((prev) => !prev);
|
||||||
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchTeam = async () => {
|
const fetchTeam = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -79,7 +84,7 @@ const TeamPanel = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fetchTeam();
|
fetchTeam();
|
||||||
}, []);
|
}, [refreshTrigger]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const ROLE_MAP = {
|
const ROLE_MAP = {
|
||||||
@@ -109,6 +114,10 @@ const TeamPanel = () => {
|
|||||||
setIsDisabled(Object.keys(errors).length !== 0 || toInvite.email === "");
|
setIsDisabled(Object.keys(errors).length !== 0 || toInvite.email === "");
|
||||||
}, [errors, toInvite.email]);
|
}, [errors, toInvite.email]);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [isRegisterOpen, setIsRegisterOpen] = useState(false);
|
||||||
|
const handleIsRegisterOpen = (open) => {
|
||||||
|
setIsRegisterOpen(open);
|
||||||
|
};
|
||||||
|
|
||||||
const handleChange = (event) => {
|
const handleChange = (event) => {
|
||||||
const { value } = event.target;
|
const { value } = event.target;
|
||||||
@@ -138,7 +147,6 @@ const TeamPanel = () => {
|
|||||||
const handleGetToken = async () => {
|
const handleGetToken = async () => {
|
||||||
await getInviteToken({ email: toInvite.email, role: toInvite.role });
|
await getInviteToken({ email: toInvite.email, role: toInvite.role });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInviteMember = async () => {
|
const handleInviteMember = async () => {
|
||||||
if (!toInvite.email) {
|
if (!toInvite.email) {
|
||||||
setErrors((prev) => ({ ...prev, email: "Email is required." }));
|
setErrors((prev) => ({ ...prev, email: "Email is required." }));
|
||||||
@@ -239,13 +247,16 @@ const TeamPanel = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Button
|
|
||||||
variant="contained"
|
<AddTeamMember
|
||||||
color="accent"
|
handleIsRegisterOpen={handleIsRegisterOpen}
|
||||||
onClick={() => setIsOpen(true)}
|
isRegisterOpen={isRegisterOpen}
|
||||||
>
|
onMemberAdded={refreshTeamList}
|
||||||
{t("teamPanel.inviteTeamMember")}
|
/>
|
||||||
</Button>
|
<AddMemberMenu
|
||||||
|
handleInviteOpen={() => setIsOpen(true)}
|
||||||
|
handleIsRegisterOpen={handleIsRegisterOpen}
|
||||||
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<DataTable
|
<DataTable
|
||||||
@@ -264,7 +275,6 @@ const TeamPanel = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<GenericDialog
|
<GenericDialog
|
||||||
title={t("teamPanel.inviteNewTeamMember")}
|
title={t("teamPanel.inviteNewTeamMember")}
|
||||||
description={t("teamPanel.inviteDescription")}
|
description={t("teamPanel.inviteDescription")}
|
||||||
|
|||||||
@@ -93,14 +93,14 @@ const PasswordTooltip = ({ feedback, form, children }) => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
<span style={{ display: "inline-block", width: "100%" }}>{children}</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
PasswordTooltip.propTypes = {
|
PasswordTooltip.propTypes = {
|
||||||
feedback: PropTypes.shape({
|
feedback: PropTypes.shape({
|
||||||
length: PropTypes.string.isRequired,
|
length: PropTypes.string,
|
||||||
special: PropTypes.string,
|
special: PropTypes.string,
|
||||||
number: PropTypes.string,
|
number: PropTypes.string,
|
||||||
uppercase: PropTypes.string,
|
uppercase: PropTypes.string,
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
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;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from "react";
|
import { useState, useCallback } from "react";
|
||||||
const useInfrastructureMonitorForm = () => {
|
const useInfrastructureMonitorForm = () => {
|
||||||
const [infrastructureMonitor, setInfrastructureMonitor] = useState({
|
const [infrastructureMonitor, setInfrastructureMonitor] = useState({
|
||||||
url: "",
|
url: "",
|
||||||
@@ -6,6 +6,8 @@ const useInfrastructureMonitorForm = () => {
|
|||||||
notifications: [],
|
notifications: [],
|
||||||
notify_email: false,
|
notify_email: false,
|
||||||
interval: 0.25,
|
interval: 0.25,
|
||||||
|
statusWindowSize: 5,
|
||||||
|
statusWindowThreshold: 60,
|
||||||
cpu: false,
|
cpu: false,
|
||||||
usage_cpu: "",
|
usage_cpu: "",
|
||||||
memory: false,
|
memory: false,
|
||||||
@@ -29,9 +31,10 @@ const useInfrastructureMonitorForm = () => {
|
|||||||
[event.target.name]: event.target.checked,
|
[event.target.name]: event.target.checked,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const initializeInfrastructureMonitorForCreate = (globalSettings) => {
|
const initializeInfrastructureMonitorForCreate = useCallback((globalSettings) => {
|
||||||
const gt = globalSettings?.data?.settings?.globalThresholds || {};
|
const gt = globalSettings?.data?.settings?.globalThresholds || {};
|
||||||
setInfrastructureMonitor({
|
setInfrastructureMonitor((prev) => ({
|
||||||
|
...prev,
|
||||||
url: "",
|
url: "",
|
||||||
name: "",
|
name: "",
|
||||||
notifications: [],
|
notifications: [],
|
||||||
@@ -45,17 +48,20 @@ const useInfrastructureMonitorForm = () => {
|
|||||||
temperature: gt.temperature !== undefined,
|
temperature: gt.temperature !== undefined,
|
||||||
usage_temperature: gt.temperature !== undefined ? gt.temperature.toString() : "",
|
usage_temperature: gt.temperature !== undefined ? gt.temperature.toString() : "",
|
||||||
secret: "",
|
secret: "",
|
||||||
});
|
}));
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const initializeInfrastructureMonitorForUpdate = (monitor) => {
|
const initializeInfrastructureMonitorForUpdate = useCallback((monitor) => {
|
||||||
const MS_PER_MINUTE = 60000;
|
const MS_PER_MINUTE = 60000;
|
||||||
const { thresholds = {} } = monitor;
|
const { thresholds = {} } = monitor;
|
||||||
setInfrastructureMonitor({
|
setInfrastructureMonitor((prev) => ({
|
||||||
|
...prev,
|
||||||
url: monitor.url.replace(/^https?:\/\//, ""),
|
url: monitor.url.replace(/^https?:\/\//, ""),
|
||||||
name: monitor.name || "",
|
name: monitor.name || "",
|
||||||
notifications: monitor.notifications || [],
|
notifications: monitor.notifications || [],
|
||||||
interval: monitor.interval / MS_PER_MINUTE,
|
interval: monitor.interval / MS_PER_MINUTE,
|
||||||
|
statusWindowSize: monitor.statusWindowSize,
|
||||||
|
statusWindowThreshold: monitor.statusWindowThreshold,
|
||||||
cpu: thresholds.usage_cpu !== undefined,
|
cpu: thresholds.usage_cpu !== undefined,
|
||||||
usage_cpu:
|
usage_cpu:
|
||||||
thresholds.usage_cpu !== undefined ? (thresholds.usage_cpu * 100).toString() : "",
|
thresholds.usage_cpu !== undefined ? (thresholds.usage_cpu * 100).toString() : "",
|
||||||
@@ -75,8 +81,8 @@ const useInfrastructureMonitorForm = () => {
|
|||||||
? (thresholds.usage_temperature * 100).toString()
|
? (thresholds.usage_temperature * 100).toString()
|
||||||
: "",
|
: "",
|
||||||
secret: monitor.secret || "",
|
secret: monitor.secret || "",
|
||||||
});
|
}));
|
||||||
};
|
}, []);
|
||||||
return {
|
return {
|
||||||
infrastructureMonitor,
|
infrastructureMonitor,
|
||||||
setInfrastructureMonitor,
|
setInfrastructureMonitor,
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ const useInfrastructureSubmit = () => {
|
|||||||
? infrastructureMonitor.url
|
? infrastructureMonitor.url
|
||||||
: infrastructureMonitor.name,
|
: infrastructureMonitor.name,
|
||||||
interval: infrastructureMonitor.interval * MS_PER_MINUTE,
|
interval: infrastructureMonitor.interval * MS_PER_MINUTE,
|
||||||
|
statusWindowSize: infrastructureMonitor.statusWindowSize,
|
||||||
|
statusWindowThreshold: infrastructureMonitor.statusWindowThreshold,
|
||||||
cpu: infrastructureMonitor.cpu,
|
cpu: infrastructureMonitor.cpu,
|
||||||
...(infrastructureMonitor.cpu
|
...(infrastructureMonitor.cpu
|
||||||
? { usage_cpu: infrastructureMonitor.usage_cpu }
|
? { usage_cpu: infrastructureMonitor.usage_cpu }
|
||||||
|
|||||||
@@ -86,7 +86,14 @@ const CreateInfrastructureMonitor = () => {
|
|||||||
setHttps(monitor.url.startsWith("https"));
|
setHttps(monitor.url.startsWith("https"));
|
||||||
initializeInfrastructureMonitorForUpdate(monitor);
|
initializeInfrastructureMonitorForUpdate(monitor);
|
||||||
}
|
}
|
||||||
}, [isCreate, monitor, globalSettings, globalSettingsLoading]);
|
}, [
|
||||||
|
isCreate,
|
||||||
|
monitor,
|
||||||
|
globalSettings,
|
||||||
|
globalSettingsLoading,
|
||||||
|
initializeInfrastructureMonitorForCreate,
|
||||||
|
initializeInfrastructureMonitorForUpdate,
|
||||||
|
]);
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
const onSubmit = async (event) => {
|
const onSubmit = async (event) => {
|
||||||
@@ -281,6 +288,39 @@ const CreateInfrastructureMonitor = () => {
|
|||||||
setNotifications={infrastructureMonitor.notifications}
|
setNotifications={infrastructureMonitor.notifications}
|
||||||
/>
|
/>
|
||||||
</ConfigBox>
|
</ConfigBox>
|
||||||
|
<ConfigBox>
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
variant="h2"
|
||||||
|
>
|
||||||
|
{t("createMonitorPage.incidentConfigTitle")}
|
||||||
|
</Typography>
|
||||||
|
<Typography component="p">
|
||||||
|
{t("createMonitorPage.incidentConfigDescription")}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Stack gap={theme.spacing(20)}>
|
||||||
|
<TextInput
|
||||||
|
name="statusWindowSize"
|
||||||
|
label={t("createMonitorPage.incidentConfigStatusWindowLabel")}
|
||||||
|
type="number"
|
||||||
|
value={infrastructureMonitor.statusWindowSize}
|
||||||
|
onChange={onChange}
|
||||||
|
error={errors["statusWindowSize"] ? true : false}
|
||||||
|
helperText={errors["statusWindowSize"]}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
name="statusWindowThreshold"
|
||||||
|
label={t("createMonitorPage.incidentConfigStatusWindowThresholdLabel")}
|
||||||
|
type="number"
|
||||||
|
value={infrastructureMonitor.statusWindowThreshold}
|
||||||
|
onChange={onChange}
|
||||||
|
error={errors["statusWindowThreshold"] ? true : false}
|
||||||
|
helperText={errors["statusWindowThreshold"]}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</ConfigBox>
|
||||||
<CustomAlertsSection
|
<CustomAlertsSection
|
||||||
errors={errors}
|
errors={errors}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
// Components
|
// Components
|
||||||
import { Stack, Typography } from "@mui/material";
|
import { Stack } from "@mui/material";
|
||||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||||
import MonitorCountHeader from "../../../Components/MonitorCountHeader";
|
import MonitorCountHeader from "../../../Components/MonitorCountHeader";
|
||||||
import MonitorCreateHeader from "../../../Components/MonitorCreateHeader";
|
import MonitorCreateHeader from "../../../Components/MonitorCreateHeader";
|
||||||
import MonitorsTable from "./Components/MonitorsTable";
|
import MonitorsTable from "./Components/MonitorsTable";
|
||||||
import Pagination from "../../..//Components/Table/TablePagination";
|
import Pagination from "../../..//Components/Table/TablePagination";
|
||||||
import GenericFallback from "../../../Components/GenericFallback";
|
import PageStateWrapper from "../../../Components/PageStateWrapper";
|
||||||
import Fallback from "../../../Components/Fallback";
|
|
||||||
import Filter from "./Components/Filters";
|
import Filter from "./Components/Filters";
|
||||||
import SearchComponent from "../../Uptime/Monitors/Components/SearchComponent";
|
import SearchComponent from "../../Uptime/Monitors/Components/SearchComponent";
|
||||||
// Utils
|
// Utils
|
||||||
@@ -81,75 +80,58 @@ const InfrastructureMonitors = () => {
|
|||||||
updateTrigger,
|
updateTrigger,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (networkError === true) {
|
|
||||||
return (
|
|
||||||
<GenericFallback>
|
|
||||||
<Typography
|
|
||||||
variant="h1"
|
|
||||||
marginY={theme.spacing(4)}
|
|
||||||
color={theme.palette.primary.contrastTextTertiary}
|
|
||||||
>
|
|
||||||
{t("common.toasts.networkError")}
|
|
||||||
</Typography>
|
|
||||||
<Typography>{t("common.toasts.checkConnection")}</Typography>
|
|
||||||
</GenericFallback>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isLoading && typeof summary?.totalMonitors === "undefined") {
|
|
||||||
return (
|
|
||||||
<Fallback
|
|
||||||
type="infrastructureMonitor"
|
|
||||||
title={t("infrastructureMonitor.fallback.title")}
|
|
||||||
checks={t("infrastructureMonitor.fallback.checks", { returnObjects: true })}
|
|
||||||
link="/infrastructure/create"
|
|
||||||
isAdmin={isAdmin}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap={theme.spacing(10)}>
|
<>
|
||||||
<Breadcrumbs list={BREADCRUMBS} />
|
<PageStateWrapper
|
||||||
<MonitorCreateHeader
|
networkError={networkError}
|
||||||
isAdmin={isAdmin}
|
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
path="/infrastructure/create"
|
items={monitors}
|
||||||
/>
|
type="infrastructureMonitor"
|
||||||
<Stack direction={"row"}>
|
fallbackLink="/infrastructure/create"
|
||||||
<MonitorCountHeader
|
>
|
||||||
isLoading={isLoading}
|
<Stack gap={theme.spacing(10)}>
|
||||||
monitorCount={summary?.totalMonitors ?? 0}
|
<Breadcrumbs list={BREADCRUMBS} />
|
||||||
/>
|
<MonitorCreateHeader
|
||||||
<Filter
|
isAdmin={isAdmin}
|
||||||
selectedStatus={selectedStatus}
|
isLoading={isLoading}
|
||||||
setSelectedStatus={setSelectedStatus}
|
path="/infrastructure/create"
|
||||||
setToFilterStatus={setToFilterStatus}
|
/>
|
||||||
handleReset={handleReset}
|
<Stack direction={"row"}>
|
||||||
/>
|
<MonitorCountHeader
|
||||||
<SearchComponent
|
isLoading={isLoading}
|
||||||
monitors={monitors}
|
monitorCount={summary?.totalMonitors ?? 0}
|
||||||
onSearchChange={setSearch}
|
/>
|
||||||
setIsSearching={setIsSearching}
|
<Filter
|
||||||
/>
|
selectedStatus={selectedStatus}
|
||||||
</Stack>
|
setSelectedStatus={setSelectedStatus}
|
||||||
|
setToFilterStatus={setToFilterStatus}
|
||||||
|
handleReset={handleReset}
|
||||||
|
/>
|
||||||
|
<SearchComponent
|
||||||
|
monitors={monitors}
|
||||||
|
onSearchChange={setSearch}
|
||||||
|
setIsSearching={setIsSearching}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
<MonitorsTable
|
<MonitorsTable
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
monitors={monitors}
|
monitors={monitors}
|
||||||
isAdmin={isAdmin}
|
isAdmin={isAdmin}
|
||||||
handleActionMenuDelete={handleActionMenuDelete}
|
handleActionMenuDelete={handleActionMenuDelete}
|
||||||
isSearching={isSearching}
|
isSearching={isSearching}
|
||||||
/>
|
/>
|
||||||
<Pagination
|
<Pagination
|
||||||
itemCount={summary?.totalMonitors}
|
itemCount={summary?.totalMonitors}
|
||||||
paginationLabel={t("monitors")}
|
paginationLabel={t("monitors")}
|
||||||
page={page}
|
page={page}
|
||||||
rowsPerPage={rowsPerPage}
|
rowsPerPage={rowsPerPage}
|
||||||
handleChangePage={handleChangePage}
|
handleChangePage={handleChangePage}
|
||||||
handleChangeRowsPerPage={handleChangeRowsPerPage}
|
handleChangeRowsPerPage={handleChangeRowsPerPage}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</PageStateWrapper>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Stack, Button } from "@mui/material";
|
import { Stack, Button } from "@mui/material";
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import Fallback from "../../Components/Fallback";
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import MaintenanceTable from "./MaintenanceTable";
|
import MaintenanceTable from "./MaintenanceTable";
|
||||||
@@ -8,24 +7,21 @@ import { useSelector } from "react-redux";
|
|||||||
import { networkService } from "../../main";
|
import { networkService } from "../../main";
|
||||||
import Breadcrumbs from "../../Components/Breadcrumbs";
|
import Breadcrumbs from "../../Components/Breadcrumbs";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useIsAdmin } from "../../Hooks/useIsAdmin";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Typography } from "@mui/material";
|
import PageStateWrapper from "../../Components/PageStateWrapper";
|
||||||
import GenericFallback from "../../Components/GenericFallback";
|
|
||||||
|
|
||||||
const Maintenance = () => {
|
const Maintenance = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const rowsPerPage = useSelector((state) => state?.ui?.maintenance?.rowsPerPage ?? 5);
|
const rowsPerPage = useSelector((state) => state?.ui?.maintenance?.rowsPerPage ?? 5);
|
||||||
const isAdmin = useIsAdmin();
|
const [maintenanceWindows, setMaintenanceWindows] = useState(undefined);
|
||||||
const [maintenanceWindows, setMaintenanceWindows] = useState([]);
|
|
||||||
const [maintenanceWindowCount, setMaintenanceWindowCount] = useState(0);
|
const [maintenanceWindowCount, setMaintenanceWindowCount] = useState(0);
|
||||||
const [page, setPage] = useState(0);
|
const [page, setPage] = useState(0);
|
||||||
const [sort, setSort] = useState({});
|
const [sort, setSort] = useState({});
|
||||||
const [updateTrigger, setUpdateTrigger] = useState(false);
|
const [updateTrigger, setUpdateTrigger] = useState(false);
|
||||||
const [networkError, setNetworkError] = useState(false);
|
const [networkError, setNetworkError] = useState(false);
|
||||||
const [isDataFetched, setIsDataFetched] = useState(false);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
const handleActionMenuDelete = () => {
|
const handleActionMenuDelete = () => {
|
||||||
setUpdateTrigger((prev) => !prev);
|
setUpdateTrigger((prev) => !prev);
|
||||||
@@ -34,80 +30,65 @@ const Maintenance = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchMaintenanceWindows = async () => {
|
const fetchMaintenanceWindows = async () => {
|
||||||
try {
|
try {
|
||||||
|
setNetworkError(false);
|
||||||
|
setIsLoading(true);
|
||||||
const response = await networkService.getMaintenanceWindowsByTeamId({
|
const response = await networkService.getMaintenanceWindowsByTeamId({
|
||||||
page: page,
|
page: page,
|
||||||
rowsPerPage: rowsPerPage,
|
rowsPerPage: rowsPerPage,
|
||||||
});
|
});
|
||||||
const { maintenanceWindows, maintenanceWindowCount } = response.data.data;
|
const { maintenanceWindows, maintenanceWindowCount } = response.data.data;
|
||||||
setMaintenanceWindows(maintenanceWindows);
|
setMaintenanceWindows(maintenanceWindows ?? []);
|
||||||
setMaintenanceWindowCount(maintenanceWindowCount);
|
setMaintenanceWindowCount(maintenanceWindowCount ?? 0);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setNetworkError(true);
|
setNetworkError(true);
|
||||||
} finally {
|
} finally {
|
||||||
setIsDataFetched(true);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
fetchMaintenanceWindows();
|
fetchMaintenanceWindows();
|
||||||
}, [page, rowsPerPage, updateTrigger]);
|
}, [page, rowsPerPage, updateTrigger]);
|
||||||
|
|
||||||
if (networkError) {
|
|
||||||
return (
|
|
||||||
<GenericFallback>
|
|
||||||
<Typography
|
|
||||||
variant="h1"
|
|
||||||
marginY={theme.spacing(4)}
|
|
||||||
color={theme.palette.primary.contrastTextTertiary}
|
|
||||||
>
|
|
||||||
{t("common.toasts.networkError")}
|
|
||||||
</Typography>
|
|
||||||
<Typography>{t("common.toasts.checkConnection")}</Typography>
|
|
||||||
</GenericFallback>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Only show the fallback if we've fetched data and there are no maintenance windows
|
|
||||||
if (isDataFetched && maintenanceWindows.length === 0) {
|
|
||||||
return (
|
|
||||||
<Fallback
|
|
||||||
type="maintenanceWindow"
|
|
||||||
title={t("maintenanceWindow.fallback.title")}
|
|
||||||
checks={t("maintenanceWindow.fallback.checks", { returnObjects: true })}
|
|
||||||
link="/maintenance/create"
|
|
||||||
isAdmin={isAdmin}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap={theme.spacing(10)}>
|
<>
|
||||||
<Stack
|
<PageStateWrapper
|
||||||
direction="row"
|
networkError={networkError}
|
||||||
justifyContent="space-between"
|
isLoading={isLoading}
|
||||||
alignItems="center"
|
items={maintenanceWindows}
|
||||||
mt={theme.spacing(5)}
|
type="maintenanceWindow"
|
||||||
|
fallbackLink="/maintenance/create"
|
||||||
>
|
>
|
||||||
<Breadcrumbs list={[{ name: "maintenance", path: "/maintenance" }]} />
|
<Stack gap={theme.spacing(10)}>
|
||||||
<Button
|
<Stack
|
||||||
variant="contained"
|
direction="row"
|
||||||
color="accent"
|
justifyContent="space-between"
|
||||||
onClick={() => {
|
alignItems="center"
|
||||||
navigate("/maintenance/create");
|
mt={theme.spacing(5)}
|
||||||
}}
|
>
|
||||||
sx={{ fontWeight: 500 }}
|
<Breadcrumbs list={[{ name: "maintenance", path: "/maintenance" }]} />
|
||||||
>
|
<Button
|
||||||
{t("createMaintenanceWindow")}
|
variant="contained"
|
||||||
</Button>
|
color="accent"
|
||||||
</Stack>
|
onClick={() => {
|
||||||
<MaintenanceTable
|
navigate("/maintenance/create");
|
||||||
page={page}
|
}}
|
||||||
setPage={setPage}
|
sx={{ fontWeight: 500 }}
|
||||||
rowsPerPage={rowsPerPage}
|
>
|
||||||
sort={sort}
|
{t("createMaintenanceWindow")}
|
||||||
setSort={setSort}
|
</Button>
|
||||||
maintenanceWindows={maintenanceWindows}
|
</Stack>
|
||||||
maintenanceWindowCount={maintenanceWindowCount}
|
<MaintenanceTable
|
||||||
updateCallback={handleActionMenuDelete}
|
page={page}
|
||||||
/>
|
setPage={setPage}
|
||||||
</Stack>
|
rowsPerPage={rowsPerPage}
|
||||||
|
sort={sort}
|
||||||
|
setSort={setSort}
|
||||||
|
maintenanceWindows={maintenanceWindows}
|
||||||
|
maintenanceWindowCount={maintenanceWindowCount}
|
||||||
|
updateCallback={handleActionMenuDelete}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</PageStateWrapper>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,10 @@ import Typography from "@mui/material/Typography";
|
|||||||
import Breadcrumbs from "../../Components/Breadcrumbs";
|
import Breadcrumbs from "../../Components/Breadcrumbs";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import DataTable from "../../Components/Table";
|
import DataTable from "../../Components/Table";
|
||||||
import Fallback from "../../Components/Fallback";
|
|
||||||
import ActionMenu from "./components/ActionMenu";
|
import ActionMenu from "./components/ActionMenu";
|
||||||
|
import PageStateWrapper from "../../Components/PageStateWrapper";
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
import { useIsAdmin } from "../../Hooks/useIsAdmin";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
@@ -23,7 +22,6 @@ const Notifications = () => {
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const BREADCRUMBS = [{ name: "notifications", path: "/notifications" }];
|
const BREADCRUMBS = [{ name: "notifications", path: "/notifications" }];
|
||||||
const [updateTrigger, setUpdateTrigger] = useState(false);
|
const [updateTrigger, setUpdateTrigger] = useState(false);
|
||||||
const isAdmin = useIsAdmin();
|
|
||||||
const [notifications, isLoading, error] = useGetNotificationsByTeamId(updateTrigger);
|
const [notifications, isLoading, error] = useGetNotificationsByTeamId(updateTrigger);
|
||||||
const [deleteNotification, isDeleting, deleteError] = useDeleteNotification();
|
const [deleteNotification, isDeleting, deleteError] = useDeleteNotification();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -76,49 +74,47 @@ const Notifications = () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (notifications?.length === 0) {
|
|
||||||
return (
|
|
||||||
<Fallback
|
|
||||||
type="notifications"
|
|
||||||
title={t("notifications.fallback.title")}
|
|
||||||
checks={t("notifications.fallback.checks", { returnObjects: true })}
|
|
||||||
link="/notifications/create"
|
|
||||||
isAdmin={isAdmin}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap={theme.spacing(10)}>
|
<>
|
||||||
<Breadcrumbs list={BREADCRUMBS} />
|
<PageStateWrapper
|
||||||
<Stack
|
networkError={error}
|
||||||
direction="row"
|
isLoading={isLoading}
|
||||||
justifyContent="flex-end"
|
items={notifications}
|
||||||
|
type="notifications"
|
||||||
|
fallbackLink="/notifications/create"
|
||||||
>
|
>
|
||||||
<Button
|
<Stack gap={theme.spacing(10)}>
|
||||||
variant="contained"
|
<Breadcrumbs list={BREADCRUMBS} />
|
||||||
color="accent"
|
<Stack
|
||||||
onClick={() => navigate("/notifications/create")}
|
direction="row"
|
||||||
>
|
justifyContent="flex-end"
|
||||||
{t("notifications.createButton")}
|
>
|
||||||
</Button>
|
<Button
|
||||||
</Stack>
|
variant="contained"
|
||||||
<Typography variant="h1">{t("notifications.createTitle")}</Typography>
|
color="accent"
|
||||||
<DataTable
|
onClick={() => navigate("/notifications/create")}
|
||||||
config={{
|
>
|
||||||
onRowClick: (row) => navigate(`/notifications/${row._id}`),
|
{t("notifications.createButton")}
|
||||||
rowSX: {
|
</Button>
|
||||||
cursor: "pointer",
|
</Stack>
|
||||||
"&:hover td": {
|
<Typography variant="h1">{t("notifications.createTitle")}</Typography>
|
||||||
backgroundColor: theme.palette.tertiary.main,
|
<DataTable
|
||||||
transition: "background-color .3s ease",
|
config={{
|
||||||
},
|
onRowClick: (row) => navigate(`/notifications/${row._id}`),
|
||||||
},
|
rowSX: {
|
||||||
}}
|
cursor: "pointer",
|
||||||
headers={headers}
|
"&:hover td": {
|
||||||
data={notifications}
|
backgroundColor: theme.palette.tertiary.main,
|
||||||
/>
|
transition: "background-color .3s ease",
|
||||||
</Stack>
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
headers={headers}
|
||||||
|
data={notifications}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</PageStateWrapper>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
// Components
|
// Components
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||||
import { Stack, Typography } from "@mui/material";
|
import { Stack } from "@mui/material";
|
||||||
import CreateMonitorHeader from "../../../Components/MonitorCreateHeader";
|
import CreateMonitorHeader from "../../../Components/MonitorCreateHeader";
|
||||||
import MonitorCountHeader from "../../../Components/MonitorCountHeader";
|
import MonitorCountHeader from "../../../Components/MonitorCountHeader";
|
||||||
import MonitorGrid from "./Components/MonitorGrid";
|
import MonitorGrid from "./Components/MonitorGrid";
|
||||||
import Fallback from "../../../Components/Fallback";
|
import PageStateWrapper from "../../../Components/PageStateWrapper";
|
||||||
import GenericFallback from "../../../Components/GenericFallback";
|
|
||||||
import FallbackPageSpeedWarning from "../../../Components/Fallback/FallbackPageSpeedWarning";
|
import FallbackPageSpeedWarning from "../../../Components/Fallback/FallbackPageSpeedWarning";
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
|
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useFetchMonitorsByTeamId } from "../../../Hooks/monitorHooks";
|
import { useFetchMonitorsByTeamId } from "../../../Hooks/monitorHooks";
|
||||||
import { useFetchSettings } from "../../../Hooks/settingsHooks";
|
import { useFetchSettings } from "../../../Hooks/settingsHooks";
|
||||||
// Constants
|
// Constants
|
||||||
@@ -20,7 +18,6 @@ const BREADCRUMBS = [{ name: `pagespeed`, path: "/pagespeed" }];
|
|||||||
const TYPES = ["pagespeed"];
|
const TYPES = ["pagespeed"];
|
||||||
const PageSpeed = () => {
|
const PageSpeed = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t } = useTranslation();
|
|
||||||
const isAdmin = useIsAdmin();
|
const isAdmin = useIsAdmin();
|
||||||
|
|
||||||
const [monitors, monitorsSummary, isLoading, networkError] = useFetchMonitorsByTeamId({
|
const [monitors, monitorsSummary, isLoading, networkError] = useFetchMonitorsByTeamId({
|
||||||
@@ -40,56 +37,42 @@ const PageSpeed = () => {
|
|||||||
setIsEmailPasswordSet: () => {},
|
setIsEmailPasswordSet: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (networkError === true) {
|
|
||||||
return (
|
|
||||||
<GenericFallback>
|
|
||||||
<Typography
|
|
||||||
variant="h1"
|
|
||||||
marginY={theme.spacing(4)}
|
|
||||||
color={theme.palette.primary.contrastTextTertiary}
|
|
||||||
>
|
|
||||||
{t("common.toasts.networkError")}
|
|
||||||
</Typography>
|
|
||||||
<Typography>{t("common.toasts.checkConnection")}</Typography>
|
|
||||||
</GenericFallback>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isLoading && monitors?.length === 0) {
|
|
||||||
return (
|
|
||||||
<Fallback
|
|
||||||
type="pageSpeed"
|
|
||||||
title={t("pageSpeed.fallback.title")}
|
|
||||||
checks={t("pageSpeed.fallback.checks", { returnObjects: true })}
|
|
||||||
link="/pagespeed/create"
|
|
||||||
isAdmin={isAdmin}
|
|
||||||
>
|
|
||||||
{isAdmin && settingsData && !settingsData.pagespeedApiKey && (
|
|
||||||
<FallbackPageSpeedWarning settingsData={settingsData} />
|
|
||||||
)}
|
|
||||||
</Fallback>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap={theme.spacing(10)}>
|
<>
|
||||||
<Breadcrumbs list={BREADCRUMBS} />
|
<PageStateWrapper
|
||||||
<CreateMonitorHeader
|
networkError={networkError}
|
||||||
isAdmin={isAdmin}
|
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
path="/pagespeed/create"
|
items={monitors}
|
||||||
/>
|
type="pageSpeed"
|
||||||
<MonitorCountHeader
|
fallbackLink="/pagespeed/create"
|
||||||
isLoading={isLoading}
|
fallbackChildren={
|
||||||
monitorCount={monitorsSummary?.totalMonitors}
|
isAdmin &&
|
||||||
sx={{ mb: theme.spacing(8) }}
|
settingsData &&
|
||||||
/>
|
!settingsData.pagespeedApiKey && (
|
||||||
<MonitorGrid
|
<FallbackPageSpeedWarning settingsData={settingsData} />
|
||||||
size={6}
|
)
|
||||||
shouldRender={!isLoading}
|
}
|
||||||
monitors={monitors}
|
>
|
||||||
/>
|
<Stack gap={theme.spacing(10)}>
|
||||||
</Stack>
|
<Breadcrumbs list={BREADCRUMBS} />
|
||||||
|
<CreateMonitorHeader
|
||||||
|
isAdmin={isAdmin}
|
||||||
|
isLoading={isLoading}
|
||||||
|
path="/pagespeed/create"
|
||||||
|
/>
|
||||||
|
<MonitorCountHeader
|
||||||
|
isLoading={isLoading}
|
||||||
|
monitorCount={monitorsSummary?.totalMonitors}
|
||||||
|
sx={{ mb: theme.spacing(8) }}
|
||||||
|
/>
|
||||||
|
<MonitorGrid
|
||||||
|
size={6}
|
||||||
|
shouldRender={!isLoading}
|
||||||
|
monitors={monitors}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</PageStateWrapper>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,12 @@ import PropTypes from "prop-types";
|
|||||||
|
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
|
|
||||||
const MonitorsList = ({ isLoading = false, shouldRender = true, monitors = [] }) => {
|
const MonitorsList = ({
|
||||||
|
isLoading = false,
|
||||||
|
shouldRender = true,
|
||||||
|
monitors = [],
|
||||||
|
statusPage = {},
|
||||||
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { determineState } = useMonitorUtils();
|
const { determineState } = useMonitorUtils();
|
||||||
|
|
||||||
@@ -40,10 +45,12 @@ const MonitorsList = ({ isLoading = false, shouldRender = true, monitors = [] })
|
|||||||
alignItems="center"
|
alignItems="center"
|
||||||
gap={theme.spacing(20)}
|
gap={theme.spacing(20)}
|
||||||
>
|
>
|
||||||
<Box flex={9}>
|
{statusPage.showCharts !== false && (
|
||||||
<StatusPageBarChart checks={monitor?.checks?.slice().reverse()} />
|
<Box flex={9}>
|
||||||
</Box>
|
<StatusPageBarChart checks={monitor?.checks?.slice().reverse()} />
|
||||||
<Box flex={1}>
|
</Box>
|
||||||
|
)}
|
||||||
|
<Box flex={statusPage.showCharts !== false ? 1 : 10}>
|
||||||
<StatusLabel
|
<StatusLabel
|
||||||
status={status}
|
status={status}
|
||||||
text={status}
|
text={status}
|
||||||
@@ -60,6 +67,7 @@ const MonitorsList = ({ isLoading = false, shouldRender = true, monitors = [] })
|
|||||||
|
|
||||||
MonitorsList.propTypes = {
|
MonitorsList.propTypes = {
|
||||||
monitors: PropTypes.array.isRequired,
|
monitors: PropTypes.array.isRequired,
|
||||||
|
statusPage: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MonitorsList;
|
export default MonitorsList;
|
||||||
@@ -154,7 +154,10 @@ const PublicStatus = () => {
|
|||||||
/>
|
/>
|
||||||
<Typography variant="h2">{t("statusPageStatusServiceStatus")}</Typography>
|
<Typography variant="h2">{t("statusPageStatusServiceStatus")}</Typography>
|
||||||
<StatusBar monitors={monitors} />
|
<StatusBar monitors={monitors} />
|
||||||
<MonitorsList monitors={monitors} />
|
<MonitorsList
|
||||||
|
monitors={monitors}
|
||||||
|
statusPage={statusPage}
|
||||||
|
/>
|
||||||
{link}
|
{link}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
// Components
|
// Components
|
||||||
import { Stack, Typography } from "@mui/material";
|
import { Stack } from "@mui/material";
|
||||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||||
import Fallback from "../../../Components/Fallback";
|
|
||||||
import MonitorCreateHeader from "../../../Components/MonitorCreateHeader";
|
import MonitorCreateHeader from "../../../Components/MonitorCreateHeader";
|
||||||
import GenericFallback from "../../../Components/GenericFallback";
|
|
||||||
import StatusPagesTable from "./Components/StatusPagesTable";
|
import StatusPagesTable from "./Components/StatusPagesTable";
|
||||||
import SkeletonLayout from "../../../Components/Skeletons/FullPage";
|
import PageStateWrapper from "../../../Components/PageStateWrapper";
|
||||||
// Utils
|
// Utils
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useStatusPagesFetch } from "./Hooks/useStatusPagesFetch";
|
import { useStatusPagesFetch } from "./Hooks/useStatusPagesFetch";
|
||||||
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
|
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
|
||||||
const BREADCRUMBS = [{ name: `Status Pages`, path: "" }];
|
const BREADCRUMBS = [{ name: `Status Pages`, path: "" }];
|
||||||
@@ -16,52 +13,30 @@ const BREADCRUMBS = [{ name: `Status Pages`, path: "" }];
|
|||||||
const StatusPages = () => {
|
const StatusPages = () => {
|
||||||
// Utils
|
// Utils
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t } = useTranslation();
|
|
||||||
const isAdmin = useIsAdmin();
|
const isAdmin = useIsAdmin();
|
||||||
const [isLoading, networkError, statusPages] = useStatusPagesFetch();
|
const [isLoading, networkError, statusPages] = useStatusPagesFetch();
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return <SkeletonLayout />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (networkError === true) {
|
|
||||||
return (
|
|
||||||
<GenericFallback>
|
|
||||||
<Typography
|
|
||||||
variant="h1"
|
|
||||||
marginY={theme.spacing(4)}
|
|
||||||
color={theme.palette.primary.contrastTextTertiary}
|
|
||||||
>
|
|
||||||
{t("common.toasts.networkError")}
|
|
||||||
</Typography>
|
|
||||||
<Typography>{t("common.toasts.checkConnection")}</Typography>
|
|
||||||
</GenericFallback>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isLoading && typeof statusPages !== "undefined" && statusPages.length === 0) {
|
|
||||||
return (
|
|
||||||
<Fallback
|
|
||||||
title={t("statusPage.fallback.title")}
|
|
||||||
checks={t("statusPage.fallback.checks", { returnObjects: true })}
|
|
||||||
type="statusPage"
|
|
||||||
link="/status/uptime/create"
|
|
||||||
isAdmin={isAdmin}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap={theme.spacing(10)}>
|
<>
|
||||||
<Breadcrumbs list={BREADCRUMBS} />
|
<PageStateWrapper
|
||||||
<MonitorCreateHeader
|
networkError={networkError}
|
||||||
label="Create status page"
|
|
||||||
isAdmin={isAdmin}
|
|
||||||
path="/status/uptime/create"
|
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
/>
|
items={statusPages}
|
||||||
<StatusPagesTable data={statusPages} />
|
type="statusPage"
|
||||||
</Stack>
|
fallbackLink="/status/uptime/create"
|
||||||
|
>
|
||||||
|
<Stack gap={theme.spacing(10)}>
|
||||||
|
<Breadcrumbs list={BREADCRUMBS} />
|
||||||
|
<MonitorCreateHeader
|
||||||
|
label="Create status page"
|
||||||
|
isAdmin={isAdmin}
|
||||||
|
path="/status/uptime/create"
|
||||||
|
isLoading={isLoading}
|
||||||
|
/>
|
||||||
|
<StatusPagesTable data={statusPages} />
|
||||||
|
</Stack>
|
||||||
|
</PageStateWrapper>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,16 +10,14 @@ import StatusBoxes from "./Components/StatusBoxes";
|
|||||||
import UptimeDataTable from "./Components/UptimeDataTable";
|
import UptimeDataTable from "./Components/UptimeDataTable";
|
||||||
import Pagination from "../../../Components/Table/TablePagination";
|
import Pagination from "../../../Components/Table/TablePagination";
|
||||||
import CreateMonitorHeader from "../../../Components/MonitorCreateHeader";
|
import CreateMonitorHeader from "../../../Components/MonitorCreateHeader";
|
||||||
import Fallback from "../../../Components/Fallback";
|
|
||||||
import GenericFallback from "../../../Components/GenericFallback";
|
|
||||||
import SearchComponent from "./Components/SearchComponent";
|
import SearchComponent from "./Components/SearchComponent";
|
||||||
import Filter from "./Components/Filter";
|
import Filter from "./Components/Filter";
|
||||||
|
import PageStateWrapper from "../../../Components/PageStateWrapper";
|
||||||
|
|
||||||
import MonitorCountHeader from "../../../Components/MonitorCountHeader";
|
import MonitorCountHeader from "../../../Components/MonitorCountHeader";
|
||||||
|
|
||||||
// MUI Components
|
// MUI Components
|
||||||
import { Stack, Box, Button, Typography } from "@mui/material";
|
import { Stack, Box, Button } from "@mui/material";
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
import { useState, useCallback, useEffect } from "react";
|
import { useState, useCallback, useEffect } from "react";
|
||||||
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
|
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
|
||||||
@@ -153,93 +151,75 @@ const UptimeMonitors = () => {
|
|||||||
}, [isSearching]);
|
}, [isSearching]);
|
||||||
|
|
||||||
const isLoading = monitorsWithSummaryIsLoading || monitorsWithChecksIsLoading;
|
const isLoading = monitorsWithSummaryIsLoading || monitorsWithChecksIsLoading;
|
||||||
if (networkError) {
|
|
||||||
return (
|
|
||||||
<GenericFallback>
|
|
||||||
<Typography
|
|
||||||
variant="h1"
|
|
||||||
marginY={theme.spacing(4)}
|
|
||||||
color={theme.palette.primary.contrastTextTertiary}
|
|
||||||
>
|
|
||||||
{t("common.toasts.networkError")}
|
|
||||||
</Typography>
|
|
||||||
<Typography>{t("common.toasts.checkConnection")}</Typography>
|
|
||||||
</GenericFallback>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
!isLoading &&
|
|
||||||
(monitorsSummary?.totalMonitors === 0 ||
|
|
||||||
typeof monitorsSummary?.totalMonitors === "undefined")
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<Fallback
|
|
||||||
type="uptimeMonitor"
|
|
||||||
title={t("uptimeMonitor.fallback.title")}
|
|
||||||
checks={t("uptimeMonitor.fallback.checks", { returnObjects: true })}
|
|
||||||
link="/uptime/create"
|
|
||||||
isAdmin={isAdmin}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Stack
|
|
||||||
className="monitors"
|
|
||||||
gap={theme.spacing(10)}
|
|
||||||
>
|
|
||||||
<Breadcrumbs list={BREADCRUMBS} />
|
|
||||||
<CreateMonitorHeader
|
|
||||||
isAdmin={isAdmin}
|
|
||||||
isLoading={isLoading}
|
|
||||||
path="/uptime/create"
|
|
||||||
bulkPath="/uptime/bulk-import"
|
|
||||||
/>
|
|
||||||
<Greeting type="uptime" />
|
|
||||||
<StatusBoxes
|
|
||||||
monitorsSummary={monitorsSummary}
|
|
||||||
shouldRender={!monitorsWithSummaryIsLoading}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Stack direction={"row"}>
|
return (
|
||||||
<MonitorCountHeader
|
<>
|
||||||
isLoading={monitorsWithSummaryIsLoading}
|
<PageStateWrapper
|
||||||
monitorCount={monitorsSummary?.totalMonitors}
|
networkError={networkError}
|
||||||
/>
|
isLoading={isLoading}
|
||||||
<Filter
|
items={monitors}
|
||||||
selectedTypes={selectedTypes}
|
type="uptimeMonitor"
|
||||||
setSelectedTypes={setSelectedTypes}
|
fallbackLink="/uptime/create"
|
||||||
selectedStatus={selectedStatus}
|
>
|
||||||
setSelectedStatus={setSelectedStatus}
|
<Stack
|
||||||
selectedState={selectedState}
|
className="monitors"
|
||||||
setSelectedState={setSelectedState}
|
gap={theme.spacing(10)}
|
||||||
setToFilterStatus={setToFilterStatus}
|
>
|
||||||
setToFilterActive={setToFilterActive}
|
<Breadcrumbs list={BREADCRUMBS} />
|
||||||
handleReset={handleReset}
|
<CreateMonitorHeader
|
||||||
/>
|
isAdmin={isAdmin}
|
||||||
<SearchComponent
|
isLoading={isLoading}
|
||||||
monitors={monitors}
|
path="/uptime/create"
|
||||||
onSearchChange={setSearch}
|
bulkPath="/uptime/bulk-import"
|
||||||
setIsSearching={setIsSearching}
|
/>
|
||||||
/>
|
<Greeting type="uptime" />
|
||||||
</Stack>
|
<StatusBoxes
|
||||||
<UptimeDataTable
|
monitorsSummary={monitorsSummary}
|
||||||
isAdmin={isAdmin}
|
shouldRender={!monitorsWithSummaryIsLoading}
|
||||||
isSearching={isSearching}
|
/>
|
||||||
filteredMonitors={monitorsWithChecks}
|
|
||||||
sort={sort}
|
<Stack direction={"row"}>
|
||||||
setSort={setSort}
|
<MonitorCountHeader
|
||||||
monitorsAreLoading={monitorsWithChecksIsLoading}
|
isLoading={monitorsWithSummaryIsLoading}
|
||||||
triggerUpdate={triggerUpdate}
|
monitorCount={monitorsSummary?.totalMonitors}
|
||||||
/>
|
/>
|
||||||
<Pagination
|
<Filter
|
||||||
itemCount={monitorsWithChecksCount}
|
selectedTypes={selectedTypes}
|
||||||
paginationLabel="monitors"
|
setSelectedTypes={setSelectedTypes}
|
||||||
page={page}
|
selectedStatus={selectedStatus}
|
||||||
rowsPerPage={rowsPerPage}
|
setSelectedStatus={setSelectedStatus}
|
||||||
handleChangePage={handleChangePage}
|
selectedState={selectedState}
|
||||||
handleChangeRowsPerPage={handleChangeRowsPerPage}
|
setSelectedState={setSelectedState}
|
||||||
/>
|
setToFilterStatus={setToFilterStatus}
|
||||||
</Stack>
|
setToFilterActive={setToFilterActive}
|
||||||
|
handleReset={handleReset}
|
||||||
|
/>
|
||||||
|
<SearchComponent
|
||||||
|
monitors={monitors}
|
||||||
|
onSearchChange={setSearch}
|
||||||
|
setIsSearching={setIsSearching}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<UptimeDataTable
|
||||||
|
isAdmin={isAdmin}
|
||||||
|
isSearching={isSearching}
|
||||||
|
filteredMonitors={monitorsWithChecks}
|
||||||
|
sort={sort}
|
||||||
|
setSort={setSort}
|
||||||
|
monitorsAreLoading={monitorsWithChecksIsLoading}
|
||||||
|
triggerUpdate={triggerUpdate}
|
||||||
|
/>
|
||||||
|
<Pagination
|
||||||
|
itemCount={monitorsWithChecksCount}
|
||||||
|
paginationLabel="monitors"
|
||||||
|
page={page}
|
||||||
|
rowsPerPage={rowsPerPage}
|
||||||
|
handleChangePage={handleChangePage}
|
||||||
|
handleChangeRowsPerPage={handleChangeRowsPerPage}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</PageStateWrapper>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ const Routes = () => {
|
|||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
path="/register/:token"
|
path="/register/:token"
|
||||||
element={<AuthRegister />}
|
element={<AuthRegister superAdminExists={true} />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
|
|||||||
@@ -437,6 +437,16 @@ const infrastructureMonitorValidation = joi.object({
|
|||||||
"number.base": "Frequency must be a number.",
|
"number.base": "Frequency must be a number.",
|
||||||
"any.required": "Frequency is required.",
|
"any.required": "Frequency is required.",
|
||||||
}),
|
}),
|
||||||
|
statusWindowSize: joi.number().min(1).max(20).messages({
|
||||||
|
"number.base": "Status window size must be a number.",
|
||||||
|
"number.min": "Status window size must be at least 1.",
|
||||||
|
"number.max": "Status window size cannot exceed 20.",
|
||||||
|
}),
|
||||||
|
statusWindowThreshold: joi.number().min(1).max(100).messages({
|
||||||
|
"number.base": "Status window threshold must be a number.",
|
||||||
|
"number.min": "Status window threshold must be at least 1%.",
|
||||||
|
"number.max": "Status window threshold cannot exceed 100%.",
|
||||||
|
}),
|
||||||
notifications: joi.array().items(joi.string()),
|
notifications: joi.array().items(joi.string()),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -376,7 +376,52 @@
|
|||||||
"email": "",
|
"email": "",
|
||||||
"role": "",
|
"role": "",
|
||||||
"created": ""
|
"created": ""
|
||||||
}
|
},
|
||||||
|
"addTeamMember": {
|
||||||
|
"addMemberMenu": "",
|
||||||
|
"title": "",
|
||||||
|
"description": "",
|
||||||
|
"addButton": ""
|
||||||
|
},
|
||||||
|
"register": "",
|
||||||
|
"registerToast": {
|
||||||
|
"success": "",
|
||||||
|
"dbUserExists": "",
|
||||||
|
"unknownError": ""
|
||||||
|
},
|
||||||
|
"registerTeamMember": {
|
||||||
|
"title": "",
|
||||||
|
"auth": {
|
||||||
|
"common": {
|
||||||
|
"inputs": {
|
||||||
|
"firstName": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lastName": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"invalid": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"errors": {
|
||||||
|
"empty": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"role": ""
|
||||||
},
|
},
|
||||||
"monitorState": {
|
"monitorState": {
|
||||||
"paused": "",
|
"paused": "",
|
||||||
@@ -742,7 +787,8 @@
|
|||||||
"failCountHeader": "",
|
"failCountHeader": "",
|
||||||
"lastRunHeader": "",
|
"lastRunHeader": "",
|
||||||
"lastFinishedAtHeader": "",
|
"lastFinishedAtHeader": "",
|
||||||
"lastRunTookHeader": ""
|
"lastRunTookHeader": "",
|
||||||
|
"intervalHeader": ""
|
||||||
},
|
},
|
||||||
"metricsTable": {
|
"metricsTable": {
|
||||||
"title": "",
|
"title": "",
|
||||||
|
|||||||
@@ -376,7 +376,52 @@
|
|||||||
"email": "",
|
"email": "",
|
||||||
"role": "",
|
"role": "",
|
||||||
"created": ""
|
"created": ""
|
||||||
}
|
},
|
||||||
|
"addTeamMember": {
|
||||||
|
"addMemberMenu": "",
|
||||||
|
"title": "",
|
||||||
|
"description": "",
|
||||||
|
"addButton": ""
|
||||||
|
},
|
||||||
|
"register": "",
|
||||||
|
"registerToast": {
|
||||||
|
"success": "",
|
||||||
|
"dbUserExists": "",
|
||||||
|
"unknownError": ""
|
||||||
|
},
|
||||||
|
"registerTeamMember": {
|
||||||
|
"title": "",
|
||||||
|
"auth": {
|
||||||
|
"common": {
|
||||||
|
"inputs": {
|
||||||
|
"firstName": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lastName": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"invalid": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"errors": {
|
||||||
|
"empty": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"role": ""
|
||||||
},
|
},
|
||||||
"monitorState": {
|
"monitorState": {
|
||||||
"paused": "",
|
"paused": "",
|
||||||
@@ -742,7 +787,8 @@
|
|||||||
"failCountHeader": "",
|
"failCountHeader": "",
|
||||||
"lastRunHeader": "",
|
"lastRunHeader": "",
|
||||||
"lastFinishedAtHeader": "",
|
"lastFinishedAtHeader": "",
|
||||||
"lastRunTookHeader": ""
|
"lastRunTookHeader": "",
|
||||||
|
"intervalHeader": ""
|
||||||
},
|
},
|
||||||
"metricsTable": {
|
"metricsTable": {
|
||||||
"title": "",
|
"title": "",
|
||||||
|
|||||||
@@ -380,7 +380,52 @@
|
|||||||
"email": "Email",
|
"email": "Email",
|
||||||
"role": "Rolle",
|
"role": "Rolle",
|
||||||
"created": "Angelegt"
|
"created": "Angelegt"
|
||||||
}
|
},
|
||||||
|
"addTeamMember": {
|
||||||
|
"addMemberMenu": "",
|
||||||
|
"title": "",
|
||||||
|
"description": "",
|
||||||
|
"addButton": ""
|
||||||
|
},
|
||||||
|
"register": "",
|
||||||
|
"registerToast": {
|
||||||
|
"success": "",
|
||||||
|
"dbUserExists": "",
|
||||||
|
"unknownError": ""
|
||||||
|
},
|
||||||
|
"registerTeamMember": {
|
||||||
|
"title": "",
|
||||||
|
"auth": {
|
||||||
|
"common": {
|
||||||
|
"inputs": {
|
||||||
|
"firstName": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lastName": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"invalid": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"errors": {
|
||||||
|
"empty": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"role": ""
|
||||||
},
|
},
|
||||||
"monitorState": {
|
"monitorState": {
|
||||||
"paused": "Pausiert",
|
"paused": "Pausiert",
|
||||||
@@ -746,7 +791,8 @@
|
|||||||
"failCountHeader": "Anzahl Fehler",
|
"failCountHeader": "Anzahl Fehler",
|
||||||
"lastRunHeader": "Letzter Lauf um",
|
"lastRunHeader": "Letzter Lauf um",
|
||||||
"lastFinishedAtHeader": "Zuletzt abgeschlossen um",
|
"lastFinishedAtHeader": "Zuletzt abgeschlossen um",
|
||||||
"lastRunTookHeader": "Letzter Lauf dauerte"
|
"lastRunTookHeader": "Letzter Lauf dauerte",
|
||||||
|
"intervalHeader": ""
|
||||||
},
|
},
|
||||||
"metricsTable": {
|
"metricsTable": {
|
||||||
"title": "Warteschlangenmetriken",
|
"title": "Warteschlangenmetriken",
|
||||||
|
|||||||
@@ -380,7 +380,52 @@
|
|||||||
"email": "Email",
|
"email": "Email",
|
||||||
"role": "Role",
|
"role": "Role",
|
||||||
"created": "Created"
|
"created": "Created"
|
||||||
}
|
},
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"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"
|
||||||
},
|
},
|
||||||
"monitorState": {
|
"monitorState": {
|
||||||
"paused": "Paused",
|
"paused": "Paused",
|
||||||
@@ -740,14 +785,14 @@
|
|||||||
"idHeader": "Monitor ID",
|
"idHeader": "Monitor ID",
|
||||||
"urlHeader": "URL",
|
"urlHeader": "URL",
|
||||||
"typeHeader": "Type",
|
"typeHeader": "Type",
|
||||||
"intervalHeader": "Interval",
|
|
||||||
"activeHeader": "Active",
|
"activeHeader": "Active",
|
||||||
"lockedAtHeader": "Locked at",
|
"lockedAtHeader": "Locked at",
|
||||||
"runCountHeader": "Run count",
|
"runCountHeader": "Run count",
|
||||||
"failCountHeader": "Fail count",
|
"failCountHeader": "Fail count",
|
||||||
"lastRunHeader": "Last run at",
|
"lastRunHeader": "Last run at",
|
||||||
"lastFinishedAtHeader": "Last finished at",
|
"lastFinishedAtHeader": "Last finished at",
|
||||||
"lastRunTookHeader": "Last run took"
|
"lastRunTookHeader": "Last run took",
|
||||||
|
"intervalHeader": "Interval"
|
||||||
},
|
},
|
||||||
"metricsTable": {
|
"metricsTable": {
|
||||||
"title": "Queue metrics",
|
"title": "Queue metrics",
|
||||||
|
|||||||
@@ -376,7 +376,52 @@
|
|||||||
"email": "",
|
"email": "",
|
||||||
"role": "",
|
"role": "",
|
||||||
"created": ""
|
"created": ""
|
||||||
}
|
},
|
||||||
|
"addTeamMember": {
|
||||||
|
"addMemberMenu": "",
|
||||||
|
"title": "",
|
||||||
|
"description": "",
|
||||||
|
"addButton": ""
|
||||||
|
},
|
||||||
|
"register": "",
|
||||||
|
"registerToast": {
|
||||||
|
"success": "",
|
||||||
|
"dbUserExists": "",
|
||||||
|
"unknownError": ""
|
||||||
|
},
|
||||||
|
"registerTeamMember": {
|
||||||
|
"title": "",
|
||||||
|
"auth": {
|
||||||
|
"common": {
|
||||||
|
"inputs": {
|
||||||
|
"firstName": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lastName": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"invalid": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"errors": {
|
||||||
|
"empty": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"role": ""
|
||||||
},
|
},
|
||||||
"monitorState": {
|
"monitorState": {
|
||||||
"paused": "",
|
"paused": "",
|
||||||
@@ -742,7 +787,8 @@
|
|||||||
"failCountHeader": "",
|
"failCountHeader": "",
|
||||||
"lastRunHeader": "",
|
"lastRunHeader": "",
|
||||||
"lastFinishedAtHeader": "",
|
"lastFinishedAtHeader": "",
|
||||||
"lastRunTookHeader": ""
|
"lastRunTookHeader": "",
|
||||||
|
"intervalHeader": ""
|
||||||
},
|
},
|
||||||
"metricsTable": {
|
"metricsTable": {
|
||||||
"title": "",
|
"title": "",
|
||||||
|
|||||||
@@ -376,7 +376,52 @@
|
|||||||
"email": "Sähköposti",
|
"email": "Sähköposti",
|
||||||
"role": "Rooli",
|
"role": "Rooli",
|
||||||
"created": "Luotu"
|
"created": "Luotu"
|
||||||
}
|
},
|
||||||
|
"addTeamMember": {
|
||||||
|
"addMemberMenu": "",
|
||||||
|
"title": "",
|
||||||
|
"description": "",
|
||||||
|
"addButton": ""
|
||||||
|
},
|
||||||
|
"register": "",
|
||||||
|
"registerToast": {
|
||||||
|
"success": "",
|
||||||
|
"dbUserExists": "",
|
||||||
|
"unknownError": ""
|
||||||
|
},
|
||||||
|
"registerTeamMember": {
|
||||||
|
"title": "",
|
||||||
|
"auth": {
|
||||||
|
"common": {
|
||||||
|
"inputs": {
|
||||||
|
"firstName": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lastName": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"invalid": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"errors": {
|
||||||
|
"empty": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"role": ""
|
||||||
},
|
},
|
||||||
"monitorState": {
|
"monitorState": {
|
||||||
"paused": "Tauota",
|
"paused": "Tauota",
|
||||||
@@ -742,7 +787,8 @@
|
|||||||
"failCountHeader": "",
|
"failCountHeader": "",
|
||||||
"lastRunHeader": "",
|
"lastRunHeader": "",
|
||||||
"lastFinishedAtHeader": "",
|
"lastFinishedAtHeader": "",
|
||||||
"lastRunTookHeader": ""
|
"lastRunTookHeader": "",
|
||||||
|
"intervalHeader": ""
|
||||||
},
|
},
|
||||||
"metricsTable": {
|
"metricsTable": {
|
||||||
"title": "",
|
"title": "",
|
||||||
|
|||||||
@@ -380,7 +380,52 @@
|
|||||||
"email": "Email",
|
"email": "Email",
|
||||||
"role": "Rôle",
|
"role": "Rôle",
|
||||||
"created": "Créée"
|
"created": "Créée"
|
||||||
}
|
},
|
||||||
|
"addTeamMember": {
|
||||||
|
"addMemberMenu": "",
|
||||||
|
"title": "",
|
||||||
|
"description": "",
|
||||||
|
"addButton": ""
|
||||||
|
},
|
||||||
|
"register": "",
|
||||||
|
"registerToast": {
|
||||||
|
"success": "",
|
||||||
|
"dbUserExists": "",
|
||||||
|
"unknownError": ""
|
||||||
|
},
|
||||||
|
"registerTeamMember": {
|
||||||
|
"title": "",
|
||||||
|
"auth": {
|
||||||
|
"common": {
|
||||||
|
"inputs": {
|
||||||
|
"firstName": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lastName": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"invalid": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"errors": {
|
||||||
|
"empty": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"role": ""
|
||||||
},
|
},
|
||||||
"monitorState": {
|
"monitorState": {
|
||||||
"paused": "Pause",
|
"paused": "Pause",
|
||||||
@@ -746,7 +791,8 @@
|
|||||||
"failCountHeader": "Nombre d'échecs",
|
"failCountHeader": "Nombre d'échecs",
|
||||||
"lastRunHeader": "Dernière tentative le",
|
"lastRunHeader": "Dernière tentative le",
|
||||||
"lastFinishedAtHeader": "Dernière finalisation à",
|
"lastFinishedAtHeader": "Dernière finalisation à",
|
||||||
"lastRunTookHeader": "Dernière exécution effectuée"
|
"lastRunTookHeader": "Dernière exécution effectuée",
|
||||||
|
"intervalHeader": ""
|
||||||
},
|
},
|
||||||
"metricsTable": {
|
"metricsTable": {
|
||||||
"title": "Métriques des files",
|
"title": "Métriques des files",
|
||||||
|
|||||||
@@ -380,7 +380,52 @@
|
|||||||
"email": "メール",
|
"email": "メール",
|
||||||
"role": "役割",
|
"role": "役割",
|
||||||
"created": "作成日"
|
"created": "作成日"
|
||||||
}
|
},
|
||||||
|
"addTeamMember": {
|
||||||
|
"addMemberMenu": "",
|
||||||
|
"title": "",
|
||||||
|
"description": "",
|
||||||
|
"addButton": ""
|
||||||
|
},
|
||||||
|
"register": "",
|
||||||
|
"registerToast": {
|
||||||
|
"success": "",
|
||||||
|
"dbUserExists": "",
|
||||||
|
"unknownError": ""
|
||||||
|
},
|
||||||
|
"registerTeamMember": {
|
||||||
|
"title": "",
|
||||||
|
"auth": {
|
||||||
|
"common": {
|
||||||
|
"inputs": {
|
||||||
|
"firstName": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lastName": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"invalid": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"errors": {
|
||||||
|
"empty": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"role": ""
|
||||||
},
|
},
|
||||||
"monitorState": {
|
"monitorState": {
|
||||||
"paused": "一時停止",
|
"paused": "一時停止",
|
||||||
@@ -746,7 +791,8 @@
|
|||||||
"failCountHeader": "失敗回数",
|
"failCountHeader": "失敗回数",
|
||||||
"lastRunHeader": "最終実行日時",
|
"lastRunHeader": "最終実行日時",
|
||||||
"lastFinishedAtHeader": "最終完了日時",
|
"lastFinishedAtHeader": "最終完了日時",
|
||||||
"lastRunTookHeader": "最終実行時間"
|
"lastRunTookHeader": "最終実行時間",
|
||||||
|
"intervalHeader": ""
|
||||||
},
|
},
|
||||||
"metricsTable": {
|
"metricsTable": {
|
||||||
"title": "キューメトリック",
|
"title": "キューメトリック",
|
||||||
|
|||||||
@@ -380,7 +380,52 @@
|
|||||||
"email": "E-mail",
|
"email": "E-mail",
|
||||||
"role": "Cargo",
|
"role": "Cargo",
|
||||||
"created": "Criado"
|
"created": "Criado"
|
||||||
}
|
},
|
||||||
|
"addTeamMember": {
|
||||||
|
"addMemberMenu": "",
|
||||||
|
"title": "",
|
||||||
|
"description": "",
|
||||||
|
"addButton": ""
|
||||||
|
},
|
||||||
|
"register": "",
|
||||||
|
"registerToast": {
|
||||||
|
"success": "",
|
||||||
|
"dbUserExists": "",
|
||||||
|
"unknownError": ""
|
||||||
|
},
|
||||||
|
"registerTeamMember": {
|
||||||
|
"title": "",
|
||||||
|
"auth": {
|
||||||
|
"common": {
|
||||||
|
"inputs": {
|
||||||
|
"firstName": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lastName": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"invalid": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"errors": {
|
||||||
|
"empty": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"role": ""
|
||||||
},
|
},
|
||||||
"monitorState": {
|
"monitorState": {
|
||||||
"paused": "Pausado",
|
"paused": "Pausado",
|
||||||
@@ -746,7 +791,8 @@
|
|||||||
"failCountHeader": "Contagem de falhas",
|
"failCountHeader": "Contagem de falhas",
|
||||||
"lastRunHeader": "Última corrida em",
|
"lastRunHeader": "Última corrida em",
|
||||||
"lastFinishedAtHeader": "Última conclusão em",
|
"lastFinishedAtHeader": "Última conclusão em",
|
||||||
"lastRunTookHeader": "A última corrida aconteceu"
|
"lastRunTookHeader": "A última corrida aconteceu",
|
||||||
|
"intervalHeader": ""
|
||||||
},
|
},
|
||||||
"metricsTable": {
|
"metricsTable": {
|
||||||
"title": "Métricas de fila",
|
"title": "Métricas de fila",
|
||||||
|
|||||||
+111
-65
@@ -376,7 +376,52 @@
|
|||||||
"email": "Email",
|
"email": "Email",
|
||||||
"role": "Роль",
|
"role": "Роль",
|
||||||
"created": "Создано"
|
"created": "Создано"
|
||||||
}
|
},
|
||||||
|
"addTeamMember": {
|
||||||
|
"addMemberMenu": "",
|
||||||
|
"title": "",
|
||||||
|
"description": "",
|
||||||
|
"addButton": ""
|
||||||
|
},
|
||||||
|
"register": "",
|
||||||
|
"registerToast": {
|
||||||
|
"success": "",
|
||||||
|
"dbUserExists": "",
|
||||||
|
"unknownError": ""
|
||||||
|
},
|
||||||
|
"registerTeamMember": {
|
||||||
|
"title": "",
|
||||||
|
"auth": {
|
||||||
|
"common": {
|
||||||
|
"inputs": {
|
||||||
|
"firstName": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lastName": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"invalid": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"errors": {
|
||||||
|
"empty": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"role": ""
|
||||||
},
|
},
|
||||||
"monitorState": {
|
"monitorState": {
|
||||||
"paused": "Остановлено",
|
"paused": "Остановлено",
|
||||||
@@ -421,23 +466,23 @@
|
|||||||
"passwordRequirements": "Новый пароль должен содержать не менее 8 символов и содержать как минимум одну заглавную букву, одну строчную букву, одну цифру и один специальный символ.",
|
"passwordRequirements": "Новый пароль должен содержать не менее 8 символов и содержать как минимум одну заглавную букву, одну строчную букву, одну цифру и один специальный символ.",
|
||||||
"saving": "Сохранение..."
|
"saving": "Сохранение..."
|
||||||
},
|
},
|
||||||
"emailSent": "",
|
"emailSent": "Письмо успешно отправлено",
|
||||||
"failedToSendEmail": "",
|
"failedToSendEmail": "Не удалось отправить письмо",
|
||||||
"settingsTestEmailSuccess": "",
|
"settingsTestEmailSuccess": "Тестовое письмо успешно отправлено",
|
||||||
"settingsTestEmailFailed": "",
|
"settingsTestEmailFailed": "Не удалось отправить тестовое письмо",
|
||||||
"settingsTestEmailFailedWithReason": "",
|
"settingsTestEmailFailedWithReason": "Не удалось отправить тестовое письмо: {{reason}}",
|
||||||
"settingsTestEmailUnknownError": "Неизвестная ошибка",
|
"settingsTestEmailUnknownError": "Неизвестная ошибка",
|
||||||
"statusMsg": {
|
"statusMsg": {
|
||||||
"paused": "",
|
"paused": "Мониторинг приостановлен",
|
||||||
"up": "",
|
"up": "",
|
||||||
"down": "",
|
"down": "",
|
||||||
"pending": ""
|
"pending": "Ожидание..."
|
||||||
},
|
},
|
||||||
"uptimeGeneralInstructions": {
|
"uptimeGeneralInstructions": {
|
||||||
"http": "",
|
"http": "Введите URL-адрес или IP-адрес для мониторинга (например, https://example.com/ или 192.168.1.100) и добавьте понятное имя, которое будет отображаться на панели инструментов.",
|
||||||
"ping": "",
|
"ping": "Ведите IP-адрес или имя хоста для пинга (например, 192.168.1.100 или example.com) и добавьте понятное имя, которое будет отображаться на панели инструментов.",
|
||||||
"docker": "",
|
"docker": "Введите Docker ID вашего контейнера. Docker ID должен быть полным 64-символьным Docker ID. Вы можете запустить docker inspect <short_id>, чтобы получить полный ID контейнера.",
|
||||||
"port": "",
|
"port": "Введите URL-адрес или IP-адрес сервера, номер порта и понятное имя, которое будет отображаться на панели инструментов.",
|
||||||
"game": ""
|
"game": ""
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
@@ -447,24 +492,24 @@
|
|||||||
"toggleTheme": ""
|
"toggleTheme": ""
|
||||||
},
|
},
|
||||||
"toasts": {
|
"toasts": {
|
||||||
"networkError": "",
|
"networkError": "Ошибка сети",
|
||||||
"checkConnection": "",
|
"checkConnection": "Пожалуйста, проверьте свое сетевое соединение",
|
||||||
"unknownError": "Неизвестная ошибка"
|
"unknownError": "Неизвестная ошибка"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"common": {
|
"common": {
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"continue": "",
|
"continue": "Продолжить",
|
||||||
"back": "Назад"
|
"back": "Назад"
|
||||||
},
|
},
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"email": {
|
"email": {
|
||||||
"label": "",
|
"label": "Электронная почта",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"errors": {
|
"errors": {
|
||||||
"empty": "Для продолжения введите свой адрес электронной почты",
|
"empty": "Для продолжения введите свой адрес электронной почты",
|
||||||
"invalid": ""
|
"invalid": "Пожалуйста, проверьте правильность введённого адреса электронной почты"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
@@ -491,49 +536,49 @@
|
|||||||
"highlighted": "одну прописную букву"
|
"highlighted": "одну прописную букву"
|
||||||
},
|
},
|
||||||
"match": {
|
"match": {
|
||||||
"beginning": "",
|
"beginning": "Пароли",
|
||||||
"highlighted": ""
|
"highlighted": "должны совпадать"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"empty": "",
|
"empty": "Пожалуйста, введите пароль",
|
||||||
"length": "",
|
"length": "Пароль должен быть длиной не менее 8 символов",
|
||||||
"uppercase": "",
|
"uppercase": "Пароль должен содержать минимум 1 прописную букву",
|
||||||
"lowercase": "",
|
"lowercase": "Пароль должен содержать минимум 1 строчную букву",
|
||||||
"number": "",
|
"number": "Пароль должен содержать минимум 1 цифру",
|
||||||
"special": "",
|
"special": "Пароль должен содержать минимум 1 спецсимвол",
|
||||||
"incorrect": ""
|
"incorrect": "Предоставленный вами пароль не соответствует нашим записям"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"passwordConfirm": {
|
"passwordConfirm": {
|
||||||
"label": "",
|
"label": "Подтвердите пароль",
|
||||||
"placeholder": "",
|
"placeholder": "Введите пароль еще раз для подтверждения",
|
||||||
"errors": {
|
"errors": {
|
||||||
"empty": "",
|
"empty": "Пожалуйста, введите свой пароль еще раз для подтверждения (помогает избежать опечаток)",
|
||||||
"different": ""
|
"different": "Введённые пароли не совпадают, вероятно, один из них напечатан с ошибкой"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"firstName": {
|
"firstName": {
|
||||||
"label": "",
|
"label": "Имя",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"errors": {
|
"errors": {
|
||||||
"empty": "",
|
"empty": "Пожалуйста, введите свое имя",
|
||||||
"length": "",
|
"length": "Имя должно быть короче 50 символов",
|
||||||
"pattern": ""
|
"pattern": "Имя должно содержать только буквы, пробелы, апострофы или дефисы"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lastName": {
|
"lastName": {
|
||||||
"label": "",
|
"label": "Фамилия",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"errors": {
|
"errors": {
|
||||||
"empty": "",
|
"empty": "Пожалуйста, введите свою фамилию",
|
||||||
"length": "",
|
"length": "Фамилия должна быть короче 50 символов",
|
||||||
"pattern": ""
|
"pattern": "Фамилия должна содержать только буквы, пробелы, апострофы или дефисы"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"validation": ""
|
"validation": "Ошибка при проверке данных."
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"password": {
|
"password": {
|
||||||
@@ -549,20 +594,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"heading": "",
|
"heading": "Войдите, чтобы продолжить",
|
||||||
"subheadings": {
|
"subheadings": {
|
||||||
"stepOne": "",
|
"stepOne": "Введите свою электронную почту",
|
||||||
"stepTwo": ""
|
"stepTwo": "Введите свой пароль"
|
||||||
},
|
},
|
||||||
"links": {
|
"links": {
|
||||||
"forgotPassword": "",
|
"forgotPassword": "Забыли пароль?",
|
||||||
"register": "",
|
"register": "У вас нет учетной записи?",
|
||||||
"forgotPasswordLink": "",
|
"forgotPasswordLink": "",
|
||||||
"registerLink": ""
|
"registerLink": ""
|
||||||
},
|
},
|
||||||
"toasts": {
|
"toasts": {
|
||||||
"success": "",
|
"success": "С возвращением! Вы успешно вошли в систему.",
|
||||||
"incorrectPassword": ""
|
"incorrectPassword": "Неверный пароль"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"password": {
|
"password": {
|
||||||
@@ -573,42 +618,42 @@
|
|||||||
},
|
},
|
||||||
"registration": {
|
"registration": {
|
||||||
"heading": {
|
"heading": {
|
||||||
"superAdmin": "",
|
"superAdmin": "Создать супер-администратора",
|
||||||
"user": ""
|
"user": "Зарегистрироваться"
|
||||||
},
|
},
|
||||||
"subheadings": {
|
"subheadings": {
|
||||||
"stepOne": "",
|
"stepOne": "Введите свои личные данные",
|
||||||
"stepTwo": "",
|
"stepTwo": "Введите вашу электронную почту",
|
||||||
"stepThree": ""
|
"stepThree": "Создайте свой пароль"
|
||||||
},
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"superAdmin": "",
|
"superAdmin": "Создайте свой аккаунт супер-администратора, чтобы начать",
|
||||||
"user": ""
|
"user": "Зарегистрируйтесь как пользователь и обратитесь к супер-администратору для доступа к вашим мониторам"
|
||||||
},
|
},
|
||||||
"gettingStartedButton": {
|
"gettingStartedButton": {
|
||||||
"superAdmin": "",
|
"superAdmin": "Создать учетную запись супер-администратора",
|
||||||
"user": ""
|
"user": "Зарегистрироваться как обычный пользователь"
|
||||||
},
|
},
|
||||||
"termsAndPolicies": "",
|
"termsAndPolicies": "Создавая учетную запись, вы соглашаетесь с нашими <a1>Условиями предоставления услуг</a1> и <a2>Политикой конфиденциальности</a2>.",
|
||||||
"links": {
|
"links": {
|
||||||
"login": ""
|
"login": "Уже есть учетная запись? <a>Войти</a>"
|
||||||
},
|
},
|
||||||
"toasts": {
|
"toasts": {
|
||||||
"success": ""
|
"success": "Добро пожаловать! Ваша учетная запись была создана успешно."
|
||||||
},
|
},
|
||||||
"welcome": ""
|
"welcome": ""
|
||||||
},
|
},
|
||||||
"forgotPassword": {
|
"forgotPassword": {
|
||||||
"heading": "",
|
"heading": "Забыли пароль?",
|
||||||
"subheadings": {
|
"subheadings": {
|
||||||
"stepOne": "",
|
"stepOne": "Не волнуйтесь, мы вышлем вам инструкции для сброса.",
|
||||||
"stepTwo": "",
|
"stepTwo": "Мы выслали ссылку для сброса пароля на <email/>",
|
||||||
"stepThree": "",
|
"stepThree": "Ваш новый пароль должен отличаться от ваших предыдущих пароей.",
|
||||||
"stepFour": ""
|
"stepFour": "Ваш пароль был успешно сброшен. Нажмите ниже, чтобы волшебным образом войти в систему."
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"openEmail": "",
|
"openEmail": "",
|
||||||
"resetPassword": ""
|
"resetPassword": "Сбросить пароль"
|
||||||
},
|
},
|
||||||
"imageAlts": {
|
"imageAlts": {
|
||||||
"passwordKey": "",
|
"passwordKey": "",
|
||||||
@@ -742,7 +787,8 @@
|
|||||||
"failCountHeader": "",
|
"failCountHeader": "",
|
||||||
"lastRunHeader": "",
|
"lastRunHeader": "",
|
||||||
"lastFinishedAtHeader": "",
|
"lastFinishedAtHeader": "",
|
||||||
"lastRunTookHeader": ""
|
"lastRunTookHeader": "",
|
||||||
|
"intervalHeader": ""
|
||||||
},
|
},
|
||||||
"metricsTable": {
|
"metricsTable": {
|
||||||
"title": "",
|
"title": "",
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -376,7 +376,52 @@
|
|||||||
"email": "Eposta",
|
"email": "Eposta",
|
||||||
"role": "Rol",
|
"role": "Rol",
|
||||||
"created": "Oluşturulma"
|
"created": "Oluşturulma"
|
||||||
}
|
},
|
||||||
|
"addTeamMember": {
|
||||||
|
"addMemberMenu": "",
|
||||||
|
"title": "",
|
||||||
|
"description": "",
|
||||||
|
"addButton": ""
|
||||||
|
},
|
||||||
|
"register": "",
|
||||||
|
"registerToast": {
|
||||||
|
"success": "",
|
||||||
|
"dbUserExists": "",
|
||||||
|
"unknownError": ""
|
||||||
|
},
|
||||||
|
"registerTeamMember": {
|
||||||
|
"title": "",
|
||||||
|
"auth": {
|
||||||
|
"common": {
|
||||||
|
"inputs": {
|
||||||
|
"firstName": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lastName": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"invalid": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"errors": {
|
||||||
|
"empty": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"role": ""
|
||||||
},
|
},
|
||||||
"monitorState": {
|
"monitorState": {
|
||||||
"paused": "Durduruldu",
|
"paused": "Durduruldu",
|
||||||
@@ -742,7 +787,8 @@
|
|||||||
"failCountHeader": "",
|
"failCountHeader": "",
|
||||||
"lastRunHeader": "",
|
"lastRunHeader": "",
|
||||||
"lastFinishedAtHeader": "Son tamamlama",
|
"lastFinishedAtHeader": "Son tamamlama",
|
||||||
"lastRunTookHeader": ""
|
"lastRunTookHeader": "",
|
||||||
|
"intervalHeader": ""
|
||||||
},
|
},
|
||||||
"metricsTable": {
|
"metricsTable": {
|
||||||
"title": "",
|
"title": "",
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -380,7 +380,52 @@
|
|||||||
"email": "",
|
"email": "",
|
||||||
"role": "",
|
"role": "",
|
||||||
"created": ""
|
"created": ""
|
||||||
}
|
},
|
||||||
|
"addTeamMember": {
|
||||||
|
"addMemberMenu": "",
|
||||||
|
"title": "",
|
||||||
|
"description": "",
|
||||||
|
"addButton": ""
|
||||||
|
},
|
||||||
|
"register": "",
|
||||||
|
"registerToast": {
|
||||||
|
"success": "",
|
||||||
|
"dbUserExists": "",
|
||||||
|
"unknownError": ""
|
||||||
|
},
|
||||||
|
"registerTeamMember": {
|
||||||
|
"title": "",
|
||||||
|
"auth": {
|
||||||
|
"common": {
|
||||||
|
"inputs": {
|
||||||
|
"firstName": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lastName": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"errors": {
|
||||||
|
"empty": "",
|
||||||
|
"invalid": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"errors": {
|
||||||
|
"empty": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"role": ""
|
||||||
},
|
},
|
||||||
"monitorState": {
|
"monitorState": {
|
||||||
"paused": "",
|
"paused": "",
|
||||||
@@ -746,7 +791,8 @@
|
|||||||
"failCountHeader": "",
|
"failCountHeader": "",
|
||||||
"lastRunHeader": "",
|
"lastRunHeader": "",
|
||||||
"lastFinishedAtHeader": "",
|
"lastFinishedAtHeader": "",
|
||||||
"lastRunTookHeader": ""
|
"lastRunTookHeader": "",
|
||||||
|
"intervalHeader": ""
|
||||||
},
|
},
|
||||||
"metricsTable": {
|
"metricsTable": {
|
||||||
"title": "",
|
"title": "",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { execSync } from "child_process";
|
|||||||
|
|
||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({ mode }) => {
|
||||||
const env = loadEnv(mode, process.cwd(), "");
|
const env = loadEnv(mode, process.cwd(), "");
|
||||||
let version = "3.1.5";
|
let version = "3.2.0";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
base: "/",
|
base: "/",
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
FROM node:20-slim
|
FROM node:20-slim
|
||||||
|
|
||||||
|
# Install ping
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y iputils-ping \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY ../../package*.json ./
|
COPY ../../package*.json ./
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
FROM node:20-slim
|
FROM node:20-slim
|
||||||
|
|
||||||
|
# Install ping
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y iputils-ping \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY ./server/package*.json ./
|
COPY ./server/package*.json ./
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ services:
|
|||||||
server:
|
server:
|
||||||
image: ghcr.io/bluewave-labs/checkmate-backend-mono-multiarch:latest
|
image: ghcr.io/bluewave-labs/checkmate-backend-mono-multiarch:latest
|
||||||
restart: always
|
restart: always
|
||||||
|
pull_policy: always
|
||||||
ports:
|
ports:
|
||||||
- "52345:52345"
|
- "52345:52345"
|
||||||
environment:
|
environment:
|
||||||
@@ -16,7 +17,7 @@ services:
|
|||||||
mongodb:
|
mongodb:
|
||||||
image: mongo:4.4.18
|
image: mongo:4.4.18
|
||||||
restart: always
|
restart: always
|
||||||
command: ["mongod", "--quiet", --bind_ip_all"]
|
command: ["mongod", "--quiet", "--bind_ip_all"]
|
||||||
ports:
|
ports:
|
||||||
- "27017:27017"
|
- "27017:27017"
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -1,30 +1,37 @@
|
|||||||
# ---------------------
|
# ---------------------
|
||||||
# Frontend build stage
|
# Frontend build stage
|
||||||
# ---------------------
|
# ---------------------
|
||||||
FROM --platform=$BUILDPLATFORM node:24-slim AS frontend-build
|
FROM node:24-slim AS frontend-build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
WORKDIR /app/client
|
WORKDIR /app/client
|
||||||
|
|
||||||
COPY client/package*.json ./
|
COPY client/package.json ./
|
||||||
|
|
||||||
RUN npm ci
|
RUN npm install
|
||||||
|
|
||||||
|
RUN npm install esbuild@0.25.5 --build-from-source
|
||||||
|
|
||||||
COPY client ./
|
COPY client ./
|
||||||
|
|
||||||
RUN npm rebuild esbuild
|
|
||||||
|
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# ---------------------
|
# ---------------------
|
||||||
# Backend stage
|
# Backend stage
|
||||||
# ---------------------
|
# ---------------------
|
||||||
FROM --platform=$BUILDPLATFORM node:24-slim AS backend
|
FROM node:24-slim AS backend
|
||||||
|
|
||||||
|
# Install ping
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y iputils-ping \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /app/server
|
WORKDIR /app/server
|
||||||
|
|
||||||
COPY server/package*.json ./
|
COPY server/package.json ./
|
||||||
|
|
||||||
RUN npm ci
|
RUN npm install
|
||||||
|
|
||||||
COPY server ./
|
COPY server ./
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
server:
|
server:
|
||||||
image: ghcr.io/bluewave-labs/checkmate-backend-mono:latest
|
image: ghcr.io/bluewave-labs/checkmate-backend-mono:latest
|
||||||
|
pull_policy: always
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- "52345:52345"
|
- "52345:52345"
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ RUN npm run build
|
|||||||
|
|
||||||
FROM node:20-slim AS app
|
FROM node:20-slim AS app
|
||||||
|
|
||||||
|
# Install ping
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y iputils-ping \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /app/server
|
WORKDIR /app/server
|
||||||
|
|
||||||
COPY server ./
|
COPY server ./
|
||||||
|
|||||||
Vendored
+5
@@ -1,5 +1,10 @@
|
|||||||
FROM node:20-slim
|
FROM node:20-slim
|
||||||
|
|
||||||
|
# Install ping
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y iputils-ping \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY ./server/package*.json ./
|
COPY ./server/package*.json ./
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
FROM node:20-slim
|
FROM node:20-slim
|
||||||
|
|
||||||
|
# Install ping
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y iputils-ping \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
ENV NODE_OPTIONS="--max-old-space-size=2048"
|
ENV NODE_OPTIONS="--max-old-space-size=2048"
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
FROM node:20-slim
|
FROM node:20-slim
|
||||||
|
|
||||||
|
# Install ping
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y iputils-ping \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
ENV NODE_OPTIONS="--max-old-space-size=2048"
|
ENV NODE_OPTIONS="--max-old-space-size=2048"
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|||||||
Generated
+448
-42
@@ -39,7 +39,7 @@
|
|||||||
"ping": "0.4.4",
|
"ping": "0.4.4",
|
||||||
"sharp": "0.33.5",
|
"sharp": "0.33.5",
|
||||||
"ssl-checker": "2.0.10",
|
"ssl-checker": "2.0.10",
|
||||||
"super-simple-scheduler": "1.4.0",
|
"super-simple-scheduler": "1.4.1",
|
||||||
"swagger-ui-express": "5.0.1",
|
"swagger-ui-express": "5.0.1",
|
||||||
"winston": "^3.13.0"
|
"winston": "^3.13.0"
|
||||||
},
|
},
|
||||||
@@ -248,10 +248,20 @@
|
|||||||
"kuler": "^2.0.0"
|
"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": {
|
"node_modules/@eslint-community/eslint-utils": {
|
||||||
"version": "4.7.0",
|
"version": "4.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.8.0.tgz",
|
||||||
"integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
|
"integrity": "sha512-MJQFqrZgcW0UNYLGOuQpey/oTN59vyWwplvCGZztn1cKz9agZPPYpJB7h2OMmuu7VLqkvEjN8feFZJmxNF9D+Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -459,33 +469,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@humanfs/node": {
|
"node_modules/@humanfs/node": {
|
||||||
"version": "0.16.6",
|
"version": "0.16.7",
|
||||||
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
|
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
|
||||||
"integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
|
"integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@humanfs/core": "^0.19.1",
|
"@humanfs/core": "^0.19.1",
|
||||||
"@humanwhocodes/retry": "^0.3.0"
|
"@humanwhocodes/retry": "^0.4.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.18.0"
|
"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": {
|
"node_modules/@humanwhocodes/module-importer": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
|
||||||
@@ -514,6 +510,130 @@
|
|||||||
"url": "https://github.com/sponsors/nzakas"
|
"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": {
|
"node_modules/@img/sharp-libvips-linux-x64": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
|
||||||
@@ -530,6 +650,104 @@
|
|||||||
"url": "https://opencollective.com/libvips"
|
"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": {
|
"node_modules/@img/sharp-linux-x64": {
|
||||||
"version": "0.33.5",
|
"version": "0.33.5",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
|
||||||
@@ -552,6 +770,107 @@
|
|||||||
"@img/sharp-libvips-linux-x64": "1.0.4"
|
"@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": {
|
"node_modules/@ioredis/commands": {
|
||||||
"version": "1.3.1",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.3.1.tgz",
|
||||||
@@ -632,6 +951,58 @@
|
|||||||
"sparse-bitfield": "^3.0.3"
|
"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": {
|
"node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz",
|
||||||
@@ -645,6 +1016,19 @@
|
|||||||
"linux"
|
"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": {
|
"node_modules/@pkgjs/parseargs": {
|
||||||
"version": "0.11.0",
|
"version": "0.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||||
@@ -886,6 +1270,12 @@
|
|||||||
"undici-types": "~7.10.0"
|
"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": {
|
"node_modules/@types/triple-beam": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
|
||||||
@@ -1515,9 +1905,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001737",
|
"version": "1.0.30001739",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz",
|
||||||
"integrity": "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==",
|
"integrity": "sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -2630,9 +3020,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.211",
|
"version": "1.5.213",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.211.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.213.tgz",
|
||||||
"integrity": "sha512-IGBvimJkotaLzFnwIVgW9/UD/AOJ2tByUmeOrtqBfACSbAw5b1G0XpvdaieKyc7ULmbwXVx+4e4Be8pOPBrYkw==",
|
"integrity": "sha512-xr9eRzSLNa4neDO0xVFrkXu3vyIzG4Ay08dApecw42Z1NbmCt+keEpXdvlYGVe0wtvY5dhW0Ay0lY0IOfsCg0Q==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/emitter-component": {
|
"node_modules/emitter-component": {
|
||||||
@@ -3441,6 +3831,20 @@
|
|||||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
@@ -3891,11 +4295,12 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/htmlnano": {
|
"node_modules/htmlnano": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.3.tgz",
|
||||||
"integrity": "sha512-8Fst+0bhAfU362S6oHVb4wtJj/UYEFr0qiCLAEi8zioqmp1JYBQx5crZAADlFVX0Ly/6s/IQz6G7PL9/hgoJaQ==",
|
"integrity": "sha512-mzTUHhxdfDw80X36rv5qFvjvor7r0uCwXI5mzo8CW61tImbe5Jpvl3JzPAetVh54wUYVuoa8x3qw8LFn8B3gHQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/relateurl": "^0.2.33",
|
||||||
"cosmiconfig": "^9.0.0",
|
"cosmiconfig": "^9.0.0",
|
||||||
"posthtml": "^0.16.5"
|
"posthtml": "^0.16.5"
|
||||||
},
|
},
|
||||||
@@ -5801,13 +6206,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nise/node_modules/path-to-regexp": {
|
"node_modules/nise/node_modules/path-to-regexp": {
|
||||||
"version": "8.2.0",
|
"version": "8.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
|
||||||
"integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
|
"integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"funding": {
|
||||||
"node": ">=16"
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/node-abort-controller": {
|
"node_modules/node-abort-controller": {
|
||||||
@@ -7865,9 +8271,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/super-simple-scheduler": {
|
"node_modules/super-simple-scheduler": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.4.1.tgz",
|
||||||
"integrity": "sha512-DO8HTrrx+23Cz2RPCuniPZ9IhHx2lgtF8ZJiBV4JfUhqDqxuJ2leQ+l3gZ+vniy4DfpMki7s6bZwM3u8norQEA==",
|
"integrity": "sha512-jKVwxdRBXOHrUstA2pvO4jBZIA3CUinKiQ6k5C2Wn5tscoV3srppPxW0zG3VNKTCaGxo3yPn6Zaf3IDVBcAwRA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"uuid": "11.1.0",
|
"uuid": "11.1.0",
|
||||||
@@ -7939,9 +8345,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/swagger-ui-dist": {
|
"node_modules/swagger-ui-dist": {
|
||||||
"version": "5.28.0",
|
"version": "5.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.28.0.tgz",
|
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.28.1.tgz",
|
||||||
"integrity": "sha512-I9ibQtr77BPzT28WFWMVktzQOtWzoSS2J99L0Att8gDar1atl1YTRI7NUFSr4kj8VvWICgylanYHIoHjITc7iA==",
|
"integrity": "sha512-IvPrtNi8MvjiuDgoSmPYgg27Lvu38fnLD1OSd8Y103xXsPAqezVNnNeHnVCZ/d+CMXJblflGaIyHxAYIF3O71w==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@scarf/scarf": "=1.4.0"
|
"@scarf/scarf": "=1.4.0"
|
||||||
|
|||||||
+1
-1
@@ -46,7 +46,7 @@
|
|||||||
"ping": "0.4.4",
|
"ping": "0.4.4",
|
||||||
"sharp": "0.33.5",
|
"sharp": "0.33.5",
|
||||||
"ssl-checker": "2.0.10",
|
"ssl-checker": "2.0.10",
|
||||||
"super-simple-scheduler": "1.4.0",
|
"super-simple-scheduler": "1.4.1",
|
||||||
"swagger-ui-express": "5.0.1",
|
"swagger-ui-express": "5.0.1",
|
||||||
"winston": "^3.13.0"
|
"winston": "^3.13.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -162,6 +162,14 @@ class StatusPageModule {
|
|||||||
as: "monitors.checks",
|
as: "monitors.checks",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
$lookup: {
|
||||||
|
from: "monitorstats",
|
||||||
|
localField: "monitors._id",
|
||||||
|
foreignField: "monitorId",
|
||||||
|
as: "monitors.stats",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
$addFields: {
|
$addFields: {
|
||||||
"monitors.orderIndex": {
|
"monitors.orderIndex": {
|
||||||
@@ -181,6 +189,9 @@ class StatusPageModule {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"monitors.uptimePercentage": {
|
||||||
|
$arrayElemAt: ["$monitors.stats.uptimePercentage", 0],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ $match: { "monitors.orderIndex": { $ne: -1 } } },
|
{ $match: { "monitors.orderIndex": { $ne: -1 } } },
|
||||||
|
|||||||
@@ -149,10 +149,12 @@
|
|||||||
"statusPageByTeamId": "Got status pages by team id successfully",
|
"statusPageByTeamId": "Got status pages by team id successfully",
|
||||||
"httpNetworkError": "Network error",
|
"httpNetworkError": "Network error",
|
||||||
"httpNotJson": "Response data is not json",
|
"httpNotJson": "Response data is not json",
|
||||||
|
"httpExpectedValueFail": "Expected value did not match",
|
||||||
"httpJsonPathError": "Failed to parse json data",
|
"httpJsonPathError": "Failed to parse json data",
|
||||||
"httpEmptyResult": "Result is empty",
|
"httpEmptyResult": "Result is empty",
|
||||||
"httpMatchSuccess": "Response data match successfully",
|
"httpMatchSuccess": "Response data match successfully",
|
||||||
"httpMatchFail": "Failed to match response data",
|
"httpMatchFail": "Failed to match response data",
|
||||||
|
"httpJsonPathFail": "JSONPath did not match expected value",
|
||||||
"webhookSendSuccess": "Webhook notification sent successfully",
|
"webhookSendSuccess": "Webhook notification sent successfully",
|
||||||
"telegramRequiresBotTokenAndChatId": "Telegram notifications require both botToken and chatId",
|
"telegramRequiresBotTokenAndChatId": "Telegram notifications require both botToken and chatId",
|
||||||
"webhookUrlRequired": "Webhook URL is required",
|
"webhookUrlRequired": "Webhook URL is required",
|
||||||
@@ -162,5 +164,18 @@
|
|||||||
"monitorDownAlert": "Downtime Alert: One of your monitors went offline.\n📌 Monitor: {monitorName}\n📅 Time: {time}\n⚠️ Status: DOWN\n📟 Status Code: {code}\n\u200B\n",
|
"monitorDownAlert": "Downtime Alert: One of your monitors went offline.\n📌 Monitor: {monitorName}\n📅 Time: {time}\n⚠️ Status: DOWN\n📟 Status Code: {code}\n\u200B\n",
|
||||||
"sendTestEmail": "Test email sent successfully",
|
"sendTestEmail": "Test email sent successfully",
|
||||||
"errorForValidEmailAddress": "A valid recipient email address is required.",
|
"errorForValidEmailAddress": "A valid recipient email address is required.",
|
||||||
"testEmailSubject": "Test E-mail from Checkmate"
|
"testEmailSubject": "Test E-mail from Checkmate",
|
||||||
|
"discordNotification": {
|
||||||
|
"monitor": "Monitor",
|
||||||
|
"status": "Status",
|
||||||
|
"statusCode": "Status Code",
|
||||||
|
"time": "Time",
|
||||||
|
"up": "UP",
|
||||||
|
"down": "DOWN",
|
||||||
|
"checkmate": "Checkmate",
|
||||||
|
"url": "URL",
|
||||||
|
"unknown": "Unknown",
|
||||||
|
"uptimeAlert": "Uptime Alert: One of your monitors is back online",
|
||||||
|
"downtimeAlert": "Downtime Alert: One of your monitors went offline"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -28,11 +28,12 @@ class SuperSimpleQueueHelper {
|
|||||||
return async (monitor) => {
|
return async (monitor) => {
|
||||||
try {
|
try {
|
||||||
const monitorId = monitor._id;
|
const monitorId = monitor._id;
|
||||||
|
const teamId = monitor.teamId;
|
||||||
if (!monitorId) {
|
if (!monitorId) {
|
||||||
throw new Error("No monitor id");
|
throw new Error("No monitor id");
|
||||||
}
|
}
|
||||||
|
|
||||||
const maintenanceWindowActive = await this.isInMaintenanceWindow(monitorId);
|
const maintenanceWindowActive = await this.isInMaintenanceWindow(monitorId, teamId);
|
||||||
if (maintenanceWindowActive) {
|
if (maintenanceWindowActive) {
|
||||||
this.logger.info({
|
this.logger.info({
|
||||||
message: `Monitor ${monitorId} is in maintenance window`,
|
message: `Monitor ${monitorId} is in maintenance window`,
|
||||||
@@ -77,8 +78,11 @@ class SuperSimpleQueueHelper {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
async isInMaintenanceWindow(monitorId) {
|
async isInMaintenanceWindow(monitorId, teamId) {
|
||||||
const maintenanceWindows = await this.db.maintenanceWindowModule.getMaintenanceWindowsByMonitorId(monitorId);
|
const maintenanceWindows = await this.db.maintenanceWindowModule.getMaintenanceWindowsByMonitorId({
|
||||||
|
monitorId: monitorId.toString(),
|
||||||
|
teamId: teamId.toString(),
|
||||||
|
});
|
||||||
// Check for active maintenance window:
|
// Check for active maintenance window:
|
||||||
const maintenanceWindowIsActive = maintenanceWindows.reduce((acc, window) => {
|
const maintenanceWindowIsActive = maintenanceWindows.reduce((acc, window) => {
|
||||||
if (window.active) {
|
if (window.active) {
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { match } from "assert";
|
|
||||||
|
|
||||||
const SERVICE_NAME = "NetworkService";
|
const SERVICE_NAME = "NetworkService";
|
||||||
|
|
||||||
class NetworkService {
|
class NetworkService {
|
||||||
@@ -75,6 +73,9 @@ class NetworkService {
|
|||||||
const { response, responseTime, error } = await this.timeRequest(() => this.ping.promise.probe(monitor.url));
|
const { response, responseTime, error } = await this.timeRequest(() => this.ping.promise.probe(monitor.url));
|
||||||
|
|
||||||
if (!response) {
|
if (!response) {
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
throw new Error("Ping failed - no result returned");
|
throw new Error("Ping failed - no result returned");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +105,7 @@ class NetworkService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async requestHttp(monitor) {
|
async requestHttp(monitor) {
|
||||||
const { url, secret, _id, name, teamId, type, ignoreTlsErrors, jsonPath, matchMethod, expectedValue } = monitor;
|
const { url, secret, _id, teamId, type, ignoreTlsErrors, jsonPath, matchMethod, expectedValue } = monitor;
|
||||||
const httpResponse = {
|
const httpResponse = {
|
||||||
monitorId: _id,
|
monitorId: _id,
|
||||||
teamId: teamId,
|
teamId: teamId,
|
||||||
@@ -153,33 +154,61 @@ class NetworkService {
|
|||||||
return httpResponse;
|
return httpResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (expectedValue) {
|
if (expectedValue && !jsonPath) {
|
||||||
let ok = false;
|
let ok = false;
|
||||||
if (matchMethod === "equal") ok = payload === expectedValue;
|
if (matchMethod === "equal") ok = payload === expectedValue;
|
||||||
if (matchMethod === "include") ok = payload.includes(expectedValue);
|
if (matchMethod === "include" && typeof payload === "string") ok = payload.includes(expectedValue);
|
||||||
if (matchMethod === "regex") ok = new RegExp(expectedValue).test(payload);
|
if (matchMethod === "regex" && typeof payload === "string") ok = new RegExp(expectedValue).test(payload);
|
||||||
|
|
||||||
if (ok === true) {
|
if (ok === true) {
|
||||||
return httpResponse;
|
return httpResponse;
|
||||||
} else {
|
} else {
|
||||||
httpResponse.code = 500;
|
httpResponse.code = 500;
|
||||||
httpResponse.status = false;
|
httpResponse.status = false;
|
||||||
|
httpResponse.message = this.stringService.httpExpectedValueFail;
|
||||||
return httpResponse;
|
return httpResponse;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jsonPath) {
|
if (jsonPath) {
|
||||||
const contentType = response.headers["content-type"];
|
const contentType = response.headers["content-type"];
|
||||||
|
|
||||||
const isJson = contentType?.includes("application/json");
|
const isJson = contentType?.includes("application/json");
|
||||||
if (!isJson) {
|
if (!isJson) {
|
||||||
httpResponse.status = false;
|
httpResponse.status = false;
|
||||||
httpResponse.message = this.stringService.httpNotJson;
|
httpResponse.message = this.stringService.httpNotJson;
|
||||||
return httpResponse;
|
return httpResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.jmespath.search(payload, jsonPath);
|
const extracted = this.jmespath.search(payload, jsonPath);
|
||||||
|
if (expectedValue) {
|
||||||
|
let ok = false;
|
||||||
|
if (matchMethod === "equal") ok = extracted === expectedValue;
|
||||||
|
if (matchMethod === "include" && typeof extracted === "string") ok = extracted.includes(expectedValue);
|
||||||
|
if (matchMethod === "regex" && typeof extracted === "string") ok = new RegExp(expectedValue).test(extracted);
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
httpResponse.extracted = extracted;
|
||||||
|
return httpResponse;
|
||||||
|
} else {
|
||||||
|
httpResponse.status = false;
|
||||||
|
httpResponse.code = 500;
|
||||||
|
httpResponse.message = this.stringService.httpJsonPathFail;
|
||||||
|
httpResponse.extracted = extracted;
|
||||||
|
return httpResponse;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const isFalsey = extracted === false || extracted === "false" || extracted === undefined || extracted === null;
|
||||||
|
if (!isFalsey) {
|
||||||
|
httpResponse.extracted = extracted;
|
||||||
|
return httpResponse;
|
||||||
|
} else {
|
||||||
|
httpResponse.status = false;
|
||||||
|
httpResponse.code = 500;
|
||||||
|
httpResponse.message = this.stringService.httpJsonPathFail;
|
||||||
|
httpResponse.extracted = extracted;
|
||||||
|
return httpResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
httpResponse.status = false;
|
httpResponse.status = false;
|
||||||
httpResponse.message = this.stringService.httpJsonPathError;
|
httpResponse.message = this.stringService.httpJsonPathError;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class NotificationService {
|
|||||||
return NotificationService.SERVICE_NAME;
|
return NotificationService.SERVICE_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
sendNotification = async ({ notification, subject, content, html }) => {
|
sendNotification = async ({ notification, subject, content, html, discordContent = null, webhookBody = null }) => {
|
||||||
const { type, address } = notification;
|
const { type, address } = notification;
|
||||||
|
|
||||||
if (type === "email") {
|
if (type === "email") {
|
||||||
@@ -28,9 +28,11 @@ class NotificationService {
|
|||||||
// Create a body for webhooks
|
// Create a body for webhooks
|
||||||
let body = { text: content };
|
let body = { text: content };
|
||||||
if (type === "discord") {
|
if (type === "discord") {
|
||||||
body = { content };
|
body = !discordContent ? { content } : discordContent;
|
||||||
|
}
|
||||||
|
if (type === "webhook") {
|
||||||
|
body = !webhookBody ? { content } : webhookBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === "slack" || type === "discord" || type === "webhook") {
|
if (type === "slack" || type === "discord" || type === "webhook") {
|
||||||
const response = await this.networkService.requestWebhook(type, address, body);
|
const response = await this.networkService.requestWebhook(type, address, body);
|
||||||
return response.status;
|
return response.status;
|
||||||
@@ -55,7 +57,6 @@ class NotificationService {
|
|||||||
|
|
||||||
const notificationIDs = networkResponse.monitor?.notifications ?? [];
|
const notificationIDs = networkResponse.monitor?.notifications ?? [];
|
||||||
if (notificationIDs.length === 0) return false;
|
if (notificationIDs.length === 0) return false;
|
||||||
|
|
||||||
if (networkResponse.monitor.type === "hardware") {
|
if (networkResponse.monitor.type === "hardware") {
|
||||||
const thresholds = networkResponse?.monitor?.thresholds;
|
const thresholds = networkResponse?.monitor?.thresholds;
|
||||||
|
|
||||||
@@ -63,30 +64,29 @@ class NotificationService {
|
|||||||
const metrics = networkResponse?.payload?.data ?? null;
|
const metrics = networkResponse?.payload?.data ?? null;
|
||||||
if (metrics === null) return false; // No metrics, we're done
|
if (metrics === null) return false; // No metrics, we're done
|
||||||
|
|
||||||
const alerts = await this.notificationUtils.buildHardwareAlerts(networkResponse);
|
const [alerts, discordContent] = await this.notificationUtils.buildHardwareAlerts(networkResponse);
|
||||||
if (alerts.length === 0) return false;
|
if (alerts.length === 0) return false;
|
||||||
|
|
||||||
const { subject, html } = await this.notificationUtils.buildHardwareEmail(networkResponse, alerts);
|
const { subject, html } = await this.notificationUtils.buildHardwareEmail(networkResponse, alerts);
|
||||||
const content = await this.notificationUtils.buildHardwareNotificationMessage(alerts);
|
const content = await this.notificationUtils.buildHardwareNotificationMessage(alerts, monitor);
|
||||||
|
const webhookBody = await this.notificationUtils.buildHardwareWebhookBody(alerts, monitor);
|
||||||
const success = await this.notifyAll({ notificationIDs, subject, html, content });
|
const success = await this.notifyAll({ notificationIDs, subject, html, content, discordContent, webhookBody });
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status monitors
|
// Status monitors
|
||||||
const { subject, html } = await this.notificationUtils.buildStatusEmail(networkResponse);
|
const { subject, html } = await this.notificationUtils.buildStatusEmail(networkResponse);
|
||||||
const content = await this.notificationUtils.buildWebhookMessage(networkResponse);
|
const [content, discordContent] = await this.notificationUtils.buildWebhookMessage(networkResponse);
|
||||||
const success = this.notifyAll({ notificationIDs, subject, html, content });
|
const success = this.notifyAll({ notificationIDs, subject, html, content, discordContent });
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
async notifyAll({ notificationIDs, subject, html, content }) {
|
async notifyAll({ notificationIDs, subject, html, content, discordContent = null, webhookBody = null }) {
|
||||||
const notifications = await this.db.notificationModule.getNotificationsByIds(notificationIDs);
|
const notifications = await this.db.notificationModule.getNotificationsByIds(notificationIDs);
|
||||||
|
|
||||||
// Map each notification to a test promise
|
// Map each notification to a test promise
|
||||||
const promises = notifications.map(async (notification) => {
|
const promises = notifications.map(async (notification) => {
|
||||||
try {
|
try {
|
||||||
await this.sendNotification({ notification, subject, content, html });
|
await this.sendNotification({ notification, subject, content, html, discordContent, webhookBody });
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -70,7 +70,25 @@ class NotificationUtils {
|
|||||||
.replace("{time}", formattedTime)
|
.replace("{time}", formattedTime)
|
||||||
.replace("{code}", code || "Unknown");
|
.replace("{code}", code || "Unknown");
|
||||||
}
|
}
|
||||||
return messageText;
|
const dn = this.stringService.discordNotification;
|
||||||
|
let discordMessageText = {
|
||||||
|
embeds: [
|
||||||
|
{
|
||||||
|
title: status ? dn.uptimeAlert : dn.downtimeAlert,
|
||||||
|
color: status ? 5763719 : 15548997,
|
||||||
|
|
||||||
|
fields: [
|
||||||
|
{ name: dn.monitor, value: monitor?.name ?? dn.unknown, inline: true },
|
||||||
|
{ name: dn.status, value: status ? dn.up : dn.down, inline: true },
|
||||||
|
{ name: dn.statusCode, value: String(code ?? dn.unknown), inline: true },
|
||||||
|
{ name: dn.time, value: formattedTime, inline: true },
|
||||||
|
{ name: dn.url, value: monitor?.url ?? dn.unknown, inline: false },
|
||||||
|
],
|
||||||
|
footer: { text: dn.checkmate },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
return [messageText, discordMessageText];
|
||||||
};
|
};
|
||||||
|
|
||||||
buildHardwareAlerts = async (networkResponse) => {
|
buildHardwareAlerts = async (networkResponse) => {
|
||||||
@@ -88,6 +106,43 @@ class NotificationUtils {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const alertsToSend = [];
|
const alertsToSend = [];
|
||||||
|
const discordEmbeds = [];
|
||||||
|
const monitorInfoFields = [
|
||||||
|
{ name: "Monitor", value: monitor.name, inline: true },
|
||||||
|
{ name: "URL", value: monitor.url, inline: false },
|
||||||
|
];
|
||||||
|
const formatDiscordAlert = {
|
||||||
|
cpu: () => ({
|
||||||
|
title: "CPU alert",
|
||||||
|
description: `Your current CPU usage (${(cpuUsage * 100).toFixed(0)}%) is above your threshold (${(cpuThreshold * 100).toFixed(0)}%)`,
|
||||||
|
color: 15548997,
|
||||||
|
fields: monitorInfoFields,
|
||||||
|
footer: { text: "Checkmate" },
|
||||||
|
}),
|
||||||
|
|
||||||
|
memory: () => ({
|
||||||
|
title: "Memory alert",
|
||||||
|
description: `Your current memory usage (${(memoryUsage * 100).toFixed(0)}%) is above your threshold (${(memoryThreshold * 100).toFixed(0)}%)`,
|
||||||
|
color: 15548997,
|
||||||
|
fields: monitorInfoFields,
|
||||||
|
footer: { text: "Checkmate" },
|
||||||
|
}),
|
||||||
|
|
||||||
|
disk: () => ({
|
||||||
|
title: "Disk alert",
|
||||||
|
description: `Your current disk usage is above your threshold (${(diskThreshold * 100).toFixed(0)}%)`,
|
||||||
|
color: 15548997,
|
||||||
|
footer: { text: "Checkmate" },
|
||||||
|
fields: [
|
||||||
|
...monitorInfoFields,
|
||||||
|
...(Array.isArray(disk) ? disk : []).map((d, idx) => ({
|
||||||
|
name: `Disk ${idx}`,
|
||||||
|
value: `${(d?.usage_percent * 100).toFixed(0)}%`,
|
||||||
|
inline: true,
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
};
|
||||||
const alertTypes = ["cpu", "memory", "disk"];
|
const alertTypes = ["cpu", "memory", "disk"];
|
||||||
for (const type of alertTypes) {
|
for (const type of alertTypes) {
|
||||||
// Iterate over each alert type to see if any need to be decremented
|
// Iterate over each alert type to see if any need to be decremented
|
||||||
@@ -108,11 +163,13 @@ class NotificationUtils {
|
|||||||
.join(", ")} is above your threshold (${(diskThreshold * 100).toFixed(0)}%)`,
|
.join(", ")} is above your threshold (${(diskThreshold * 100).toFixed(0)}%)`,
|
||||||
};
|
};
|
||||||
alertsToSend.push(formatAlert[type]());
|
alertsToSend.push(formatAlert[type]());
|
||||||
|
discordEmbeds.push(formatDiscordAlert[type]());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await monitor.save();
|
await monitor.save();
|
||||||
return alertsToSend;
|
const discordPayload = discordEmbeds.length ? { embeds: discordEmbeds } : null;
|
||||||
|
return [alertsToSend, discordPayload];
|
||||||
};
|
};
|
||||||
|
|
||||||
buildHardwareEmail = async (networkResponse, alerts) => {
|
buildHardwareEmail = async (networkResponse, alerts) => {
|
||||||
@@ -124,8 +181,15 @@ class NotificationUtils {
|
|||||||
return { subject, html };
|
return { subject, html };
|
||||||
};
|
};
|
||||||
|
|
||||||
buildHardwareNotificationMessage = (alerts) => {
|
buildHardwareNotificationMessage = (alerts, monitor) => {
|
||||||
return alerts.map((alert) => alert).join("\n");
|
const alertsHeader = [`Monitor: ${monitor.name}`, `URL: ${monitor.url}`];
|
||||||
|
const alertText = alerts.length > 0 ? [...alertsHeader, ...alerts] : [];
|
||||||
|
return alertText.map((alert) => alert).join("\n");
|
||||||
|
};
|
||||||
|
buildHardwareWebhookBody = (alerts, monitor) => {
|
||||||
|
const content = alerts.map((alert) => alert).join("\n");
|
||||||
|
const body = { text: content, name: monitor.name, url: monitor.url };
|
||||||
|
return body;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -216,6 +216,10 @@ class StringService {
|
|||||||
return this.translationService.getTranslation("monitorDownAlert");
|
return this.translationService.getTranslation("monitorDownAlert");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get discordNotification() {
|
||||||
|
return this.translationService.getTranslation("discordNotification");
|
||||||
|
}
|
||||||
|
|
||||||
getWebhookUnsupportedPlatform(platform) {
|
getWebhookUnsupportedPlatform(platform) {
|
||||||
return this.translationService.getTranslation("webhookUnsupportedPlatform").replace("{platform}", platform);
|
return this.translationService.getTranslation("webhookUnsupportedPlatform").replace("{platform}", platform);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user