chore: Extract the sqlserver monitor to its own monitor-type. Enable support conditions for a single result value.

This commit is contained in:
Pedro Magno
2025-12-30 11:06:04 +00:00
parent ebf1a5bb6f
commit 534ba5d547
7 changed files with 469 additions and 54 deletions
+1 -9
View File
@@ -8,7 +8,7 @@ const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MI
PING_COUNT_MIN, PING_COUNT_MAX, PING_COUNT_DEFAULT,
PING_PER_REQUEST_TIMEOUT_MIN, PING_PER_REQUEST_TIMEOUT_MAX, PING_PER_REQUEST_TIMEOUT_DEFAULT
} = require("../../src/util");
const { ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, mysqlQuery, setSetting, httpNtlm, radius,
const { ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mysqlQuery, setSetting, httpNtlm, radius,
kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal, checkCertificateHostname
} = require("../util-server");
const { R } = require("redbean-node");
@@ -782,14 +782,6 @@ class Monitor extends BeanModel {
} else {
throw Error("Container State is " + res.data.State.Status);
}
} else if (this.type === "sqlserver") {
let startTime = dayjs().valueOf();
await mssqlQuery(this.databaseConnectionString, this.databaseQuery || "SELECT 1");
bean.msg = "";
bean.status = UP;
bean.ping = dayjs().valueOf() - startTime;
} else if (this.type === "mysql") {
let startTime = dayjs().valueOf();
+118
View File
@@ -0,0 +1,118 @@
const { MonitorType } = require("./monitor-type");
const { log, UP } = require("../../src/util");
const dayjs = require("dayjs");
const mssql = require("mssql");
const { ConditionVariable } = require("../monitor-conditions/variables");
const { defaultStringOperators } = require("../monitor-conditions/operators");
const {
ConditionExpressionGroup,
} = require("../monitor-conditions/expression");
const { evaluateExpressionGroup } = require("../monitor-conditions/evaluator");
class MssqlMonitorType extends MonitorType {
name = "sqlserver";
supportsConditions = true;
conditionVariables = [
new ConditionVariable("result", defaultStringOperators),
];
/**
* @inheritdoc
*/
async check(monitor, heartbeat, _server) {
let startTime = dayjs().valueOf();
let query = monitor.databaseQuery;
// No query provided by user, use SELECT 1
if (!query || (typeof query === "string" && query.trim() === "")) {
query = "SELECT 1";
}
let result;
try {
result = await this.mssqlQuery(
monitor.databaseConnectionString,
query
);
} catch (error) {
log.error("sqlserver", "Database query failed:", error.message);
throw new Error(
`Database connection/query failed: ${error.message}`
);
} finally {
heartbeat.ping = dayjs().valueOf() - startTime;
}
const conditions = ConditionExpressionGroup.fromMonitor(monitor);
const handleConditions = (data) =>
conditions ? evaluateExpressionGroup(conditions, data) : true;
// Since result is now a single value, pass it directly to conditions
const conditionsResult = handleConditions({ result: String(result) });
if (!conditionsResult) {
throw new Error(
`Query result did not meet the specified conditions (${result})`
);
}
heartbeat.msg = "";
heartbeat.status = UP;
}
/**
* Run a query on MSSQL server
* @param {string} connectionString The database connection string
* @param {string} query The query to validate the database with
* @returns {Promise<any>} Single value from the first column of the first row
*/
async mssqlQuery(connectionString, query) {
let pool;
try {
pool = new mssql.ConnectionPool(connectionString);
await pool.connect();
const result = await pool.request().query(query);
// Check if we have results
if (!result.recordset || result.recordset.length === 0) {
throw new Error("Query returned no results");
}
// Check if we have multiple rows
if (result.recordset.length > 1) {
throw new Error(
"Multiple values were found, expected only one value"
);
}
const firstRow = result.recordset[0];
const columnNames = Object.keys(firstRow);
// Check if we have multiple columns
if (columnNames.length > 1) {
throw new Error(
"Multiple columns were found, expected only one value"
);
}
// Return the single value from the first (and only) column
return firstRow[columnNames[0]];
} catch (err) {
log.debug(
"sqlserver",
"Error caught in the query execution.",
err.message
);
throw err;
} finally {
if (pool) {
await pool.close();
}
}
}
}
module.exports = {
MssqlMonitorType,
};
+2
View File
@@ -124,6 +124,7 @@ class UptimeKumaServer {
UptimeKumaServer.monitorTypeList["port"] = new TCPMonitorType();
UptimeKumaServer.monitorTypeList["manual"] = new ManualMonitorType();
UptimeKumaServer.monitorTypeList["redis"] = new RedisMonitorType();
UptimeKumaServer.monitorTypeList["sqlserver"] = new MssqlMonitorType();
// Allow all CORS origins (polling) in development
let cors = undefined;
@@ -572,5 +573,6 @@ const { RabbitMqMonitorType } = require("./monitor-types/rabbitmq");
const { TCPMonitorType } = require("./monitor-types/tcp.js");
const { ManualMonitorType } = require("./monitor-types/manual");
const { RedisMonitorType } = require("./monitor-types/redis");
const { MssqlMonitorType } = require("./monitor-types/mssql");
const Monitor = require("./model/monitor");
-26
View File
@@ -10,7 +10,6 @@ const { Resolver } = require("dns");
const iconv = require("iconv-lite");
const chardet = require("chardet");
const chroma = require("chroma-js");
const mssql = require("mssql");
const mysql = require("mysql2");
const { NtlmClient } = require("./modules/axios-ntlm/lib/ntlmClient.js");
const { Settings } = require("./settings");
@@ -322,31 +321,6 @@ exports.dnsResolve = function (hostname, resolverServer, resolverPort, rrtype) {
});
};
/**
* Run a query on SQL Server
* @param {string} connectionString The database connection string
* @param {string} query The query to validate the database with
* @returns {Promise<(string[] | object[] | object)>} Response from
* server
*/
exports.mssqlQuery = async function (connectionString, query) {
let pool;
try {
pool = new mssql.ConnectionPool(connectionString);
await pool.connect();
if (!query) {
query = "SELECT 1";
}
await pool.request().query(query);
pool.close();
} catch (e) {
if (pool) {
pool.close();
}
throw e;
}
};
/**
* Run a query on MySQL/MariaDB
* @param {string} connectionString The database connection string