Merge remote-tracking branch 'upstream/develop' into feat/be/webhook-integrations
This commit is contained in:
commit
d83773abd1
35 files changed
+1830
-1810
No files matched your search
@@ -1,4 +1,4 @@
|
||||
**(when sending your PR, only remove this line. Do not remove any lines below. Check all the relevant items before sending your PR)**
|
||||
**(Please remove this line only before submitting your PR. Ensure that all relevant items are checked before submission.)**
|
||||
|
||||
## Describe your changes
|
||||
|
||||
@@ -8,10 +8,10 @@ Briefly describe the changes you made and their purpose.
|
||||
|
||||
Mention the issue number(s) this PR addresses (e.g., #123).
|
||||
|
||||
## Please ensure all items are checked off before requesting a review:
|
||||
## Please ensure all items are checked off before requesting a review. "Checked off" means you need to add an "x" character between brackets so they turn into checkmarks.
|
||||
|
||||
- [ ] I deployed the application locally.
|
||||
- [ ] I have performed a self-review and testing of my code.
|
||||
- [ ] (Do not skip this or your PR will be closed) I deployed the application locally.
|
||||
- [ ] (Do not skip this or your PR will be closed) I have performed a self-review and testing of my code.
|
||||
- [ ] I have included the issue # in the PR.
|
||||
- [ ] I have labelled the PR correctly.
|
||||
- [ ] The issue I am working on is assigned to me.
|
||||
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
|
||||
# Contributing to Uptime Manager application
|
||||
# Contributing to Checkmate
|
||||
|
||||
First off, thanks for taking the time to contribute! ❤️
|
||||
|
||||
All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how the Uptime Manager project handles them. Please make sure to read the relevant section before making your contribution. It will make it much easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉
|
||||
All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how the Checkmate project handles them. Please make sure to read the relevant section before making your contribution. It will make it much easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉
|
||||
|
||||
> And if you like the project, but don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about:
|
||||
> - Star the project
|
||||
@@ -77,7 +77,7 @@ Once it's filed:
|
||||
This section guides you through submitting an enhancement suggestion for the application, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community understand your enhancements and find related suggestions.
|
||||
|
||||
- Make sure that you are using the latest version.
|
||||
- Make sure you run the code locally. The Uptime Manager [documentation](https://bluewavelabs.gitbook.io/uptime-manager) covers it.
|
||||
- Make sure you run the code locally. The Checkmate [documentation](https://bluewavelabs.gitbook.io/checkmate) covers it.
|
||||
- Find out if the functionality is already covered, maybe by an individual configuration.
|
||||
- Perform a [search](/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
|
||||
- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library.
|
||||
|
||||
Generated
+17
-17
@@ -15,8 +15,8 @@
|
||||
"@mui/lab": "6.0.0-beta.24",
|
||||
"@mui/material": "6.4.1",
|
||||
"@mui/x-charts": "^7.5.1",
|
||||
"@mui/x-data-grid": "7.24.0",
|
||||
"@mui/x-date-pickers": "7.24.0",
|
||||
"@mui/x-data-grid": "7.24.1",
|
||||
"@mui/x-date-pickers": "7.24.1",
|
||||
"@reduxjs/toolkit": "2.5.0",
|
||||
"axios": "^1.7.4",
|
||||
"dayjs": "1.11.13",
|
||||
@@ -1390,15 +1390,15 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@mui/x-charts": {
|
||||
"version": "7.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-7.24.0.tgz",
|
||||
"integrity": "sha512-X/zN0GxPHJC24QckFrtEbIK0HYoGwZgpNC5CV9cvRGvHphDFuNZk9xg9fJwIg1i3PPZHXRO3E+L/ee6fJ9ufhw==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-7.24.1.tgz",
|
||||
"integrity": "sha512-OdTS/nXaANPe4AoUFIDD4LlID8kK/00q+uqVOCkVClEvFQeAkj3pBaghdS4hY7rVqsCgsm+yOStQVJa9G2MR+Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.25.7",
|
||||
"@mui/utils": "^5.16.6 || ^6.0.0",
|
||||
"@mui/x-charts-vendor": "7.20.0",
|
||||
"@mui/x-internals": "7.24.0",
|
||||
"@mui/x-internals": "7.24.1",
|
||||
"@react-spring/rafz": "^9.7.5",
|
||||
"@react-spring/web": "^9.7.5",
|
||||
"clsx": "^2.1.1",
|
||||
@@ -1448,14 +1448,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-data-grid": {
|
||||
"version": "7.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.24.0.tgz",
|
||||
"integrity": "sha512-goYTKDp+e+dXw7E+WndWUhWXTjX3aTqN8W2dCKhXnmE9Gu8dFwG6Azl7GK9l2m5YHGuqYmpWqcSG9etLdwYaVg==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.24.1.tgz",
|
||||
"integrity": "sha512-4sYTbMwsDotuTd2Cwa2JGTPXPWQs8RGJvocAKnIsNOzNdZNMrikE//HO35snriK8s4dauAApY7RVbeisjpVT+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.25.7",
|
||||
"@mui/utils": "^5.16.6 || ^6.0.0",
|
||||
"@mui/x-internals": "7.24.0",
|
||||
"@mui/x-internals": "7.24.1",
|
||||
"clsx": "^2.1.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"reselect": "^5.1.1"
|
||||
@@ -1485,14 +1485,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-date-pickers": {
|
||||
"version": "7.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.24.0.tgz",
|
||||
"integrity": "sha512-oBM9Yp2H3tJ7qoHB4APQJYxZG4rz6JD4CwLzbzD9o3r+E1HGpGSLhwK3rDEz9VEjbOq8893Z2TGYLLWoyjeFXQ==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.24.1.tgz",
|
||||
"integrity": "sha512-ykQugMQHuQKBk3kViW/r0PpubtHQOlrd54bgbdafgTMCeM2VpXvv4zimzOu5IGnM6wEN8hupC7EXZbkrT6x46w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.25.7",
|
||||
"@mui/utils": "^5.16.6 || ^6.0.0",
|
||||
"@mui/x-internals": "7.24.0",
|
||||
"@mui/x-internals": "7.24.1",
|
||||
"@types/react-transition-group": "^4.4.11",
|
||||
"clsx": "^2.1.1",
|
||||
"prop-types": "^15.8.1",
|
||||
@@ -1551,9 +1551,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-internals": {
|
||||
"version": "7.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.24.0.tgz",
|
||||
"integrity": "sha512-lYa/XLltxNMY8YAFDopIHrXda2EAoqMCilyGMuPMz+WTG+b+StlUKqtj8cgFPQ/sa5dQ2fR7R3KJdjLREKUrlQ==",
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.24.1.tgz",
|
||||
"integrity": "sha512-9BvJzpLJnS9BDphvkiv6v0QOLxbnu8jhwcexFjtCQ2ZyxtVuVsWzGZ2npT9sGOil7+eaFDmWnJtea/tgrPvSwQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.25.7",
|
||||
|
||||
+2
-2
@@ -18,8 +18,8 @@
|
||||
"@mui/lab": "6.0.0-beta.24",
|
||||
"@mui/material": "6.4.1",
|
||||
"@mui/x-charts": "^7.5.1",
|
||||
"@mui/x-data-grid": "7.24.0",
|
||||
"@mui/x-date-pickers": "7.24.0",
|
||||
"@mui/x-data-grid": "7.24.1",
|
||||
"@mui/x-date-pickers": "7.24.1",
|
||||
"@reduxjs/toolkit": "2.5.0",
|
||||
"axios": "^1.7.4",
|
||||
"dayjs": "1.11.13",
|
||||
|
||||
@@ -16,7 +16,7 @@ import { BaseLabel } from "../Label";
|
||||
const DEFAULT_CODE = 9999; // Default code for unknown status
|
||||
|
||||
const handleStatusCode = (status) => {
|
||||
if (status >= 100 && status < 600) {
|
||||
if (status) {
|
||||
return status;
|
||||
}
|
||||
return DEFAULT_CODE;
|
||||
|
||||
@@ -104,6 +104,14 @@ const Search = ({
|
||||
"& .MuiOutlinedInput-root": {
|
||||
paddingY: 0,
|
||||
},
|
||||
"& .MuiAutocomplete-tag": {
|
||||
// CAIO_REVIEW
|
||||
color: theme.palette.primary.contrastText,
|
||||
backgroundColor: theme.palette.primary.lowContrast,
|
||||
},
|
||||
"& .MuiChip-deleteIcon": {
|
||||
color: theme.palette.primary.contrastText, // CAIO_REVIEW
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{error && (
|
||||
@@ -165,6 +173,7 @@ const Search = ({
|
||||
"& .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true'], & .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true'].Mui-focused, & .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true']:hover":
|
||||
{
|
||||
backgroundColor: theme.palette.primary.lowContrast,
|
||||
color: "red",
|
||||
},
|
||||
"& li.MuiAutocomplete-option:hover:not([aria-selected='true'])": {
|
||||
color: theme.palette.primary.main,
|
||||
|
||||
@@ -5,11 +5,11 @@ import { TablePaginationActions } from "./Actions";
|
||||
import SelectorVertical from "../../../assets/icons/selector-vertical.svg?react";
|
||||
|
||||
Pagination.propTypes = {
|
||||
monitorCount: PropTypes.number.isRequired, // Total number of items for pagination.
|
||||
itemCount: PropTypes.number.isRequired, // Total number of items for pagination.
|
||||
page: PropTypes.number.isRequired, // Current page index.
|
||||
rowsPerPage: PropTypes.number.isRequired, // Number of rows displayed per page.
|
||||
handleChangePage: PropTypes.func.isRequired, // Function to handle page changes.
|
||||
handleChangeRowsPerPage: PropTypes.func.isRequired, // Function to handle changes in rows per page.
|
||||
handleChangeRowsPerPage: PropTypes.func, // Function to handle changes in rows per page.
|
||||
};
|
||||
|
||||
const ROWS_PER_PAGE_OPTIONS = [5, 10, 15, 25];
|
||||
@@ -18,6 +18,7 @@ const ROWS_PER_PAGE_OPTIONS = [5, 10, 15, 25];
|
||||
* Pagination component for table navigation with customized styling and behavior.
|
||||
*
|
||||
* @param {object} props - Component properties.
|
||||
* @param {string} props.paginationLabel - Label for the pagination.
|
||||
* @param {number} props.monitorCount - Total number of monitors to paginate.
|
||||
* @param {number} props.page - Current page index (0-based).
|
||||
* @param {number} props.rowsPerPage - Number of rows to display per page.
|
||||
@@ -26,7 +27,8 @@ const ROWS_PER_PAGE_OPTIONS = [5, 10, 15, 25];
|
||||
* @returns {JSX.Element} The Pagination component.
|
||||
*/
|
||||
function Pagination({
|
||||
monitorCount,
|
||||
paginationLabel,
|
||||
itemCount,
|
||||
page,
|
||||
rowsPerPage,
|
||||
handleChangePage,
|
||||
@@ -35,7 +37,7 @@ function Pagination({
|
||||
const theme = useTheme();
|
||||
|
||||
const start = page * rowsPerPage + 1;
|
||||
const end = Math.min(page * rowsPerPage + rowsPerPage, monitorCount);
|
||||
const end = Math.min(page * rowsPerPage + rowsPerPage, itemCount);
|
||||
const range = `${start} - ${end}`;
|
||||
|
||||
return (
|
||||
@@ -51,11 +53,11 @@ function Pagination({
|
||||
variant="body2"
|
||||
sx={{ opacity: 0.7 }}
|
||||
>
|
||||
Showing {range} of {monitorCount} monitor(s)
|
||||
Showing {range} of {itemCount} {paginationLabel}
|
||||
</Typography>
|
||||
<TablePagination
|
||||
component="div"
|
||||
count={monitorCount}
|
||||
count={itemCount}
|
||||
page={page}
|
||||
onPageChange={handleChangePage}
|
||||
rowsPerPage={rowsPerPage}
|
||||
@@ -121,4 +123,4 @@ function Pagination({
|
||||
);
|
||||
}
|
||||
|
||||
export { Pagination };
|
||||
export default Pagination;
|
||||
@@ -82,11 +82,12 @@ const DataTable = ({ headers, data, config = { emptyView: "No data" } }) => {
|
||||
</TableRow>
|
||||
) : (
|
||||
data.map((row) => {
|
||||
const key = row.id || row._id || Math.random();
|
||||
return (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
key={key}
|
||||
sx={config?.rowSX ?? {}}
|
||||
onClick={() => config?.onRowClick(row)}
|
||||
onClick={config?.onRowClick ? () => config.onRowClick(row) : null}
|
||||
>
|
||||
{headers.map((header, index) => {
|
||||
return (
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { Pagination, PaginationItem, Typography, Box } from "@mui/material";
|
||||
import { Typography, Box } from "@mui/material";
|
||||
|
||||
import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
|
||||
import ArrowForwardRoundedIcon from "@mui/icons-material/ArrowForwardRounded";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { networkService } from "../../../main";
|
||||
@@ -16,7 +14,9 @@ import { HttpStatusLabel } from "../../../Components/HttpStatusLabel";
|
||||
import { Empty } from "./Empty/Empty";
|
||||
import { IncidentSkeleton } from "./Skeleton/Skeleton";
|
||||
import DataTable from "../../../Components/Table";
|
||||
const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
|
||||
import Pagination from "../../../Components/Table/TablePagination";
|
||||
|
||||
const IncidentTable = ({ monitors, selectedMonitor, filter, dateRange }) => {
|
||||
const uiTimezone = useSelector((state) => state.ui.timezone);
|
||||
|
||||
const theme = useTheme();
|
||||
@@ -24,19 +24,10 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
|
||||
const mode = useSelector((state) => state.ui.mode);
|
||||
const [checks, setChecks] = useState([]);
|
||||
const [checksCount, setChecksCount] = useState(0);
|
||||
const [paginationController, setPaginationController] = useState({
|
||||
page: 0,
|
||||
rowsPerPage: 14,
|
||||
});
|
||||
const [page, setPage] = useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(10);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setPaginationController((prevPaginationController) => ({
|
||||
...prevPaginationController,
|
||||
page: 0,
|
||||
}));
|
||||
}, [filter, selectedMonitor]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPage = async () => {
|
||||
if (!monitors || Object.keys(monitors).length === 0) {
|
||||
@@ -51,10 +42,10 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
|
||||
teamId: user.teamId,
|
||||
sortOrder: "desc",
|
||||
limit: null,
|
||||
dateRange: null,
|
||||
dateRange,
|
||||
filter: filter,
|
||||
page: paginationController.page,
|
||||
rowsPerPage: paginationController.rowsPerPage,
|
||||
page: page,
|
||||
rowsPerPage: rowsPerPage,
|
||||
});
|
||||
} else {
|
||||
res = await networkService.getChecksByMonitor({
|
||||
@@ -62,12 +53,13 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
|
||||
monitorId: selectedMonitor,
|
||||
sortOrder: "desc",
|
||||
limit: null,
|
||||
dateRange: null,
|
||||
dateRange,
|
||||
filter: filter,
|
||||
page: paginationController.page,
|
||||
rowsPerPage: paginationController.rowsPerPage,
|
||||
page,
|
||||
rowsPerPage,
|
||||
});
|
||||
}
|
||||
|
||||
setChecks(res.data.data.checks);
|
||||
setChecksCount(res.data.data.checksCount);
|
||||
} catch (error) {
|
||||
@@ -77,21 +69,14 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
|
||||
}
|
||||
};
|
||||
fetchPage();
|
||||
}, [
|
||||
authToken,
|
||||
user,
|
||||
monitors,
|
||||
selectedMonitor,
|
||||
filter,
|
||||
paginationController.page,
|
||||
paginationController.rowsPerPage,
|
||||
]);
|
||||
}, [authToken, user, monitors, selectedMonitor, filter, page, rowsPerPage, dateRange]);
|
||||
|
||||
const handlePageChange = (_, newPage) => {
|
||||
setPaginationController({
|
||||
...paginationController,
|
||||
page: newPage - 1, // 0-indexed
|
||||
});
|
||||
setPage(newPage);
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (event) => {
|
||||
setRowsPerPage(event.target.value);
|
||||
};
|
||||
|
||||
const headers = [
|
||||
@@ -134,28 +119,6 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
|
||||
{ id: "message", content: "Message", render: (row) => row.message },
|
||||
];
|
||||
|
||||
let paginationComponent = <></>;
|
||||
if (checksCount > paginationController.rowsPerPage) {
|
||||
paginationComponent = (
|
||||
<Pagination
|
||||
count={Math.ceil(checksCount / paginationController.rowsPerPage)}
|
||||
page={paginationController.page + 1} //0-indexed
|
||||
onChange={handlePageChange}
|
||||
shape="rounded"
|
||||
renderItem={(item) => (
|
||||
<PaginationItem
|
||||
slots={{
|
||||
previous: ArrowBackRoundedIcon,
|
||||
next: ArrowForwardRoundedIcon,
|
||||
}}
|
||||
{...item}
|
||||
/>
|
||||
)}
|
||||
sx={{ mt: "auto" }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let sharedStyles = {
|
||||
border: 1,
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
@@ -198,8 +161,14 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
|
||||
headers={headers}
|
||||
data={checks}
|
||||
/>
|
||||
|
||||
{paginationComponent}
|
||||
<Pagination
|
||||
paginationLabel="incidents"
|
||||
itemCount={checksCount}
|
||||
page={page}
|
||||
rowsPerPage={rowsPerPage}
|
||||
handleChangePage={handlePageChange}
|
||||
handleChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
@@ -210,6 +179,7 @@ IncidentTable.propTypes = {
|
||||
monitors: PropTypes.object.isRequired,
|
||||
selectedMonitor: PropTypes.string.isRequired,
|
||||
filter: PropTypes.string.isRequired,
|
||||
dateRange: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default IncidentTable;
|
||||
@@ -1,11 +0,0 @@
|
||||
.incidents h1.MuiTypography-root {
|
||||
font-size: var(--env-var-font-size-large);
|
||||
font-weight: 600;
|
||||
}
|
||||
.incidents button.MuiButtonBase-root,
|
||||
.incidents p.MuiTypography-root {
|
||||
font-size: var(--env-var-font-size-medium);
|
||||
}
|
||||
.incidents button.MuiButtonBase-root {
|
||||
min-height: 34px;
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import { useTheme } from "@emotion/react";
|
||||
import Select from "../../Components/Inputs/Select";
|
||||
import IncidentTable from "./IncidentTable";
|
||||
import SkeletonLayout from "./skeleton";
|
||||
import "./index.css";
|
||||
|
||||
const Incidents = () => {
|
||||
const theme = useTheme();
|
||||
@@ -21,6 +20,7 @@ const Incidents = () => {
|
||||
|
||||
// TODO do something with these filters
|
||||
const [filter, setFilter] = useState("all");
|
||||
const [dateRange, setDateRange] = useState("hour");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMonitors = async () => {
|
||||
@@ -77,62 +77,132 @@ const Incidents = () => {
|
||||
<>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
gap={theme.spacing(6)}
|
||||
>
|
||||
<Typography
|
||||
display="inline-block"
|
||||
component="h1"
|
||||
color={theme.palette.primary.contrastTextSecondary}
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(6)}
|
||||
>
|
||||
Incidents for
|
||||
</Typography>
|
||||
<Select
|
||||
id="incidents-select-monitor"
|
||||
placeholder="All servers"
|
||||
value={selectedMonitor}
|
||||
onChange={handleSelect}
|
||||
items={Object.values(monitors)}
|
||||
sx={{
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.contrastTextSecondary,
|
||||
}}
|
||||
/>
|
||||
<ButtonGroup
|
||||
sx={{
|
||||
ml: "auto",
|
||||
"& .MuiButtonBase-root, & .MuiButtonBase-root:hover": {
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
},
|
||||
}}
|
||||
<Typography
|
||||
display="inline-block"
|
||||
component="h1"
|
||||
color={theme.palette.primary.contrastTextSecondary}
|
||||
>
|
||||
Incidents for
|
||||
</Typography>
|
||||
<Select
|
||||
id="incidents-select-monitor"
|
||||
placeholder="All servers"
|
||||
value={selectedMonitor}
|
||||
onChange={handleSelect}
|
||||
items={Object.values(monitors)}
|
||||
sx={{
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.contrastTextSecondary,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(6)}
|
||||
>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(filter === "all").toString()}
|
||||
onClick={() => setFilter("all")}
|
||||
<Typography
|
||||
display="inline-block"
|
||||
component="h1"
|
||||
color={theme.palette.primary.contrastTextSecondary}
|
||||
>
|
||||
All
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(filter === "down").toString()}
|
||||
onClick={() => setFilter("down")}
|
||||
Filter by:
|
||||
</Typography>
|
||||
<ButtonGroup
|
||||
sx={{
|
||||
ml: "auto",
|
||||
"& .MuiButtonBase-root, & .MuiButtonBase-root:hover": {
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Down
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(filter === "resolve").toString()}
|
||||
onClick={() => setFilter("resolve")}
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(filter === "all").toString()}
|
||||
onClick={() => setFilter("all")}
|
||||
>
|
||||
All
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(filter === "down").toString()}
|
||||
onClick={() => setFilter("down")}
|
||||
>
|
||||
Down
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(filter === "resolve").toString()}
|
||||
onClick={() => setFilter("resolve")}
|
||||
>
|
||||
Cannot resolve
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(6)}
|
||||
>
|
||||
<Typography
|
||||
display="inline-block"
|
||||
component="h1"
|
||||
color={theme.palette.primary.contrastTextSecondary}
|
||||
>
|
||||
Cannot Resolve
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
Show:
|
||||
</Typography>
|
||||
<ButtonGroup
|
||||
sx={{
|
||||
ml: "auto",
|
||||
"& .MuiButtonBase-root, & .MuiButtonBase-root:hover": {
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "hour").toString()}
|
||||
onClick={() => setDateRange("hour")}
|
||||
>
|
||||
Last hour
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "day").toString()}
|
||||
onClick={() => setDateRange("day")}
|
||||
>
|
||||
Last day
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "week").toString()}
|
||||
onClick={() => setDateRange("week")}
|
||||
>
|
||||
Last week
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "all").toString()}
|
||||
onClick={() => setDateRange("all")}
|
||||
>
|
||||
All
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<IncidentTable
|
||||
monitors={monitors}
|
||||
selectedMonitor={selectedMonitor}
|
||||
filter={filter}
|
||||
dateRange={dateRange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { Box, Button, IconButton, Stack } from "@mui/material";
|
||||
import Breadcrumbs from "../../Components/Breadcrumbs";
|
||||
import { StatusLabel } from "../../Components/Label";
|
||||
import { Heading } from "../../Components/Heading";
|
||||
import { Pagination } from "../../Components/Table/TablePagination/index.jsx";
|
||||
import Pagination from "../../Components/Table/TablePagination/index.jsx";
|
||||
// import { getInfrastructureMonitorsByTeamId } from "../../Features/InfrastructureMonitors/infrastructureMonitorsSlice";
|
||||
import { networkService } from "../../Utils/NetworkService.js";
|
||||
import CustomGauge from "../../Components/Charts/CustomGauge/index.jsx";
|
||||
@@ -150,7 +150,7 @@ function Infrastructure() {
|
||||
|
||||
const monitorsAsRows = monitors.map((monitor) => {
|
||||
const processor =
|
||||
((monitor.checks[0]?.cpu?.usage_frequency ?? 0) / 1000).toFixed(2) + " GHz";
|
||||
((monitor.checks[0]?.cpu?.frequency ?? 0) / 1000).toFixed(2) + " GHz";
|
||||
const cpu = (monitor?.checks[0]?.cpu.usage_percent ?? 0) * 100;
|
||||
const mem = (monitor?.checks[0]?.memory.usage_percent ?? 0) * 100;
|
||||
const disk = (monitor?.checks[0]?.disk[0]?.usage_percent ?? 0) * 100;
|
||||
@@ -269,7 +269,8 @@ function Infrastructure() {
|
||||
/>
|
||||
|
||||
<Pagination
|
||||
monitorCount={summary?.totalMonitors ?? 0}
|
||||
itemCount={summary?.totalMonitors ?? 0}
|
||||
paginationLabel="monitors"
|
||||
page={page}
|
||||
rowsPerPage={rowsPerPage}
|
||||
handleChangePage={handleChangePage}
|
||||
|
||||
@@ -412,7 +412,7 @@ const CreateMaintenance = () => {
|
||||
backgroundColor: theme.palette.accent.light, // Hover background
|
||||
},
|
||||
"&.Mui-disabled": {
|
||||
color: theme.palette.primary.lowContrast, // Disabled day color
|
||||
color: theme.palette.primary.ContrastTextTertiary, // Disabled day color
|
||||
},
|
||||
},
|
||||
"& .MuiDayCalendar-weekDayLabel": {
|
||||
|
||||
@@ -1,34 +1,16 @@
|
||||
import PropTypes from "prop-types";
|
||||
import {
|
||||
TableContainer,
|
||||
Table,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableBody,
|
||||
Paper,
|
||||
Box,
|
||||
TablePagination,
|
||||
Stack,
|
||||
Typography,
|
||||
Button,
|
||||
} from "@mui/material";
|
||||
import { Box } from "@mui/material";
|
||||
import DataTable from "../../../Components/Table";
|
||||
import Pagination from "../../../Components/Table/TablePagination";
|
||||
import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded";
|
||||
import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded";
|
||||
import ActionsMenu from "./ActionsMenu";
|
||||
import { useState, useEffect, memo, useCallback, useRef } from "react";
|
||||
import { memo } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import LeftArrowDouble from "../../../assets/icons/left-arrow-double.svg?react";
|
||||
import RightArrowDouble from "../../../assets/icons/right-arrow-double.svg?react";
|
||||
import LeftArrow from "../../../assets/icons/left-arrow.svg?react";
|
||||
import RightArrow from "../../../assets/icons/right-arrow.svg?react";
|
||||
import SelectorVertical from "../../../assets/icons/selector-vertical.svg?react";
|
||||
import { formatDurationRounded } from "../../../Utils/timeUtils";
|
||||
import { StatusLabel } from "../../../Components/Label";
|
||||
import { setRowsPerPage } from "../../../Features/UI/uiSlice";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
/**
|
||||
* Component for pagination actions (first, previous, next, last).
|
||||
*
|
||||
@@ -41,65 +23,6 @@ import dayjs from "dayjs";
|
||||
*
|
||||
* @returns {JSX.Element} Pagination actions component.
|
||||
*/
|
||||
const TablePaginationActions = (props) => {
|
||||
const { count, page, rowsPerPage, onPageChange } = props;
|
||||
const handleFirstPageButtonClick = (event) => {
|
||||
onPageChange(event, 0);
|
||||
};
|
||||
const handleBackButtonClick = (event) => {
|
||||
onPageChange(event, page - 1);
|
||||
};
|
||||
const handleNextButtonClick = (event) => {
|
||||
onPageChange(event, page + 1);
|
||||
};
|
||||
const handleLastPageButtonClick = (event) => {
|
||||
onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1));
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ flexShrink: 0, ml: "24px" }}>
|
||||
<Button
|
||||
variant="group"
|
||||
onClick={handleFirstPageButtonClick}
|
||||
disabled={page === 0}
|
||||
aria-label="first page"
|
||||
>
|
||||
<LeftArrowDouble />
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
onClick={handleBackButtonClick}
|
||||
disabled={page === 0}
|
||||
aria-label="previous page"
|
||||
>
|
||||
<LeftArrow />
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
onClick={handleNextButtonClick}
|
||||
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
|
||||
aria-label="next page"
|
||||
>
|
||||
<RightArrow />
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
onClick={handleLastPageButtonClick}
|
||||
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
|
||||
aria-label="last page"
|
||||
>
|
||||
<RightArrowDouble />
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
TablePaginationActions.propTypes = {
|
||||
count: PropTypes.number.isRequired,
|
||||
page: PropTypes.number.isRequired,
|
||||
rowsPerPage: PropTypes.number.isRequired,
|
||||
onPageChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const MaintenanceTable = ({
|
||||
page,
|
||||
@@ -111,13 +34,102 @@ const MaintenanceTable = ({
|
||||
updateCallback,
|
||||
}) => {
|
||||
const { rowsPerPage } = useSelector((state) => state.ui.maintenance);
|
||||
const theme = useTheme();
|
||||
console.log(rowsPerPage);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleChangePage = (event, newPage) => {
|
||||
setPage(newPage);
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (event) => {
|
||||
dispatch(
|
||||
setRowsPerPage({
|
||||
value: parseInt(event.target.value, 10),
|
||||
table: "maintenance",
|
||||
})
|
||||
);
|
||||
setPage(0);
|
||||
};
|
||||
|
||||
const headers = [
|
||||
{
|
||||
id: "name",
|
||||
content: (
|
||||
<Box onClick={() => handleSort("name")}>
|
||||
Maintenance Window Name
|
||||
<span
|
||||
style={{
|
||||
visibility: sort.field === "name" ? "visible" : "hidden",
|
||||
}}
|
||||
>
|
||||
{sort.order === "asc" ? (
|
||||
<ArrowUpwardRoundedIcon />
|
||||
) : (
|
||||
<ArrowDownwardRoundedIcon />
|
||||
)}
|
||||
</span>
|
||||
</Box>
|
||||
),
|
||||
render: (row) => row.name,
|
||||
},
|
||||
{
|
||||
id: "status",
|
||||
content: (
|
||||
<Box onClick={() => handleSort("status")}>
|
||||
{" "}
|
||||
Status
|
||||
<span
|
||||
style={{
|
||||
visibility: sort.field === "active" ? "visible" : "hidden",
|
||||
}}
|
||||
>
|
||||
{sort.order === "asc" ? (
|
||||
<ArrowUpwardRoundedIcon />
|
||||
) : (
|
||||
<ArrowDownwardRoundedIcon />
|
||||
)}
|
||||
</span>
|
||||
</Box>
|
||||
),
|
||||
render: (row) => {
|
||||
const status = row.active ? "up" : "paused";
|
||||
const text = row.active ? "active" : "paused";
|
||||
|
||||
return (
|
||||
<StatusLabel
|
||||
status={status}
|
||||
text={text}
|
||||
customStyles={{ textTransform: "capitalize" }}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "nextWindow",
|
||||
content: "Next window",
|
||||
render: (row) => {
|
||||
return getTimeToNextWindow(row.start, row.end, row.repeat);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "repeat",
|
||||
content: "Repeat",
|
||||
render: (row) => {
|
||||
return row.repeat === 0 ? "N/A" : formatDurationRounded(row.repeat);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
content: "Actions",
|
||||
render: (row) => (
|
||||
<ActionsMenu
|
||||
maintenanceWindow={row}
|
||||
updateCallback={updateCallback}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const getTimeToNextWindow = (startTime, endTime, repeat) => {
|
||||
//1. Advance time closest to next window as possible
|
||||
const now = dayjs();
|
||||
@@ -153,26 +165,6 @@ const MaintenanceTable = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (event) => {
|
||||
dispatch(
|
||||
setRowsPerPage({
|
||||
value: parseInt(event.target.value, 10),
|
||||
table: "maintenance",
|
||||
})
|
||||
);
|
||||
setPage(0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to calculate the range of displayed rows.
|
||||
* @returns {string}
|
||||
*/
|
||||
const getRange = () => {
|
||||
let start = page * rowsPerPage + 1;
|
||||
let end = Math.min(page * rowsPerPage + rowsPerPage, maintenanceWindowCount);
|
||||
return `${start} - ${end}`;
|
||||
};
|
||||
|
||||
const handleSort = async (field) => {
|
||||
let order = "";
|
||||
if (sort.field !== field) {
|
||||
@@ -183,165 +175,21 @@ const MaintenanceTable = ({
|
||||
setSort({ field, order });
|
||||
};
|
||||
|
||||
console.log(handleChangePage);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell
|
||||
sx={{ cursor: "pointer" }}
|
||||
onClick={() => handleSort("name")}
|
||||
>
|
||||
<Box>
|
||||
Maintenance Window Name
|
||||
<span
|
||||
style={{
|
||||
visibility: sort.field === "name" ? "visible" : "hidden",
|
||||
}}
|
||||
>
|
||||
{sort.order === "asc" ? (
|
||||
<ArrowUpwardRoundedIcon />
|
||||
) : (
|
||||
<ArrowDownwardRoundedIcon />
|
||||
)}
|
||||
</span>
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
sx={{ cursor: "pointer" }}
|
||||
onClick={() => handleSort("status")}
|
||||
>
|
||||
{" "}
|
||||
<Box width="max-content">
|
||||
{" "}
|
||||
Status
|
||||
<span
|
||||
style={{
|
||||
visibility: sort.field === "active" ? "visible" : "hidden",
|
||||
}}
|
||||
>
|
||||
{sort.order === "asc" ? (
|
||||
<ArrowUpwardRoundedIcon />
|
||||
) : (
|
||||
<ArrowDownwardRoundedIcon />
|
||||
)}
|
||||
</span>
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell>Next Window</TableCell>
|
||||
<TableCell>Repeat</TableCell>
|
||||
<TableCell>Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{maintenanceWindows.map((maintenanceWindow) => {
|
||||
const text = maintenanceWindow.active ? "active" : "paused";
|
||||
const status = maintenanceWindow.active ? "up" : "paused";
|
||||
return (
|
||||
<TableRow key={maintenanceWindow._id}>
|
||||
<TableCell>{maintenanceWindow.name}</TableCell>
|
||||
<TableCell>
|
||||
<StatusLabel
|
||||
status={status}
|
||||
text={text}
|
||||
customStyles={{ textTransform: "capitalize" }}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{getTimeToNextWindow(
|
||||
maintenanceWindow.start,
|
||||
maintenanceWindow.end,
|
||||
maintenanceWindow.repeat
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{maintenanceWindow.repeat === 0
|
||||
? "N/A"
|
||||
: formatDurationRounded(maintenanceWindow.repeat)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<ActionsMenu
|
||||
maintenanceWindow={maintenanceWindow}
|
||||
updateCallback={updateCallback}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
px={theme.spacing(4)}
|
||||
>
|
||||
<Typography
|
||||
px={theme.spacing(2)}
|
||||
variant="body2"
|
||||
sx={{ opacity: 0.7 }}
|
||||
>
|
||||
Showing {getRange()} of {maintenanceWindowCount} maintenance window(s)
|
||||
</Typography>
|
||||
<TablePagination
|
||||
component="div"
|
||||
count={maintenanceWindowCount}
|
||||
page={page}
|
||||
onPageChange={handleChangePage}
|
||||
rowsPerPage={rowsPerPage}
|
||||
rowsPerPageOptions={[5, 10, 15, 25]}
|
||||
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||
ActionsComponent={TablePaginationActions}
|
||||
labelRowsPerPage="Rows per page"
|
||||
labelDisplayedRows={({ page, count }) =>
|
||||
`Page ${page + 1} of ${Math.max(0, Math.ceil(count / rowsPerPage))}`
|
||||
}
|
||||
slotProps={{
|
||||
select: {
|
||||
MenuProps: {
|
||||
keepMounted: true,
|
||||
disableScrollLock: true,
|
||||
PaperProps: {
|
||||
className: "pagination-dropdown",
|
||||
sx: {
|
||||
mt: 0,
|
||||
mb: theme.spacing(2),
|
||||
},
|
||||
},
|
||||
transformOrigin: { vertical: "bottom", horizontal: "left" },
|
||||
anchorOrigin: { vertical: "top", horizontal: "left" },
|
||||
sx: { mt: theme.spacing(-2) },
|
||||
},
|
||||
inputProps: { id: "pagination-dropdown" },
|
||||
IconComponent: SelectorVertical,
|
||||
sx: {
|
||||
ml: theme.spacing(4),
|
||||
mr: theme.spacing(12),
|
||||
minWidth: theme.spacing(20),
|
||||
textAlign: "left",
|
||||
"&.Mui-focused > div": {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
sx={{
|
||||
mt: theme.spacing(6),
|
||||
color: theme.palette.primary.contrastTextSecondary,
|
||||
"& svg path": {
|
||||
stroke: theme.palette.primary.contrastTextTertiary,
|
||||
strokeWidth: 1.3,
|
||||
},
|
||||
"& .MuiSelect-select": {
|
||||
border: 1,
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
<DataTable
|
||||
headers={headers}
|
||||
data={maintenanceWindows}
|
||||
/>
|
||||
<Pagination
|
||||
itemCount={maintenanceWindowCount}
|
||||
page={page}
|
||||
rowsPerPage={rowsPerPage}
|
||||
handleChangePage={handleChangePage}
|
||||
handleChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -108,16 +108,16 @@ const CreateMonitor = () => {
|
||||
}
|
||||
|
||||
if (monitor.type === "http") {
|
||||
const checkEndpointAction = await dispatch(
|
||||
checkEndpointResolution({ authToken, monitorURL: form.url })
|
||||
);
|
||||
if (checkEndpointAction.meta.requestStatus === "rejected") {
|
||||
createToast({
|
||||
body: "The endpoint you entered doesn't resolve. Check the URL again.",
|
||||
});
|
||||
setErrors({ url: "The entered URL is not reachable." });
|
||||
return;
|
||||
}
|
||||
// const checkEndpointAction = await dispatch(
|
||||
// checkEndpointResolution({ authToken, monitorURL: form.url })
|
||||
// );
|
||||
// if (checkEndpointAction.meta.requestStatus === "rejected") {
|
||||
// createToast({
|
||||
// body: "The endpoint you entered doesn't resolve. Check the URL again.",
|
||||
// });
|
||||
// setErrors({ url: "The entered URL is not reachable." });
|
||||
// return;
|
||||
// }
|
||||
}
|
||||
|
||||
form = {
|
||||
|
||||
@@ -5,7 +5,7 @@ import SkeletonLayout from "./skeleton";
|
||||
import Fallback from "./fallback";
|
||||
import StatusBox from "./StatusBox";
|
||||
import UptimeDataTable from "./UptimeDataTable";
|
||||
import { Pagination } from "../../../Components/Table/TablePagination";
|
||||
import Pagination from "../../../Components/Table/TablePagination";
|
||||
|
||||
// Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
@@ -225,7 +225,8 @@ const UptimeMonitors = () => {
|
||||
triggerUpdate={triggerUpdate}
|
||||
/>
|
||||
<Pagination
|
||||
monitorCount={totalMonitors}
|
||||
itemCount={totalMonitors}
|
||||
paginationLabel="monitors"
|
||||
page={page}
|
||||
rowsPerPage={rowsPerPage}
|
||||
handleChangePage={handleChangePage}
|
||||
|
||||
@@ -103,11 +103,6 @@ const monitorValidation = joi.object({
|
||||
"(?:(?:https?|ftp):\\/\\/)?" +
|
||||
// user:pass BasicAuth (optional)
|
||||
"(?:" +
|
||||
// IP address exclusion
|
||||
// private & local networks
|
||||
"(?!(?:10|127)(?:\\.\\d{1,3}){3})" +
|
||||
"(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})" +
|
||||
"(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})" +
|
||||
// IP address dotted notation octets
|
||||
// excludes loopback network 0.0.0.0
|
||||
// excludes reserved space >= 224.0.0.0
|
||||
|
||||
@@ -44,7 +44,7 @@ class CheckController {
|
||||
}
|
||||
};
|
||||
|
||||
getChecks = async (req, res, next) => {
|
||||
getChecksByMonitor = async (req, res, next) => {
|
||||
try {
|
||||
await getChecksParamValidation.validateAsync(req.params);
|
||||
await getChecksQueryValidation.validateAsync(req.query);
|
||||
@@ -54,19 +54,18 @@ class CheckController {
|
||||
}
|
||||
|
||||
try {
|
||||
const checks = await this.db.getChecks(req);
|
||||
const checksCount = await this.db.getChecksCount(req);
|
||||
const result = await this.db.getChecksByMonitor(req);
|
||||
|
||||
return res.success({
|
||||
msg: successMessages.CHECK_GET,
|
||||
data: { checksCount, checks },
|
||||
data: result,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "getChecks"));
|
||||
}
|
||||
};
|
||||
|
||||
getTeamChecks = async (req, res, next) => {
|
||||
getChecksByTeam = async (req, res, next) => {
|
||||
try {
|
||||
await getTeamChecksParamValidation.validateAsync(req.params);
|
||||
await getTeamChecksQueryValidation.validateAsync(req.query);
|
||||
@@ -75,7 +74,7 @@ class CheckController {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const checkData = await this.db.getTeamChecks(req);
|
||||
const checkData = await this.db.getChecksByTeam(req);
|
||||
|
||||
return res.success({
|
||||
msg: successMessages.CHECK_GET,
|
||||
|
||||
@@ -20,7 +20,7 @@ import { getTokenFromHeaders } from "../utils/utils.js";
|
||||
import logger from "../utils/logger.js";
|
||||
import { handleError, handleValidationError } from "./controllerUtils.js";
|
||||
import axios from "axios";
|
||||
|
||||
import seedDb from "../db/mongo/utils/seedDb.js";
|
||||
const SERVICE_NAME = "monitorController";
|
||||
|
||||
class MonitorController {
|
||||
@@ -495,6 +495,17 @@ class MonitorController {
|
||||
next(handleError(error, SERVICE_NAME, "getMonitorsByTeamId"));
|
||||
}
|
||||
};
|
||||
|
||||
seedDb = async (req, res, next) => {
|
||||
try {
|
||||
const token = getTokenFromHeaders(req.headers);
|
||||
const { jwtSecret } = this.settingsService.getSettings();
|
||||
const { _id, teamId } = jwt.verify(token, jwtSecret);
|
||||
await seedDb(_id, teamId);
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "seedDb"));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default MonitorController;
|
||||
@@ -12,6 +12,13 @@ const BaseCheckSchema = mongoose.Schema({
|
||||
immutable: true,
|
||||
index: true,
|
||||
},
|
||||
|
||||
teamId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "Team",
|
||||
immutable: true,
|
||||
index: true,
|
||||
},
|
||||
/**
|
||||
* Status of the check (true for up, false for down).
|
||||
*
|
||||
@@ -69,6 +76,8 @@ const CheckSchema = mongoose.Schema({ ...BaseCheckSchema.obj }, { timestamps: tr
|
||||
CheckSchema.index({ createdAt: 1 });
|
||||
CheckSchema.index({ monitorId: 1, createdAt: 1 });
|
||||
CheckSchema.index({ monitorId: 1, createdAt: -1 });
|
||||
CheckSchema.index({ teamId: 1, createdAt: -1 });
|
||||
CheckSchema.index({ teamId: 1 });
|
||||
|
||||
export default mongoose.model("Check", CheckSchema);
|
||||
export { BaseCheckSchema };
|
||||
@@ -2,11 +2,15 @@ import Check from "../../models/Check.js";
|
||||
import Monitor from "../../models/Monitor.js";
|
||||
import User from "../../models/User.js";
|
||||
import logger from "../../../utils/logger.js";
|
||||
import { ObjectId } from "mongodb";
|
||||
|
||||
const SERVICE_NAME = "checkModule";
|
||||
const dateRangeLookup = {
|
||||
hour: new Date(new Date().setHours(new Date().getHours() - 1)),
|
||||
day: new Date(new Date().setDate(new Date().getDate() - 1)),
|
||||
week: new Date(new Date().setDate(new Date().getDate() - 7)),
|
||||
month: new Date(new Date().setMonth(new Date().getMonth() - 1)),
|
||||
all: undefined,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -56,41 +60,6 @@ const createCheck = async (checkData) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getChecksCount = async (req) => {
|
||||
const monitorId = req.params.monitorId;
|
||||
const dateRange = req.query.dateRange;
|
||||
const filter = req.query.filter;
|
||||
// Build query
|
||||
const checksQuery = { monitorId: monitorId };
|
||||
// Filter checks by "day", "week", or "month"
|
||||
if (dateRange !== undefined) {
|
||||
checksQuery.createdAt = { $gte: dateRangeLookup[dateRange] };
|
||||
}
|
||||
|
||||
if (filter !== undefined) {
|
||||
checksQuery.status = false;
|
||||
switch (filter) {
|
||||
case "all":
|
||||
break;
|
||||
case "down":
|
||||
break;
|
||||
case "resolve":
|
||||
checksQuery.statusCode = 5000;
|
||||
break;
|
||||
default:
|
||||
logger.warn({
|
||||
message: "invalid filter",
|
||||
service: SERVICE_NAME,
|
||||
method: "getChecksCount",
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const count = await Check.countDocuments(checksQuery);
|
||||
return count;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all checks for a monitor
|
||||
* @async
|
||||
@@ -98,33 +67,31 @@ const getChecksCount = async (req) => {
|
||||
* @returns {Promise<Array<Check>>}
|
||||
* @throws {Error}
|
||||
*/
|
||||
|
||||
const getChecks = async (req) => {
|
||||
const getChecksByMonitor = async (req) => {
|
||||
try {
|
||||
const { monitorId } = req.params;
|
||||
let { sortOrder, limit, dateRange, filter, page, rowsPerPage } = req.query;
|
||||
// Default limit to 0 if not provided
|
||||
limit = limit === undefined ? 0 : limit;
|
||||
let { sortOrder, dateRange, filter, page, rowsPerPage } = req.query;
|
||||
page = parseInt(page);
|
||||
rowsPerPage = parseInt(rowsPerPage);
|
||||
// Match
|
||||
const matchStage = {
|
||||
monitorId: ObjectId.createFromHexString(monitorId),
|
||||
status: false,
|
||||
...(dateRangeLookup[dateRange] && {
|
||||
createdAt: {
|
||||
$gte: dateRangeLookup[dateRange],
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
// Default sort order is newest -> oldest
|
||||
sortOrder = sortOrder === "asc" ? 1 : -1;
|
||||
|
||||
// Build query
|
||||
const checksQuery = { monitorId: monitorId };
|
||||
// Filter checks by "day", "week", or "month"
|
||||
if (dateRange !== undefined) {
|
||||
checksQuery.createdAt = { $gte: dateRangeLookup[dateRange] };
|
||||
}
|
||||
// Filter checks by status
|
||||
if (filter !== undefined) {
|
||||
checksQuery.status = false;
|
||||
switch (filter) {
|
||||
case "all":
|
||||
break;
|
||||
case "down":
|
||||
break;
|
||||
case "resolve":
|
||||
checksQuery.statusCode = 5000;
|
||||
matchStage.statusCode = 5000;
|
||||
break;
|
||||
default:
|
||||
logger.warn({
|
||||
@@ -136,16 +103,33 @@ const getChecks = async (req) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Need to skip and limit here
|
||||
//Sort
|
||||
sortOrder = sortOrder === "asc" ? 1 : -1;
|
||||
|
||||
// Pagination
|
||||
let skip = 0;
|
||||
if (page && rowsPerPage) {
|
||||
skip = page * rowsPerPage;
|
||||
}
|
||||
const checks = await Check.find(checksQuery)
|
||||
.skip(skip)
|
||||
.limit(rowsPerPage)
|
||||
.sort({ createdAt: sortOrder });
|
||||
return checks;
|
||||
|
||||
const checks = await Check.aggregate([
|
||||
{ $match: matchStage },
|
||||
{ $sort: { createdAt: sortOrder } },
|
||||
{
|
||||
$facet: {
|
||||
summary: [{ $count: "checksCount" }],
|
||||
checks: [{ $skip: skip }, { $limit: rowsPerPage }],
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
checksCount: { $arrayElemAt: ["$summary.checksCount", 0] },
|
||||
checks: "$checks",
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
return checks[0];
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "getChecks";
|
||||
@@ -153,64 +137,71 @@ const getChecks = async (req) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getTeamChecks = async (req) => {
|
||||
const getChecksByTeam = async (req) => {
|
||||
try {
|
||||
let { sortOrder, dateRange, filter, page, rowsPerPage } = req.query;
|
||||
page = parseInt(page);
|
||||
rowsPerPage = parseInt(rowsPerPage);
|
||||
const { teamId } = req.params;
|
||||
let { sortOrder, limit, dateRange, filter, page, rowsPerPage } = req.query;
|
||||
|
||||
// Get monitorIDs
|
||||
const userMonitors = await Monitor.find({ teamId: teamId }).select("_id");
|
||||
|
||||
//Build check query
|
||||
// Default limit to 0 if not provided
|
||||
limit = limit === undefined ? 0 : limit;
|
||||
// Default sort order is newest -> oldest
|
||||
sortOrder = sortOrder === "asc" ? 1 : -1;
|
||||
|
||||
const checksQuery = { monitorId: { $in: userMonitors } };
|
||||
|
||||
const matchStage = {
|
||||
teamId: ObjectId.createFromHexString(teamId),
|
||||
status: false,
|
||||
...(dateRangeLookup[dateRange] && {
|
||||
createdAt: {
|
||||
$gte: dateRangeLookup[dateRange],
|
||||
},
|
||||
}),
|
||||
};
|
||||
// Add filter to match stage
|
||||
if (filter !== undefined) {
|
||||
checksQuery.status = false;
|
||||
switch (filter) {
|
||||
case "all":
|
||||
break;
|
||||
case "down":
|
||||
checksQuery.status = false;
|
||||
break;
|
||||
case "resolve":
|
||||
checksQuery.statusCode = 5000;
|
||||
matchStage.statusCode = 5000;
|
||||
break;
|
||||
default:
|
||||
logger.warn({
|
||||
message: "invalid filter",
|
||||
service: SERVICE_NAME,
|
||||
method: "getTeamChecks",
|
||||
method: "getChecksByTeam",
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (dateRange !== undefined) {
|
||||
checksQuery.createdAt = { $gte: dateRangeLookup[dateRange] };
|
||||
}
|
||||
sortOrder = sortOrder === "asc" ? 1 : -1;
|
||||
|
||||
// Skip and limit for pagination
|
||||
// pagination
|
||||
let skip = 0;
|
||||
if (page && rowsPerPage) {
|
||||
skip = page * rowsPerPage;
|
||||
}
|
||||
|
||||
const checksCount = await Check.countDocuments(checksQuery);
|
||||
const checks = await Check.aggregate([
|
||||
{ $match: matchStage },
|
||||
{ $sort: { createdAt: sortOrder } },
|
||||
|
||||
const checks = await Check.find(checksQuery)
|
||||
.skip(skip)
|
||||
.limit(rowsPerPage)
|
||||
.sort({ createdAt: sortOrder })
|
||||
.select(["monitorId", "status", "responseTime", "statusCode", "message"]);
|
||||
return { checksCount, checks };
|
||||
{
|
||||
$facet: {
|
||||
summary: [{ $count: "checksCount" }],
|
||||
checks: [{ $skip: skip }, { $limit: rowsPerPage }],
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
checksCount: { $arrayElemAt: ["$summary.checksCount", 0] },
|
||||
checks: "$checks",
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
return checks[0];
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "getTeamChecks";
|
||||
error.method = "getChecksByTeam";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -298,9 +289,8 @@ const updateChecksTTL = async (teamId, ttl) => {
|
||||
|
||||
export {
|
||||
createCheck,
|
||||
getChecksCount,
|
||||
getChecks,
|
||||
getTeamChecks,
|
||||
getChecksByMonitor,
|
||||
getChecksByTeam,
|
||||
deleteChecks,
|
||||
deleteChecksByTeamId,
|
||||
updateChecksTTL,
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import Monitor from "../../models/Monitor.js";
|
||||
import Check from "../../models/Check.js";
|
||||
|
||||
const generateRandomUrl = () => {
|
||||
const domains = ["example.com", "test.org", "demo.net", "sample.io", "mock.dev"];
|
||||
const paths = ["api", "status", "health", "ping", "check"];
|
||||
return `https://${domains[Math.floor(Math.random() * domains.length)]}/${paths[Math.floor(Math.random() * paths.length)]}`;
|
||||
};
|
||||
|
||||
const generateChecks = (monitorId, teamId, count) => {
|
||||
const checks = [];
|
||||
const endTime = new Date(Date.now() - 10 * 60 * 1000); // 10 minutes ago
|
||||
const startTime = new Date(endTime - count * 60 * 1000); // count minutes before endTime
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const timestamp = new Date(startTime.getTime() + i * 60 * 1000);
|
||||
const status = Math.random() > 0.05; // 95% chance of being up
|
||||
|
||||
checks.push({
|
||||
monitorId,
|
||||
teamId,
|
||||
status,
|
||||
responseTime: Math.floor(Math.random() * 1000), // Random response time between 0-1000ms
|
||||
createdAt: timestamp,
|
||||
updatedAt: timestamp,
|
||||
});
|
||||
}
|
||||
|
||||
return checks;
|
||||
};
|
||||
|
||||
const seedDb = async (userId, teamId) => {
|
||||
try {
|
||||
console.log("Deleting all monitors and checks");
|
||||
await Monitor.deleteMany({});
|
||||
await Check.deleteMany({});
|
||||
console.log("Adding monitors");
|
||||
for (let i = 0; i < 300; i++) {
|
||||
const monitor = await Monitor.create({
|
||||
name: `Monitor ${i}`,
|
||||
url: generateRandomUrl(),
|
||||
type: "http",
|
||||
userId,
|
||||
teamId,
|
||||
interval: 60000,
|
||||
active: false,
|
||||
});
|
||||
console.log(`Adding monitor and checks for monitor ${i}`);
|
||||
const checks = generateChecks(monitor._id, teamId, 10000);
|
||||
await Check.insertMany(checks);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
export default seedDb;
|
||||
Generated
+14
-14
@@ -11,7 +11,7 @@
|
||||
"dependencies": {
|
||||
"axios": "^1.7.2",
|
||||
"bcrypt": "5.1.1",
|
||||
"bullmq": "5.35.0",
|
||||
"bullmq": "5.37.0",
|
||||
"cors": "^2.8.5",
|
||||
"dockerode": "4.0.4",
|
||||
"dotenv": "^16.4.5",
|
||||
@@ -324,9 +324,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "9.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz",
|
||||
"integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==",
|
||||
"version": "9.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz",
|
||||
"integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1748,9 +1748,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/bullmq": {
|
||||
"version": "5.35.0",
|
||||
"resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.35.0.tgz",
|
||||
"integrity": "sha512-4wOFPv5oVkcK5A11cwsk5Zzc7Hz7xFe2rHIQfsg0UmWjtwTj/nc7+vPjcMGML6jdl7WejuHXnMeop4uxxo/3WA==",
|
||||
"version": "5.37.0",
|
||||
"resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.37.0.tgz",
|
||||
"integrity": "sha512-h/wf979+9uROyYpB8oTE44Py6JERCluCSd+ZFpCZlPsYh+wxAkqrfHsHHHKBgsNJp9odWLIY4SG+280EXzXiCQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cron-parser": "^4.9.0",
|
||||
@@ -3071,9 +3071,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "9.18.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz",
|
||||
"integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==",
|
||||
"version": "9.19.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz",
|
||||
"integrity": "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3082,7 +3082,7 @@
|
||||
"@eslint/config-array": "^0.19.0",
|
||||
"@eslint/core": "^0.10.0",
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
"@eslint/js": "9.18.0",
|
||||
"@eslint/js": "9.19.0",
|
||||
"@eslint/plugin-kit": "^0.2.5",
|
||||
"@humanfs/node": "^0.16.6",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
@@ -5848,9 +5848,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nodemailer": {
|
||||
"version": "6.9.16",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz",
|
||||
"integrity": "sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==",
|
||||
"version": "6.10.0",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.0.tgz",
|
||||
"integrity": "sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA==",
|
||||
"license": "MIT-0",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
"dependencies": {
|
||||
"axios": "^1.7.2",
|
||||
"bcrypt": "5.1.1",
|
||||
"bullmq": "5.35.0",
|
||||
"bullmq": "5.37.0",
|
||||
"cors": "^2.8.5",
|
||||
"dockerode": "4.0.4",
|
||||
"dotenv": "^16.4.5",
|
||||
|
||||
@@ -11,7 +11,7 @@ class CheckRoutes {
|
||||
}
|
||||
|
||||
initRoutes() {
|
||||
this.router.get("/:monitorId", this.checkController.getChecks);
|
||||
this.router.get("/:monitorId", this.checkController.getChecksByMonitor);
|
||||
this.router.post(
|
||||
"/:monitorId",
|
||||
verifyOwnership(Monitor, "monitorId"),
|
||||
@@ -23,7 +23,7 @@ class CheckRoutes {
|
||||
this.checkController.deleteChecks
|
||||
);
|
||||
|
||||
this.router.get("/team/:teamId", this.checkController.getTeamChecks);
|
||||
this.router.get("/team/:teamId", this.checkController.getChecksByTeam);
|
||||
|
||||
this.router.delete(
|
||||
"/team/:teamId",
|
||||
|
||||
@@ -75,6 +75,8 @@ class MonitorRoutes {
|
||||
isAllowed(["admin", "superadmin"]),
|
||||
this.monitorController.addDemoMonitors
|
||||
);
|
||||
|
||||
this.router.post("/seed", isAllowed(["superadmin"]), this.monitorController.seedDb);
|
||||
}
|
||||
|
||||
getRouter() {
|
||||
|
||||
@@ -5,6 +5,7 @@ const QUEUE_LOOKUP = {
|
||||
hardware: "hardware",
|
||||
http: "uptime",
|
||||
ping: "uptime",
|
||||
port: "uptime",
|
||||
docker: "uptime",
|
||||
pagespeed: "pagespeed",
|
||||
};
|
||||
|
||||
@@ -131,6 +131,7 @@ class NetworkService {
|
||||
|
||||
const httpResponse = {
|
||||
monitorId: job.data._id,
|
||||
teamId: job.data.teamId,
|
||||
type: job.data.type,
|
||||
responseTime,
|
||||
payload: response?.data,
|
||||
|
||||
@@ -83,10 +83,11 @@ class StatusService {
|
||||
* @returns {Object} The check object.
|
||||
*/
|
||||
buildCheck = (networkResponse) => {
|
||||
const { monitorId, type, status, responseTime, code, message, payload } =
|
||||
const { monitorId, teamId, type, status, responseTime, code, message, payload } =
|
||||
networkResponse;
|
||||
const check = {
|
||||
monitorId,
|
||||
teamId,
|
||||
status,
|
||||
statusCode: code,
|
||||
responseTime,
|
||||
|
||||
+10
-1259
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -131,7 +131,7 @@ const getMonitorByIdQueryValidation = joi.object({
|
||||
status: joi.boolean(),
|
||||
sortOrder: joi.string().valid("asc", "desc"),
|
||||
limit: joi.number(),
|
||||
dateRange: joi.string().valid("day", "week", "month"),
|
||||
dateRange: joi.string().valid("hour", "day", "week", "month", "all"),
|
||||
numToDisplay: joi.number(),
|
||||
normalize: joi.boolean(),
|
||||
});
|
||||
@@ -166,7 +166,7 @@ const getMonitorStatsByIdQueryValidation = joi.object({
|
||||
status: joi.string(),
|
||||
limit: joi.number(),
|
||||
sortOrder: joi.string().valid("asc", "desc"),
|
||||
dateRange: joi.string().valid("day", "week", "month", "all"),
|
||||
dateRange: joi.string().valid("hour", "day", "week", "month", "all"),
|
||||
numToDisplay: joi.number(),
|
||||
normalize: joi.boolean(),
|
||||
});
|
||||
@@ -217,7 +217,7 @@ const getHardwareDetailsByIdParamValidation = joi.object({
|
||||
});
|
||||
|
||||
const getHardwareDetailsByIdQueryValidation = joi.object({
|
||||
dateRange: joi.string().valid("day", "week", "month"),
|
||||
dateRange: joi.string().valid("hour", "day", "week", "month", "all"),
|
||||
});
|
||||
|
||||
//****************************************
|
||||
@@ -288,7 +288,7 @@ const getChecksParamValidation = joi.object({
|
||||
const getChecksQueryValidation = joi.object({
|
||||
sortOrder: joi.string().valid("asc", "desc"),
|
||||
limit: joi.number(),
|
||||
dateRange: joi.string().valid("day", "week", "month"),
|
||||
dateRange: joi.string().valid("hour", "day", "week", "month", "all"),
|
||||
filter: joi.string().valid("all", "down", "resolve"),
|
||||
page: joi.number(),
|
||||
rowsPerPage: joi.number(),
|
||||
@@ -301,7 +301,7 @@ const getTeamChecksParamValidation = joi.object({
|
||||
const getTeamChecksQueryValidation = joi.object({
|
||||
sortOrder: joi.string().valid("asc", "desc"),
|
||||
limit: joi.number(),
|
||||
dateRange: joi.string().valid("day", "week", "month"),
|
||||
dateRange: joi.string().valid("hour", "day", "week", "month", "all"),
|
||||
filter: joi.string().valid("all", "down", "resolve"),
|
||||
page: joi.number(),
|
||||
rowsPerPage: joi.number(),
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 301 KiB |
@@ -0,0 +1,35 @@
|
||||
---
|
||||
icon: clock-nine
|
||||
---
|
||||
|
||||
# Maintenance mode
|
||||
|
||||
The Maintenance mode allows you to manage and schedule maintenance periods for monitored URLs. During a maintenance window, no pings or HTTP requests are sent to the specified URLs, preventing downtime alerts during scheduled maintenance activities.
|
||||
|
||||
### Creating a new maintenance window
|
||||
|
||||
When you click the **"Create Maintenance Window"** button, you will see a page similar to this:
|
||||
|
||||
<figure><img src="../.gitbook/assets/Screenshot 2024-10-10 at 10.37.36 PM.png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
Here's how to set up a new maintenance window:
|
||||
|
||||
**General settings:**
|
||||
|
||||
* **Maintenance repeat**: Select how often the maintenance window should repeat (e.g., don't repeat, daily, weekly).
|
||||
* **Date**: Select the date the maintenance window should start.
|
||||
* **Start time**: Set the time of day the window begins. 
|
||||
* **Duration**: Specify how long the maintenance window should last (in seconds, minutes, or hours).
|
||||
|
||||
**Monitor related settings:**
|
||||
|
||||
* **Friendly Name**: Provide a descriptive name for the maintenance window (e.g., "Maintenance at 10:37 PM for 30 minutes").
|
||||
* **Add Monitors**: Select the specific monitors (URLs) for which this maintenance window will apply by typing and selecting from the list.
|
||||
|
||||
Once all fields are filled, click **Create maintenance** to save the new window.
|
||||
|
||||
### Editing a maintenance window
|
||||
|
||||
* To edit an existing window, click the gear icon under **Actions** in the table view.
|
||||
* Update the required fields and save your changes.
|
||||
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "bluewave-uptime",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
||||
Reference in new issue
Block a user