diff --git a/client/src/Hooks/checkHooks.js b/client/src/Hooks/checkHooks.js
index 98fb0cfd5..a7dae8b9a 100644
--- a/client/src/Hooks/checkHooks.js
+++ b/client/src/Hooks/checkHooks.js
@@ -122,4 +122,30 @@ const useFetchChecksByMonitor = ({
return [checks, checksCount, isLoading, networkError];
};
-export { useFetchChecksByMonitor, useFetchChecksTeam };
+const useFetchChecksSummaryByTeamId = () => {
+ const [summary, setSummary] = useState(undefined);
+ const [isLoading, setIsLoading] = useState(false);
+ const [networkError, setNetworkError] = useState(false);
+
+ useEffect(() => {
+ const fetchSummary = async () => {
+ try {
+ setIsLoading(true);
+
+ const res = await networkService.getChecksAndSummaryByTeamId();
+ setSummary(res.data.data);
+ } catch (error) {
+ setNetworkError(true);
+ createToast({ body: error.message });
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ fetchSummary();
+ }, []);
+
+ return [summary, isLoading, networkError];
+};
+
+export { useFetchChecksByMonitor, useFetchChecksTeam, useFetchChecksSummaryByTeamId };
diff --git a/client/src/Pages/Incidents/Components/OptionsHeader/index.jsx b/client/src/Pages/Incidents/Components/OptionsHeader/index.jsx
index 457c6fd96..08a9bcf1c 100644
--- a/client/src/Pages/Incidents/Components/OptionsHeader/index.jsx
+++ b/client/src/Pages/Incidents/Components/OptionsHeader/index.jsx
@@ -87,13 +87,13 @@ const OptionsHeader = ({
>
{t("incidentsOptionsHeaderFilterCannotResolve")}
- {/* */}
+
diff --git a/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx b/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx
new file mode 100644
index 000000000..b19f017e7
--- /dev/null
+++ b/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx
@@ -0,0 +1,116 @@
+import PropTypes from "prop-types";
+import { useTheme } from "@emotion/react";
+import { Box, Stack, Typography } from "@mui/material";
+import Background from "../../../../assets/Images/background-grid.svg?react";
+import MonitorHeartOutlinedIcon from "@mui/icons-material/MonitorHeartOutlined";
+import TaskAltOutlinedIcon from "@mui/icons-material/TaskAltOutlined";
+import CancelOutlinedIcon from "@mui/icons-material/CancelOutlined";
+import WarningAmberRoundedIcon from "@mui/icons-material/WarningAmberRounded";
+
+const StatusBox = ({ title, value, status }) => {
+ const theme = useTheme();
+ let sharedStyles = {
+ position: "absolute",
+ right: 8,
+ opacity: 0.5,
+ "& svg path": { stroke: theme.palette.primary.contrastTextTertiary },
+ };
+
+ let color;
+ let icon;
+ if (status === "up") {
+ color = theme.palette.success.lowContrast;
+ icon = (
+
+
+
+ );
+ } else if (status === "down") {
+ color = theme.palette.error.lowContrast;
+ icon = (
+
+
+
+ );
+ } else if (status === "paused") {
+ color = theme.palette.warning.lowContrast;
+ icon = (
+
+
+
+ );
+ } else {
+ color = theme.palette.accent.main;
+ icon = (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+ {title}
+
+ {icon}
+
+
+ {value}
+
+
+ #
+
+
+
+
+ );
+};
+
+StatusBox.propTypes = {
+ title: PropTypes.string.isRequired,
+ value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ status: PropTypes.string,
+};
+
+export default StatusBox;
diff --git a/client/src/Pages/Incidents/Components/StatusBoxes/index.jsx b/client/src/Pages/Incidents/Components/StatusBoxes/index.jsx
new file mode 100644
index 000000000..6ba2f1820
--- /dev/null
+++ b/client/src/Pages/Incidents/Components/StatusBoxes/index.jsx
@@ -0,0 +1,46 @@
+import PropTypes from "prop-types";
+import { Stack } from "@mui/material";
+import StatusBox from "./StatusBox";
+import { useTheme } from "@emotion/react";
+import { useTranslation } from "react-i18next";
+import SkeletonLayout from "./skeleton";
+
+const StatusBoxes = ({ isLoading, summary }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ if (isLoading) return ;
+ return (
+
+
+
+
+
+
+ );
+};
+
+StatusBoxes.propTypes = {
+ isLoading: PropTypes.bool,
+ summary: PropTypes.object,
+};
+
+export default StatusBoxes;
diff --git a/client/src/Pages/Incidents/Components/StatusBoxes/skeleton.jsx b/client/src/Pages/Incidents/Components/StatusBoxes/skeleton.jsx
new file mode 100644
index 000000000..b9aa016b7
--- /dev/null
+++ b/client/src/Pages/Incidents/Components/StatusBoxes/skeleton.jsx
@@ -0,0 +1,36 @@
+import { Skeleton, Stack } from "@mui/material";
+import { useTheme } from "@emotion/react";
+
+const SkeletonLayout = () => {
+ const theme = useTheme();
+ return (
+
+
+
+
+
+
+ );
+};
+
+export default SkeletonLayout;
diff --git a/client/src/Pages/Incidents/index.jsx b/client/src/Pages/Incidents/index.jsx
index 09d4f0e25..11946ae63 100644
--- a/client/src/Pages/Incidents/index.jsx
+++ b/client/src/Pages/Incidents/index.jsx
@@ -4,10 +4,12 @@ import Breadcrumbs from "../../Components/Breadcrumbs";
import GenericFallback from "../../Components/GenericFallback";
import IncidentTable from "./Components/IncidentTable";
import OptionsHeader from "./Components/OptionsHeader";
+import StatusBoxes from "./Components/StatusBoxes";
//Utils
import { useTheme } from "@emotion/react";
import { useFetchMonitorsByTeamId } from "../../Hooks/monitorHooks";
+import { useFetchChecksSummaryByTeamId } from "../../Hooks/checkHooks";
import { useState, useEffect } from "react";
import NetworkError from "../../Components/GenericFallback/NetworkError";
import { useTranslation } from "react-i18next";
@@ -31,6 +33,8 @@ const Incidents = () => {
//Utils
const theme = useTheme();
const [monitors, , isLoading, networkError] = useFetchMonitorsByTeamId({});
+ const [summary, isLoadingSummary, networkErrorSummary] =
+ useFetchChecksSummaryByTeamId();
const { monitorId } = useParams();
useEffect(() => {
@@ -51,7 +55,7 @@ const Incidents = () => {
setMonitorLookup(monitorLookup);
}, [monitors]);
- if (networkError) {
+ if (networkError || networkErrorSummary) {
return (
@@ -62,6 +66,10 @@ const Incidents = () => {
return (
+
{
if (status === "up") {
color = theme.palette.success.lowContrast;
icon = (
-
+
);
} else if (status === "down") {
color = theme.palette.error.lowContrast;
icon = (
-
+
);
} else if (status === "paused") {
color = theme.palette.warning.lowContrast;
icon = (
-
+
);
@@ -75,15 +75,15 @@ const StatusBox = ({ title, value, status }) => {
{value}
} The response from the axios GET request.
+ */
+ getChecksAndSummaryByTeamId = async () => {
+ return this.axiosInstance.get(`/checks/team/summary`);
+ };
+
/**
* ************************************
* Update the status of a check
diff --git a/client/src/locales/en.json b/client/src/locales/en.json
index 658d08a09..9e6c9fe91 100644
--- a/client/src/locales/en.json
+++ b/client/src/locales/en.json
@@ -388,7 +388,8 @@
"incidentsOptionsHeader": "Incidents for:",
"incidentsOptionsHeaderFilterAll": "All",
"incidentsOptionsHeaderFilterBy": "Filter by:",
- "incidentsOptionsHeaderFilterCannotResolve": "Cannot resolve",
+ "incidentsOptionsHeaderTotalIncidents": "Total Incidents",
+ "incidentsOptionsHeaderFilterCannotResolve": "Cannot Resolve",
"incidentsOptionsHeaderFilterDown": "Down",
"incidentsOptionsHeaderFilterResolved": "Resolved",
"incidentsOptionsHeaderLastDay": "Last day",
diff --git a/server/controllers/checkController.js b/server/controllers/checkController.js
index 8f9d0fbb5..e50d5b7fc 100755
--- a/server/controllers/checkController.js
+++ b/server/controllers/checkController.js
@@ -111,6 +111,19 @@ class CheckController {
}
};
+ getChecksSummaryByTeamId = async (req, res, next) => {
+ try {
+ const { teamId } = req.user;
+ const summary = await this.db.getChecksSummaryByTeamId({ teamId });
+ return res.success({
+ msg: this.stringService.checkGetSummary,
+ data: summary,
+ });
+ } catch (error) {
+ next(handleError(error, SERVICE_NAME, "getChecksSummaryByTeamId"));
+ }
+ };
+
ackCheck = async (req, res, next) => {
try {
await ackCheckBodyValidation.validateAsync(req.body);
diff --git a/server/db/mongo/modules/checkModule.js b/server/db/mongo/modules/checkModule.js
index b7d9f4fb2..fb0cc539e 100755
--- a/server/db/mongo/modules/checkModule.js
+++ b/server/db/mongo/modules/checkModule.js
@@ -5,6 +5,7 @@ import PageSpeedCheck from "../../models/PageSpeedCheck.js";
import User from "../../models/User.js";
import logger from "../../../utils/logger.js";
import { ObjectId } from "mongodb";
+import { buildChecksSummaryByTeamIdPipeline } from "./checkModuleQueries.js";
const SERVICE_NAME = "checkModule";
const dateRangeLookup = {
@@ -304,6 +305,29 @@ const ackAllChecks = async (monitorId, teamId, ack, path) => {
}
};
+/**
+ * Get checks and summary by team ID
+ * @async
+ * @param {string} teamId
+ * @returns {Promise