monitor select
This commit is contained in:
1 parent
f88e4ee6f4
commit
f3ba8c9815
4 files changed
+197
-20
No files matched your search
@@ -4,12 +4,16 @@ import Box from "@mui/material/Box";
|
||||
import { BaseBox } from "@/Components/v2/design-elements";
|
||||
import Background from "@/assets/Images/background-grid.svg?react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { SxProps } from "@mui/material";
|
||||
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
|
||||
type StatusBoxProps = React.PropsWithChildren<{ children: React.ReactNode }>;
|
||||
type StatusBoxProps = React.PropsWithChildren<{
|
||||
children: React.ReactNode;
|
||||
sx?: SxProps;
|
||||
}>;
|
||||
|
||||
export const BGBox = ({ children }: StatusBoxProps) => {
|
||||
export const BGBox = ({ children, sx }: StatusBoxProps) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<BaseBox
|
||||
@@ -18,6 +22,7 @@ export const BGBox = ({ children }: StatusBoxProps) => {
|
||||
position: "relative",
|
||||
flex: 1,
|
||||
padding: theme.spacing(4),
|
||||
...sx,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
@@ -36,14 +41,16 @@ const StatusBox = ({
|
||||
label,
|
||||
n,
|
||||
color,
|
||||
sx,
|
||||
}: {
|
||||
label: string;
|
||||
n: number;
|
||||
color: string | undefined;
|
||||
sx?: SxProps;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<BGBox>
|
||||
<BGBox sx={sx}>
|
||||
<Stack spacing={theme.spacing(4)}>
|
||||
<Typography
|
||||
variant={"h2"}
|
||||
@@ -106,6 +113,9 @@ export const TotalChecksBox = ({ n }: { n: number }) => {
|
||||
label={t("pages.common.monitors.status.total")}
|
||||
n={n}
|
||||
color={theme.palette.primary.light}
|
||||
sx={{
|
||||
maxWidth: "300px",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -117,6 +127,9 @@ export const DownChecksBox = ({ n }: { n: number }) => {
|
||||
label={t("pages.common.monitors.status.down")}
|
||||
n={n}
|
||||
color={theme.palette.error.light}
|
||||
sx={{
|
||||
maxWidth: "300px",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import {
|
||||
Table,
|
||||
Pagination,
|
||||
ValueLabel,
|
||||
StatusLabel,
|
||||
} from "@/Components/v2/design-elements";
|
||||
import Box from "@mui/material/Box";
|
||||
import type { Header } from "@/Components/v2/design-elements/Table";
|
||||
import type { Monitor, MonitorStatus } from "@/Types/Monitor";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { formatDateWithTz } from "@/Utils/TimeUtils";
|
||||
import { useNavigate } from "react-router";
|
||||
import type { Check } from "@/Types/Check";
|
||||
import type { RootState } from "@/Types/state";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
export const ChecksTable = ({
|
||||
monitors,
|
||||
selectedMonitorId,
|
||||
page,
|
||||
setPage,
|
||||
rowsPerPage,
|
||||
setRowsPerPage,
|
||||
}: {
|
||||
monitors: Monitor[] | null;
|
||||
selectedMonitorId: string;
|
||||
page: number;
|
||||
setPage: (page: number) => void;
|
||||
rowsPerPage: number;
|
||||
setRowsPerPage: (rowsPerPage: number) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
|
||||
const navigate = useNavigate();
|
||||
|
||||
// DATA FETCHING HERE
|
||||
|
||||
const getHeaders = (t: Function, uiTimezone: string) => {
|
||||
const headers: Header<Check>[] = [
|
||||
{
|
||||
id: "monitorName",
|
||||
content: t("common.table.headers.monitor"),
|
||||
render: (row) => {
|
||||
return (
|
||||
monitors?.find((monitor) => monitor.id === row.metadata.monitorId)?.name ||
|
||||
"N/A"
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "status",
|
||||
content: "Status",
|
||||
render: (row) => {
|
||||
return <StatusLabel status={row.status as MonitorStatus} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "date",
|
||||
content: t("checks.table.headers.dateTime"),
|
||||
render: (row) => {
|
||||
return formatDateWithTz(
|
||||
row.createdAt,
|
||||
"ddd, MMMM D, YYYY, HH:mm A",
|
||||
uiTimezone
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "statusCode",
|
||||
content: t("checks.table.headers.statusCode"),
|
||||
render: (row) => {
|
||||
const code = row.statusCode;
|
||||
if (!code) return "N/A";
|
||||
const value = code < 300 ? "positive" : code < 400 ? "neutral" : "negative";
|
||||
return (
|
||||
<ValueLabel
|
||||
value={value}
|
||||
text={String(code)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "message",
|
||||
content: t("common.table.headers.message"),
|
||||
render: (row) => {
|
||||
return row.message || "N/A";
|
||||
},
|
||||
},
|
||||
];
|
||||
return headers;
|
||||
};
|
||||
|
||||
const headers = getHeaders(t, uiTimezone);
|
||||
|
||||
const handlePageChange = (
|
||||
_e: React.MouseEvent<HTMLButtonElement> | null,
|
||||
newPage: number
|
||||
) => {
|
||||
setPage(newPage);
|
||||
};
|
||||
|
||||
const handleRowsPerPageChange = (
|
||||
e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>
|
||||
) => {
|
||||
const value = Number(e.target.value);
|
||||
setPage(0);
|
||||
setRowsPerPage(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Table
|
||||
headers={headers}
|
||||
data={checks}
|
||||
onRowClick={(row) => {
|
||||
navigate(`/checks/${row.id}`);
|
||||
}}
|
||||
emptyViewText={t("checks.table.empty")}
|
||||
/>
|
||||
<Pagination
|
||||
component="div"
|
||||
count={checks.length}
|
||||
page={page}
|
||||
rowsPerPage={rowsPerPage}
|
||||
onPageChange={handlePageChange}
|
||||
onRowsPerPageChange={handleRowsPerPageChange}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,10 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import { BasePage, TotalChecksBox, DownChecksBox } from "@/Components/v2/design-elements";
|
||||
import { HeaderTimeRange } from "@/Components/v2/common";
|
||||
import { Select } from "@/Components/v2/inputs";
|
||||
import IncidentTable from "./Components/IncidentTable/index.jsx";
|
||||
|
||||
import { useTheme } from "@mui/material";
|
||||
import { MenuItem, useTheme } from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
@@ -9,6 +12,12 @@ import { useGet } from "@/Hooks/UseApi";
|
||||
import type { Monitor } from "@/Types/Monitor";
|
||||
import type { ChecksSummary } from "@/Types/Check";
|
||||
|
||||
interface MonitorLookupEntry {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
const Checks = () => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
@@ -16,8 +25,9 @@ const Checks = () => {
|
||||
|
||||
// Local state
|
||||
const [selectedMonitor, setSelectedMonitor] = useState<string>("0");
|
||||
const [filter, setFilter] = useState<boolean | undefined>(undefined);
|
||||
const [filter, setFilter] = useState<string | undefined>(undefined);
|
||||
const [dateRange, setDateRange] = useState<string>("hour");
|
||||
const [updateTrigger, setUpdateTrigger] = useState<boolean>(false);
|
||||
|
||||
// Data fetching with SWR
|
||||
const monitorsUrl = "/monitors/team";
|
||||
@@ -35,8 +45,6 @@ const Checks = () => {
|
||||
error: summaryError,
|
||||
} = useGet<ChecksSummary>(summaryUrl);
|
||||
|
||||
console.log(monitorsResponse, summaryResponse);
|
||||
|
||||
return (
|
||||
<BasePage>
|
||||
<Stack
|
||||
@@ -46,6 +54,33 @@ const Checks = () => {
|
||||
<TotalChecksBox n={summaryResponse?.totalChecks || 0} />
|
||||
<DownChecksBox n={summaryResponse?.downChecks || 0} />
|
||||
</Stack>
|
||||
|
||||
<Stack
|
||||
direction={{ xs: "column", md: "row" }}
|
||||
justifyContent={"space-between"}
|
||||
alignItems={"center"}
|
||||
>
|
||||
<Select
|
||||
value={selectedMonitor}
|
||||
onChange={(e: any) => {
|
||||
setSelectedMonitor(e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="0">All monitors</MenuItem>
|
||||
{monitorsResponse?.map((monitor) => (
|
||||
<MenuItem
|
||||
key={monitor.id}
|
||||
value={monitor.id}
|
||||
>
|
||||
{monitor.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<HeaderTimeRange
|
||||
dateRange={dateRange}
|
||||
setDateRange={setDateRange}
|
||||
/>
|
||||
</Stack>
|
||||
</BasePage>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -366,27 +366,24 @@ class MongoChecksRepository implements IChecksRepository {
|
||||
};
|
||||
|
||||
findSummaryByTeamId = async (teamId: string, dateRange: string) => {
|
||||
const matchStage = {
|
||||
const baseMatch = {
|
||||
"metadata.teamId": new mongoose.Types.ObjectId(teamId),
|
||||
status: false,
|
||||
...(dateRangeLookup[dateRange] && {
|
||||
createdAt: {
|
||||
$gte: dateRangeLookup[dateRange],
|
||||
},
|
||||
}),
|
||||
};
|
||||
const checks = await CheckModel.aggregate([
|
||||
{ $match: matchStage },
|
||||
{
|
||||
$group: {
|
||||
_id: null,
|
||||
totalChecks: { $sum: 1 },
|
||||
downChecks: { $sum: { $cond: [{ $eq: ["$ack", false] }, 1, 0] } },
|
||||
},
|
||||
},
|
||||
{ $project: { _id: 0 } },
|
||||
|
||||
const [totalResult, downResult] = await Promise.all([
|
||||
CheckModel.countDocuments(baseMatch),
|
||||
CheckModel.countDocuments({ ...baseMatch, status: false }),
|
||||
]);
|
||||
return checks[0] ?? { totalChecks: 0, downChecks: 0 };
|
||||
|
||||
return {
|
||||
totalChecks: totalResult,
|
||||
downChecks: downResult,
|
||||
};
|
||||
};
|
||||
|
||||
deleteByMonitorId = async (monitorId: string): Promise<number> => {
|
||||
|
||||
Reference in new issue
Block a user