From efb1642e3c910c641dddf2f70d36dd6b771317c2 Mon Sep 17 00:00:00 2001 From: Matt Visnovsky Date: Wed, 5 Jun 2024 16:09:53 -0600 Subject: [PATCH] Blend json-query and snmp monitors Utilizes the JSON Query library to handle comparison logic. --- server/model/monitor.js | 11 +++---- server/monitor-types/snmp.js | 38 ++++------------------ src/lang/en.json | 6 ++-- src/pages/EditMonitor.vue | 60 ++++++++++++++++++---------------- src/util.js | 45 +++++++++++++++++++++++++- src/util.ts | 62 ++++++++++++++++++++++++++++++++++++ 6 files changed, 152 insertions(+), 70 deletions(-) diff --git a/server/model/monitor.js b/server/model/monitor.js index 55ccfd587..85db89222 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -2,7 +2,7 @@ const dayjs = require("dayjs"); const axios = require("axios"); const { Prometheus } = require("../prometheus"); const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND, - SQL_DATETIME_FORMAT + SQL_DATETIME_FORMAT, evaluateJsonQuery } = require("../../src/util"); const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, setSetting, httpNtlm, radius, grpcQuery, redisPingAsync, mongodbPing, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal @@ -17,7 +17,6 @@ const apicache = require("../modules/apicache"); const { UptimeKumaServer } = require("../uptime-kuma-server"); const { DockerHost } = require("../docker"); const Gamedig = require("gamedig"); -const jsonata = require("jsonata"); const jwt = require("jsonwebtoken"); const crypto = require("crypto"); const { UptimeCalculator } = require("../uptime-calculator"); @@ -610,15 +609,13 @@ class Monitor extends BeanModel { } } - let expression = jsonata(this.jsonPath); + const result = await evaluateJsonQuery(data, this.jsonPath, this.jsonPathOperator, this.expectedValue); - let result = await expression.evaluate(data); - - if (result.toString() === this.expectedValue) { + if (result) { 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: [" + result + "]"); + throw new Error(`${bean.msg}, but value is not equal to expected value, value was: [${result}]`); } } diff --git a/server/monitor-types/snmp.js b/server/monitor-types/snmp.js index 2ca81ff71..1cad575d8 100644 --- a/server/monitor-types/snmp.js +++ b/server/monitor-types/snmp.js @@ -1,7 +1,6 @@ const { MonitorType } = require("./monitor-type"); -const { UP, DOWN, log } = require("../../src/util"); +const { UP, DOWN, log, evaluateJsonQuery } = require("../../src/util"); const snmp = require("net-snmp"); -const jsonata = require("jsonata"); class SNMPMonitorType extends MonitorType { name = "snmp"; @@ -45,46 +44,23 @@ 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; - // Check if inputs are numeric. If not, re-parse as strings. This ensures comparisons are handled correctly. - const expectedValue = isNaN(monitor.expectedValue) ? monitor.expectedValue.toString() : parseFloat(monitor.expectedValue); - let snmpResponse = isNaN(value) ? value.toString() : parseFloat(value); + const result = await evaluateJsonQuery(value, monitor.jsonPath, monitor.jsonPathOperator, monitor.expectedValue); - let jsonQueryExpression; - switch (monitor.jsonPathOperator) { - case ">": - case ">=": - case "<": - case "<=": - jsonQueryExpression = `$.value ${monitor.jsonPathOperator} $.control`; - break; - case "==": - jsonQueryExpression = "$string($.value) = $string($.control)"; - break; - case "contains": - jsonQueryExpression = "$contains($string($.value), $string($.control))"; - break; - default: - throw new Error(`Invalid condition ${monitor.jsonPathOperator}`); - } - - const expression = jsonata(jsonQueryExpression); - const evaluation = await expression.evaluate({ - value: snmpResponse, - control: expectedValue - }); heartbeat.status = result ? UP : DOWN; - heartbeat.msg = `SNMP value ${result ? "passes" : "does not pass"} comparison: ${snmpValue} ${monitor.snmpCondition} ${snmpControlValue}`; + heartbeat.msg = `SNMP value ${result ? "passes" : "does not pass"} `; + heartbeat.msg += (monitor.jsonPathOperator === "custom") + ? `custom query. Query result: ${result}. Expected Value: ${monitor.expectedValue}.` + : `comparison: ${value.toString()} ${monitor.jsonPathOperator} ${monitor.expectedValue}.`; } catch (err) { heartbeat.status = DOWN; - heartbeat.msg = `SNMP Error: ${err.message}`; + heartbeat.msg = `Error: ${err.message}`; } finally { if (session) { session.close(); } } } - } module.exports = { diff --git a/src/lang/en.json b/src/lang/en.json index 184ae3c51..d95c3dd4e 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -59,10 +59,10 @@ "Keyword": "Keyword", "Invert Keyword": "Invert Keyword", "Expected Value": "Expected Value", - "Json Query": "Json Query", + "Custom Json Query Expression": "Custom Json Query Expression", "Friendly Name": "Friendly Name", "URL": "URL", - "Hostname": "Hostname", + "Hostname or IP Address": "Hostname or IP Address", "Host URL": "Host URL", "locally configured mail transfer agent": "locally configured mail transfer agent", "Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "Either enter the hostname of the server you want to connect to or {localhost} if you intend to use a {local_mta}", @@ -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": "Do a json Query against the response and check for expected value (Return value will get converted into string for comparison). Check out {0} for the documentation about the query language. A playground can be found {1}.", + "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. Access the response value using $.value and the expected value using $.control. Refer to {0} for detailed documentation on the query language or experiment with queries using the {1}.", "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 fa49d8579..6db318bd7 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -171,21 +171,6 @@ - -
- - - - - jsonata.org - {{ $t('here') }} - -
- - - -
-
@@ -251,7 +236,7 @@
- +
@@ -278,6 +263,29 @@
+ + +
+ + + +
+
+ + + jsonata.org + {{ $t('playground') }} + + +
+
@@ -288,6 +296,7 @@ +
@@ -298,18 +307,6 @@
-
- - -
-