Skip to content

Commit

Permalink
✨ feat: json-query monitor added (#3253)
Browse files Browse the repository at this point in the history
* ✨ feat: json-query monitor added

Signed-off-by: Muhammed Hussein Karimi <info@karimi.dev>

* 🐛 fix: import warning error

Signed-off-by: Muhammed Hussein Karimi <info@karimi.dev>

* 🐛 fix: br tag and remove comment

Signed-off-by: Muhammed Hussein Karimi <info@karimi.dev>

* 🐛 fix: supporting compare string with other types

Signed-off-by: Muhammed Hussein Karimi <info@karimi.dev>

* 🐛 fix: switch to a better lib for json query

Signed-off-by: Muhammed Hussein Karimi <info@karimi.dev>

* 🐛 fix: better description on json query and using `v-html` in jsonQueryDescription element to fix `a` tags

Signed-off-by: Muhammed Hussein Karimi <info@karimi.dev>

* 🐛 fix: result variable in error message

Signed-off-by: Muhammed Hussein Karimi <info@karimi.dev>

* 🐛 fix: typos in json query description

Co-authored-by: Frank Elsinga <frank@elsinga.de>

* 📝 docs: `HTTP(s) Json Query` added to monitor list in `README.md`

Signed-off-by: Muhammed Hussein Karimi <info@karimi.dev>

* 🐛 fix: needed white space in `README.md`

Co-authored-by: Frank Elsinga <frank@elsinga.de>

* Nostr dm notifications (#3051)

* Add nostr DM notification provider

* require crypto for node 18 compatibility

* remove whitespace

Co-authored-by: Frank Elsinga <frank@elsinga.de>

* move closer to where it is used

* simplify success or failure logic

* don't clobber the non-alert msg

* Update server/notification-providers/nostr.js

Co-authored-by: Frank Elsinga <frank@elsinga.de>

* polyfills required for node <= 18

* resolve linter warnings

* missing comma

---------

Co-authored-by: Frank Elsinga <frank@elsinga.de>

* Drop nostr

* Rebuild package-lock.json

* Lint

---------

Signed-off-by: Muhammed Hussein Karimi <info@karimi.dev>
Co-authored-by: Frank Elsinga <frank@elsinga.de>
Co-authored-by: zappityzap <128872140+zappityzap@users.noreply.github.com>
Co-authored-by: Louis Lam <louislam@users.noreply.github.com>
  • Loading branch information
4 people authored Jul 13, 2023
1 parent e7d1b4e commit 6bece87
Show file tree
Hide file tree
Showing 13 changed files with 88 additions and 18 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
10 changes: 10 additions & 0 deletions db/patch-added-json-query.sql
Original file line number Diff line number Diff line change
@@ -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;
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions server/database.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

/**
Expand Down
25 changes: 23 additions & 2 deletions server/model/monitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");

/**
Expand Down Expand Up @@ -126,6 +127,8 @@ class Monitor extends BeanModel {
radiusCallingStationId: this.radiusCallingStationId,
game: this.game,
httpBodyEncoding: this.httpBodyEncoding,
jsonPath: this.jsonPath,
expectedValue: this.expectedValue,
screenshot,
};

Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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;

Expand All @@ -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") {
Expand Down
2 changes: 1 addition & 1 deletion server/notification-providers/smtp.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"];
Expand Down
2 changes: 2 additions & 0 deletions server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
2 changes: 1 addition & 1 deletion src/components/MonitorSettingDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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;
},
Expand Down
2 changes: 1 addition & 1 deletion src/components/PublicGroupList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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;
},
Expand Down
3 changes: 3 additions & 0 deletions src/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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 <a href='https://jsonata.org/'>jsonata.org</a> for the documentation about the query language. A playground can be found <a href='https://try.jsonata.org/'>here</a>.",
"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.",
Expand Down
10 changes: 8 additions & 2 deletions src/pages/Details.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<Tag v-for="tag in monitor.tags" :key="tag.id" :item="tag" :size="'sm'" />
</div>
<p class="url">
<a v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'mp-health' " :href="monitor.url" target="_blank" rel="noopener noreferrer">{{ filterPassword(monitor.url) }}</a>
<a v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' || monitor.type === 'mp-health' " :href="monitor.url" target="_blank" rel="noopener noreferrer">{{ filterPassword(monitor.url) }}</a>
<span v-if="monitor.type === 'port'">TCP Port {{ monitor.hostname }}:{{ monitor.port }}</span>
<span v-if="monitor.type === 'ping'">Ping: {{ monitor.hostname }}</span>
<span v-if="monitor.type === 'keyword'">
Expand All @@ -17,6 +17,12 @@
<span class="keyword">{{ monitor.keyword }}</span>
<span v-if="monitor.invertKeyword" alt="Inverted keyword" class="keyword-inverted"> ↧</span>
</span>
<span v-if="monitor.type === 'json-query'">
<br>
<span>{{ $t("Json Query") }}:</span> <span class="keyword">{{ monitor.jsonPath }}</span>
<br>
<span>{{ $t("Expected Value") }}:</span> <span class="keyword">{{ monitor.expectedValue }}</span>
</span>
<span v-if="monitor.type === 'dns'">[{{ monitor.dns_resolve_type }}] {{ monitor.hostname }}
<br>
<span>{{ $t("Last Result") }}:</span> <span class="keyword">{{ monitor.dns_last_result }}</span>
Expand Down Expand Up @@ -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");
}
Expand Down
29 changes: 23 additions & 6 deletions src/pages/EditMonitor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
<option value="keyword">
HTTP(s) - {{ $t("Keyword") }}
</option>
<option value="json-query">
HTTP(s) - {{ $t("Json Query") }}
</option>
<option value="grpc-keyword">
gRPC(s) - {{ $t("Keyword") }}
</option>
Expand Down Expand Up @@ -97,7 +100,7 @@
</div>

<!-- URL -->
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'real-browser' " class="my-3">
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' || monitor.type === 'real-browser' " class="my-3">
<label for="url" class="form-label">{{ $t("URL") }}</label>
<input id="url" v-model="monitor.url" type="url" class="form-control" pattern="https?://.+" required>
</div>
Expand Down Expand Up @@ -138,6 +141,20 @@
</div>
</div>

<!-- Json Query -->
<div v-if="monitor.type === 'json-query'" class="my-3">
<label for="jsonPath" class="form-label">{{ $t("Json Query") }}</label>
<input id="jsonPath" v-model="monitor.jsonPath" type="text" class="form-control" required>

<!-- eslint-disable-next-line vue/no-v-html -->
<div class="form-text" v-html="$t('jsonQueryDescription')">
</div>
<br>

<label for="expectedValue" class="form-label">{{ $t("Expected Value") }}</label>
<input id="expectedValue" v-model="monitor.expectedValue" type="text" class="form-control" required>
</div>

<!-- Game -->
<!-- GameDig only -->
<div v-if="monitor.type === 'gamedig'" class="my-3">
Expand Down Expand Up @@ -367,7 +384,7 @@

<h2 v-if="monitor.type !== 'push'" class="mt-5 mb-2">{{ $t("Advanced") }}</h2>

<div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3 form-check">
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' " class="my-3 form-check">
<input id="expiry-notification" v-model="monitor.expiryNotification" class="form-check-input" type="checkbox">
<label class="form-check-label" for="expiry-notification">
{{ $t("Certificate Expiry Notification") }}
Expand All @@ -376,7 +393,7 @@
</div>
</div>

<div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3 form-check">
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' " class="my-3 form-check">
<input id="ignore-tls" v-model="monitor.ignoreTls" class="form-check-input" type="checkbox" value="">
<label class="form-check-label" for="ignore-tls">
{{ $t("ignoreTLSError") }}
Expand Down Expand Up @@ -468,7 +485,7 @@
</button>

<!-- Proxies -->
<div v-if="monitor.type === 'http' || monitor.type === 'keyword'">
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query'">
<h2 class="mt-5 mb-2">{{ $t("Proxy") }}</h2>
<p v-if="$root.proxyList.length === 0">
{{ $t("Not available, please setup.") }}
Expand Down Expand Up @@ -496,7 +513,7 @@
</div>

<!-- HTTP Options -->
<template v-if="monitor.type === 'http' || monitor.type === 'keyword' ">
<template v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' ">
<h2 class="mt-5 mb-2">{{ $t("HTTP Options") }}</h2>

<!-- Method -->
Expand Down Expand Up @@ -1118,7 +1135,7 @@ message HealthCheckResponse {
this.monitor.body = JSON.stringify(JSON.parse(this.monitor.body), null, 4);
}
if (this.monitor.type && this.monitor.type !== "http" && this.monitor.type !== "keyword") {
if (this.monitor.type && this.monitor.type !== "http" && (this.monitor.type !== "keyword" || this.monitor.type !== "json-query")) {
this.monitor.httpBodyEncoding = null;
}
Expand Down

0 comments on commit 6bece87

Please sign in to comment.