From f685c9cd5d6059b2c930bbe0be0bb132233cc71e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 30 Jan 2026 20:21:31 +0000 Subject: [PATCH] useAdvncedMatching --- client/src/Hooks/useMonitorForm.ts | 2 + client/src/Pages/CreateMonitor/index.tsx | 63 ++++++++++++++----- client/src/Types/Monitor.ts | 1 + client/src/Validation/monitor.ts | 1 + client/src/locales/en.json | 4 +- server/src/db/models/Monitor.ts | 4 ++ .../monitors/MongoMonitorsRepository.ts | 2 + .../service/infrastructure/networkService.ts | 4 +- server/src/types/monitor.ts | 1 + server/src/validation/joi.ts | 2 + 10 files changed, 66 insertions(+), 18 deletions(-) diff --git a/client/src/Hooks/useMonitorForm.ts b/client/src/Hooks/useMonitorForm.ts index 50dc7b920..153cf9f19 100644 --- a/client/src/Hooks/useMonitorForm.ts +++ b/client/src/Hooks/useMonitorForm.ts @@ -33,6 +33,7 @@ export const useMonitorForm = ({ type: "http", url: data?.url || "", ignoreTlsErrors: data?.ignoreTlsErrors || false, + useAdvancedMatching: data?.useAdvancedMatching || false, matchMethod: data?.matchMethod || "", expectedValue: data?.expectedValue || "", jsonPath: data?.jsonPath || "", @@ -95,6 +96,7 @@ export const useMonitorForm = ({ type: "http", url: "", ignoreTlsErrors: false, + useAdvancedMatching: false, matchMethod: "", expectedValue: "", jsonPath: "", diff --git a/client/src/Pages/CreateMonitor/index.tsx b/client/src/Pages/CreateMonitor/index.tsx index 313b744cd..d7d9a645e 100644 --- a/client/src/Pages/CreateMonitor/index.tsx +++ b/client/src/Pages/CreateMonitor/index.tsx @@ -1,5 +1,5 @@ import { useMemo } from "react"; -import { useEffect, useState } from "react"; +import { useEffect } from "react"; import { useParams, useLocation, useNavigate } from "react-router"; import { useForm, Controller } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; @@ -7,9 +7,10 @@ import { useTheme } from "@mui/material"; import Stack from "@mui/material/Stack"; import RadioGroup from "@mui/material/RadioGroup"; import FormControl from "@mui/material/FormControl"; -import { useTranslation } from "react-i18next"; +import { Trans, useTranslation } from "react-i18next"; import MenuItem from "@mui/material/MenuItem"; import Typography from "@mui/material/Typography"; +import Link from "@mui/material/Link"; import Divider from "@mui/material/Divider"; import IconButton from "@mui/material/IconButton"; import FormControlLabel from "@mui/material/FormControlLabel"; @@ -163,7 +164,7 @@ const CreateMonitorPage = () => { const watchedType = watch("type") as MonitorType; - const [showAdvancedSettings, setShowAdvancedSettings] = useState(false); + const watchedUseAdvancedMatching = watch("useAdvancedMatching") as boolean; useEffect(() => { clearErrors(); @@ -550,16 +551,22 @@ const CreateMonitorPage = () => { subtitle={t("pages.createMonitor.form.advanced.description")} rightContent={ - setShowAdvancedSettings(e.target.checked)} + ( + field.onChange(e.target.checked)} + /> + } + label={t("advancedMatching")} /> - } - label={t("advancedMatching")} + )} /> - {showAdvancedSettings && ( + {watchedUseAdvancedMatching && ( { )} @@ -583,7 +594,9 @@ const CreateMonitorPage = () => { { )} /> + + + ), + }} + /> + )} diff --git a/client/src/Types/Monitor.ts b/client/src/Types/Monitor.ts index 94b3d83d7..ddf44e56f 100644 --- a/client/src/Types/Monitor.ts +++ b/client/src/Types/Monitor.ts @@ -35,6 +35,7 @@ export interface Monitor { statusWindowThreshold: number; type: MonitorType; ignoreTlsErrors: boolean; + useAdvancedMatching: boolean; jsonPath?: string; expectedValue?: string; matchMethod?: MonitorMatchMethod; diff --git a/client/src/Validation/monitor.ts b/client/src/Validation/monitor.ts index 39fccafbd..68a15fbec 100644 --- a/client/src/Validation/monitor.ts +++ b/client/src/Validation/monitor.ts @@ -27,6 +27,7 @@ const httpSchema = baseSchema.extend({ type: z.literal("http"), url: urlSchema, ignoreTlsErrors: z.boolean(), + useAdvancedMatching: z.boolean(), matchMethod: z.enum(["equal", "include", "regex", ""]).optional(), expectedValue: z.string().optional(), jsonPath: z.string().optional(), diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 80cb2e206..123a1f146 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -2,6 +2,8 @@ "common": { "appName": "Checkmate", "monitoringAgentName": "Capture", + "see": "See", + "forDocumentation": "for query language documentation.", "breadcrumbs": { "home": "Home", "details": "Details" @@ -322,7 +324,7 @@ }, "jsonPath": { "label": "JSONPath expression", - "description": "This expression will be evaluated against the response JSON data and the result will be used to match against the expected value." + "description": "This expression will be evaluated against the response JSON data and the result will be used to match against the expected value. See jmespath.org for query language documentation." } } } diff --git a/server/src/db/models/Monitor.ts b/server/src/db/models/Monitor.ts index 24c8ef0bf..d4cc9a872 100644 --- a/server/src/db/models/Monitor.ts +++ b/server/src/db/models/Monitor.ts @@ -92,6 +92,10 @@ const MonitorSchema = new Schema( type: Boolean, default: false, }, + useAdvancedMatching: { + type: Boolean, + default: false, + }, jsonPath: { type: String, }, diff --git a/server/src/repositories/monitors/MongoMonitorsRepository.ts b/server/src/repositories/monitors/MongoMonitorsRepository.ts index 9cb519303..9b30e5ac2 100644 --- a/server/src/repositories/monitors/MongoMonitorsRepository.ts +++ b/server/src/repositories/monitors/MongoMonitorsRepository.ts @@ -290,6 +290,7 @@ class MongoMonitorsRepository implements IMonitorsRepository { statusWindowThreshold: doc.statusWindowThreshold, type: doc.type, ignoreTlsErrors: doc.ignoreTlsErrors, + useAdvancedMatching: doc.useAdvancedMatching ?? false, jsonPath: doc.jsonPath ?? undefined, expectedValue: doc.expectedValue ?? undefined, matchMethod: doc.matchMethod ?? undefined, @@ -374,6 +375,7 @@ class MongoMonitorsRepository implements IMonitorsRepository { statusWindowThreshold: doc.statusWindowThreshold, type: doc.type, ignoreTlsErrors: doc.ignoreTlsErrors, + useAdvancedMatching: doc.useAdvancedMatching ?? false, jsonPath: doc.jsonPath ?? undefined, expectedValue: doc.expectedValue ?? undefined, matchMethod: doc.matchMethod ?? undefined, diff --git a/server/src/service/infrastructure/networkService.ts b/server/src/service/infrastructure/networkService.ts index 7db8fc6e2..92d269808 100644 --- a/server/src/service/infrastructure/networkService.ts +++ b/server/src/service/infrastructure/networkService.ts @@ -255,7 +255,7 @@ class NetworkService implements INetworkService { } private async requestHttp(monitor: Monitor): Promise { - const { url, secret, id, teamId, type, ignoreTlsErrors, jsonPath, matchMethod, expectedValue } = monitor; + const { url, secret, id, teamId, type, ignoreTlsErrors, useAdvancedMatching, jsonPath, matchMethod, expectedValue } = monitor; const httpResponse = this.buildStatusResponse({ monitor, overrides: { @@ -305,7 +305,7 @@ class NetworkService implements INetworkService { timings: response.timings, }); - if (!expectedValue && !jsonPath) { + if (!useAdvancedMatching) { return httpResponse; } diff --git a/server/src/types/monitor.ts b/server/src/types/monitor.ts index 62ced091d..fe6ac8c8a 100644 --- a/server/src/types/monitor.ts +++ b/server/src/types/monitor.ts @@ -26,6 +26,7 @@ export interface Monitor { statusWindowThreshold: number; type: MonitorType; ignoreTlsErrors: boolean; + useAdvancedMatching: boolean; jsonPath?: string; expectedValue?: string; matchMethod?: MonitorMatchMethod; diff --git a/server/src/validation/joi.ts b/server/src/validation/joi.ts index 581d2afd2..926d3b0fe 100755 --- a/server/src/validation/joi.ts +++ b/server/src/validation/joi.ts @@ -158,6 +158,7 @@ const createMonitorBodyValidation = joi.object({ statusWindowThreshold: joi.number().min(1).max(100).default(60), url: joi.string().required(), ignoreTlsErrors: joi.boolean().default(false), + useAdvancedMatching: joi.boolean().default(false), port: joi.number(), isActive: joi.boolean(), interval: joi.number(), @@ -193,6 +194,7 @@ const editMonitorBodyValidation = joi.object({ notifications: joi.array().items(joi.string()), secret: joi.string(), ignoreTlsErrors: joi.boolean(), + useAdvancedMatching: joi.boolean(), jsonPath: joi.string().allow(""), expectedValue: joi.string().allow(""), matchMethod: joi.string().allow(null, ""),