update usePatch, implement actions

This commit is contained in:
Alex Holliday committed 2025-10-08 14:28:51 -07:00
1 parent aae1fefb31
commit f9d0ab5e07
7 files changed
+181 -131

No files matched your search

@@ -15,11 +15,13 @@ export const ActionsMenu = ({ items }: { items: ActionMenuItem[] }) => {
const [anchorEl, setAnchorEl] = useState<null | any>(null);
const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<any>) => {
setAnchorEl(event.currentTarget);
const handleClick = (e: React.MouseEvent<any>) => {
e.stopPropagation();
setAnchorEl(e.currentTarget);
};
const handleClose = () => {
const handleClose = (e: React.MouseEvent<any>) => {
e.stopPropagation();
setAnchorEl(null);
};
@@ -36,8 +38,9 @@ export const ActionsMenu = ({ items }: { items: ActionMenuItem[] }) => {
{items.map((item) => (
<MenuItem
key={item.id}
onClick={() => {
if (item.closeMenu) handleClose();
onClick={(e: React.MouseEvent<HTMLLIElement>) => {
e.stopPropagation();
if (item.closeMenu) handleClose(e);
item.action();
}}
>
@@ -28,12 +28,16 @@ export type Header<T> = {
type DataTableProps<T extends { id?: string | number; _id?: string | number }> = {
headers: Header<T>[];
data: T[];
onRowClick?: (row: T) => void;
};
export function DataTable<T extends { id?: string | number; _id?: string | number }>({
headers,
data,
}: DataTableProps<T>) {
export function DataTable<
T extends {
id?: string | number;
_id?: string | number;
onRowClick?: (row: T) => void;
},
>({ headers, data, onRowClick }: DataTableProps<T>) {
const theme = useTheme();
if (data.length === 0 || headers.length === 0) return <div>No data</div>;
return (
@@ -77,7 +81,11 @@ export function DataTable<T extends { id?: string | number; _id?: string | numbe
const key = row.id || row._id || Math.random();
return (
<TableRow key={key}>
<TableRow
key={key}
sx={{ cursor: onRowClick ? "pointer" : "default" }}
onClick={() => (onRowClick ? onRowClick(row) : null)}
>
{headers.map((header, index) => {
return (
<TableCell
@@ -50,7 +50,7 @@ export const HeaderControls = ({
<Button
loading={isPatching}
onClick={async () => {
await patch();
await patch(`/monitors/${monitor._id}/active`);
refetch();
}}
startIcon={
+6 -2
View File
@@ -58,11 +58,15 @@ export const usePost = <B = any, R = any>(endpoint: string) => {
return { post: postFn, loading, error };
};
export const usePatch = <B = any, R = any>(endpoint: string) => {
export const usePatch = <B = any, R = any>() => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const patchFn = async (body: B, config?: AxiosRequestConfig): Promise<R | null> => {
const patchFn = async (
endpoint: string,
body?: B,
config?: AxiosRequestConfig
): Promise<R | null> => {
setLoading(true);
setError(null);
+1 -5
View File
@@ -52,11 +52,7 @@ const UptimeDetailsPage = () => {
{}
);
const {
patch,
loading: isPatching,
error: postError,
} = usePatch<ApiResponse>(`/monitors/${id}/active`);
const { patch, loading: isPatching, error: postError } = usePatch<ApiResponse>();
const monitor: IMonitor = response?.data?.monitor;
+121 -107
View File
@@ -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: <Typography color={theme.palette.error.main}>Remove</Typography>,
action: () => {
console.log("Open delete");
},
closeMenu: true,
},
];
};
const getHeaders = (theme: any, t: Function, navigate: Function) => {
const headers: Header<IMonitor>[] = [
{
id: "name",
content: t("host"),
render: (row) => {
return row.name;
},
},
{
id: "status",
content: t("status"),
render: (row) => {
return <StatusLabel status={row.status} />;
},
},
{
id: "histogram",
content: t("responseTime"),
render: (row) => {
return (
<Stack alignItems={"center"}>
<HistogramResponseTime checks={row.latestChecks} />
</Stack>
);
},
},
{
id: "type",
content: t("type"),
render: (row) => {
return row.type;
},
},
{
id: "actions",
content: t("actions"),
render: (row) => {
return <ActionsMenu items={getActions(theme, row, navigate)} />;
},
},
];
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<ApiResponse>();
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: <Typography color={theme.palette.error.main}>Remove</Typography>,
action: () => {
console.log("Open delete");
},
closeMenu: true,
},
];
};
const getHeaders = () => {
const headers: Header<IMonitor>[] = [
{
id: "name",
content: t("host"),
render: (row) => {
return row.name;
},
},
{
id: "status",
content: t("status"),
render: (row) => {
return <StatusLabel status={row.status} />;
},
},
{
id: "histogram",
content: t("responseTime"),
render: (row) => {
return (
<Stack alignItems={"center"}>
<HistogramResponseTime checks={row.latestChecks} />
</Stack>
);
},
},
{
id: "type",
content: t("type"),
render: (row) => {
return row.type;
},
},
{
id: "actions",
content: t("actions"),
render: (row) => {
return <ActionsMenu items={getActions(row)} />;
},
},
];
return headers;
};
let headers = getHeaders();
if (isSmall) {
headers = headers.filter((h) => h.id !== "histogram");
@@ -134,6 +145,9 @@ export const MonitorTable = ({ monitors }: { monitors: IMonitor[] }) => {
<Table
headers={headers}
data={monitors}
onRowClick={(row) => {
navigate(row._id);
}}
/>
);
};
+31 -6
View File
@@ -18,8 +18,30 @@ const UptimeMonitors = () => {
const theme = useTheme();
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
const { response, loading } = useGet<ApiResponse>("/monitors?embedChecks=true", {});
const monitors = response?.data ?? ([] as IMonitor[]);
const { response, loading, refetch } = useGet<ApiResponse>(
"/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)}
>
<UpStatusBox n={1} />
<DownStatusBox n={1} />
<PausedStatusBox n={1} />
<UpStatusBox n={monitorStatuses.up} />
<DownStatusBox n={monitorStatuses.down} />
<PausedStatusBox n={monitorStatuses.paused} />
</Stack>
<MonitorTable monitors={monitors} />
<MonitorTable
monitors={monitors}
refetch={refetch}
/>
</BasePage>
);
};