mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-11-23 23:04:04 +00:00
Merge pull request #1499 from Computroniks/add-JSDoc-comments
Add JSDoc comments for server/*/*
This commit is contained in:
commit
d6c3fdb6fb
37 changed files with 849 additions and 212 deletions
|
@ -2,6 +2,11 @@ const { R } = require("redbean-node");
|
|||
|
||||
class TwoFA {
|
||||
|
||||
/**
|
||||
* Disable 2FA for specified user
|
||||
* @param {number} userID ID of user to disable
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async disable2FA(userID) {
|
||||
return await R.exec("UPDATE `user` SET twofa_status = 0 WHERE id = ? ", [
|
||||
userID,
|
||||
|
|
|
@ -5,10 +5,10 @@ const { setting } = require("./util-server");
|
|||
const { loginRateLimiter } = require("./rate-limiter");
|
||||
|
||||
/**
|
||||
*
|
||||
* @param username : string
|
||||
* @param password : string
|
||||
* @returns {Promise<Bean|null>}
|
||||
* Login to web app
|
||||
* @param {string} username
|
||||
* @param {string} password
|
||||
* @returns {Promise<(Bean|null)>}
|
||||
*/
|
||||
exports.login = async function (username, password) {
|
||||
if (typeof username !== "string" || typeof password !== "string") {
|
||||
|
@ -34,11 +34,17 @@ exports.login = async function (username, password) {
|
|||
};
|
||||
|
||||
/**
|
||||
* A function that checks if a user is logged in.
|
||||
* @param {string} username The username of the user to check for.
|
||||
* @param {function} callback The callback to call when done, with an error and result parameter.
|
||||
*
|
||||
* Generated by Trelent
|
||||
* Callback for myAuthorizer
|
||||
* @callback myAuthorizerCB
|
||||
* @param {any} err Any error encountered
|
||||
* @param {boolean} authorized Is the client authorized?
|
||||
*/
|
||||
|
||||
/**
|
||||
* Custom authorizer for express-basic-auth
|
||||
* @param {string} username
|
||||
* @param {string} password
|
||||
* @param {myAuthorizerCB} callback
|
||||
*/
|
||||
function myAuthorizer(username, password, callback) {
|
||||
// Login Rate Limit
|
||||
|
|
|
@ -7,6 +7,7 @@ exports.latestVersion = null;
|
|||
|
||||
let interval;
|
||||
|
||||
/** Start 48 hour check interval */
|
||||
exports.startInterval = () => {
|
||||
let check = async () => {
|
||||
try {
|
||||
|
@ -42,6 +43,11 @@ exports.startInterval = () => {
|
|||
interval = setInterval(check, 3600 * 1000 * 48);
|
||||
};
|
||||
|
||||
/**
|
||||
* Enable the check update feature
|
||||
* @param {boolean} value Should the check update feature be enabled?
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
exports.enableCheckUpdate = async (value) => {
|
||||
await setSetting("checkUpdate", value);
|
||||
|
||||
|
|
|
@ -9,10 +9,9 @@ const { setting } = require("./util-server");
|
|||
const checkVersion = require("./check-version");
|
||||
|
||||
/**
|
||||
* Send a list of notifications to the user.
|
||||
* @param {Socket} socket The socket object that is connected to the client.
|
||||
*
|
||||
* Generated by Trelent
|
||||
* Send list of notification providers to client
|
||||
* @param {Socket} socket Socket.io socket instance
|
||||
* @returns {Promise<Bean[]>}
|
||||
*/
|
||||
async function sendNotificationList(socket) {
|
||||
const timeLogger = new TimeLogger();
|
||||
|
@ -35,8 +34,11 @@ async function sendNotificationList(socket) {
|
|||
|
||||
/**
|
||||
* Send Heartbeat History list to socket
|
||||
* @param toUser True = send to all browsers with the same user id, False = send to the current browser only
|
||||
* @param overwrite Overwrite client-side's heartbeat list
|
||||
* @param {Socket} socket Socket.io instance
|
||||
* @param {number} monitorID ID of monitor to send heartbeat history
|
||||
* @param {boolean} [toUser=false] True = send to all browsers with the same user id, False = send to the current browser only
|
||||
* @param {boolean} [overwrite=false] Overwrite client-side's heartbeat list
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite = false) {
|
||||
const timeLogger = new TimeLogger();
|
||||
|
@ -62,11 +64,12 @@ async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite =
|
|||
}
|
||||
|
||||
/**
|
||||
* Important Heart beat list (aka event list)
|
||||
* @param socket
|
||||
* @param monitorID
|
||||
* @param toUser True = send to all browsers with the same user id, False = send to the current browser only
|
||||
* @param overwrite Overwrite client-side's heartbeat list
|
||||
* Important Heart beat list (aka event list)
|
||||
* @param {Socket} socket Socket.io instance
|
||||
* @param {number} monitorID ID of monitor to send heartbeat history
|
||||
* @param {boolean} [toUser=false] True = send to all browsers with the same user id, False = send to the current browser only
|
||||
* @param {boolean} [overwrite=false] Overwrite client-side's heartbeat list
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function sendImportantHeartbeatList(socket, monitorID, toUser = false, overwrite = false) {
|
||||
const timeLogger = new TimeLogger();
|
||||
|
@ -91,9 +94,8 @@ async function sendImportantHeartbeatList(socket, monitorID, toUser = false, ove
|
|||
}
|
||||
|
||||
/**
|
||||
* Delivers proxy list
|
||||
*
|
||||
* @param socket
|
||||
* Emit proxy list to client
|
||||
* @param {Socket} socket Socket.io socket instance
|
||||
* @return {Promise<Bean[]>}
|
||||
*/
|
||||
async function sendProxyList(socket) {
|
||||
|
@ -109,9 +111,8 @@ async function sendProxyList(socket) {
|
|||
|
||||
/**
|
||||
* Emits the version information to the client.
|
||||
* @param {Socket} socket The socket object that is connected to the client.
|
||||
*
|
||||
* Generated by Trelent
|
||||
* @param {Socket} socket Socket.io socket instance
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function sendInfo(socket) {
|
||||
socket.emit("info", {
|
||||
|
|
|
@ -68,6 +68,10 @@ class Database {
|
|||
|
||||
static noReject = true;
|
||||
|
||||
/**
|
||||
* Initialize the database
|
||||
* @param {Object} args Arguments to initialize DB with
|
||||
*/
|
||||
static init(args) {
|
||||
// Data Directory (must be end with "/")
|
||||
Database.dataDir = process.env.DATA_DIR || args["data-dir"] || "./data/";
|
||||
|
@ -85,6 +89,15 @@ class Database {
|
|||
log.info("db", `Data Dir: ${Database.dataDir}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the database
|
||||
* @param {boolean} [testMode=false] Should the connection be
|
||||
* started in test mode?
|
||||
* @param {boolean} [autoloadModels=true] Should models be
|
||||
* automatically loaded?
|
||||
* @param {boolean} [noLog=false] Should logs not be output?
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async connect(testMode = false, autoloadModels = true, noLog = false) {
|
||||
const acquireConnectionTimeout = 120 * 1000;
|
||||
|
||||
|
@ -144,6 +157,7 @@ class Database {
|
|||
}
|
||||
}
|
||||
|
||||
/** Patch the database */
|
||||
static async patch() {
|
||||
let version = parseInt(await setting("database_version"));
|
||||
|
||||
|
@ -189,7 +203,9 @@ class Database {
|
|||
}
|
||||
|
||||
/**
|
||||
* Patch DB using new process
|
||||
* Call it from patch() only
|
||||
* @private
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async patch2() {
|
||||
|
@ -296,9 +312,12 @@ class Database {
|
|||
}
|
||||
|
||||
/**
|
||||
* Patch database using new patching process
|
||||
* Used it patch2() only
|
||||
* @private
|
||||
* @param sqlFilename
|
||||
* @param databasePatchedFiles
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async patch2Recursion(sqlFilename, databasePatchedFiles) {
|
||||
let value = this.patchList[sqlFilename];
|
||||
|
@ -333,12 +352,12 @@ class Database {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sadly, multi sql statements is not supported by many sqlite libraries, I have to implement it myself
|
||||
* @param filename
|
||||
* Load an SQL file and execute it
|
||||
* @param filename Filename of SQL file to import
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async importSQLFile(filename) {
|
||||
|
||||
// Sadly, multi sql statements is not supported by many sqlite libraries, I have to implement it myself
|
||||
await R.getCell("SELECT 1");
|
||||
|
||||
let text = fs.readFileSync(filename).toString();
|
||||
|
@ -366,6 +385,10 @@ class Database {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aquire a direct connection to database
|
||||
* @returns {any}
|
||||
*/
|
||||
static getBetterSQLite3Database() {
|
||||
return R.knex.client.acquireConnection();
|
||||
}
|
||||
|
@ -401,7 +424,7 @@ class Database {
|
|||
/**
|
||||
* One backup one time in this process.
|
||||
* Reset this.backupPath if you want to backup again
|
||||
* @param version
|
||||
* @param {string} version Version code of backup
|
||||
*/
|
||||
static backup(version) {
|
||||
if (! this.backupPath) {
|
||||
|
@ -423,9 +446,7 @@ class Database {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
/** Restore from most recent backup */
|
||||
static restore() {
|
||||
if (this.backupPath) {
|
||||
log.error("db", "Patching the database failed!!! Restoring the backup");
|
||||
|
@ -467,6 +488,7 @@ class Database {
|
|||
}
|
||||
}
|
||||
|
||||
/** Get the size of the database */
|
||||
static getSize() {
|
||||
log.debug("db", "Database.getSize()");
|
||||
let stats = fs.statSync(Database.path);
|
||||
|
@ -474,6 +496,10 @@ class Database {
|
|||
return stats.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shrink the database
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async shrink() {
|
||||
await R.exec("VACUUM");
|
||||
}
|
||||
|
|
|
@ -8,10 +8,12 @@ const { log } = require("../src/util");
|
|||
let ImageDataURI = (() => {
|
||||
|
||||
/**
|
||||
* @param {string} dataURI - A string that is a valid Data URI.
|
||||
* @returns {?Object} An object with properties "imageType" and "dataBase64". The former is the image type, e.g., "png", and the latter is a base64 encoded string of the image's binary data. If it fails to parse, returns null instead of an object.
|
||||
*
|
||||
* Generated by Trelent
|
||||
* Decode the data:image/ URI
|
||||
* @param {string} dataURI data:image/ URI to decode
|
||||
* @returns {?Object} An object with properties "imageType" and "dataBase64".
|
||||
* The former is the image type, e.g., "png", and the latter is a base64
|
||||
* encoded string of the image's binary data. If it fails to parse, returns
|
||||
* null instead of an object.
|
||||
*/
|
||||
function decode(dataURI) {
|
||||
if (!/data:image\//.test(dataURI)) {
|
||||
|
@ -28,11 +30,11 @@ let ImageDataURI = (() => {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {Buffer} data - The image data to be encoded.
|
||||
* @param {String} mediaType - The type of the image, e.g., "image/png".
|
||||
* @returns {String|null} A string representing the base64-encoded version of the given Buffer object or null if an error occurred.
|
||||
*
|
||||
* Generated by Trelent
|
||||
* Endcode an image into data:image/ URI
|
||||
* @param {(Buffer|string)} data Data to encode
|
||||
* @param {string} mediaType Media type of data
|
||||
* @returns {(string|null)} A string representing the base64-encoded
|
||||
* version of the given Buffer object or null if an error occurred.
|
||||
*/
|
||||
function encode(data, mediaType) {
|
||||
if (!data || !mediaType) {
|
||||
|
@ -48,11 +50,10 @@ let ImageDataURI = (() => {
|
|||
}
|
||||
|
||||
/**
|
||||
* Converts a data URI to a file path.
|
||||
* @param {string} dataURI The Data URI of the image.
|
||||
* @param {string} [filePath] The path where the image will be saved, defaults to "./".
|
||||
*
|
||||
* Generated by Trelent
|
||||
* Write data URI to file
|
||||
* @param {string} dataURI data:image/ URI
|
||||
* @param {string} [filePath] Path to write file to
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
function outputFile(dataURI, filePath) {
|
||||
filePath = filePath || "./";
|
||||
|
|
|
@ -10,6 +10,11 @@ const jobs = [
|
|||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Initialize background jobs
|
||||
* @param {Object} args Arguments to pass to workers
|
||||
* @returns {Bree}
|
||||
*/
|
||||
const initBackgroundJobs = function (args) {
|
||||
bree = new Bree({
|
||||
root: path.resolve("server", "jobs"),
|
||||
|
|
|
@ -2,12 +2,22 @@ const { parentPort, workerData } = require("worker_threads");
|
|||
const Database = require("../database");
|
||||
const path = require("path");
|
||||
|
||||
/**
|
||||
* Send message to parent process for logging
|
||||
* since worker_thread does not have access to stdout, this is used
|
||||
* instead of console.log()
|
||||
* @param {any} any The message to log
|
||||
*/
|
||||
const log = function (any) {
|
||||
if (parentPort) {
|
||||
parentPort.postMessage(any);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Exit the worker process
|
||||
* @param {number} error The status code to exit
|
||||
*/
|
||||
const exit = function (error) {
|
||||
if (error && error !== 0) {
|
||||
process.exit(error);
|
||||
|
@ -20,6 +30,7 @@ const exit = function (error) {
|
|||
}
|
||||
};
|
||||
|
||||
/** Connects to the database */
|
||||
const connectDb = async function () {
|
||||
const dbPath = path.join(
|
||||
process.env.DATA_DIR || workerData["data-dir"] || "./data/"
|
||||
|
|
|
@ -3,6 +3,12 @@ const { R } = require("redbean-node");
|
|||
|
||||
class Group extends BeanModel {
|
||||
|
||||
/**
|
||||
* Return an object that ready to parse to JSON for public
|
||||
* Only show necessary data to public
|
||||
* @param {boolean} [showTags=false] Should the JSON include monitor tags
|
||||
* @returns {Object}
|
||||
*/
|
||||
async toPublicJSON(showTags = false) {
|
||||
let monitorBeanList = await this.getMonitorList();
|
||||
let monitorList = [];
|
||||
|
@ -19,6 +25,10 @@ class Group extends BeanModel {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all monitors
|
||||
* @returns {Bean[]}
|
||||
*/
|
||||
async getMonitorList() {
|
||||
return R.convertToBeans("monitor", await R.getAll(`
|
||||
SELECT monitor.* FROM monitor, monitor_group
|
||||
|
|
|
@ -13,6 +13,11 @@ const { BeanModel } = require("redbean-node/dist/bean-model");
|
|||
*/
|
||||
class Heartbeat extends BeanModel {
|
||||
|
||||
/**
|
||||
* Return an object that ready to parse to JSON for public
|
||||
* Only show necessary data to public
|
||||
* @returns {Object}
|
||||
*/
|
||||
toPublicJSON() {
|
||||
return {
|
||||
status: this.status,
|
||||
|
@ -22,6 +27,10 @@ class Heartbeat extends BeanModel {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an object that ready to parse to JSON
|
||||
* @returns {Object}
|
||||
*/
|
||||
toJSON() {
|
||||
return {
|
||||
monitorID: this.monitor_id,
|
||||
|
|
|
@ -2,6 +2,11 @@ const { BeanModel } = require("redbean-node/dist/bean-model");
|
|||
|
||||
class Incident extends BeanModel {
|
||||
|
||||
/**
|
||||
* Return an object that ready to parse to JSON for public
|
||||
* Only show necessary data to public
|
||||
* @returns {Object}
|
||||
*/
|
||||
toPublicJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
|
|
|
@ -27,6 +27,7 @@ class Monitor extends BeanModel {
|
|||
/**
|
||||
* Return an object that ready to parse to JSON for public
|
||||
* Only show necessary data to public
|
||||
* @returns {Object}
|
||||
*/
|
||||
async toPublicJSON(showTags = false) {
|
||||
let obj = {
|
||||
|
@ -41,6 +42,7 @@ class Monitor extends BeanModel {
|
|||
|
||||
/**
|
||||
* Return an object that ready to parse to JSON
|
||||
* @returns {Object}
|
||||
*/
|
||||
async toJSON(includeSensitiveData = true) {
|
||||
|
||||
|
@ -101,6 +103,10 @@ class Monitor extends BeanModel {
|
|||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all tags applied to this monitor
|
||||
* @returns {Promise<LooseObject<any>[]>}
|
||||
*/
|
||||
async getTags() {
|
||||
return await R.getAll("SELECT mt.*, tag.name, tag.color FROM monitor_tag mt JOIN tag ON mt.tag_id = tag.id WHERE mt.monitor_id = ?", [ this.id ]);
|
||||
}
|
||||
|
@ -114,6 +120,10 @@ class Monitor extends BeanModel {
|
|||
return Buffer.from(user + ":" + pass).toString("base64");
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the TLS expiry notification enabled?
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isEnabledExpiryNotification() {
|
||||
return Boolean(this.expiryNotification);
|
||||
}
|
||||
|
@ -134,10 +144,18 @@ class Monitor extends BeanModel {
|
|||
return Boolean(this.upsideDown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get accepted status codes
|
||||
* @returns {Object}
|
||||
*/
|
||||
getAcceptedStatuscodes() {
|
||||
return JSON.parse(this.accepted_statuscodes_json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start monitor
|
||||
* @param {Server} io Socket server instance
|
||||
*/
|
||||
start(io) {
|
||||
let previousBeat = null;
|
||||
let retries = 0;
|
||||
|
@ -497,6 +515,7 @@ class Monitor extends BeanModel {
|
|||
|
||||
};
|
||||
|
||||
/** Get a heartbeat and handle errors */
|
||||
const safeBeat = async () => {
|
||||
try {
|
||||
await beat();
|
||||
|
@ -522,6 +541,7 @@ class Monitor extends BeanModel {
|
|||
}
|
||||
}
|
||||
|
||||
/** Stop monitor */
|
||||
stop() {
|
||||
clearTimeout(this.heartbeatInterval);
|
||||
this.isStop = true;
|
||||
|
@ -529,6 +549,10 @@ class Monitor extends BeanModel {
|
|||
this.prometheus().remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new prometheus instance
|
||||
* @returns {Prometheus}
|
||||
*/
|
||||
prometheus() {
|
||||
return new Prometheus(this);
|
||||
}
|
||||
|
@ -537,7 +561,7 @@ class Monitor extends BeanModel {
|
|||
* Helper Method:
|
||||
* returns URL object for further usage
|
||||
* returns null if url is invalid
|
||||
* @returns {null|URL}
|
||||
* @returns {(null|URL)}
|
||||
*/
|
||||
getUrl() {
|
||||
try {
|
||||
|
@ -550,7 +574,7 @@ class Monitor extends BeanModel {
|
|||
/**
|
||||
* Store TLS info to database
|
||||
* @param checkCertificateResult
|
||||
* @returns {Promise<object>}
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async updateTlsInfo(checkCertificateResult) {
|
||||
let tlsInfoBean = await R.findOne("monitor_tls_info", "monitor_id = ?", [
|
||||
|
@ -592,6 +616,12 @@ class Monitor extends BeanModel {
|
|||
return checkCertificateResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send statistics to clients
|
||||
* @param {Server} io Socket server instance
|
||||
* @param {number} monitorID ID of monitor to send
|
||||
* @param {number} userID ID of user to send to
|
||||
*/
|
||||
static async sendStats(io, monitorID, userID) {
|
||||
const hasClients = getTotalClientInRoom(io, userID) > 0;
|
||||
|
||||
|
@ -606,8 +636,8 @@ class Monitor extends BeanModel {
|
|||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param duration : int Hours
|
||||
* Send the average ping to user
|
||||
* @param {number} duration Hours
|
||||
*/
|
||||
static async sendAvgPing(duration, io, monitorID, userID) {
|
||||
const timeLogger = new TimeLogger();
|
||||
|
@ -627,6 +657,12 @@ class Monitor extends BeanModel {
|
|||
io.to(userID).emit("avgPing", monitorID, avgPing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send certificate information to client
|
||||
* @param {Server} io Socket server instance
|
||||
* @param {number} monitorID ID of monitor to send
|
||||
* @param {number} userID ID of user to send to
|
||||
*/
|
||||
static async sendCertInfo(io, monitorID, userID) {
|
||||
let tlsInfo = await R.findOne("monitor_tls_info", "monitor_id = ?", [
|
||||
monitorID,
|
||||
|
@ -640,7 +676,8 @@ class Monitor extends BeanModel {
|
|||
* Uptime with calculation
|
||||
* Calculation based on:
|
||||
* https://www.uptrends.com/support/kb/reporting/calculation-of-uptime-and-downtime
|
||||
* @param duration : int Hours
|
||||
* @param {number} duration Hours
|
||||
* @param {number} monitorID ID of monitor to calculate
|
||||
*/
|
||||
static async calcUptime(duration, monitorID) {
|
||||
const timeLogger = new TimeLogger();
|
||||
|
@ -706,13 +743,23 @@ class Monitor extends BeanModel {
|
|||
|
||||
/**
|
||||
* Send Uptime
|
||||
* @param duration : int Hours
|
||||
* @param {number} duration Hours
|
||||
* @param {Server} io Socket server instance
|
||||
* @param {number} monitorID ID of monitor to send
|
||||
* @param {number} userID ID of user to send to
|
||||
*/
|
||||
static async sendUptime(duration, io, monitorID, userID) {
|
||||
const uptime = await this.calcUptime(duration, monitorID);
|
||||
io.to(userID).emit("uptime", monitorID, duration, uptime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Has status of monitor changed since last beat?
|
||||
* @param {boolean} isFirstBeat Is this the first beat of this monitor?
|
||||
* @param {const} previousBeatStatus Status of the previous beat
|
||||
* @param {const} currentBeatStatus Status of the current beat
|
||||
* @returns {boolean} True if is an important beat else false
|
||||
*/
|
||||
static isImportantBeat(isFirstBeat, previousBeatStatus, currentBeatStatus) {
|
||||
// * ? -> ANY STATUS = important [isFirstBeat]
|
||||
// UP -> PENDING = not important
|
||||
|
@ -731,6 +778,12 @@ class Monitor extends BeanModel {
|
|||
return isImportant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a notification about a monitor
|
||||
* @param {boolean} isFirstBeat Is this beat the first of this monitor?
|
||||
* @param {Monitor} monitor The monitor to send a notificaton about
|
||||
* @param {Bean} bean Status information about monitor
|
||||
*/
|
||||
static async sendNotification(isFirstBeat, monitor, bean) {
|
||||
if (!isFirstBeat || bean.status === DOWN) {
|
||||
const notificationList = await Monitor.getNotificationList(monitor);
|
||||
|
@ -755,6 +808,11 @@ class Monitor extends BeanModel {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of notification providers for a given monitor
|
||||
* @param {Monitor} monitor Monitor to get notification providers for
|
||||
* @returns {Promise<LooseObject<any>[]>}
|
||||
*/
|
||||
static async getNotificationList(monitor) {
|
||||
let notificationList = await R.getAll("SELECT notification.* FROM notification, monitor_notification WHERE monitor_id = ? AND monitor_notification.notification_id = notification.id ", [
|
||||
monitor.id,
|
||||
|
@ -762,6 +820,10 @@ class Monitor extends BeanModel {
|
|||
return notificationList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send notification about a certificate
|
||||
* @param {Object} tlsInfoObject Information about certificate
|
||||
*/
|
||||
async sendCertNotification(tlsInfoObject) {
|
||||
if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) {
|
||||
const notificationList = await Monitor.getNotificationList(this);
|
||||
|
@ -773,6 +835,14 @@ class Monitor extends BeanModel {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a certificate notification when certificate expires in less
|
||||
* than target days
|
||||
* @param {number} daysRemaining Number of days remaining on certifcate
|
||||
* @param {number} targetDays Number of days to alert after
|
||||
* @param {LooseObject<any>[]} notificationList List of notification providers
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async sendCertNotificationByTargetDays(daysRemaining, targetDays, notificationList) {
|
||||
|
||||
if (daysRemaining > targetDays) {
|
||||
|
@ -820,6 +890,11 @@ class Monitor extends BeanModel {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status of the previous heartbeat
|
||||
* @param {number} monitorID ID of monitor to check
|
||||
* @returns {Promise<LooseObject<any>>}
|
||||
*/
|
||||
static async getPreviousHeartbeat(monitorID) {
|
||||
return await R.getRow(`
|
||||
SELECT status, time FROM heartbeat
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||
|
||||
class Proxy extends BeanModel {
|
||||
/**
|
||||
* Return an object that ready to parse to JSON
|
||||
* @returns {Object}
|
||||
*/
|
||||
toJSON() {
|
||||
return {
|
||||
id: this._id,
|
||||
|
|
|
@ -6,6 +6,7 @@ class StatusPage extends BeanModel {
|
|||
static domainMappingList = { };
|
||||
|
||||
/**
|
||||
* Loads domain mapping from DB
|
||||
* Return object like this: { "test-uptime.kuma.pet": "default" }
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
|
@ -17,6 +18,12 @@ class StatusPage extends BeanModel {
|
|||
`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send status page list to client
|
||||
* @param {Server} io io Socket server instance
|
||||
* @param {Socket} socket Socket.io instance
|
||||
* @returns {Promise<Bean[]>}
|
||||
*/
|
||||
static async sendStatusPageList(io, socket) {
|
||||
let result = {};
|
||||
|
||||
|
@ -30,6 +37,11 @@ class StatusPage extends BeanModel {
|
|||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update list of domain names
|
||||
* @param {string[]} domainNameList
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async updateDomainNameList(domainNameList) {
|
||||
|
||||
if (!Array.isArray(domainNameList)) {
|
||||
|
@ -69,6 +81,10 @@ class StatusPage extends BeanModel {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of domain names
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
getDomainNameList() {
|
||||
let domainList = [];
|
||||
for (let domain in StatusPage.domainMappingList) {
|
||||
|
@ -81,6 +97,10 @@ class StatusPage extends BeanModel {
|
|||
return domainList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an object that ready to parse to JSON
|
||||
* @returns {Object}
|
||||
*/
|
||||
async toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
|
@ -98,6 +118,11 @@ class StatusPage extends BeanModel {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an object that ready to parse to JSON for public
|
||||
* Only show necessary data to public
|
||||
* @returns {Object}
|
||||
*/
|
||||
async toPublicJSON() {
|
||||
return {
|
||||
slug: this.slug,
|
||||
|
@ -113,12 +138,20 @@ class StatusPage extends BeanModel {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert slug to status page ID
|
||||
* @param {string} slug
|
||||
*/
|
||||
static async slugToID(slug) {
|
||||
return await R.getCell("SELECT id FROM status_page WHERE slug = ? ", [
|
||||
slug
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path to the icon for the page
|
||||
* @returns {string}
|
||||
*/
|
||||
getIcon() {
|
||||
if (!this.icon) {
|
||||
return "/icon.svg";
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||
|
||||
class Tag extends BeanModel {
|
||||
|
||||
/**
|
||||
* Return an object that ready to parse to JSON
|
||||
* @returns {Object}
|
||||
*/
|
||||
toJSON() {
|
||||
return {
|
||||
id: this._id,
|
||||
|
|
|
@ -3,12 +3,11 @@ const passwordHash = require("../password-hash");
|
|||
const { R } = require("redbean-node");
|
||||
|
||||
class User extends BeanModel {
|
||||
|
||||
/**
|
||||
*
|
||||
* Reset user password
|
||||
* Fix #1510, as in the context reset-password.js, there is no auto model mapping. Call this static function instead.
|
||||
* @param userID
|
||||
* @param newPassword
|
||||
* @param {number} userID ID of user to update
|
||||
* @param {string} newPassword
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async resetPassword(userID, newPassword) {
|
||||
|
@ -19,8 +18,8 @@ class User extends BeanModel {
|
|||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param newPassword
|
||||
* Reset this users password
|
||||
* @param {string} newPassword
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async resetPassword(newPassword) {
|
||||
|
|
|
@ -13,27 +13,49 @@ let t = {
|
|||
|
||||
let instances = [];
|
||||
|
||||
/**
|
||||
* Does a === b
|
||||
* @param {any} a
|
||||
* @returns {function(any): boolean}
|
||||
*/
|
||||
let matches = function (a) {
|
||||
return function (b) {
|
||||
return a === b;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Does a!==b
|
||||
* @param {any} a
|
||||
* @returns {function(any): boolean}
|
||||
*/
|
||||
let doesntMatch = function (a) {
|
||||
return function (b) {
|
||||
return !matches(a)(b);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get log duration
|
||||
* @param {number} d Time in ms
|
||||
* @param {string} prefix Prefix for log
|
||||
* @returns {string} Coloured log string
|
||||
*/
|
||||
let logDuration = function (d, prefix) {
|
||||
let str = d > 1000 ? (d / 1000).toFixed(2) + "sec" : d + "ms";
|
||||
return "\x1b[33m- " + (prefix ? prefix + " " : "") + str + "\x1b[0m";
|
||||
};
|
||||
|
||||
/**
|
||||
* Get safe headers
|
||||
* @param {Object} res Express response object
|
||||
* @returns {Object}
|
||||
*/
|
||||
function getSafeHeaders(res) {
|
||||
return res.getHeaders ? res.getHeaders() : res._headers;
|
||||
}
|
||||
|
||||
/** Constructor for ApiCache instance */
|
||||
function ApiCache() {
|
||||
let memCache = new MemoryCache();
|
||||
|
||||
|
@ -70,10 +92,10 @@ function ApiCache() {
|
|||
|
||||
/**
|
||||
* Logs a message to the console if the `DEBUG` environment variable is set.
|
||||
* @param {string} a - The first argument to log.
|
||||
* @param {string} b - The second argument to log.
|
||||
* @param {string} c - The third argument to log.
|
||||
* @param {string} d - The fourth argument to log, and so on... (optional)
|
||||
* @param {string} a The first argument to log.
|
||||
* @param {string} b The second argument to log.
|
||||
* @param {string} c The third argument to log.
|
||||
* @param {string} d The fourth argument to log, and so on... (optional)
|
||||
*
|
||||
* Generated by Trelent
|
||||
*/
|
||||
|
@ -90,8 +112,8 @@ function ApiCache() {
|
|||
* Returns true if the given request and response should be logged.
|
||||
* @param {Object} request The HTTP request object.
|
||||
* @param {Object} response The HTTP response object.
|
||||
*
|
||||
* Generated by Trelent
|
||||
* @param {function(Object, Object):boolean} toggle
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function shouldCacheResponse(request, response, toggle) {
|
||||
let opt = globalOptions;
|
||||
|
@ -116,10 +138,9 @@ function ApiCache() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Adds a key to the index.
|
||||
* @param {string} key The key to add.
|
||||
*
|
||||
* Generated by Trelent
|
||||
* Add key to index array
|
||||
* @param {string} key Key to add
|
||||
* @param {Object} req Express request object
|
||||
*/
|
||||
function addIndexEntries(key, req) {
|
||||
let groupName = req.apicacheGroup;
|
||||
|
@ -135,8 +156,11 @@ function ApiCache() {
|
|||
|
||||
/**
|
||||
* Returns a new object containing only the whitelisted headers.
|
||||
* @param {Object} headers The original object of header names and values.
|
||||
* @param {Array.<string>} globalOptions.headerWhitelist An array of strings representing the whitelisted header names to keep in the output object.
|
||||
* @param {Object} headers The original object of header names and
|
||||
* values.
|
||||
* @param {string[]} globalOptions.headerWhitelist An array of
|
||||
* strings representing the whitelisted header names to keep in the
|
||||
* output object.
|
||||
*
|
||||
* Generated by Trelent
|
||||
*/
|
||||
|
@ -152,8 +176,10 @@ function ApiCache() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Create a cache object
|
||||
* @param {Object} headers The response headers to filter.
|
||||
* @returns {Object} A new object containing only the whitelisted response headers.
|
||||
* @returns {Object} A new object containing only the whitelisted
|
||||
* response headers.
|
||||
*
|
||||
* Generated by Trelent
|
||||
*/
|
||||
|
@ -170,8 +196,9 @@ function ApiCache() {
|
|||
/**
|
||||
* Sets a cache value for the given key.
|
||||
* @param {string} key The cache key to set.
|
||||
* @param {*} value The cache value to set.
|
||||
* @param {number} duration How long in milliseconds the cached response should be valid for (defaults to 1 hour).
|
||||
* @param {any} value The cache value to set.
|
||||
* @param {number} duration How long in milliseconds the cached
|
||||
* response should be valid for (defaults to 1 hour).
|
||||
*
|
||||
* Generated by Trelent
|
||||
*/
|
||||
|
@ -199,7 +226,8 @@ function ApiCache() {
|
|||
|
||||
/**
|
||||
* Appends content to the response.
|
||||
* @param {string|Buffer} content The content to append.
|
||||
* @param {Object} res Express response object
|
||||
* @param {(string|Buffer)} content The content to append.
|
||||
*
|
||||
* Generated by Trelent
|
||||
*/
|
||||
|
@ -229,11 +257,15 @@ function ApiCache() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Monkeypatches the response object to add cache control headers and create a cache object.
|
||||
* @param {Object} req - The request object.
|
||||
* @param {Object} res - The response object.
|
||||
*
|
||||
* Generated by Trelent
|
||||
* Monkeypatches the response object to add cache control headers
|
||||
* and create a cache object.
|
||||
* @param {Object} req Express request object
|
||||
* @param {Object} res Express response object
|
||||
* @param {function} next Function to call next
|
||||
* @param {string} key Key to add response as
|
||||
* @param {number} duration Time to cache response for
|
||||
* @param {string} strDuration Duration in string form
|
||||
* @param {function(Object, Object):boolean} toggle
|
||||
*/
|
||||
function makeResponseCacheable(req, res, next, key, duration, strDuration, toggle) {
|
||||
// monkeypatch res.end to create cache object
|
||||
|
@ -302,11 +334,15 @@ function ApiCache() {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {Request} request
|
||||
* @param {Response} response
|
||||
* @returns {boolean|undefined} true if the request should be cached, false otherwise. If undefined, defaults to true.
|
||||
*
|
||||
* Generated by Trelent
|
||||
* Send a cached response to client
|
||||
* @param {Request} request Express request object
|
||||
* @param {Response} response Express response object
|
||||
* @param {object} cacheObject Cache object to send
|
||||
* @param {function(Object, Object):boolean} toggle
|
||||
* @param {function} next Function to call next
|
||||
* @param {number} duration Not used
|
||||
* @returns {boolean|undefined} true if the request should be
|
||||
* cached, false otherwise. If undefined, defaults to true.
|
||||
*/
|
||||
function sendCachedResponse(request, response, cacheObject, toggle, next, duration) {
|
||||
if (toggle && !toggle(request, response)) {
|
||||
|
@ -348,12 +384,19 @@ function ApiCache() {
|
|||
return response.end(data, cacheObject.encoding);
|
||||
}
|
||||
|
||||
/** Sync caching options */
|
||||
function syncOptions() {
|
||||
for (let i in middlewareOptions) {
|
||||
Object.assign(middlewareOptions[i].options, globalOptions, middlewareOptions[i].localOptions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear key from cache
|
||||
* @param {string} target Key to clear
|
||||
* @param {boolean} isAutomatic Is the key being cleared automatically
|
||||
* @returns {number}
|
||||
*/
|
||||
this.clear = function (target, isAutomatic) {
|
||||
let group = index.groups[target];
|
||||
let redis = globalOptions.redisClient;
|
||||
|
@ -430,10 +473,11 @@ function ApiCache() {
|
|||
|
||||
/**
|
||||
* Converts a duration string to an integer number of milliseconds.
|
||||
* @param {string} duration - The string to convert.
|
||||
* @returns {number} The converted value in milliseconds, or the defaultDuration if it can't be parsed.
|
||||
*
|
||||
* Generated by Trelent
|
||||
* @param {(string|number)} duration The string to convert.
|
||||
* @param {number} defaultDuration The default duration to return if
|
||||
* can't parse duration
|
||||
* @returns {number} The converted value in milliseconds, or the
|
||||
* defaultDuration if it can't be parsed.
|
||||
*/
|
||||
function parseDuration(duration, defaultDuration) {
|
||||
if (typeof duration === "number") {
|
||||
|
@ -457,17 +501,24 @@ function ApiCache() {
|
|||
return defaultDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse duration
|
||||
* @param {(number|string)} duration
|
||||
* @returns {number} Duration parsed to a number
|
||||
*/
|
||||
this.getDuration = function (duration) {
|
||||
return parseDuration(duration, globalOptions.defaultDuration);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return cache performance statistics (hit rate). Suitable for putting into a route:
|
||||
* Return cache performance statistics (hit rate). Suitable for
|
||||
* putting into a route:
|
||||
* <code>
|
||||
* app.get('/api/cache/performance', (req, res) => {
|
||||
* res.json(apicache.getPerformance())
|
||||
* })
|
||||
* </code>
|
||||
* @returns {any[]}
|
||||
*/
|
||||
this.getPerformance = function () {
|
||||
return performanceArray.map(function (p) {
|
||||
|
@ -475,6 +526,11 @@ function ApiCache() {
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get index of a group
|
||||
* @param {string} group
|
||||
* @returns {number}
|
||||
*/
|
||||
this.getIndex = function (group) {
|
||||
if (group) {
|
||||
return index.groups[group];
|
||||
|
@ -483,6 +539,14 @@ function ApiCache() {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Express middleware
|
||||
* @param {(string|number)} strDuration Duration to cache responses
|
||||
* for.
|
||||
* @param {function(Object, Object):boolean} middlewareToggle
|
||||
* @param {Object} localOptions Options for APICache
|
||||
* @returns
|
||||
*/
|
||||
this.middleware = function cache(strDuration, middlewareToggle, localOptions) {
|
||||
let duration = instance.getDuration(strDuration);
|
||||
let opt = {};
|
||||
|
@ -506,63 +570,72 @@ function ApiCache() {
|
|||
options(localOptions);
|
||||
|
||||
/**
|
||||
* A Function for non tracking performance
|
||||
*/
|
||||
* A Function for non tracking performance
|
||||
*/
|
||||
function NOOPCachePerformance() {
|
||||
this.report = this.hit = this.miss = function () {}; // noop;
|
||||
}
|
||||
|
||||
/**
|
||||
* A function for tracking and reporting hit rate. These statistics are returned by the getPerformance() call above.
|
||||
*/
|
||||
* A function for tracking and reporting hit rate. These
|
||||
* statistics are returned by the getPerformance() call above.
|
||||
*/
|
||||
function CachePerformance() {
|
||||
/**
|
||||
* Tracks the hit rate for the last 100 requests.
|
||||
* If there have been fewer than 100 requests, the hit rate just considers the requests that have happened.
|
||||
*/
|
||||
* Tracks the hit rate for the last 100 requests. If there
|
||||
* have been fewer than 100 requests, the hit rate just
|
||||
* considers the requests that have happened.
|
||||
*/
|
||||
this.hitsLast100 = new Uint8Array(100 / 4); // each hit is 2 bits
|
||||
|
||||
/**
|
||||
* Tracks the hit rate for the last 1000 requests.
|
||||
* If there have been fewer than 1000 requests, the hit rate just considers the requests that have happened.
|
||||
*/
|
||||
* Tracks the hit rate for the last 1000 requests. If there
|
||||
* have been fewer than 1000 requests, the hit rate just
|
||||
* considers the requests that have happened.
|
||||
*/
|
||||
this.hitsLast1000 = new Uint8Array(1000 / 4); // each hit is 2 bits
|
||||
|
||||
/**
|
||||
* Tracks the hit rate for the last 10000 requests.
|
||||
* If there have been fewer than 10000 requests, the hit rate just considers the requests that have happened.
|
||||
*/
|
||||
* Tracks the hit rate for the last 10000 requests. If there
|
||||
* have been fewer than 10000 requests, the hit rate just
|
||||
* considers the requests that have happened.
|
||||
*/
|
||||
this.hitsLast10000 = new Uint8Array(10000 / 4); // each hit is 2 bits
|
||||
|
||||
/**
|
||||
* Tracks the hit rate for the last 100000 requests.
|
||||
* If there have been fewer than 100000 requests, the hit rate just considers the requests that have happened.
|
||||
*/
|
||||
* Tracks the hit rate for the last 100000 requests. If
|
||||
* there have been fewer than 100000 requests, the hit rate
|
||||
* just considers the requests that have happened.
|
||||
*/
|
||||
this.hitsLast100000 = new Uint8Array(100000 / 4); // each hit is 2 bits
|
||||
|
||||
/**
|
||||
* The number of calls that have passed through the middleware since the server started.
|
||||
*/
|
||||
* The number of calls that have passed through the
|
||||
* middleware since the server started.
|
||||
*/
|
||||
this.callCount = 0;
|
||||
|
||||
/**
|
||||
* The total number of hits since the server started
|
||||
*/
|
||||
* The total number of hits since the server started
|
||||
*/
|
||||
this.hitCount = 0;
|
||||
|
||||
/**
|
||||
* The key from the last cache hit. This is useful in identifying which route these statistics apply to.
|
||||
*/
|
||||
* The key from the last cache hit. This is useful in
|
||||
* identifying which route these statistics apply to.
|
||||
*/
|
||||
this.lastCacheHit = null;
|
||||
|
||||
/**
|
||||
* The key from the last cache miss. This is useful in identifying which route these statistics apply to.
|
||||
*/
|
||||
* The key from the last cache miss. This is useful in
|
||||
* identifying which route these statistics apply to.
|
||||
*/
|
||||
this.lastCacheMiss = null;
|
||||
|
||||
/**
|
||||
* Return performance statistics
|
||||
*/
|
||||
* Return performance statistics
|
||||
* @returns {Object}
|
||||
*/
|
||||
this.report = function () {
|
||||
return {
|
||||
lastCacheHit: this.lastCacheHit,
|
||||
|
@ -579,10 +652,13 @@ function ApiCache() {
|
|||
};
|
||||
|
||||
/**
|
||||
* Computes a cache hit rate from an array of hits and misses.
|
||||
* @param {Uint8Array} array An array representing hits and misses.
|
||||
* @returns a number between 0 and 1, or null if the array has no hits or misses
|
||||
*/
|
||||
* Computes a cache hit rate from an array of hits and
|
||||
* misses.
|
||||
* @param {Uint8Array} array An array representing hits and
|
||||
* misses.
|
||||
* @returns {?number} a number between 0 and 1, or null if
|
||||
* the array has no hits or misses
|
||||
*/
|
||||
this.hitRate = function (array) {
|
||||
let hits = 0;
|
||||
let misses = 0;
|
||||
|
@ -608,16 +684,17 @@ function ApiCache() {
|
|||
};
|
||||
|
||||
/**
|
||||
* Record a hit or miss in the given array. It will be recorded at a position determined
|
||||
* by the current value of the callCount variable.
|
||||
* @param {Uint8Array} array An array representing hits and misses.
|
||||
* @param {boolean} hit true for a hit, false for a miss
|
||||
* Each element in the array is 8 bits, and encodes 4 hit/miss records.
|
||||
* Each hit or miss is encoded as to bits as follows:
|
||||
* 00 means no hit or miss has been recorded in these bits
|
||||
* 01 encodes a hit
|
||||
* 10 encodes a miss
|
||||
*/
|
||||
* Record a hit or miss in the given array. It will be
|
||||
* recorded at a position determined by the current value of
|
||||
* the callCount variable.
|
||||
* @param {Uint8Array} array An array representing hits and
|
||||
* misses.
|
||||
* @param {boolean} hit true for a hit, false for a miss
|
||||
* Each element in the array is 8 bits, and encodes 4
|
||||
* hit/miss records. Each hit or miss is encoded as to bits
|
||||
* as follows: 00 means no hit or miss has been recorded in
|
||||
* these bits 01 encodes a hit 10 encodes a miss
|
||||
*/
|
||||
this.recordHitInArray = function (array, hit) {
|
||||
let arrayIndex = ~~(this.callCount / 4) % array.length;
|
||||
let bitOffset = (this.callCount % 4) * 2; // 2 bits per record, 4 records per uint8 array element
|
||||
|
@ -627,9 +704,11 @@ function ApiCache() {
|
|||
};
|
||||
|
||||
/**
|
||||
* Records the hit or miss in the tracking arrays and increments the call count.
|
||||
* @param {boolean} hit true records a hit, false records a miss
|
||||
*/
|
||||
* Records the hit or miss in the tracking arrays and
|
||||
* increments the call count.
|
||||
* @param {boolean} hit true records a hit, false records a
|
||||
* miss
|
||||
*/
|
||||
this.recordHit = function (hit) {
|
||||
this.recordHitInArray(this.hitsLast100, hit);
|
||||
this.recordHitInArray(this.hitsLast1000, hit);
|
||||
|
@ -642,18 +721,18 @@ function ApiCache() {
|
|||
};
|
||||
|
||||
/**
|
||||
* Records a hit event, setting lastCacheMiss to the given key
|
||||
* @param {string} key The key that had the cache hit
|
||||
*/
|
||||
* Records a hit event, setting lastCacheMiss to the given key
|
||||
* @param {string} key The key that had the cache hit
|
||||
*/
|
||||
this.hit = function (key) {
|
||||
this.recordHit(true);
|
||||
this.lastCacheHit = key;
|
||||
};
|
||||
|
||||
/**
|
||||
* Records a miss event, setting lastCacheMiss to the given key
|
||||
* @param {string} key The key that had the cache miss
|
||||
*/
|
||||
* Records a miss event, setting lastCacheMiss to the given key
|
||||
* @param {string} key The key that had the cache miss
|
||||
*/
|
||||
this.miss = function (key) {
|
||||
this.recordHit(false);
|
||||
this.lastCacheMiss = key;
|
||||
|
@ -664,6 +743,13 @@ function ApiCache() {
|
|||
|
||||
performanceArray.push(perf);
|
||||
|
||||
/**
|
||||
* Cache a request
|
||||
* @param {Object} req Express request object
|
||||
* @param {Object} res Express response object
|
||||
* @param {function} next Function to call next
|
||||
* @returns {any}
|
||||
*/
|
||||
let cache = function (req, res, next) {
|
||||
function bypass() {
|
||||
debug("bypass detected, skipping cache.");
|
||||
|
@ -771,6 +857,11 @@ function ApiCache() {
|
|||
return cache;
|
||||
};
|
||||
|
||||
/**
|
||||
* Process options
|
||||
* @param {Object} options
|
||||
* @returns {Object}
|
||||
*/
|
||||
this.options = function (options) {
|
||||
if (options) {
|
||||
Object.assign(globalOptions, options);
|
||||
|
@ -791,6 +882,7 @@ function ApiCache() {
|
|||
}
|
||||
};
|
||||
|
||||
/** Reset the index */
|
||||
this.resetIndex = function () {
|
||||
index = {
|
||||
all: [],
|
||||
|
@ -798,6 +890,11 @@ function ApiCache() {
|
|||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new instance of ApiCache
|
||||
* @param {Object} config Config to pass
|
||||
* @returns {ApiCache}
|
||||
*/
|
||||
this.newInstance = function (config) {
|
||||
let instance = new ApiCache();
|
||||
|
||||
|
@ -808,6 +905,7 @@ function ApiCache() {
|
|||
return instance;
|
||||
};
|
||||
|
||||
/** Clone this instance */
|
||||
this.clone = function () {
|
||||
return this.newInstance(this.options());
|
||||
};
|
||||
|
|
|
@ -3,6 +3,15 @@ function MemoryCache() {
|
|||
this.size = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} key Key to store cache as
|
||||
* @param {any} value Value to store
|
||||
* @param {number} time Time to store for
|
||||
* @param {function(any, string)} timeoutCallback Callback to call in
|
||||
* case of timeout
|
||||
* @returns {Object}
|
||||
*/
|
||||
MemoryCache.prototype.add = function (key, value, time, timeoutCallback) {
|
||||
let old = this.cache[key];
|
||||
let instance = this;
|
||||
|
@ -22,6 +31,11 @@ MemoryCache.prototype.add = function (key, value, time, timeoutCallback) {
|
|||
return entry;
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a cache entry
|
||||
* @param {string} key Key to delete
|
||||
* @returns {null}
|
||||
*/
|
||||
MemoryCache.prototype.delete = function (key) {
|
||||
let entry = this.cache[key];
|
||||
|
||||
|
@ -36,18 +50,32 @@ MemoryCache.prototype.delete = function (key) {
|
|||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get value of key
|
||||
* @param {string} key
|
||||
* @returns {Object}
|
||||
*/
|
||||
MemoryCache.prototype.get = function (key) {
|
||||
let entry = this.cache[key];
|
||||
|
||||
return entry;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get value of cache entry
|
||||
* @param {string} key
|
||||
* @returns {any}
|
||||
*/
|
||||
MemoryCache.prototype.getValue = function (key) {
|
||||
let entry = this.get(key);
|
||||
|
||||
return entry && entry.value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear cache
|
||||
* @returns {boolean}
|
||||
*/
|
||||
MemoryCache.prototype.clear = function () {
|
||||
Object.keys(this.cache).forEach(function (key) {
|
||||
this.delete(key);
|
||||
|
|
|
@ -37,6 +37,12 @@ class AliyunSMS extends NotificationProvider {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the SMS notification
|
||||
* @param {BeanModel} notification Notification details
|
||||
* @param {string} msgbody Message template
|
||||
* @returns {boolean} True if successful else false
|
||||
*/
|
||||
async sendSms(notification, msgbody) {
|
||||
let params = {
|
||||
PhoneNumbers: notification.phonenumber,
|
||||
|
@ -70,7 +76,12 @@ class AliyunSMS extends NotificationProvider {
|
|||
return false;
|
||||
}
|
||||
|
||||
/** Aliyun request sign */
|
||||
/**
|
||||
* Aliyun request sign
|
||||
* @param {Object} param Parameters object to sign
|
||||
* @param {string} AccessKeySecret Secret key to sign parameters with
|
||||
* @returns {string}
|
||||
*/
|
||||
sign(param, AccessKeySecret) {
|
||||
let param2 = {};
|
||||
let data = [];
|
||||
|
@ -93,6 +104,11 @@ class AliyunSMS extends NotificationProvider {
|
|||
.digest("base64");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert status constant to string
|
||||
* @param {const} status The status constant
|
||||
* @returns {string}
|
||||
*/
|
||||
statusToString(status) {
|
||||
switch (status) {
|
||||
case DOWN:
|
||||
|
|
|
@ -44,7 +44,12 @@ class Bark extends NotificationProvider {
|
|||
}
|
||||
}
|
||||
|
||||
// add additional parameter for better on device styles (iOS 15 optimized)
|
||||
/**
|
||||
* Add additional parameter for better on device styles (iOS 15
|
||||
* optimized)
|
||||
* @param {string} postUrl URL to append parameters to
|
||||
* @returns {string}
|
||||
*/
|
||||
appendAdditionalParameters(postUrl) {
|
||||
// grouping all our notifications
|
||||
postUrl += "?group=" + barkNotificationGroup;
|
||||
|
@ -55,7 +60,11 @@ class Bark extends NotificationProvider {
|
|||
return postUrl;
|
||||
}
|
||||
|
||||
// thrown if failed to check result, result code should be in range 2xx
|
||||
/**
|
||||
* Check if result is successful
|
||||
* @param {Object} result Axios response object
|
||||
* @throws {Error} The status code is not in range 2xx
|
||||
*/
|
||||
checkResult(result) {
|
||||
if (result.status == null) {
|
||||
throw new Error("Bark notification failed with invalid response!");
|
||||
|
@ -65,6 +74,13 @@ class Bark extends NotificationProvider {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the message
|
||||
* @param {string} title Message title
|
||||
* @param {string} subtitle Message
|
||||
* @param {string} endpoint Endpoint to send request to
|
||||
* @returns {string}
|
||||
*/
|
||||
async postNotification(title, subtitle, endpoint) {
|
||||
// url encode title and subtitle
|
||||
title = encodeURIComponent(title);
|
||||
|
|
|
@ -37,6 +37,12 @@ class DingDing extends NotificationProvider {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message to DingDing
|
||||
* @param {BeanModel} notification
|
||||
* @param {Object} params Parameters of message
|
||||
* @returns {boolean} True if successful else false
|
||||
*/
|
||||
async sendToDingDing(notification, params) {
|
||||
let timestamp = Date.now();
|
||||
|
||||
|
@ -56,7 +62,12 @@ class DingDing extends NotificationProvider {
|
|||
return false;
|
||||
}
|
||||
|
||||
/** DingDing sign */
|
||||
/**
|
||||
* DingDing sign
|
||||
* @param {Date} timestamp Timestamp of message
|
||||
* @param {string} secretKey Secret key to sign data with
|
||||
* @returns {string}
|
||||
*/
|
||||
sign(timestamp, secretKey) {
|
||||
return Crypto
|
||||
.createHmac("sha256", Buffer.from(secretKey, "utf8"))
|
||||
|
@ -64,7 +75,13 @@ class DingDing extends NotificationProvider {
|
|||
.digest("base64");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert status constant to string
|
||||
* @param {const} status The status constant
|
||||
* @returns {string}
|
||||
*/
|
||||
statusToString(status) {
|
||||
// TODO: Move to notification-provider.js to avoid repetition in classes
|
||||
switch (status) {
|
||||
case DOWN:
|
||||
return "DOWN";
|
||||
|
|
|
@ -7,17 +7,23 @@ class NotificationProvider {
|
|||
name = undefined;
|
||||
|
||||
/**
|
||||
* @param notification : BeanModel
|
||||
* @param msg : string General Message
|
||||
* @param monitorJSON : object Monitor details (For Up/Down only)
|
||||
* @param heartbeatJSON : object Heartbeat details (For Up/Down only)
|
||||
* Send a notification
|
||||
* @param {BeanModel} notification
|
||||
* @param {string} msg General Message
|
||||
* @param {?Object} monitorJSON Monitor details (For Up/Down only)
|
||||
* @param {?Object} heartbeatJSON Heartbeat details (For Up/Down only)
|
||||
* @returns {Promise<string>} Return Successful Message
|
||||
* Throw Error with fail msg
|
||||
* @throws Error with fail msg
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
throw new Error("Have to override Notification.send(...)");
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an error
|
||||
* @param {any} error The error to throw
|
||||
* @throws {any} The error specified
|
||||
*/
|
||||
throwGeneralAxiosError(error) {
|
||||
let msg = "Error: " + error + " ";
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ class Slack extends NotificationProvider {
|
|||
/**
|
||||
* Deprecated property notification.slackbutton
|
||||
* Set it as primary base url if this is not yet set.
|
||||
* @param {string} url The primary base URL to use
|
||||
*/
|
||||
static async deprecateURL(url) {
|
||||
let currentPrimaryBaseURL = await setting("primaryBaseURL");
|
||||
|
|
|
@ -5,6 +5,12 @@ const { DOWN, UP } = require("../../src/util");
|
|||
class Teams extends NotificationProvider {
|
||||
name = "teams";
|
||||
|
||||
/**
|
||||
* Generate the message to send
|
||||
* @param {const} status The status constant
|
||||
* @param {string} monitorName Name of monitor
|
||||
* @returns {string}
|
||||
*/
|
||||
_statusMessageFactory = (status, monitorName) => {
|
||||
if (status === DOWN) {
|
||||
return `🔴 Application [${monitorName}] went down`;
|
||||
|
@ -14,6 +20,11 @@ class Teams extends NotificationProvider {
|
|||
return "Notification";
|
||||
};
|
||||
|
||||
/**
|
||||
* Select theme color to use based on status
|
||||
* @param {const} status The status constant
|
||||
* @returns {string} Selected color in hex RGB format
|
||||
*/
|
||||
_getThemeColor = (status) => {
|
||||
if (status === DOWN) {
|
||||
return "ff0000";
|
||||
|
@ -24,6 +35,14 @@ class Teams extends NotificationProvider {
|
|||
return "008cff";
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate payload for notification
|
||||
* @param {const} status The status of the monitor
|
||||
* @param {string} monitorMessage Message to send
|
||||
* @param {string} monitorName Name of monitor affected
|
||||
* @param {string} monitorUrl URL of monitor affected
|
||||
* @returns {Object}
|
||||
*/
|
||||
_notificationPayloadFactory = ({
|
||||
status,
|
||||
monitorMessage,
|
||||
|
@ -74,10 +93,21 @@ class Teams extends NotificationProvider {
|
|||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Send the notification
|
||||
* @param {string} webhookUrl URL to send the request to
|
||||
* @param {Object} payload Payload generated by _notificationPayloadFactory
|
||||
*/
|
||||
_sendNotification = async (webhookUrl, payload) => {
|
||||
await axios.post(webhookUrl, payload);
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a general notification
|
||||
* @param {string} webhookUrl URL to send request to
|
||||
* @param {string} msg Message to send
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
_handleGeneralNotification = (webhookUrl, msg) => {
|
||||
const payload = this._notificationPayloadFactory({
|
||||
monitorMessage: msg
|
||||
|
|
|
@ -24,6 +24,12 @@ class WeCom extends NotificationProvider {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the message to send
|
||||
* @param {Object} heartbeatJSON Heartbeat details (For Up/Down only)
|
||||
* @param {string} msg General message
|
||||
* @returns {Object}
|
||||
*/
|
||||
composeMessage(heartbeatJSON, msg) {
|
||||
let title;
|
||||
if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] === UP) {
|
||||
|
|
|
@ -38,6 +38,7 @@ class Notification {
|
|||
|
||||
providerList = {};
|
||||
|
||||
/** Initialize the notification providers */
|
||||
static init() {
|
||||
log.info("notification", "Prepare Notification Providers");
|
||||
|
||||
|
@ -92,13 +93,13 @@ class Notification {
|
|||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param notification : BeanModel
|
||||
* @param msg : string General Message
|
||||
* @param monitorJSON : object Monitor details (For Up/Down only)
|
||||
* @param heartbeatJSON : object Heartbeat details (For Up/Down only)
|
||||
* Send a notification
|
||||
* @param {BeanModel} notification
|
||||
* @param {string} msg General Message
|
||||
* @param {Object} monitorJSON Monitor details (For Up/Down only)
|
||||
* @param {Object} heartbeatJSON Heartbeat details (For Up/Down only)
|
||||
* @returns {Promise<string>} Successful msg
|
||||
* Throw Error with fail msg
|
||||
* @throws Error with fail msg
|
||||
*/
|
||||
static async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
if (this.providerList[notification.type]) {
|
||||
|
@ -108,6 +109,13 @@ class Notification {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a notification
|
||||
* @param {Object} notification Notification to save
|
||||
* @param {?number} notificationID ID of notification to update
|
||||
* @param {number} userID ID of user who adds notification
|
||||
* @returns {Promise<Bean>}
|
||||
*/
|
||||
static async save(notification, notificationID, userID) {
|
||||
let bean;
|
||||
|
||||
|
@ -138,6 +146,12 @@ class Notification {
|
|||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a notification
|
||||
* @param {number} notificationID ID of notification to delete
|
||||
* @param {number} userID ID of user who created notification
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async delete(notificationID, userID) {
|
||||
let bean = await R.findOne("notification", " id = ? AND user_id = ? ", [
|
||||
notificationID,
|
||||
|
@ -151,6 +165,10 @@ class Notification {
|
|||
await R.trash(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if apprise exists
|
||||
* @returns {boolean} Does the command apprise exist?
|
||||
*/
|
||||
static checkApprise() {
|
||||
let commandExistsSync = require("command-exists").sync;
|
||||
let exists = commandExistsSync("apprise");
|
||||
|
@ -160,11 +178,10 @@ class Notification {
|
|||
}
|
||||
|
||||
/**
|
||||
* Adds a new monitor to the database.
|
||||
* @param {number} userID The ID of the user that owns this monitor.
|
||||
* @param {string} name The name of this monitor.
|
||||
*
|
||||
* Generated by Trelent
|
||||
* Apply the notification to every monitor
|
||||
* @param {number} notificationID ID of notification to apply
|
||||
* @param {number} userID ID of user who created notification
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function applyNotificationEveryMonitor(notificationID, userID) {
|
||||
let monitors = await R.getAll("SELECT id FROM monitor WHERE user_id = ?", [
|
||||
|
|
|
@ -2,10 +2,21 @@ const passwordHashOld = require("password-hash");
|
|||
const bcrypt = require("bcryptjs");
|
||||
const saltRounds = 10;
|
||||
|
||||
/**
|
||||
* Hash a password
|
||||
* @param {string} password
|
||||
* @returns {string}
|
||||
*/
|
||||
exports.generate = function (password) {
|
||||
return bcrypt.hashSync(password, saltRounds);
|
||||
};
|
||||
|
||||
/**
|
||||
* Verify a password against a hash
|
||||
* @param {string} password
|
||||
* @param {string} hash
|
||||
* @returns {boolean} Does the password match the hash?
|
||||
*/
|
||||
exports.verify = function (password, hash) {
|
||||
if (isSHA1(hash)) {
|
||||
return passwordHashOld.verify(password, hash);
|
||||
|
@ -14,10 +25,19 @@ exports.verify = function (password, hash) {
|
|||
return bcrypt.compareSync(password, hash);
|
||||
};
|
||||
|
||||
/**
|
||||
* Is the hash a SHA1 hash
|
||||
* @param {string} hash
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isSHA1(hash) {
|
||||
return (typeof hash === "string" && hash.startsWith("sha1"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the hash need to be rehashed?
|
||||
* @returns {boolean}
|
||||
*/
|
||||
exports.needRehash = function (hash) {
|
||||
return isSHA1(hash);
|
||||
};
|
||||
|
|
|
@ -9,11 +9,10 @@ const util = require("./util-server");
|
|||
module.exports = Ping;
|
||||
|
||||
/**
|
||||
* @param {string} host - The host to ping
|
||||
* @param {object} [options] - Options for the ping command
|
||||
* Constructor for ping class
|
||||
* @param {string} host Host to ping
|
||||
* @param {object} [options] Options for the ping command
|
||||
* @param {array|string} [options.args] - Arguments to pass to the ping command
|
||||
*
|
||||
* Generated by Trelent
|
||||
*/
|
||||
function Ping(host, options) {
|
||||
if (!host) {
|
||||
|
@ -82,8 +81,17 @@ function Ping(host, options) {
|
|||
|
||||
Ping.prototype.__proto__ = events.EventEmitter.prototype;
|
||||
|
||||
// SEND A PING
|
||||
// ===========
|
||||
/**
|
||||
* Callback for send
|
||||
* @callback pingCB
|
||||
* @param {any} err Any error encountered
|
||||
* @param {number} ms Ping time in ms
|
||||
*/
|
||||
|
||||
/**
|
||||
* Send a ping
|
||||
* @param {pingCB} callback Callback to call with results
|
||||
*/
|
||||
Ping.prototype.send = function (callback) {
|
||||
let self = this;
|
||||
callback = callback || function (err, ms) {
|
||||
|
@ -157,8 +165,10 @@ Ping.prototype.send = function (callback) {
|
|||
}
|
||||
};
|
||||
|
||||
// CALL Ping#send(callback) ON A TIMER
|
||||
// ===================================
|
||||
/**
|
||||
* Ping every interval
|
||||
* @param {pingCB} callback Callback to call with results
|
||||
*/
|
||||
Ping.prototype.start = function (callback) {
|
||||
let self = this;
|
||||
this._i = setInterval(function () {
|
||||
|
@ -167,8 +177,7 @@ Ping.prototype.start = function (callback) {
|
|||
self.send(callback);
|
||||
};
|
||||
|
||||
// STOP SENDING PINGS
|
||||
// ==================
|
||||
/** Stop sending pings */
|
||||
Ping.prototype.stop = function () {
|
||||
clearInterval(this._i);
|
||||
};
|
||||
|
@ -177,7 +186,7 @@ Ping.prototype.stop = function () {
|
|||
* Try to convert to UTF-8 for Windows, as the ping's output on Windows is not UTF-8 and could be in other languages
|
||||
* Thank @pemassi
|
||||
* https://github.com/louislam/uptime-kuma/issues/570#issuecomment-941984094
|
||||
* @param data
|
||||
* @param {any} data
|
||||
* @returns {string}
|
||||
*/
|
||||
function convertOutput(data) {
|
||||
|
|
|
@ -35,6 +35,9 @@ const monitorStatus = new PrometheusClient.Gauge({
|
|||
class Prometheus {
|
||||
monitorLabelValues = {}
|
||||
|
||||
/**
|
||||
* @param {Object} monitor Monitor object to monitor
|
||||
*/
|
||||
constructor(monitor) {
|
||||
this.monitorLabelValues = {
|
||||
monitor_name: monitor.name,
|
||||
|
@ -45,6 +48,11 @@ class Prometheus {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the metrics page
|
||||
* @param {Object} heartbeat Heartbeat details
|
||||
* @param {Object} tlsInfo TLS details
|
||||
*/
|
||||
update(heartbeat, tlsInfo) {
|
||||
|
||||
if (typeof tlsInfo !== "undefined") {
|
||||
|
|
|
@ -2,11 +2,26 @@ const { RateLimiter } = require("limiter");
|
|||
const { log } = require("../src/util");
|
||||
|
||||
class KumaRateLimiter {
|
||||
/**
|
||||
* @param {Object} config Rate limiter configuration object
|
||||
*/
|
||||
constructor(config) {
|
||||
this.errorMessage = config.errorMessage;
|
||||
this.rateLimiter = new RateLimiter(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for pass
|
||||
* @callback passCB
|
||||
* @param {Object} err Too many requests
|
||||
*/
|
||||
|
||||
/**
|
||||
* Should the request be passed through
|
||||
* @param {passCB} callback
|
||||
* @param {number} [num=1] Number of tokens to remove
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async pass(callback, num = 1) {
|
||||
const remainingRequests = await this.removeTokens(num);
|
||||
log.info("rate-limit", "remaining requests: " + remainingRequests);
|
||||
|
@ -22,6 +37,11 @@ class KumaRateLimiter {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a given number of tokens
|
||||
* @param {number} [num=1] Number of tokens to remove
|
||||
* @returns {Promise<number>}
|
||||
*/
|
||||
async removeTokens(num = 1) {
|
||||
return await this.rateLimiter.removeTokens(num);
|
||||
}
|
||||
|
|
|
@ -196,6 +196,11 @@ router.get("/api/status-page/heartbeat/:slug", cache("1 minutes"), async (reques
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Send a 403 response
|
||||
* @param {Object} res Express response object
|
||||
* @param {string} [msg=""] Message to send
|
||||
*/
|
||||
function send403(res, msg = "") {
|
||||
res.status(403).json({
|
||||
"status": "fail",
|
||||
|
|
|
@ -1473,11 +1473,11 @@ try {
|
|||
})();
|
||||
|
||||
/**
|
||||
* Adds or removes notifications from a monitor.
|
||||
* @param {number} monitorID The ID of the monitor to add/remove notifications from.
|
||||
* @param {Array.<number>} notificationIDList An array of IDs for the notifications to add/remove.
|
||||
*
|
||||
* Generated by Trelent
|
||||
* Update notifications for a given monitor
|
||||
* @param {number} monitorID ID of monitor to update
|
||||
* @param {number[]} notificationIDList List of new notification
|
||||
* providers to add
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function updateMonitorNotification(monitorID, notificationIDList) {
|
||||
await R.exec("DELETE FROM monitor_notification WHERE monitor_id = ? ", [
|
||||
|
@ -1495,11 +1495,11 @@ async function updateMonitorNotification(monitorID, notificationIDList) {
|
|||
}
|
||||
|
||||
/**
|
||||
* This function checks if the user owns a monitor with the given ID.
|
||||
* @param {number} monitorID - The ID of the monitor to check ownership for.
|
||||
* @param {number} userID - The ID of the user who is trying to access this data.
|
||||
*
|
||||
* Generated by Trelent
|
||||
* Check if a given user owns a specific monitor
|
||||
* @param {number} userID
|
||||
* @param {number} monitorID
|
||||
* @returns {Promise<void>}
|
||||
* @throws {Error} The specified user does not own the monitor
|
||||
*/
|
||||
async function checkOwner(userID, monitorID) {
|
||||
let row = await R.getRow("SELECT id FROM monitor WHERE id = ? AND user_id = ? ", [
|
||||
|
@ -1513,8 +1513,11 @@ async function checkOwner(userID, monitorID) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Function called after user login
|
||||
* This function is used to send the heartbeat list of a monitor.
|
||||
* @param {Socket} socket - The socket object that will be used to send the data.
|
||||
* @param {Socket} socket Socket.io instance
|
||||
* @param {Object} user User object
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function afterLogin(socket, user) {
|
||||
socket.userID = user.id;
|
||||
|
@ -1542,9 +1545,10 @@ async function afterLogin(socket, user) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Connect to the database and patch it if necessary.
|
||||
*
|
||||
* Generated by Trelent
|
||||
* Initialize the database
|
||||
* @param {boolean} [testMode=false] Should the connection be
|
||||
* started in test mode?
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function initDatabase(testMode = false) {
|
||||
if (! fs.existsSync(Database.path)) {
|
||||
|
@ -1581,11 +1585,10 @@ async function initDatabase(testMode = false) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Resume a monitor.
|
||||
* @param {string} userID - The ID of the user who owns the monitor.
|
||||
* @param {string} monitorID - The ID of the monitor to resume.
|
||||
*
|
||||
* Generated by Trelent
|
||||
* Start the specified monitor
|
||||
* @param {number} userID ID of user who owns monitor
|
||||
* @param {number} monitorID ID of monitor to start
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function startMonitor(userID, monitorID) {
|
||||
await checkOwner(userID, monitorID);
|
||||
|
@ -1609,16 +1612,21 @@ async function startMonitor(userID, monitorID) {
|
|||
monitor.start(io);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart a given monitor
|
||||
* @param {number} userID ID of user who owns monitor
|
||||
* @param {number} monitorID ID of monitor to start
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function restartMonitor(userID, monitorID) {
|
||||
return await startMonitor(userID, monitorID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause a monitor.
|
||||
* @param {string} userID - The ID of the user who owns the monitor.
|
||||
* @param {string} monitorID - The ID of the monitor to pause.
|
||||
*
|
||||
* Generated by Trelent
|
||||
* Pause a given monitor
|
||||
* @param {number} userID ID of user who owns monitor
|
||||
* @param {number} monitorID ID of monitor to start
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function pauseMonitor(userID, monitorID) {
|
||||
await checkOwner(userID, monitorID);
|
||||
|
@ -1635,9 +1643,7 @@ async function pauseMonitor(userID, monitorID) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume active monitors
|
||||
*/
|
||||
/** Resume active monitors */
|
||||
async function startMonitors() {
|
||||
let list = await R.find("monitor", " active = 1 ");
|
||||
|
||||
|
@ -1653,10 +1659,10 @@ async function startMonitors() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Shutdown the application
|
||||
* Stops all monitors and closes the database connection.
|
||||
* @param {string} signal The signal that triggered this function to be called.
|
||||
*
|
||||
* Generated by Trelent
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function shutdownFunction(signal) {
|
||||
log.info("server", "Shutdown requested");
|
||||
|
@ -1678,6 +1684,7 @@ function getClientIp(socket) {
|
|||
return socket.client.conn.remoteAddress.replace(/^.*:/, "");
|
||||
}
|
||||
|
||||
/** Final function called before application exits */
|
||||
function finalFunction() {
|
||||
log.info("server", "Graceful shutdown successful!");
|
||||
}
|
||||
|
|
|
@ -6,15 +6,28 @@ const io = UptimeKumaServer.getInstance().io;
|
|||
const prefix = "cloudflared_";
|
||||
const cloudflared = new CloudflaredTunnel();
|
||||
|
||||
/**
|
||||
* Change running state
|
||||
* @param {string} running Is it running?
|
||||
* @param {string} message Message to pass
|
||||
*/
|
||||
cloudflared.change = (running, message) => {
|
||||
io.to("cloudflared").emit(prefix + "running", running);
|
||||
io.to("cloudflared").emit(prefix + "message", message);
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit an error message
|
||||
* @param {string} errorMessage
|
||||
*/
|
||||
cloudflared.error = (errorMessage) => {
|
||||
io.to("cloudflared").emit(prefix + "errorMessage", errorMessage);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handler for cloudflared
|
||||
* @param {Socket} socket Socket.io instance
|
||||
*/
|
||||
module.exports.cloudflaredSocketHandler = (socket) => {
|
||||
|
||||
socket.on(prefix + "join", async () => {
|
||||
|
@ -69,6 +82,10 @@ module.exports.cloudflaredSocketHandler = (socket) => {
|
|||
|
||||
};
|
||||
|
||||
/**
|
||||
* Automatically start cloudflared
|
||||
* @param {string} token Cloudflared tunnel token
|
||||
*/
|
||||
module.exports.autoStart = async (token) => {
|
||||
if (!token) {
|
||||
token = await setting("cloudflaredTunnelToken");
|
||||
|
@ -85,6 +102,7 @@ module.exports.autoStart = async (token) => {
|
|||
}
|
||||
};
|
||||
|
||||
/** Stop cloudflared */
|
||||
module.exports.stop = async () => {
|
||||
console.log("Stop cloudflared");
|
||||
if (cloudflared) {
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
const { checkLogin } = require("../util-server");
|
||||
const Database = require("../database");
|
||||
|
||||
/**
|
||||
* Handlers for database
|
||||
* @param {Socket} socket Socket.io instance
|
||||
*/
|
||||
module.exports = (socket) => {
|
||||
|
||||
// Post or edit incident
|
||||
|
|
|
@ -4,6 +4,10 @@ const { sendProxyList } = require("../client");
|
|||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
const server = UptimeKumaServer.getInstance();
|
||||
|
||||
/**
|
||||
* Handlers for proxy
|
||||
* @param {Socket} socket Socket.io instance
|
||||
*/
|
||||
module.exports.proxySocketHandler = (socket) => {
|
||||
socket.on("addProxy", async (proxy, proxyID, callback) => {
|
||||
try {
|
||||
|
|
|
@ -8,6 +8,10 @@ const apicache = require("../modules/apicache");
|
|||
const StatusPage = require("../model/status_page");
|
||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||
|
||||
/**
|
||||
* Socket handlers for status page
|
||||
* @param {Socket} socket Socket.io instance to add listeners on
|
||||
*/
|
||||
module.exports.statusPageSocketHandler = (socket) => {
|
||||
|
||||
// Post or edit incident
|
||||
|
@ -338,6 +342,7 @@ module.exports.statusPageSocketHandler = (socket) => {
|
|||
/**
|
||||
* Check slug a-z, 0-9, - only
|
||||
* Regex from: https://stackoverflow.com/questions/22454258/js-regex-string-validation-for-slug
|
||||
* @param {string} slug Slug to test
|
||||
*/
|
||||
function checkSlug(slug) {
|
||||
if (typeof slug !== "string") {
|
||||
|
|
|
@ -37,6 +37,12 @@ exports.initJWTSecret = async () => {
|
|||
return jwtSecretBean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Send TCP request to specified hostname and port
|
||||
* @param {string} hostname Hostname / address of machine
|
||||
* @param {number} port TCP port to test
|
||||
* @returns {Promise<number>} Maximum time in ms rounded to nearest integer
|
||||
*/
|
||||
exports.tcping = function (hostname, port) {
|
||||
return new Promise((resolve, reject) => {
|
||||
tcpp.ping({
|
||||
|
@ -58,6 +64,11 @@ exports.tcping = function (hostname, port) {
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Ping the specified machine
|
||||
* @param {string} hostname Hostname / address of machine
|
||||
* @returns {Promise<number>} Time for ping in ms rounded to nearest integer
|
||||
*/
|
||||
exports.ping = async (hostname) => {
|
||||
try {
|
||||
return await exports.pingAsync(hostname);
|
||||
|
@ -71,6 +82,12 @@ exports.ping = async (hostname) => {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Ping the specified machine
|
||||
* @param {string} hostname Hostname / address of machine to ping
|
||||
* @param {boolean} ipv6 Should IPv6 be used?
|
||||
* @returns {Promise<number>} Time for ping in ms rounded to nearest integer
|
||||
*/
|
||||
exports.pingAsync = function (hostname, ipv6 = false) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const ping = new Ping(hostname, {
|
||||
|
@ -89,6 +106,15 @@ exports.pingAsync = function (hostname, ipv6 = false) {
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* MQTT Monitor
|
||||
* @param {string} hostname Hostname / address of machine to test
|
||||
* @param {string} topic MQTT topic
|
||||
* @param {string} okMessage Expected result
|
||||
* @param {Object} [options={}] MQTT options. Contains port, username,
|
||||
* password and interval (interval defaults to 20)
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
exports.mqttAsync = function (hostname, topic, okMessage, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { port, username, password, interval = 20 } = options;
|
||||
|
@ -146,6 +172,13 @@ exports.mqttAsync = function (hostname, topic, okMessage, options = {}) {
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolves a given record using the specified DNS server
|
||||
* @param {string} hostname The hostname of the record to lookup
|
||||
* @param {string} resolverServer The DNS server to use
|
||||
* @param {string} rrtype The type of record to request
|
||||
* @returns {Promise<(string[]|Object[]|Object)>}
|
||||
*/
|
||||
exports.dnsResolve = function (hostname, resolverServer, rrtype) {
|
||||
const resolver = new Resolver();
|
||||
resolver.setServers([ resolverServer ]);
|
||||
|
@ -170,6 +203,11 @@ exports.dnsResolve = function (hostname, resolverServer, rrtype) {
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve value of setting based on key
|
||||
* @param {string} key Key of setting to retrieve
|
||||
* @returns {Promise<Object>} Object representation of setting
|
||||
*/
|
||||
exports.setting = async function (key) {
|
||||
let value = await R.getCell("SELECT `value` FROM setting WHERE `key` = ? ", [
|
||||
key,
|
||||
|
@ -184,6 +222,13 @@ exports.setting = async function (key) {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the specified setting to specifed value
|
||||
* @param {string} key Key of setting to set
|
||||
* @param {any} value Value to set to
|
||||
* @param {?string} type Type of setting
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
exports.setSetting = async function (key, value, type = null) {
|
||||
let bean = await R.findOne("setting", " `key` = ? ", [
|
||||
key,
|
||||
|
@ -197,6 +242,11 @@ exports.setSetting = async function (key, value, type = null) {
|
|||
await R.store(bean);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get settings based on type
|
||||
* @param {?string} type The type of setting
|
||||
* @returns {Promise<Bean>}
|
||||
*/
|
||||
exports.getSettings = async function (type) {
|
||||
let list = await R.getAll("SELECT `key`, `value` FROM setting WHERE `type` = ? ", [
|
||||
type,
|
||||
|
@ -215,6 +265,12 @@ exports.getSettings = async function (type) {
|
|||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set settings based on type
|
||||
* @param {?string} type Type of settings to set
|
||||
* @param {Object} data Values of settings
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
exports.setSettings = async function (type, data) {
|
||||
let keyList = Object.keys(data);
|
||||
|
||||
|
@ -241,12 +297,23 @@ exports.setSettings = async function (type, data) {
|
|||
};
|
||||
|
||||
// ssl-checker by @dyaa
|
||||
// param: res - response object from axios
|
||||
// return an object containing the certificate information
|
||||
//https://github.com/dyaa/ssl-checker/blob/master/src/index.ts
|
||||
|
||||
/**
|
||||
* Get number of days between two dates
|
||||
* @param {Date} validFrom Start date
|
||||
* @param {Date} validTo End date
|
||||
* @returns {number}
|
||||
*/
|
||||
const getDaysBetween = (validFrom, validTo) =>
|
||||
Math.round(Math.abs(+validFrom - +validTo) / 8.64e7);
|
||||
|
||||
/**
|
||||
* Get days remaining from a time range
|
||||
* @param {Date} validFrom Start date
|
||||
* @param {Date} validTo End date
|
||||
* @returns {number}
|
||||
*/
|
||||
const getDaysRemaining = (validFrom, validTo) => {
|
||||
const daysRemaining = getDaysBetween(validFrom, validTo);
|
||||
if (new Date(validTo).getTime() < new Date().getTime()) {
|
||||
|
@ -255,8 +322,11 @@ const getDaysRemaining = (validFrom, validTo) => {
|
|||
return daysRemaining;
|
||||
};
|
||||
|
||||
// Fix certificate Info for display
|
||||
// param: info - the chain obtained from getPeerCertificate()
|
||||
/**
|
||||
* Fix certificate info for display
|
||||
* @param {Object} info The chain obtained from getPeerCertificate()
|
||||
* @returns {Object} An object representing certificate information
|
||||
*/
|
||||
const parseCertificateInfo = function (info) {
|
||||
let link = info;
|
||||
let i = 0;
|
||||
|
@ -296,6 +366,11 @@ const parseCertificateInfo = function (info) {
|
|||
return info;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if certificate is valid
|
||||
* @param {Object} res Response object from axios
|
||||
* @returns {Object} Object containing certificate information
|
||||
*/
|
||||
exports.checkCertificate = function (res) {
|
||||
const info = res.request.res.socket.getPeerCertificate(true);
|
||||
const valid = res.request.res.socket.authorized || false;
|
||||
|
@ -309,12 +384,13 @@ exports.checkCertificate = function (res) {
|
|||
};
|
||||
};
|
||||
|
||||
// Check if the provided status code is within the accepted ranges
|
||||
// Param: status - the status code to check
|
||||
// Param: accepted_codes - an array of accepted status codes
|
||||
// Return: true if the status code is within the accepted ranges, false otherwise
|
||||
// Will throw an error if the provided status code is not a valid range string or code string
|
||||
|
||||
/**
|
||||
* Check if the provided status code is within the accepted ranges
|
||||
* @param {string} status The status code to check
|
||||
* @param {string[]} acceptedCodes An array of accepted status codes
|
||||
* @returns {boolean} True if status code within range, false otherwise
|
||||
* @throws {Error} Will throw an error if the provided status code is not a valid range string or code string
|
||||
*/
|
||||
exports.checkStatusCode = function (status, acceptedCodes) {
|
||||
if (acceptedCodes == null || acceptedCodes.length === 0) {
|
||||
return false;
|
||||
|
@ -338,6 +414,12 @@ exports.checkStatusCode = function (status, acceptedCodes) {
|
|||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get total number of clients in room
|
||||
* @param {Server} io Socket server instance
|
||||
* @param {string} roomName Name of room to check
|
||||
* @returns {number}
|
||||
*/
|
||||
exports.getTotalClientInRoom = (io, roomName) => {
|
||||
|
||||
const sockets = io.sockets;
|
||||
|
@ -361,17 +443,29 @@ exports.getTotalClientInRoom = (io, roomName) => {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Allow CORS all origins if development
|
||||
* @param {Object} res Response object from axios
|
||||
*/
|
||||
exports.allowDevAllOrigin = (res) => {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
exports.allowAllOrigin(res);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Allow CORS all origins
|
||||
* @param {Object} res Response object from axios
|
||||
*/
|
||||
exports.allowAllOrigin = (res) => {
|
||||
res.header("Access-Control-Allow-Origin", "*");
|
||||
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a user is logged in
|
||||
* @param {Socket} socket Socket instance
|
||||
*/
|
||||
exports.checkLogin = (socket) => {
|
||||
if (!socket.userID) {
|
||||
throw new Error("You are not logged in.");
|
||||
|
@ -380,8 +474,8 @@ exports.checkLogin = (socket) => {
|
|||
|
||||
/**
|
||||
* For logged-in users, double-check the password
|
||||
* @param socket
|
||||
* @param currentPassword
|
||||
* @param {Socket} socket Socket.io instance
|
||||
* @param {string} currentPassword
|
||||
* @returns {Promise<Bean>}
|
||||
*/
|
||||
exports.doubleCheckPassword = async (socket, currentPassword) => {
|
||||
|
@ -400,6 +494,7 @@ exports.doubleCheckPassword = async (socket, currentPassword) => {
|
|||
return user;
|
||||
};
|
||||
|
||||
/** Start Unit tests */
|
||||
exports.startUnitTest = async () => {
|
||||
console.log("Starting unit test...");
|
||||
const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm";
|
||||
|
@ -420,7 +515,8 @@ exports.startUnitTest = async () => {
|
|||
};
|
||||
|
||||
/**
|
||||
* @param body : Buffer
|
||||
* Convert unknown string to UTF8
|
||||
* @param {Uint8Array} body Buffer
|
||||
* @returns {string}
|
||||
*/
|
||||
exports.convertToUTF8 = (body) => {
|
||||
|
@ -437,6 +533,11 @@ try {
|
|||
});
|
||||
} catch (_) { }
|
||||
|
||||
/**
|
||||
* Write error to log file
|
||||
* @param {any} error The error to write
|
||||
* @param {boolean} outputToConsole Should the error also be output to console?
|
||||
*/
|
||||
exports.errorLog = (error, outputToConsole = true) => {
|
||||
try {
|
||||
if (logFile) {
|
||||
|
|
Loading…
Reference in a new issue