diff --git a/client/package.json b/client/package.json
index 71f088909..376671942 100644
--- a/client/package.json
+++ b/client/package.json
@@ -47,14 +47,6 @@
"vite-plugin-svgr": "^4.2.0",
"zod": "4.1.11"
},
- "unusedDepencies": {
- "@solana/wallet-adapter-base": "0.9.25",
- "@solana/wallet-adapter-material-ui": "0.16.35",
- "@solana/wallet-adapter-react": "0.15.37",
- "@solana/wallet-adapter-react-ui": "0.9.37",
- "@solana/wallet-adapter-wallets": "0.19.34",
- "@solana/web3.js": "1.98.0"
- },
"devDependencies": {
"@types/node": "24.5.2",
"@types/react": "^18.2.66",
diff --git a/client/src/App.jsx b/client/src/App.jsx
index 04f0d5b65..d21850ebb 100644
--- a/client/src/App.jsx
+++ b/client/src/App.jsx
@@ -9,7 +9,6 @@ import { CssBaseline, GlobalStyles } from "@mui/material";
import { logger } from "./Utils/Logger"; // Import the logger
import { networkService } from "./main";
import { Routes } from "./Routes";
-import WalletProvider from "./Components/WalletProvider";
import AppLayout from "@/Components/v1/Layouts/AppLayout";
function App() {
@@ -24,16 +23,12 @@ function App() {
}, []);
return (
- /* Extract Themeprovider, baseline and global styles to Styles */
-
-
-
-
-
-
-
-
+
+
+
+
+
);
}
diff --git a/client/src/Components/v2/DesignElements/StatBox.tsx b/client/src/Components/v2/DesignElements/StatBox.tsx
index e5d4dcef5..3450444b2 100644
--- a/client/src/Components/v2/DesignElements/StatBox.tsx
+++ b/client/src/Components/v2/DesignElements/StatBox.tsx
@@ -1,6 +1,5 @@
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";
diff --git a/client/src/Components/v2/DesignElements/StatusLabel.tsx b/client/src/Components/v2/DesignElements/StatusLabel.tsx
new file mode 100644
index 000000000..69b788e96
--- /dev/null
+++ b/client/src/Components/v2/DesignElements/StatusLabel.tsx
@@ -0,0 +1,35 @@
+import Box from "@mui/material/Box";
+import { BaseBox } from "@/Components/v2/DesignElements";
+import type { MonitorStatus } from "@/Types/Monitor";
+
+import { getStatusPalette } from "@/Utils/MonitorUtils";
+import { useTheme } from "@mui/material/styles";
+
+export const StatusLabel = ({ status }: { status: MonitorStatus }) => {
+ const theme = useTheme();
+ const palette = getStatusPalette(status);
+ const transformedText = status.charAt(0).toUpperCase() + status.slice(1).toLowerCase();
+
+ return (
+
+
+ {transformedText}
+
+ );
+};
diff --git a/client/src/Components/v2/DesignElements/Table.tsx b/client/src/Components/v2/DesignElements/Table.tsx
index 6296d260c..58cc6aed1 100644
--- a/client/src/Components/v2/DesignElements/Table.tsx
+++ b/client/src/Components/v2/DesignElements/Table.tsx
@@ -5,7 +5,19 @@ import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
+
+import IconButton from "@mui/material/IconButton";
+import LastPageIcon from "@mui/icons-material/LastPage";
+import FirstPageIcon from "@mui/icons-material/FirstPage";
+import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft";
+import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight";
+
+import Box from "@mui/material/Box";
+import TablePagination from "@mui/material/TablePagination";
+import type { TablePaginationProps } from "@mui/material/TablePagination";
+
import { useTheme } from "@mui/material/styles";
+import { useMediaQuery } from "@mui/material";
export type Header = {
id: number | string;
content: React.ReactNode;
@@ -87,3 +99,109 @@ export function DataTable
);
}
+
+interface TablePaginationActionsProps {
+ count: number;
+ page: number;
+ rowsPerPage: number;
+ onPageChange: (event: React.MouseEvent, newPage: number) => void;
+}
+
+function TablePaginationActions(props: TablePaginationActionsProps) {
+ const theme = useTheme();
+ const { count, page, rowsPerPage, onPageChange } = props;
+
+ const handleFirstPageButtonClick = (event: React.MouseEvent) => {
+ onPageChange(event, 0);
+ };
+
+ const handleBackButtonClick = (event: React.MouseEvent) => {
+ onPageChange(event, page - 1);
+ };
+
+ const handleNextButtonClick = (event: React.MouseEvent) => {
+ onPageChange(event, page + 1);
+ };
+
+ const handleLastPageButtonClick = (event: React.MouseEvent) => {
+ onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1));
+ };
+
+ return (
+
+
+ {theme.direction === "rtl" ? : }
+
+
+ {theme.direction === "rtl" ? : }
+
+ = Math.ceil(count / rowsPerPage) - 1}
+ aria-label="next page"
+ >
+ {theme.direction === "rtl" ? : }
+
+ = Math.ceil(count / rowsPerPage) - 1}
+ aria-label="last page"
+ >
+ {theme.direction === "rtl" ? : }
+
+
+ );
+}
+
+export const Pagination: React.FC = ({ ...props }) => {
+ const isSmall = useMediaQuery((theme: any) => theme.breakpoints.down("sm"));
+ const theme = useTheme();
+ return (
+
+ );
+};
diff --git a/client/src/Components/v2/DesignElements/index.tsx b/client/src/Components/v2/DesignElements/index.tsx
index 359982c42..6ba0a19ac 100644
--- a/client/src/Components/v2/DesignElements/index.tsx
+++ b/client/src/Components/v2/DesignElements/index.tsx
@@ -1,6 +1,7 @@
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 { DataTable as Table, Pagination } from "./Table";
export { GradientBox, StatBox } from "./StatBox";
export { BaseBox } from "./BaseBox";
+export { StatusLabel } from "./StatusLabel";
diff --git a/client/src/Components/v2/Inputs/Button.tsx b/client/src/Components/v2/Inputs/Button.tsx
index bca6cee34..2f28e8eda 100644
--- a/client/src/Components/v2/Inputs/Button.tsx
+++ b/client/src/Components/v2/Inputs/Button.tsx
@@ -1,10 +1,9 @@
import Button from "@mui/material/Button";
import type { ButtonProps } from "@mui/material/Button";
-export const ButtonInput: React.FC = ({ filled, sx, ...props }) => {
+export const ButtonInput: React.FC = ({ sx, ...props }) => {
return (
diff --git a/client/src/Components/v2/Inputs/ButtonGroup.tsx b/client/src/Components/v2/Inputs/ButtonGroup.tsx
index 89ec1f1da..3d8acd8cc 100644
--- a/client/src/Components/v2/Inputs/ButtonGroup.tsx
+++ b/client/src/Components/v2/Inputs/ButtonGroup.tsx
@@ -1,12 +1,9 @@
-import { useTheme } from "@mui/material/styles";
import ButtonGroup from "@mui/material/ButtonGroup";
import type { ButtonGroupProps } from "@mui/material/ButtonGroup";
export const ButtonGroupInput: React.FC = ({
orientation,
...props
}) => {
- const theme = useTheme();
-
return (
};
return (
- }>
+ }
+ title={"Average response time"}
+ >
>
= ({
@@ -59,13 +60,14 @@ const ResponseTimeToolTip: React.FC = ({
payload,
label,
range,
+ theme,
+ uiTimezone,
}) => {
if (!label) return null;
if (!payload) return null;
+ if (!active) return null;
- const theme = useTheme();
const format = tooltipDateFormatLookup(range);
- const uiTimezone = useSelector((state: any) => state.ui.timezone);
const responseTime = Math.floor(payload?.[0]?.value || 0);
return (
@@ -83,6 +85,8 @@ export const ChartResponseTime = ({
range: string;
}) => {
const theme = useTheme();
+ const uiTimezone = useSelector((state: any) => state.ui.timezone);
+
return (
}
@@ -137,6 +141,8 @@ export const ChartResponseTime = ({
)}
/>
diff --git a/client/src/Components/v2/Monitors/HeaderControls.tsx b/client/src/Components/v2/Monitors/HeaderControls.tsx
index 056d88f0a..95a03b5cf 100644
--- a/client/src/Components/v2/Monitors/HeaderControls.tsx
+++ b/client/src/Components/v2/Monitors/HeaderControls.tsx
@@ -41,6 +41,7 @@ export const HeaderControls = ({
>
diff --git a/client/src/Components/v2/Monitors/HeaderRange.tsx b/client/src/Components/v2/Monitors/HeaderRange.tsx
index ef9205328..21ce01fe2 100644
--- a/client/src/Components/v2/Monitors/HeaderRange.tsx
+++ b/client/src/Components/v2/Monitors/HeaderRange.tsx
@@ -2,8 +2,7 @@ import Stack from "@mui/material/Stack";
import { ButtonGroup, Button } from "@/Components/v2/Inputs";
import { useTheme } from "@mui/material/styles";
import Typography from "@mui/material/Typography";
-import { theme } from "@/Utils/Theme/v2/theme";
-
+import { useMediaQuery } from "@mui/material";
export const HeaderRange = ({
range,
setRange,
@@ -14,15 +13,18 @@ export const HeaderRange = ({
loading: boolean;
}) => {
const theme = useTheme();
+ const isSmall = useMediaQuery(theme.breakpoints.down("md"));
return (
{`Showing statistics for past ${range}`}
diff --git a/client/src/Components/v2/Monitors/HistogramStatus.tsx b/client/src/Components/v2/Monitors/HistogramStatus.tsx
index f71f92b47..aeacf31d2 100644
--- a/client/src/Components/v2/Monitors/HistogramStatus.tsx
+++ b/client/src/Components/v2/Monitors/HistogramStatus.tsx
@@ -65,7 +65,6 @@ export const BaseChart: React.FC = ({ children, icon, title }) =
(
axiosConfig?: AxiosRequestConfig,
swrConfig?: SWRConfiguration
) => {
- const { data, error, isLoading, mutate } = useSWR(
+ const { data, error, isLoading, isValidating, mutate } = useSWR(
url,
(url) => fetcher(url, axiosConfig),
swrConfig
@@ -29,6 +29,7 @@ export const useGet = (
return {
response: data ?? null,
loading: isLoading,
+ isValidating,
error: error?.message ?? null,
refetch: mutate,
};
diff --git a/client/src/Pages/v2/Uptime/CheckTable.tsx b/client/src/Pages/v2/Uptime/CheckTable.tsx
new file mode 100644
index 000000000..42da6da12
--- /dev/null
+++ b/client/src/Pages/v2/Uptime/CheckTable.tsx
@@ -0,0 +1,93 @@
+import { Table, Pagination } from "@/Components/v2/DesignElements";
+import { StatusLabel } from "@/Components/v2/DesignElements";
+import Box from "@mui/material/Box";
+
+import type { Header } from "@/Components/v2/DesignElements/Table";
+import type { Check } from "@/Types/Check";
+import type { ApiResponse } from "@/Hooks/v2/UseApi";
+import type { MonitorStatus } from "@/Types/Monitor";
+
+import { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { useGet } from "@/Hooks/v2/UseApi";
+import { formatDateWithTz } from "@/Utils/TimeUtils";
+import { useSelector } from "react-redux";
+const getHeaders = (t: Function, uiTimezone: string) => {
+ const headers: Header[] = [
+ {
+ id: "status",
+ content: t("status"),
+ render: (row) => {
+ return ;
+ },
+ },
+ {
+ id: "date",
+ content: t("date&Time"),
+ render: (row) => {
+ return formatDateWithTz(row.createdAt, "ddd, MMMM D, YYYY, HH:mm A", uiTimezone);
+ },
+ },
+ {
+ id: "statusCode",
+ content: t("statusCode"),
+ render: (row) => {
+ return row.httpStatusCode || "N/A";
+ },
+ },
+ ];
+ return headers;
+};
+
+export const CheckTable = ({ monitorId }: { monitorId: string }) => {
+ const [page, setPage] = useState(0);
+ const [rowsPerPage, setRowsPerPage] = useState(5);
+ const { t } = useTranslation();
+ const uiTimezone = useSelector((state: any) => state.ui.timezone);
+ const headers = getHeaders(t, uiTimezone);
+
+ const { response, error } = useGet(
+ `/monitors/${monitorId}/checks?page=${page}&rowsPerPage=${rowsPerPage}`,
+ {},
+ { keepPreviousData: true }
+ );
+
+ const checks = response?.data?.checks || [];
+ const count = response?.data?.count || 0;
+
+ 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);
+ };
+
+ if (error) {
+ console.error(error);
+ }
+
+ return (
+
+
+
+
+ );
+};
diff --git a/client/src/Pages/v2/Uptime/Details.tsx b/client/src/Pages/v2/Uptime/Details.tsx
index a1419178c..c3ef14e72 100644
--- a/client/src/Pages/v2/Uptime/Details.tsx
+++ b/client/src/Pages/v2/Uptime/Details.tsx
@@ -4,7 +4,11 @@ import Stack from "@mui/material/Stack";
import { StatBox } from "@/Components/v2/DesignElements";
import { HistogramStatus } from "@/Components/v2/Monitors/HistogramStatus";
import { ChartAvgResponse } from "@/Components/v2/Monitors/ChartAvgResponse";
+import { ChartResponseTime } from "@/Components/v2/Monitors/ChartResponseTime";
+import { HeaderRange } from "@/Components/v2/Monitors/HeaderRange";
+import { CheckTable } from "@/Pages/v2/Uptime/CheckTable";
+import type { IMonitor } from "@/Types/Monitor";
import { useMediaQuery } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import { useParams } from "react-router";
@@ -12,8 +16,6 @@ import { useGet, usePatch, type ApiResponse } from "@/Hooks/v2/UseApi";
import { useState } from "react";
import { getStatusPalette } from "@/Utils/MonitorUtils";
import prettyMilliseconds from "pretty-ms";
-import { ChartResponseTime } from "@/Components/v2/Monitors/ChartResponseTime";
-import { HeaderRange } from "@/Components/v2/Monitors/HeaderRange";
const UptimeDetailsPage = () => {
const { id } = useParams();
@@ -23,7 +25,7 @@ const UptimeDetailsPage = () => {
// Local state
const [range, setRange] = useState("2h");
- const { response, loading, error, refetch } = useGet(
+ const { response, isValidating, error, refetch } = useGet(
`/monitors/${id}?embedChecks=true&range=${range}`,
{},
@@ -32,8 +34,8 @@ const UptimeDetailsPage = () => {
const {
response: upResponse,
+ isValidating: upIsValidating,
error: upError,
- loading: upLoading,
} = useGet(
`/monitors/${id}?embedChecks=true&range=${range}&status=up`,
{},
@@ -43,7 +45,7 @@ const UptimeDetailsPage = () => {
const {
response: downResponse,
error: downError,
- loading: downLoading,
+ isValidating: downIsValidating,
} = useGet(
`/monitors/${id}?embedChecks=true&range=${range}&status=down`,
{},
@@ -56,7 +58,8 @@ const UptimeDetailsPage = () => {
error: postError,
} = usePatch(`/monitors/${id}/active`);
- const monitor = response?.data?.monitor || null;
+ const monitor: IMonitor = response?.data?.monitor;
+
if (!monitor) {
return null;
}
@@ -79,12 +82,17 @@ const UptimeDetailsPage = () => {
? [...downResponse.data.checks].reverse()
: [];
- // TODO something with these
-
- console.log(loading, error, postError, checks, setRange);
-
const palette = getStatusPalette(monitor.status);
+ if (error || upError || downError || postError) {
+ console.error("Error fetching monitor data:", {
+ error,
+ upError,
+ downError,
+ postError,
+ });
+ }
+
return (
{
/>
@@ -144,6 +153,7 @@ const UptimeDetailsPage = () => {
checks={checks}
range={range}
/>
+
);
};
diff --git a/client/src/Pages/v2/Uptime/MonitorTable.tsx b/client/src/Pages/v2/Uptime/MonitorTable.tsx
index 488533d8a..038da2e24 100644
--- a/client/src/Pages/v2/Uptime/MonitorTable.tsx
+++ b/client/src/Pages/v2/Uptime/MonitorTable.tsx
@@ -4,6 +4,7 @@ import { Table } from "@/Components/v2/DesignElements";
import { HistogramResponseTime } from "@/Components/v2/Monitors/HistogramResponseTime";
import type { Header } from "@/Components/v2/DesignElements/Table";
import { ActionsMenu } from "@/Components/v2/ActionsMenu";
+import { StatusLabel } from "@/Components/v2/DesignElements";
import { useTranslation } from "react-i18next";
import { useMediaQuery } from "@mui/material";
@@ -86,7 +87,7 @@ const getHeaders = (theme: any, t: Function, navigate: Function) => {
id: "status",
content: t("status"),
render: (row) => {
- return row.status;
+ return ;
},
},
{
diff --git a/client/src/Types/Check.ts b/client/src/Types/Check.ts
index c3c3712f0..542507a4b 100644
--- a/client/src/Types/Check.ts
+++ b/client/src/Types/Check.ts
@@ -1,8 +1,38 @@
+export interface CheckTimingPhases {
+ wait: number;
+ dns: number;
+ tcp: number;
+ tls: number;
+ request: number;
+ firstByte: number;
+ download: number;
+ total: number;
+}
+
+export interface CheckTimings {
+ start: string;
+ socket: string;
+ lookup: string;
+ connect: string;
+ secureConnect: string;
+ response: string;
+ end: string;
+ phases: CheckTimingPhases;
+}
+
export interface Check {
_id: string;
+ monitorId: string;
+ type: string;
status: string;
+ message: string;
responseTime: number;
+ httpStatusCode: number;
+ ack: boolean;
+ expiry: string;
createdAt: string;
+ updatedAt: string;
+ timings: CheckTimings;
}
export interface GroupedCheck {