Compare commits

...

13 commits

Author SHA1 Message Date
Antonino Natale
bc03551f97
Merge b398ba7497 into 7dc6191b0a 2025-01-24 17:49:47 +00:00
Elliot Matson
7dc6191b0a
fix: add notification-fallback for better google chat popups (#5476)
Some checks failed
Auto Test / armv7-simple-test (18, ARMv7) (push) Has been cancelled
Auto Test / armv7-simple-test (20, ARMv7) (push) Has been cancelled
Auto Test / check-linters (push) Has been cancelled
Auto Test / e2e-test (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
Merge Conflict Labeler / Labeling (push) Has been cancelled
validate / json-yaml-validate (push) Has been cancelled
validate / validate (push) Has been cancelled
Auto Test / auto-test (18, ARM64) (push) Has been cancelled
Auto Test / auto-test (18, macos-latest) (push) Has been cancelled
Auto Test / auto-test (18, ubuntu-latest) (push) Has been cancelled
Auto Test / auto-test (18, windows-latest) (push) Has been cancelled
Auto Test / auto-test (20, ARM64) (push) Has been cancelled
Auto Test / auto-test (20, macos-latest) (push) Has been cancelled
Auto Test / auto-test (20, ubuntu-latest) (push) Has been cancelled
Auto Test / auto-test (20, windows-latest) (push) Has been cancelled
Co-authored-by: Frank Elsinga <frank@elsinga.de>
2025-01-24 18:49:29 +01:00
Antonino Natale
b398ba7497 docs: update parameter type in mssqlQuery function documentation 2024-12-11 00:12:02 +01:00
Antonino Natale
dc931124f1 chore: revert Italian localization 2024-12-10 23:51:13 +01:00
Antonino Natale
e6f9327b8d fix: correct formatting in EditMonitor.vue for SQL error handling template 2024-12-10 23:20:52 +01:00
Antonino Natale
ae5f8de538 fix: ensure result is declared as a constant in mssqlQuery function 2024-12-10 23:19:39 +01:00
Antonino Natale
d3fc87a4b7 docs: update jsdoc in sql query functions 2024-12-10 23:18:55 +01:00
Antonino Natale
2c6431967c chore: add localization for handling empty result as error in English and Italian 2024-12-10 23:12:31 +01:00
Antonino Natale
443cc68a17 feat: add error handling option for empty SQL query results in EditMonitor 2024-12-10 23:12:20 +01:00
Antonino Natale
16f8e4ee80 chore: add database_query_handle_empty_result migration 2024-12-10 23:12:10 +01:00
Antonino Natale
bdbd9e5d05 feat: add handling for empty result sets in backend 2024-12-10 23:10:59 +01:00
Antonino Natale
0817515b10 fix: handle empty result set by rejecting with an error on mysql query 2024-12-10 21:59:41 +01:00
Antonino Natale
0348d04b0e feat: add sql query result evaluation 2024-12-10 21:51:36 +01:00
7 changed files with 67 additions and 10 deletions

View file

@ -0,0 +1,13 @@
exports.up = function (knex) {
return knex.schema
.alterTable("monitor", function (table) {
table.boolean("database_query_handle_empty_as_failure").notNullable().defaultTo(false);
});
};
exports.down = function (knex) {
return knex.schema
.alterTable("monitor", function (table) {
table.dropColumn("database_query_handle_empty_as_failure");
});
};

View file

@ -129,6 +129,7 @@ class Monitor extends BeanModel {
mqttSuccessMessage: this.mqttSuccessMessage,
mqttCheckType: this.mqttCheckType,
databaseQuery: this.databaseQuery,
databaseQueryHandleEmptyAsFailure: this.isDatabaseQueryHandleEmptyAsFailure(),
authMethod: this.authMethod,
grpcUrl: this.grpcUrl,
grpcProtobuf: this.grpcProtobuf,
@ -271,6 +272,14 @@ class Monitor extends BeanModel {
return Boolean(this.invertKeyword);
}
/**
* Parse to boolean
* @returns {boolean} Is sql query empty result handled as failure?
*/
isDatabaseQueryHandleEmptyAsFailure() {
return Boolean(this.databaseQueryHandleEmptyAsFailure);
}
/**
* Parse to boolean
* @returns {boolean} Enable TLS for gRPC?
@ -760,7 +769,7 @@ class Monitor extends BeanModel {
} else if (this.type === "sqlserver") {
let startTime = dayjs().valueOf();
await mssqlQuery(this.databaseConnectionString, this.databaseQuery || "SELECT 1");
await mssqlQuery(this.databaseConnectionString, this.databaseQueryHandleEmptyAsFailure, this.databaseQuery || "SELECT 1");
bean.msg = "";
bean.status = UP;
@ -799,7 +808,7 @@ class Monitor extends BeanModel {
} else if (this.type === "postgres") {
let startTime = dayjs().valueOf();
await postgresQuery(this.databaseConnectionString, this.databaseQuery || "SELECT 1");
await postgresQuery(this.databaseConnectionString, this.databaseQueryHandleEmptyAsFailure, this.databaseQuery || "SELECT 1");
bean.msg = "";
bean.status = UP;
@ -811,7 +820,7 @@ class Monitor extends BeanModel {
// TODO: rename `radius_password` to `password` later for general use
let mysqlPassword = this.radiusPassword;
bean.msg = await mysqlQuery(this.databaseConnectionString, this.databaseQuery || "SELECT 1", mysqlPassword);
bean.msg = await mysqlQuery(this.databaseConnectionString, this.databaseQueryHandleEmptyAsFailure, this.databaseQuery || "SELECT 1", mysqlPassword);
bean.status = UP;
bean.ping = dayjs().valueOf() - startTime;
} else if (this.type === "radius") {

View file

@ -72,6 +72,7 @@ class GoogleChat extends NotificationProvider {
// construct json data
let data = {
fallbackText: chatHeader["title"],
cardsV2: [
{
card: {

View file

@ -837,6 +837,7 @@ let needSetup = false;
bean.mqttCheckType = monitor.mqttCheckType;
bean.databaseConnectionString = monitor.databaseConnectionString;
bean.databaseQuery = monitor.databaseQuery;
bean.databaseQueryHandleEmptyAsFailure = monitor.databaseQueryHandleEmptyAsFailure;
bean.authMethod = monitor.authMethod;
bean.authWorkstation = monitor.authWorkstation;
bean.authDomain = monitor.authDomain;

View file

@ -318,11 +318,12 @@ exports.dnsResolve = function (hostname, resolverServer, resolverPort, rrtype) {
/**
* Run a query on SQL Server
* @param {string} connectionString The database connection string
* @param {boolean} handleEmptyResult Should empty results be handled as an error
* @param {string} query The query to validate the database with
* @returns {Promise<(string[] | object[] | object)>} Response from
* server
*/
exports.mssqlQuery = async function (connectionString, query) {
exports.mssqlQuery = async function (connectionString, handleEmptyResult, query) {
let pool;
try {
pool = new mssql.ConnectionPool(connectionString);
@ -330,7 +331,12 @@ exports.mssqlQuery = async function (connectionString, query) {
if (!query) {
query = "SELECT 1";
}
await pool.request().query(query);
const result = await pool.request().query(query);
if (Array.isArray(result.recordset)) {
if (handleEmptyResult && result.recordset.length === 0) {
throw new Error("No rows returned");
}
}
pool.close();
} catch (e) {
if (pool) {
@ -343,11 +349,12 @@ exports.mssqlQuery = async function (connectionString, query) {
/**
* Run a query on Postgres
* @param {string} connectionString The database connection string
* @param {boolean} handleEmptyResult Should empty results be handled as an error
* @param {string} query The query to validate the database with
* @returns {Promise<(string[] | object[] | object)>} Response from
* server
*/
exports.postgresQuery = function (connectionString, query) {
exports.postgresQuery = function (connectionString, handleEmptyResult, query) {
return new Promise((resolve, reject) => {
const config = postgresConParse(connectionString);
@ -384,7 +391,13 @@ exports.postgresQuery = function (connectionString, query) {
if (err) {
reject(err);
} else {
resolve(res);
if (Array.isArray(res.rows)) {
if (handleEmptyResult && res.rows.length === 0) {
reject(new Error("No rows returned"));
} else {
resolve(res);
}
}
}
client.end();
});
@ -401,11 +414,12 @@ exports.postgresQuery = function (connectionString, query) {
/**
* Run a query on MySQL/MariaDB
* @param {string} connectionString The database connection string
* @param {boolean} handleEmptyResult Should empty results be handled as an error
* @param {string} query The query to validate the database with
* @param {?string} password The password to use
* @returns {Promise<(string)>} Response from server
*/
exports.mysqlQuery = function (connectionString, query, password = undefined) {
exports.mysqlQuery = function (connectionString, handleEmptyResult, query, password = undefined) {
return new Promise((resolve, reject) => {
const connection = mysql.createConnection({
uri: connectionString,
@ -421,7 +435,11 @@ exports.mysqlQuery = function (connectionString, query, password = undefined) {
reject(err);
} else {
if (Array.isArray(res)) {
resolve("Rows: " + res.length);
if (handleEmptyResult && res.length === 0) {
reject(new Error("No rows returned"));
} else {
resolve("Rows: " + res.length);
}
} else {
resolve("No Error, but the result is not an array. Type: " + typeof res);
}

View file

@ -1051,5 +1051,7 @@
"RabbitMQ Password": "RabbitMQ Password",
"rabbitmqHelpText": "To use the monitor, you will need to enable the Management Plugin in your RabbitMQ setup. For more information, please consult the {rabitmq_documentation}.",
"SendGrid API Key": "SendGrid API Key",
"Separate multiple email addresses with commas": "Separate multiple email addresses with commas"
"Separate multiple email addresses with commas": "Separate multiple email addresses with commas",
"Handle empty result as error": "Handle empty result as error",
"handleEmptyResultDescription": "If the query returns no results, the monitor will be treated as down."
}

View file

@ -539,6 +539,19 @@
</div>
</template>
<!-- SQL Server / PostgreSQL / MySQL Error Handling -->
<template v-if="monitor.type === 'sqlserver' || monitor.type === 'postgres' || monitor.type === 'mysql'">
<div class="my-3 form-check">
<input id="sqlErrorHandling" v-model="monitor.databaseQueryHandleEmptyAsFailure" class="form-check-input" type="checkbox">
<label class="form-check-label" for="sqlErrorHandling">
{{ $t("Handle empty result as error") }}
</label>
<div class="form-text">
{{ $t("handleEmptyResultDescription") }}
</div>
</div>
</template>
<!-- MongoDB -->
<template v-if="monitor.type === 'mongodb'">
<div class="my-3">