This commit is contained in:
Alex Holliday
2026-01-28 17:59:11 +00:00
parent 07705e9823
commit fcfad945a0
7 changed files with 110 additions and 46 deletions
@@ -2,6 +2,8 @@ import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { ToggleButtonGroup, ToggleButton } from "@/Components/v2/inputs";
import { useTheme } from "@mui/material/styles";
import CircularProgress from "@mui/material/CircularProgress";
import Box from "@mui/material/Box";
interface MonitorTimeFrameHeaderProps {
isLoading?: boolean;
@@ -72,6 +74,9 @@ export const HeaderTimeRange = ({
alignItems="center"
gap={theme.spacing(4)}
>
<Box sx={{ width: 20, height: 20, display: "flex", alignItems: "center", justifyContent: "center" }}>
{isLoading && <CircularProgress size={16} />}
</Box>
<Typography variant="body2">
Showing statistics for past{" "}
{dateRange === "recent"
@@ -133,6 +133,20 @@ export const DownChecksBox = ({ n }: { n: number }) => {
/>
);
};
export const UpChecksBox = ({ n }: { n: number }) => {
const theme = useTheme();
const { t } = useTranslation();
return (
<StatusBox
label={t("pages.common.monitors.status.up")}
n={n}
color={theme.palette.success.light}
sx={{
maxWidth: "300px",
}}
/>
);
};
export const InitializingStatusBox = ({ n }: { n: number }) => {
const theme = useTheme();
@@ -20,6 +20,7 @@ import { useGet } from "@/Hooks/UseApi";
export const ChecksTable = ({
monitors,
selectedMonitorId,
statusFilter,
dateRange,
page,
setPage,
@@ -28,6 +29,7 @@ export const ChecksTable = ({
}: {
monitors: Monitor[] | null;
selectedMonitorId: string;
statusFilter: string;
dateRange: string;
page: number;
setPage: (page: number) => void;
@@ -38,40 +40,36 @@ export const ChecksTable = ({
const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
const navigate = useNavigate();
// Get selected monitor type
const selectedMonitorType = monitors?.find((m) => m.id === selectedMonitorId)?.type;
// Team checks URL (when selectedMonitorId === "0")
const teamChecksUrl = useMemo(() => {
if (selectedMonitorId !== "0") return null;
const params = new URLSearchParams();
params.append("sortOrder", "desc");
if (dateRange) params.append("dateRange", dateRange);
if (statusFilter) params.append("filter", statusFilter);
params.append("page", String(page));
params.append("rowsPerPage", String(rowsPerPage));
return `/checks/team?${params.toString()}`;
}, [selectedMonitorId, dateRange, page, rowsPerPage]);
}, [selectedMonitorId, dateRange, statusFilter, page, rowsPerPage]);
// Monitor checks URL (when specific monitor selected)
const monitorChecksUrl = useMemo(() => {
if (selectedMonitorId === "0" || !selectedMonitorType) return null;
const params = new URLSearchParams();
params.append("type", selectedMonitorType);
params.append("sortOrder", "desc");
params.append("status", "false");
if (statusFilter) params.append("filter", statusFilter);
if (dateRange) params.append("dateRange", dateRange);
params.append("page", String(page));
params.append("rowsPerPage", String(rowsPerPage));
return `/checks/${selectedMonitorId}?${params.toString()}`;
}, [selectedMonitorId, selectedMonitorType, dateRange, page, rowsPerPage]);
}, [selectedMonitorId, selectedMonitorType, dateRange, statusFilter, page, rowsPerPage]);
// Fetch data - null key skips the request
const { data: teamData, isLoading: isLoadingTeam } =
useGet<ChecksResponse>(teamChecksUrl);
const { data: monitorData, isLoading: isLoadingMonitor } =
useGet<ChecksResponse>(monitorChecksUrl);
// Select correct data based on selection
const checks =
selectedMonitorId === "0" ? (teamData?.checks ?? []) : (monitorData?.checks ?? []);
const checksCount =
@@ -101,7 +99,7 @@ export const ChecksTable = ({
},
{
id: "date",
content: t("checks.table.headers.dateTime"),
content: t("common.table.headers.dateTime"),
render: (row) => {
return formatDateWithTz(
row.createdAt,
@@ -112,7 +110,7 @@ export const ChecksTable = ({
},
{
id: "statusCode",
content: t("checks.table.headers.statusCode"),
content: t("pages.checks.table.headers.statusCode"),
render: (row) => {
const code = row.statusCode;
if (!code) return "N/A";
@@ -161,7 +159,7 @@ export const ChecksTable = ({
onRowClick={(row) => {
navigate(`/checks/${row.id}`);
}}
emptyViewText={t("checks.table.empty")}
emptyViewText={t("pages.checks.table.empty")}
/>
<Pagination
component="div"
+54 -29
View File
@@ -1,5 +1,10 @@
import Stack from "@mui/material/Stack";
import { BasePage, TotalChecksBox, DownChecksBox } from "@/Components/v2/design-elements";
import {
BasePage,
TotalChecksBox,
UpChecksBox,
DownChecksBox,
} from "@/Components/v2/design-elements";
import { HeaderTimeRange } from "@/Components/v2/common";
import { Select } from "@/Components/v2/inputs";
import { ChecksTable } from "./Components/ChecksTable";
@@ -14,12 +19,12 @@ import type { ChecksSummary } from "@/Types/Check";
const Checks = () => {
const { t } = useTranslation();
const theme = useTheme();
const { monitorId } = useParams<{ monitorId?: string }>();
// Local state
const [selectedMonitor, setSelectedMonitor] = useState<string>(monitorId || "0");
const [dateRange, setDateRange] = useState<string>("recent");
const [statusFilter, setStatusFilter] = useState<string>("down");
const [page, setPage] = useState<number>(0);
const [rowsPerPage, setRowsPerPage] = useState<number>(10);
@@ -27,17 +32,16 @@ const Checks = () => {
const monitorsUrl = "/monitors/team";
const summaryUrl = `/checks/team/summary?dateRange=${dateRange}`;
const {
data: monitorsResponse,
isLoading: isLoadingMonitors,
error: monitorsError,
} = useGet<Monitor[]>(monitorsUrl);
const { data: monitorsResponse, isLoading: isLoadingMonitors } =
useGet<Monitor[]>(monitorsUrl);
const {
data: summaryResponse,
isLoading: isLoadingSummary,
error: summaryError,
} = useGet<ChecksSummary>(summaryUrl);
const { data: summaryResponse, isLoading: isLoadingSummary } =
useGet<ChecksSummary>(summaryUrl);
const isLoading = isLoadingMonitors || isLoadingSummary;
const totalChecks = summaryResponse?.totalChecks || 0;
const downChecks = summaryResponse?.downChecks || 0;
const upChecks = totalChecks - (summaryResponse?.downChecks || 0);
return (
<BasePage>
@@ -45,32 +49,52 @@ const Checks = () => {
direction={{ xs: "column", md: "row" }}
gap={4}
>
<TotalChecksBox n={summaryResponse?.totalChecks || 0} />
<DownChecksBox n={summaryResponse?.downChecks || 0} />
<TotalChecksBox n={totalChecks} />
<UpChecksBox n={upChecks} />
<DownChecksBox n={downChecks || 0} />
</Stack>
<Stack
direction={{ xs: "column", md: "row" }}
justifyContent={"space-between"}
alignItems={"center"}
gap={2}
>
<Select
value={selectedMonitor}
onChange={(e: any) => {
setSelectedMonitor(e.target.value);
}}
<Stack
direction="row"
gap={2}
>
<MenuItem value="0">All monitors</MenuItem>
{monitorsResponse?.map((monitor) => (
<MenuItem
key={monitor.id}
value={monitor.id}
>
{monitor.name}
</MenuItem>
))}
</Select>
<Select
value={selectedMonitor}
onChange={(e: any) => {
setSelectedMonitor(e.target.value);
setPage(0);
}}
>
<MenuItem value="0">{t("pages.checks.selects.monitor.all")}</MenuItem>
{monitorsResponse?.map((monitor) => (
<MenuItem
key={monitor.id}
value={monitor.id}
>
{monitor.name}
</MenuItem>
))}
</Select>
<Select
value={statusFilter}
onChange={(e: any) => {
setStatusFilter(e.target.value);
setPage(0);
}}
>
<MenuItem value="all">{t("pages.checks.selects.status.all")}</MenuItem>
<MenuItem value="up">{t("pages.checks.selects.status.up")}</MenuItem>
<MenuItem value="down">{t("pages.checks.selects.status.down")}</MenuItem>
</Select>
</Stack>
<HeaderTimeRange
isLoading={isLoading}
dateRange={dateRange}
setDateRange={setDateRange}
/>
@@ -79,6 +103,7 @@ const Checks = () => {
<ChecksTable
monitors={monitorsResponse ?? null}
selectedMonitorId={selectedMonitor}
statusFilter={statusFilter}
dateRange={dateRange}
page={page}
setPage={setPage}
+14 -1
View File
@@ -40,7 +40,8 @@
"monitor": "Monitor",
"name": "Name",
"status": "Status",
"type": "Type"
"type": "Type",
"dateTime": "Date & time"
},
"empty": "Nothing here"
},
@@ -177,6 +178,7 @@
"up": "up",
"down": "down",
"paused": "paused",
"total": "total",
"initializing": "initializing"
},
"actions": {
@@ -223,6 +225,17 @@
"headers": {
"dateTime": "Date & time",
"statusCode": "Status code"
},
"empty": "No down checks in this time range"
},
"selects": {
"monitor": {
"all": "All monitors"
},
"status": {
"all": "All",
"down": "Down",
"up": "Up"
}
}
},
@@ -221,9 +221,14 @@ class MongoChecksRepository implements IChecksRepository {
switch (filter) {
case "all":
break;
case "up":
matchStage.status = true;
break;
case "down":
matchStage.status = false;
break;
case "resolve":
matchStage.status = false;
matchStage.statusCode = 5000;
break;
default:
@@ -271,7 +276,6 @@ class MongoChecksRepository implements IChecksRepository {
findByTeamId = async (sortOrder: string, dateRange: string, filter: string, page: number, rowsPerPage: number, teamId: string) => {
const matchStage: Record<string, any> = {
"metadata.teamId": new mongoose.Types.ObjectId(teamId),
status: false,
...(dateRangeLookup[dateRange] && {
createdAt: {
$gte: dateRangeLookup[dateRange],
@@ -283,9 +287,14 @@ class MongoChecksRepository implements IChecksRepository {
switch (filter) {
case "all":
break;
case "up":
matchStage.status = true;
break;
case "down":
matchStage.status = false;
break;
case "resolve":
matchStage.status = false;
matchStage.statusCode = 5000;
break;
default:
+4 -4
View File
@@ -111,7 +111,7 @@ const getMonitorByIdQueryValidation = joi.object({
status: joi.boolean(),
sortOrder: joi.string().valid("asc", "desc"),
limit: joi.number(),
dateRange: joi.string().valid("hour", "day", "week", "month", "all"),
dateRange: joi.string().valid("recent", "hour", "day", "week", "month", "all"),
numToDisplay: joi.number(),
normalize: joi.boolean(),
});
@@ -307,7 +307,7 @@ const getChecksQueryValidation = joi.object({
sortOrder: joi.string().valid("asc", "desc"),
limit: joi.number(),
dateRange: joi.string().valid("recent", "hour", "day", "week", "month", "all"),
filter: joi.string().valid("all", "down", "resolve"),
filter: joi.string().valid("all", "up", "down", "resolve"),
ack: joi.boolean(),
page: joi.number(),
rowsPerPage: joi.number(),
@@ -317,8 +317,8 @@ const getChecksQueryValidation = joi.object({
const getTeamChecksQueryValidation = joi.object({
sortOrder: joi.string().valid("asc", "desc"),
limit: joi.number(),
dateRange: joi.string().valid("hour", "day", "week", "month", "all"),
filter: joi.string().valid("all", "down", "resolve"),
dateRange: joi.string().valid("recent", "hour", "day", "week", "month", "all"),
filter: joi.string().valid("all", "up", "down", "resolve"),
ack: joi.boolean(),
page: joi.number(),
rowsPerPage: joi.number(),