diff --git a/README.md b/README.md
index 3f8a054036..c7aa4150f6 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,7 @@ It is a temporary live demo, all data will be deleted after 10 minutes. Use the
## ⭐ Features
-* Monitoring uptime for HTTP(s) / TCP / HTTP(s) Keyword / Ping / DNS Record / Push / Steam Game Server / Docker Containers
+* Monitoring uptime for HTTP(s) / TCP / HTTP(s) Keyword / HTTP(s) Json Query / Ping / DNS Record / Push / Steam Game Server / Docker Containers
* Fancy, Reactive, Fast UI/UX
* Notifications via Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP), and [90+ notification services, click here for the full list](https://github.com/louislam/uptime-kuma/tree/master/src/components/notifications)
* 20 second intervals
@@ -49,14 +49,14 @@ Uptime Kuma is now running on http://localhost:3001
### 💪🏻 Non-Docker
-Requirements:
+Requirements:
- Platform
- - ✅ Major Linux distros such as Debian, Ubuntu, CentOS, Fedora and ArchLinux etc.
+ - ✅ Major Linux distros such as Debian, Ubuntu, CentOS, Fedora and ArchLinux etc.
- ✅ Windows 10 (x64), Windows Server 2012 R2 (x64) or higher
- ❌ Replit / Heroku
- [Node.js](https://nodejs.org/en/download/) 14 / 16 / 18 (20 is not supported)
- [npm](https://docs.npmjs.com/cli/) >= 7
-- [Git](https://git-scm.com/downloads)
+- [Git](https://git-scm.com/downloads)
- [pm2](https://pm2.keymetrics.io/) - For running Uptime Kuma in the background
```bash
@@ -71,7 +71,7 @@ npm run setup
node server/server.js
# (Recommended) Option 2. Run in background using PM2
-# Install PM2 if you don't have it:
+# Install PM2 if you don't have it:
npm install pm2 -g && pm2 install pm2-logrotate
# Start Server
diff --git a/db/patch-added-json-query.sql b/db/patch-added-json-query.sql
new file mode 100644
index 0000000000..d5b7f1a8f4
--- /dev/null
+++ b/db/patch-added-json-query.sql
@@ -0,0 +1,10 @@
+-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
+BEGIN TRANSACTION;
+
+ALTER TABLE monitor
+ ADD json_path TEXT;
+
+ALTER TABLE monitor
+ ADD expected_value VARCHAR(255);
+
+COMMIT;
diff --git a/package-lock.json b/package-lock.json
index 04b4ea15b2..ad26942e28 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -38,6 +38,7 @@
"https-proxy-agent": "~5.0.1",
"iconv-lite": "~0.6.3",
"jsesc": "~3.0.2",
+ "jsonata": "^2.0.3",
"jsonwebtoken": "~9.0.0",
"jwt-decode": "~3.1.2",
"limiter": "~2.1.0",
@@ -12921,6 +12922,14 @@
"node": ">=6"
}
},
+ "node_modules/jsonata": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/jsonata/-/jsonata-2.0.3.tgz",
+ "integrity": "sha512-Up2H81MUtjqI/dWwWX7p4+bUMfMrQJVMN/jW6clFMTiYP528fBOBNtRu944QhKTs3+IsVWbgMeUTny5fw2VMUA==",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
diff --git a/package.json b/package.json
index ff5c0dc011..fadd0f0dfd 100644
--- a/package.json
+++ b/package.json
@@ -97,6 +97,7 @@
"https-proxy-agent": "~5.0.1",
"iconv-lite": "~0.6.3",
"jsesc": "~3.0.2",
+ "jsonata": "^2.0.3",
"jsonwebtoken": "~9.0.0",
"jwt-decode": "~3.1.2",
"limiter": "~2.1.0",
diff --git a/server/database.js b/server/database.js
index 1348e10ce1..3e2d0350de 100644
--- a/server/database.js
+++ b/server/database.js
@@ -72,6 +72,7 @@ class Database {
"patch-maintenance-cron.sql": true,
"patch-add-parent-monitor.sql": true,
"patch-add-invert-keyword.sql": true,
+ "patch-added-json-query.sql": true,
};
/**
diff --git a/server/model/monitor.js b/server/model/monitor.js
index b3d8bec2a4..fb71f7546e 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -20,6 +20,7 @@ const { CacheableDnsHttpAgent } = require("../cacheable-dns-http-agent");
const { DockerHost } = require("../docker");
const { UptimeCacheList } = require("../uptime-cache-list");
const Gamedig = require("gamedig");
+const jsonata = require("jsonata");
const jwt = require("jsonwebtoken");
/**
@@ -126,6 +127,8 @@ class Monitor extends BeanModel {
radiusCallingStationId: this.radiusCallingStationId,
game: this.game,
httpBodyEncoding: this.httpBodyEncoding,
+ jsonPath: this.jsonPath,
+ expectedValue: this.expectedValue,
screenshot,
};
@@ -320,7 +323,7 @@ class Monitor extends BeanModel {
bean.msg = "Group empty";
}
- } else if (this.type === "http" || this.type === "keyword") {
+ } else if (this.type === "http" || this.type === "keyword" || this.type === "json-query") {
// Do not do any queries/high loading things before the "bean.ping"
let startTime = dayjs().valueOf();
@@ -448,7 +451,7 @@ class Monitor extends BeanModel {
if (this.type === "http") {
bean.status = UP;
- } else {
+ } else if (this.type === "keyword") {
let data = res.data;
@@ -470,6 +473,24 @@ class Monitor extends BeanModel {
(keywordFound ? "present" : "not") + " in [" + data + "]");
}
+ } else if (this.type === "json-query") {
+ let data = res.data;
+
+ // convert data to object
+ if (typeof data === "string") {
+ data = JSON.parse(data);
+ }
+
+ let expression = jsonata(this.jsonPath);
+
+ let result = await expression.evaluate(data);
+
+ if (result.toString() === this.expectedValue) {
+ 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 + "]");
+ }
}
} else if (this.type === "port") {
diff --git a/server/notification-providers/smtp.js b/server/notification-providers/smtp.js
index aae0e46c74..accee9ea7e 100644
--- a/server/notification-providers/smtp.js
+++ b/server/notification-providers/smtp.js
@@ -67,7 +67,7 @@ class SMTP extends NotificationProvider {
if (monitorJSON !== null) {
monitorName = monitorJSON["name"];
- if (monitorJSON["type"] === "http" || monitorJSON["type"] === "keyword") {
+ if (monitorJSON["type"] === "http" || monitorJSON["type"] === "keyword" || monitorJSON["type"] === "json-query") {
monitorHostnameOrURL = monitorJSON["url"];
} else {
monitorHostnameOrURL = monitorJSON["hostname"];
diff --git a/server/server.js b/server/server.js
index c35af7fc16..b9d618f51f 100644
--- a/server/server.js
+++ b/server/server.js
@@ -748,6 +748,8 @@ let needSetup = false;
bean.radiusCallingStationId = monitor.radiusCallingStationId;
bean.radiusSecret = monitor.radiusSecret;
bean.httpBodyEncoding = monitor.httpBodyEncoding;
+ bean.expectedValue = monitor.expectedValue;
+ bean.jsonPath = monitor.jsonPath;
bean.validate();
diff --git a/src/components/MonitorSettingDialog.vue b/src/components/MonitorSettingDialog.vue
index a6976853c0..d8e9da1a76 100644
--- a/src/components/MonitorSettingDialog.vue
+++ b/src/components/MonitorSettingDialog.vue
@@ -104,7 +104,7 @@ export default {
// We must check if there are any elements in monitorList to
// prevent undefined errors if it hasn't been loaded yet
if (this.$parent.editMode && ignoreSendUrl && Object.keys(this.$root.monitorList).length) {
- return this.$root.monitorList[monitor.element.id].type === "http" || this.$root.monitorList[monitor.element.id].type === "keyword";
+ return this.$root.monitorList[monitor.element.id].type === "http" || this.$root.monitorList[monitor.element.id].type === "keyword" || this.$root.monitorList[monitor.element.id].type === "json-query";
}
return monitor.element.sendUrl && monitor.element.url && monitor.element.url !== "https://" && !this.editMode;
},
diff --git a/src/components/PublicGroupList.vue b/src/components/PublicGroupList.vue
index 00f38c5b9e..4bd4a29520 100644
--- a/src/components/PublicGroupList.vue
+++ b/src/components/PublicGroupList.vue
@@ -150,7 +150,7 @@ export default {
// We must check if there are any elements in monitorList to
// prevent undefined errors if it hasn't been loaded yet
if (this.$parent.editMode && ignoreSendUrl && Object.keys(this.$root.monitorList).length) {
- return this.$root.monitorList[monitor.element.id].type === "http" || this.$root.monitorList[monitor.element.id].type === "keyword";
+ return this.$root.monitorList[monitor.element.id].type === "http" || this.$root.monitorList[monitor.element.id].type === "keyword" || this.$root.monitorList[monitor.element.id].type === "json-query";
}
return monitor.element.sendUrl && monitor.element.url && monitor.element.url !== "https://" && !this.editMode;
},
diff --git a/src/lang/en.json b/src/lang/en.json
index 7a686cd5dd..e656b60fc0 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -52,6 +52,8 @@
"Monitor Type": "Monitor Type",
"Keyword": "Keyword",
"Invert Keyword": "Invert Keyword",
+ "Expected Value": "Expected Value",
+ "Json Query": "Json Query",
"Friendly Name": "Friendly Name",
"URL": "URL",
"Hostname": "Hostname",
@@ -523,6 +525,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 jsonata.org for the documentation about the query language. A playground can be found here.",
"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/Details.vue b/src/pages/Details.vue
index 4a647d32ef..9504cfd81f 100644
--- a/src/pages/Details.vue
+++ b/src/pages/Details.vue
@@ -8,7 +8,7 @@
- {{ filterPassword(monitor.url) }}
+ {{ filterPassword(monitor.url) }}
TCP Port {{ monitor.hostname }}:{{ monitor.port }}
Ping: {{ monitor.hostname }}
@@ -17,6 +17,12 @@
{{ monitor.keyword }}
↧
+
+
+ {{ $t("Json Query") }}: {{ monitor.jsonPath }}
+
+ {{ $t("Expected Value") }}: {{ monitor.expectedValue }}
+
[{{ monitor.dns_resolve_type }}] {{ monitor.hostname }}
{{ $t("Last Result") }}: {{ monitor.dns_last_result }}
@@ -434,7 +440,7 @@ export default {
translationPrefix = "Avg. ";
}
- if (this.monitor.type === "http" || this.monitor.type === "keyword") {
+ if (this.monitor.type === "http" || this.monitor.type === "keyword" || this.monitor.type === "json-query") {
return this.$t(translationPrefix + "Response");
}
diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index de93f043bd..1ce6227932 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -27,6 +27,9 @@
+
@@ -97,7 +100,7 @@
-
+
+
+
+ {{ $t("Advanced") }}
-