Skip to content

Commit bcdf6b8

Browse files
Merge branch 'master' into websocket_test
2 parents 88b9283 + 4a27f92 commit bcdf6b8

8 files changed

Lines changed: 471 additions & 57 deletions

File tree

package-lock.json

Lines changed: 43 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@
116116
"mitt": "~3.0.1",
117117
"mongodb": "~4.17.1",
118118
"mqtt": "~4.3.7",
119-
"mssql": "~11.0.0",
119+
"mssql": "~12.0.0",
120120
"mysql2": "~3.11.3",
121121
"nanoid": "~3.3.4",
122122
"net-snmp": "^3.11.2",
@@ -161,6 +161,7 @@
161161
"@playwright/test": "~1.39.0",
162162
"@popperjs/core": "~2.10.2",
163163
"@testcontainers/hivemq": "^10.13.1",
164+
"@testcontainers/mssqlserver": "^10.28.0",
164165
"@testcontainers/postgresql": "^11.9.0",
165166
"@testcontainers/rabbitmq": "^10.13.2",
166167
"@types/bootstrap": "~5.1.9",

server/model/monitor.js

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MI
88
PING_COUNT_MIN, PING_COUNT_MAX, PING_COUNT_DEFAULT,
99
PING_PER_REQUEST_TIMEOUT_MIN, PING_PER_REQUEST_TIMEOUT_MAX, PING_PER_REQUEST_TIMEOUT_DEFAULT
1010
} = require("../../src/util");
11-
const { ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, mysqlQuery, setSetting, httpNtlm, radius,
11+
const { ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mysqlQuery, setSetting, httpNtlm, radius,
1212
kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal, checkCertificateHostname
1313
} = require("../util-server");
1414
const { R } = require("redbean-node");
@@ -782,14 +782,6 @@ class Monitor extends BeanModel {
782782
} else {
783783
throw Error("Container State is " + res.data.State.Status);
784784
}
785-
} else if (this.type === "sqlserver") {
786-
let startTime = dayjs().valueOf();
787-
788-
await mssqlQuery(this.databaseConnectionString, this.databaseQuery || "SELECT 1");
789-
790-
bean.msg = "";
791-
bean.status = UP;
792-
bean.ping = dayjs().valueOf() - startTime;
793785
} else if (this.type === "mysql") {
794786
let startTime = dayjs().valueOf();
795787

server/monitor-types/mssql.js

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
const { MonitorType } = require("./monitor-type");
2+
const { log, UP } = require("../../src/util");
3+
const dayjs = require("dayjs");
4+
const mssql = require("mssql");
5+
const { ConditionVariable } = require("../monitor-conditions/variables");
6+
const { defaultStringOperators } = require("../monitor-conditions/operators");
7+
const {
8+
ConditionExpressionGroup,
9+
} = require("../monitor-conditions/expression");
10+
const { evaluateExpressionGroup } = require("../monitor-conditions/evaluator");
11+
12+
class MssqlMonitorType extends MonitorType {
13+
name = "sqlserver";
14+
15+
supportsConditions = true;
16+
conditionVariables = [
17+
new ConditionVariable("result", defaultStringOperators),
18+
];
19+
20+
/**
21+
* @inheritdoc
22+
*/
23+
async check(monitor, heartbeat, _server) {
24+
let startTime = dayjs().valueOf();
25+
26+
let query = monitor.databaseQuery;
27+
// No query provided by user, use SELECT 1
28+
if (!query || (typeof query === "string" && query.trim() === "")) {
29+
query = "SELECT 1";
30+
}
31+
32+
let result;
33+
try {
34+
result = await this.mssqlQuery(
35+
monitor.databaseConnectionString,
36+
query
37+
);
38+
} catch (error) {
39+
log.error("sqlserver", "Database query failed:", error.message);
40+
throw new Error(
41+
`Database connection/query failed: ${error.message}`
42+
);
43+
} finally {
44+
heartbeat.ping = dayjs().valueOf() - startTime;
45+
}
46+
47+
const conditions = ConditionExpressionGroup.fromMonitor(monitor);
48+
const handleConditions = (data) =>
49+
conditions ? evaluateExpressionGroup(conditions, data) : true;
50+
51+
// Since result is now a single value, pass it directly to conditions
52+
const conditionsResult = handleConditions({ result: String(result) });
53+
54+
if (!conditionsResult) {
55+
throw new Error(
56+
`Query result did not meet the specified conditions (${result})`
57+
);
58+
}
59+
60+
heartbeat.msg = "";
61+
heartbeat.status = UP;
62+
}
63+
64+
/**
65+
* Run a query on MSSQL server
66+
* @param {string} connectionString The database connection string
67+
* @param {string} query The query to validate the database with
68+
* @returns {Promise<any>} Single value from the first column of the first row
69+
*/
70+
async mssqlQuery(connectionString, query) {
71+
let pool;
72+
try {
73+
pool = new mssql.ConnectionPool(connectionString);
74+
await pool.connect();
75+
const result = await pool.request().query(query);
76+
77+
// Check if we have results
78+
if (!result.recordset || result.recordset.length === 0) {
79+
throw new Error("Query returned no results");
80+
}
81+
82+
// Check if we have multiple rows
83+
if (result.recordset.length > 1) {
84+
throw new Error(
85+
"Multiple values were found, expected only one value"
86+
);
87+
}
88+
89+
const firstRow = result.recordset[0];
90+
const columnNames = Object.keys(firstRow);
91+
92+
// Check if we have multiple columns
93+
if (columnNames.length > 1) {
94+
throw new Error(
95+
"Multiple columns were found, expected only one value"
96+
);
97+
}
98+
99+
// Return the single value from the first (and only) column
100+
return firstRow[columnNames[0]];
101+
} catch (err) {
102+
log.debug(
103+
"sqlserver",
104+
"Error caught in the query execution.",
105+
err.message
106+
);
107+
throw err;
108+
} finally {
109+
if (pool) {
110+
await pool.close();
111+
}
112+
}
113+
}
114+
}
115+
116+
module.exports = {
117+
MssqlMonitorType,
118+
};

server/uptime-kuma-server.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ class UptimeKumaServer {
124124
UptimeKumaServer.monitorTypeList["port"] = new TCPMonitorType();
125125
UptimeKumaServer.monitorTypeList["manual"] = new ManualMonitorType();
126126
UptimeKumaServer.monitorTypeList["redis"] = new RedisMonitorType();
127+
UptimeKumaServer.monitorTypeList["sqlserver"] = new MssqlMonitorType();
127128

128129
// Allow all CORS origins (polling) in development
129130
let cors = undefined;
@@ -572,5 +573,6 @@ const { RabbitMqMonitorType } = require("./monitor-types/rabbitmq");
572573
const { TCPMonitorType } = require("./monitor-types/tcp.js");
573574
const { ManualMonitorType } = require("./monitor-types/manual");
574575
const { RedisMonitorType } = require("./monitor-types/redis");
576+
const { MssqlMonitorType } = require("./monitor-types/mssql");
575577
const Monitor = require("./model/monitor");
576578

server/util-server.js

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ const { Resolver } = require("dns");
1010
const iconv = require("iconv-lite");
1111
const chardet = require("chardet");
1212
const chroma = require("chroma-js");
13-
const mssql = require("mssql");
1413
const mysql = require("mysql2");
1514
const { NtlmClient } = require("./modules/axios-ntlm/lib/ntlmClient.js");
1615
const { Settings } = require("./settings");
@@ -322,31 +321,6 @@ exports.dnsResolve = function (hostname, resolverServer, resolverPort, rrtype) {
322321
});
323322
};
324323

325-
/**
326-
* Run a query on SQL Server
327-
* @param {string} connectionString The database connection string
328-
* @param {string} query The query to validate the database with
329-
* @returns {Promise<(string[] | object[] | object)>} Response from
330-
* server
331-
*/
332-
exports.mssqlQuery = async function (connectionString, query) {
333-
let pool;
334-
try {
335-
pool = new mssql.ConnectionPool(connectionString);
336-
await pool.connect();
337-
if (!query) {
338-
query = "SELECT 1";
339-
}
340-
await pool.request().query(query);
341-
pool.close();
342-
} catch (e) {
343-
if (pool) {
344-
pool.close();
345-
}
346-
throw e;
347-
}
348-
};
349-
350324
/**
351325
* Run a query on MySQL/MariaDB
352326
* @param {string} connectionString The database connection string

test/backend-test/test-domain.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,12 @@ test("Domain Expiry", async (t) => {
5353
"user_id": 1,
5454
"name": "Testhook"
5555
});
56-
const manyDays = 1500;
57-
setSetting("domainExpiryNotifyDays", [ 7, 14, manyDays ], "general");
58-
const [ notifRet, data ] = await Promise.all([
56+
const manyDays = 3650;
57+
setSetting("domainExpiryNotifyDays", [ manyDays ], "general");
58+
const [ , data ] = await Promise.all([
5959
DomainExpiry.sendNotifications(monHttpCom, [ notif ]),
6060
mockWebhook(hook.port, hook.url)
6161
]);
62-
assert.equal(notifRet, manyDays);
6362
assert.match(data.msg, /will expire in/);
6463
});
6564
}).finally(() => {

0 commit comments

Comments
 (0)