diff --git a/client/src/Components/v2/design-elements/BasePage.tsx b/client/src/Components/v2/design-elements/BasePage.tsx
index de20fc005..e5091091e 100644
--- a/client/src/Components/v2/design-elements/BasePage.tsx
+++ b/client/src/Components/v2/design-elements/BasePage.tsx
@@ -91,7 +91,7 @@ export const BasePage = ({
spacing={theme.spacing(10)}
{...props}
>
-
+
{children}
);
diff --git a/client/src/Components/v2/design-elements/Breadcrumb.tsx b/client/src/Components/v2/design-elements/Breadcrumb.tsx
index e0a310c9f..ae3e768c2 100644
--- a/client/src/Components/v2/design-elements/Breadcrumb.tsx
+++ b/client/src/Components/v2/design-elements/Breadcrumb.tsx
@@ -4,6 +4,7 @@ import { Link, useLocation } from "react-router-dom";
import { useTheme } from "@mui/material/styles";
import { ChevronRight } from "lucide-react";
import { useTranslation } from "react-i18next";
+import type { ReactNode } from "react";
const isId = (segment: string): boolean => {
return segment.length === 24 || /^[a-f0-9-]{36}$/.test(segment);
@@ -11,28 +12,8 @@ const isId = (segment: string): boolean => {
const actionSegments = ["create", "configure"];
-export const Breadcrumb = () => {
- const { t } = useTranslation();
+const BreadcrumbWrapper = ({ children }: { children: ReactNode }) => {
const theme = useTheme();
- const location = useLocation();
-
- const segments = location.pathname.split("/").filter((x) => x);
-
- // Build simplified breadcrumb: "uptime" or "uptime / details" or "uptime / create"
- const basePage = segments[0] || t("common.breadcrumbs.home");
-
- const secondSegment = segments[1];
- const isActionPage = secondSegment && actionSegments.includes(secondSegment); // create/config
- const isDetailsPage = secondSegment && isId(secondSegment); // details
- const hasSubPage = isActionPage || isDetailsPage;
-
- const getSubPageLabel = (): string => {
- if (isActionPage) {
- return secondSegment.charAt(0).toUpperCase() + secondSegment.slice(1);
- }
- return t("common.breadcrumbs.details");
- };
-
return (
{
},
}}
>
+ {children}
+
+ );
+};
+
+export const Breadcrumb = ({
+ breadcrumbOverride,
+}: {
+ breadcrumbOverride?: string[] | undefined;
+}) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const location = useLocation();
+
+ // If override is an empty array, hide entirely
+ if (breadcrumbOverride !== undefined && breadcrumbOverride.length === 0) {
+ return null;
+ }
+
+ // If override has items, render them directly
+ if (breadcrumbOverride !== undefined && breadcrumbOverride.length > 0) {
+ return (
+
+ {breadcrumbOverride.map((item, index) => {
+ const isLast = index === breadcrumbOverride.length - 1;
+ return (
+
+ {item}
+
+ );
+ })}
+
+ );
+ }
+
+ // Default behavior: use location pathname
+ const segments = location.pathname.split("/").filter((x) => x);
+
+ if (segments.length === 0) {
+ return null;
+ }
+
+ // Build simplified breadcrumb: "uptime" or "uptime / details" or "uptime / create"
+ const basePage = segments[0] || t("common.breadcrumbs.home");
+
+ const secondSegment = segments[1];
+ const isActionPage = secondSegment && actionSegments.includes(secondSegment);
+ const isDetailsPage = secondSegment && isId(secondSegment);
+ const hasSubPage = isActionPage || isDetailsPage;
+
+ const getSubPageLabel = (): string => {
+ if (isActionPage) {
+ return secondSegment.charAt(0).toUpperCase() + secondSegment.slice(1);
+ }
+ return t("common.breadcrumbs.details");
+ };
+
+ return (
+
{hasSubPage ? (
{
{getSubPageLabel()}
)}
-
+
);
};
diff --git a/client/src/Components/v2/inputs/Switch.tsx b/client/src/Components/v2/inputs/Switch.tsx
index b8ae8c1ec..05be73a24 100644
--- a/client/src/Components/v2/inputs/Switch.tsx
+++ b/client/src/Components/v2/inputs/Switch.tsx
@@ -3,8 +3,12 @@ import Switch from "@mui/material/Switch";
import type { SwitchProps } from "@mui/material/Switch";
import { useTheme } from "@mui/material/styles";
-export const SwitchComponent = forwardRef(
- function SwitchComponent({ sx, ...props }, ref) {
+interface SwitchComponentProps extends SwitchProps {
+ dualOption?: boolean;
+}
+
+export const SwitchComponent = forwardRef(
+ function SwitchComponent({ sx, dualOption = false, ...props }, ref) {
const theme = useTheme();
const additionalSx = Array.isArray(sx) ? sx : sx ? [sx] : [];
@@ -28,6 +32,12 @@ export const SwitchComponent = forwardRef(
},
},
},
+ ...(dualOption && {
+ "& .MuiSwitch-track": {
+ backgroundColor: theme.palette.primary.main,
+ opacity: 1,
+ },
+ }),
},
...additionalSx,
]}
diff --git a/client/src/Components/v2/monitors/HeaderMonitorControls.tsx b/client/src/Components/v2/monitors/HeaderMonitorControls.tsx
index d1a0d078d..6d321cbb8 100644
--- a/client/src/Components/v2/monitors/HeaderMonitorControls.tsx
+++ b/client/src/Components/v2/monitors/HeaderMonitorControls.tsx
@@ -95,7 +95,7 @@ export const HeaderMonitorControls = ({
await refetch();
}}
>
- {monitor?.isActive ? t("pause") : t("resume")}
+ {monitor?.isActive ? t("common.buttons.pause") : t("common.buttons.resume")}
)}
{isAdmin && (
@@ -105,7 +105,7 @@ export const HeaderMonitorControls = ({
startIcon={}
onClick={() => navigate(`/${path}/configure/${monitor.id}`)}
>
- Configure
+ {t("common.buttons.configure")}
)}
@@ -155,7 +155,7 @@ export const HeaderDeleteControls = ({
await refetch();
}}
>
- {monitor?.isActive ? t("pause") : t("resume")}
+ {monitor?.isActive ? t("common.buttons.pause") : t("common.buttons.resume")}
)}
{isAdmin && (
diff --git a/client/src/Pages/StatusPage/Create/index.jsx b/client/src/Pages/StatusPage/Create/index.jsx
index 4eb171232..c5aee4b0a 100644
--- a/client/src/Pages/StatusPage/Create/index.jsx
+++ b/client/src/Pages/StatusPage/Create/index.jsx
@@ -17,7 +17,6 @@ import { useNavigate } from "react-router-dom";
import { useStatusPageFetch } from "../Status/Hooks/useStatusPageFetch.jsx";
import { useParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
-import { useStatusPageDelete } from "../Status/Hooks/useStatusPageDelete.jsx";
//Constants
const ERROR_TAB_MAPPING = [
["companyName", "url", "timezone", "color", "isPublished", "logo"],
@@ -60,7 +59,7 @@ const CreateStatusPage = () => {
const [createStatusPage] = useCreateStatusPage(isCreate);
const [statusPage, statusPageMonitors, statusPageIsLoading, , fetchStatusPage] =
useStatusPageFetch(isCreate, url);
- const [deleteStatusPage, isDeleting] = useStatusPageDelete(fetchStatusPage, url);
+ // const [deleteStatusPage, isDeleting] = useStatusPageDelete(fetchStatusPage, url);
// Handlers
const handleFormChange = (e) => {
@@ -133,7 +132,7 @@ const CreateStatusPage = () => {
const handleDelete = async () => {
setIsDeleteOpen(false);
// Start deletion process but don't wait for it
- deleteStatusPage();
+ // deleteStatusPage();
// Immediately navigate away to prevent additional fetches for the deleted page
navigate("/status");
};
@@ -252,7 +251,7 @@ const CreateStatusPage = () => {
justifyContent="flex-end"
>
+ )}
+
+ );
+};
diff --git a/client/src/Pages/StatusPage/Status/Components/MonitorsList.tsx b/client/src/Pages/StatusPage/Status/Components/MonitorsList.tsx
new file mode 100644
index 000000000..d559e7c82
--- /dev/null
+++ b/client/src/Pages/StatusPage/Status/Components/MonitorsList.tsx
@@ -0,0 +1,113 @@
+import Stack from "@mui/material/Stack";
+import { useTranslation } from "react-i18next";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import { HistogramResponseTime, HeatmapResponseTime } from "@/Components/v2/common";
+import { StatusLabel, BaseBox } from "@/Components/v2/design-elements";
+import { SwitchComponent } from "@/Components/v2/inputs";
+
+import { useTheme } from "@mui/material/styles";
+import { determineState } from "@/Utils/MonitorUtils";
+import { useSelector } from "react-redux";
+import { useState } from "react";
+import type { Monitor } from "@/Types/Monitor";
+import type { StatusPage } from "@/Types/StatusPage";
+import type { RootState } from "@/Types/state";
+
+interface StatusPageMonitor extends Monitor {
+ checks?: Monitor["recentChecks"];
+}
+
+interface MonitorsListProps {
+ statusPage: StatusPage;
+ monitors: StatusPageMonitor[];
+}
+
+export const MonitorsList = ({ statusPage, monitors }: MonitorsListProps) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+
+ const showURL = useSelector((state: RootState) => state.ui?.showURL);
+
+ const [chartType, setChartType] = useState<"histogram" | "heatmap">("histogram");
+
+ return (
+
+ {statusPage.showCharts !== false && (
+
+ {t("pages.statusPages.monitorsList.chartTypeHeatmap")}
+ {
+ setChartType(e.target.checked ? "histogram" : "heatmap");
+ }}
+ />
+
+ {t("pages.statusPages.monitorsList.chartTypeHistogram")}
+
+
+ )}
+
+ {monitors?.map((monitor) => {
+ const status = determineState(monitor);
+ return (
+
+
+
+
+ {monitor.name}
+
+ {showURL && (
+
+ {monitor.url}
+
+ )}
+
+
+
+ {statusPage.showCharts !== false && (
+
+ {chartType === "histogram" ? (
+
+ ) : (
+
+ )}
+
+ )}
+
+ );
+ })}
+
+ );
+};
diff --git a/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx b/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx
deleted file mode 100644
index 33f771233..000000000
--- a/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx
+++ /dev/null
@@ -1,69 +0,0 @@
-// Components
-import { Stack, Box } from "@mui/material";
-import Host from "@/Components/v1/Host/index.jsx";
-import StatusPageBarChart from "@/Components/v1/Charts/StatusPageBarChart/index.jsx";
-import { StatusLabel } from "@/Components/v1/Label/index.jsx";
-
-//Utils
-import { useTheme } from "@mui/material/styles";
-import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils.js";
-import PropTypes from "prop-types";
-
-import { useSelector } from "react-redux";
-
-const MonitorsList = ({
- isLoading = false,
- shouldRender = true,
- monitors = [],
- statusPage = {},
-}) => {
- const theme = useTheme();
- const { determineState } = useMonitorUtils();
-
- const { showURL } = useSelector((state) => state.ui);
-
- return (
- <>
- {monitors?.map((monitor) => {
- const status = determineState(monitor);
- return (
-
-
-
- {statusPage.showCharts !== false && (
-
-
-
- )}
-
-
- );
- })}
- >
- );
-};
-
-MonitorsList.propTypes = {
- monitors: PropTypes.array.isRequired,
- statusPage: PropTypes.object,
-};
-
-export default MonitorsList;
diff --git a/client/src/Pages/StatusPage/Status/Components/Skeleton/index.jsx b/client/src/Pages/StatusPage/Status/Components/Skeleton/index.jsx
deleted file mode 100644
index 482c490fb..000000000
--- a/client/src/Pages/StatusPage/Status/Components/Skeleton/index.jsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { Stack, Skeleton } from "@mui/material";
-
-const SkeletonLayout = () => {
- return (
-
-
-
- );
-};
-
-export default SkeletonLayout;
diff --git a/client/src/Pages/StatusPage/Status/Components/StatusBar.tsx b/client/src/Pages/StatusPage/Status/Components/StatusBar.tsx
new file mode 100644
index 000000000..c5aca4754
--- /dev/null
+++ b/client/src/Pages/StatusPage/Status/Components/StatusBar.tsx
@@ -0,0 +1,62 @@
+import { AlertTriangle, CircleCheck } from "lucide-react";
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+
+import { useTranslation } from "react-i18next";
+import type { Theme } from "@mui/material";
+import { useTheme } from "@mui/material";
+import type { Monitor } from "@/Types/Monitor";
+
+const getMonitorStatus = (monitors: Monitor[], theme: Theme, t: Function) => {
+ const monitorsStatus: Record = {
+ icon: ,
+ };
+
+ if (monitors.every((monitor) => monitor.status === true)) {
+ monitorsStatus.msg = t("pages.statusPages.statusBar.allUp");
+ monitorsStatus.color = theme.palette.success.main;
+ monitorsStatus.icon = ;
+ }
+
+ if (monitors.every((monitor) => monitor.status === false)) {
+ monitorsStatus.msg = t("pages.statusPages.statusBar.allDown");
+ monitorsStatus.color = theme.palette.error.main;
+ }
+
+ if (monitors.some((monitor) => monitor.status === false)) {
+ monitorsStatus.msg = t("pages.statusPages.statusBar.degraded");
+ monitorsStatus.color = theme.palette.warning.main;
+ }
+
+ // Paused or unknown
+ if (monitors.some((monitor) => typeof monitor.status === "undefined")) {
+ monitorsStatus.msg = t("pages.statusPages.statusBar.unknown");
+ monitorsStatus.color = theme.palette.warning.main;
+ }
+ return monitorsStatus;
+};
+
+interface StatusBarProps {
+ monitors: Monitor[];
+}
+
+export const StatusBar = ({ monitors }: StatusBarProps) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ const monitorsStatus = getMonitorStatus(monitors, theme, t);
+
+ return (
+
+ {monitorsStatus.icon}
+ {monitorsStatus.msg}
+
+ );
+};
diff --git a/client/src/Pages/StatusPage/Status/Components/StatusBar/index.jsx b/client/src/Pages/StatusPage/Status/Components/StatusBar/index.jsx
deleted file mode 100644
index 7b9460068..000000000
--- a/client/src/Pages/StatusPage/Status/Components/StatusBar/index.jsx
+++ /dev/null
@@ -1,77 +0,0 @@
-// Components
-import { Stack, Typography } from "@mui/material";
-import Icon from "@/Components/v1/Icon";
-
-// Utils
-import { useTheme } from "@mui/material/styles";
-import PropTypes from "prop-types";
-
-const getMonitorStatus = (monitors, theme) => {
- const monitorsStatus = {
- icon: (
-
- ),
- };
- if (monitors.every((monitor) => monitor.status === true)) {
- monitorsStatus.msg = "All systems operational";
- monitorsStatus.color = theme.palette.success.lowContrast;
- monitorsStatus.icon = (
-
- );
- }
-
- if (monitors.every((monitor) => monitor.status === false)) {
- monitorsStatus.msg = "All systems down";
- monitorsStatus.color = theme.palette.error.lowContrast;
- }
-
- if (monitors.some((monitor) => monitor.status === false)) {
- monitorsStatus.msg = "Degraded performance";
- monitorsStatus.color = theme.palette.warning.lowContrast;
- }
-
- // Paused or unknown
- if (monitors.some((monitor) => typeof monitor.status === "undefined")) {
- monitorsStatus.msg = "Unknown status";
- monitorsStatus.color = theme.palette.warning.lowContrast;
- }
- return monitorsStatus;
-};
-
-const StatusBar = ({ monitors }) => {
- const theme = useTheme();
-
- if (typeof monitors === "undefined") return;
-
- const monitorsStatus = getMonitorStatus(monitors, theme);
- return (
-
- {monitorsStatus.icon}
- {/* CAIO_REVIEW */}
- {monitorsStatus.msg}
-
- );
-};
-
-export default StatusBar;
-
-StatusBar.propTypes = {
- status: PropTypes.object,
-};
diff --git a/client/src/Pages/StatusPage/Status/index.jsx b/client/src/Pages/StatusPage/Status/index.jsx
deleted file mode 100644
index adcd9c2c7..000000000
--- a/client/src/Pages/StatusPage/Status/index.jsx
+++ /dev/null
@@ -1,166 +0,0 @@
-// Components
-import { Typography, Stack } from "@mui/material";
-import GenericFallback from "@/Components/v1/GenericFallback/index.jsx";
-import AdminLink from "./Components/AdminLink/index.jsx";
-import ControlsHeader from "./Components/ControlsHeader/index.jsx";
-import SkeletonLayout from "./Components/Skeleton/index.jsx";
-import StatusBar from "./Components/StatusBar/index.jsx";
-import MonitorsList from "./Components/MonitorsList/index.jsx";
-import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
-import TextLink from "@/Components/v1/TextLink/index.jsx";
-
-// Utils
-import { useStatusPageFetch } from "./Hooks/useStatusPageFetch.jsx";
-import { useTheme } from "@emotion/react";
-import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
-import { useLocation } from "react-router-dom";
-import { useParams } from "react-router-dom";
-import { useTranslation } from "react-i18next";
-
-const PublicStatus = () => {
- const { url } = useParams();
-
- // Utils
- const theme = useTheme();
- const { t } = useTranslation();
- const location = useLocation();
- const isAdmin = useIsAdmin();
-
- const [statusPage, monitors, isLoading, networkError] = useStatusPageFetch(false, url);
-
- // Breadcrumbs
- const crumbs = [
- { name: t("statusBreadCrumbsStatusPages"), path: "/status" },
- { name: t("statusBreadCrumbsDetails"), path: `/status/uptime/${statusPage?.url}` },
- ];
-
- // Setup
- let sx = { paddingLeft: theme.spacing(20), paddingRight: theme.spacing(20) };
- let link = undefined;
- const isPublic = location.pathname.startsWith("/status/uptime/public");
- // Public status page
- if (isPublic && statusPage && statusPage.showAdminLoginLink === true) {
- sx = {
- paddingTop: theme.spacing(20),
- paddingLeft: "20vw",
- paddingRight: "20vw",
- };
- link = ;
- }
-
- // Loading
- if (isLoading) {
- return ;
- }
-
- if (monitors?.length === 0) {
- return (
-
-
- {"Theres nothing here yet"}
-
- {isAdmin && (
-
- )}
-
- );
- }
-
- // Error fetching data
- if (networkError === true) {
- return (
-
-
- {t("common.toasts.networkError")}
-
- {t("common.toasts.checkConnection")}
-
- );
- }
-
- // Public status page fallback
- if (!isLoading && typeof statusPage === "undefined" && isPublic) {
- return (
-
-
-
- {t("statusPageStatus")}
-
- {t("statusPageStatusContactAdmin")}
-
-
- );
- }
-
- // Finished loading, but status page is not public
- if (!isLoading && isPublic && statusPage.isPublished === false) {
- return (
-
-
-
- {t("statusPageStatusNotPublic")}
-
- {t("statusPageStatusContactAdmin")}
-
-
- );
- }
-
- // Status page doesn't exist
- if (!isLoading && typeof statusPage === "undefined") {
- return (
-
-
- {t("statusPageStatusNoPage")}
-
- {t("statusPageStatusContactAdmin")}
-
- );
- }
-
- return (
-
- {!isPublic && }
-
- {t("statusPageStatusServiceStatus")}
-
-
- {link}
-
- );
-};
-
-export default PublicStatus;
diff --git a/client/src/Pages/StatusPage/Status/index.tsx b/client/src/Pages/StatusPage/Status/index.tsx
new file mode 100644
index 000000000..41373b19a
--- /dev/null
+++ b/client/src/Pages/StatusPage/Status/index.tsx
@@ -0,0 +1,60 @@
+import { BasePage } from "@/Components/v2/design-elements";
+import { StatusBar } from "@/Pages/StatusPage/Status/Components/StatusBar";
+import { MonitorsList } from "@/Pages/StatusPage/Status/Components/MonitorsList";
+import Typography from "@mui/material/Typography";
+
+import { useTheme } from "@mui/material";
+import { useTranslation } from "react-i18next";
+import { useIsAdmin } from "@/Hooks/useIsAdmin";
+import { useLocation, useParams } from "react-router-dom";
+import { useGet } from "@/Hooks/UseApi";
+import type { StatusPageResponse } from "@/Types/StatusPage";
+import { HeaderStatusPageControls } from "./Components/HeaderStatusPageControls";
+
+const StatusPageView = () => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ const { url } = useParams();
+ const isAdmin = useIsAdmin();
+ const location = useLocation();
+ const isPublic = location.pathname.startsWith("/status/uptime/public");
+
+ const apiUrl = url ? `/status-page/${url}?type=uptime` : null;
+
+ const { data, isLoading, error } = useGet(apiUrl);
+
+ const statusPage = data?.statusPage;
+ const monitors = data?.monitors ?? [];
+
+ if (!statusPage) return null;
+
+ let sx: React.CSSProperties = {};
+ if (isPublic) {
+ sx.paddingTop = theme.spacing(20);
+ sx.paddingLeft = "20vw";
+ sx.paddingRight = "20vw";
+ }
+
+ return (
+
+
+ {t("statusPageStatusServiceStatus")}
+
+
+
+ );
+};
+
+export default StatusPageView;
diff --git a/client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable.tsx b/client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable.tsx
new file mode 100644
index 000000000..1741aeadf
--- /dev/null
+++ b/client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable.tsx
@@ -0,0 +1,109 @@
+import Box from "@mui/material/Box";
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import { Table, type Header, ValueLabel } from "@/Components/v2/design-elements";
+import { ExternalLink } from "lucide-react";
+
+import { useTranslation } from "react-i18next";
+import { useTheme } from "@mui/material/styles";
+import { useNavigate } from "react-router-dom";
+
+import type { StatusPage } from "@/Types/StatusPage";
+
+interface StatusPagesTableProps {
+ data: StatusPage[];
+}
+
+export const StatusPagesTable = ({ data }: StatusPagesTableProps) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const navigate = useNavigate();
+
+ const handleUrlClick = (e: React.MouseEvent, row: StatusPage) => {
+ if (row.isPublished) {
+ e.stopPropagation();
+ const url = `/status/uptime/public/${row.url}`;
+ window.open(url, "_blank", "noopener,noreferrer");
+ }
+ };
+
+ const getHeaders = (): Header[] => {
+ return [
+ {
+ id: "name",
+ content: t("pages.statusPages.table.headers.name"),
+ render: (row) => row.companyName,
+ },
+ {
+ id: "url",
+ content: t("pages.statusPages.table.headers.url"),
+ render: (row) => {
+ const content = row.isPublished
+ ? `/${row.url}`
+ : t("pages.statusPages.table.unpublished");
+ return (
+ handleUrlClick(e, row)}
+ sx={{
+ ...(row.isPublished && {
+ display: "inline-flex",
+ ":hover": {
+ cursor: "pointer",
+ borderBottom: 1,
+ },
+ }),
+ }}
+ >
+ {content}
+ {row.isPublished && }
+
+ );
+ },
+ },
+ {
+ id: "type",
+ content: t("common.table.headers.type"),
+ render: (row) => row.type,
+ },
+ {
+ id: "status",
+ content: t("common.table.headers.status"),
+ render: (row) => {
+ return (
+
+ );
+ },
+ },
+ ];
+ };
+
+ const handleRowClick = (statusPage: StatusPage) => {
+ navigate(`/status/uptime/${statusPage.url}`);
+ };
+
+ return (
+
+
+
+ );
+};
+
+export default StatusPagesTable;
diff --git a/client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx b/client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx
deleted file mode 100644
index 9882239bd..000000000
--- a/client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx
+++ /dev/null
@@ -1,107 +0,0 @@
-import DataTable from "@/Components/v1/Table/index.jsx";
-import { useTheme } from "@emotion/react";
-import { useNavigate } from "react-router-dom";
-import { StatusLabel } from "@/Components/v1/Label/index.jsx";
-import Icon from "@/Components/v1/Icon";
-import { Stack, Typography } from "@mui/material";
-import { useTranslation } from "react-i18next";
-const StatusPagesTable = ({ data }) => {
- const theme = useTheme();
- const { t } = useTranslation();
- const navigate = useNavigate();
- const headers = [
- {
- id: "name",
- content: t("statusPageName"),
- render: (row) => {
- return row.companyName;
- },
- },
- {
- id: "url",
- content: t("publicURL"),
- onClick: (e, row) => {
- if (row.isPublished) {
- e.stopPropagation();
- const url = `/status/uptime/public/${row.url}`;
- window.open(url, "_blank", "noopener,noreferrer");
- }
- },
- render: (row) => {
- const content = row.isPublished ? `/${row.url}` : "Unpublished";
- return (
-
- {content}
- {row.isPublished && (
-
- )}
-
- );
- },
- },
- {
- id: "type",
- content: t("type"),
- render: (row) => {
- return row.type;
- },
- },
- {
- id: "status",
- content: t("status"),
- render: (row) => {
- const status = row.isPublished ? "published" : "unpublished";
- return (
-
- );
- },
- },
- ];
-
- const handleRowClick = (statusPage) => {
- navigate(`/status/uptime/${statusPage.url}`);
- };
-
- return (
- {
- handleRowClick(row);
- },
- }}
- headers={headers}
- data={data}
- />
- );
-};
-
-export default StatusPagesTable;
diff --git a/client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx b/client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx
deleted file mode 100644
index 9336e5d31..000000000
--- a/client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { useState, useEffect } from "react";
-import { networkService } from "../../../../main.jsx";
-import { useSelector } from "react-redux";
-import { createToast } from "../../../../Utils/toastUtils.jsx";
-
-const useStatusPagesFetch = () => {
- const { user } = useSelector((state) => state.auth);
-
- const [isLoading, setIsLoading] = useState(true);
- const [networkError, setNetworkError] = useState(false);
- const [statusPages, setStatusPages] = useState(undefined);
-
- useEffect(() => {
- const fetchStatusPages = async () => {
- try {
- const res = await networkService.getStatusPagesByTeamId();
- setStatusPages(res?.data?.data || []);
- } catch (error) {
- setNetworkError(true);
- createToast({
- body: error.message,
- });
- } finally {
- setIsLoading(false);
- }
- };
- fetchStatusPages();
- }, [user]);
- return [isLoading, networkError, statusPages];
-};
-
-export { useStatusPagesFetch };
diff --git a/client/src/Pages/StatusPage/StatusPages/index.jsx b/client/src/Pages/StatusPage/StatusPages/index.jsx
deleted file mode 100644
index 7137d01ad..000000000
--- a/client/src/Pages/StatusPage/StatusPages/index.jsx
+++ /dev/null
@@ -1,43 +0,0 @@
-// Components
-import { Stack } from "@mui/material";
-import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
-import MonitorCreateHeader from "@/Components/v1/MonitorCreateHeader/index.jsx";
-import StatusPagesTable from "./Components/StatusPagesTable/index.jsx";
-import PageStateWrapper from "@/Components/v1/PageStateWrapper/index.jsx";
-// Utils
-import { useTheme } from "@emotion/react";
-import { useStatusPagesFetch } from "./Hooks/useStatusPagesFetch.jsx";
-import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
-const BREADCRUMBS = [{ name: `Status Pages`, path: "" }];
-
-const StatusPages = () => {
- // Utils
- const theme = useTheme();
- const isAdmin = useIsAdmin();
- const [isLoading, networkError, statusPages] = useStatusPagesFetch();
-
- return (
- <>
-
-
-
-
-
-
-
- >
- );
-};
-
-export default StatusPages;
diff --git a/client/src/Pages/StatusPage/StatusPages/index.tsx b/client/src/Pages/StatusPage/StatusPages/index.tsx
new file mode 100644
index 000000000..6a34e587d
--- /dev/null
+++ b/client/src/Pages/StatusPage/StatusPages/index.tsx
@@ -0,0 +1,41 @@
+import { BasePageWithStates } from "@/Components/v2/design-elements";
+import { StatusPagesTable } from "./Components/StatusPagesTable";
+import { useGet } from "@/Hooks/UseApi";
+import type { StatusPage } from "@/Types/StatusPage";
+import { useTranslation } from "react-i18next";
+import { HeaderCreate } from "@/Components/v2/common";
+import { useIsAdmin } from "@/Hooks/useIsAdmin";
+
+const StatusPages = () => {
+ const { t } = useTranslation();
+
+ const {
+ data: statusPages,
+ isLoading,
+ error,
+ } = useGet("/status-page/team");
+
+ const isAdmin = useIsAdmin();
+
+ return (
+
+
+
+
+ );
+};
+
+export default StatusPages;
diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx
index ecda66cd1..bbba8eac0 100644
--- a/client/src/Routes/index.jsx
+++ b/client/src/Routes/index.jsx
@@ -37,8 +37,8 @@ import Incidents from "../Pages/Incidents/";
// Status pages
import CreateStatus from "../Pages/StatusPage/Create/index.jsx";
-import StatusPages from "../Pages/StatusPage/StatusPages/index.jsx";
-import Status from "../Pages/StatusPage/Status/index.jsx";
+import StatusPages from "../Pages/StatusPage/StatusPages";
+import Status from "../Pages/StatusPage/Status";
import Notifications from "../Pages/Notifications";
import CreateNotifications from "../Pages/Notifications/create";
@@ -227,12 +227,24 @@ const Routes = () => {
}
+ element={
+ <>
+
+
+
+ >
+ }
/>
}
+ element={
+ <>
+
+
+
+ >
+ }
/>
{
/>
}
+ element={
+ <>
+
+
+
+ >
+ }
/>
{
+ if (typeof monitor === "undefined") return "pending";
+ if (monitor?.isActive === false) return "paused";
+ if (monitor?.status === undefined) return "pending";
+ return monitor?.status == true ? "up" : "down";
+};
+
export const getMonitorPath = (type: MonitorType): string => {
const pathMap: Record = {
http: "uptime",
diff --git a/client/src/locales/en.json b/client/src/locales/en.json
index f4564109f..88f1ffc7f 100644
--- a/client/src/locales/en.json
+++ b/client/src/locales/en.json
@@ -22,7 +22,8 @@
"cancel": "Cancel",
"confirm": "Confirm",
"save": "Save",
- "test": "Test"
+ "test": "Test",
+ "configure": "Configure"
},
"alerts": {
@@ -452,6 +453,37 @@
}
}
}
+ },
+ "statusPages": {
+ "title": "Status Pages",
+ "table": {
+ "headers": {
+ "name": "Status page name",
+ "url": "Public URL"
+ },
+ "published": "Published",
+ "unpublished": "Unpublished"
+ },
+ "fallback": {
+ "actionButton": "Create a status page!",
+ "checks": [
+ "Monitor and display the health of your services in real time",
+ "Track multiple services and share their status",
+ "Keep users informed about outages and performance"
+ ],
+ "title": "A status page is used to:"
+ },
+ "serviceStauts": "Service stauts",
+ "statusBar": {
+ "allUp": "All systems operational",
+ "allDown": "All systems down",
+ "degraded": "Degraded performance",
+ "unknown": "Unknown status"
+ },
+ "monitorsList": {
+ "chartTypeHeatmap": "Heatmap",
+ "chartTypeHistogram": "Histogram"
+ }
}
},
"used": "Used",
@@ -490,8 +522,6 @@
"cancel": "Cancel",
"close": "Close",
"type": "Type",
- "statusPageName": "Status page name",
- "publicURL": "Public URL",
"repeat": "Repeat",
"edit": "Edit",
"createA": "Create a",
@@ -516,10 +546,6 @@
"showCharts": "Show charts",
"showUptimePercentage": "Show uptime percentage",
"removeLogo": "Remove Logo",
- "statusPageStatus": "A public status page is not set up.",
- "statusPageStatusContactAdmin": "Please contact to your administrator",
- "statusPageStatusNotPublic": "This status page is not public.",
- "statusPageStatusNoPage": "There's no status page here.",
"statusPageStatusServiceStatus": "Service status",
"deleteStatusPage": "Do you want to delete this status page?",
"deleteStatusPageConfirm": "Yes, delete status page",
@@ -589,6 +615,10 @@
"getToken": "Get token",
"emailToken": "E-mail token",
"table": {
+ "headers": {
+ "name": "Status page name",
+ "url": "Public URL"
+ },
"name": "Name",
"email": "Email",
"role": "Role",
@@ -793,6 +823,10 @@
}
},
"table": {
+ "headers": {
+ "name": "Status page name",
+ "url": "Public URL"
+ },
"timestamp": "Timestamp",
"level": "Level",
"service": "Service",
@@ -973,6 +1007,10 @@
"save": "Save"
},
"table": {
+ "headers": {
+ "name": "Status page name",
+ "url": "Public URL"
+ },
"actionHeader": "Action",
"roleHeader": "Role"
},
diff --git a/server/src/controllers/statusPageController.ts b/server/src/controllers/statusPageController.ts
index 18428d424..3271e39a1 100644
--- a/server/src/controllers/statusPageController.ts
+++ b/server/src/controllers/statusPageController.ts
@@ -81,7 +81,7 @@ class StatusPageController {
const settings = await this.settingsService.getDBSettings();
const showURL = settings.showURL;
- const monitors = await this.monitorsRepository.findByIdsWithChecks(statusPage.monitors);
+ const monitors = await this.monitorsRepository.findByIds(statusPage.monitors);
const normalizedMonitors = monitors.map((monitor) => {
const normalizedChecks = NormalizeData(monitor.recentChecks, 10, 100);
if (!showURL) {