mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-08-08 14:23:22 +08:00
Merge branch 'master' into feature/#1221-clickable-hostaname-on-status-page
This commit is contained in:
@@ -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);
|
||||
|
@@ -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" ] },
|
||||
};
|
||||
|
||||
/**
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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({
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user