Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fc013246d | ||
|
|
5a8d0432ff | ||
|
|
866388c09e | ||
|
|
c7d93311ce | ||
|
|
41fa2717a9 | ||
|
|
6cea199e4f |
No files matched your search
@@ -3,7 +3,7 @@ import Stack from "@mui/material/Stack";
|
|||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
import { useTheme, type Theme } from "@mui/material/styles";
|
||||||
import { useMemo, useState, useEffect } from "react";
|
import { useMemo, useState, useEffect } from "react";
|
||||||
import { getInfraGaugeColor } from "@/Utils/MonitorUtils";
|
import { getInfraGaugeColor } from "@/Utils/MonitorUtils";
|
||||||
|
|
||||||
@@ -17,6 +17,10 @@ export const Gauge = ({
|
|||||||
strokeWidth = 15,
|
strokeWidth = 15,
|
||||||
precision = 1,
|
precision = 1,
|
||||||
unit = "%",
|
unit = "%",
|
||||||
|
// colorFn lets callers override the default color logic.
|
||||||
|
// The Gauge was originally designed for metrics where higher values are worse (e.g., CPU usage).
|
||||||
|
// PageSpeed scores are the opposite—higher is better—so a custom colorFn can be used to invert the behavior.
|
||||||
|
colorFn,
|
||||||
}: {
|
}: {
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
progress?: number;
|
progress?: number;
|
||||||
@@ -24,6 +28,7 @@ export const Gauge = ({
|
|||||||
strokeWidth?: number;
|
strokeWidth?: number;
|
||||||
precision?: number;
|
precision?: number;
|
||||||
unit?: string;
|
unit?: string;
|
||||||
|
colorFn?: (val: number, theme: Theme) => string;
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const progressWithinRange = Math.max(MINIMUM_VALUE, Math.min(progress, MAXIMUM_VALUE));
|
const progressWithinRange = Math.max(MINIMUM_VALUE, Math.min(progress, MAXIMUM_VALUE));
|
||||||
@@ -48,7 +53,8 @@ export const Gauge = ({
|
|||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, [progress, circumference, strokeLength]);
|
}, [progress, circumference, strokeLength]);
|
||||||
|
|
||||||
const fillColor = getInfraGaugeColor(progressWithinRange, theme);
|
const resolvedColorFn = colorFn ?? getInfraGaugeColor;
|
||||||
|
const fillColor = resolvedColorFn(progressWithinRange, theme);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export const useStatusPageForm = ({
|
|||||||
showUptimePercentage: data?.showUptimePercentage ?? true,
|
showUptimePercentage: data?.showUptimePercentage ?? true,
|
||||||
showAdminLoginLink: data?.showAdminLoginLink ?? false,
|
showAdminLoginLink: data?.showAdminLoginLink ?? false,
|
||||||
showInfrastructure: data?.showInfrastructure ?? false,
|
showInfrastructure: data?.showInfrastructure ?? false,
|
||||||
|
showPageSpeed: data?.showPageSpeed ?? false,
|
||||||
customCSS: data?.customCSS || "",
|
customCSS: data?.customCSS || "",
|
||||||
logo: transformLogo(data?.logo),
|
logo: transformLogo(data?.logo),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import { useStatusPageForm } from "@/Hooks/useStatusPageForm";
|
|||||||
import type { StatusPageFormData } from "@/Validation/statusPage";
|
import type { StatusPageFormData } from "@/Validation/statusPage";
|
||||||
import { useGet, usePost, usePut, useDelete } from "@/Hooks/UseApi";
|
import { useGet, usePost, usePut, useDelete } from "@/Hooks/UseApi";
|
||||||
import type { Monitor } from "@/Types/Monitor";
|
import type { Monitor } from "@/Types/Monitor";
|
||||||
import type { MonitorDisplayType, StatusPageResponse } from "@/Types/StatusPage";
|
import type { StatusPageType, StatusPageResponse } from "@/Types/StatusPage";
|
||||||
import { getMonitorTypeLabel } from "@/Types/StatusPage";
|
import { getMonitorTypeLabel } from "@/Types/StatusPage";
|
||||||
import timezones from "@/Utils/timezones.json";
|
import timezones from "@/Utils/timezones.json";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
@@ -37,9 +37,17 @@ import { HeaderConfigStatusControls } from "./Components/HeaderConfigStatusContr
|
|||||||
|
|
||||||
const monitorsUrl = (() => {
|
const monitorsUrl = (() => {
|
||||||
const params = new URLSearchParams();
|
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()}`;
|
return `/monitors/team?${params.toString()}`;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@@ -59,7 +67,9 @@ const CreateStatusPage = () => {
|
|||||||
// Fetch existing status page data when configuring
|
// Fetch existing status page data when configuring
|
||||||
const { data: statusPageData, isLoading: isLoadingStatusPage } =
|
const { data: statusPageData, isLoading: isLoadingStatusPage } =
|
||||||
useGet<StatusPageResponse>(
|
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);
|
const { data: monitorsResponse } = useGet<Monitor[]>(monitorsUrl);
|
||||||
@@ -91,14 +101,16 @@ const CreateStatusPage = () => {
|
|||||||
}, [defaults, reset]);
|
}, [defaults, reset]);
|
||||||
|
|
||||||
const watchedMonitorIds: string[] = form.watch("monitors") ?? [];
|
const watchedMonitorIds: string[] = form.watch("monitors") ?? [];
|
||||||
const computedTypes: MonitorDisplayType[] = useMemo(() => {
|
const computedTypes: StatusPageType[] = useMemo(() => {
|
||||||
const selectedMonitors = (watchedMonitorIds ?? [])
|
const selectedMonitors = (watchedMonitorIds ?? [])
|
||||||
.map((id) => monitors.find((m) => m.id === id))
|
.map((id) => monitors.find((m) => m.id === id))
|
||||||
.filter((m): m is Monitor => m !== undefined);
|
.filter((m): m is Monitor => m !== undefined);
|
||||||
|
|
||||||
const typesSet = new Set<MonitorDisplayType>();
|
const typesSet = new Set<StatusPageType>();
|
||||||
selectedMonitors.forEach((m) => {
|
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"];
|
return typesSet.size ? Array.from(typesSet) : ["uptime"];
|
||||||
@@ -139,6 +151,7 @@ const CreateStatusPage = () => {
|
|||||||
fd.append("showUptimePercentage", String(data.showUptimePercentage));
|
fd.append("showUptimePercentage", String(data.showUptimePercentage));
|
||||||
fd.append("showAdminLoginLink", String(data.showAdminLoginLink));
|
fd.append("showAdminLoginLink", String(data.showAdminLoginLink));
|
||||||
fd.append("showInfrastructure", String(data.showInfrastructure));
|
fd.append("showInfrastructure", String(data.showInfrastructure));
|
||||||
|
fd.append("showPageSpeed", String(data.showPageSpeed));
|
||||||
|
|
||||||
data.monitors.forEach((monitorId) => {
|
data.monitors.forEach((monitorId) => {
|
||||||
fd.append("monitors[]", 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
|
{/* <Controller
|
||||||
name="showUptimePercentage"
|
name="showUptimePercentage"
|
||||||
control={control}
|
control={control}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { HistogramResponseTime, HeatmapResponseTime } from "@/Components/common"
|
|||||||
import { StatusLabel, BaseBox } from "@/Components/design-elements";
|
import { StatusLabel, BaseBox } from "@/Components/design-elements";
|
||||||
import { SwitchComponent } from "@/Components/inputs";
|
import { SwitchComponent } from "@/Components/inputs";
|
||||||
import { InfrastructureMetrics } from "@/Pages/StatusPage/Status/Components/InfrastructureMetrics";
|
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 { useTheme, type Theme } from "@mui/material/styles";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
@@ -26,8 +27,12 @@ interface MonitorsListProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getMonitorBadgeStyles = (monitorType: string, theme: Theme) => {
|
const getMonitorBadgeStyles = (monitorType: string, theme: Theme) => {
|
||||||
const bg =
|
const monitorColors: Record<string, string> = {
|
||||||
monitorType === "hardware" ? theme.palette.info.light : theme.palette.success.light;
|
hardware: theme.palette.info.light,
|
||||||
|
pagespeed: theme.palette.warning.light,
|
||||||
|
};
|
||||||
|
|
||||||
|
const bg = monitorColors[monitorType] || theme.palette.success.light;
|
||||||
return {
|
return {
|
||||||
backgroundColor: bg,
|
backgroundColor: bg,
|
||||||
color: theme.palette.background.paper,
|
color: theme.palette.background.paper,
|
||||||
@@ -105,6 +110,12 @@ const MonitorContent = ({
|
|||||||
return <InfrastructureMetrics monitor={monitor} />;
|
return <InfrastructureMetrics monitor={monitor} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (monitor.type === "pagespeed") {
|
||||||
|
return statusPage.showPageSpeed === false ? null : (
|
||||||
|
<PageSpeedMetrics monitor={monitor} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (statusPage.showCharts === false) return null;
|
if (statusPage.showCharts === false) return null;
|
||||||
|
|
||||||
const checks = monitor.checks?.slice().reverse() ?? [];
|
const checks = monitor.checks?.slice().reverse() ?? [];
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
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 type { CheckSnapshot } from "@/Types/Check";
|
||||||
|
import Grid from "@mui/material/Grid";
|
||||||
|
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();
|
||||||
|
return (
|
||||||
|
<Grid
|
||||||
|
size={{ xs: 12, md: 3 }}
|
||||||
|
display="flex"
|
||||||
|
flexDirection="column"
|
||||||
|
alignItems="center"
|
||||||
|
textAlign="center"
|
||||||
|
gap={theme.spacing(SPACING.LG)}
|
||||||
|
padding={theme.spacing(LAYOUT.XS)}
|
||||||
|
sx={{
|
||||||
|
borderRight: { xs: "none", md: `1px solid ${theme.palette.divider}` },
|
||||||
|
borderBottom: { xs: `1px solid ${theme.palette.divider}`, md: "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: CheckSnapshot, t: (key: string) => string): MetricConfig[] =>
|
||||||
|
[
|
||||||
|
{
|
||||||
|
key: "performance",
|
||||||
|
label: t("pages.statusPages.monitorsList.pagespeed.performance"),
|
||||||
|
progress: check.performance,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "accessibility",
|
||||||
|
label: t("pages.statusPages.monitorsList.pagespeed.accessibility"),
|
||||||
|
progress: check.accessibility,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "bestPractices",
|
||||||
|
label: t("pages.statusPages.monitorsList.pagespeed.bestPractices"),
|
||||||
|
progress: check.bestPractices,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "seo",
|
||||||
|
label: t("pages.statusPages.monitorsList.pagespeed.seo"),
|
||||||
|
progress: check.seo,
|
||||||
|
},
|
||||||
|
].filter((m): m is MetricConfig => m.progress !== undefined);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
alignItems="center"
|
||||||
|
padding={theme.spacing(LAYOUT.LG)}
|
||||||
|
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 isSmall = useMediaQuery(theme.breakpoints.down("md"));
|
||||||
const isPublic = location.pathname.startsWith("/status/public");
|
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>(
|
const { data, isLoading, error } = useGet<StatusPageResponse>(
|
||||||
apiUrl,
|
apiUrl,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { Monitor, MonitorType } from "@/Types/Monitor";
|
import type { Monitor, MonitorType } from "@/Types/Monitor";
|
||||||
export type MonitorDisplayType = "uptime" | "infrastructure";
|
export const StatusPageTypes = ["uptime", "infrastructure", "pagespeed"] as const;
|
||||||
|
export type StatusPageType = (typeof StatusPageTypes)[number];
|
||||||
|
|
||||||
export const MONITOR_TYPE_KEYS: Partial<Record<MonitorType, string>> = {
|
export const MONITOR_TYPE_KEYS: Partial<Record<MonitorType, string>> = {
|
||||||
http: "pages.common.monitors.monitorTypes.optionHttp",
|
http: "pages.common.monitors.monitorTypes.optionHttp",
|
||||||
@@ -25,7 +26,7 @@ export interface StatusPage {
|
|||||||
id: string;
|
id: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
teamId: string;
|
teamId: string;
|
||||||
type: MonitorDisplayType[];
|
type: StatusPageType[];
|
||||||
companyName: string;
|
companyName: string;
|
||||||
url: string;
|
url: string;
|
||||||
timezone?: string;
|
timezone?: string;
|
||||||
@@ -42,6 +43,7 @@ export interface StatusPage {
|
|||||||
showUptimePercentage: boolean;
|
showUptimePercentage: boolean;
|
||||||
showAdminLoginLink: boolean;
|
showAdminLoginLink: boolean;
|
||||||
showInfrastructure: boolean;
|
showInfrastructure: boolean;
|
||||||
|
showPageSpeed: boolean;
|
||||||
customCSS: string;
|
customCSS: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
|||||||
@@ -78,6 +78,12 @@ export const getPageSpeedPalette = (score: number): PaletteKey => {
|
|||||||
else return "error";
|
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) => {
|
export const formatUrl = (url: string, maxLength: number = 55) => {
|
||||||
if (!url) return "";
|
if (!url) return "";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { StatusPageTypes } from "@/Types/StatusPage";
|
||||||
|
|
||||||
export const statusPageSchema = z.object({
|
export const statusPageSchema = z.object({
|
||||||
companyName: z
|
companyName: z
|
||||||
@@ -14,9 +15,7 @@ export const statusPageSchema = z.object({
|
|||||||
"URL can only contain lowercase letters, numbers, and hyphens"
|
"URL can only contain lowercase letters, numbers, and hyphens"
|
||||||
),
|
),
|
||||||
timezone: z.string().optional(),
|
timezone: z.string().optional(),
|
||||||
type: z
|
type: z.array(z.enum(StatusPageTypes)).min(1, "At least one type is required"),
|
||||||
.array(z.enum(["uptime", "infrastructure"]))
|
|
||||||
.min(1, "At least one type is required"),
|
|
||||||
color: z.string().min(1, "Color is required"),
|
color: z.string().min(1, "Color is required"),
|
||||||
monitors: z.array(z.string()).min(1, "At least one monitor is required"),
|
monitors: z.array(z.string()).min(1, "At least one monitor is required"),
|
||||||
isPublished: z.boolean(),
|
isPublished: z.boolean(),
|
||||||
@@ -24,6 +23,7 @@ export const statusPageSchema = z.object({
|
|||||||
showUptimePercentage: z.boolean(),
|
showUptimePercentage: z.boolean(),
|
||||||
showAdminLoginLink: z.boolean(),
|
showAdminLoginLink: z.boolean(),
|
||||||
showInfrastructure: z.boolean(),
|
showInfrastructure: z.boolean(),
|
||||||
|
showPageSpeed: z.boolean(),
|
||||||
customCSS: z.string().optional(),
|
customCSS: z.string().optional(),
|
||||||
logo: z
|
logo: z
|
||||||
.object({
|
.object({
|
||||||
|
|||||||
@@ -324,6 +324,7 @@ const statusPageValidation = joi.object({
|
|||||||
showCharts: joi.boolean(),
|
showCharts: joi.boolean(),
|
||||||
showAdminLoginLink: joi.boolean(),
|
showAdminLoginLink: joi.boolean(),
|
||||||
showInfrastructure: joi.boolean(),
|
showInfrastructure: joi.boolean(),
|
||||||
|
showPageSpeed: joi.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const settingsValidation = joi.object({
|
const settingsValidation = joi.object({
|
||||||
|
|||||||
@@ -1202,6 +1202,13 @@
|
|||||||
"used": "Used",
|
"used": "Used",
|
||||||
"total": "Total"
|
"total": "Total"
|
||||||
},
|
},
|
||||||
|
"pagespeed": {
|
||||||
|
"title": "PageSpeed",
|
||||||
|
"performance": "Performance",
|
||||||
|
"accessibility": "Accessibility",
|
||||||
|
"bestPractices": "Best Practices",
|
||||||
|
"seo": "SEO"
|
||||||
|
},
|
||||||
"uptime": {
|
"uptime": {
|
||||||
"title": "Uptime",
|
"title": "Uptime",
|
||||||
"responseTime": "Response time"
|
"responseTime": "Response time"
|
||||||
@@ -1300,6 +1307,9 @@
|
|||||||
},
|
},
|
||||||
"showInfrastructure": {
|
"showInfrastructure": {
|
||||||
"label": "Show infrastructure metrics"
|
"label": "Show infrastructure metrics"
|
||||||
|
},
|
||||||
|
"showPageSpeed": {
|
||||||
|
"label": "Show PageSpeed scores"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import type { Pool } from "pg";
|
||||||
|
|
||||||
|
export const addShowPageSpeedToStatusPages = async (pool: Pool) => {
|
||||||
|
await pool.query(`
|
||||||
|
ALTER TABLE status_pages
|
||||||
|
ADD COLUMN IF NOT EXISTS show_page_speed BOOLEAN DEFAULT FALSE;
|
||||||
|
`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dropShowPageSpeedFromStatusPages = async (pool: Pool) => {
|
||||||
|
await pool.query(`
|
||||||
|
ALTER TABLE status_pages
|
||||||
|
DROP COLUMN IF EXISTS show_page_speed;
|
||||||
|
`);
|
||||||
|
};
|
||||||
@@ -21,6 +21,7 @@ import { createStatusPages, dropStatusPages } from "./0018_create_status_pages.j
|
|||||||
import { createAppSettings, dropAppSettings } from "./0019_create_app_settings.js";
|
import { createAppSettings, dropAppSettings } from "./0019_create_app_settings.js";
|
||||||
import { createContinuousAggregates, dropContinuousAggregates } from "./0020_create_continuous_aggregates.js";
|
import { createContinuousAggregates, dropContinuousAggregates } from "./0020_create_continuous_aggregates.js";
|
||||||
import { createRetentionCompression, dropRetentionCompression } from "./0021_create_retention_compression.js";
|
import { createRetentionCompression, dropRetentionCompression } from "./0021_create_retention_compression.js";
|
||||||
|
import { addShowPageSpeedToStatusPages, dropShowPageSpeedFromStatusPages } from "./0022_add_show_page_speed_to_status_pages.js";
|
||||||
|
|
||||||
const SERVICE_NAME = "TimescaleDB Migrations";
|
const SERVICE_NAME = "TimescaleDB Migrations";
|
||||||
|
|
||||||
@@ -52,6 +53,7 @@ const migrations: MigrationEntry[] = [
|
|||||||
{ name: "0019_create_app_settings", up: createAppSettings, down: dropAppSettings },
|
{ name: "0019_create_app_settings", up: createAppSettings, down: dropAppSettings },
|
||||||
{ name: "0020_create_continuous_aggregates", up: createContinuousAggregates, down: dropContinuousAggregates },
|
{ name: "0020_create_continuous_aggregates", up: createContinuousAggregates, down: dropContinuousAggregates },
|
||||||
{ name: "0021_create_retention_compression", up: createRetentionCompression, down: dropRetentionCompression },
|
{ name: "0021_create_retention_compression", up: createRetentionCompression, down: dropRetentionCompression },
|
||||||
|
{ name: "0022_add_show_page_speed_to_status_pages", up: addShowPageSpeedToStatusPages, down: dropShowPageSpeedFromStatusPages },
|
||||||
];
|
];
|
||||||
|
|
||||||
const ensureMigrationsTable = async (pool: Pool) => {
|
const ensureMigrationsTable = async (pool: Pool) => {
|
||||||
|
|||||||
@@ -104,6 +104,10 @@ const StatusPageSchema = new Schema<StatusPageDocument>(
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
showPageSpeed: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
customCSS: {
|
customCSS: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: "",
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ class MongoStatusPagesRepository implements IStatusPagesRepository {
|
|||||||
showUptimePercentage: doc.showUptimePercentage,
|
showUptimePercentage: doc.showUptimePercentage,
|
||||||
showAdminLoginLink: doc.showAdminLoginLink,
|
showAdminLoginLink: doc.showAdminLoginLink,
|
||||||
showInfrastructure: doc.showInfrastructure,
|
showInfrastructure: doc.showInfrastructure,
|
||||||
|
showPageSpeed: doc.showPageSpeed,
|
||||||
customCSS: doc.customCSS,
|
customCSS: doc.customCSS,
|
||||||
createdAt: this.toDateString(doc.createdAt),
|
createdAt: this.toDateString(doc.createdAt),
|
||||||
updatedAt: this.toDateString(doc.updatedAt),
|
updatedAt: this.toDateString(doc.updatedAt),
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ interface StatusPageRow {
|
|||||||
show_uptime_percentage: boolean;
|
show_uptime_percentage: boolean;
|
||||||
show_admin_login_link: boolean;
|
show_admin_login_link: boolean;
|
||||||
show_infrastructure: boolean;
|
show_infrastructure: boolean;
|
||||||
|
show_page_speed: boolean;
|
||||||
custom_css: string | null;
|
custom_css: string | null;
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
updated_at: Date;
|
updated_at: Date;
|
||||||
@@ -33,7 +34,7 @@ interface StatusPageRow {
|
|||||||
|
|
||||||
const COLUMNS = `id, user_id, team_id, types, company_name, url, timezone, color,
|
const COLUMNS = `id, user_id, team_id, types, company_name, url, timezone, color,
|
||||||
logo_data, logo_content_type, is_published, show_charts, show_uptime_percentage,
|
logo_data, logo_content_type, is_published, show_charts, show_uptime_percentage,
|
||||||
show_admin_login_link, show_infrastructure, custom_css, created_at, updated_at`;
|
show_admin_login_link, show_infrastructure, show_page_speed, custom_css, created_at, updated_at`;
|
||||||
|
|
||||||
export class TimescaleStatusPagesRepository implements IStatusPagesRepository {
|
export class TimescaleStatusPagesRepository implements IStatusPagesRepository {
|
||||||
constructor(private pool: Pool) {}
|
constructor(private pool: Pool) {}
|
||||||
@@ -42,8 +43,8 @@ export class TimescaleStatusPagesRepository implements IStatusPagesRepository {
|
|||||||
const result = await this.pool.query<StatusPageRow>(
|
const result = await this.pool.query<StatusPageRow>(
|
||||||
`INSERT INTO status_pages (user_id, team_id, types, company_name, url, timezone, color,
|
`INSERT INTO status_pages (user_id, team_id, types, company_name, url, timezone, color,
|
||||||
logo_data, logo_content_type, is_published, show_charts, show_uptime_percentage,
|
logo_data, logo_content_type, is_published, show_charts, show_uptime_percentage,
|
||||||
show_admin_login_link, show_infrastructure, custom_css)
|
show_admin_login_link, show_infrastructure, show_page_speed, custom_css)
|
||||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15)
|
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16)
|
||||||
RETURNING ${COLUMNS}`,
|
RETURNING ${COLUMNS}`,
|
||||||
[
|
[
|
||||||
userId,
|
userId,
|
||||||
@@ -60,6 +61,7 @@ export class TimescaleStatusPagesRepository implements IStatusPagesRepository {
|
|||||||
data.showUptimePercentage ?? false,
|
data.showUptimePercentage ?? false,
|
||||||
data.showAdminLoginLink ?? false,
|
data.showAdminLoginLink ?? false,
|
||||||
data.showInfrastructure ?? false,
|
data.showInfrastructure ?? false,
|
||||||
|
data.showPageSpeed ?? false,
|
||||||
data.customCSS ?? null,
|
data.customCSS ?? null,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -128,6 +130,7 @@ export class TimescaleStatusPagesRepository implements IStatusPagesRepository {
|
|||||||
["showUptimePercentage", "show_uptime_percentage"],
|
["showUptimePercentage", "show_uptime_percentage"],
|
||||||
["showAdminLoginLink", "show_admin_login_link"],
|
["showAdminLoginLink", "show_admin_login_link"],
|
||||||
["showInfrastructure", "show_infrastructure"],
|
["showInfrastructure", "show_infrastructure"],
|
||||||
|
["showPageSpeed", "show_page_speed"],
|
||||||
["customCSS", "custom_css"],
|
["customCSS", "custom_css"],
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -252,6 +255,7 @@ export class TimescaleStatusPagesRepository implements IStatusPagesRepository {
|
|||||||
showUptimePercentage: row.show_uptime_percentage,
|
showUptimePercentage: row.show_uptime_percentage,
|
||||||
showAdminLoginLink: row.show_admin_login_link,
|
showAdminLoginLink: row.show_admin_login_link,
|
||||||
showInfrastructure: row.show_infrastructure,
|
showInfrastructure: row.show_infrastructure,
|
||||||
|
showPageSpeed: row.show_page_speed,
|
||||||
customCSS: row.custom_css ?? "",
|
customCSS: row.custom_css ?? "",
|
||||||
createdAt: row.created_at.toISOString(),
|
createdAt: row.created_at.toISOString(),
|
||||||
updatedAt: row.updated_at.toISOString(),
|
updatedAt: row.updated_at.toISOString(),
|
||||||
|
|||||||
@@ -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 type StatusPageType = (typeof StatusPageTypes)[number];
|
||||||
|
|
||||||
export interface StatusPageLogo {
|
export interface StatusPageLogo {
|
||||||
@@ -29,6 +29,7 @@ export interface StatusPage {
|
|||||||
showUptimePercentage: boolean;
|
showUptimePercentage: boolean;
|
||||||
showAdminLoginLink: boolean;
|
showAdminLoginLink: boolean;
|
||||||
showInfrastructure: boolean;
|
showInfrastructure: boolean;
|
||||||
|
showPageSpeed: boolean;
|
||||||
customCSS: string;
|
customCSS: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export const createStatusPageBodyValidation = z
|
|||||||
showUptimePercentage: booleanCoercion,
|
showUptimePercentage: booleanCoercion,
|
||||||
showAdminLoginLink: booleanCoercion.optional(),
|
showAdminLoginLink: booleanCoercion.optional(),
|
||||||
showInfrastructure: booleanCoercion.optional(),
|
showInfrastructure: booleanCoercion.optional(),
|
||||||
|
showPageSpeed: booleanCoercion.optional(),
|
||||||
removeLogo: z.union([z.literal("true"), z.literal("false")]).optional(),
|
removeLogo: z.union([z.literal("true"), z.literal("false")]).optional(),
|
||||||
})
|
})
|
||||||
.strip();
|
.strip();
|
||||||
|
|||||||
Reference in new issue
Block a user