diff --git a/.gitignore b/.gitignore
index 420ffdc03..4867444db 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,5 @@
AGENTS.md
*.sh
notes
-docker-compose.yml
\ No newline at end of file
+docker-compose.yml
+mongo
\ No newline at end of file
diff --git a/client/src/components/design-elements/BasePage.tsx b/client/src/components/design-elements/BasePage.tsx
index cebe801b3..57bf017db 100644
--- a/client/src/components/design-elements/BasePage.tsx
+++ b/client/src/components/design-elements/BasePage.tsx
@@ -7,16 +7,42 @@ import type { StackProps } from "@mui/material/Stack";
import { useTheme } from "@mui/material/styles";
import { useTranslation } from "react-i18next";
interface BasePageProps extends StackProps {
+ loading?: boolean;
+ error?: boolean;
children: React.ReactNode;
breadcrumbOverride?: string[];
}
export const BasePage = ({
+ loading,
+ error,
children,
breadcrumbOverride,
...props
}: BasePageProps) => {
const theme = useTheme();
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ if (error) {
+ return (
+
+ );
+ }
+
return (
@@ -49,27 +75,6 @@ export const BasePageWithStates = ({
}: BasePageWithStatesProps) => {
const showLoading = loading && (!items || items.length === 0);
- if (showLoading) {
- return (
-
-
-
- );
- }
-
- if (error) {
- return (
-
- );
- }
-
if (isEmpty(items)) {
return (
{children};
+ return (
+
+ {children}
+
+ );
};
interface MonitorBasePageWithStatesProps extends StackProps {
@@ -114,27 +123,6 @@ export const MonitorBasePageWithStates = ({
const showLoading = loading && (!items || items.length === 0);
- if (showLoading) {
- return (
-
-
-
- );
- }
-
- if (error) {
- return (
-
- );
- }
-
if (isEmpty(items)) {
return (
{children};
+ return (
+
+ {children}
+
+ );
};
diff --git a/client/src/pages/checks/CheckDetails.tsx b/client/src/pages/checks/CheckDetails.tsx
new file mode 100644
index 000000000..ea3630c9c
--- /dev/null
+++ b/client/src/pages/checks/CheckDetails.tsx
@@ -0,0 +1,21 @@
+import { BasePage } from "@/components/design-elements";
+
+import { useParams } from "react-router";
+import { useGet } from "@/hooks/UseApi";
+import type { ICheck } from "@/types/check";
+import type { ApiResponse } from "@/types/api";
+const CheckDetails = () => {
+ const { id } = useParams();
+ const { response, loading, error } = useGet>(
+ `/checks/${id}`
+ );
+
+ const check = response?.data;
+ return (
+
+ {JSON.stringify(check)}
+
+ );
+};
+
+export default CheckDetails;
diff --git a/client/src/pages/uptime/CheckTable.tsx b/client/src/pages/uptime/CheckTable.tsx
index 789edc9d8..e6276169c 100644
--- a/client/src/pages/uptime/CheckTable.tsx
+++ b/client/src/pages/uptime/CheckTable.tsx
@@ -6,6 +6,7 @@ import type { ICheck, IChecksWithCount } from "@/types/check";
import type { ApiResponse } from "@/types/api";
import type { MonitorStatus } from "@/types/monitor";
+import { useNavigate } from "react-router";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useGet } from "@/hooks/UseApi";
@@ -51,6 +52,7 @@ const getHeaders = (t: Function, uiTimezone: string) => {
};
export const CheckTable = ({ monitorId }: { monitorId: string }) => {
+ const navigate = useNavigate();
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(5);
const { t } = useTranslation();
@@ -87,7 +89,13 @@ export const CheckTable = ({ monitorId }: { monitorId: string }) => {
return (
-
+ {
+ navigate(`/checks/${row._id}`);
+ }}
+ />
{
return (
{
} />
} />
} />
+ } />
} />
void;
+ getCheckById: (req: Request, res: Response, next: NextFunction) => void;
}
class ChecksController implements IChecksController {
@@ -53,6 +54,30 @@ class ChecksController implements IChecksController {
next(error);
}
};
+
+ getCheckById = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const userContext = req.user;
+ if (!userContext) {
+ return res.status(401).json({ message: "Unauthorized" });
+ }
+
+ const teamId = userContext.currentTeamId;
+ if (!teamId) {
+ throw new ApiError("No team ID", 400);
+ }
+
+ const checkId = req.params.id;
+ const check = await this.checkService.getCheckById(checkId, teamId);
+
+ return res.status(200).json({
+ message: "Check retrieved successfully",
+ data: check,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
}
export default ChecksController;
diff --git a/server/src/routes/checks.ts b/server/src/routes/checks.ts
index 60256c569..6e92d2696 100644
--- a/server/src/routes/checks.ts
+++ b/server/src/routes/checks.ts
@@ -26,6 +26,13 @@ class CheckRoutes {
validateQuery(checksStatusIdQuerySchema),
this.controller.getChecksByStatus
);
+ this.router.get(
+ "/:id",
+ verifyToken,
+ addUserContext,
+ verifyOrgPermission([PERMISSIONS.checks.read]),
+ this.controller.getCheckById
+ );
};
getRouter() {
diff --git a/server/src/services/business/CheckService.ts b/server/src/services/business/CheckService.ts
index 5ee36598c..1748553dd 100644
--- a/server/src/services/business/CheckService.ts
+++ b/server/src/services/business/CheckService.ts
@@ -33,6 +33,8 @@ export interface ICheckService {
range: string
) => Promise<{ checks: ICheck[]; count: number }>;
+ getCheckById: (checkId: string, teamId: string) => Promise;
+
cleanupOrphanedChecks: () => Promise;
}
@@ -259,6 +261,13 @@ class CheckService implements ICheckService {
]);
return { checks, count };
};
+
+ getCheckById = async (checkId: string, teamId: string) => {
+ return await Check.findOne({
+ _id: new mongoose.Types.ObjectId(checkId),
+ "metadata.teamId": new mongoose.Types.ObjectId(teamId),
+ });
+ };
}
export default CheckService;