diff --git a/server/model/monitor.js b/server/model/monitor.js
index bd155128f..2ef7aae9e 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -600,23 +600,12 @@ class Monitor extends BeanModel {
} else if (this.type === "json-query") {
let data = res.data;
- // convert data to object
- if (typeof data === "string" && res.headers["content-type"] !== "application/json") {
- try {
- data = JSON.parse(data);
- } catch (_) {
- // Failed to parse as JSON, just process it as a string
- }
- }
+ const { status, response } = await evaluateJsonQuery(data, this.jsonPath, this.jsonPathOperator, this.expectedValue);
- const { status, evaluation } = await evaluateJsonQuery(data, this.jsonPath, this.jsonPathOperator, this.expectedValue);
+ bean.status = status ? UP : DOWN;
+ bean.msg = `JSON query ${status ? "passes" : "does not pass"} `;
+ bean.msg += `comparison: ${response} ${this.jsonPathOperator} ${this.expectedValue}.`;
- if (status) {
- bean.msg += ", expected value is found";
- bean.status = UP;
- } else {
- throw new Error(`${bean.msg}, but value is not equal to expected value, value was: [${evaluation}]`);
- }
}
} else if (this.type === "port") {
diff --git a/server/monitor-types/snmp.js b/server/monitor-types/snmp.js
index f3f67bc46..5168cc9ad 100644
--- a/server/monitor-types/snmp.js
+++ b/server/monitor-types/snmp.js
@@ -44,13 +44,11 @@ class SNMPMonitorType extends MonitorType {
// We restrict querying to one OID per monitor, therefore `varbinds[0]` will always contain the value we're interested in.
const value = varbinds[0].value;
- const { status, evaluation } = await evaluateJsonQuery(value, monitor.jsonPath, monitor.jsonPathOperator, monitor.expectedValue);
+ const { status, response } = await evaluateJsonQuery(value, monitor.jsonPath, monitor.jsonPathOperator, monitor.expectedValue);
heartbeat.status = status ? UP : DOWN;
heartbeat.msg = `SNMP value ${status ? "passes" : "does not pass"} `;
- heartbeat.msg += (monitor.jsonPathOperator === "custom")
- ? `custom query. Query result: ${evaluation}. Expected Value: ${monitor.expectedValue}.`
- : `comparison: ${value.toString()} ${monitor.jsonPathOperator} ${monitor.expectedValue}.`;
+ heartbeat.msg += `comparison: ${response} ${monitor.jsonPathOperator} ${monitor.expectedValue}.`;
} catch (err) {
heartbeat.status = DOWN;
diff --git a/src/lang/en.json b/src/lang/en.json
index d1ed2f1b0..1b2de3146 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -59,7 +59,7 @@
"Keyword": "Keyword",
"Invert Keyword": "Invert Keyword",
"Expected Value": "Expected Value",
- "Custom Json Query Expression": "Custom Json Query Expression",
+ "Json Query Expression": "Json Query Expression",
"Friendly Name": "Friendly Name",
"URL": "URL",
"Hostname or IP Address": "Hostname or IP Address",
@@ -577,7 +577,7 @@
"notificationDescription": "Notifications must be assigned to a monitor to function.",
"keywordDescription": "Search keyword in plain HTML or JSON response. The search is case-sensitive.",
"invertKeywordDescription": "Look for the keyword to be absent rather than present.",
- "jsonQueryDescription": "Use JSON query to parse and extract specific data from the server's JSON response. Compare the evaluated query against the expected value after converting it into a string. Refer to {0} for detailed documentation on the query language or experiment with queries using the {1}.",
+ "jsonQueryDescription": "Parse and extract specific data from the server's JSON response using JSON query or use \"$\" for the raw response, if not expecting JSON. The result is then compared to the expected value, as strings. See {0} for documentation and use {1} to experiment with queries.",
"backupDescription": "You can backup all monitors and notifications into a JSON file.",
"backupDescription2": "Note: history and event data is not included.",
"backupDescription3": "Sensitive data such as notification tokens are included in the export file; please store export securely.",
diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index 6db318bd7..5f64c4c96 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -277,13 +277,13 @@
-
@@ -1330,10 +1330,13 @@ message HealthCheckResponse {
this.monitor.snmpVersion = "1";
}
+ // Set default jsonPath
+ if (!this.monitor.jsonPath) {
+ this.monitor.jsonPath = "$";
+ }
+
// Set default condition for for jsonPathOperator
- if (this.monitor.type === "json-query") {
- this.monitor.jsonPathOperator = "custom";
- } else {
+ if (!this.monitor.jsonPathOperator) {
this.monitor.jsonPathOperator = "==";
}
diff --git a/src/util.js b/src/util.js
index 5340ff7c5..0a576a9a4 100644
--- a/src/util.js
+++ b/src/util.js
@@ -398,7 +398,6 @@ function intHash(str, length = 10) {
}
exports.intHash = intHash;
async function evaluateJsonQuery(data, jsonPath, jsonPathOperator, expectedValue) {
- const expected = isNaN(expectedValue) ? expectedValue.toString() : parseFloat(expectedValue);
let response;
try {
response = JSON.parse(data);
@@ -406,46 +405,43 @@ async function evaluateJsonQuery(data, jsonPath, jsonPathOperator, expectedValue
catch (_a) {
response = typeof data === "number" || typeof data === "object" ? data : data.toString();
}
+ if (jsonPath && typeof data === "object") {
+ try {
+ response = await jsonata(jsonPath).evaluate(response);
+ }
+ catch (err) {
+ throw new Error(`Error evaluating JSON query: ${err.message}`);
+ }
+ }
let jsonQueryExpression;
switch (jsonPathOperator) {
case ">":
case ">=":
case "<":
case "<=":
- jsonQueryExpression = `$.value ${jsonPathOperator} $.control`;
+ case "!=":
+ jsonQueryExpression = `$.value ${jsonPathOperator} $.expected`;
break;
case "==":
- jsonQueryExpression = "$string($.value) = $string($.control)";
+ jsonQueryExpression = "$string($.value) = $string($.expected)";
break;
case "contains":
- jsonQueryExpression = "$contains($string($.value), $string($.control))";
- break;
- case "custom":
- jsonQueryExpression = jsonPath;
+ jsonQueryExpression = "$contains($string($.value), $string($.expected))";
break;
default:
throw new Error(`Invalid condition ${jsonPathOperator}`);
}
const expression = jsonata(jsonQueryExpression);
- let evaluation;
- if (jsonPathOperator === "custom") {
- evaluation = await expression.evaluate(response);
- }
- else {
- evaluation = await expression.evaluate({
- value: response,
- control: expectedValue
- });
- }
- if (evaluation === undefined) {
+ const status = await expression.evaluate({
+ value: response.toString(),
+ expected: expectedValue.toString()
+ });
+ if (response === undefined || status === undefined) {
throw new Error("Query evaluation returned undefined. Check your query syntax and the structure of the response data.");
}
- const status = (jsonPathOperator === "custom")
- ? evaluation.toString() === expected.toString()
- : evaluation;
return {
status,
- evaluation
+ response
};
}
exports.evaluateJsonQuery = evaluateJsonQuery;
diff --git a/src/util.ts b/src/util.ts
index 5af0088e4..94765f187 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -654,10 +654,8 @@ export function intHash(str : string, length = 10) : number {
* @returns An object containing the status and the evaluation result.
* @throws Error if the evaluation returns undefined.
*/
-export async function evaluateJsonQuery(data: any, jsonPath: string, jsonPathOperator: string, expectedValue: any): Promise<{ status: boolean; evaluation: any }> {
- // Check if inputs are numeric. If not, re-parse as strings. This ensures comparisons are handled correctly.
- const expected = isNaN(expectedValue) ? expectedValue.toString() : parseFloat(expectedValue);
-
+export async function evaluateJsonQuery(data: any, jsonPath: string, jsonPathOperator: string, expectedValue: any): Promise<{ status: boolean; response: any }> {
+ // Attempt to parse data as JSON; if unsuccessful, handle based on data type.
let response: any;
try {
response = JSON.parse(data);
@@ -665,22 +663,31 @@ export async function evaluateJsonQuery(data: any, jsonPath: string, jsonPathOpe
response = typeof data === "number" || typeof data === "object" ? data : data.toString();
}
+ // If a JSON path is provided, pre-evaluate the data using it.
+ if (jsonPath && typeof data === "object") {
+ try {
+ response = await jsonata(jsonPath).evaluate(response);
+ } catch (err: any) {
+ throw new Error(`Error evaluating JSON query: ${err.message}`);
+ }
+ }
+
+ // Perform the comparison logic using the chosen operator
+ // Perform the comparison logic using the chosen operator
let jsonQueryExpression;
switch (jsonPathOperator) {
case ">":
case ">=":
case "<":
case "<=":
- jsonQueryExpression = `$.value ${jsonPathOperator} $.control`;
+ case "!=":
+ jsonQueryExpression = `$.value ${jsonPathOperator} $.expected`;
break;
case "==":
- jsonQueryExpression = "$string($.value) = $string($.control)";
+ jsonQueryExpression = "$string($.value) = $string($.expected)";
break;
case "contains":
- jsonQueryExpression = "$contains($string($.value), $string($.control))";
- break;
- case "custom":
- jsonQueryExpression = jsonPath;
+ jsonQueryExpression = "$contains($string($.value), $string($.expected))";
break;
default:
throw new Error(`Invalid condition ${jsonPathOperator}`);
@@ -688,27 +695,17 @@ export async function evaluateJsonQuery(data: any, jsonPath: string, jsonPathOpe
// Evaluate the JSON Query Expression
const expression = jsonata(jsonQueryExpression);
+ const status = await expression.evaluate({
+ value: response.toString(),
+ expected: expectedValue.toString()
+ });
- let evaluation;
- if (jsonPathOperator === "custom") {
- evaluation = await expression.evaluate(response);
- } else {
- evaluation = await expression.evaluate({
- value: response,
- control: expectedValue
- });
- }
-
- if (evaluation === undefined) {
+ if (response === undefined || status === undefined) {
throw new Error("Query evaluation returned undefined. Check your query syntax and the structure of the response data.");
}
- const status = (jsonPathOperator === "custom")
- ? evaluation.toString() === expected.toString()
- : evaluation;
-
return {
- status,
- evaluation
+ status, // The evaluation of the json query
+ response // The response from the server or result from initial json-query evaluation
};
}