From f9d0ab5e07695c57581018ca3f7eb36f74480c8b Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 8 Oct 2025 14:28:51 -0700 Subject: [PATCH] update usePatch, implement actions --- .../src/Components/v2/ActionsMenu/index.tsx | 13 +- .../Components/v2/DesignElements/Table.tsx | 18 +- .../Components/v2/Monitors/HeaderControls.tsx | 2 +- client/src/Hooks/v2/UseApi.tsx | 8 +- client/src/Pages/v2/Uptime/Details.tsx | 6 +- client/src/Pages/v2/Uptime/MonitorTable.tsx | 228 ++++++++++-------- client/src/Pages/v2/Uptime/UptimeMonitors.tsx | 37 ++- 7 files changed, 181 insertions(+), 131 deletions(-) diff --git a/client/src/Components/v2/ActionsMenu/index.tsx b/client/src/Components/v2/ActionsMenu/index.tsx index 5b606e5b0..a143be3f5 100644 --- a/client/src/Components/v2/ActionsMenu/index.tsx +++ b/client/src/Components/v2/ActionsMenu/index.tsx @@ -15,11 +15,13 @@ export const ActionsMenu = ({ items }: { items: ActionMenuItem[] }) => { const [anchorEl, setAnchorEl] = useState(null); const open = Boolean(anchorEl); - const handleClick = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); + const handleClick = (e: React.MouseEvent) => { + e.stopPropagation(); + setAnchorEl(e.currentTarget); }; - const handleClose = () => { + const handleClose = (e: React.MouseEvent) => { + e.stopPropagation(); setAnchorEl(null); }; @@ -36,8 +38,9 @@ export const ActionsMenu = ({ items }: { items: ActionMenuItem[] }) => { {items.map((item) => ( { - if (item.closeMenu) handleClose(); + onClick={(e: React.MouseEvent) => { + e.stopPropagation(); + if (item.closeMenu) handleClose(e); item.action(); }} > diff --git a/client/src/Components/v2/DesignElements/Table.tsx b/client/src/Components/v2/DesignElements/Table.tsx index 58cc6aed1..bf75fd9bf 100644 --- a/client/src/Components/v2/DesignElements/Table.tsx +++ b/client/src/Components/v2/DesignElements/Table.tsx @@ -28,12 +28,16 @@ export type Header = { type DataTableProps = { headers: Header[]; data: T[]; + onRowClick?: (row: T) => void; }; -export function DataTable({ - headers, - data, -}: DataTableProps) { +export function DataTable< + T extends { + id?: string | number; + _id?: string | number; + onRowClick?: (row: T) => void; + }, +>({ headers, data, onRowClick }: DataTableProps) { const theme = useTheme(); if (data.length === 0 || headers.length === 0) return
No data
; return ( @@ -77,7 +81,11 @@ export function DataTable + (onRowClick ? onRowClick(row) : null)} + > {headers.map((header, index) => { return ( { - await patch(); + await patch(`/monitors/${monitor._id}/active`); refetch(); }} startIcon={ diff --git a/client/src/Hooks/v2/UseApi.tsx b/client/src/Hooks/v2/UseApi.tsx index 9808dbf49..5324f6343 100644 --- a/client/src/Hooks/v2/UseApi.tsx +++ b/client/src/Hooks/v2/UseApi.tsx @@ -58,11 +58,15 @@ export const usePost = (endpoint: string) => { return { post: postFn, loading, error }; }; -export const usePatch = (endpoint: string) => { +export const usePatch = () => { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - const patchFn = async (body: B, config?: AxiosRequestConfig): Promise => { + const patchFn = async ( + endpoint: string, + body?: B, + config?: AxiosRequestConfig + ): Promise => { setLoading(true); setError(null); diff --git a/client/src/Pages/v2/Uptime/Details.tsx b/client/src/Pages/v2/Uptime/Details.tsx index c3ef14e72..a3753eb4b 100644 --- a/client/src/Pages/v2/Uptime/Details.tsx +++ b/client/src/Pages/v2/Uptime/Details.tsx @@ -52,11 +52,7 @@ const UptimeDetailsPage = () => { {} ); - const { - patch, - loading: isPatching, - error: postError, - } = usePatch(`/monitors/${id}/active`); + const { patch, loading: isPatching, error: postError } = usePatch(); const monitor: IMonitor = response?.data?.monitor; diff --git a/client/src/Pages/v2/Uptime/MonitorTable.tsx b/client/src/Pages/v2/Uptime/MonitorTable.tsx index 038da2e24..a7b1d203f 100644 --- a/client/src/Pages/v2/Uptime/MonitorTable.tsx +++ b/client/src/Pages/v2/Uptime/MonitorTable.tsx @@ -10,122 +10,133 @@ import { useTranslation } from "react-i18next"; import { useMediaQuery } from "@mui/material"; import { useTheme } from "@mui/material/styles"; import { useNavigate } from "react-router-dom"; +import { usePatch } from "@/Hooks/v2/UseApi"; +import type { ApiResponse } from "@/Hooks/v2/UseApi"; import type { IMonitor } from "@/Types/Monitor"; import type { ActionMenuItem } from "@/Components/v2/ActionsMenu"; -const getActions = ( - theme: any, - monitor: IMonitor, - navigate: Function -): ActionMenuItem[] => { - return [ - { - id: 1, - label: "Open site", - action: () => { - console.log("Open site"); - }, - closeMenu: true, - }, - { - id: 2, - label: "Details", - action: () => { - navigate(`${monitor._id}`); - }, - }, - { - id: 3, - label: "Incidents", - action: () => { - console.log("Open incidents"); - }, - }, - { - id: 4, - label: "Configure", - action: () => { - console.log("Open configure"); - }, - }, - { - id: 5, - label: "Clone", - action: () => { - console.log("Open clone"); - }, - }, - { - id: 6, - label: "Pause", - action: () => { - console.log("Open pause"); - }, - closeMenu: true, - }, - { - id: 7, - label: Remove, - action: () => { - console.log("Open delete"); - }, - closeMenu: true, - }, - ]; -}; -const getHeaders = (theme: any, t: Function, navigate: Function) => { - const headers: Header[] = [ - { - id: "name", - content: t("host"), - render: (row) => { - return row.name; - }, - }, - { - id: "status", - content: t("status"), - render: (row) => { - return ; - }, - }, - { - id: "histogram", - content: t("responseTime"), - render: (row) => { - return ( - - - - ); - }, - }, - { - id: "type", - content: t("type"), - render: (row) => { - return row.type; - }, - }, - { - id: "actions", - content: t("actions"), - render: (row) => { - return ; - }, - }, - ]; - return headers; -}; - -export const MonitorTable = ({ monitors }: { monitors: IMonitor[] }) => { +export const MonitorTable = ({ + monitors, + refetch, +}: { + monitors: IMonitor[]; + refetch: Function; +}) => { const { t } = useTranslation(); const theme = useTheme(); const isSmall = useMediaQuery(theme.breakpoints.down("md")); const navigate = useNavigate(); + const { + patch, + // loading: isPatching, + // error: postError, + } = usePatch(); - let headers = getHeaders(theme, t, navigate); + const getActions = (monitor: IMonitor): ActionMenuItem[] => { + return [ + { + id: 1, + label: "Open site", + action: () => { + window.open(monitor.url, "_blank", "noreferrer"); + }, + closeMenu: true, + }, + { + id: 2, + label: "Details", + action: () => { + navigate(`${monitor._id}`); + }, + }, + { + id: 3, + label: "Incidents", + action: () => { + navigate(`/v2/incidents/${monitor._id}`); + }, + }, + { + id: 4, + label: "Configure", + action: () => { + console.log("Open configure"); + }, + }, + { + id: 5, + label: "Clone", + action: () => { + console.log("Open clone"); + }, + }, + { + id: 6, + label: monitor.isActive ? "Pause" : "Resume", + action: async () => { + await patch(`/monitors/${monitor._id}/active`); + refetch(); + }, + closeMenu: true, + }, + { + id: 7, + label: Remove, + action: () => { + console.log("Open delete"); + }, + closeMenu: true, + }, + ]; + }; + + const getHeaders = () => { + const headers: Header[] = [ + { + id: "name", + content: t("host"), + render: (row) => { + return row.name; + }, + }, + { + id: "status", + content: t("status"), + render: (row) => { + return ; + }, + }, + { + id: "histogram", + content: t("responseTime"), + render: (row) => { + return ( + + + + ); + }, + }, + { + id: "type", + content: t("type"), + render: (row) => { + return row.type; + }, + }, + { + id: "actions", + content: t("actions"), + render: (row) => { + return ; + }, + }, + ]; + return headers; + }; + + let headers = getHeaders(); if (isSmall) { headers = headers.filter((h) => h.id !== "histogram"); @@ -134,6 +145,9 @@ export const MonitorTable = ({ monitors }: { monitors: IMonitor[] }) => { { + navigate(row._id); + }} /> ); }; diff --git a/client/src/Pages/v2/Uptime/UptimeMonitors.tsx b/client/src/Pages/v2/Uptime/UptimeMonitors.tsx index 8980c8260..57865d929 100644 --- a/client/src/Pages/v2/Uptime/UptimeMonitors.tsx +++ b/client/src/Pages/v2/Uptime/UptimeMonitors.tsx @@ -18,8 +18,30 @@ const UptimeMonitors = () => { const theme = useTheme(); const isSmall = useMediaQuery(theme.breakpoints.down("md")); - const { response, loading } = useGet("/monitors?embedChecks=true", {}); - const monitors = response?.data ?? ([] as IMonitor[]); + const { response, loading, refetch } = useGet( + "/monitors?embedChecks=true", + {}, + { refreshInterval: 30000 } + ); + const monitors: IMonitor[] = response?.data ?? ([] as IMonitor[]); + + const monitorStatuses = monitors.reduce( + (acc, monitor) => { + if (monitor.status === "up") { + acc.up += 1; + } else if (monitor.status === "down") { + acc.down += 1; + } else if (monitor.isActive === false) { + acc.paused += 1; + } + return acc; + }, + { + up: 0, + down: 0, + paused: 0, + } + ); if (monitors.length === 0 && !loading) { return "No monitors found"; @@ -35,11 +57,14 @@ const UptimeMonitors = () => { direction={isSmall ? "column" : "row"} gap={theme.spacing(8)} > - - - + + + - + ); };