diff --git a/db/patch-add-invert-keyword.sql b/db/patch-add-invert-keyword.sql new file mode 100644 index 000000000..8ca199eed --- /dev/null +++ b/db/patch-add-invert-keyword.sql @@ -0,0 +1,7 @@ +-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. +BEGIN TRANSACTION; + +ALTER TABLE monitor + ADD invert_keyword BOOLEAN default 0 not null; + +COMMIT; diff --git a/server/database.js b/server/database.js index 8576bdedc..6201a08b4 100644 --- a/server/database.js +++ b/server/database.js @@ -70,6 +70,7 @@ class Database { "patch-api-key-table.sql": true, "patch-monitor-tls.sql": true, "patch-maintenance-cron.sql": true, + "patch-add-invert-keyword.sql": true, }; /** diff --git a/server/model/monitor.js b/server/model/monitor.js index b4a0ba2a3..f6732b69d 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -84,6 +84,7 @@ class Monitor extends BeanModel { retryInterval: this.retryInterval, resendInterval: this.resendInterval, keyword: this.keyword, + invertKeyword: this.isInvertKeyword(), expiryNotification: this.isEnabledExpiryNotification(), ignoreTls: this.getIgnoreTls(), upsideDown: this.isUpsideDown(), @@ -183,6 +184,14 @@ class Monitor extends BeanModel { return Boolean(this.upsideDown); } + /** + * Parse to boolean + * @returns {boolean} + */ + isInvertKeyword() { + return Boolean(this.invertKeyword); + } + /** * Parse to boolean * @returns {boolean} @@ -394,15 +403,17 @@ class Monitor extends BeanModel { data = JSON.stringify(data); } - if (data.includes(this.keyword)) { - bean.msg += ", keyword is found"; + let keyword_found = data.includes(this.keyword); + if (keyword_found == !this.isInvertKeyword()) { + bean.msg += ", keyword " + (keyword_found ? "is" : "not") + " found"; bean.status = UP; } else { data = data.replace(/<[^>]*>?|[\n\r]|\s+/gm, " "); if (data.length > 50) { data = data.substring(0, 47) + "..."; } - throw new Error(bean.msg + ", but keyword is not in [" + data + "]"); + throw new Error(bean.msg + ", but keyword is " + + (keyword_found ? "present" : "not") + " in [" + data + "]"); } } @@ -603,7 +614,6 @@ class Monitor extends BeanModel { grpcEnableTls: this.grpcEnableTls, grpcMethod: this.grpcMethod, grpcBody: this.grpcBody, - keyword: this.keyword }; const response = await grpcQuery(options); bean.ping = dayjs().valueOf() - startTime; @@ -616,13 +626,14 @@ class Monitor extends BeanModel { bean.status = DOWN; bean.msg = `Error in send gRPC ${response.code} ${response.errorMessage}`; } else { - if (response.data.toString().includes(this.keyword)) { + let keyword_found = response.data.toString().includes(this.keyword) + if (keyword_found == !this.isInvertKeyword()) { bean.status = UP; - bean.msg = `${responseData}, keyword [${this.keyword}] is found`; + bean.msg = `${responseData}, keyword [${this.keyword}] ${keyword_found ? "is" : "not"} found`; } else { - log.debug("monitor:", `GRPC response [${response.data}] + ", but keyword [${this.keyword}] is not in [" + ${response.data} + "]"`); + log.debug("monitor:", `GRPC response [${response.data}] + ", but keyword [${this.keyword}] is ${keyword_found ? "present" : "not"} in [" + ${response.data} + "]"`); bean.status = DOWN; - bean.msg = `, but keyword [${this.keyword}] is not in [" + ${responseData} + "]`; + bean.msg = `, but keyword [${this.keyword}] is ${keyword_found ? "present" : "not"} in [" + ${responseData} + "]`; } } } else if (this.type === "postgres") { diff --git a/server/server.js b/server/server.js index ac2851ab4..cd2d9966d 100644 --- a/server/server.js +++ b/server/server.js @@ -699,6 +699,7 @@ let needSetup = false; bean.maxretries = monitor.maxretries; bean.port = parseInt(monitor.port); bean.keyword = monitor.keyword; + bean.invertKeyword = monitor.invertKeyword; bean.ignoreTls = monitor.ignoreTls; bean.expiryNotification = monitor.expiryNotification; bean.upsideDown = monitor.upsideDown; @@ -1345,6 +1346,7 @@ let needSetup = false; maxretries: monitorListData[i].maxretries, port: monitorListData[i].port, keyword: monitorListData[i].keyword, + invertKeyword: monitorListData[i].invertKeyword, ignoreTls: monitorListData[i].ignoreTls, upsideDown: monitorListData[i].upsideDown, maxredirects: monitorListData[i].maxredirects, diff --git a/src/lang/en.json b/src/lang/en.json index 4a2fe8813..a0cdaf058 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -48,6 +48,7 @@ "Ping": "Ping", "Monitor Type": "Monitor Type", "Keyword": "Keyword", + "Invert Keyword": "Invert Keyword", "Friendly Name": "Friendly Name", "URL": "URL", "Hostname": "Hostname", @@ -511,6 +512,7 @@ "passwordNotMatchMsg": "The repeat password does not match.", "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.", "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 ba034136c..ff3af096a 100644 --- a/src/pages/Details.vue +++ b/src/pages/Details.vue @@ -12,7 +12,9 @@ Ping: {{ monitor.hostname }}
- {{ $t("Keyword") }}: {{ monitor.keyword }} + {{ $t("Keyword") }}: + {{ monitor.keyword }} +
[{{ monitor.dns_resolve_type }}] {{ monitor.hostname }}
@@ -490,6 +492,10 @@ table { color: $dark-font-color; } + .keyword-inverted { + color: $dark-font-color; + } + .dropdown-clear-data { ul { background-color: $dark-bg; diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index 997faf955..04270f694 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -120,6 +120,17 @@ + +
+ + +
+ {{ $t("invertKeywordDescription") }} +
+
+