feat: statusPage - adding pagespeed section
This commit is contained in:
1 parent
8ab049aa6c
commit
6cea199e4f
15 files changed
+231
-11
No files matched your search
@@ -17,6 +17,7 @@ export const Gauge = ({
|
||||
strokeWidth = 15,
|
||||
precision = 1,
|
||||
unit = "%",
|
||||
colorFn,
|
||||
}: {
|
||||
isLoading?: boolean;
|
||||
progress?: number;
|
||||
@@ -24,6 +25,7 @@ export const Gauge = ({
|
||||
strokeWidth?: number;
|
||||
precision?: number;
|
||||
unit?: string;
|
||||
colorFn?: (val: number, theme: any) => string;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const progressWithinRange = Math.max(MINIMUM_VALUE, Math.min(progress, MAXIMUM_VALUE));
|
||||
@@ -48,7 +50,8 @@ export const Gauge = ({
|
||||
return () => clearTimeout(timer);
|
||||
}, [progress, circumference, strokeLength]);
|
||||
|
||||
const fillColor = getInfraGaugeColor(progressWithinRange, theme);
|
||||
const resolvedColorFn = colorFn ?? getInfraGaugeColor;
|
||||
const fillColor = resolvedColorFn(progressWithinRange, theme);
|
||||
|
||||
if (isLoading) {
|
||||
return;
|
||||
|
||||
@@ -35,6 +35,7 @@ export const useStatusPageForm = ({
|
||||
showUptimePercentage: data?.showUptimePercentage ?? true,
|
||||
showAdminLoginLink: data?.showAdminLoginLink ?? false,
|
||||
showInfrastructure: data?.showInfrastructure ?? false,
|
||||
showPageSpeed: data?.showPageSpeed ?? false,
|
||||
customCSS: data?.customCSS || "",
|
||||
logo: transformLogo(data?.logo),
|
||||
};
|
||||
|
||||
@@ -37,9 +37,17 @@ import { HeaderConfigStatusControls } from "./Components/HeaderConfigStatusContr
|
||||
|
||||
const monitorsUrl = (() => {
|
||||
const params = new URLSearchParams();
|
||||
["http", "ping", "port", "docker", "game", "grpc", "websocket", "hardware"].forEach(
|
||||
(type) => params.append("type", type)
|
||||
);
|
||||
[
|
||||
"http",
|
||||
"ping",
|
||||
"port",
|
||||
"docker",
|
||||
"game",
|
||||
"grpc",
|
||||
"websocket",
|
||||
"hardware",
|
||||
"pagespeed",
|
||||
].forEach((type) => params.append("type", type));
|
||||
return `/monitors/team?${params.toString()}`;
|
||||
})();
|
||||
|
||||
@@ -59,7 +67,9 @@ const CreateStatusPage = () => {
|
||||
// Fetch existing status page data when configuring
|
||||
const { data: statusPageData, isLoading: isLoadingStatusPage } =
|
||||
useGet<StatusPageResponse>(
|
||||
isCreate ? null : `/status-page/${url}?type=uptime&type=infrastructure`
|
||||
isCreate
|
||||
? null
|
||||
: `/status-page/${url}?type=uptime&type=infrastructure&type=pagespeed`
|
||||
);
|
||||
|
||||
const { data: monitorsResponse } = useGet<Monitor[]>(monitorsUrl);
|
||||
@@ -98,7 +108,9 @@ const CreateStatusPage = () => {
|
||||
|
||||
const typesSet = new Set<MonitorDisplayType>();
|
||||
selectedMonitors.forEach((m) => {
|
||||
typesSet.add(m.type === "hardware" ? "infrastructure" : "uptime");
|
||||
if (m.type === "hardware") typesSet.add("infrastructure");
|
||||
else if (m.type === "pagespeed") typesSet.add("pagespeed");
|
||||
else typesSet.add("uptime");
|
||||
});
|
||||
|
||||
return typesSet.size ? Array.from(typesSet) : ["uptime"];
|
||||
@@ -139,6 +151,7 @@ const CreateStatusPage = () => {
|
||||
fd.append("showUptimePercentage", String(data.showUptimePercentage));
|
||||
fd.append("showAdminLoginLink", String(data.showAdminLoginLink));
|
||||
fd.append("showInfrastructure", String(data.showInfrastructure));
|
||||
fd.append("showPageSpeed", String(data.showPageSpeed));
|
||||
|
||||
data.monitors.forEach((monitorId) => {
|
||||
fd.append("monitors[]", monitorId);
|
||||
@@ -475,6 +488,21 @@ const CreateStatusPage = () => {
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="showPageSpeed"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
}
|
||||
label={t("pages.statusPages.form.features.option.showPageSpeed.label")}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{/* <Controller
|
||||
name="showUptimePercentage"
|
||||
control={control}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { HistogramResponseTime, HeatmapResponseTime } from "@/Components/common"
|
||||
import { StatusLabel, BaseBox } from "@/Components/design-elements";
|
||||
import { SwitchComponent } from "@/Components/inputs";
|
||||
import { InfrastructureMetrics } from "@/Pages/StatusPage/Status/Components/InfrastructureMetrics";
|
||||
import { PageSpeedMetrics } from "@/Pages/StatusPage/Status/Components/PageSpeedMetrics";
|
||||
|
||||
import { useTheme, type Theme } from "@mui/material/styles";
|
||||
import { useSelector } from "react-redux";
|
||||
@@ -26,8 +27,14 @@ interface MonitorsListProps {
|
||||
}
|
||||
|
||||
const getMonitorBadgeStyles = (monitorType: string, theme: Theme) => {
|
||||
const monitorColors = {
|
||||
hardware: theme.palette.info.light,
|
||||
pagespeed: theme.palette.warning.light,
|
||||
};
|
||||
|
||||
const bg =
|
||||
monitorType === "hardware" ? theme.palette.info.light : theme.palette.success.light;
|
||||
monitorColors[monitorType as keyof typeof monitorColors] ||
|
||||
theme.palette.success.light;
|
||||
return {
|
||||
backgroundColor: bg,
|
||||
color: theme.palette.background.paper,
|
||||
@@ -105,6 +112,11 @@ const MonitorContent = ({
|
||||
return <InfrastructureMetrics monitor={monitor} />;
|
||||
}
|
||||
|
||||
if (monitor.type === "pagespeed") {
|
||||
if (statusPage.showPageSpeed === false) return null;
|
||||
return <PageSpeedMetrics monitor={monitor} />;
|
||||
}
|
||||
|
||||
if (statusPage.showCharts === false) return null;
|
||||
|
||||
const checks = monitor.checks?.slice().reverse() ?? [];
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { Gauge } from "@/Components/design-elements";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { Monitor } from "@/Types/Monitor";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||
import Box from "@mui/material/Box";
|
||||
import { LAYOUT, SPACING } from "@/Utils/Theme/constants";
|
||||
import { getPageSpeedGaugeColor } from "@/Utils/MonitorUtils";
|
||||
|
||||
const GAUGE_RADIUS = 60;
|
||||
const GAUGE_STROKE_WIDTH = 12;
|
||||
|
||||
interface StatusPageMonitor extends Monitor {
|
||||
checks?: Monitor["recentChecks"];
|
||||
}
|
||||
|
||||
interface MetricConfig {
|
||||
key: string;
|
||||
label: string;
|
||||
progress: number;
|
||||
}
|
||||
|
||||
const MetricItem = ({ label, progress }: MetricConfig) => {
|
||||
const theme = useTheme();
|
||||
const isSmall = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
return (
|
||||
<Grid
|
||||
size={isSmall ? 12 : 3}
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
textAlign: "center",
|
||||
gap: theme.spacing(SPACING.LG),
|
||||
padding: theme.spacing(LAYOUT.XS),
|
||||
borderRight: isSmall ? "none" : `1px solid ${theme.palette.divider}`,
|
||||
borderBottom: isSmall ? `1px solid ${theme.palette.divider}` : "none",
|
||||
"&:last-child": {
|
||||
borderRight: "none",
|
||||
borderBottom: "none",
|
||||
paddingBottom: theme.spacing(SPACING.LG),
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
alignItems="center"
|
||||
>
|
||||
<Gauge
|
||||
progress={progress}
|
||||
radius={GAUGE_RADIUS}
|
||||
strokeWidth={GAUGE_STROKE_WIDTH}
|
||||
colorFn={getPageSpeedGaugeColor}
|
||||
/>
|
||||
<Typography variant="body2">{label}</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
const buildMetrics = (
|
||||
check: NonNullable<Monitor["recentChecks"]>[number],
|
||||
t: (key: string) => string
|
||||
): MetricConfig[] => {
|
||||
const metrics: MetricConfig[] = [];
|
||||
|
||||
if (typeof check.performance === "number") {
|
||||
metrics.push({
|
||||
key: "performance",
|
||||
label: t("pages.statusPages.monitorsList.pagespeed.performance"),
|
||||
progress: check.performance,
|
||||
});
|
||||
}
|
||||
if (typeof check.accessibility === "number") {
|
||||
metrics.push({
|
||||
key: "accessibility",
|
||||
label: t("pages.statusPages.monitorsList.pagespeed.accessibility"),
|
||||
progress: check.accessibility,
|
||||
});
|
||||
}
|
||||
if (typeof check.bestPractices === "number") {
|
||||
metrics.push({
|
||||
key: "bestPractices",
|
||||
label: t("pages.statusPages.monitorsList.pagespeed.bestPractices"),
|
||||
progress: check.bestPractices,
|
||||
});
|
||||
}
|
||||
if (typeof check.seo === "number") {
|
||||
metrics.push({
|
||||
key: "seo",
|
||||
label: t("pages.statusPages.monitorsList.pagespeed.seo"),
|
||||
progress: check.seo,
|
||||
});
|
||||
}
|
||||
|
||||
return metrics;
|
||||
};
|
||||
|
||||
export const PageSpeedMetrics = ({ monitor }: { monitor: StatusPageMonitor }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const latestCheck = monitor.recentChecks?.[0] ?? monitor.checks?.[0];
|
||||
|
||||
if (!latestCheck) {
|
||||
return (
|
||||
<Typography
|
||||
variant="body2"
|
||||
color={theme.palette.text.secondary}
|
||||
>
|
||||
{t("pages.statusPages.monitorsList.noData")}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
const metrics = buildMetrics(latestCheck, t);
|
||||
|
||||
if (metrics.length === 0) {
|
||||
return (
|
||||
<Typography
|
||||
variant="body2"
|
||||
color={theme.palette.text.secondary}
|
||||
>
|
||||
{t("pages.statusPages.monitorsList.noData")}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
alignItems="center"
|
||||
padding={theme.spacing(LAYOUT.LG)}
|
||||
sx={{ flex: 1 }}
|
||||
>
|
||||
{metrics.map((metric) => (
|
||||
<MetricItem
|
||||
key={metric.key}
|
||||
label={metric.label}
|
||||
progress={metric.progress}
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
@@ -23,7 +23,9 @@ const StatusPageView = () => {
|
||||
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
|
||||
const isPublic = location.pathname.startsWith("/status/public");
|
||||
|
||||
const apiUrl = url ? `/status-page/${url}?type=uptime&type=infrastructure` : null;
|
||||
const apiUrl = url
|
||||
? `/status-page/${url}?type=uptime&type=infrastructure&type=pagespeed`
|
||||
: null;
|
||||
|
||||
const { data, isLoading, error } = useGet<StatusPageResponse>(
|
||||
apiUrl,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Monitor, MonitorType } from "@/Types/Monitor";
|
||||
export type MonitorDisplayType = "uptime" | "infrastructure";
|
||||
export type MonitorDisplayType = "uptime" | "infrastructure" | "pagespeed";
|
||||
|
||||
export const MONITOR_TYPE_KEYS: Partial<Record<MonitorType, string>> = {
|
||||
http: "pages.common.monitors.monitorTypes.optionHttp",
|
||||
@@ -42,6 +42,7 @@ export interface StatusPage {
|
||||
showUptimePercentage: boolean;
|
||||
showAdminLoginLink: boolean;
|
||||
showInfrastructure: boolean;
|
||||
showPageSpeed: boolean;
|
||||
customCSS: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
|
||||
@@ -78,6 +78,12 @@ export const getPageSpeedPalette = (score: number): PaletteKey => {
|
||||
else return "error";
|
||||
};
|
||||
|
||||
export const getPageSpeedGaugeColor = (val: number, theme: any) => {
|
||||
if (val >= 90) return theme.palette.success.main;
|
||||
else if (val >= 50) return theme.palette.warning.light;
|
||||
else return theme.palette.error.light;
|
||||
};
|
||||
|
||||
export const formatUrl = (url: string, maxLength: number = 55) => {
|
||||
if (!url) return "";
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export const statusPageSchema = z.object({
|
||||
),
|
||||
timezone: z.string().optional(),
|
||||
type: z
|
||||
.array(z.enum(["uptime", "infrastructure"]))
|
||||
.array(z.enum(["uptime", "infrastructure", "pagespeed"]))
|
||||
.min(1, "At least one type is required"),
|
||||
color: z.string().min(1, "Color is required"),
|
||||
monitors: z.array(z.string()).min(1, "At least one monitor is required"),
|
||||
@@ -24,6 +24,7 @@ export const statusPageSchema = z.object({
|
||||
showUptimePercentage: z.boolean(),
|
||||
showAdminLoginLink: z.boolean(),
|
||||
showInfrastructure: z.boolean(),
|
||||
showPageSpeed: z.boolean(),
|
||||
customCSS: z.string().optional(),
|
||||
logo: z
|
||||
.object({
|
||||
|
||||
@@ -324,6 +324,7 @@ const statusPageValidation = joi.object({
|
||||
showCharts: joi.boolean(),
|
||||
showAdminLoginLink: joi.boolean(),
|
||||
showInfrastructure: joi.boolean(),
|
||||
showPageSpeed: joi.boolean(),
|
||||
});
|
||||
|
||||
const settingsValidation = joi.object({
|
||||
|
||||
@@ -1202,6 +1202,13 @@
|
||||
"used": "Used",
|
||||
"total": "Total"
|
||||
},
|
||||
"pagespeed": {
|
||||
"title": "PageSpeed",
|
||||
"performance": "Performance",
|
||||
"accessibility": "Accessibility",
|
||||
"bestPractices": "Best Practices",
|
||||
"seo": "SEO"
|
||||
},
|
||||
"uptime": {
|
||||
"title": "Uptime",
|
||||
"responseTime": "Response time"
|
||||
@@ -1300,6 +1307,9 @@
|
||||
},
|
||||
"showInfrastructure": {
|
||||
"label": "Show infrastructure metrics"
|
||||
},
|
||||
"showPageSpeed": {
|
||||
"label": "Show PageSpeed scores"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,6 +104,10 @@ const StatusPageSchema = new Schema<StatusPageDocument>(
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showPageSpeed: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
customCSS: {
|
||||
type: String,
|
||||
default: "",
|
||||
|
||||
@@ -58,6 +58,7 @@ class MongoStatusPagesRepository implements IStatusPagesRepository {
|
||||
showUptimePercentage: doc.showUptimePercentage,
|
||||
showAdminLoginLink: doc.showAdminLoginLink,
|
||||
showInfrastructure: doc.showInfrastructure,
|
||||
showPageSpeed: doc.showPageSpeed,
|
||||
customCSS: doc.customCSS,
|
||||
createdAt: this.toDateString(doc.createdAt),
|
||||
updatedAt: this.toDateString(doc.updatedAt),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const StatusPageTypes = ["uptime", "infrastructure"] as const;
|
||||
export const StatusPageTypes = ["uptime", "infrastructure", "pagespeed"] as const;
|
||||
export type StatusPageType = (typeof StatusPageTypes)[number];
|
||||
|
||||
export interface StatusPageLogo {
|
||||
@@ -29,6 +29,7 @@ export interface StatusPage {
|
||||
showUptimePercentage: boolean;
|
||||
showAdminLoginLink: boolean;
|
||||
showInfrastructure: boolean;
|
||||
showPageSpeed: boolean;
|
||||
customCSS: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
|
||||
@@ -32,6 +32,7 @@ export const createStatusPageBodyValidation = z
|
||||
showUptimePercentage: booleanCoercion,
|
||||
showAdminLoginLink: booleanCoercion.optional(),
|
||||
showInfrastructure: booleanCoercion.optional(),
|
||||
showPageSpeed: booleanCoercion.optional(),
|
||||
removeLogo: z.union([z.literal("true"), z.literal("false")]).optional(),
|
||||
})
|
||||
.strip();
|
||||
|
||||
Reference in new issue
Block a user