fix: refactoring changes and adding type migration to statusPageType
This commit is contained in:
1 parent
be1ca4ac28
commit
a9e9097a1f
7 files changed
+307
-176
No files matched your search
@@ -302,35 +302,35 @@ const CreateMonitorPage = () => {
|
||||
>
|
||||
<RadioWithDescription
|
||||
value="http"
|
||||
label={t("pages.createMonitor.form.type.optionHttp")}
|
||||
label={t("pages.common.monitors.monitorTypes.optionHttp")}
|
||||
description={t(
|
||||
"pages.createMonitor.form.type.optionHttpDescription"
|
||||
)}
|
||||
/>
|
||||
<RadioWithDescription
|
||||
value="ping"
|
||||
label={t("pages.createMonitor.form.type.optionPing")}
|
||||
label={t("pages.common.monitors.monitorTypes.optionPing")}
|
||||
description={t(
|
||||
"pages.createMonitor.form.type.optionPingDescription"
|
||||
)}
|
||||
/>
|
||||
<RadioWithDescription
|
||||
value="docker"
|
||||
label={t("pages.createMonitor.form.type.optionDocker")}
|
||||
label={t("pages.common.monitors.monitorTypes.optionDocker")}
|
||||
description={t(
|
||||
"pages.createMonitor.form.type.optionDockerDescription"
|
||||
)}
|
||||
/>
|
||||
<RadioWithDescription
|
||||
value="port"
|
||||
label={t("pages.createMonitor.form.type.optionPort")}
|
||||
label={t("pages.common.monitors.monitorTypes.optionPort")}
|
||||
description={t(
|
||||
"pages.createMonitor.form.type.optionPortDescription"
|
||||
)}
|
||||
/>
|
||||
<RadioWithDescription
|
||||
value="game"
|
||||
label={t("pages.createMonitor.form.type.optionGame")}
|
||||
label={t("pages.common.monitors.monitorTypes.optionGame")}
|
||||
description={t(
|
||||
"pages.createMonitor.form.type.optionGameDescription"
|
||||
)}
|
||||
|
||||
@@ -330,7 +330,9 @@ const CreateStatusPage = () => {
|
||||
}}
|
||||
>
|
||||
<GripVertical size={20} />
|
||||
<Typography flexGrow={1}>{monitor.name}</Typography>
|
||||
<Typography
|
||||
flexGrow={1}
|
||||
>{`${monitor.name} (${t(`pages.common.monitors.monitorTypes.option${monitor?.type.charAt(0).toUpperCase() + monitor?.type.slice(1)}`)})`}</Typography>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => {
|
||||
|
||||
@@ -5,6 +5,9 @@ import { useTheme } from "@mui/material/styles";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import prettyBytes from "pretty-bytes";
|
||||
import type { Monitor } from "@/Types/Monitor";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||
import Box from "@mui/material/Box";
|
||||
|
||||
interface StatusPageMonitor extends Monitor {
|
||||
checks?: Monitor["recentChecks"];
|
||||
@@ -13,34 +16,62 @@ interface StatusPageMonitor extends Monitor {
|
||||
infrastructureDisk?: number;
|
||||
}
|
||||
|
||||
interface MetricItemProps {
|
||||
interface MetricDetailRowProps {
|
||||
label: string;
|
||||
progress: number;
|
||||
upperLabel?: string;
|
||||
upperValue?: string;
|
||||
lowerLabel?: string;
|
||||
lowerValue?: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
const MetricItem = ({
|
||||
label,
|
||||
progress,
|
||||
upperLabel,
|
||||
upperValue,
|
||||
lowerLabel,
|
||||
lowerValue,
|
||||
}: MetricItemProps) => {
|
||||
const MetricDetailRow = ({ label, value }: MetricDetailRowProps) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={`${theme.spacing(4)} ${theme.spacing(10)}`}
|
||||
flex={1}
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Stack
|
||||
<Typography
|
||||
variant="body2"
|
||||
color={theme.palette.text.secondary}
|
||||
>
|
||||
{label}
|
||||
</Typography>
|
||||
<Typography variant="body2">{value}</Typography>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
interface MetricItemProps {
|
||||
label: string;
|
||||
progress: number;
|
||||
details?: MetricDetailRowProps[];
|
||||
}
|
||||
|
||||
const MetricItem = ({ label, progress, details }: MetricItemProps) => {
|
||||
const theme = useTheme();
|
||||
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
|
||||
return (
|
||||
<Grid
|
||||
size={isSmall ? 12 : 4}
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
textAlign: "center",
|
||||
gap: theme.spacing(2),
|
||||
padding: theme.spacing(2),
|
||||
paddingBottom: theme.spacing(4),
|
||||
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(2),
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
alignItems="center"
|
||||
spacing={theme.spacing(2)}
|
||||
>
|
||||
<Gauge
|
||||
progress={progress}
|
||||
@@ -48,44 +79,22 @@ const MetricItem = ({
|
||||
strokeWidth={12}
|
||||
/>
|
||||
<Typography variant="body2">{label}</Typography>
|
||||
</Stack>
|
||||
{(upperLabel || lowerLabel) && (
|
||||
<Stack
|
||||
spacing={theme.spacing(1)}
|
||||
flex={1}
|
||||
paddingBottom={theme.spacing(10)}
|
||||
</Box>
|
||||
{details && details.length > 0 && (
|
||||
<Box
|
||||
width="100%"
|
||||
paddingX={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>
|
||||
{details.map((detail) => (
|
||||
<MetricDetailRow
|
||||
key={detail.label}
|
||||
label={detail.label}
|
||||
value={detail.value}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -99,54 +108,93 @@ export const InfrastructureMetrics = ({ monitor }: { monitor: StatusPageMonitor
|
||||
return (
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
color={theme.palette.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);
|
||||
const metrics = [
|
||||
{
|
||||
key: t("pages.statusPages.monitorsList.infrastructure.cpu"),
|
||||
labelKey: t("pages.statusPages.monitorsList.infrastructure.cpu"),
|
||||
hasData: latestCheck.cpu && typeof latestCheck.cpu.usage_percent === "number",
|
||||
progress: latestCheck.cpu?.usage_percent ? latestCheck.cpu.usage_percent * 100 : 0,
|
||||
details: [
|
||||
{
|
||||
label: t("pages.statusPages.monitorsList.infrastructure.usage"),
|
||||
value: `${((latestCheck.cpu?.usage_percent || 0) * 100).toFixed(2)}%`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: t("pages.statusPages.monitorsList.infrastructure.memoryText"),
|
||||
labelKey: t("pages.statusPages.monitorsList.infrastructure.memory"),
|
||||
hasData:
|
||||
latestCheck.memory &&
|
||||
typeof latestCheck.memory.usage_percent === "number" &&
|
||||
typeof latestCheck.memory.used_bytes === "number" &&
|
||||
typeof latestCheck.memory.total_bytes === "number",
|
||||
progress: latestCheck.memory?.usage_percent
|
||||
? latestCheck.memory.usage_percent * 100
|
||||
: 0,
|
||||
details: [
|
||||
{
|
||||
label: t("pages.statusPages.monitorsList.infrastructure.used"),
|
||||
value: prettyBytes(latestCheck.memory?.used_bytes || 0),
|
||||
},
|
||||
{
|
||||
label: t("pages.statusPages.monitorsList.infrastructure.total"),
|
||||
value: prettyBytes(latestCheck.memory?.total_bytes || 0),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: t("pages.statusPages.monitorsList.infrastructure.disk"),
|
||||
labelKey: t("pages.statusPages.monitorsList.infrastructure.disk"),
|
||||
hasData: latestCheck.disk && latestCheck.disk.length > 0,
|
||||
progress: latestCheck.disk
|
||||
? (latestCheck.disk.reduce((acc, disk) => acc + (disk?.usage_percent || 0), 0) /
|
||||
latestCheck.disk.length) *
|
||||
100
|
||||
: 0,
|
||||
details: [
|
||||
{
|
||||
label: t("pages.statusPages.monitorsList.infrastructure.used"),
|
||||
value: prettyBytes(
|
||||
latestCheck?.disk?.reduce((acc, disk) => acc + (disk?.used_bytes || 0), 0) ??
|
||||
0
|
||||
),
|
||||
},
|
||||
{
|
||||
label: t("pages.statusPages.monitorsList.infrastructure.total"),
|
||||
value: prettyBytes(
|
||||
latestCheck?.disk?.reduce((acc, disk) => acc + (disk?.total_bytes || 0), 0) ??
|
||||
0
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Stack
|
||||
direction={{ xs: "column", sm: "row" }}
|
||||
gap={theme.spacing(10)}
|
||||
columnGap={theme.spacing(15)}
|
||||
padding={theme.spacing(8)}
|
||||
<Grid
|
||||
container
|
||||
alignItems="center"
|
||||
padding={theme.spacing(4)}
|
||||
>
|
||||
<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>
|
||||
{metrics.map(
|
||||
({ key, labelKey, hasData, progress, details }) =>
|
||||
hasData && (
|
||||
<MetricItem
|
||||
key={key}
|
||||
label={t(labelKey)}
|
||||
progress={progress}
|
||||
details={details}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
@@ -7,7 +7,7 @@ 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 { useTheme, type Theme } from "@mui/material/styles";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useState } from "react";
|
||||
import type { Monitor } from "@/Types/Monitor";
|
||||
@@ -23,6 +23,87 @@ interface MonitorsListProps {
|
||||
monitors: StatusPageMonitor[];
|
||||
}
|
||||
|
||||
const getMonitorBadgeStyles = (monitorType: string, theme: Theme) => {
|
||||
// for future: specific badge styles can be defined here based on monitor type
|
||||
const badgeConfig: Record<string, { color: string; bg: string }> = {
|
||||
hardware: {
|
||||
color: theme.palette.background.paper,
|
||||
bg: theme.palette.info.light,
|
||||
},
|
||||
http: {
|
||||
color: theme.palette.background.paper,
|
||||
bg: theme.palette.success.light,
|
||||
},
|
||||
ping: {
|
||||
color: theme.palette.background.paper,
|
||||
bg: theme.palette.success.light,
|
||||
},
|
||||
port: {
|
||||
color: theme.palette.background.paper,
|
||||
bg: theme.palette.success.light,
|
||||
},
|
||||
docker: {
|
||||
color: theme.palette.background.paper,
|
||||
bg: theme.palette.success.light,
|
||||
},
|
||||
pagespeed: {
|
||||
color: theme.palette.background.paper,
|
||||
bg: theme.palette.success.light,
|
||||
},
|
||||
game: {
|
||||
color: theme.palette.background.paper,
|
||||
bg: theme.palette.success.light,
|
||||
},
|
||||
};
|
||||
|
||||
const config = badgeConfig[monitorType] ?? badgeConfig.http;
|
||||
return {
|
||||
backgroundColor: config.bg,
|
||||
color: config.color,
|
||||
padding: `${theme.spacing(1)} ${theme.spacing(2)}`,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
};
|
||||
};
|
||||
|
||||
// Extracted component for monitor content below the header row
|
||||
const MonitorContent = ({
|
||||
monitor,
|
||||
statusPage,
|
||||
chartType,
|
||||
}: {
|
||||
monitor: Monitor & { checks?: Monitor["recentChecks"] };
|
||||
statusPage: StatusPage;
|
||||
chartType: string;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const isInfrastructureMonitor = monitor?.type === "hardware";
|
||||
|
||||
if (isInfrastructureMonitor) {
|
||||
if (statusPage.showInfrastructure === false) {
|
||||
return null;
|
||||
}
|
||||
return <InfrastructureMetrics monitor={monitor} />;
|
||||
}
|
||||
|
||||
if (statusPage.showCharts === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ overflow: "hidden", minWidth: 0, flex: 1, mb: theme.spacing(2) }}>
|
||||
{chartType === "histogram" ? (
|
||||
<HistogramResponseTime
|
||||
height={{ xs: 50, md: 100 }}
|
||||
gap={{ xs: theme.spacing(1), md: theme.spacing(5) }}
|
||||
checks={monitor?.checks?.slice().reverse() ?? []}
|
||||
/>
|
||||
) : (
|
||||
<HeatmapResponseTime checks={monitor?.checks?.slice().reverse() ?? []} />
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const MonitorsList = ({ statusPage, monitors }: MonitorsListProps) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
@@ -55,7 +136,6 @@ export const MonitorsList = ({ statusPage, monitors }: MonitorsListProps) => {
|
||||
)}
|
||||
|
||||
{monitors?.map((monitor) => {
|
||||
const isInfrastructureMonitor = monitor?.type === "hardware";
|
||||
return (
|
||||
<BaseBox
|
||||
key={monitor.id}
|
||||
@@ -87,27 +167,22 @@ export const MonitorsList = ({ statusPage, monitors }: MonitorsListProps) => {
|
||||
</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,
|
||||
}}
|
||||
sx={getMonitorBadgeStyles(monitor?.type ?? "", theme)}
|
||||
>
|
||||
{t(`pages.statusPages.monitorsList.badge.${monitor?.type}`)}
|
||||
{t(
|
||||
`pages.common.monitors.monitorTypes.option${monitor?.type.charAt(0).toUpperCase() + monitor?.type.slice(1)}`
|
||||
)}
|
||||
</Typography>
|
||||
</Stack>
|
||||
{showURL && monitor?.url && (
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
color={theme.palette.text.secondary}
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{monitor.url}
|
||||
</Typography>
|
||||
@@ -115,48 +190,11 @@ export const MonitorsList = ({ statusPage, monitors }: MonitorsListProps) => {
|
||||
</Box>
|
||||
<StatusLabel status={monitor?.status} />
|
||||
</Stack>
|
||||
|
||||
{!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>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{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>
|
||||
)}
|
||||
<MonitorContent
|
||||
monitor={monitor}
|
||||
statusPage={statusPage}
|
||||
chartType={chartType}
|
||||
/>
|
||||
</BaseBox>
|
||||
);
|
||||
})}
|
||||
|
||||
+12
-16
@@ -407,6 +407,17 @@
|
||||
"paused": "paused",
|
||||
"total": "total",
|
||||
"up": "up"
|
||||
},
|
||||
"monitorTypes": {
|
||||
"optionDocker": "Docker",
|
||||
"optionGame": "Game",
|
||||
"optionHttp": "HTTP(S)",
|
||||
"optionPing": "Ping",
|
||||
"optionPort": "Port",
|
||||
"optionPagespeed": "PageSpeed",
|
||||
"optionHardware": "Infrastructure",
|
||||
"optionGrpc": "gRPC",
|
||||
"optionWebSocket": "WebSocket"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -534,19 +545,12 @@
|
||||
},
|
||||
"type": {
|
||||
"description": "Select the type of check to perform",
|
||||
"optionDocker": "Docker",
|
||||
"optionDockerDescription": "Use Docker to monitor if a container is running.",
|
||||
"optionGame": "Game",
|
||||
"optionGameDescription": "Monitor if a specific game server is online.",
|
||||
"optionGrpc": "gRPC",
|
||||
"optionGrpcDescription": "Monitor gRPC services using the standard Health Checking Protocol.",
|
||||
"optionWebSocket": "WebSocket",
|
||||
"optionWebSocketDescription": "Monitor WebSocket endpoints for connection health and response time.",
|
||||
"optionHttp": "HTTP(S)",
|
||||
"optionHttpDescription": "Use HTTP(S) to monitor your website or API endpoint.",
|
||||
"optionPing": "Ping",
|
||||
"optionPingDescription": "Use ICMP Ping to monitor if a server is online.",
|
||||
"optionPort": "Port",
|
||||
"optionPortDescription": "Monitor if a specific port on a server is open.",
|
||||
"title": "Type"
|
||||
},
|
||||
@@ -1184,6 +1188,7 @@
|
||||
"title": "Infrastructure",
|
||||
"cpu": "CPU",
|
||||
"memory": "RAM",
|
||||
"memoryText": "Memory",
|
||||
"disk": "Disk",
|
||||
"usage": "Usage",
|
||||
"used": "Used",
|
||||
@@ -1192,15 +1197,6 @@
|
||||
"uptime": {
|
||||
"title": "Uptime",
|
||||
"responseTime": "Response time"
|
||||
},
|
||||
"badge": {
|
||||
"http": "HTTP",
|
||||
"ping": "Ping",
|
||||
"port": "Port",
|
||||
"docker": "Docker",
|
||||
"pagespeed": "PageSpeed",
|
||||
"hardware": "Infrastructure",
|
||||
"game": "Game"
|
||||
}
|
||||
},
|
||||
"statusBar": {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import mongoose from "mongoose";
|
||||
import { logger } from "@/utils/logger.js";
|
||||
import StatusPageModel from "../models/StatusPage.js";
|
||||
|
||||
/**
|
||||
* Migration: Convert StatusPage `type` field from string to array of strings
|
||||
*
|
||||
* Old format: { type: "uptime" }
|
||||
* New format: { type: ["uptime"] }
|
||||
*/
|
||||
export async function migrateStatusPageTypeToArray(): Promise<void> {
|
||||
const SERVICE_NAME = "Migration:MigrateStatusPageTypeToArray";
|
||||
|
||||
try {
|
||||
logger.info({ service: SERVICE_NAME, message: "Starting migration of StatusPage type field from string to array" });
|
||||
|
||||
const db = mongoose.connection.db;
|
||||
if (!db) {
|
||||
throw new Error("Database connection is not initialized");
|
||||
}
|
||||
|
||||
// Find all status pages where `type` is a string (not yet migrated)
|
||||
const result = await StatusPageModel.updateMany({ type: { $type: "string" } }, [
|
||||
{
|
||||
$set: {
|
||||
type: ["$type"],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
if (result.modifiedCount === 0) {
|
||||
logger.info({ service: SERVICE_NAME, message: "No StatusPage documents needed migration" });
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info({
|
||||
service: SERVICE_NAME,
|
||||
message: `Migration complete. Converted ${result.modifiedCount} StatusPage document(s) from string type to array type`,
|
||||
});
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
logger.error({ service: SERVICE_NAME, message: `Error during StatusPage type migration: ${errorMessage}` });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { convertChecksToTimeSeries } from "./0002_convertChecksToTimeSeries.js";
|
||||
import { cleanupDuplicateMonitorStats } from "./0003_cleanupDuplicateMonitorStats.js";
|
||||
import { fixInfrastructureThresholds } from "./0004_fixInfrastructureThresholds.js";
|
||||
import MigrationModel from "../models/Migration.js";
|
||||
import { migrateStatusPageTypeToArray } from "./0005_migrateStatusPageTypeToArray.js";
|
||||
|
||||
type MigrationEntry = {
|
||||
name: string;
|
||||
@@ -14,6 +15,7 @@ const migrations: MigrationEntry[] = [
|
||||
{ name: "0002_convertChecksToTimeSeries", execute: convertChecksToTimeSeries },
|
||||
{ name: "0003_cleanupDuplicateMonitorStats", execute: cleanupDuplicateMonitorStats },
|
||||
{ name: "0004_fixInfrastructureThresholds", execute: fixInfrastructureThresholds },
|
||||
{ name: "0005_migrateStatusPageTypeToArray", execute: migrateStatusPageTypeToArray },
|
||||
];
|
||||
|
||||
const runMigrations = async (logger?: { info: Function; error: Function }) => {
|
||||
|
||||
Reference in new issue
Block a user