add invert keyword feature

This commit is contained in:
Miles Steele 2023-04-05 19:10:21 -05:00
parent be7d3f6142
commit 171aff1226
7 changed files with 49 additions and 9 deletions

View file

@ -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;

View file

@ -70,6 +70,7 @@ class Database {
"patch-api-key-table.sql": true, "patch-api-key-table.sql": true,
"patch-monitor-tls.sql": true, "patch-monitor-tls.sql": true,
"patch-maintenance-cron.sql": true, "patch-maintenance-cron.sql": true,
"patch-add-invert-keyword.sql": true,
}; };
/** /**

View file

@ -84,6 +84,7 @@ class Monitor extends BeanModel {
retryInterval: this.retryInterval, retryInterval: this.retryInterval,
resendInterval: this.resendInterval, resendInterval: this.resendInterval,
keyword: this.keyword, keyword: this.keyword,
invertKeyword: this.isInvertKeyword(),
expiryNotification: this.isEnabledExpiryNotification(), expiryNotification: this.isEnabledExpiryNotification(),
ignoreTls: this.getIgnoreTls(), ignoreTls: this.getIgnoreTls(),
upsideDown: this.isUpsideDown(), upsideDown: this.isUpsideDown(),
@ -183,6 +184,14 @@ class Monitor extends BeanModel {
return Boolean(this.upsideDown); return Boolean(this.upsideDown);
} }
/**
* Parse to boolean
* @returns {boolean}
*/
isInvertKeyword() {
return Boolean(this.invertKeyword);
}
/** /**
* Parse to boolean * Parse to boolean
* @returns {boolean} * @returns {boolean}
@ -394,15 +403,17 @@ class Monitor extends BeanModel {
data = JSON.stringify(data); data = JSON.stringify(data);
} }
if (data.includes(this.keyword)) { let keyword_found = data.includes(this.keyword);
bean.msg += ", keyword is found"; if (keyword_found == !this.isInvertKeyword()) {
bean.msg += ", keyword " + (keyword_found ? "is" : "not") + " found";
bean.status = UP; bean.status = UP;
} else { } else {
data = data.replace(/<[^>]*>?|[\n\r]|\s+/gm, " "); data = data.replace(/<[^>]*>?|[\n\r]|\s+/gm, " ");
if (data.length > 50) { if (data.length > 50) {
data = data.substring(0, 47) + "..."; 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, grpcEnableTls: this.grpcEnableTls,
grpcMethod: this.grpcMethod, grpcMethod: this.grpcMethod,
grpcBody: this.grpcBody, grpcBody: this.grpcBody,
keyword: this.keyword
}; };
const response = await grpcQuery(options); const response = await grpcQuery(options);
bean.ping = dayjs().valueOf() - startTime; bean.ping = dayjs().valueOf() - startTime;
@ -616,13 +626,14 @@ class Monitor extends BeanModel {
bean.status = DOWN; bean.status = DOWN;
bean.msg = `Error in send gRPC ${response.code} ${response.errorMessage}`; bean.msg = `Error in send gRPC ${response.code} ${response.errorMessage}`;
} else { } 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.status = UP;
bean.msg = `${responseData}, keyword [${this.keyword}] is found`; bean.msg = `${responseData}, keyword [${this.keyword}] ${keyword_found ? "is" : "not"} found`;
} else { } 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.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") { } else if (this.type === "postgres") {

View file

@ -699,6 +699,7 @@ let needSetup = false;
bean.maxretries = monitor.maxretries; bean.maxretries = monitor.maxretries;
bean.port = parseInt(monitor.port); bean.port = parseInt(monitor.port);
bean.keyword = monitor.keyword; bean.keyword = monitor.keyword;
bean.invertKeyword = monitor.invertKeyword;
bean.ignoreTls = monitor.ignoreTls; bean.ignoreTls = monitor.ignoreTls;
bean.expiryNotification = monitor.expiryNotification; bean.expiryNotification = monitor.expiryNotification;
bean.upsideDown = monitor.upsideDown; bean.upsideDown = monitor.upsideDown;
@ -1345,6 +1346,7 @@ let needSetup = false;
maxretries: monitorListData[i].maxretries, maxretries: monitorListData[i].maxretries,
port: monitorListData[i].port, port: monitorListData[i].port,
keyword: monitorListData[i].keyword, keyword: monitorListData[i].keyword,
invertKeyword: monitorListData[i].invertKeyword,
ignoreTls: monitorListData[i].ignoreTls, ignoreTls: monitorListData[i].ignoreTls,
upsideDown: monitorListData[i].upsideDown, upsideDown: monitorListData[i].upsideDown,
maxredirects: monitorListData[i].maxredirects, maxredirects: monitorListData[i].maxredirects,

View file

@ -48,6 +48,7 @@
"Ping": "Ping", "Ping": "Ping",
"Monitor Type": "Monitor Type", "Monitor Type": "Monitor Type",
"Keyword": "Keyword", "Keyword": "Keyword",
"Invert Keyword": "Invert Keyword",
"Friendly Name": "Friendly Name", "Friendly Name": "Friendly Name",
"URL": "URL", "URL": "URL",
"Hostname": "Hostname", "Hostname": "Hostname",
@ -511,6 +512,7 @@
"passwordNotMatchMsg": "The repeat password does not match.", "passwordNotMatchMsg": "The repeat password does not match.",
"notificationDescription": "Notifications must be assigned to a monitor to function.", "notificationDescription": "Notifications must be assigned to a monitor to function.",
"keywordDescription": "Search keyword in plain HTML or JSON response. The search is case-sensitive.", "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.", "backupDescription": "You can backup all monitors and notifications into a JSON file.",
"backupDescription2": "Note: history and event data is not included.", "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.", "backupDescription3": "Sensitive data such as notification tokens are included in the export file; please store export securely.",

View file

@ -12,7 +12,9 @@
<span v-if="monitor.type === 'ping'">Ping: {{ monitor.hostname }}</span> <span v-if="monitor.type === 'ping'">Ping: {{ monitor.hostname }}</span>
<span v-if="monitor.type === 'keyword'"> <span v-if="monitor.type === 'keyword'">
<br> <br>
<span>{{ $t("Keyword") }}:</span> <span class="keyword">{{ monitor.keyword }}</span> <span>{{ $t("Keyword") }}: </span>
<span class="keyword">{{ monitor.keyword }}</span>
<span v-if="monitor.invertKeyword" alt="Inverted keyword" class="keyword-inverted"> </span>
</span> </span>
<span v-if="monitor.type === 'dns'">[{{ monitor.dns_resolve_type }}] {{ monitor.hostname }} <span v-if="monitor.type === 'dns'">[{{ monitor.dns_resolve_type }}] {{ monitor.hostname }}
<br> <br>
@ -490,6 +492,10 @@ table {
color: $dark-font-color; color: $dark-font-color;
} }
.keyword-inverted {
color: $dark-font-color;
}
.dropdown-clear-data { .dropdown-clear-data {
ul { ul {
background-color: $dark-bg; background-color: $dark-bg;

View file

@ -120,6 +120,17 @@
</div> </div>
</div> </div>
<!-- Invert keyword -->
<div v-if="monitor.type === 'keyword' || monitor.type === 'grpc-keyword'" class="my-3 form-check">
<input id="invert-keyword" v-model="monitor.invertKeyword" class="form-check-input" type="checkbox">
<label class="form-check-label" for="invert-keyword">
{{ $t("Invert Keyword") }}
</label>
<div class="form-text">
{{ $t("invertKeywordDescription") }}
</div>
</div>
<!-- Game --> <!-- Game -->
<!-- GameDig only --> <!-- GameDig only -->
<div v-if="monitor.type === 'gamedig'" class="my-3"> <div v-if="monitor.type === 'gamedig'" class="my-3">