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;