diff --git a/client/src/Components/v2/DesignElements/StatBox.tsx b/client/src/Components/v2/DesignElements/StatBox.tsx new file mode 100644 index 000000000..e65454188 --- /dev/null +++ b/client/src/Components/v2/DesignElements/StatBox.tsx @@ -0,0 +1,61 @@ +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import Box from "@mui/material/Box"; +import { useTheme } from "@mui/material/styles"; +import { useMediaQuery } from "@mui/material"; +import type { PaletteKey } from "@/Utils/Theme/v2/theme"; + +type GradientBox = React.PropsWithChildren<{ palette?: PaletteKey }>; + +export const GradientBox: React.FC = ({ children, palette }) => { + const theme = useTheme(); + const isSmall = useMediaQuery(theme.breakpoints.down("md")); + const bg = palette + ? `linear-gradient(to bottom right, ${theme.palette[palette].main} 30%, ${theme.palette[palette].lowContrast} 70%)` + : `linear-gradient(340deg, ${theme.palette.tertiary.main} 10%, ${theme.palette.primary.main} 45%)`; + + return ( + + {children} + + ); +}; + +type StatBoxProps = React.PropsWithChildren<{ + title: string; + subtitle: string; + palette?: PaletteKey; +}>; + +export const StatBox: React.FC = ({ + title, + subtitle, + palette, + children, +}) => { + const theme = useTheme(); + const textColor = palette ? theme.palette[palette].contrastText : "inherit"; + + return ( + + + {title} + {subtitle} + {children} + + + ); +}; diff --git a/client/src/Components/v2/DesignElements/index.tsx b/client/src/Components/v2/DesignElements/index.tsx index 41b4b319a..5e2f69c69 100644 --- a/client/src/Components/v2/DesignElements/index.tsx +++ b/client/src/Components/v2/DesignElements/index.tsx @@ -2,3 +2,4 @@ export { SplitBox as HorizontalSplitBox, ConfigBox } from "./SplitBox"; export { BasePage } from "./BasePage"; export { BGBox, UpStatusBox, DownStatusBox, PausedStatusBox } from "./StatusBox"; export { DataTable as Table } from "./Table"; +export { GradientBox, StatBox } from "./StatBox"; diff --git a/client/src/Pages/v2/Uptime/Details.tsx b/client/src/Pages/v2/Uptime/Details.tsx index 5f743913a..eb8f986ad 100644 --- a/client/src/Pages/v2/Uptime/Details.tsx +++ b/client/src/Pages/v2/Uptime/Details.tsx @@ -1,17 +1,27 @@ import { BasePage } from "@/Components/v2/DesignElements"; import { HeaderControls } from "@/Components/v2/Monitors/HeaderControls"; +import Stack from "@mui/material/Stack"; +import { StatBox } from "@/Components/v2/DesignElements"; +import { useTheme } from "@mui/material/styles"; import { useParams } from "react-router"; import { useGet, usePatch, type ApiResponse } from "@/Hooks/v2/UseApi"; import { useState } from "react"; +import { getStatusPalette } from "@/Utils/MonitorUtils"; +import prettyMilliseconds from "pretty-ms"; + const UptimeDetailsPage = () => { const { id } = useParams(); + const theme = useTheme(); // Local state const [range, setRange] = useState("30m"); const { response, loading, error, refetch } = useGet( - `/monitors/${id}?range=${range}` + `/monitors/${id}?embedChecks=true&range=${range}`, + + {}, + { refreshInterval: 30000 } ); const { patch, @@ -19,11 +29,27 @@ const UptimeDetailsPage = () => { error: postError, } = usePatch(`/monitors/${id}/active`); - const monitor = response?.data || null; + const monitor = response?.data?.monitor || null; if (!monitor) { return null; } + const stats = response?.data?.stats || null; + + const streakDuration = stats?.currentStreakStartedAt + ? Date.now() - stats?.currentStreakStartedAt + : 0; + + const lastChecked = stats?.lastCheckTimestamp + ? Date.now() - stats?.lastCheckTimestamp + : -1; + + const checks = response?.data?.checks || null; + + console.log(response); + + const palette = getStatusPalette(monitor.status); + return ( { isPatching={isPatching} refetch={refetch} /> + + + = 0 + ? `${prettyMilliseconds(lastChecked, { secondsDecimalDigits: 0 })} ago` + : "N/A" + } + /> + + ); }; diff --git a/client/src/Pages/v2/Uptime/UptimeMonitors.tsx b/client/src/Pages/v2/Uptime/UptimeMonitors.tsx index e69da7ebb..8980c8260 100644 --- a/client/src/Pages/v2/Uptime/UptimeMonitors.tsx +++ b/client/src/Pages/v2/Uptime/UptimeMonitors.tsx @@ -18,7 +18,7 @@ const UptimeMonitors = () => { const theme = useTheme(); const isSmall = useMediaQuery(theme.breakpoints.down("md")); - const { response, loading } = useGet("/monitors?embedChecks=true"); + const { response, loading } = useGet("/monitors?embedChecks=true", {}); const monitors = response?.data ?? ([] as IMonitor[]); if (monitors.length === 0 && !loading) { diff --git a/client/src/Utils/MonitorUtils.ts b/client/src/Utils/MonitorUtils.ts index 92afa8371..5f598ba33 100644 --- a/client/src/Utils/MonitorUtils.ts +++ b/client/src/Utils/MonitorUtils.ts @@ -1,4 +1,13 @@ import type { MonitorStatus } from "@/Types/Monitor"; +import type { PaletteKey } from "./Theme/v2/theme"; +export const getStatusPalette = (status: MonitorStatus): PaletteKey => { + const paletteMap: Record = { + up: "success", + down: "error", + initializing: "warning", + }; + return paletteMap[status]; +}; export const getStatusColor = (status: MonitorStatus, theme: any): string => { const statusColors: Record = { diff --git a/client/src/Utils/Theme/v2/theme.ts b/client/src/Utils/Theme/v2/theme.ts index 3d68613b9..d2581e236 100644 --- a/client/src/Utils/Theme/v2/theme.ts +++ b/client/src/Utils/Theme/v2/theme.ts @@ -1,5 +1,12 @@ import { createTheme } from "@mui/material"; import { lightPalette, darkPalette, typographyLevels } from "./palette"; + +import type { Theme } from "@mui/material/styles"; + +export type PaletteKey = { + [K in keyof Theme["palette"]]: Theme["palette"][K] extends { main: any } ? K : never; +}[keyof Theme["palette"]]; + const fontFamilyPrimary = '"Inter" , sans-serif'; const shadow = "0px 4px 24px -4px rgba(16, 24, 40, 0.08), 0px 3px 3px -3px rgba(16, 24, 40, 0.03)";