This commit is contained in:
Alex Holliday committed 2026-02-02 22:15:57 +00:00
1 parent 85190992c7
commit 1fefb038f0
9 files changed
+13 -513

No files matched your search

+5 -6
View File
@@ -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"
>
<Button
loading={isDeleting}
// loading={isDeleting}
variant="contained"
color="error"
onClick={() => setIsDeleteOpen(true)}
@@ -266,7 +265,7 @@ const CreateStatusPage = () => {
open={isDeleteOpen}
confirmationButtonLabel={t("deleteStatusPageConfirm")}
description={t("deleteStatusPageDescription")}
isLoading={isDeleting || statusPageIsLoading}
// isLoading={isDeleting || statusPageIsLoading}
/>
</Stack>
)}
@@ -290,7 +289,7 @@ const CreateStatusPage = () => {
handleDelete={handleDelete}
isDeleteOpen={isDeleteOpen}
setIsDeleteOpen={setIsDeleteOpen}
isDeleting={isDeleting}
// isDeleting={isDeleting}
isLoading={statusPageIsLoading}
/>
<Stack
@@ -1,36 +0,0 @@
// Components
import { Box, Typography } from "@mui/material";
// Utils
import { useTheme } from "@mui/material/styles";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
const AdminLink = () => {
const theme = useTheme();
const { t } = useTranslation();
const navigate = useNavigate();
return (
<Box>
<Typography
className="forgot-p"
display="inline-block"
color={theme.palette.primary.contrastText}
>
{t("administrator")}
</Typography>
<Typography
component="span"
color={theme.palette.accent.main}
ml={theme.spacing(2)}
sx={{ cursor: "pointer" }}
onClick={() => navigate("/login")}
>
{t("loginHere")}
</Typography>
</Box>
);
};
export default AdminLink;
@@ -1,141 +0,0 @@
// Components
import { Box, Stack, Typography, Button } from "@mui/material";
import Image from "@/Components/v1/Image/index.jsx";
import Icon from "@/Components/v1/Icon";
import ThemeSwitch from "@/Components/v1/ThemeSwitch/index.jsx";
//Utils
import { useTheme } from "@mui/material/styles";
import { useNavigate } from "react-router-dom";
import { useLocation } from "react-router-dom";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
const Controls = ({ url, type }) => {
const theme = useTheme();
const { t } = useTranslation();
const location = useLocation();
const currentPath = location.pathname;
const navigate = useNavigate();
if (currentPath.startsWith("/status/uptime/public")) {
return null;
}
return (
<Stack
direction="row"
gap={theme.spacing(2)}
>
<Box>
<Button
variant="contained"
color="secondary"
onClick={() => {
if (type === "uptime") {
navigate(`/status/uptime/configure/${url}`);
}
}}
sx={{
px: theme.spacing(5),
"& svg": {
mr: theme.spacing(3),
"& path": {
stroke: theme.palette.secondary.contrastText,
},
},
}}
>
<Icon
name="Settings"
size={18}
style={{ marginRight: "8px" }}
/>{" "}
{t("configure")}
</Button>
</Box>
</Stack>
);
};
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 (
<Stack
alignSelf="flex-start"
direction="row"
width="100%"
gap={theme.spacing(2)}
justifyContent="space-between"
alignItems="flex-end"
>
<Stack
direction="row"
gap={theme.spacing(8)}
alignItems="flex-end"
>
<Image
shouldRender={statusPage?.logo?.data ? true : false}
alt={"Company logo"}
maxWidth={"300px"}
maxHeight={"50px"}
base64={statusPage?.logo?.data}
/>
<Typography
variant="h1"
overflow="hidden"
textOverflow="ellipsis"
sx={{
maxWidth: { xs: "200px", sm: "100%" },
}}
>
{statusPage?.companyName}
</Typography>
{statusPage?.isPublished && !isPublic && (
<Stack
direction="row"
alignItems="center"
justifyContent="center"
onClick={() => {
window.open(publicUrl, "_blank", "noopener,noreferrer");
}}
sx={{
display: "inline-flex",
":hover": {
cursor: "pointer",
borderBottom: 1,
},
}}
>
<Typography>{t("publicLink")}</Typography>
<Icon
name="ExternalLink"
size={18}
/>
</Stack>
)}
</Stack>
<Controls
url={url}
type={type}
/>
{isPublic && <ThemeSwitch />}
</Stack>
);
};
ControlsHeader.propTypes = {
url: PropTypes.string,
statusPage: PropTypes.object,
isPublic: PropTypes.bool,
type: PropTypes.string,
};
export default ControlsHeader;
@@ -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 (
<Stack
key={monitor.id}
width="100%"
gap={theme.spacing(10)}
margin="0 auto"
maxWidth="95%"
>
<Host
key={monitor.id}
url={monitor.url}
title={monitor.name}
percentageColor={monitor.percentageColor}
percentage={monitor.percentage}
showURL={showURL}
status={status}
/>
<Stack
direction="row"
alignItems="center"
gap={theme.spacing(1)}
>
{statusPage.showCharts !== false && (
<Box flex={9}>
<StatusPageBarChart checks={monitor?.checks?.slice().reverse()} />
</Box>
)}
</Stack>
</Stack>
);
})}
</>
);
};
MonitorsList.propTypes = {
monitors: PropTypes.array.isRequired,
statusPage: PropTypes.object,
};
export default MonitorsList;
@@ -1,14 +0,0 @@
import { Stack, Skeleton } from "@mui/material";
const SkeletonLayout = () => {
return (
<Stack>
<Skeleton
variant="rectangular"
height={"90vh"}
/>
</Stack>
);
};
export default SkeletonLayout;
@@ -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: (
<Icon
name="AlertTriangle"
size={24}
color="primary.contrastTextSecondaryDarkBg"
/>
),
};
if (monitors.every((monitor) => monitor.status === true)) {
monitorsStatus.msg = "All systems operational";
monitorsStatus.color = theme.palette.success.lowContrast;
monitorsStatus.icon = (
<Icon
name="CheckCircle2"
size={24}
color="primary.contrastTextSecondaryDarkBg"
/>
);
}
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 (
<Stack
direction="row"
alignItems="center"
justifyContent="center"
gap={theme.spacing(2)}
height={theme.spacing(30)}
width={"100%"}
backgroundColor={monitorsStatus.color}
borderRadius={theme.spacing(2)}
>
{monitorsStatus.icon}
{/* CAIO_REVIEW */}
<Typography variant="h2DarkBg">{monitorsStatus.msg}</Typography>
</Stack>
);
};
export default StatusBar;
StatusBar.propTypes = {
status: PropTypes.object,
};
-166
View File
@@ -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 = <AdminLink />;
}
// Loading
if (isLoading) {
return <SkeletonLayout />;
}
if (monitors?.length === 0) {
return (
<GenericFallback>
<Typography
variant="h1"
marginY={theme.spacing(4)}
color={theme.palette.primary.contrastTextTertiary}
>
{"Theres nothing here yet"}
</Typography>
{isAdmin && (
<TextLink
linkText={"Add a monitor to get started"}
href={`/status/uptime/configure/${url}`}
/>
)}
</GenericFallback>
);
}
// Error fetching data
if (networkError === true) {
return (
<GenericFallback>
<Typography
variant="h1"
marginY={theme.spacing(4)}
color={theme.palette.primary.contrastTextTertiary}
>
{t("common.toasts.networkError")}
</Typography>
<Typography>{t("common.toasts.checkConnection")}</Typography>
</GenericFallback>
);
}
// Public status page fallback
if (!isLoading && typeof statusPage === "undefined" && isPublic) {
return (
<Stack sx={sx}>
<GenericFallback>
<Typography
variant="h1"
marginY={theme.spacing(4)}
color={theme.palette.primary.contrastTextTertiary}
>
{t("statusPageStatus")}
</Typography>
<Typography>{t("statusPageStatusContactAdmin")}</Typography>
</GenericFallback>
</Stack>
);
}
// Finished loading, but status page is not public
if (!isLoading && isPublic && statusPage.isPublished === false) {
return (
<Stack sx={sx}>
<GenericFallback>
<Typography
variant="h1"
marginY={theme.spacing(4)}
color={theme.palette.primary.contrastTextTertiary}
>
{t("statusPageStatusNotPublic")}
</Typography>
<Typography>{t("statusPageStatusContactAdmin")}</Typography>
</GenericFallback>
</Stack>
);
}
// Status page doesn't exist
if (!isLoading && typeof statusPage === "undefined") {
return (
<GenericFallback>
<Typography
variant="h1"
marginY={theme.spacing(4)}
color={theme.palette.primary.contrastTextTertiary}
>
{t("statusPageStatusNoPage")}
</Typography>
<Typography>{t("statusPageStatusContactAdmin")}</Typography>
</GenericFallback>
);
}
return (
<Stack
gap={theme.spacing(10)}
sx={{ ...sx, position: "relative" }}
>
{!isPublic && <Breadcrumbs list={crumbs} />}
<ControlsHeader
statusPage={statusPage}
url={url}
isPublic={isPublic}
/>
<Typography variant="h2">{t("statusPageStatusServiceStatus")}</Typography>
<StatusBar monitors={monitors} />
<MonitorsList
monitors={monitors}
statusPage={statusPage}
/>
{link}
</Stack>
);
};
export default PublicStatus;
@@ -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<StatusPage[]>("/status-page/team");
const isAdmin = useIsAdmin();
return (
<BasePageWithStates
page={t("pages.statusPages.title")}
@@ -25,6 +29,10 @@ const StatusPages = () => {
actionButtonText={t("pages.statusPages.fallback.actionButton")}
actionLink="/status/uptime/create"
>
<HeaderCreate
path="/status/uptime/create"
isAdmin={isAdmin}
/>
<StatusPagesTable data={statusPages ?? []} />
</BasePageWithStates>
);
-4
View File
@@ -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",