breadcrumbs
This commit is contained in:
1 parent
a912a5ebb8
commit
56ead03416
8 files changed
+186
-17
No files matched your search
@@ -91,7 +91,7 @@ export const BasePage = ({
|
||||
spacing={theme.spacing(10)}
|
||||
{...props}
|
||||
>
|
||||
<Breadcrumb />
|
||||
<Breadcrumb breadcrumbOverride={breadcrumbOverride} />
|
||||
{children}
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -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 (
|
||||
<MuiBreadcrumbs
|
||||
separator={
|
||||
<ChevronRight
|
||||
size={16}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
}
|
||||
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 (
|
||||
<Typography
|
||||
key={index}
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
fontWeight: isLast ? 600 : 400,
|
||||
color: isLast ? theme.palette.primary.main : theme.palette.text.secondary,
|
||||
}}
|
||||
>
|
||||
{item}
|
||||
</Typography>
|
||||
);
|
||||
})}
|
||||
</MuiBreadcrumbs>
|
||||
);
|
||||
}
|
||||
|
||||
// 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");
|
||||
|
||||
|
||||
@@ -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<HTMLInputElement, SwitchProps>(
|
||||
function SwitchComponent({ sx, ...props }, ref) {
|
||||
interface SwitchComponentProps extends SwitchProps {
|
||||
dualOption?: boolean;
|
||||
}
|
||||
|
||||
export const SwitchComponent = forwardRef<HTMLInputElement, SwitchComponentProps>(
|
||||
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<HTMLInputElement, SwitchProps>(
|
||||
},
|
||||
},
|
||||
},
|
||||
...(dualOption && {
|
||||
"& .MuiSwitch-track": {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
opacity: 1,
|
||||
},
|
||||
}),
|
||||
},
|
||||
...additionalSx,
|
||||
]}
|
||||
|
||||
@@ -72,7 +72,7 @@ export const HeaderStatusPageControls = ({
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
{isAdmin && (
|
||||
{isAdmin && !isPublic && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
|
||||
@@ -1,11 +1,104 @@
|
||||
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 MontorsListProps {
|
||||
stautsPage: StatusPage;
|
||||
monitors: Monitor[];
|
||||
interface StatusPageMonitor extends Monitor {
|
||||
checks?: Monitor["recentChecks"];
|
||||
}
|
||||
|
||||
export const MonitorsList = ({ stautsPage, monitors }: MontorsListProps) => {
|
||||
return <div>Monitors List</div>;
|
||||
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 (
|
||||
<Stack gap={theme.spacing(8)}>
|
||||
{statusPage.showCharts !== false && (
|
||||
<Stack
|
||||
direction={"row"}
|
||||
alignItems={"center"}
|
||||
>
|
||||
<Typography>{t("pages.statusPages.monitorsList.chartTypeHeatmap")}</Typography>
|
||||
<SwitchComponent
|
||||
dualOption
|
||||
value={chartType}
|
||||
checked={chartType === "histogram"}
|
||||
onChange={(e) => {
|
||||
setChartType(e.target.checked ? "histogram" : "heatmap");
|
||||
}}
|
||||
/>
|
||||
<Typography>
|
||||
{t("pages.statusPages.monitorsList.chartTypeHistogram")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{monitors?.map((monitor) => {
|
||||
const status = determineState(monitor);
|
||||
return (
|
||||
<BaseBox
|
||||
key={monitor.id}
|
||||
padding={theme.spacing(4)}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
gap={theme.spacing(4)}
|
||||
mb={statusPage.showCharts !== false ? theme.spacing(4) : 0}
|
||||
>
|
||||
<Box>
|
||||
<Typography variant="h6">{monitor.name}</Typography>
|
||||
{showURL && (
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
>
|
||||
{monitor.url}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<StatusLabel
|
||||
status={status === "up"}
|
||||
isActive={monitor.isActive}
|
||||
/>
|
||||
</Stack>
|
||||
{statusPage.showCharts !== false && (
|
||||
<Box>
|
||||
{chartType === "histogram" ? (
|
||||
<HistogramResponseTime
|
||||
checks={monitor?.checks?.slice().reverse() ?? []}
|
||||
/>
|
||||
) : (
|
||||
<HeatmapResponseTime
|
||||
checks={monitor?.checks?.slice().reverse() ?? []}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</BaseBox>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -3,6 +3,7 @@ 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";
|
||||
@@ -11,6 +12,7 @@ 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();
|
||||
@@ -24,18 +26,21 @@ const StatusPageView = () => {
|
||||
const statusPage = data?.statusPage;
|
||||
const monitors = data?.monitors ?? [];
|
||||
|
||||
if (!statusPage || !monitors) {
|
||||
return null;
|
||||
}
|
||||
if (!statusPage) return null;
|
||||
|
||||
if (monitors.length === 0) {
|
||||
return "poo";
|
||||
let sx: React.CSSProperties = {};
|
||||
if (isPublic) {
|
||||
sx.paddingTop = theme.spacing(20);
|
||||
sx.paddingLeft = "20vw";
|
||||
sx.paddingRight = "20vw";
|
||||
}
|
||||
|
||||
return (
|
||||
<BasePage
|
||||
loading={isLoading}
|
||||
error={error}
|
||||
sx={sx}
|
||||
breadcrumbOverride={isPublic ? [] : undefined}
|
||||
>
|
||||
<HeaderStatusPageControls
|
||||
isAdmin={isAdmin}
|
||||
@@ -45,10 +50,9 @@ const StatusPageView = () => {
|
||||
<Typography variant="h2">{t("statusPageStatusServiceStatus")}</Typography>
|
||||
<StatusBar monitors={monitors} />
|
||||
<MonitorsList
|
||||
stautsPage={statusPage}
|
||||
statusPage={statusPage}
|
||||
monitors={monitors}
|
||||
/>
|
||||
<pre> {JSON.stringify(data, null, 2)}</pre>
|
||||
</BasePage>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,13 @@ import type { Monitor, MonitorStatus, MonitorType } from "@/Types/Monitor";
|
||||
import type { PaletteKey } from "@/Utils/Theme/v2Theme";
|
||||
import type { ValueType } from "@/Components/v2/design-elements/StatusLabel";
|
||||
|
||||
export const determineState = (monitor: Monitor) => {
|
||||
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<MonitorType, string> = {
|
||||
http: "uptime",
|
||||
|
||||
@@ -479,6 +479,10 @@
|
||||
"allDown": "All systems down",
|
||||
"degraded": "Degraded performance",
|
||||
"unknown": "Unknown status"
|
||||
},
|
||||
"monitorsList": {
|
||||
"chartTypeHeatmap": "Heatmap",
|
||||
"chartTypeHistogram": "Histogram"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user