uptime
This commit is contained in:
1 parent
c2dece862c
commit
a0a8ceb0db
27 files changed
+2326
-94
No files matched your search
@@ -0,0 +1,276 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Box from "@mui/material/Box";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import {
|
||||
Table,
|
||||
type Header,
|
||||
Pagination,
|
||||
StatusLabel,
|
||||
} from "@/Components/v2/design-elements";
|
||||
import { HeatmapResponseTime, HistogramResponseTime } from "@/Components/v2/common";
|
||||
import { ActionsMenu } from "@/Components/v2/actions-menu";
|
||||
import { ArrowDown, ArrowUp } from "lucide-react";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { usePost } from "@/Hooks/UseApi";
|
||||
|
||||
import type { Check } from "@/Types/Check";
|
||||
import type { Monitor } from "@/Types/Monitor";
|
||||
import type { ActionMenuItem } from "@/Components/v2/actions-menu";
|
||||
// import type { ChartType } from "@/features/uiSlice";
|
||||
|
||||
export const MonitorTable = ({
|
||||
monitors,
|
||||
refetch,
|
||||
// setSelectedMonitor,
|
||||
// sortField,
|
||||
// setSortField,
|
||||
// sortOrder,
|
||||
// setSortOrder,
|
||||
count,
|
||||
page,
|
||||
setPage,
|
||||
rowsPerPage,
|
||||
setRowsPerPage,
|
||||
}: {
|
||||
monitors: Monitor[];
|
||||
refetch: Function;
|
||||
// setSelectedMonitor: Function;
|
||||
// sortField: string;
|
||||
// setSortField: (field: string) => void;
|
||||
// sortOrder: "asc" | "desc";
|
||||
// setSortOrder: (order: "asc" | "desc") => void;
|
||||
count: number;
|
||||
page: number;
|
||||
setPage: (page: number) => void;
|
||||
rowsPerPage: number;
|
||||
setRowsPerPage: (rowsPerPage: number) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const chartType = "histogram";
|
||||
const {
|
||||
post,
|
||||
// loading: isPosting,
|
||||
// error: postError,
|
||||
} = usePost<any, Monitor>();
|
||||
|
||||
// const handlePageChange = (
|
||||
// _e: React.MouseEvent<HTMLButtonElement> | null,
|
||||
// newPage: number
|
||||
// ) => {
|
||||
// setPage(newPage);
|
||||
// };
|
||||
|
||||
// const handleRowsPerPageChange = (
|
||||
// e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>
|
||||
// ) => {
|
||||
// const value = Number(e.target.value);
|
||||
// setPage(0);
|
||||
// setRowsPerPage(value);
|
||||
// };
|
||||
|
||||
// const handleSort = (e: any, field: string) => {
|
||||
// e.preventDefault();
|
||||
// e.stopPropagation();
|
||||
// if (sortField === field) {
|
||||
// const newOrder = sortOrder === "asc" ? "desc" : "asc";
|
||||
// setSortOrder(newOrder);
|
||||
// } else {
|
||||
// setSortField(field);
|
||||
// setSortOrder("asc");
|
||||
// }
|
||||
// refetch();
|
||||
// };
|
||||
|
||||
const getActions = (monitor: Monitor): ActionMenuItem[] => {
|
||||
return [
|
||||
{
|
||||
id: 1,
|
||||
label: t("pages.common.monitors.actions.openSite"),
|
||||
action: () => {
|
||||
window.open(monitor.url, "_blank", "noreferrer");
|
||||
},
|
||||
closeMenu: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
label: t("pages.common.monitors.actions.details"),
|
||||
action: () => {
|
||||
navigate(`${monitor.id}`);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
label: t("pages.common.monitors.actions.incidents"),
|
||||
action: () => {
|
||||
navigate(`/incidents?monitorId=${monitor.id}`);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
label: t("pages.common.monitors.actions.configure"),
|
||||
action: () => {
|
||||
navigate(`/uptime/create/${monitor.id}`);
|
||||
},
|
||||
},
|
||||
// {
|
||||
// id: 5,
|
||||
// label: "Clone",
|
||||
// action: () => {
|
||||
|
||||
// },
|
||||
// },
|
||||
{
|
||||
id: 6,
|
||||
label:
|
||||
monitor.isActive === false
|
||||
? t("pages.common.monitors.actions.resume")
|
||||
: t("pages.common.monitors.actions.pause"),
|
||||
action: async () => {
|
||||
await post(`/monitors/pause/${monitor.id}`, {});
|
||||
refetch();
|
||||
},
|
||||
closeMenu: true,
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
label: (
|
||||
<Typography color={theme.palette.error.main}>
|
||||
{t("pages.common.monitors.actions.delete")}
|
||||
</Typography>
|
||||
),
|
||||
action: () => {
|
||||
// setSelectedMonitor(monitor);
|
||||
},
|
||||
closeMenu: true,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const getHeaders = (chartType: string) => {
|
||||
const renderSortIcon = (isActive: boolean) => (
|
||||
<Box
|
||||
width={16}
|
||||
display="inline-flex"
|
||||
justifyContent="center"
|
||||
>
|
||||
{/* {isActive ? (
|
||||
sortOrder === "asc" ? (
|
||||
<ArrowUp size={16} />
|
||||
) : (
|
||||
<ArrowDown size={16} />
|
||||
)
|
||||
) : null} */}
|
||||
</Box>
|
||||
);
|
||||
const headers: Header<Monitor>[] = [
|
||||
{
|
||||
id: "name",
|
||||
content: (
|
||||
<Stack
|
||||
gap={theme.spacing(4)}
|
||||
direction={"row"}
|
||||
alignItems={"center"}
|
||||
// onClick={(e) => handleSort(e, "name")}
|
||||
sx={{ cursor: "pointer" }}
|
||||
>
|
||||
{t("common.table.headers.name")}
|
||||
{/* {renderSortIcon(sortField === "name")} */}
|
||||
</Stack>
|
||||
),
|
||||
|
||||
render: (row) => {
|
||||
return row?.name;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "status",
|
||||
content: (
|
||||
<Stack
|
||||
gap={theme.spacing(4)}
|
||||
direction={"row"}
|
||||
justifyContent={"center"}
|
||||
alignItems={"center"}
|
||||
// onClick={(e) => handleSort(e, "status")}
|
||||
sx={{ cursor: "pointer" }}
|
||||
>
|
||||
{t("common.table.headers.status")}
|
||||
{/* {renderSortIcon(sortField === "status")} */}
|
||||
</Stack>
|
||||
),
|
||||
render: (row) => {
|
||||
return (
|
||||
<StatusLabel
|
||||
status={row.status}
|
||||
isActive={row.isActive}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "histogram",
|
||||
content: t("pages.uptime.table.headers.responseTime"),
|
||||
render: (row) => {
|
||||
if (chartType === "histogram") {
|
||||
return <HistogramResponseTime checks={row.checks} />;
|
||||
} else {
|
||||
return <HeatmapResponseTime checks={row.checks} />;
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "type",
|
||||
content: (
|
||||
<Stack
|
||||
gap={theme.spacing(4)}
|
||||
direction={"row"}
|
||||
justifyContent={"center"}
|
||||
alignItems={"center"}
|
||||
// onClick={(e) => handleSort(e, "type")}
|
||||
sx={{ cursor: "pointer" }}
|
||||
>
|
||||
{t("common.table.headers.type")}
|
||||
{/* {renderSortIcon(sortField === "type")} */}
|
||||
</Stack>
|
||||
),
|
||||
render: (row) => {
|
||||
return row.type;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
content: t("common.table.headers.actions"),
|
||||
render: (row) => {
|
||||
return <ActionsMenu items={getActions(row)} />;
|
||||
},
|
||||
},
|
||||
];
|
||||
return headers;
|
||||
};
|
||||
|
||||
const headers = getHeaders(chartType);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Table
|
||||
headers={headers}
|
||||
data={monitors}
|
||||
onRowClick={(row) => {
|
||||
navigate(`/uptime/${row.id}`);
|
||||
}}
|
||||
/>
|
||||
<Pagination
|
||||
component="div"
|
||||
count={count}
|
||||
page={page}
|
||||
rowsPerPage={rowsPerPage}
|
||||
onPageChange={(_e, newPage) => setPage(newPage)}
|
||||
onRowsPerPageChange={(e) => setRowsPerPage(Number(e.target.value))}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,39 +1,86 @@
|
||||
import { useGet } from "@/Hooks/UseApi";
|
||||
import { MonitorBasePageWithStates } from "@/Components/v2/design-elements";
|
||||
import type { Monitor, MonitorType, MonitorsWithChecksResponse } from "@/Types/Monitor";
|
||||
import { FilterControls } from "@/Components/v2/monitors";
|
||||
import { useState, useMemo } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import {
|
||||
MonitorBasePageWithStates,
|
||||
UpStatusBox,
|
||||
DownStatusBox,
|
||||
PausedStatusBox,
|
||||
} from "@/Components/v2/design-elements";
|
||||
import { TextField } from "@/Components/v2/inputs";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import { MonitorTable } from "@/Pages/Uptime/Monitors/Components/UptimeMonitorsTable";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||
import { useGet } from "@/Hooks/UseApi";
|
||||
import type { Monitor, MonitorType, MonitorsWithChecksResponse } from "@/Types/Monitor";
|
||||
import { useState, useMemo, useCallback } from "react";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { setRowsPerPage } from "@/Features/UI/uiSlice.js";
|
||||
|
||||
import type { RootState } from "@/Types/state";
|
||||
import { useTheme } from "@mui/material";
|
||||
|
||||
const UptimeMonitorsPage = () => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// Redux state
|
||||
const rowsPerPage = useSelector((state: RootState) => state.ui.monitors.rowsPerPage);
|
||||
const rowsPerPage = useSelector(
|
||||
(state: RootState) => state.ui?.monitors?.rowsPerPage ?? 10
|
||||
);
|
||||
|
||||
// Local state
|
||||
const [selectedTypes, setSelectedTypes] = useState<MonitorType[]>([]);
|
||||
const [selectedStatus, setSelectedStatus] = useState<string>("");
|
||||
const [selectedState, setSelectedState] = useState<string>("");
|
||||
const [page, setPage] = useState(0);
|
||||
const [search, setSearch] = useState("");
|
||||
const [sort, setSort] = useState<
|
||||
{ field: string; order: "asc" | "desc" } | undefined
|
||||
>();
|
||||
const [page, setPage] = useState<number>(0);
|
||||
const [search, setSearch] = useState<string>("");
|
||||
const [sort, setSort] = useState<{ field: string; order: "asc" | "desc" } | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
// Convert filter selections to API filter values
|
||||
// Status: "up" -> true, "down" -> false
|
||||
// State: "active" -> true, "paused" -> false
|
||||
const toFilterStatus = useMemo(() => {
|
||||
if (selectedStatus === "up") return "true";
|
||||
if (selectedStatus === "down") return "false";
|
||||
return undefined;
|
||||
}, [selectedStatus]);
|
||||
|
||||
const toFilterActive = useMemo(() => {
|
||||
if (selectedState === "active") return "true";
|
||||
if (selectedState === "paused") return "false";
|
||||
return undefined;
|
||||
}, [selectedState]);
|
||||
|
||||
// Determine field and filter for the API request
|
||||
// Priority: status > isActive > search > sort
|
||||
const filterLookup = new Map<string | undefined, string>([
|
||||
[toFilterStatus, "status"],
|
||||
[toFilterActive, "isActive"],
|
||||
]);
|
||||
const activeFilter = [...filterLookup].find(([key]) => key !== undefined);
|
||||
const field = activeFilter?.[1] || (search ? "name" : sort?.field);
|
||||
const filter = activeFilter?.[0] || search;
|
||||
|
||||
// Default to all types when none selected
|
||||
const effectiveTypes =
|
||||
selectedTypes.length > 0 ? selectedTypes : ["http", "ping", "docker", "port"];
|
||||
|
||||
// Build URL for monitors with checks
|
||||
const monitorsWithChecksUrl = useMemo(() => {
|
||||
const params = new URLSearchParams();
|
||||
const types =
|
||||
selectedTypes.length > 0 ? selectedTypes : ["http", "ping", "docker", "port"];
|
||||
types.forEach((type) => params.append("type", type));
|
||||
effectiveTypes.forEach((type) => params.append("type", type));
|
||||
params.append("limit", "25");
|
||||
if (page) params.append("page", String(page));
|
||||
if (page !== undefined) params.append("page", String(page));
|
||||
if (rowsPerPage) params.append("rowsPerPage", String(rowsPerPage));
|
||||
if (search) params.append("filter", search);
|
||||
if (sort?.field) params.append("field", sort.field);
|
||||
if (filter) params.append("filter", filter);
|
||||
if (field) params.append("field", field);
|
||||
if (sort?.order) params.append("order", sort.order);
|
||||
return `/monitors/team/with-checks?${params.toString()}`;
|
||||
}, [selectedTypes, page, rowsPerPage, search, sort]);
|
||||
}, [effectiveTypes, page, rowsPerPage, filter, field, sort?.order]);
|
||||
|
||||
// Data fetching
|
||||
const {
|
||||
@@ -46,10 +93,23 @@ const UptimeMonitorsPage = () => {
|
||||
data: monitorsWithChecksData,
|
||||
isLoading: monitorsWithChecksLoading,
|
||||
error: monitorsWithChecksError,
|
||||
} = useGet<MonitorsWithChecksResponse>(monitorsWithChecksUrl);
|
||||
refetch,
|
||||
} = useGet<MonitorsWithChecksResponse>(
|
||||
monitorsWithChecksUrl,
|
||||
{},
|
||||
{ refreshInterval: 5000 }
|
||||
);
|
||||
|
||||
const { monitors: monitorsWithChecks, summary, count } = monitorsWithChecksData ?? {};
|
||||
|
||||
// Handlers
|
||||
const handleClearFilters = useCallback(() => {
|
||||
setSelectedTypes([]);
|
||||
setSelectedStatus("");
|
||||
setSelectedState("");
|
||||
setSearch("");
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<MonitorBasePageWithStates
|
||||
loading={isLoading || monitorsWithChecksLoading}
|
||||
@@ -58,15 +118,53 @@ const UptimeMonitorsPage = () => {
|
||||
page="uptime"
|
||||
actionLink="/uptime/create"
|
||||
>
|
||||
<FilterControls
|
||||
selectedTypes={selectedTypes}
|
||||
setSelectedTypes={setSelectedTypes}
|
||||
selectedStatus={selectedStatus}
|
||||
setSelectedStatus={setSelectedStatus}
|
||||
selectedState={selectedState}
|
||||
setSelectedState={setSelectedState}
|
||||
<Stack
|
||||
direction={isSmall ? "column" : "row"}
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<UpStatusBox n={summary?.upMonitors || 0} />
|
||||
<DownStatusBox n={summary?.downMonitors || 0} />
|
||||
<PausedStatusBox n={summary?.pausedMonitors || 0} />
|
||||
</Stack>
|
||||
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent={"space-between"}
|
||||
>
|
||||
<FilterControls
|
||||
selectedTypes={selectedTypes}
|
||||
setSelectedTypes={setSelectedTypes}
|
||||
selectedStatus={selectedStatus}
|
||||
setSelectedStatus={setSelectedStatus}
|
||||
selectedState={selectedState}
|
||||
setSelectedState={setSelectedState}
|
||||
onClearFilters={handleClearFilters}
|
||||
/>
|
||||
<TextField
|
||||
placeholder={t("pages.uptime.filters.search.placeholder")}
|
||||
value={search}
|
||||
onChange={(event) => {
|
||||
setSearch(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
<MonitorTable
|
||||
monitors={monitorsWithChecks || []}
|
||||
refetch={refetch}
|
||||
count={count || 0}
|
||||
page={page}
|
||||
setPage={setPage}
|
||||
rowsPerPage={rowsPerPage}
|
||||
setRowsPerPage={(e: any) => {
|
||||
dispatch(
|
||||
setRowsPerPage({
|
||||
value: parseInt(e.target.value, 10),
|
||||
table: "monitors",
|
||||
})
|
||||
);
|
||||
setPage(0);
|
||||
}}
|
||||
/>
|
||||
<pre>{JSON.stringify(monitors, null, 2)}</pre>
|
||||
</MonitorBasePageWithStates>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user