diff --git a/client/src/Components/v2/common/controls/HeaderMonitorControls.tsx b/client/src/Components/v2/common/controls/HeaderMonitorControls.tsx
new file mode 100644
index 000000000..9a1f99380
--- /dev/null
+++ b/client/src/Components/v2/common/controls/HeaderMonitorControls.tsx
@@ -0,0 +1,19 @@
+import Stack from "@mui/material/Stack";
+import { MonitorStatus } from "@/Components/v2/design-elements";
+
+import type { Monitor } from "@/Types/Monitor.js";
+
+interface HeaderMonitorControlsProps {
+ monitor: Monitor;
+}
+
+export const HeaderMonitorControls = ({ monitor }: HeaderMonitorControlsProps) => {
+ return (
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/common/index.ts b/client/src/Components/v2/common/index.ts
index e354983eb..16baac7fe 100644
--- a/client/src/Components/v2/common/index.ts
+++ b/client/src/Components/v2/common/index.ts
@@ -2,3 +2,4 @@ export * from "../common/charts/HistogramResponseTime";
export * from "../common/charts/HeatmapResponseTime";
export * from "../common/charts/HeatmapResponseTimeTooltip";
export * from "../common/controls/HeaderCreate";
+export * from "../common/controls/HeaderMonitorControls";
diff --git a/client/src/Components/v2/design-elements/BasePage.tsx b/client/src/Components/v2/design-elements/BasePage.tsx
index c853a8d30..c8618fa67 100644
--- a/client/src/Components/v2/design-elements/BasePage.tsx
+++ b/client/src/Components/v2/design-elements/BasePage.tsx
@@ -55,7 +55,7 @@ export const BasePage = ({
spacing={theme.spacing(10)}
{...props}
>
-
+
{children}
);
diff --git a/client/src/Components/v2/design-elements/Dot.tsx b/client/src/Components/v2/design-elements/Dot.tsx
new file mode 100644
index 000000000..541e90812
--- /dev/null
+++ b/client/src/Components/v2/design-elements/Dot.tsx
@@ -0,0 +1,23 @@
+import type { CSSProperties } from "react";
+
+interface DotProps {
+ color?: string;
+ size?: string;
+ style?: CSSProperties;
+}
+
+export const Dot = ({ color = "gray", size = "4px", style }: DotProps) => {
+ return (
+
+ );
+};
diff --git a/client/src/Components/v2/design-elements/MonitorStatus.tsx b/client/src/Components/v2/design-elements/MonitorStatus.tsx
new file mode 100644
index 000000000..0ecb77df8
--- /dev/null
+++ b/client/src/Components/v2/design-elements/MonitorStatus.tsx
@@ -0,0 +1,59 @@
+import type { Monitor } from "@/Types/Monitor";
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import { PulseDot, Dot } from "@/Components/v2/design-elements";
+import { getStatusColor, formatUrl } from "@/Utils/MonitorUtils";
+import { useTheme } from "@mui/material/styles";
+import prettyMilliseconds from "pretty-ms";
+import { typographyLevels } from "@/Utils/Theme/v2Palette";
+import useMediaQuery from "@mui/material/useMediaQuery";
+export const MonitorStatus = ({ monitor }: { monitor: Monitor }) => {
+ const theme = useTheme();
+ const isSmall = useMediaQuery(theme.breakpoints.down("md"));
+
+ if (!monitor) {
+ return null;
+ }
+ return (
+
+
+ {monitor.name}
+
+
+
+
+ {formatUrl(monitor?.url)}
+
+ {!isSmall && (
+ <>
+
+
+ Checking every {prettyMilliseconds(monitor?.interval, { verbose: true })}
+
+ >
+ )}
+
+
+ );
+};
diff --git a/client/src/Components/v2/design-elements/PulseDot.tsx b/client/src/Components/v2/design-elements/PulseDot.tsx
new file mode 100644
index 000000000..ab792b628
--- /dev/null
+++ b/client/src/Components/v2/design-elements/PulseDot.tsx
@@ -0,0 +1,48 @@
+import { Box, Stack, useTheme } from "@mui/material";
+
+interface PulseDotProps {
+ color: string;
+}
+
+export const PulseDot = ({ color }: PulseDotProps) => {
+ const theme = useTheme();
+
+ return (
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/design-elements/index.tsx b/client/src/Components/v2/design-elements/index.tsx
index 374dabef7..ce2a3b48d 100644
--- a/client/src/Components/v2/design-elements/index.tsx
+++ b/client/src/Components/v2/design-elements/index.tsx
@@ -8,3 +8,6 @@ export * from "./BaseBox";
export * from "./Table";
export { DataTable as Table } from "./Table";
export * from "./StatusLabel";
+export * from "./PulseDot";
+export * from "./Dot";
+export * from "./MonitorStatus";
diff --git a/client/src/Pages/Uptime/Details/index.tsx b/client/src/Pages/Uptime/Details/index.tsx
new file mode 100644
index 000000000..b2f32276b
--- /dev/null
+++ b/client/src/Pages/Uptime/Details/index.tsx
@@ -0,0 +1,96 @@
+import { useState, useMemo } from "react";
+import { useParams } from "react-router-dom";
+import { useSelector } from "react-redux";
+import { useGet } from "@/Hooks/UseApi";
+import type { MonitorDetailsResponse } from "@/Types/Monitor";
+import type { ChecksResponse } from "@/Types/Check";
+import type { RootState } from "@/Types/state";
+import useCertificateFetch from "./Hooks/useCertificateFetch";
+import { BasePage } from "@/Components/v2/design-elements";
+import { HeaderMonitorControls } from "@/Components/v2/common";
+
+const certificateDateFormat = "MMM D, YYYY h A";
+
+const UptimeDetailsPage = () => {
+ const { monitorId } = useParams<{ monitorId: string }>();
+ const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
+
+ const [page, setPage] = useState(0);
+ const [rowsPerPage, setRowsPerPage] = useState(5);
+ const [dateRange, setDateRange] = useState("recent");
+
+ const monitorDetailsUrl = useMemo(() => {
+ if (!monitorId) {
+ return null;
+ }
+ const params = new URLSearchParams();
+ params.append("dateRange", dateRange);
+ params.append("normalize", "true");
+ return `/monitors/uptime/details/${monitorId}?${params.toString()}`;
+ }, [monitorId, dateRange]);
+
+ const {
+ data: monitorDetailsData,
+ isLoading: monitorIsLoading,
+ error: monitorError,
+ refetch: refetchMonitor,
+ } = useGet(monitorDetailsUrl);
+
+ const monitorData = monitorDetailsData?.monitorData;
+ const monitor = monitorData?.monitor;
+ const monitorStats = monitorDetailsData?.monitorStats ?? null;
+
+ const [certificateExpiry] = useCertificateFetch({
+ monitor,
+ monitorId,
+ certificateDateFormat,
+ uiTimezone,
+ });
+
+ const checksUrl = useMemo(() => {
+ if (!monitorId || !monitor?.type) {
+ return null;
+ }
+ const params = new URLSearchParams();
+ params.append("type", monitor.type);
+ params.append("sortOrder", "desc");
+ params.append("dateRange", dateRange);
+ params.append("page", String(page));
+ params.append("rowsPerPage", String(rowsPerPage));
+ return `/checks/${monitorId}?${params.toString()}`;
+ }, [monitorId, monitor?.type, dateRange, page, rowsPerPage]);
+
+ const {
+ data: checksData,
+ isLoading: checksIsLoading,
+ error: checksError,
+ } = useGet(checksUrl);
+
+ const checks = checksData?.checks ?? [];
+ const checksCount = checksData?.checksCount ?? 0;
+
+ // TODO: Add UI components here
+ // Placeholder to consume all data fetching results
+ console.log({
+ monitorIsLoading,
+ monitorError,
+ refetchMonitor,
+ monitorStats,
+ certificateExpiry,
+ checksIsLoading,
+ checksError,
+ checks,
+ checksCount,
+ setPage,
+ setRowsPerPage,
+ setDateRange,
+ });
+
+ return (
+
+
+
+ );
+};
+
+export default UptimeDetailsPage;
diff --git a/client/src/Pages/Uptime/Details/index.jsx b/client/src/Pages/Uptime/Details/old.jsx
similarity index 100%
rename from client/src/Pages/Uptime/Details/index.jsx
rename to client/src/Pages/Uptime/Details/old.jsx
diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx
index e37bae063..715d4374e 100644
--- a/client/src/Routes/index.jsx
+++ b/client/src/Routes/index.jsx
@@ -16,7 +16,7 @@ import AuthNewPasswordConfirmed from "../Pages/Auth/NewPasswordConfirmed.jsx";
// Uptime
import Uptime from "../Pages/Uptime/Monitors";
-import UptimeDetails from "../Pages/Uptime/Details/index.jsx";
+import UptimeDetails from "../Pages/Uptime/Details";
import UptimeCreate from "../Pages/Uptime/Create/index.jsx";
// PageSpeed
@@ -103,7 +103,13 @@ const Routes = () => {
/>
}
+ element={
+ <>
+
+
+
+ >
+ }
/>