Merge branch 'master' into feature/#1221-clickable-hostaname-on-status-page

This commit is contained in:
Matthew Nickson
2022-06-18 23:53:35 +01:00
committed by GitHub
92 changed files with 3655 additions and 552 deletions

View File

@@ -22,7 +22,10 @@ async function sendNotificationList(socket) {
]);
for (let bean of list) {
result.push(bean.export());
let notificationObject = bean.export();
notificationObject.isDefault = (notificationObject.isDefault === 1);
notificationObject.active = (notificationObject.active === 1);
result.push(notificationObject);
}
io.to(socket.userID).emit("notificationList", result);

View File

@@ -59,6 +59,8 @@ class Database {
"patch-status-page-footer-css.sql": true,
"patch-added-mqtt-monitor.sql": true,
"patch-add-clickable-status-page-link.sql": true,
"patch-add-sqlserver-monitor.sql": true,
"patch-add-other-auth.sql": { parents: [ "patch-monitor-basic-auth.sql" ] },
};
/**

View File

@@ -7,7 +7,7 @@ dayjs.extend(timezone);
const axios = require("axios");
const { Prometheus } = require("../prometheus");
const { log, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util");
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mqttAsync } = require("../util-server");
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, mqttAsync, setSetting, httpNtlm } = require("../util-server");
const { R } = require("redbean-node");
const { BeanModel } = require("redbean-node/dist/bean-model");
const { Notification } = require("../notification");
@@ -17,6 +17,12 @@ const version = require("../../package.json").version;
const apicache = require("../modules/apicache");
const { UptimeKumaServer } = require("../uptime-kuma-server");
const axiosCachedDnsResolve = require("esm-wallaby")(module)("axios-cached-dns-resolve");
// create an axios client instance with the cached DNS resolve interceptor
const axiosClient = axios.create();
axiosCachedDnsResolve.registerInterceptor(axiosClient);
/**
* status:
* 0 = DOWN
@@ -93,7 +99,12 @@ class Monitor extends BeanModel {
mqttUsername: this.mqttUsername,
mqttPassword: this.mqttPassword,
mqttTopic: this.mqttTopic,
mqttSuccessMessage: this.mqttSuccessMessage
mqttSuccessMessage: this.mqttSuccessMessage,
databaseConnectionString: this.databaseConnectionString,
databaseQuery: this.databaseQuery,
authMethod: this.authMethod,
authWorkstation: this.authWorkstation,
authDomain: this.authDomain,
};
if (includeSensitiveData) {
@@ -219,7 +230,7 @@ class Monitor extends BeanModel {
// HTTP basic auth
let basicAuthHeader = {};
if (this.basic_auth_user) {
if (this.auth_method === "basic") {
basicAuthHeader = {
"Authorization": "Basic " + this.encodeBase64(this.basic_auth_user, this.basic_auth_pass),
};
@@ -270,7 +281,21 @@ class Monitor extends BeanModel {
log.debug("monitor", `[${this.name}] Axios Options: ${JSON.stringify(options)}`);
log.debug("monitor", `[${this.name}] Axios Request`);
let res = await axios.request(options);
let res;
if (this.auth_method === "ntlm") {
options.httpsAgent.keepAlive = true;
res = await httpNtlm(options, {
username: this.basic_auth_user,
password: this.basic_auth_pass,
domain: this.authDomain,
workstation: this.authWorkstation ? this.authWorkstation : undefined
});
} else {
res = await axiosClient.request(options);
}
bean.msg = `${res.status} - ${res.statusText}`;
bean.ping = dayjs().valueOf() - startTime;
@@ -318,7 +343,11 @@ class Monitor extends BeanModel {
bean.msg += ", keyword is found";
bean.status = UP;
} else {
throw new Error(bean.msg + ", but keyword is not found");
data = data.replace(/<[^>]*>?|[\n\r]|\s+/gm, " ");
if (data.length > 50) {
data = data.substring(0, 47) + "...";
}
throw new Error(bean.msg + ", but keyword is not in [" + data + "]");
}
}
@@ -383,7 +412,7 @@ class Monitor extends BeanModel {
// If the previous beat was down or pending we use the regular
// beatInterval/retryInterval in the setTimeout further below
if (previousBeat.status !== UP || msSinceLastBeat > beatInterval * 1000 + bufferTime) {
if (previousBeat.status !== (this.isUpsideDown() ? DOWN : UP) || msSinceLastBeat > beatInterval * 1000 + bufferTime) {
throw new Error("No heartbeat in the time window");
} else {
let timeout = beatInterval * 1000 - msSinceLastBeat;
@@ -411,7 +440,7 @@ class Monitor extends BeanModel {
throw new Error("Steam API Key not found");
}
let res = await axios.get(steamApiUrl, {
let res = await axiosClient.get(steamApiUrl, {
timeout: this.interval * 1000 * 0.8,
headers: {
"Accept": "*/*",
@@ -449,6 +478,14 @@ class Monitor extends BeanModel {
interval: this.interval,
});
bean.status = UP;
} else if (this.type === "sqlserver") {
let startTime = dayjs().valueOf();
await mssqlQuery(this.databaseConnectionString, this.databaseQuery);
bean.msg = "";
bean.status = UP;
bean.ping = dayjs().valueOf() - startTime;
} else {
bean.msg = "Unknown Monitor Type";
bean.status = PENDING;
@@ -499,7 +536,7 @@ class Monitor extends BeanModel {
}
if (bean.status === UP) {
log.info("monitor", `Monitor #${this.id} '${this.name}': Successful Response: ${bean.ping} ms | Interval: ${beatInterval} seconds | Type: ${this.type}`);
log.debug("monitor", `Monitor #${this.id} '${this.name}': Successful Response: ${bean.ping} ms | Interval: ${beatInterval} seconds | Type: ${this.type}`);
} else if (bean.status === PENDING) {
if (this.retryInterval > 0) {
beatInterval = this.retryInterval;
@@ -843,10 +880,19 @@ class Monitor extends BeanModel {
if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) {
const notificationList = await Monitor.getNotificationList(this);
log.debug("monitor", "call sendCertNotificationByTargetDays");
await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 21, notificationList);
await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 14, notificationList);
await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 7, notificationList);
let notifyDays = await setting("tlsExpiryNotifyDays");
if (notifyDays == null || !Array.isArray(notifyDays)) {
// Reset Default
setSetting("tlsExpiryNotifyDays", [ 7, 14, 21 ], "general");
notifyDays = [ 7, 14, 21 ];
}
if (notifyDays != null && Array.isArray(notifyDays)) {
for (const day of notifyDays) {
log.debug("monitor", "call sendCertNotificationByTargetDays", day);
await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, day, notificationList);
}
}
}
}

View File

@@ -669,6 +669,11 @@ let needSetup = false;
bean.mqttPassword = monitor.mqttPassword;
bean.mqttTopic = monitor.mqttTopic;
bean.mqttSuccessMessage = monitor.mqttSuccessMessage;
bean.databaseConnectionString = monitor.databaseConnectionString;
bean.databaseQuery = monitor.databaseQuery;
bean.authMethod = monitor.authMethod;
bean.authWorkstation = monitor.authWorkstation;
bean.authDomain = monitor.authDomain;
await R.store(bean);
@@ -1242,8 +1247,11 @@ let needSetup = false;
method: monitorListData[i].method || "GET",
body: monitorListData[i].body,
headers: monitorListData[i].headers,
authMethod: monitorListData[i].authMethod,
basic_auth_user: monitorListData[i].basic_auth_user,
basic_auth_pass: monitorListData[i].basic_auth_pass,
authWorkstation: monitorListData[i].authWorkstation,
authDomain: monitorListData[i].authDomain,
interval: monitorListData[i].interval,
retryInterval: retryInterval,
hostname: monitorListData[i].hostname,

View File

@@ -63,7 +63,10 @@ module.exports.cloudflaredSocketHandler = (socket) => {
socket.on(prefix + "stop", async (currentPassword, callback) => {
try {
checkLogin(socket);
await doubleCheckPassword(socket, currentPassword);
const disabledAuth = await setting("disableAuth");
if (!disabledAuth) {
await doubleCheckPassword(socket, currentPassword);
}
cloudflared.stop();
} catch (error) {
callback({

View File

@@ -10,6 +10,8 @@ const chardet = require("chardet");
const mqtt = require("mqtt");
const chroma = require("chroma-js");
const { badgeConstants } = require("./config");
const mssql = require("mssql");
const { NtlmClient } = require("axios-ntlm");
// From ping-lite
exports.WIN = /^win/.test(process.platform);
@@ -172,6 +174,26 @@ exports.mqttAsync = function (hostname, topic, okMessage, options = {}) {
});
};
/**
* Use NTLM Auth for a http request.
* @param {Object} options The http request options
* @param {Object} ntlmOptions The auth options
* @returns {Promise<(string[]|Object[]|Object)>}
*/
exports.httpNtlm = function (options, ntlmOptions) {
return new Promise((resolve, reject) => {
let client = NtlmClient(ntlmOptions);
client(options)
.then((resp) => {
resolve(resp);
})
.catch((err) => {
reject(err);
});
});
};
/**
* Resolves a given record using the specified DNS server
* @param {string} hostname The hostname of the record to lookup
@@ -207,6 +229,31 @@ 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)>}
*/
exports.mssqlQuery = function (connectionString, query) {
return new Promise((resolve, reject) => {
mssql.on("error", err => {
reject(err);
});
mssql.connect(connectionString).then(pool => {
return pool.request()
.query(query);
}).then(result => {
resolve(result);
}).catch(err => {
reject(err);
}).finally(() => {
mssql.close();
});
});
};
/**
* Retrieve value of setting based on key
* @param {string} key Key of setting to retrieve