Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d0373989f | ||
|
|
ac51dfeab7 | ||
|
|
99a5aa1722 | ||
|
|
5791bdd5e7 | ||
|
|
f7514bc938 | ||
|
|
2bbaf7d1eb |
No files matched your search
@@ -1,12 +1,13 @@
|
|||||||
import type { Monitor } from "@/Types/Monitor";
|
import type { Monitor } from "@/Types/Monitor";
|
||||||
import Stack from "@mui/material/Stack";
|
import Stack from "@mui/material/Stack";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import { PulseDot, Dot } from "@/Components/design-elements";
|
import { PulseDot, Dot, StrategyBadge } from "@/Components/design-elements";
|
||||||
import { getStatusColor, formatUrl } from "@/Utils/MonitorUtils";
|
import { getStatusColor, formatUrl } from "@/Utils/MonitorUtils";
|
||||||
import { useTheme } from "@mui/material/styles";
|
import { useTheme } from "@mui/material/styles";
|
||||||
import prettyMilliseconds from "pretty-ms";
|
import prettyMilliseconds from "pretty-ms";
|
||||||
import { typographyLevels } from "@/Utils/Theme/Palette";
|
import { typographyLevels } from "@/Utils/Theme/Palette";
|
||||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||||
|
import { LAYOUT } from "@/Utils/Theme/constants";
|
||||||
export const MonitorStatus = ({ monitor }: { monitor: Monitor }) => {
|
export const MonitorStatus = ({ monitor }: { monitor: Monitor }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
|
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
|
||||||
@@ -28,7 +29,7 @@ export const MonitorStatus = ({ monitor }: { monitor: Monitor }) => {
|
|||||||
<Stack
|
<Stack
|
||||||
direction="row"
|
direction="row"
|
||||||
alignItems={"center"}
|
alignItems={"center"}
|
||||||
gap={theme.spacing(4)}
|
gap={theme.spacing(LAYOUT.XS)}
|
||||||
>
|
>
|
||||||
<PulseDot color={getStatusColor(monitor.status, theme)} />
|
<PulseDot color={getStatusColor(monitor.status, theme)} />
|
||||||
<Typography
|
<Typography
|
||||||
@@ -41,6 +42,12 @@ export const MonitorStatus = ({ monitor }: { monitor: Monitor }) => {
|
|||||||
>
|
>
|
||||||
{formatUrl(monitor?.url)}
|
{formatUrl(monitor?.url)}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
{monitor.type === "pagespeed" && monitor.strategy && (
|
||||||
|
<>
|
||||||
|
<Dot />
|
||||||
|
<StrategyBadge strategy={monitor.strategy} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{!isSmall && (
|
{!isSmall && (
|
||||||
<>
|
<>
|
||||||
<Dot />
|
<Dot />
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
import { useTheme } from "@mui/material/styles";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import type { PageSpeedStrategy } from "@/Types/Monitor";
|
||||||
|
import { SPACING } from "@/Utils/Theme/constants";
|
||||||
|
|
||||||
|
interface StrategyBadgeProps {
|
||||||
|
strategy: PageSpeedStrategy | undefined;
|
||||||
|
variant?: "caption" | "body2";
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StrategyBadge = ({ strategy, variant = "body2" }: StrategyBadgeProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
if (!strategy) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Typography
|
||||||
|
variant={variant}
|
||||||
|
px={SPACING.XXL}
|
||||||
|
py={SPACING.SM}
|
||||||
|
borderRadius={theme.shape.borderRadius}
|
||||||
|
bgcolor={theme.palette.secondary.light}
|
||||||
|
color={theme.palette.text.primary}
|
||||||
|
>
|
||||||
|
{t(`pages.pageSpeed.strategy.${strategy}`)}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -21,3 +21,4 @@ export * from "./SplitBox";
|
|||||||
export * from "./TextLink";
|
export * from "./TextLink";
|
||||||
export * from "./OfflineBanner";
|
export * from "./OfflineBanner";
|
||||||
export * from "./Avatar";
|
export * from "./Avatar";
|
||||||
|
export * from "./StrategyBadge";
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { monitorSchema, type MonitorFormData } from "@/Validation/monitor";
|
import { monitorSchema, type MonitorFormData } from "@/Validation/monitor";
|
||||||
|
import { DefaultPageSpeedStrategy } from "@/Types/Monitor";
|
||||||
import type { Monitor, MonitorType } from "@/Types/Monitor";
|
import type { Monitor, MonitorType } from "@/Types/Monitor";
|
||||||
|
|
||||||
interface UseMonitorFormOptions {
|
interface UseMonitorFormOptions {
|
||||||
@@ -88,6 +89,7 @@ export const useMonitorForm = ({
|
|||||||
...base,
|
...base,
|
||||||
type: "pagespeed",
|
type: "pagespeed",
|
||||||
url: data?.url || "",
|
url: data?.url || "",
|
||||||
|
strategy: data?.strategy ?? DefaultPageSpeedStrategy,
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case "hardware":
|
case "hardware":
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import {
|
|||||||
type MonitorType,
|
type MonitorType,
|
||||||
type GamesMap,
|
type GamesMap,
|
||||||
supportsGeoCheck,
|
supportsGeoCheck,
|
||||||
|
DefaultPageSpeedStrategy,
|
||||||
} from "@/Types/Monitor";
|
} from "@/Types/Monitor";
|
||||||
import type { Notification } from "@/Types/Notification";
|
import type { Notification } from "@/Types/Notification";
|
||||||
import type { MonitorFormData } from "@/Validation/monitor";
|
import type { MonitorFormData } from "@/Validation/monitor";
|
||||||
@@ -51,6 +52,7 @@ interface GeneralSettingsConfig {
|
|||||||
showSecret: boolean;
|
showSecret: boolean;
|
||||||
showGrpcServiceName: boolean;
|
showGrpcServiceName: boolean;
|
||||||
showIgnoreTls: boolean;
|
showIgnoreTls: boolean;
|
||||||
|
showStrategy: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getGeneralSettingsConfig = (
|
const getGeneralSettingsConfig = (
|
||||||
@@ -68,6 +70,7 @@ const getGeneralSettingsConfig = (
|
|||||||
showSecret: false,
|
showSecret: false,
|
||||||
showGrpcServiceName: false,
|
showGrpcServiceName: false,
|
||||||
showIgnoreTls: false,
|
showIgnoreTls: false,
|
||||||
|
showStrategy: false,
|
||||||
},
|
},
|
||||||
ping: {
|
ping: {
|
||||||
urlLabel: t("pages.createMonitor.form.general.option.host.label"),
|
urlLabel: t("pages.createMonitor.form.general.option.host.label"),
|
||||||
@@ -79,6 +82,7 @@ const getGeneralSettingsConfig = (
|
|||||||
showSecret: false,
|
showSecret: false,
|
||||||
showGrpcServiceName: false,
|
showGrpcServiceName: false,
|
||||||
showIgnoreTls: false,
|
showIgnoreTls: false,
|
||||||
|
showStrategy: false,
|
||||||
},
|
},
|
||||||
docker: {
|
docker: {
|
||||||
urlLabel: t("pages.createMonitor.form.general.option.container.label"),
|
urlLabel: t("pages.createMonitor.form.general.option.container.label"),
|
||||||
@@ -90,6 +94,7 @@ const getGeneralSettingsConfig = (
|
|||||||
showSecret: false,
|
showSecret: false,
|
||||||
showGrpcServiceName: false,
|
showGrpcServiceName: false,
|
||||||
showIgnoreTls: false,
|
showIgnoreTls: false,
|
||||||
|
showStrategy: false,
|
||||||
},
|
},
|
||||||
port: {
|
port: {
|
||||||
urlLabel: t("pages.createMonitor.form.general.option.url.label"),
|
urlLabel: t("pages.createMonitor.form.general.option.url.label"),
|
||||||
@@ -101,6 +106,7 @@ const getGeneralSettingsConfig = (
|
|||||||
showSecret: false,
|
showSecret: false,
|
||||||
showGrpcServiceName: false,
|
showGrpcServiceName: false,
|
||||||
showIgnoreTls: false,
|
showIgnoreTls: false,
|
||||||
|
showStrategy: false,
|
||||||
},
|
},
|
||||||
game: {
|
game: {
|
||||||
urlLabel: t("pages.createMonitor.form.general.option.url.label"),
|
urlLabel: t("pages.createMonitor.form.general.option.url.label"),
|
||||||
@@ -112,6 +118,7 @@ const getGeneralSettingsConfig = (
|
|||||||
showSecret: false,
|
showSecret: false,
|
||||||
showGrpcServiceName: false,
|
showGrpcServiceName: false,
|
||||||
showIgnoreTls: false,
|
showIgnoreTls: false,
|
||||||
|
showStrategy: false,
|
||||||
},
|
},
|
||||||
grpc: {
|
grpc: {
|
||||||
urlLabel: t("pages.createMonitor.form.general.option.host.label"),
|
urlLabel: t("pages.createMonitor.form.general.option.host.label"),
|
||||||
@@ -123,6 +130,7 @@ const getGeneralSettingsConfig = (
|
|||||||
showSecret: false,
|
showSecret: false,
|
||||||
showGrpcServiceName: true,
|
showGrpcServiceName: true,
|
||||||
showIgnoreTls: true,
|
showIgnoreTls: true,
|
||||||
|
showStrategy: false,
|
||||||
},
|
},
|
||||||
pagespeed: {
|
pagespeed: {
|
||||||
urlLabel: t("pages.createMonitor.form.general.option.url.label"),
|
urlLabel: t("pages.createMonitor.form.general.option.url.label"),
|
||||||
@@ -134,6 +142,7 @@ const getGeneralSettingsConfig = (
|
|||||||
showSecret: false,
|
showSecret: false,
|
||||||
showGrpcServiceName: false,
|
showGrpcServiceName: false,
|
||||||
showIgnoreTls: false,
|
showIgnoreTls: false,
|
||||||
|
showStrategy: true,
|
||||||
},
|
},
|
||||||
hardware: {
|
hardware: {
|
||||||
urlLabel: t("pages.createMonitor.form.general.option.url.label"),
|
urlLabel: t("pages.createMonitor.form.general.option.url.label"),
|
||||||
@@ -145,6 +154,7 @@ const getGeneralSettingsConfig = (
|
|||||||
showSecret: true,
|
showSecret: true,
|
||||||
showGrpcServiceName: false,
|
showGrpcServiceName: false,
|
||||||
showIgnoreTls: false,
|
showIgnoreTls: false,
|
||||||
|
showStrategy: false,
|
||||||
},
|
},
|
||||||
websocket: {
|
websocket: {
|
||||||
urlLabel: t("pages.createMonitor.form.general.option.wsUrl.label"),
|
urlLabel: t("pages.createMonitor.form.general.option.wsUrl.label"),
|
||||||
@@ -156,6 +166,7 @@ const getGeneralSettingsConfig = (
|
|||||||
showSecret: false,
|
showSecret: false,
|
||||||
showGrpcServiceName: false,
|
showGrpcServiceName: false,
|
||||||
showIgnoreTls: true,
|
showIgnoreTls: true,
|
||||||
|
showStrategy: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return configs[type] || configs.http;
|
return configs[type] || configs.http;
|
||||||
@@ -382,6 +393,31 @@ const CreateMonitorPage = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Strategy field - only for pagespeed type */}
|
||||||
|
{generalSettingsConfig.showStrategy && (
|
||||||
|
<Controller
|
||||||
|
name="strategy"
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState }) => (
|
||||||
|
<Select
|
||||||
|
{...field}
|
||||||
|
value={field.value ?? DefaultPageSpeedStrategy}
|
||||||
|
fieldLabel={t(
|
||||||
|
"pages.createMonitor.form.general.option.strategy.label"
|
||||||
|
)}
|
||||||
|
error={!!fieldState.error}
|
||||||
|
>
|
||||||
|
<MenuItem value="desktop">
|
||||||
|
{t("pages.createMonitor.form.general.option.strategy.desktop")}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem value="mobile">
|
||||||
|
{t("pages.createMonitor.form.general.option.strategy.mobile")}
|
||||||
|
</MenuItem>
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Port field - only for port and game types */}
|
{/* Port field - only for port and game types */}
|
||||||
{generalSettingsConfig.showPort && (
|
{generalSettingsConfig.showPort && (
|
||||||
<Controller
|
<Controller
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
import Stack from "@mui/material/Stack";
|
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 { Table, Pagination, StatusLabel } from "@/Components/design-elements";
|
import {
|
||||||
|
Table,
|
||||||
|
Pagination,
|
||||||
|
StatusLabel,
|
||||||
|
StrategyBadge,
|
||||||
|
} from "@/Components/design-elements";
|
||||||
|
import { LAYOUT } from "@/Utils/Theme/constants";
|
||||||
import { HistogramPageSpeed } from "@/Components/monitors";
|
import { HistogramPageSpeed } from "@/Components/monitors";
|
||||||
import { ArrowDown, ArrowUp } from "lucide-react";
|
import { ArrowDown, ArrowUp } from "lucide-react";
|
||||||
import type { Header } from "@/Components/design-elements/Table";
|
import type { Header } from "@/Components/design-elements/Table";
|
||||||
@@ -179,7 +185,19 @@ export const PageSpeedMonitorsTable = ({
|
|||||||
</Stack>
|
</Stack>
|
||||||
),
|
),
|
||||||
render: (row) => {
|
render: (row) => {
|
||||||
return row.name;
|
return (
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
alignItems="center"
|
||||||
|
gap={LAYOUT.XS}
|
||||||
|
>
|
||||||
|
<Typography>{row.name}</Typography>
|
||||||
|
<StrategyBadge
|
||||||
|
strategy={row.strategy}
|
||||||
|
variant="caption"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ export type MonitorStatus = (typeof MonitorStatuses)[number];
|
|||||||
|
|
||||||
export type MonitorMatchMethod = "equal" | "include" | "regex" | "";
|
export type MonitorMatchMethod = "equal" | "include" | "regex" | "";
|
||||||
|
|
||||||
|
export const PageSpeedStrategies = ["desktop", "mobile"] as const;
|
||||||
|
export type PageSpeedStrategy = (typeof PageSpeedStrategies)[number];
|
||||||
|
export const DefaultPageSpeedStrategy: PageSpeedStrategy = "desktop";
|
||||||
|
|
||||||
export interface Monitor {
|
export interface Monitor {
|
||||||
id: string;
|
id: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
@@ -72,6 +76,7 @@ export interface Monitor {
|
|||||||
selectedDisks: string[];
|
selectedDisks: string[];
|
||||||
gameId?: string;
|
gameId?: string;
|
||||||
grpcServiceName?: string;
|
grpcServiceName?: string;
|
||||||
|
strategy?: PageSpeedStrategy;
|
||||||
group: string | null;
|
group: string | null;
|
||||||
geoCheckEnabled?: boolean;
|
geoCheckEnabled?: boolean;
|
||||||
geoCheckLocations?: GeoContinent[];
|
geoCheckLocations?: GeoContinent[];
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { GeoContinents } from "@/Types/GeoCheck";
|
import { GeoContinents } from "@/Types/GeoCheck";
|
||||||
|
import { PageSpeedStrategies } from "@/Types/Monitor";
|
||||||
|
|
||||||
// URL schema with custom error message
|
// URL schema with custom error message
|
||||||
const urlSchema = z.url({ message: "Please enter a valid URL" });
|
const urlSchema = z.url({ message: "Please enter a valid URL" });
|
||||||
@@ -89,6 +90,7 @@ const grpcSchema = baseSchema.extend({
|
|||||||
const pagespeedSchema = baseSchema.extend({
|
const pagespeedSchema = baseSchema.extend({
|
||||||
type: z.literal("pagespeed"),
|
type: z.literal("pagespeed"),
|
||||||
url: urlSchema,
|
url: urlSchema,
|
||||||
|
strategy: z.enum(PageSpeedStrategies),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Hardware/Infrastructure monitor schema
|
// Hardware/Infrastructure monitor schema
|
||||||
|
|||||||
@@ -503,6 +503,11 @@
|
|||||||
"wsUrl": {
|
"wsUrl": {
|
||||||
"label": "WebSocket URL",
|
"label": "WebSocket URL",
|
||||||
"placeholder": "wss://example.com/socket"
|
"placeholder": "wss://example.com/socket"
|
||||||
|
},
|
||||||
|
"strategy": {
|
||||||
|
"label": "Strategy",
|
||||||
|
"desktop": "Desktop",
|
||||||
|
"mobile": "Mobile"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "General settings",
|
"title": "General settings",
|
||||||
@@ -968,6 +973,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pageSpeed": {
|
"pageSpeed": {
|
||||||
|
"strategy": {
|
||||||
|
"desktop": "Desktop",
|
||||||
|
"mobile": "Mobile"
|
||||||
|
},
|
||||||
"charts": {
|
"charts": {
|
||||||
"common": {
|
"common": {
|
||||||
"cls": "Cumulative Layout Shift (CLS)",
|
"cls": "Cumulative Layout Shift (CLS)",
|
||||||
|
|||||||
@@ -3631,6 +3631,12 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"default": ""
|
"default": ""
|
||||||
},
|
},
|
||||||
|
"strategy": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["desktop", "mobile"],
|
||||||
|
"default": "desktop",
|
||||||
|
"description": "PageSpeed analysis strategy"
|
||||||
|
},
|
||||||
"selectedDisks": {
|
"selectedDisks": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -3737,6 +3743,11 @@
|
|||||||
"grpcServiceName": {
|
"grpcServiceName": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"strategy": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["desktop", "mobile"],
|
||||||
|
"description": "PageSpeed analysis strategy"
|
||||||
|
},
|
||||||
"selectedDisks": {
|
"selectedDisks": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import type { Pool } from "pg";
|
||||||
|
|
||||||
|
export const addStrategyToMonitors = async (pool: Pool) => {
|
||||||
|
await pool.query(`
|
||||||
|
ALTER TABLE monitors
|
||||||
|
ADD COLUMN IF NOT EXISTS strategy TEXT DEFAULT NULL;
|
||||||
|
`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dropStrategyFromMonitors = async (pool: Pool) => {
|
||||||
|
await pool.query(`
|
||||||
|
ALTER TABLE monitors
|
||||||
|
DROP COLUMN IF EXISTS strategy;
|
||||||
|
`);
|
||||||
|
};
|
||||||
@@ -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 { addStrategyToMonitors, dropStrategyFromMonitors } from "./0022_add_strategy_to_monitors.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_strategy_to_monitors", up: addStrategyToMonitors, down: dropStrategyFromMonitors },
|
||||||
];
|
];
|
||||||
|
|
||||||
const ensureMigrationsTable = async (pool: Pool) => {
|
const ensureMigrationsTable = async (pool: Pool) => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Schema, model, Types } from "mongoose";
|
import { Schema, model, Types } from "mongoose";
|
||||||
import type { Monitor, MonitorMatchMethod, CheckSnapshot } from "@/types/monitor.js";
|
import type { Monitor, MonitorMatchMethod, CheckSnapshot } from "@/types/monitor.js";
|
||||||
import { MonitorTypes, MonitorStatuses } from "@/types/monitor.js";
|
import { MonitorTypes, MonitorStatuses, PageSpeedStrategies } from "@/types/monitor.js";
|
||||||
import type {
|
import type {
|
||||||
CheckAudits,
|
CheckAudits,
|
||||||
CheckCaptureInfo,
|
CheckCaptureInfo,
|
||||||
@@ -330,6 +330,10 @@ const MonitorSchema = new Schema<MonitorDocument>(
|
|||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: "",
|
||||||
},
|
},
|
||||||
|
strategy: {
|
||||||
|
type: String,
|
||||||
|
enum: PageSpeedStrategies,
|
||||||
|
},
|
||||||
group: {
|
group: {
|
||||||
type: String,
|
type: String,
|
||||||
trim: true,
|
trim: true,
|
||||||
|
|||||||
@@ -386,6 +386,7 @@ class MongoMonitorsRepository implements IMonitorsRepository {
|
|||||||
selectedDisks: doc.selectedDisks ?? [],
|
selectedDisks: doc.selectedDisks ?? [],
|
||||||
gameId: doc.gameId ?? undefined,
|
gameId: doc.gameId ?? undefined,
|
||||||
grpcServiceName: doc.grpcServiceName ?? undefined,
|
grpcServiceName: doc.grpcServiceName ?? undefined,
|
||||||
|
strategy: doc.strategy ?? undefined,
|
||||||
group: doc.group ?? null,
|
group: doc.group ?? null,
|
||||||
recentChecks: (doc.recentChecks ?? []).map((check: CheckSnapshotDocument) => this.toCheckSnapshot(check)),
|
recentChecks: (doc.recentChecks ?? []).map((check: CheckSnapshotDocument) => this.toCheckSnapshot(check)),
|
||||||
geoCheckEnabled: doc.geoCheckEnabled ?? false,
|
geoCheckEnabled: doc.geoCheckEnabled ?? false,
|
||||||
@@ -445,6 +446,7 @@ class MongoMonitorsRepository implements IMonitorsRepository {
|
|||||||
selectedDisks: doc.selectedDisks ?? [],
|
selectedDisks: doc.selectedDisks ?? [],
|
||||||
gameId: doc.gameId ?? undefined,
|
gameId: doc.gameId ?? undefined,
|
||||||
grpcServiceName: doc.grpcServiceName ?? undefined,
|
grpcServiceName: doc.grpcServiceName ?? undefined,
|
||||||
|
strategy: doc.strategy ?? undefined,
|
||||||
group: doc.group ?? null,
|
group: doc.group ?? null,
|
||||||
recentChecks: (doc.recentChecks ?? []).map((check: CheckSnapshotDocument) => this.toCheckSnapshot(check)),
|
recentChecks: (doc.recentChecks ?? []).map((check: CheckSnapshotDocument) => this.toCheckSnapshot(check)),
|
||||||
geoCheckEnabled: doc.geoCheckEnabled ?? false,
|
geoCheckEnabled: doc.geoCheckEnabled ?? false,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { Pool } from "pg";
|
import type { Pool } from "pg";
|
||||||
import type { Monitor, MonitorsSummary, MonitorStatus, MonitorType, MonitorMatchMethod, GeoContinent } from "@/types/monitor.js";
|
import type { Monitor, MonitorsSummary, MonitorStatus, MonitorType, MonitorMatchMethod, GeoContinent, PageSpeedStrategy } from "@/types/monitor.js";
|
||||||
|
|
||||||
import type { IMonitorsRepository, TeamQueryConfig, SummaryConfig } from "./IMonitorsRepository.js";
|
import type { IMonitorsRepository, TeamQueryConfig, SummaryConfig } from "./IMonitorsRepository.js";
|
||||||
import { AppError } from "@/utils/AppError.js";
|
import { AppError } from "@/utils/AppError.js";
|
||||||
|
|
||||||
@@ -36,6 +37,7 @@ interface MonitorRow {
|
|||||||
selected_disks: string[] | null;
|
selected_disks: string[] | null;
|
||||||
game_id: string | null;
|
game_id: string | null;
|
||||||
grpc_service_name: string | null;
|
grpc_service_name: string | null;
|
||||||
|
strategy: PageSpeedStrategy | null;
|
||||||
monitor_group: string | null;
|
monitor_group: string | null;
|
||||||
geo_check_enabled: boolean;
|
geo_check_enabled: boolean;
|
||||||
geo_check_locations: GeoContinent[] | null;
|
geo_check_locations: GeoContinent[] | null;
|
||||||
@@ -49,7 +51,7 @@ const MONITOR_COLUMNS = `id, user_id, team_id, name, description, type, status,
|
|||||||
interval_ms, is_active, status_window, status_window_size, status_window_threshold, uptime_percentage,
|
interval_ms, is_active, status_window, status_window_size, status_window_threshold, uptime_percentage,
|
||||||
cpu_alert_threshold, cpu_alert_counter, memory_alert_threshold, memory_alert_counter,
|
cpu_alert_threshold, cpu_alert_counter, memory_alert_threshold, memory_alert_counter,
|
||||||
disk_alert_threshold, disk_alert_counter, temp_alert_threshold, temp_alert_counter, selected_disks,
|
disk_alert_threshold, disk_alert_counter, temp_alert_threshold, temp_alert_counter, selected_disks,
|
||||||
game_id, grpc_service_name, monitor_group, geo_check_enabled, geo_check_locations, geo_check_interval_ms,
|
game_id, grpc_service_name, strategy, monitor_group, geo_check_enabled, geo_check_locations, geo_check_interval_ms,
|
||||||
created_at, updated_at`;
|
created_at, updated_at`;
|
||||||
|
|
||||||
export class TimescaleMonitorsRepository implements IMonitorsRepository {
|
export class TimescaleMonitorsRepository implements IMonitorsRepository {
|
||||||
@@ -62,8 +64,8 @@ export class TimescaleMonitorsRepository implements IMonitorsRepository {
|
|||||||
interval_ms, is_active, status_window, status_window_size, status_window_threshold,
|
interval_ms, is_active, status_window, status_window_size, status_window_threshold,
|
||||||
cpu_alert_threshold, cpu_alert_counter, memory_alert_threshold, memory_alert_counter,
|
cpu_alert_threshold, cpu_alert_counter, memory_alert_threshold, memory_alert_counter,
|
||||||
disk_alert_threshold, disk_alert_counter, temp_alert_threshold, temp_alert_counter, selected_disks,
|
disk_alert_threshold, disk_alert_counter, temp_alert_threshold, temp_alert_counter, selected_disks,
|
||||||
game_id, grpc_service_name, monitor_group, geo_check_enabled, geo_check_locations, geo_check_interval_ms)
|
game_id, grpc_service_name, strategy, monitor_group, geo_check_enabled, geo_check_locations, geo_check_interval_ms)
|
||||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34)
|
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34,$35)
|
||||||
RETURNING ${MONITOR_COLUMNS}`,
|
RETURNING ${MONITOR_COLUMNS}`,
|
||||||
[
|
[
|
||||||
userId,
|
userId,
|
||||||
@@ -96,6 +98,7 @@ export class TimescaleMonitorsRepository implements IMonitorsRepository {
|
|||||||
monitor.selectedDisks ?? [],
|
monitor.selectedDisks ?? [],
|
||||||
monitor.gameId ?? null,
|
monitor.gameId ?? null,
|
||||||
monitor.grpcServiceName ?? null,
|
monitor.grpcServiceName ?? null,
|
||||||
|
monitor.strategy ?? null,
|
||||||
monitor.group ?? null,
|
monitor.group ?? null,
|
||||||
monitor.geoCheckEnabled ?? false,
|
monitor.geoCheckEnabled ?? false,
|
||||||
monitor.geoCheckLocations ?? [],
|
monitor.geoCheckLocations ?? [],
|
||||||
@@ -594,6 +597,7 @@ export class TimescaleMonitorsRepository implements IMonitorsRepository {
|
|||||||
["selectedDisks", "selected_disks"],
|
["selectedDisks", "selected_disks"],
|
||||||
["gameId", "game_id"],
|
["gameId", "game_id"],
|
||||||
["grpcServiceName", "grpc_service_name"],
|
["grpcServiceName", "grpc_service_name"],
|
||||||
|
["strategy", "strategy"],
|
||||||
["group", "monitor_group"],
|
["group", "monitor_group"],
|
||||||
["geoCheckEnabled", "geo_check_enabled"],
|
["geoCheckEnabled", "geo_check_enabled"],
|
||||||
["geoCheckLocations", "geo_check_locations"],
|
["geoCheckLocations", "geo_check_locations"],
|
||||||
@@ -1033,6 +1037,7 @@ export class TimescaleMonitorsRepository implements IMonitorsRepository {
|
|||||||
selectedDisks: row.selected_disks ?? [],
|
selectedDisks: row.selected_disks ?? [],
|
||||||
gameId: row.game_id ?? undefined,
|
gameId: row.game_id ?? undefined,
|
||||||
grpcServiceName: row.grpc_service_name ?? undefined,
|
grpcServiceName: row.grpc_service_name ?? undefined,
|
||||||
|
strategy: row.strategy ?? undefined,
|
||||||
group: row.monitor_group,
|
group: row.monitor_group,
|
||||||
geoCheckEnabled: row.geo_check_enabled,
|
geoCheckEnabled: row.geo_check_enabled,
|
||||||
geoCheckLocations: row.geo_check_locations ?? [],
|
geoCheckLocations: row.geo_check_locations ?? [],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { IStatusProvider } from "@/service/infrastructure/network/IStatusProvider.js";
|
import { IStatusProvider } from "@/service/infrastructure/network/IStatusProvider.js";
|
||||||
import { MonitorStatusResponse, PageSpeedStatusPayload } from "@/types/network.js";
|
import { MonitorStatusResponse, PageSpeedStatusPayload } from "@/types/network.js";
|
||||||
import { Monitor, MonitorType } from "@/types/monitor.js";
|
import { Monitor, MonitorType, DefaultPageSpeedStrategy } from "@/types/monitor.js";
|
||||||
import { HttpProvider } from "@/service/infrastructure/network/HttpProvider.js";
|
import { HttpProvider } from "@/service/infrastructure/network/HttpProvider.js";
|
||||||
import { ISettingsService } from "@/service/system/settingsService.js";
|
import { ISettingsService } from "@/service/system/settingsService.js";
|
||||||
import { ILogger } from "@/utils/logger.js";
|
import { ILogger } from "@/utils/logger.js";
|
||||||
@@ -19,14 +19,15 @@ export class PageSpeedProvider implements IStatusProvider<PageSpeedStatusPayload
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handle(monitor: Monitor): Promise<MonitorStatusResponse<PageSpeedStatusPayload>> {
|
async handle(monitor: Monitor): Promise<MonitorStatusResponse<PageSpeedStatusPayload>> {
|
||||||
const { url } = monitor;
|
const { url, strategy } = monitor;
|
||||||
try {
|
try {
|
||||||
if (!url) throw new Error("URL is required for PageSpeed monitor");
|
if (!url) throw new Error("URL is required for PageSpeed monitor");
|
||||||
const dbSettings = await this.settingsService.getDBSettings();
|
const dbSettings = await this.settingsService.getDBSettings();
|
||||||
const apiKey = dbSettings?.pagespeedApiKey;
|
const apiKey = dbSettings?.pagespeedApiKey;
|
||||||
|
const resolvedStrategy = strategy ?? DefaultPageSpeedStrategy;
|
||||||
let pageSpeedUrl = `https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${encodeURIComponent(
|
let pageSpeedUrl = `https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${encodeURIComponent(
|
||||||
url
|
url
|
||||||
)}&category=seo&category=accessibility&category=best-practices&category=performance`;
|
)}&strategy=${encodeURIComponent(resolvedStrategy)}&category=seo&category=accessibility&category=best-practices&category=performance`;
|
||||||
|
|
||||||
if (apiKey) {
|
if (apiKey) {
|
||||||
pageSpeedUrl += `&key=${apiKey}`;
|
pageSpeedUrl += `&key=${apiKey}`;
|
||||||
|
|||||||
@@ -249,6 +249,9 @@ export class StatusService implements IStatusService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// With a full window, a single raw check must not change monitor.status; only the sliding-window threshold can trigger a transition.
|
||||||
|
newStatus = monitor.status;
|
||||||
|
|
||||||
// Check if threshold has been met
|
// Check if threshold has been met
|
||||||
const failures = monitor.statusWindow.filter((s) => s === false).length;
|
const failures = monitor.statusWindow.filter((s) => s === false).length;
|
||||||
const failureRate = (failures / monitor.statusWindow.length) * 100;
|
const failureRate = (failures / monitor.statusWindow.length) * 100;
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ export type { GeoContinent } from "@/types/geoCheck.js";
|
|||||||
export const MonitorTypes = ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "unknown"] as const;
|
export const MonitorTypes = ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "unknown"] as const;
|
||||||
export type MonitorType = (typeof MonitorTypes)[number];
|
export type MonitorType = (typeof MonitorTypes)[number];
|
||||||
|
|
||||||
|
export const PageSpeedStrategies = ["desktop", "mobile"] as const;
|
||||||
|
export type PageSpeedStrategy = (typeof PageSpeedStrategies)[number];
|
||||||
|
export const DefaultPageSpeedStrategy: PageSpeedStrategy = "desktop";
|
||||||
|
|
||||||
export const GeoCheckSupportedTypes: readonly MonitorType[] = ["http", "ping"] as const;
|
export const GeoCheckSupportedTypes: readonly MonitorType[] = ["http", "ping"] as const;
|
||||||
export const supportsGeoCheck = (type: MonitorType): boolean => GeoCheckSupportedTypes.includes(type);
|
export const supportsGeoCheck = (type: MonitorType): boolean => GeoCheckSupportedTypes.includes(type);
|
||||||
|
|
||||||
@@ -49,6 +53,7 @@ export interface Monitor {
|
|||||||
selectedDisks: string[];
|
selectedDisks: string[];
|
||||||
gameId?: string;
|
gameId?: string;
|
||||||
grpcServiceName?: string;
|
grpcServiceName?: string;
|
||||||
|
strategy?: PageSpeedStrategy;
|
||||||
group: string | null;
|
group: string | null;
|
||||||
geoCheckEnabled?: boolean;
|
geoCheckEnabled?: boolean;
|
||||||
geoCheckLocations?: GeoContinent[];
|
geoCheckLocations?: GeoContinent[];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { booleanCoercion } from "./shared.js";
|
import { booleanCoercion } from "./shared.js";
|
||||||
import { GeoContinents } from "@/types/geoCheck.js";
|
import { GeoContinents } from "@/types/geoCheck.js";
|
||||||
import { MonitorMatchMethods, MonitorTypes } from "@/types/monitor.js";
|
import { MonitorMatchMethods, MonitorTypes, PageSpeedStrategies, DefaultPageSpeedStrategy } from "@/types/monitor.js";
|
||||||
|
|
||||||
export const getMonitorByIdParamValidation = z.object({
|
export const getMonitorByIdParamValidation = z.object({
|
||||||
monitorId: z.string().min(1, "Monitor ID is required"),
|
monitorId: z.string().min(1, "Monitor ID is required"),
|
||||||
@@ -73,6 +73,7 @@ export const createMonitorBodyValidation = z.object({
|
|||||||
matchMethod: z.union([z.enum(MonitorMatchMethods), z.literal("")]).optional(),
|
matchMethod: z.union([z.enum(MonitorMatchMethods), z.literal("")]).optional(),
|
||||||
gameId: z.union([z.string(), z.literal("")]).optional(),
|
gameId: z.union([z.string(), z.literal("")]).optional(),
|
||||||
grpcServiceName: z.union([z.string(), z.literal("")]).default(""),
|
grpcServiceName: z.union([z.string(), z.literal("")]).default(""),
|
||||||
|
strategy: z.enum(PageSpeedStrategies).optional(),
|
||||||
selectedDisks: z.array(z.string()).optional(),
|
selectedDisks: z.array(z.string()).optional(),
|
||||||
group: z.union([z.string().max(50).trim(), z.null(), z.literal("")]).optional(),
|
group: z.union([z.string().max(50).trim(), z.null(), z.literal("")]).optional(),
|
||||||
geoCheckEnabled: z.boolean().optional(),
|
geoCheckEnabled: z.boolean().optional(),
|
||||||
@@ -102,6 +103,7 @@ export const editMonitorBodyValidation = z.object({
|
|||||||
tempAlertThreshold: z.number().optional(),
|
tempAlertThreshold: z.number().optional(),
|
||||||
gameId: z.union([z.string(), z.literal("")]).optional(),
|
gameId: z.union([z.string(), z.literal("")]).optional(),
|
||||||
grpcServiceName: z.union([z.string(), z.literal("")]).optional(),
|
grpcServiceName: z.union([z.string(), z.literal("")]).optional(),
|
||||||
|
strategy: z.enum(PageSpeedStrategies).optional(),
|
||||||
selectedDisks: z.array(z.string()).optional(),
|
selectedDisks: z.array(z.string()).optional(),
|
||||||
group: z.union([z.string().max(50).trim(), z.null(), z.literal("")]).optional(),
|
group: z.union([z.string().max(50).trim(), z.null(), z.literal("")]).optional(),
|
||||||
geoCheckEnabled: z.boolean().optional(),
|
geoCheckEnabled: z.boolean().optional(),
|
||||||
@@ -156,6 +158,7 @@ const importedMonitorSchema = z.object({
|
|||||||
selectedDisks: z.array(z.string()).default([]),
|
selectedDisks: z.array(z.string()).default([]),
|
||||||
gameId: z.union([z.string(), z.literal("")]).optional(),
|
gameId: z.union([z.string(), z.literal("")]).optional(),
|
||||||
grpcServiceName: z.union([z.string(), z.literal("")]).default(""),
|
grpcServiceName: z.union([z.string(), z.literal("")]).default(""),
|
||||||
|
strategy: z.enum(PageSpeedStrategies).default(DefaultPageSpeedStrategy),
|
||||||
group: z.union([z.string().max(50).trim(), z.null()]).default(null),
|
group: z.union([z.string().max(50).trim(), z.null()]).default(null),
|
||||||
geoCheckEnabled: z.boolean().default(false),
|
geoCheckEnabled: z.boolean().default(false),
|
||||||
geoCheckLocations: z.array(z.enum(GeoContinents)).default([]),
|
geoCheckLocations: z.array(z.enum(GeoContinents)).default([]),
|
||||||
|
|||||||
Reference in new issue
Block a user