diff --git a/client/src/Components/v2/common/controls/HeaderTimeRange.tsx b/client/src/Components/v2/common/controls/HeaderTimeRange.tsx
index 57b61473a..9b8b9dcec 100644
--- a/client/src/Components/v2/common/controls/HeaderTimeRange.tsx
+++ b/client/src/Components/v2/common/controls/HeaderTimeRange.tsx
@@ -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)}
>
+
+ {isLoading && }
+
Showing statistics for past{" "}
{dateRange === "recent"
diff --git a/client/src/Components/v2/design-elements/StatusBox.tsx b/client/src/Components/v2/design-elements/StatusBox.tsx
index 9e322cf73..d48871a2a 100644
--- a/client/src/Components/v2/design-elements/StatusBox.tsx
+++ b/client/src/Components/v2/design-elements/StatusBox.tsx
@@ -133,6 +133,20 @@ export const DownChecksBox = ({ n }: { n: number }) => {
/>
);
};
+export const UpChecksBox = ({ n }: { n: number }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ return (
+
+ );
+};
export const InitializingStatusBox = ({ n }: { n: number }) => {
const theme = useTheme();
diff --git a/client/src/Pages/Checks/Components/ChecksTable.tsx b/client/src/Pages/Checks/Components/ChecksTable.tsx
index 9088c2cef..e48af37b8 100644
--- a/client/src/Pages/Checks/Components/ChecksTable.tsx
+++ b/client/src/Pages/Checks/Components/ChecksTable.tsx
@@ -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(teamChecksUrl);
const { data: monitorData, isLoading: isLoadingMonitor } =
useGet(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")}
/>
{
const { t } = useTranslation();
- const theme = useTheme();
const { monitorId } = useParams<{ monitorId?: string }>();
// Local state
const [selectedMonitor, setSelectedMonitor] = useState(monitorId || "0");
const [dateRange, setDateRange] = useState("recent");
+ const [statusFilter, setStatusFilter] = useState("down");
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(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(monitorsUrl);
+ const { data: monitorsResponse, isLoading: isLoadingMonitors } =
+ useGet(monitorsUrl);
- const {
- data: summaryResponse,
- isLoading: isLoadingSummary,
- error: summaryError,
- } = useGet(summaryUrl);
+ const { data: summaryResponse, isLoading: isLoadingSummary } =
+ useGet(summaryUrl);
+
+ const isLoading = isLoadingMonitors || isLoadingSummary;
+ const totalChecks = summaryResponse?.totalChecks || 0;
+ const downChecks = summaryResponse?.downChecks || 0;
+ const upChecks = totalChecks - (summaryResponse?.downChecks || 0);
return (
@@ -45,32 +49,52 @@ const Checks = () => {
direction={{ xs: "column", md: "row" }}
gap={4}
>
-
-
+
+
+
-
+
+
+
@@ -79,6 +103,7 @@ const Checks = () => {
{
const matchStage: Record = {
"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:
diff --git a/server/src/validation/joi.ts b/server/src/validation/joi.ts
index 8871c29e2..1d5fcbbf0 100755
--- a/server/src/validation/joi.ts
+++ b/server/src/validation/joi.ts
@@ -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(),