feat: adding infrastructure display support to status page
This commit is contained in:
1 parent
7a0e0bccf4
commit
be1ca4ac28
13 files changed
+341
-41
No files matched your search
@@ -27,12 +27,14 @@ export const useStatusPageForm = ({
|
||||
companyName: data?.companyName || "",
|
||||
url: data?.url || generateDefaultUrl(),
|
||||
timezone: data?.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
type: data?.type || ["uptime"],
|
||||
color: data?.color || "#4169E1",
|
||||
monitors: data?.monitors || [],
|
||||
isPublished: data?.isPublished ?? false,
|
||||
showCharts: data?.showCharts ?? true,
|
||||
showUptimePercentage: data?.showUptimePercentage ?? true,
|
||||
showAdminLoginLink: data?.showAdminLoginLink ?? false,
|
||||
showInfrastructure: data?.showInfrastructure ?? false,
|
||||
customCSS: data?.customCSS || "",
|
||||
logo: transformLogo(data?.logo),
|
||||
};
|
||||
|
||||
@@ -28,7 +28,7 @@ import { useStatusPageForm } from "@/Hooks/useStatusPageForm";
|
||||
import type { StatusPageFormData } from "@/Validation/statusPage";
|
||||
import { useGet, usePost, usePut, useDelete } from "@/Hooks/UseApi";
|
||||
import type { Monitor } from "@/Types/Monitor";
|
||||
import type { StatusPageResponse } from "@/Types/StatusPage";
|
||||
import type { MonitorDisplayType, StatusPageResponse } from "@/Types/StatusPage";
|
||||
import timezones from "@/Utils/timezones.json";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import axios from "axios";
|
||||
@@ -49,10 +49,12 @@ const CreateStatusPage = () => {
|
||||
|
||||
// Fetch existing status page data when configuring
|
||||
const { data: statusPageData, isLoading: isLoadingStatusPage } =
|
||||
useGet<StatusPageResponse>(isCreate ? null : `/status-page/${url}?type=uptime`);
|
||||
useGet<StatusPageResponse>(
|
||||
isCreate ? null : `/status-page/${url}?type=uptime&type=infrastructure`
|
||||
);
|
||||
|
||||
const { data: monitorsResponse } = useGet<Monitor[]>(
|
||||
"/monitors/team?type=http&type=ping&type=port&type=docker"
|
||||
"/monitors/team?type=http&type=ping&type=port&type=docker&type=hardware"
|
||||
);
|
||||
const monitors = monitorsResponse ?? [];
|
||||
|
||||
@@ -81,6 +83,27 @@ const CreateStatusPage = () => {
|
||||
reset(defaults);
|
||||
}, [defaults, reset]);
|
||||
|
||||
const watchedMonitorIds: string[] = form.watch("monitors") ?? [];
|
||||
useEffect(() => {
|
||||
const selectedMonitors = (watchedMonitorIds ?? [])
|
||||
.map((id) => monitors.find((m) => m.id === id))
|
||||
.filter((m): m is Monitor => m !== undefined);
|
||||
|
||||
const typesSet: Set<MonitorDisplayType> = new Set();
|
||||
selectedMonitors.forEach((m) => {
|
||||
if (m.type === "hardware") {
|
||||
typesSet.add("infrastructure");
|
||||
} else {
|
||||
typesSet.add("uptime");
|
||||
}
|
||||
});
|
||||
|
||||
const computedTypes: MonitorDisplayType[] = typesSet.size
|
||||
? Array.from(typesSet)
|
||||
: ["uptime"];
|
||||
form.setValue("type", computedTypes);
|
||||
}, [watchedMonitorIds, monitors, form]);
|
||||
|
||||
const onError = (errors: any) => {
|
||||
logger.debug("Status page validation errors", errors);
|
||||
};
|
||||
@@ -103,7 +126,6 @@ const CreateStatusPage = () => {
|
||||
|
||||
const onSubmit = async (data: StatusPageFormData) => {
|
||||
const fd = new FormData();
|
||||
fd.append("type", "uptime");
|
||||
fd.append("isPublished", String(data.isPublished));
|
||||
if (data.companyName) fd.append("companyName", data.companyName);
|
||||
if (data.url) fd.append("url", data.url);
|
||||
@@ -112,11 +134,16 @@ const CreateStatusPage = () => {
|
||||
fd.append("showCharts", String(data.showCharts));
|
||||
fd.append("showUptimePercentage", String(data.showUptimePercentage));
|
||||
fd.append("showAdminLoginLink", String(data.showAdminLoginLink));
|
||||
fd.append("showInfrastructure", String(data.showInfrastructure));
|
||||
|
||||
data.monitors.forEach((monitorId) => {
|
||||
fd.append("monitors[]", monitorId);
|
||||
});
|
||||
|
||||
data.type.forEach((type) => {
|
||||
fd.append("type[]", type);
|
||||
});
|
||||
|
||||
// Handle logo upload
|
||||
if (data.logo === null) {
|
||||
// Signal to remove the logo
|
||||
@@ -425,6 +452,23 @@ const CreateStatusPage = () => {
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="showInfrastructure"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
}
|
||||
label={t(
|
||||
"pages.statusPages.form.features.option.showInfrastructure.label"
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{/* <Controller
|
||||
name="showUptimePercentage"
|
||||
control={control}
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { Gauge } from "@/Components/design-elements";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import prettyBytes from "pretty-bytes";
|
||||
import type { Monitor } from "@/Types/Monitor";
|
||||
|
||||
interface StatusPageMonitor extends Monitor {
|
||||
checks?: Monitor["recentChecks"];
|
||||
infrastructureCPU?: number;
|
||||
infrastructureMemory?: number;
|
||||
infrastructureDisk?: number;
|
||||
}
|
||||
|
||||
interface MetricItemProps {
|
||||
label: string;
|
||||
progress: number;
|
||||
upperLabel?: string;
|
||||
upperValue?: string;
|
||||
lowerLabel?: string;
|
||||
lowerValue?: string;
|
||||
}
|
||||
|
||||
const MetricItem = ({
|
||||
label,
|
||||
progress,
|
||||
upperLabel,
|
||||
upperValue,
|
||||
lowerLabel,
|
||||
lowerValue,
|
||||
}: MetricItemProps) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={`${theme.spacing(4)} ${theme.spacing(10)}`}
|
||||
flex={1}
|
||||
>
|
||||
<Stack
|
||||
alignItems="center"
|
||||
spacing={theme.spacing(2)}
|
||||
>
|
||||
<Gauge
|
||||
progress={progress}
|
||||
radius={60}
|
||||
strokeWidth={12}
|
||||
/>
|
||||
<Typography variant="body2">{label}</Typography>
|
||||
</Stack>
|
||||
{(upperLabel || lowerLabel) && (
|
||||
<Stack
|
||||
spacing={theme.spacing(1)}
|
||||
flex={1}
|
||||
paddingBottom={theme.spacing(10)}
|
||||
>
|
||||
{upperLabel && (
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
>
|
||||
{upperLabel}
|
||||
</Typography>
|
||||
<Typography variant="body2">{upperValue}</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
{lowerLabel && (
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
>
|
||||
{lowerLabel}
|
||||
</Typography>
|
||||
<Typography variant="body2">{lowerValue}</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const InfrastructureMetrics = ({ monitor }: { monitor: StatusPageMonitor }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const latestCheck = monitor.recentChecks?.[0] ?? monitor.checks?.[0];
|
||||
|
||||
if (!latestCheck) {
|
||||
return (
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
>
|
||||
{t("pages.statusPages.monitorsList.noData")}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
const cpuUsage = (latestCheck?.cpu?.usage_percent || 0) * 100;
|
||||
const memoryUsage = (latestCheck?.memory?.usage_percent || 0) * 100;
|
||||
const memoryUsed = latestCheck?.memory?.used_bytes || 0;
|
||||
const memoryTotal = latestCheck?.memory?.total_bytes || 0;
|
||||
|
||||
const disks = latestCheck?.disk ?? [];
|
||||
const totalDiskUsage = disks.reduce((acc, disk) => acc + (disk?.usage_percent || 0), 0);
|
||||
const diskCount = disks.length || 1;
|
||||
const diskUsage = (totalDiskUsage / diskCount) * 100;
|
||||
const diskUsed = disks.reduce((acc, disk) => acc + (disk?.used_bytes || 0), 0);
|
||||
const diskTotal = disks.reduce((acc, disk) => acc + (disk?.total_bytes || 0), 0);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
direction={{ xs: "column", sm: "row" }}
|
||||
gap={theme.spacing(10)}
|
||||
columnGap={theme.spacing(15)}
|
||||
padding={theme.spacing(8)}
|
||||
>
|
||||
<MetricItem
|
||||
label={t("pages.statusPages.monitorsList.infrastructure.cpu")}
|
||||
progress={cpuUsage}
|
||||
upperLabel={t("pages.statusPages.monitorsList.infrastructure.usage")}
|
||||
upperValue={`${cpuUsage.toFixed(1)}%`}
|
||||
/>
|
||||
<MetricItem
|
||||
label={t("pages.statusPages.monitorsList.infrastructure.memory")}
|
||||
progress={memoryUsage}
|
||||
upperLabel={t("pages.statusPages.monitorsList.infrastructure.used")}
|
||||
upperValue={prettyBytes(memoryUsed)}
|
||||
lowerLabel={t("pages.statusPages.monitorsList.infrastructure.total")}
|
||||
lowerValue={prettyBytes(memoryTotal)}
|
||||
/>
|
||||
<MetricItem
|
||||
label={t("pages.statusPages.monitorsList.infrastructure.disk")}
|
||||
progress={diskUsage}
|
||||
upperLabel={t("pages.statusPages.monitorsList.infrastructure.used")}
|
||||
upperValue={prettyBytes(diskUsed)}
|
||||
lowerLabel={t("pages.statusPages.monitorsList.infrastructure.total")}
|
||||
lowerValue={prettyBytes(diskTotal)}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -5,6 +5,7 @@ import Typography from "@mui/material/Typography";
|
||||
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 { useTheme } from "@mui/material/styles";
|
||||
import { useSelector } from "react-redux";
|
||||
@@ -32,10 +33,11 @@ export const MonitorsList = ({ statusPage, monitors }: MonitorsListProps) => {
|
||||
|
||||
return (
|
||||
<Stack gap={theme.spacing(8)}>
|
||||
{statusPage.showCharts !== false && (
|
||||
{statusPage.showCharts && (
|
||||
<Stack
|
||||
direction={"row"}
|
||||
alignItems={"center"}
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Typography>{t("pages.statusPages.monitorsList.chartTypeHeatmap")}</Typography>
|
||||
<SwitchComponent
|
||||
@@ -53,30 +55,56 @@ export const MonitorsList = ({ statusPage, monitors }: MonitorsListProps) => {
|
||||
)}
|
||||
|
||||
{monitors?.map((monitor) => {
|
||||
const isInfrastructureMonitor = monitor?.type === "hardware";
|
||||
return (
|
||||
<BaseBox
|
||||
key={monitor.id}
|
||||
padding={theme.spacing(4)}
|
||||
padding={theme.spacing(8)}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
gap={theme.spacing(4)}
|
||||
mb={statusPage.showCharts !== false ? theme.spacing(4) : 0}
|
||||
mb={theme.spacing(4)}
|
||||
>
|
||||
<Box sx={{ overflow: "hidden", minWidth: 0, flex: 1 }}>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(2)}
|
||||
mb={theme.spacing(1)}
|
||||
>
|
||||
{monitor.name}
|
||||
</Typography>
|
||||
{showURL && (
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{monitor.name}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{
|
||||
backgroundColor:
|
||||
monitor?.type === "hardware"
|
||||
? theme.palette.info.light
|
||||
: theme.palette.success.light,
|
||||
color:
|
||||
monitor?.type === "hardware"
|
||||
? theme.palette.info.dark
|
||||
: theme.palette.success.dark,
|
||||
padding: `${theme.spacing(0.5)} ${theme.spacing(1.5)}`,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{t(`pages.statusPages.monitorsList.badge.${monitor?.type}`)}
|
||||
</Typography>
|
||||
</Stack>
|
||||
{showURL && monitor?.url && (
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
@@ -85,22 +113,49 @@ export const MonitorsList = ({ statusPage, monitors }: MonitorsListProps) => {
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<StatusLabel status={monitor.status} />
|
||||
<StatusLabel status={monitor?.status} />
|
||||
</Stack>
|
||||
{statusPage.showCharts !== false && (
|
||||
<Box sx={{ overflow: "hidden", minWidth: 0, flex: 1 }}>
|
||||
{chartType === "histogram" ? (
|
||||
<HistogramResponseTime
|
||||
height={{ xs: 50, md: 100 }}
|
||||
gap={{ xs: theme.spacing(0.5), md: theme.spacing(5) }}
|
||||
checks={monitor?.checks?.slice().reverse() ?? []}
|
||||
/>
|
||||
) : (
|
||||
<HeatmapResponseTime
|
||||
checks={monitor?.checks?.slice().reverse() ?? []}
|
||||
/>
|
||||
|
||||
{!isInfrastructureMonitor && (
|
||||
<>
|
||||
{statusPage.showCharts !== false && (
|
||||
<Box
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
minWidth: 0,
|
||||
flex: 1,
|
||||
mb: theme.spacing(2),
|
||||
}}
|
||||
>
|
||||
{chartType === "histogram" ? (
|
||||
<HistogramResponseTime
|
||||
height={{ xs: 50, md: 100 }}
|
||||
gap={{ xs: theme.spacing(0.5), md: theme.spacing(5) }}
|
||||
checks={monitor?.checks?.slice().reverse() ?? []}
|
||||
/>
|
||||
) : (
|
||||
<HeatmapResponseTime
|
||||
checks={monitor?.checks?.slice().reverse() ?? []}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isInfrastructureMonitor && statusPage.showInfrastructure !== false && (
|
||||
<BaseBox
|
||||
key={monitor.id}
|
||||
padding={theme.spacing(4)}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
gap={theme.spacing(8)}
|
||||
></Stack>
|
||||
<InfrastructureMetrics monitor={monitor} />
|
||||
</BaseBox>
|
||||
)}
|
||||
</BaseBox>
|
||||
);
|
||||
|
||||
@@ -23,16 +23,17 @@ const StatusPageView = () => {
|
||||
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
|
||||
const isPublic = location.pathname.startsWith("/status/public");
|
||||
|
||||
const apiUrl = url ? `/status-page/${url}?type=uptime` : null;
|
||||
const apiUrl = url ? `/status-page/${url}?type=uptime&type=infrastructure` : null;
|
||||
|
||||
const { data, isLoading, error } = useGet<StatusPageResponse>(apiUrl);
|
||||
|
||||
const statusPage = data?.statusPage;
|
||||
const monitors = data?.monitors ?? [];
|
||||
const infrastructureMonitors = data?.infrastructureMonitors ?? [];
|
||||
|
||||
if (!statusPage) return null;
|
||||
|
||||
if (monitors?.length === 0) {
|
||||
if (monitors?.length === 0 && infrastructureMonitors?.length === 0) {
|
||||
return (
|
||||
<BasePage
|
||||
loading={isLoading}
|
||||
|
||||
@@ -97,7 +97,7 @@ export const StatusPagesTable = ({
|
||||
{
|
||||
id: "type",
|
||||
content: t("common.table.headers.type"),
|
||||
render: (row) => row.type,
|
||||
render: (row) => row.type.join(", "),
|
||||
},
|
||||
{
|
||||
id: "status",
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { Monitor } from "@/Types/Monitor";
|
||||
export type MonitorDisplayType = "uptime" | "infrastructure";
|
||||
|
||||
export interface StatusPage {
|
||||
id: string;
|
||||
userId: string;
|
||||
teamId: string;
|
||||
type: string;
|
||||
type: MonitorDisplayType[];
|
||||
companyName: string;
|
||||
url: string;
|
||||
timezone?: string;
|
||||
@@ -20,6 +21,7 @@ export interface StatusPage {
|
||||
showCharts: boolean;
|
||||
showUptimePercentage: boolean;
|
||||
showAdminLoginLink: boolean;
|
||||
showInfrastructure: boolean;
|
||||
customCSS: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
@@ -28,4 +30,5 @@ export interface StatusPage {
|
||||
export interface StatusPageResponse {
|
||||
statusPage: StatusPage;
|
||||
monitors: Monitor[];
|
||||
infrastructureMonitors?: Monitor[];
|
||||
}
|
||||
@@ -14,12 +14,16 @@ export const statusPageSchema = z.object({
|
||||
"URL can only contain lowercase letters, numbers, and hyphens"
|
||||
),
|
||||
timezone: z.string().optional(),
|
||||
type: z
|
||||
.array(z.enum(["uptime", "infrastructure"]))
|
||||
.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"),
|
||||
isPublished: z.boolean(),
|
||||
showCharts: z.boolean(),
|
||||
showUptimePercentage: z.boolean(),
|
||||
showAdminLoginLink: z.boolean(),
|
||||
showInfrastructure: z.boolean(),
|
||||
customCSS: z.string().optional(),
|
||||
logo: z
|
||||
.object({
|
||||
|
||||
@@ -288,7 +288,13 @@ const logoImageValidation = joi
|
||||
.optional(); // Make entire object optional
|
||||
|
||||
const statusPageValidation = joi.object({
|
||||
type: joi.string().valid("uptime").required(),
|
||||
type: joi
|
||||
.alternatives()
|
||||
.try(
|
||||
joi.string().valid("uptime", "infrastructure"),
|
||||
joi.array().items(joi.string().valid("uptime", "infrastructure"))
|
||||
)
|
||||
.required(),
|
||||
isPublished: joi.bool(),
|
||||
companyName: joi
|
||||
.string()
|
||||
@@ -317,6 +323,7 @@ const statusPageValidation = joi.object({
|
||||
showUptimePercentage: joi.boolean(),
|
||||
showCharts: joi.boolean(),
|
||||
showAdminLoginLink: joi.boolean(),
|
||||
showInfrastructure: joi.boolean(),
|
||||
});
|
||||
|
||||
const settingsValidation = joi.object({
|
||||
|
||||
@@ -1178,7 +1178,30 @@
|
||||
},
|
||||
"monitorsList": {
|
||||
"chartTypeHeatmap": "Heatmap",
|
||||
"chartTypeHistogram": "Histogram"
|
||||
"chartTypeHistogram": "Histogram",
|
||||
"noData": "No data available",
|
||||
"infrastructure": {
|
||||
"title": "Infrastructure",
|
||||
"cpu": "CPU",
|
||||
"memory": "RAM",
|
||||
"disk": "Disk",
|
||||
"usage": "Usage",
|
||||
"used": "Used",
|
||||
"total": "Total"
|
||||
},
|
||||
"uptime": {
|
||||
"title": "Uptime",
|
||||
"responseTime": "Response time"
|
||||
},
|
||||
"badge": {
|
||||
"http": "HTTP",
|
||||
"ping": "Ping",
|
||||
"port": "Port",
|
||||
"docker": "Docker",
|
||||
"pagespeed": "PageSpeed",
|
||||
"hardware": "Infrastructure",
|
||||
"game": "Game"
|
||||
}
|
||||
},
|
||||
"statusBar": {
|
||||
"allDown": "All systems are down",
|
||||
@@ -1270,6 +1293,9 @@
|
||||
},
|
||||
"showAdminLoginLink": {
|
||||
"label": "Show admin login link"
|
||||
},
|
||||
"showInfrastructure": {
|
||||
"label": "Show infrastructure metrics"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,9 +43,9 @@ const StatusPageSchema = new Schema<StatusPageDocument>(
|
||||
required: true,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
type: [String],
|
||||
required: true,
|
||||
default: "uptime",
|
||||
default: [],
|
||||
enum: StatusPageTypes,
|
||||
},
|
||||
companyName: {
|
||||
@@ -100,6 +100,10 @@ const StatusPageSchema = new Schema<StatusPageDocument>(
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showInfrastructure: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
customCSS: {
|
||||
type: String,
|
||||
default: "",
|
||||
|
||||
@@ -57,6 +57,7 @@ class MongoStatusPagesRepository implements IStatusPagesRepository {
|
||||
showCharts: doc.showCharts,
|
||||
showUptimePercentage: doc.showUptimePercentage,
|
||||
showAdminLoginLink: doc.showAdminLoginLink,
|
||||
showInfrastructure: doc.showInfrastructure,
|
||||
customCSS: doc.customCSS,
|
||||
createdAt: this.toDateString(doc.createdAt),
|
||||
updatedAt: this.toDateString(doc.updatedAt),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const StatusPageTypes = ["uptime"] as const;
|
||||
export const StatusPageTypes = ["uptime", "infrastructure"] as const;
|
||||
export type StatusPageType = (typeof StatusPageTypes)[number];
|
||||
|
||||
export interface StatusPageLogo {
|
||||
@@ -15,7 +15,7 @@ export interface StatusPage {
|
||||
id: string;
|
||||
userId: string;
|
||||
teamId: string;
|
||||
type: StatusPageType;
|
||||
type: StatusPageType[];
|
||||
companyName: string;
|
||||
url: string;
|
||||
timezone?: string;
|
||||
@@ -28,6 +28,7 @@ export interface StatusPage {
|
||||
showCharts: boolean;
|
||||
showUptimePercentage: boolean;
|
||||
showAdminLoginLink: boolean;
|
||||
showInfrastructure: boolean;
|
||||
customCSS: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
|
||||
Reference in new issue
Block a user