diff --git a/client/src/Components/v2/monitors/charts/HistogramPageSpeed.tsx b/client/src/Components/v2/monitors/charts/HistogramPageSpeed.tsx new file mode 100644 index 000000000..509886434 --- /dev/null +++ b/client/src/Components/v2/monitors/charts/HistogramPageSpeed.tsx @@ -0,0 +1,106 @@ +import Stack from "@mui/material/Stack"; +import { Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip } from "recharts"; +import { HistogramPageSpeedTooltip } from "@/Components/v2/monitors/charts/HistogramPageSpeedTooltip"; + +import { useTheme } from "@mui/material/styles"; +import type { Check } from "@/Types/Check"; +import type { MonitorStatus } from "@/Types/Monitor"; + +import { getStatusColor } from "@/Utils/MonitorUtils"; + +const processData = (checks: Check[]) => { + if (checks.length === 0) return []; + const formattedData: { score: number; date: string }[] = []; + + const calculateScore = (entry: Check) => { + const accessibility = entry?.accessibility || 0; + const bestPractices = entry?.bestPractices || 0; + const performance = entry?.performance || 0; + const seo = entry?.seo || 0; + return Math.floor(((accessibility + bestPractices + performance + seo) / 4) * 100); + }; + + checks.forEach((entry) => { + const score = calculateScore(entry); + const date = entry.createdAt; + formattedData.push({ score, date }); + }); + + return formattedData; +}; + +export const HistogramPageSpeed = ({ + checks, + status, +}: { + checks: Check[]; + status: MonitorStatus; +}) => { + const theme = useTheme(); + const scores = processData(checks ?? []); + const color = getStatusColor(status, theme); + return ( + event.stopPropagation()} + overflow={"hidden"} + sx={{ + cursor: "default", + position: "relative", + }} + > + + + + } + /> + + + + + + + + + + + ); +}; diff --git a/client/src/Components/v2/monitors/charts/HistogramPageSpeedTooltip.tsx b/client/src/Components/v2/monitors/charts/HistogramPageSpeedTooltip.tsx new file mode 100644 index 000000000..6fd18251c --- /dev/null +++ b/client/src/Components/v2/monitors/charts/HistogramPageSpeedTooltip.tsx @@ -0,0 +1,40 @@ +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import { formatDateWithTz } from "@/Utils/TimeUtils"; +import type { TooltipProps } from "recharts"; +import { useTheme } from "@mui/material/styles"; +import { useSelector } from "react-redux"; +import type { RootState } from "@/Types/state"; + +export const HistogramPageSpeedTooltip = ({ + payload, +}: TooltipProps) => { + const uiTimezone = useSelector((state: RootState) => state.ui.timezone); + const theme = useTheme(); + return ( + + + {formatDateWithTz( + payload?.[0]?.payload?.date ?? 0, + "MMMM D, YYYY, HH:mm A", + uiTimezone + )} + + + Score: {payload?.[0]?.payload?.score} + + + ); +}; diff --git a/client/src/Components/v2/monitors/index.tsx b/client/src/Components/v2/monitors/index.tsx index de38d2cab..ca0aaa15e 100644 --- a/client/src/Components/v2/monitors/index.tsx +++ b/client/src/Components/v2/monitors/index.tsx @@ -3,3 +3,5 @@ export * from "./MonitorStatBoxes"; export * from "./charts/HistogramStatus"; export * from "./charts/RadialAvgResponse"; export * from "./charts/HistogramDetails"; +export * from "./charts/HistogramPageSpeed"; +export * from "./charts/HistogramPageSpeedTooltip"; diff --git a/client/src/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable.tsx b/client/src/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable.tsx new file mode 100644 index 000000000..0322948c3 --- /dev/null +++ b/client/src/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable.tsx @@ -0,0 +1,253 @@ +import Stack from "@mui/material/Stack"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import { Table, Pagination, StatusLabel } from "@/Components/v2/design-elements"; +import { HistogramPageSpeed } from "@/Components/v2/monitors"; +import { ArrowDown, ArrowUp } from "lucide-react"; +import type { Header } from "@/Components/v2/design-elements/Table"; +import { ActionsMenu } from "@/Components/v2/actions-menu"; + +import { useTranslation } from "react-i18next"; +import useMediaQuery from "@mui/material/useMediaQuery"; +import { useTheme } from "@mui/material/styles"; +import { useNavigate } from "react-router-dom"; +import { usePatch } from "@/Hooks/UseApi"; + +import type { Check } from "@/Types/Check"; +import type { Monitor, MonitorWithChecks } from "@/Types/Monitor"; +import type { ActionMenuItem } from "@/Components/v2/actions-menu"; + +export const PageSpeedMonitorsTable = ({ + monitors, + refetch, + sortField, + setSortField, + sortOrder, + setSortOrder, + count, + page, + setPage, + rowsPerPage, + setRowsPerPage, +}: { + monitors: MonitorWithChecks[]; + refetch: 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 isSmall = useMediaQuery(theme.breakpoints.down("md")); + const navigate = useNavigate(); + const { + patch, + // loading: isPatching, + // error: postError, + } = usePatch(); + + const handlePageChange = ( + _e: React.MouseEvent | null, + newPage: number + ) => { + setPage(newPage); + }; + + const handleRowsPerPageChange = ( + e: React.ChangeEvent + ) => { + 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(`/pagespeed/configure/${monitor.id}`); + }, + }, + // { + // id: 5, + // label: "Clone", + // action: () => { + + // }, + // }, + { + id: 6, + label: + monitor.isActive === false + ? t("common.buttons.resume") + : t("common.buttons.pause"), + action: async () => { + await patch(`/monitors/${monitor.id}/active`); + refetch(); + }, + closeMenu: true, + }, + { + id: 7, + label: ( + + {t("common.buttons.delete")} + + ), + action: () => { + // setSelectedMonitor(monitor); + }, + closeMenu: true, + }, + ]; + }; + + const getHeaders = () => { + const renderSortIcon = (isActive: boolean) => ( + + {isActive ? ( + sortOrder === "asc" ? ( + + ) : ( + + ) + ) : null} + + ); + + const headers: Header[] = [ + { + id: "name", + content: ( + handleSort(e, "name")} + sx={{ cursor: "pointer" }} + > + {t("common.table.headers.name")} + {renderSortIcon(sortField === "name")} + + ), + render: (row) => { + return row.name; + }, + }, + { + id: "status", + content: ( + handleSort(e, "status")} + sx={{ cursor: "pointer" }} + > + {t("common.table.headers.status")} + {renderSortIcon(sortField === "status")} + + ), + render: (row) => { + return ; + }, + }, + { + id: "histogram", + content: t("pages.pageSpeed.table.headers.pageSpeedScore"), + render: (row) => { + return ( + + + + ); + }, + }, + + { + id: "actions", + content: t("common.table.headers.actions"), + render: (row) => { + return ; + }, + }, + ]; + return headers; + }; + + let headers = getHeaders(); + + if (isSmall) { + headers = headers.filter((h) => h.id !== "histogram"); + } + return ( + + { + navigate(`/pagespeed/${row.id}`); + }} + /> + + + ); +}; diff --git a/client/src/Pages/PageSpeed/Monitors/index.tsx b/client/src/Pages/PageSpeed/Monitors/index.tsx new file mode 100644 index 000000000..158260456 --- /dev/null +++ b/client/src/Pages/PageSpeed/Monitors/index.tsx @@ -0,0 +1,86 @@ +import { + MonitorBasePageWithStates, + // UpStatusBox, + // DownStatusBox, + // PausedStatusBox, +} from "@/Components/v2/design-elements"; +import { HeaderCreate } from "@/Components/v2/common"; +// import Stack from "@mui/material/Stack"; +import { PageSpeedMonitorsTable } from "@/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable"; + +import { useState } from "react"; +import { useTheme } from "@mui/material"; +import { useIsAdmin } from "@/Hooks/useIsAdmin"; +import { useGet } from "@/Hooks/UseApi"; +import type { MonitorsWithChecksResponse } from "@/Types/Monitor"; +import type { AppSettingsResponse } from "@/Types/Settings"; + +const PageSpeedMonitorsPage = () => { + const theme = useTheme(); + const isAdmin = useIsAdmin(); + const monitorsUrl = "/monitors/team/with-checks?type=pagespeed&limit=10"; + const { + data: monitorsData, + isLoading: monitorsIsLoading, + error: monitorsError, + refetch, + } = useGet(monitorsUrl); + + const settingsUrl = "/settings"; + const { + data: settingsData, + isLoading: settingsIsLoading, + error: settingsError, + } = useGet(settingsUrl); + + const monitors = monitorsData?.monitors; + const monitorsCount = monitorsData?.count; + // const summary = monitorsData?.summary; + const settings = settingsData; + + const isLoading = monitorsIsLoading || settingsIsLoading; + + const [sortField, setSortField] = useState("name"); + const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc"); + const [page, setPage] = useState(0); + const [rowsPerPage, setRowsPerPage] = useState(10); + + return ( + + + {/* + + + + */} + + + ); +}; + +export default PageSpeedMonitorsPage; diff --git a/client/src/Pages/PageSpeed/Monitors/index.jsx b/client/src/Pages/PageSpeed/Monitors/old.jsx similarity index 100% rename from client/src/Pages/PageSpeed/Monitors/index.jsx rename to client/src/Pages/PageSpeed/Monitors/old.jsx diff --git a/client/src/Pages/Uptime/Monitors/Components/UptimeMonitorsTable.tsx b/client/src/Pages/Uptime/Monitors/Components/UptimeMonitorsTable.tsx index 21e014688..59daf1702 100644 --- a/client/src/Pages/Uptime/Monitors/Components/UptimeMonitorsTable.tsx +++ b/client/src/Pages/Uptime/Monitors/Components/UptimeMonitorsTable.tsx @@ -97,7 +97,7 @@ export const MonitorTable = ({ id: 4, label: t("pages.common.monitors.actions.configure"), action: () => { - navigate(`/uptime/create/${monitor.id}`); + navigate(`/uptime/configure/${monitor.id}`); }, }, // { diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx index 715d4374e..c9a1084d6 100644 --- a/client/src/Routes/index.jsx +++ b/client/src/Routes/index.jsx @@ -20,7 +20,7 @@ import UptimeDetails from "../Pages/Uptime/Details"; import UptimeCreate from "../Pages/Uptime/Create/index.jsx"; // PageSpeed -import PageSpeed from "../Pages/PageSpeed/Monitors/index.jsx"; +import PageSpeed from "../Pages/PageSpeed/Monitors/index"; import PageSpeedDetails from "../Pages/PageSpeed/Details/index.jsx"; import PageSpeedCreate from "../Pages/PageSpeed/Create/index.jsx"; @@ -63,6 +63,7 @@ import Logs from "../Pages/Logs/index.jsx"; const Routes = () => { const mode = useSelector((state) => state.ui.mode); const AdminCheckedRegister = withAdminCheck(AuthRegister); + const v2theme = mode === "light" ? lightTheme : darkTheme; return ( { path="/uptime" element={ <> - + @@ -105,7 +106,7 @@ const Routes = () => { path="/uptime/:monitorId/" element={ <> - + @@ -118,7 +119,13 @@ const Routes = () => { } + element={ + <> + + + + + } />