From 57c3bf32406b282159cec22e39a5da826daa9bf3 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 2 Feb 2026 19:00:03 +0000 Subject: [PATCH 01/10] findBYIds only --- server/src/controllers/statusPageController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) { From 853e21623e9a3c08174f8a6d5850a1fbc54e8a73 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 2 Feb 2026 19:20:59 +0000 Subject: [PATCH 02/10] new status pages --- .../Components/StatusPagesTable.tsx | 109 ++++++++++++++++++ .../Pages/StatusPage/StatusPages/index.tsx | 33 ++++++ .../StatusPages/{index.jsx => old.jsx} | 0 client/src/Routes/index.jsx | 10 +- client/src/Types/StatusPage.ts | 24 ++++ client/src/locales/en.json | 56 +++++++++ 6 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable.tsx create mode 100644 client/src/Pages/StatusPage/StatusPages/index.tsx rename client/src/Pages/StatusPage/StatusPages/{index.jsx => old.jsx} (100%) create mode 100644 client/src/Types/StatusPage.ts 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/index.tsx b/client/src/Pages/StatusPage/StatusPages/index.tsx new file mode 100644 index 000000000..ad1a39b29 --- /dev/null +++ b/client/src/Pages/StatusPage/StatusPages/index.tsx @@ -0,0 +1,33 @@ +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"; + +const StatusPages = () => { + const { t } = useTranslation(); + + const { + data: statusPages, + isLoading, + error, + } = useGet("/status-page/team"); + + return ( + + + + ); +}; + +export default StatusPages; diff --git a/client/src/Pages/StatusPage/StatusPages/index.jsx b/client/src/Pages/StatusPage/StatusPages/old.jsx similarity index 100% rename from client/src/Pages/StatusPage/StatusPages/index.jsx rename to client/src/Pages/StatusPage/StatusPages/old.jsx diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx index ecda66cd1..aec872a0f 100644 --- a/client/src/Routes/index.jsx +++ b/client/src/Routes/index.jsx @@ -37,7 +37,7 @@ import Incidents from "../Pages/Incidents/"; // Status pages import CreateStatus from "../Pages/StatusPage/Create/index.jsx"; -import StatusPages from "../Pages/StatusPage/StatusPages/index.jsx"; +import StatusPages from "../Pages/StatusPage/StatusPages"; import Status from "../Pages/StatusPage/Status/index.jsx"; import Notifications from "../Pages/Notifications"; @@ -227,7 +227,13 @@ const Routes = () => { } + element={ + <> + + + + + } /> Date: Mon, 2 Feb 2026 19:25:30 +0000 Subject: [PATCH 03/10] remove unused --- .../Components/StatusPagesTable/index.jsx | 107 ------------------ .../StatusPages/Hooks/useStatusPagesFetch.jsx | 32 ------ .../src/Pages/StatusPage/StatusPages/old.jsx | 43 ------- client/src/locales/en.json | 2 - 4 files changed, 184 deletions(-) delete mode 100644 client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx delete mode 100644 client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx delete mode 100644 client/src/Pages/StatusPage/StatusPages/old.jsx 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/old.jsx b/client/src/Pages/StatusPage/StatusPages/old.jsx deleted file mode 100644 index 7137d01ad..000000000 --- a/client/src/Pages/StatusPage/StatusPages/old.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/locales/en.json b/client/src/locales/en.json index 0282e5d2d..0b2594ccf 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -534,8 +534,6 @@ "cancel": "Cancel", "close": "Close", "type": "Type", - "statusPageName": "Status page name", - "publicURL": "Public URL", "repeat": "Repeat", "edit": "Edit", "createA": "Create a", From 0fd417381660a0c6b1685ac7b6f8ef090d21610a Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 2 Feb 2026 20:39:16 +0000 Subject: [PATCH 04/10] status bar --- .../v2/monitors/HeaderMonitorControls.tsx | 6 +- .../Components/HeaderStatusPageControls.tsx | 87 +++++++++++++++++++ .../Status/Components/StatusBar.tsx | 62 +++++++++++++ client/src/Pages/StatusPage/Status/index.tsx | 51 +++++++++++ .../StatusPage/Status/{index.jsx => old.jsx} | 0 client/src/Routes/index.jsx | 18 +++- client/src/Types/StatusPage.ts | 7 ++ client/src/locales/en.json | 34 ++------ 8 files changed, 234 insertions(+), 31 deletions(-) create mode 100644 client/src/Pages/StatusPage/Status/Components/HeaderStatusPageControls.tsx create mode 100644 client/src/Pages/StatusPage/Status/Components/StatusBar.tsx create mode 100644 client/src/Pages/StatusPage/Status/index.tsx rename client/src/Pages/StatusPage/Status/{index.jsx => old.jsx} (100%) 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/Status/Components/HeaderStatusPageControls.tsx b/client/src/Pages/StatusPage/Status/Components/HeaderStatusPageControls.tsx new file mode 100644 index 000000000..87349d703 --- /dev/null +++ b/client/src/Pages/StatusPage/Status/Components/HeaderStatusPageControls.tsx @@ -0,0 +1,87 @@ +import Stack from "@mui/material/Stack"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import { Icon } from "@/Components/v2/design-elements"; +import { Button } from "@/Components/v2/inputs"; +import { Settings, ExternalLink } from "lucide-react"; + +import { useTheme } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; +import type { StatusPage } from "@/Types/StatusPage"; + +interface HeaderStatusPageControlsProps { + isAdmin: boolean; + statusPage: StatusPage; + isPublic?: boolean; +} +export const HeaderStatusPageControls = ({ + isAdmin, + statusPage, + isPublic = false, +}: HeaderStatusPageControlsProps) => { + const theme = useTheme(); + const navigate = useNavigate(); + const { t } = useTranslation(); + return ( + + + + {statusPage?.companyName} + + {statusPage?.isPublished && !isPublic && ( + <> + { + window.open( + `/status/uptime/public/${statusPage.url}`, + "_blank", + "noopener,noreferrer" + ); + }} + sx={{ + borderBottom: 1, + borderColor: "transparent", + ":hover": { + cursor: "pointer", + borderBottom: 1, + }, + }} + > + {t("publicLink")} + + + + + + )} + + {isAdmin && ( + + )} + + ); +}; 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/index.tsx b/client/src/Pages/StatusPage/Status/index.tsx new file mode 100644 index 000000000..d35503011 --- /dev/null +++ b/client/src/Pages/StatusPage/Status/index.tsx @@ -0,0 +1,51 @@ +import { BasePage } from "@/Components/v2/design-elements"; +import { StatusBar } from "@/Pages/StatusPage/Status/Components/StatusBar"; +import Typography from "@mui/material/Typography"; + +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 { 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 || !monitors) { + return null; + } + + if (monitors.length === 0) { + return "poo"; + } + + return ( + + + {t("statusPageStatusServiceStatus")} + +
 {JSON.stringify(data, null, 2)}
+
+ ); +}; + +export default StatusPageView; diff --git a/client/src/Pages/StatusPage/Status/index.jsx b/client/src/Pages/StatusPage/Status/old.jsx similarity index 100% rename from client/src/Pages/StatusPage/Status/index.jsx rename to client/src/Pages/StatusPage/Status/old.jsx diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx index aec872a0f..bbba8eac0 100644 --- a/client/src/Routes/index.jsx +++ b/client/src/Routes/index.jsx @@ -38,7 +38,7 @@ import Incidents from "../Pages/Incidents/"; // Status pages import CreateStatus from "../Pages/StatusPage/Create/index.jsx"; import StatusPages from "../Pages/StatusPage/StatusPages"; -import Status from "../Pages/StatusPage/Status/index.jsx"; +import Status from "../Pages/StatusPage/Status"; import Notifications from "../Pages/Notifications"; import CreateNotifications from "../Pages/Notifications/create"; @@ -238,7 +238,13 @@ const Routes = () => { } + element={ + <> + + + + + } /> { /> } + element={ + <> + + + + + } /> Date: Mon, 2 Feb 2026 20:42:27 +0000 Subject: [PATCH 05/10] monitors lis tskeleton --- .../StatusPage/Status/Components/MonitorsList.tsx | 11 +++++++++++ client/src/Pages/StatusPage/Status/index.tsx | 5 +++++ 2 files changed, 16 insertions(+) create mode 100644 client/src/Pages/StatusPage/Status/Components/MonitorsList.tsx 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..8425a2256 --- /dev/null +++ b/client/src/Pages/StatusPage/Status/Components/MonitorsList.tsx @@ -0,0 +1,11 @@ +import type { Monitor } from "@/Types/Monitor"; +import type { StatusPage } from "@/Types/StatusPage"; + +interface MontorsListProps { + stautsPage: StatusPage; + monitors: Monitor[]; +} + +export const MonitorsList = ({ stautsPage, monitors }: MontorsListProps) => { + return
Monitors List
; +}; diff --git a/client/src/Pages/StatusPage/Status/index.tsx b/client/src/Pages/StatusPage/Status/index.tsx index d35503011..411234c15 100644 --- a/client/src/Pages/StatusPage/Status/index.tsx +++ b/client/src/Pages/StatusPage/Status/index.tsx @@ -1,5 +1,6 @@ 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 { useTranslation } from "react-i18next"; @@ -43,6 +44,10 @@ const StatusPageView = () => { /> {t("statusPageStatusServiceStatus")} +
 {JSON.stringify(data, null, 2)}
); From 56ead03416be76bb10780e5d57e40345e0f084a4 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 2 Feb 2026 21:51:06 +0000 Subject: [PATCH 06/10] breadcrumbs --- .../v2/design-elements/BasePage.tsx | 2 +- .../v2/design-elements/Breadcrumb.tsx | 53 ++++++++- client/src/Components/v2/inputs/Switch.tsx | 14 ++- .../Components/HeaderStatusPageControls.tsx | 2 +- .../Status/Components/MonitorsList.tsx | 103 +++++++++++++++++- client/src/Pages/StatusPage/Status/index.tsx | 18 +-- client/src/Utils/MonitorUtils.ts | 7 ++ client/src/locales/en.json | 4 + 8 files changed, 186 insertions(+), 17 deletions(-) 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..8ecfd6ec6 100644 --- a/client/src/Components/v2/design-elements/Breadcrumb.tsx +++ b/client/src/Components/v2/design-elements/Breadcrumb.tsx @@ -11,13 +11,64 @@ const isId = (segment: string): boolean => { const actionSegments = ["create", "configure"]; -export const Breadcrumb = () => { +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 ( + + } + sx={{ + fontSize: "14px", + marginBottom: theme.spacing(6), + "& .MuiBreadcrumbs-separator": { + color: theme.palette.text.secondary, + }, + }} + > + {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"); 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/Pages/StatusPage/Status/Components/HeaderStatusPageControls.tsx b/client/src/Pages/StatusPage/Status/Components/HeaderStatusPageControls.tsx index 87349d703..382bf08e4 100644 --- a/client/src/Pages/StatusPage/Status/Components/HeaderStatusPageControls.tsx +++ b/client/src/Pages/StatusPage/Status/Components/HeaderStatusPageControls.tsx @@ -72,7 +72,7 @@ export const HeaderStatusPageControls = ({ )} - {isAdmin && ( + {isAdmin && !isPublic && ( From 85190992c705334aad52bbdc40710460ea4c197f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 2 Feb 2026 22:02:14 +0000 Subject: [PATCH 09/10] format --- .../StatusPage/Status/Components/MonitorsList.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/client/src/Pages/StatusPage/Status/Components/MonitorsList.tsx b/client/src/Pages/StatusPage/Status/Components/MonitorsList.tsx index fca54ae4f..d559e7c82 100644 --- a/client/src/Pages/StatusPage/Status/Components/MonitorsList.tsx +++ b/client/src/Pages/StatusPage/Status/Components/MonitorsList.tsx @@ -68,7 +68,16 @@ export const MonitorsList = ({ statusPage, monitors }: MonitorsListProps) => { mb={statusPage.showCharts !== false ? theme.spacing(4) : 0} > - {monitor.name} + + {monitor.name} + {showURL && ( Date: Mon, 2 Feb 2026 22:15:57 +0000 Subject: [PATCH 10/10] cleanup --- client/src/Pages/StatusPage/Create/index.jsx | 11 +- .../Status/Components/AdminLink/index.jsx | 36 ---- .../Components/ControlsHeader/index.jsx | 141 --------------- .../Status/Components/MonitorsList/index.jsx | 69 -------- .../Status/Components/Skeleton/index.jsx | 14 -- .../Status/Components/StatusBar/index.jsx | 77 -------- client/src/Pages/StatusPage/Status/old.jsx | 166 ------------------ .../Pages/StatusPage/StatusPages/index.tsx | 8 + client/src/locales/en.json | 4 - 9 files changed, 13 insertions(+), 513 deletions(-) delete mode 100644 client/src/Pages/StatusPage/Status/Components/AdminLink/index.jsx delete mode 100644 client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx delete mode 100644 client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx delete mode 100644 client/src/Pages/StatusPage/Status/Components/Skeleton/index.jsx delete mode 100644 client/src/Pages/StatusPage/Status/Components/StatusBar/index.jsx delete mode 100644 client/src/Pages/StatusPage/Status/old.jsx 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" > - - - ); -}; - -Controls.propTypes = { - type: PropTypes.string, - url: PropTypes.string, -}; - -const ControlsHeader = ({ statusPage, isPublic, url, type = "uptime" }) => { - const theme = useTheme(); - const { t } = useTranslation(); - const publicUrl = `/status/uptime/public/${url}`; - - return ( - - - - - {statusPage?.companyName} - - {statusPage?.isPublished && !isPublic && ( - { - window.open(publicUrl, "_blank", "noopener,noreferrer"); - }} - sx={{ - display: "inline-flex", - ":hover": { - cursor: "pointer", - borderBottom: 1, - }, - }} - > - {t("publicLink")} - - - )} - - - {isPublic && } - - ); -}; - -ControlsHeader.propTypes = { - url: PropTypes.string, - statusPage: PropTypes.object, - isPublic: PropTypes.bool, - type: PropTypes.string, -}; - -export default ControlsHeader; 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/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/old.jsx b/client/src/Pages/StatusPage/Status/old.jsx deleted file mode 100644 index adcd9c2c7..000000000 --- a/client/src/Pages/StatusPage/Status/old.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/StatusPages/index.tsx b/client/src/Pages/StatusPage/StatusPages/index.tsx index ad1a39b29..6a34e587d 100644 --- a/client/src/Pages/StatusPage/StatusPages/index.tsx +++ b/client/src/Pages/StatusPage/StatusPages/index.tsx @@ -3,6 +3,8 @@ 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(); @@ -13,6 +15,8 @@ const StatusPages = () => { error, } = useGet("/status-page/team"); + const isAdmin = useIsAdmin(); + return ( { actionButtonText={t("pages.statusPages.fallback.actionButton")} actionLink="/status/uptime/create" > + ); diff --git a/client/src/locales/en.json b/client/src/locales/en.json index affb70a73..88f1ffc7f 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -546,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",