mirror of
https://github.com/koichixD/uptime-kuma.git
synced 2026-09-04 09:13:21 +00:00
chore: enable formatting over the entire codebase in CI (#6655)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
autofix-ci[bot]
parent
6658f2ce41
commit
0f61d7ee1b
+37
-24
@@ -14,9 +14,7 @@ class DnsMonitorType extends MonitorType {
|
||||
|
||||
supportsConditions = true;
|
||||
|
||||
conditionVariables = [
|
||||
new ConditionVariable("record", defaultStringOperators ),
|
||||
];
|
||||
conditionVariables = [new ConditionVariable("record", defaultStringOperators)];
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
@@ -31,19 +29,19 @@ class DnsMonitorType extends MonitorType {
|
||||
|
||||
const conditions = ConditionExpressionGroup.fromMonitor(monitor);
|
||||
let conditionsResult = true;
|
||||
const handleConditions = (data) => conditions ? evaluateExpressionGroup(conditions, data) : true;
|
||||
const handleConditions = (data) => (conditions ? evaluateExpressionGroup(conditions, data) : true);
|
||||
|
||||
switch (monitor.dns_resolve_type) {
|
||||
case "A":
|
||||
case "AAAA":
|
||||
case "PTR":
|
||||
dnsMessage = `Records: ${dnsRes.join(" | ")}`;
|
||||
conditionsResult = dnsRes.some(record => handleConditions({ record }));
|
||||
conditionsResult = dnsRes.some((record) => handleConditions({ record }));
|
||||
break;
|
||||
|
||||
case "TXT":
|
||||
dnsMessage = `Records: ${dnsRes.join(" | ")}`;
|
||||
conditionsResult = dnsRes.flat().some(record => handleConditions({ record }));
|
||||
conditionsResult = dnsRes.flat().some((record) => handleConditions({ record }));
|
||||
break;
|
||||
|
||||
case "CNAME":
|
||||
@@ -54,18 +52,23 @@ class DnsMonitorType extends MonitorType {
|
||||
case "CAA":
|
||||
// .filter(Boolean) was added because some CAA records do not contain an issue key, resulting in a blank list item.
|
||||
// Hypothetical dnsRes [{ critical: 0, issuewild: 'letsencrypt.org' }, { critical: 0, issue: 'letsencrypt.org' }]
|
||||
dnsMessage = `Records: ${dnsRes.map(record => record.issue).filter(Boolean).join(" | ")}`;
|
||||
conditionsResult = dnsRes.some(record => handleConditions({ record: record.issue }));
|
||||
dnsMessage = `Records: ${dnsRes
|
||||
.map((record) => record.issue)
|
||||
.filter(Boolean)
|
||||
.join(" | ")}`;
|
||||
conditionsResult = dnsRes.some((record) => handleConditions({ record: record.issue }));
|
||||
break;
|
||||
|
||||
case "MX":
|
||||
dnsMessage = dnsRes.map(record => `Hostname: ${record.exchange} - Priority: ${record.priority}`).join(" | ");
|
||||
conditionsResult = dnsRes.some(record => handleConditions({ record: record.exchange }));
|
||||
dnsMessage = dnsRes
|
||||
.map((record) => `Hostname: ${record.exchange} - Priority: ${record.priority}`)
|
||||
.join(" | ");
|
||||
conditionsResult = dnsRes.some((record) => handleConditions({ record: record.exchange }));
|
||||
break;
|
||||
|
||||
case "NS":
|
||||
dnsMessage = `Servers: ${dnsRes.join(" | ")}`;
|
||||
conditionsResult = dnsRes.some(record => handleConditions({ record }));
|
||||
conditionsResult = dnsRes.some((record) => handleConditions({ record }));
|
||||
break;
|
||||
|
||||
case "SOA":
|
||||
@@ -74,13 +77,18 @@ class DnsMonitorType extends MonitorType {
|
||||
break;
|
||||
|
||||
case "SRV":
|
||||
dnsMessage = dnsRes.map(record => `Name: ${record.name} | Port: ${record.port} | Priority: ${record.priority} | Weight: ${record.weight}`).join(" | ");
|
||||
conditionsResult = dnsRes.some(record => handleConditions({ record: record.name }));
|
||||
dnsMessage = dnsRes
|
||||
.map(
|
||||
(record) =>
|
||||
`Name: ${record.name} | Port: ${record.port} | Priority: ${record.priority} | Weight: ${record.weight}`
|
||||
)
|
||||
.join(" | ");
|
||||
conditionsResult = dnsRes.some((record) => handleConditions({ record: record.name }));
|
||||
break;
|
||||
}
|
||||
|
||||
if (monitor.dns_last_result !== dnsMessage && dnsMessage !== undefined) {
|
||||
await R.exec("UPDATE `monitor` SET dns_last_result = ? WHERE id = ? ", [ dnsMessage, monitor.id ]);
|
||||
await R.exec("UPDATE `monitor` SET dns_last_result = ? WHERE id = ? ", [dnsMessage, monitor.id]);
|
||||
}
|
||||
|
||||
if (!conditionsResult) {
|
||||
@@ -108,23 +116,26 @@ class DnsMonitorType extends MonitorType {
|
||||
*/
|
||||
async resolveDnsResolverServers(dnsResolveServer) {
|
||||
// Remove all spaces, split into array, remove all elements that are empty
|
||||
const addresses = dnsResolveServer.replace(/\s/g, "").split(",").filter((x) => x !== "");
|
||||
const addresses = dnsResolveServer
|
||||
.replace(/\s/g, "")
|
||||
.split(",")
|
||||
.filter((x) => x !== "");
|
||||
if (!addresses.length) {
|
||||
throw new Error("No Resolver Servers specified. Please specifiy at least one resolver server like 1.1.1.1 or a hostname");
|
||||
throw new Error(
|
||||
"No Resolver Servers specified. Please specifiy at least one resolver server like 1.1.1.1 or a hostname"
|
||||
);
|
||||
}
|
||||
const resolver = new Resolver();
|
||||
|
||||
// Make promises to be resolved concurrently
|
||||
const promises = addresses.map(async (e) => {
|
||||
if (net.isIP(e)) { // If IPv4 or IPv6 addr, immediately return
|
||||
return [ e ];
|
||||
if (net.isIP(e)) {
|
||||
// If IPv4 or IPv6 addr, immediately return
|
||||
return [e];
|
||||
}
|
||||
|
||||
// Otherwise, attempt to resolve hostname
|
||||
const [ v4, v6 ] = await Promise.allSettled([
|
||||
resolver.resolve4(e),
|
||||
resolver.resolve6(e),
|
||||
]);
|
||||
const [v4, v6] = await Promise.allSettled([resolver.resolve4(e), resolver.resolve6(e)]);
|
||||
|
||||
const addrs = [
|
||||
...(v4.status === "fulfilled" ? v4.value : []),
|
||||
@@ -145,7 +156,9 @@ class DnsMonitorType extends MonitorType {
|
||||
// only the resolver resolution can discard an address
|
||||
// -> no special error message for only the net.isIP case is necessary
|
||||
if (!parsed.length) {
|
||||
throw new Error("None of the configured resolver servers could be resolved to an IP address. Please provide a comma-separated list of valid resolver hostnames or IP addresses.");
|
||||
throw new Error(
|
||||
"None of the configured resolver servers could be resolved to an IP address. Please provide a comma-separated list of valid resolver hostnames or IP addresses."
|
||||
);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
@@ -160,7 +173,7 @@ class DnsMonitorType extends MonitorType {
|
||||
*/
|
||||
async dnsResolve(hostname, resolverServer, resolverPort, rrtype) {
|
||||
const resolver = new Resolver();
|
||||
resolver.setServers(resolverServer.map(server => `[${server}]:${resolverPort}`));
|
||||
resolver.setServers(resolverServer.map((server) => `[${server}]:${resolverPort}`));
|
||||
if (rrtype === "PTR") {
|
||||
return await resolver.reverse(hostname);
|
||||
}
|
||||
|
||||
@@ -79,4 +79,3 @@ class GroupMonitorType extends MonitorType {
|
||||
module.exports = {
|
||||
GroupMonitorType,
|
||||
};
|
||||
|
||||
|
||||
@@ -12,17 +12,27 @@ class GrpcKeywordMonitorType extends MonitorType {
|
||||
*/
|
||||
async check(monitor, heartbeat, _server) {
|
||||
const startTime = dayjs().valueOf();
|
||||
const service = this.constructGrpcService(monitor.grpcUrl, monitor.grpcProtobuf, monitor.grpcServiceName, monitor.grpcEnableTls);
|
||||
const service = this.constructGrpcService(
|
||||
monitor.grpcUrl,
|
||||
monitor.grpcProtobuf,
|
||||
monitor.grpcServiceName,
|
||||
monitor.grpcEnableTls
|
||||
);
|
||||
let response = await this.grpcQuery(service, monitor.grpcMethod, monitor.grpcBody);
|
||||
heartbeat.ping = dayjs().valueOf() - startTime;
|
||||
log.debug(this.name, "gRPC response:", response);
|
||||
let keywordFound = response.toString().includes(monitor.keyword);
|
||||
if (keywordFound !== !monitor.isInvertKeyword()) {
|
||||
log.debug(this.name, `GRPC response [${response}] + ", but keyword [${monitor.keyword}] is ${keywordFound ? "present" : "not"} in [" + ${response} + "]"`);
|
||||
log.debug(
|
||||
this.name,
|
||||
`GRPC response [${response}] + ", but keyword [${monitor.keyword}] is ${keywordFound ? "present" : "not"} in [" + ${response} + "]"`
|
||||
);
|
||||
|
||||
let truncatedResponse = (response.length > 50) ? response.toString().substring(0, 47) + "..." : response;
|
||||
let truncatedResponse = response.length > 50 ? response.toString().substring(0, 47) + "..." : response;
|
||||
|
||||
throw new Error(`keyword [${monitor.keyword}] is ${keywordFound ? "present" : "not"} in [" + ${truncatedResponse} + "]`);
|
||||
throw new Error(
|
||||
`keyword [${monitor.keyword}] is ${keywordFound ? "present" : "not"} in [" + ${truncatedResponse} + "]`
|
||||
);
|
||||
}
|
||||
heartbeat.status = UP;
|
||||
heartbeat.msg = `${response}, keyword [${monitor.keyword}] ${keywordFound ? "is" : "not"} found`;
|
||||
@@ -42,19 +52,24 @@ class GrpcKeywordMonitorType extends MonitorType {
|
||||
const Client = grpc.makeGenericClientConstructor({});
|
||||
const credentials = enableTls ? grpc.credentials.createSsl() : grpc.credentials.createInsecure();
|
||||
const client = new Client(url, credentials);
|
||||
return protoServiceObject.create((method, requestData, cb) => {
|
||||
const fullServiceName = method.fullName;
|
||||
const serviceFQDN = fullServiceName.split(".");
|
||||
const serviceMethod = serviceFQDN.pop();
|
||||
const serviceMethodClientImpl = `/${serviceFQDN.slice(1).join(".")}/${serviceMethod}`;
|
||||
log.debug(this.name, `gRPC method ${serviceMethodClientImpl}`);
|
||||
client.makeUnaryRequest(
|
||||
serviceMethodClientImpl,
|
||||
arg => arg,
|
||||
arg => arg,
|
||||
requestData,
|
||||
cb);
|
||||
}, false, false);
|
||||
return protoServiceObject.create(
|
||||
(method, requestData, cb) => {
|
||||
const fullServiceName = method.fullName;
|
||||
const serviceFQDN = fullServiceName.split(".");
|
||||
const serviceMethod = serviceFQDN.pop();
|
||||
const serviceMethodClientImpl = `/${serviceFQDN.slice(1).join(".")}/${serviceMethod}`;
|
||||
log.debug(this.name, `gRPC method ${serviceMethodClientImpl}`);
|
||||
client.makeUnaryRequest(
|
||||
serviceMethodClientImpl,
|
||||
(arg) => arg,
|
||||
(arg) => arg,
|
||||
requestData,
|
||||
cb
|
||||
);
|
||||
},
|
||||
false,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,5 +34,5 @@ class ManualMonitorType extends MonitorType {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ManualMonitorType
|
||||
ManualMonitorType,
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ class MongodbMonitorType extends MonitorType {
|
||||
* @inheritdoc
|
||||
*/
|
||||
async check(monitor, heartbeat, _server) {
|
||||
let command = { "ping": 1 };
|
||||
let command = { ping: 1 };
|
||||
if (monitor.databaseQuery) {
|
||||
command = JSON.parse(monitor.databaseQuery);
|
||||
}
|
||||
@@ -37,7 +37,11 @@ class MongodbMonitorType extends MonitorType {
|
||||
if (result.toString() === monitor.expectedValue) {
|
||||
heartbeat.msg = "Command executed successfully and expected value was found";
|
||||
} else {
|
||||
throw new Error("Query executed, but value is not equal to expected value, value was: [" + JSON.stringify(result) + "]");
|
||||
throw new Error(
|
||||
"Query executed, but value is not equal to expected value, value was: [" +
|
||||
JSON.stringify(result) +
|
||||
"]"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ class MqttMonitorType extends MonitorType {
|
||||
* @inheritdoc
|
||||
*/
|
||||
async check(monitor, heartbeat, server) {
|
||||
const [ messageTopic, receivedMessage ] = await this.mqttAsync(monitor.hostname, monitor.mqttTopic, {
|
||||
const [messageTopic, receivedMessage] = await this.mqttAsync(monitor.hostname, monitor.mqttTopic, {
|
||||
port: monitor.port,
|
||||
username: monitor.mqttUsername,
|
||||
password: monitor.mqttPassword,
|
||||
@@ -143,11 +143,14 @@ class MqttMonitorType extends MonitorType {
|
||||
hostname = "mqtt://" + hostname;
|
||||
}
|
||||
|
||||
const timeoutID = setTimeout(() => {
|
||||
log.debug(this.name, "MQTT timeout triggered");
|
||||
client.end();
|
||||
reject(new Error("Timeout, Message not received"));
|
||||
}, interval * 1000 * 0.8);
|
||||
const timeoutID = setTimeout(
|
||||
() => {
|
||||
log.debug(this.name, "MQTT timeout triggered");
|
||||
client.end();
|
||||
reject(new Error("Timeout, Message not received"));
|
||||
},
|
||||
interval * 1000 * 0.8
|
||||
);
|
||||
|
||||
// Construct the URL based on protocol
|
||||
let mqttUrl = `${hostname}:${port}`;
|
||||
@@ -164,7 +167,7 @@ class MqttMonitorType extends MonitorType {
|
||||
let client = mqtt.connect(mqttUrl, {
|
||||
username,
|
||||
password,
|
||||
clientId: "uptime-kuma_" + Math.random().toString(16).substr(2, 8)
|
||||
clientId: "uptime-kuma_" + Math.random().toString(16).substr(2, 8),
|
||||
});
|
||||
|
||||
client.on("connect", () => {
|
||||
@@ -190,9 +193,8 @@ class MqttMonitorType extends MonitorType {
|
||||
client.on("message", (messageTopic, message) => {
|
||||
client.end();
|
||||
clearTimeout(timeoutID);
|
||||
resolve([ messageTopic, message.toString("utf8") ]);
|
||||
resolve([messageTopic, message.toString("utf8")]);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,18 +4,14 @@ 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 { ConditionExpressionGroup } = require("../monitor-conditions/expression");
|
||||
const { evaluateExpressionGroup } = require("../monitor-conditions/evaluator");
|
||||
|
||||
class MssqlMonitorType extends MonitorType {
|
||||
name = "sqlserver";
|
||||
|
||||
supportsConditions = true;
|
||||
conditionVariables = [
|
||||
new ConditionVariable("result", defaultStringOperators),
|
||||
];
|
||||
conditionVariables = [new ConditionVariable("result", defaultStringOperators)];
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
@@ -34,10 +30,7 @@ class MssqlMonitorType extends MonitorType {
|
||||
try {
|
||||
if (hasConditions) {
|
||||
// When conditions are enabled, expect a single value result
|
||||
const result = await this.mssqlQuerySingleValue(
|
||||
monitor.databaseConnectionString,
|
||||
query
|
||||
);
|
||||
const result = await this.mssqlQuerySingleValue(monitor.databaseConnectionString, query);
|
||||
heartbeat.ping = dayjs().valueOf() - startTime;
|
||||
|
||||
const conditionsResult = evaluateExpressionGroup(conditions, { result: String(result) });
|
||||
@@ -50,10 +43,7 @@ class MssqlMonitorType extends MonitorType {
|
||||
heartbeat.msg = "Query did meet specified conditions";
|
||||
} else {
|
||||
// Backwards compatible: just check connection and return row count
|
||||
const result = await this.mssqlQuery(
|
||||
monitor.databaseConnectionString,
|
||||
query
|
||||
);
|
||||
const result = await this.mssqlQuery(monitor.databaseConnectionString, query);
|
||||
heartbeat.ping = dayjs().valueOf() - startTime;
|
||||
heartbeat.status = UP;
|
||||
heartbeat.msg = result;
|
||||
@@ -87,11 +77,7 @@ class MssqlMonitorType extends MonitorType {
|
||||
return "No Error, but the result is not an array. Type: " + typeof result.recordset;
|
||||
}
|
||||
} catch (err) {
|
||||
log.debug(
|
||||
"sqlserver",
|
||||
"Error caught in the query execution.",
|
||||
err.message
|
||||
);
|
||||
log.debug("sqlserver", "Error caught in the query execution.", err.message);
|
||||
throw err;
|
||||
} finally {
|
||||
if (pool) {
|
||||
@@ -120,9 +106,7 @@ class MssqlMonitorType extends MonitorType {
|
||||
|
||||
// Check if we have multiple rows
|
||||
if (result.recordset.length > 1) {
|
||||
throw new Error(
|
||||
"Multiple values were found, expected only one value"
|
||||
);
|
||||
throw new Error("Multiple values were found, expected only one value");
|
||||
}
|
||||
|
||||
const firstRow = result.recordset[0];
|
||||
@@ -130,19 +114,13 @@ class MssqlMonitorType extends MonitorType {
|
||||
|
||||
// Check if we have multiple columns
|
||||
if (columnNames.length > 1) {
|
||||
throw new Error(
|
||||
"Multiple columns were found, expected only one value"
|
||||
);
|
||||
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
|
||||
);
|
||||
log.debug("sqlserver", "Error caught in the query execution.", err.message);
|
||||
throw err;
|
||||
} finally {
|
||||
if (pool) {
|
||||
|
||||
@@ -11,9 +11,7 @@ class MysqlMonitorType extends MonitorType {
|
||||
name = "mysql";
|
||||
|
||||
supportsConditions = true;
|
||||
conditionVariables = [
|
||||
new ConditionVariable("result", defaultStringOperators),
|
||||
];
|
||||
conditionVariables = [new ConditionVariable("result", defaultStringOperators)];
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
@@ -74,7 +72,7 @@ class MysqlMonitorType extends MonitorType {
|
||||
return new Promise((resolve, reject) => {
|
||||
const connection = mysql.createConnection({
|
||||
uri: connectionString,
|
||||
password
|
||||
password,
|
||||
});
|
||||
|
||||
connection.on("error", (err) => {
|
||||
@@ -113,7 +111,7 @@ class MysqlMonitorType extends MonitorType {
|
||||
return new Promise((resolve, reject) => {
|
||||
const connection = mysql.createConnection({
|
||||
uri: connectionString,
|
||||
password
|
||||
password,
|
||||
});
|
||||
|
||||
connection.on("error", (err) => {
|
||||
|
||||
@@ -31,7 +31,10 @@ class RabbitMqMonitorType extends MonitorType {
|
||||
await this.checkSingleNode(monitor, baseUrl, `${nodeIndex}/${baseUrls.length}`);
|
||||
// If checkSingleNode succeeds (doesn't throw), set heartbeat to UP
|
||||
heartbeat.status = UP;
|
||||
heartbeat.msg = baseUrls.length === 1 ? "Node is reachable and there are no alerts in the cluster" : `One of the ${baseUrls.length} nodes is reachable and there are no alerts in the cluster`;
|
||||
heartbeat.msg =
|
||||
baseUrls.length === 1
|
||||
? "Node is reachable and there are no alerts in the cluster"
|
||||
: `One of the ${baseUrls.length} nodes is reachable and there are no alerts in the cluster`;
|
||||
return;
|
||||
} catch (error) {
|
||||
log.warn(this.name, `Node ${nodeIndex}: ${error.message}`);
|
||||
@@ -64,8 +67,12 @@ class RabbitMqMonitorType extends MonitorType {
|
||||
method: "get",
|
||||
timeout: monitor.timeout * 1000,
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Authorization": "Basic " + Buffer.from(`${monitor.rabbitmqUsername || ""}:${monitor.rabbitmqPassword || ""}`).toString("base64"),
|
||||
Accept: "application/json",
|
||||
Authorization:
|
||||
"Basic " +
|
||||
Buffer.from(`${monitor.rabbitmqUsername || ""}:${monitor.rabbitmqPassword || ""}`).toString(
|
||||
"base64"
|
||||
),
|
||||
},
|
||||
signal: axiosAbortSignal((monitor.timeout + 10) * 1000),
|
||||
// Capture reason for 503 status
|
||||
@@ -76,7 +83,10 @@ class RabbitMqMonitorType extends MonitorType {
|
||||
|
||||
try {
|
||||
const res = await axios.request(options);
|
||||
log.debug("monitor", `[${monitor.name}] Axios Response: status=${res.status} body=${JSON.stringify(res.data)}`);
|
||||
log.debug(
|
||||
"monitor",
|
||||
`[${monitor.name}] Axios Response: status=${res.status} body=${JSON.stringify(res.data)}`
|
||||
);
|
||||
|
||||
if (res.status === 200) {
|
||||
log.debug("monitor", `[${monitor.name}] Node ${nodeInfo} is healthy`);
|
||||
|
||||
@@ -38,7 +38,6 @@ if (process.platform === "win32") {
|
||||
allowedList.push(drive + ":\\Program Files\\Google\\Chrome\\Application\\chrome.exe");
|
||||
allowedList.push(drive + ":\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe");
|
||||
}
|
||||
|
||||
} else if (process.platform === "linux") {
|
||||
allowedList = [
|
||||
"chromium",
|
||||
@@ -48,7 +47,7 @@ if (process.platform === "win32") {
|
||||
"/usr/bin/chromium",
|
||||
"/usr/bin/chromium-browser",
|
||||
"/usr/bin/google-chrome",
|
||||
"/snap/bin/chromium", // Ubuntu
|
||||
"/snap/bin/chromium", // Ubuntu
|
||||
];
|
||||
} else if (process.platform === "darwin") {
|
||||
allowedList = [
|
||||
@@ -126,8 +125,10 @@ async function prepareChromeExecutable(executablePath) {
|
||||
} else {
|
||||
// User specified a path
|
||||
// Check if the executablePath is in the list of allowed
|
||||
if (!await isAllowedChromeExecutable(executablePath)) {
|
||||
throw new Error("This Chromium executable path is not allowed by default. If you are sure this is safe, please add an environment variable UPTIME_KUMA_ALLOW_ALL_CHROME_EXEC=1 to allow it.");
|
||||
if (!(await isAllowedChromeExecutable(executablePath))) {
|
||||
throw new Error(
|
||||
"This Chromium executable path is not allowed by default. If you are sure this is safe, please add an environment variable UPTIME_KUMA_ALLOW_ALL_CHROME_EXEC=1 to allow it."
|
||||
);
|
||||
}
|
||||
}
|
||||
return executablePath;
|
||||
@@ -146,11 +147,13 @@ async function prepareChromeExecutable(executablePath) {
|
||||
*/
|
||||
async function installChromiumViaApt(executablePath) {
|
||||
if (await commandExists(executablePath)) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
await new Promise((resolve, reject) => {
|
||||
log.info("chromium", "Installing Chromium...");
|
||||
let child = childProcess.exec("apt update && apt --yes --no-install-recommends install chromium fonts-indic fonts-noto fonts-noto-cjk");
|
||||
let child = childProcess.exec(
|
||||
"apt update && apt --yes --no-install-recommends install chromium fonts-indic fonts-noto fonts-noto-cjk"
|
||||
);
|
||||
|
||||
// On exit
|
||||
child.on("exit", (code) => {
|
||||
@@ -241,14 +244,15 @@ async function testRemoteBrowser(remoteBrowserURL) {
|
||||
}
|
||||
}
|
||||
class RealBrowserMonitorType extends MonitorType {
|
||||
|
||||
name = "real-browser";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async check(monitor, heartbeat, server) {
|
||||
const browser = monitor.remote_browser ? await getRemoteBrowser(monitor.remote_browser, monitor.user_id) : await getBrowser();
|
||||
const browser = monitor.remote_browser
|
||||
? await getRemoteBrowser(monitor.remote_browser, monitor.user_id)
|
||||
: await getBrowser();
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ class RedisMonitorType extends MonitorType {
|
||||
const client = redis.createClient({
|
||||
url: dsn,
|
||||
socket: {
|
||||
rejectUnauthorized
|
||||
}
|
||||
rejectUnauthorized,
|
||||
},
|
||||
});
|
||||
client.on("error", (err) => {
|
||||
if (client.isOpen) {
|
||||
@@ -37,16 +37,19 @@ class RedisMonitorType extends MonitorType {
|
||||
if (!client.isOpen) {
|
||||
client.emit("error", new Error("connection isn't open"));
|
||||
}
|
||||
client.ping().then((res, err) => {
|
||||
if (client.isOpen) {
|
||||
client.disconnect();
|
||||
}
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(res);
|
||||
}
|
||||
}).catch(error => reject(error));
|
||||
client
|
||||
.ping()
|
||||
.then((res, err) => {
|
||||
if (client.isOpen) {
|
||||
client.disconnect();
|
||||
}
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(res);
|
||||
}
|
||||
})
|
||||
.catch((error) => reject(error));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -30,11 +30,7 @@ class SIPMonitorType extends MonitorType {
|
||||
async runSipSak(hostname, port, timeout) {
|
||||
const { stdout, stderr } = await execFile(
|
||||
"sipsak",
|
||||
[
|
||||
"-s", `sip:${hostname}:${port}`,
|
||||
"--from", `sip:sipsak@${hostname}`,
|
||||
"-v",
|
||||
],
|
||||
["-s", `sip:${hostname}:${port}`, "--from", `sip:sipsak@${hostname}`, "-v"],
|
||||
{ timeout }
|
||||
);
|
||||
|
||||
|
||||
@@ -25,11 +25,14 @@ class SNMPMonitorType extends MonitorType {
|
||||
});
|
||||
|
||||
const varbinds = await new Promise((resolve, reject) => {
|
||||
session.get([ monitor.snmpOid ], (error, varbinds) => {
|
||||
session.get([monitor.snmpOid], (error, varbinds) => {
|
||||
error ? reject(error) : resolve(varbinds);
|
||||
});
|
||||
});
|
||||
log.debug(this.name, `SNMP: Received varbinds (Type: ${snmp.ObjectType[varbinds[0].type]} Value: ${varbinds[0].value})`);
|
||||
log.debug(
|
||||
this.name,
|
||||
`SNMP: Received varbinds (Type: ${snmp.ObjectType[varbinds[0].type]} Value: ${varbinds[0].value})`
|
||||
);
|
||||
|
||||
if (varbinds.length === 0) {
|
||||
throw new Error(`No varbinds returned from SNMP session (OID: ${monitor.snmpOid})`);
|
||||
@@ -42,13 +45,20 @@ class SNMPMonitorType extends MonitorType {
|
||||
// We restrict querying to one OID per monitor, therefore `varbinds[0]` will always contain the value we're interested in.
|
||||
const value = varbinds[0].value;
|
||||
|
||||
const { status, response } = await evaluateJsonQuery(value, monitor.jsonPath, monitor.jsonPathOperator, monitor.expectedValue);
|
||||
const { status, response } = await evaluateJsonQuery(
|
||||
value,
|
||||
monitor.jsonPath,
|
||||
monitor.jsonPathOperator,
|
||||
monitor.expectedValue
|
||||
);
|
||||
|
||||
if (status) {
|
||||
heartbeat.status = UP;
|
||||
heartbeat.msg = `JSON query passes (comparing ${response} ${monitor.jsonPathOperator} ${monitor.expectedValue})`;
|
||||
} else {
|
||||
throw new Error(`JSON query does not pass (comparing ${response} ${monitor.jsonPathOperator} ${monitor.expectedValue})`);
|
||||
throw new Error(
|
||||
`JSON query does not pass (comparing ${response} ${monitor.jsonPathOperator} ${monitor.expectedValue})`
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (session) {
|
||||
|
||||
@@ -43,7 +43,7 @@ class SystemServiceMonitorType extends MonitorType {
|
||||
return;
|
||||
}
|
||||
|
||||
execFile("systemctl", [ "is-active", serviceName ], { timeout: 5000 }, (error, stdout, stderr) => {
|
||||
execFile("systemctl", ["is-active", serviceName], { timeout: 5000 }, (error, stdout, stderr) => {
|
||||
// Combine output and truncate to ~200 chars to prevent DB bloat
|
||||
let output = (stderr || stdout || "").toString().trim();
|
||||
if (output.length > 200) {
|
||||
@@ -72,9 +72,7 @@ class SystemServiceMonitorType extends MonitorType {
|
||||
return new Promise((resolve, reject) => {
|
||||
// SECURITY: Validate service name to reduce command-injection risk
|
||||
if (!/^[A-Za-z0-9._-]+$/.test(serviceName)) {
|
||||
throw new Error(
|
||||
"Invalid service name. Only alphanumeric characters and '.', '_', '-' are allowed."
|
||||
);
|
||||
throw new Error("Invalid service name. Only alphanumeric characters and '.', '_', '-' are allowed.");
|
||||
}
|
||||
|
||||
const cmd = "powershell";
|
||||
@@ -83,7 +81,7 @@ class SystemServiceMonitorType extends MonitorType {
|
||||
"-NonInteractive",
|
||||
"-Command",
|
||||
// Single quotes around the service name
|
||||
`(Get-Service -Name '${serviceName.replaceAll("'", "''")}').Status`
|
||||
`(Get-Service -Name '${serviceName.replaceAll("'", "''")}').Status`,
|
||||
];
|
||||
|
||||
execFile(cmd, args, { timeout: 5000 }, (error, stdout, stderr) => {
|
||||
|
||||
@@ -27,7 +27,7 @@ class TailscalePing extends MonitorType {
|
||||
*/
|
||||
async runTailscalePing(hostname, interval) {
|
||||
let timeout = interval * 1000 * 0.8;
|
||||
let res = await childProcessAsync.spawn("tailscale", [ "ping", "--c", "1", hostname ], {
|
||||
let res = await childProcessAsync.spawn("tailscale", ["ping", "--c", "1", hostname], {
|
||||
timeout: timeout,
|
||||
encoding: "utf8",
|
||||
});
|
||||
|
||||
+21
-10
@@ -135,7 +135,7 @@ class TCPMonitorType extends MonitorType {
|
||||
let socket_;
|
||||
|
||||
// Handle TLS certificate checking for secure/starttls connections
|
||||
if ([ "secure", "starttls" ].includes(monitor.smtpSecurity) && monitor.isEnabledExpiryNotification()) {
|
||||
if (["secure", "starttls"].includes(monitor.smtpSecurity) && monitor.isEnabledExpiryNotification()) {
|
||||
const reuseSocket = monitor.smtpSecurity === "starttls" ? await this.performStartTls(monitor) : {};
|
||||
socket_ = reuseSocket.socket;
|
||||
await this.checkTlsCertificate(monitor, reuseSocket);
|
||||
@@ -165,7 +165,9 @@ class TCPMonitorType extends MonitorType {
|
||||
const onBannerTimeout = () => {
|
||||
log.debug(this.name, `[${monitor.name}] Pre-TLS timed out waiting for banner`);
|
||||
// No banner. Could be a XMPP server?
|
||||
socket_.write(`<stream:stream to='${monitor.hostname}' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>`);
|
||||
socket_.write(
|
||||
`<stream:stream to='${monitor.hostname}' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>`
|
||||
);
|
||||
};
|
||||
|
||||
const doResolve = () => {
|
||||
@@ -185,7 +187,7 @@ class TCPMonitorType extends MonitorType {
|
||||
log.debug(this.name, `[${monitor.name}] Pre-TLS connection: ${JSON.stringify(socket_)}`);
|
||||
});
|
||||
|
||||
socket_.on("data", data => {
|
||||
socket_.on("data", (data) => {
|
||||
const response = data.toString();
|
||||
const response_ = response.toLowerCase();
|
||||
log.debug(this.name, `[${monitor.name}] Pre-TLS response: ${response}`);
|
||||
@@ -207,7 +209,7 @@ class TCPMonitorType extends MonitorType {
|
||||
doResolve();
|
||||
break;
|
||||
case response_.includes("<starttls"):
|
||||
socket_.write("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>");
|
||||
socket_.write('<starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>');
|
||||
break;
|
||||
case response_.includes("<stream:stream") || response_.includes("</stream:stream>"):
|
||||
break;
|
||||
@@ -215,7 +217,7 @@ class TCPMonitorType extends MonitorType {
|
||||
doReject(`Unexpected response: ${response}`);
|
||||
}
|
||||
});
|
||||
socket_.on("error", error => {
|
||||
socket_.on("error", (error) => {
|
||||
log.debug(this.name, `[${monitor.name}] ${error.toString()}`);
|
||||
reject(error);
|
||||
});
|
||||
@@ -253,7 +255,7 @@ class TCPMonitorType extends MonitorType {
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("error", error => {
|
||||
socket.on("error", (error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
@@ -320,11 +322,17 @@ class TCPMonitorType extends MonitorType {
|
||||
heartbeat.status = UP;
|
||||
heartbeat.msg = `TLS alert received as expected: ${result.alertName} (${result.alertNumber})`;
|
||||
} else if (result.success) {
|
||||
throw new Error(`Expected TLS alert '${expectedTlsAlert}' but connection succeeded. The server accepted the connection without requiring a client certificate.`);
|
||||
throw new Error(
|
||||
`Expected TLS alert '${expectedTlsAlert}' but connection succeeded. The server accepted the connection without requiring a client certificate.`
|
||||
);
|
||||
} else if (result.alertNumber !== null) {
|
||||
throw new Error(`Expected TLS alert '${expectedTlsAlert}' but received '${result.alertName}' (${result.alertNumber})`);
|
||||
throw new Error(
|
||||
`Expected TLS alert '${expectedTlsAlert}' but received '${result.alertName}' (${result.alertNumber})`
|
||||
);
|
||||
} else {
|
||||
throw new Error(`Expected TLS alert '${expectedTlsAlert}' but got unexpected error: ${result.errorMessage}`);
|
||||
throw new Error(
|
||||
`Expected TLS alert '${expectedTlsAlert}' but got unexpected error: ${result.errorMessage}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,7 +384,10 @@ class TCPMonitorType extends MonitorType {
|
||||
const alertNumber = parseTlsAlertNumber(errorMessage);
|
||||
const alertName = alertNumber !== null ? getTlsAlertName(alertNumber) : null;
|
||||
|
||||
log.debug(this.name, `[${monitor.name}] TLS error: ${errorMessage}, alert: ${alertNumber} (${alertName})`);
|
||||
log.debug(
|
||||
this.name,
|
||||
`[${monitor.name}] TLS error: ${errorMessage}, alert: ${alertNumber} (${alertName})`
|
||||
);
|
||||
|
||||
resolve({
|
||||
success: false,
|
||||
|
||||
@@ -29,7 +29,7 @@ class WebSocketMonitorType extends MonitorType {
|
||||
* @inheritdoc
|
||||
*/
|
||||
async check(monitor, heartbeat, _server) {
|
||||
const [ message, code ] = await this.attemptUpgrade(monitor);
|
||||
const [message, code] = await this.attemptUpgrade(monitor);
|
||||
|
||||
if (typeof code !== "undefined") {
|
||||
// If returned status code matches user controlled accepted status code(default 1000), return success
|
||||
@@ -70,17 +70,20 @@ class WebSocketMonitorType extends MonitorType {
|
||||
ws.onerror = (error) => {
|
||||
// Give user the choice to ignore Sec-WebSocket-Accept header for non compliant servers
|
||||
// Header in HTTP 101 Switching Protocols response from server, technically already upgraded to WS
|
||||
if (monitor.wsIgnoreSecWebsocketAcceptHeader && error.message === "Invalid Sec-WebSocket-Accept header") {
|
||||
resolve([ "1000 - OK", 1000 ]);
|
||||
if (
|
||||
monitor.wsIgnoreSecWebsocketAcceptHeader &&
|
||||
error.message === "Invalid Sec-WebSocket-Accept header"
|
||||
) {
|
||||
resolve(["1000 - OK", 1000]);
|
||||
return;
|
||||
}
|
||||
// Upgrade failed, return message to user
|
||||
resolve([ error.message, error.code ]);
|
||||
resolve([error.message, error.code]);
|
||||
};
|
||||
|
||||
ws.onclose = (event) => {
|
||||
// Return the close code, if connection didn't close cleanly, return the reason if present
|
||||
resolve([ event.wasClean ? event.code.toString() + " - OK" : event.reason, event.code ]);
|
||||
resolve([event.wasClean ? event.code.toString() + " - OK" : event.reason, event.code]);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user