Merge pull request #1499 from Computroniks/add-JSDoc-comments

Add JSDoc comments for server/*/*
This commit is contained in:
Louis Lam 2022-04-27 14:46:23 +08:00 committed by GitHub
commit d6c3fdb6fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 849 additions and 212 deletions

View file

@ -2,6 +2,11 @@ const { R } = require("redbean-node");
class TwoFA { class TwoFA {
/**
* Disable 2FA for specified user
* @param {number} userID ID of user to disable
* @returns {Promise<void>}
*/
static async disable2FA(userID) { static async disable2FA(userID) {
return await R.exec("UPDATE `user` SET twofa_status = 0 WHERE id = ? ", [ return await R.exec("UPDATE `user` SET twofa_status = 0 WHERE id = ? ", [
userID, userID,

View file

@ -5,10 +5,10 @@ const { setting } = require("./util-server");
const { loginRateLimiter } = require("./rate-limiter"); const { loginRateLimiter } = require("./rate-limiter");
/** /**
* * Login to web app
* @param username : string * @param {string} username
* @param password : string * @param {string} password
* @returns {Promise<Bean|null>} * @returns {Promise<(Bean|null)>}
*/ */
exports.login = async function (username, password) { exports.login = async function (username, password) {
if (typeof username !== "string" || typeof password !== "string") { 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. * Callback for myAuthorizer
* @param {string} username The username of the user to check for. * @callback myAuthorizerCB
* @param {function} callback The callback to call when done, with an error and result parameter. * @param {any} err Any error encountered
* * @param {boolean} authorized Is the client authorized?
* Generated by Trelent */
/**
* Custom authorizer for express-basic-auth
* @param {string} username
* @param {string} password
* @param {myAuthorizerCB} callback
*/ */
function myAuthorizer(username, password, callback) { function myAuthorizer(username, password, callback) {
// Login Rate Limit // Login Rate Limit

View file

@ -7,6 +7,7 @@ exports.latestVersion = null;
let interval; let interval;
/** Start 48 hour check interval */
exports.startInterval = () => { exports.startInterval = () => {
let check = async () => { let check = async () => {
try { try {
@ -42,6 +43,11 @@ exports.startInterval = () => {
interval = setInterval(check, 3600 * 1000 * 48); 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) => { exports.enableCheckUpdate = async (value) => {
await setSetting("checkUpdate", value); await setSetting("checkUpdate", value);

View file

@ -9,10 +9,9 @@ const { setting } = require("./util-server");
const checkVersion = require("./check-version"); const checkVersion = require("./check-version");
/** /**
* Send a list of notifications to the user. * Send list of notification providers to client
* @param {Socket} socket The socket object that is connected to the client. * @param {Socket} socket Socket.io socket instance
* * @returns {Promise<Bean[]>}
* Generated by Trelent
*/ */
async function sendNotificationList(socket) { async function sendNotificationList(socket) {
const timeLogger = new TimeLogger(); const timeLogger = new TimeLogger();
@ -35,8 +34,11 @@ async function sendNotificationList(socket) {
/** /**
* Send Heartbeat History list to 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 {Socket} socket Socket.io instance
* @param overwrite Overwrite client-side's heartbeat list * @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) { async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite = false) {
const timeLogger = new TimeLogger(); const timeLogger = new TimeLogger();
@ -63,10 +65,11 @@ async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite =
/** /**
* Important Heart beat list (aka event list) * Important Heart beat list (aka event list)
* @param socket * @param {Socket} socket Socket.io instance
* @param monitorID * @param {number} monitorID ID of monitor to send heartbeat history
* @param toUser True = send to all browsers with the same user id, False = send to the current browser only * @param {boolean} [toUser=false] 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 {boolean} [overwrite=false] Overwrite client-side's heartbeat list
* @returns {Promise<void>}
*/ */
async function sendImportantHeartbeatList(socket, monitorID, toUser = false, overwrite = false) { async function sendImportantHeartbeatList(socket, monitorID, toUser = false, overwrite = false) {
const timeLogger = new TimeLogger(); const timeLogger = new TimeLogger();
@ -91,9 +94,8 @@ async function sendImportantHeartbeatList(socket, monitorID, toUser = false, ove
} }
/** /**
* Delivers proxy list * Emit proxy list to client
* * @param {Socket} socket Socket.io socket instance
* @param socket
* @return {Promise<Bean[]>} * @return {Promise<Bean[]>}
*/ */
async function sendProxyList(socket) { async function sendProxyList(socket) {
@ -109,9 +111,8 @@ async function sendProxyList(socket) {
/** /**
* Emits the version information to the client. * Emits the version information to the client.
* @param {Socket} socket The socket object that is connected to the client. * @param {Socket} socket Socket.io socket instance
* * @returns {Promise<void>}
* Generated by Trelent
*/ */
async function sendInfo(socket) { async function sendInfo(socket) {
socket.emit("info", { socket.emit("info", {

View file

@ -68,6 +68,10 @@ class Database {
static noReject = true; static noReject = true;
/**
* Initialize the database
* @param {Object} args Arguments to initialize DB with
*/
static init(args) { static init(args) {
// Data Directory (must be end with "/") // Data Directory (must be end with "/")
Database.dataDir = process.env.DATA_DIR || args["data-dir"] || "./data/"; Database.dataDir = process.env.DATA_DIR || args["data-dir"] || "./data/";
@ -85,6 +89,15 @@ class Database {
log.info("db", `Data Dir: ${Database.dataDir}`); 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) { static async connect(testMode = false, autoloadModels = true, noLog = false) {
const acquireConnectionTimeout = 120 * 1000; const acquireConnectionTimeout = 120 * 1000;
@ -144,6 +157,7 @@ class Database {
} }
} }
/** Patch the database */
static async patch() { static async patch() {
let version = parseInt(await setting("database_version")); let version = parseInt(await setting("database_version"));
@ -189,7 +203,9 @@ class Database {
} }
/** /**
* Patch DB using new process
* Call it from patch() only * Call it from patch() only
* @private
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
static async patch2() { static async patch2() {
@ -296,9 +312,12 @@ class Database {
} }
/** /**
* Patch database using new patching process
* Used it patch2() only * Used it patch2() only
* @private
* @param sqlFilename * @param sqlFilename
* @param databasePatchedFiles * @param databasePatchedFiles
* @returns {Promise<void>}
*/ */
static async patch2Recursion(sqlFilename, databasePatchedFiles) { static async patch2Recursion(sqlFilename, databasePatchedFiles) {
let value = this.patchList[sqlFilename]; 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 * Load an SQL file and execute it
* @param filename * @param filename Filename of SQL file to import
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
static async importSQLFile(filename) { 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"); await R.getCell("SELECT 1");
let text = fs.readFileSync(filename).toString(); let text = fs.readFileSync(filename).toString();
@ -366,6 +385,10 @@ class Database {
} }
} }
/**
* Aquire a direct connection to database
* @returns {any}
*/
static getBetterSQLite3Database() { static getBetterSQLite3Database() {
return R.knex.client.acquireConnection(); return R.knex.client.acquireConnection();
} }
@ -401,7 +424,7 @@ class Database {
/** /**
* One backup one time in this process. * One backup one time in this process.
* Reset this.backupPath if you want to backup again * Reset this.backupPath if you want to backup again
* @param version * @param {string} version Version code of backup
*/ */
static backup(version) { static backup(version) {
if (! this.backupPath) { if (! this.backupPath) {
@ -423,9 +446,7 @@ class Database {
} }
} }
/** /** Restore from most recent backup */
*
*/
static restore() { static restore() {
if (this.backupPath) { if (this.backupPath) {
log.error("db", "Patching the database failed!!! Restoring the backup"); log.error("db", "Patching the database failed!!! Restoring the backup");
@ -467,6 +488,7 @@ class Database {
} }
} }
/** Get the size of the database */
static getSize() { static getSize() {
log.debug("db", "Database.getSize()"); log.debug("db", "Database.getSize()");
let stats = fs.statSync(Database.path); let stats = fs.statSync(Database.path);
@ -474,6 +496,10 @@ class Database {
return stats.size; return stats.size;
} }
/**
* Shrink the database
* @returns {Promise<void>}
*/
static async shrink() { static async shrink() {
await R.exec("VACUUM"); await R.exec("VACUUM");
} }

View file

@ -8,10 +8,12 @@ const { log } = require("../src/util");
let ImageDataURI = (() => { let ImageDataURI = (() => {
/** /**
* @param {string} dataURI - A string that is a valid Data URI. * Decode the data:image/ 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. * @param {string} dataURI data:image/ URI to decode
* * @returns {?Object} An object with properties "imageType" and "dataBase64".
* Generated by Trelent * 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) { function decode(dataURI) {
if (!/data:image\//.test(dataURI)) { if (!/data:image\//.test(dataURI)) {
@ -28,11 +30,11 @@ let ImageDataURI = (() => {
} }
/** /**
* @param {Buffer} data - The image data to be encoded. * Endcode an image into data:image/ URI
* @param {String} mediaType - The type of the image, e.g., "image/png". * @param {(Buffer|string)} data Data to encode
* @returns {String|null} A string representing the base64-encoded version of the given Buffer object or null if an error occurred. * @param {string} mediaType Media type of data
* * @returns {(string|null)} A string representing the base64-encoded
* Generated by Trelent * version of the given Buffer object or null if an error occurred.
*/ */
function encode(data, mediaType) { function encode(data, mediaType) {
if (!data || !mediaType) { if (!data || !mediaType) {
@ -48,11 +50,10 @@ let ImageDataURI = (() => {
} }
/** /**
* Converts a data URI to a file path. * Write data URI to file
* @param {string} dataURI The Data URI of the image. * @param {string} dataURI data:image/ URI
* @param {string} [filePath] The path where the image will be saved, defaults to "./". * @param {string} [filePath] Path to write file to
* * @returns {Promise<string>}
* Generated by Trelent
*/ */
function outputFile(dataURI, filePath) { function outputFile(dataURI, filePath) {
filePath = filePath || "./"; filePath = filePath || "./";

View file

@ -10,6 +10,11 @@ const jobs = [
}, },
]; ];
/**
* Initialize background jobs
* @param {Object} args Arguments to pass to workers
* @returns {Bree}
*/
const initBackgroundJobs = function (args) { const initBackgroundJobs = function (args) {
bree = new Bree({ bree = new Bree({
root: path.resolve("server", "jobs"), root: path.resolve("server", "jobs"),

View file

@ -2,12 +2,22 @@ const { parentPort, workerData } = require("worker_threads");
const Database = require("../database"); const Database = require("../database");
const path = require("path"); 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) { const log = function (any) {
if (parentPort) { if (parentPort) {
parentPort.postMessage(any); parentPort.postMessage(any);
} }
}; };
/**
* Exit the worker process
* @param {number} error The status code to exit
*/
const exit = function (error) { const exit = function (error) {
if (error && error !== 0) { if (error && error !== 0) {
process.exit(error); process.exit(error);
@ -20,6 +30,7 @@ const exit = function (error) {
} }
}; };
/** Connects to the database */
const connectDb = async function () { const connectDb = async function () {
const dbPath = path.join( const dbPath = path.join(
process.env.DATA_DIR || workerData["data-dir"] || "./data/" process.env.DATA_DIR || workerData["data-dir"] || "./data/"

View file

@ -3,6 +3,12 @@ const { R } = require("redbean-node");
class Group extends BeanModel { 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) { async toPublicJSON(showTags = false) {
let monitorBeanList = await this.getMonitorList(); let monitorBeanList = await this.getMonitorList();
let monitorList = []; let monitorList = [];
@ -19,6 +25,10 @@ class Group extends BeanModel {
}; };
} }
/**
* Get all monitors
* @returns {Bean[]}
*/
async getMonitorList() { async getMonitorList() {
return R.convertToBeans("monitor", await R.getAll(` return R.convertToBeans("monitor", await R.getAll(`
SELECT monitor.* FROM monitor, monitor_group SELECT monitor.* FROM monitor, monitor_group

View file

@ -13,6 +13,11 @@ const { BeanModel } = require("redbean-node/dist/bean-model");
*/ */
class Heartbeat extends BeanModel { class Heartbeat extends BeanModel {
/**
* Return an object that ready to parse to JSON for public
* Only show necessary data to public
* @returns {Object}
*/
toPublicJSON() { toPublicJSON() {
return { return {
status: this.status, status: this.status,
@ -22,6 +27,10 @@ class Heartbeat extends BeanModel {
}; };
} }
/**
* Return an object that ready to parse to JSON
* @returns {Object}
*/
toJSON() { toJSON() {
return { return {
monitorID: this.monitor_id, monitorID: this.monitor_id,

View file

@ -2,6 +2,11 @@ const { BeanModel } = require("redbean-node/dist/bean-model");
class Incident extends BeanModel { class Incident extends BeanModel {
/**
* Return an object that ready to parse to JSON for public
* Only show necessary data to public
* @returns {Object}
*/
toPublicJSON() { toPublicJSON() {
return { return {
id: this.id, id: this.id,

View file

@ -27,6 +27,7 @@ class Monitor extends BeanModel {
/** /**
* Return an object that ready to parse to JSON for public * Return an object that ready to parse to JSON for public
* Only show necessary data to public * Only show necessary data to public
* @returns {Object}
*/ */
async toPublicJSON(showTags = false) { async toPublicJSON(showTags = false) {
let obj = { let obj = {
@ -41,6 +42,7 @@ class Monitor extends BeanModel {
/** /**
* Return an object that ready to parse to JSON * Return an object that ready to parse to JSON
* @returns {Object}
*/ */
async toJSON(includeSensitiveData = true) { async toJSON(includeSensitiveData = true) {
@ -101,6 +103,10 @@ class Monitor extends BeanModel {
return data; return data;
} }
/**
* Get all tags applied to this monitor
* @returns {Promise<LooseObject<any>[]>}
*/
async getTags() { 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 ]); 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"); return Buffer.from(user + ":" + pass).toString("base64");
} }
/**
* Is the TLS expiry notification enabled?
* @returns {boolean}
*/
isEnabledExpiryNotification() { isEnabledExpiryNotification() {
return Boolean(this.expiryNotification); return Boolean(this.expiryNotification);
} }
@ -134,10 +144,18 @@ class Monitor extends BeanModel {
return Boolean(this.upsideDown); return Boolean(this.upsideDown);
} }
/**
* Get accepted status codes
* @returns {Object}
*/
getAcceptedStatuscodes() { getAcceptedStatuscodes() {
return JSON.parse(this.accepted_statuscodes_json); return JSON.parse(this.accepted_statuscodes_json);
} }
/**
* Start monitor
* @param {Server} io Socket server instance
*/
start(io) { start(io) {
let previousBeat = null; let previousBeat = null;
let retries = 0; let retries = 0;
@ -497,6 +515,7 @@ class Monitor extends BeanModel {
}; };
/** Get a heartbeat and handle errors */
const safeBeat = async () => { const safeBeat = async () => {
try { try {
await beat(); await beat();
@ -522,6 +541,7 @@ class Monitor extends BeanModel {
} }
} }
/** Stop monitor */
stop() { stop() {
clearTimeout(this.heartbeatInterval); clearTimeout(this.heartbeatInterval);
this.isStop = true; this.isStop = true;
@ -529,6 +549,10 @@ class Monitor extends BeanModel {
this.prometheus().remove(); this.prometheus().remove();
} }
/**
* Get a new prometheus instance
* @returns {Prometheus}
*/
prometheus() { prometheus() {
return new Prometheus(this); return new Prometheus(this);
} }
@ -537,7 +561,7 @@ class Monitor extends BeanModel {
* Helper Method: * Helper Method:
* returns URL object for further usage * returns URL object for further usage
* returns null if url is invalid * returns null if url is invalid
* @returns {null|URL} * @returns {(null|URL)}
*/ */
getUrl() { getUrl() {
try { try {
@ -550,7 +574,7 @@ class Monitor extends BeanModel {
/** /**
* Store TLS info to database * Store TLS info to database
* @param checkCertificateResult * @param checkCertificateResult
* @returns {Promise<object>} * @returns {Promise<Object>}
*/ */
async updateTlsInfo(checkCertificateResult) { async updateTlsInfo(checkCertificateResult) {
let tlsInfoBean = await R.findOne("monitor_tls_info", "monitor_id = ?", [ let tlsInfoBean = await R.findOne("monitor_tls_info", "monitor_id = ?", [
@ -592,6 +616,12 @@ class Monitor extends BeanModel {
return checkCertificateResult; 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) { static async sendStats(io, monitorID, userID) {
const hasClients = getTotalClientInRoom(io, userID) > 0; const hasClients = getTotalClientInRoom(io, userID) > 0;
@ -606,8 +636,8 @@ class Monitor extends BeanModel {
} }
/** /**
* * Send the average ping to user
* @param duration : int Hours * @param {number} duration Hours
*/ */
static async sendAvgPing(duration, io, monitorID, userID) { static async sendAvgPing(duration, io, monitorID, userID) {
const timeLogger = new TimeLogger(); const timeLogger = new TimeLogger();
@ -627,6 +657,12 @@ class Monitor extends BeanModel {
io.to(userID).emit("avgPing", monitorID, avgPing); 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) { static async sendCertInfo(io, monitorID, userID) {
let tlsInfo = await R.findOne("monitor_tls_info", "monitor_id = ?", [ let tlsInfo = await R.findOne("monitor_tls_info", "monitor_id = ?", [
monitorID, monitorID,
@ -640,7 +676,8 @@ class Monitor extends BeanModel {
* Uptime with calculation * Uptime with calculation
* Calculation based on: * Calculation based on:
* https://www.uptrends.com/support/kb/reporting/calculation-of-uptime-and-downtime * 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) { static async calcUptime(duration, monitorID) {
const timeLogger = new TimeLogger(); const timeLogger = new TimeLogger();
@ -706,13 +743,23 @@ class Monitor extends BeanModel {
/** /**
* Send Uptime * 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) { static async sendUptime(duration, io, monitorID, userID) {
const uptime = await this.calcUptime(duration, monitorID); const uptime = await this.calcUptime(duration, monitorID);
io.to(userID).emit("uptime", monitorID, duration, uptime); 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) { static isImportantBeat(isFirstBeat, previousBeatStatus, currentBeatStatus) {
// * ? -> ANY STATUS = important [isFirstBeat] // * ? -> ANY STATUS = important [isFirstBeat]
// UP -> PENDING = not important // UP -> PENDING = not important
@ -731,6 +778,12 @@ class Monitor extends BeanModel {
return isImportant; 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) { static async sendNotification(isFirstBeat, monitor, bean) {
if (!isFirstBeat || bean.status === DOWN) { if (!isFirstBeat || bean.status === DOWN) {
const notificationList = await Monitor.getNotificationList(monitor); 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) { 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 ", [ let notificationList = await R.getAll("SELECT notification.* FROM notification, monitor_notification WHERE monitor_id = ? AND monitor_notification.notification_id = notification.id ", [
monitor.id, monitor.id,
@ -762,6 +820,10 @@ class Monitor extends BeanModel {
return notificationList; return notificationList;
} }
/**
* Send notification about a certificate
* @param {Object} tlsInfoObject Information about certificate
*/
async sendCertNotification(tlsInfoObject) { async sendCertNotification(tlsInfoObject) {
if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) { if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) {
const notificationList = await Monitor.getNotificationList(this); 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) { async sendCertNotificationByTargetDays(daysRemaining, targetDays, notificationList) {
if (daysRemaining > targetDays) { 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) { static async getPreviousHeartbeat(monitorID) {
return await R.getRow(` return await R.getRow(`
SELECT status, time FROM heartbeat SELECT status, time FROM heartbeat

View file

@ -1,6 +1,10 @@
const { BeanModel } = require("redbean-node/dist/bean-model"); const { BeanModel } = require("redbean-node/dist/bean-model");
class Proxy extends BeanModel { class Proxy extends BeanModel {
/**
* Return an object that ready to parse to JSON
* @returns {Object}
*/
toJSON() { toJSON() {
return { return {
id: this._id, id: this._id,

View file

@ -6,6 +6,7 @@ class StatusPage extends BeanModel {
static domainMappingList = { }; static domainMappingList = { };
/** /**
* Loads domain mapping from DB
* Return object like this: { "test-uptime.kuma.pet": "default" } * Return object like this: { "test-uptime.kuma.pet": "default" }
* @returns {Promise<void>} * @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) { static async sendStatusPageList(io, socket) {
let result = {}; let result = {};
@ -30,6 +37,11 @@ class StatusPage extends BeanModel {
return list; return list;
} }
/**
* Update list of domain names
* @param {string[]} domainNameList
* @returns {Promise<void>}
*/
async updateDomainNameList(domainNameList) { async updateDomainNameList(domainNameList) {
if (!Array.isArray(domainNameList)) { if (!Array.isArray(domainNameList)) {
@ -69,6 +81,10 @@ class StatusPage extends BeanModel {
} }
} }
/**
* Get list of domain names
* @returns {Object[]}
*/
getDomainNameList() { getDomainNameList() {
let domainList = []; let domainList = [];
for (let domain in StatusPage.domainMappingList) { for (let domain in StatusPage.domainMappingList) {
@ -81,6 +97,10 @@ class StatusPage extends BeanModel {
return domainList; return domainList;
} }
/**
* Return an object that ready to parse to JSON
* @returns {Object}
*/
async toJSON() { async toJSON() {
return { return {
id: this.id, 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() { async toPublicJSON() {
return { return {
slug: this.slug, slug: this.slug,
@ -113,12 +138,20 @@ class StatusPage extends BeanModel {
}; };
} }
/**
* Convert slug to status page ID
* @param {string} slug
*/
static async slugToID(slug) { static async slugToID(slug) {
return await R.getCell("SELECT id FROM status_page WHERE slug = ? ", [ return await R.getCell("SELECT id FROM status_page WHERE slug = ? ", [
slug slug
]); ]);
} }
/**
* Get path to the icon for the page
* @returns {string}
*/
getIcon() { getIcon() {
if (!this.icon) { if (!this.icon) {
return "/icon.svg"; return "/icon.svg";

View file

@ -1,6 +1,11 @@
const { BeanModel } = require("redbean-node/dist/bean-model"); const { BeanModel } = require("redbean-node/dist/bean-model");
class Tag extends BeanModel { class Tag extends BeanModel {
/**
* Return an object that ready to parse to JSON
* @returns {Object}
*/
toJSON() { toJSON() {
return { return {
id: this._id, id: this._id,

View file

@ -3,12 +3,11 @@ const passwordHash = require("../password-hash");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
class User extends BeanModel { 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. * Fix #1510, as in the context reset-password.js, there is no auto model mapping. Call this static function instead.
* @param userID * @param {number} userID ID of user to update
* @param newPassword * @param {string} newPassword
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
static async resetPassword(userID, newPassword) { static async resetPassword(userID, newPassword) {
@ -19,8 +18,8 @@ class User extends BeanModel {
} }
/** /**
* * Reset this users password
* @param newPassword * @param {string} newPassword
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async resetPassword(newPassword) { async resetPassword(newPassword) {

View file

@ -13,27 +13,49 @@ let t = {
let instances = []; let instances = [];
/**
* Does a === b
* @param {any} a
* @returns {function(any): boolean}
*/
let matches = function (a) { let matches = function (a) {
return function (b) { return function (b) {
return a === b; return a === b;
}; };
}; };
/**
* Does a!==b
* @param {any} a
* @returns {function(any): boolean}
*/
let doesntMatch = function (a) { let doesntMatch = function (a) {
return function (b) { return function (b) {
return !matches(a)(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 logDuration = function (d, prefix) {
let str = d > 1000 ? (d / 1000).toFixed(2) + "sec" : d + "ms"; let str = d > 1000 ? (d / 1000).toFixed(2) + "sec" : d + "ms";
return "\x1b[33m- " + (prefix ? prefix + " " : "") + str + "\x1b[0m"; return "\x1b[33m- " + (prefix ? prefix + " " : "") + str + "\x1b[0m";
}; };
/**
* Get safe headers
* @param {Object} res Express response object
* @returns {Object}
*/
function getSafeHeaders(res) { function getSafeHeaders(res) {
return res.getHeaders ? res.getHeaders() : res._headers; return res.getHeaders ? res.getHeaders() : res._headers;
} }
/** Constructor for ApiCache instance */
function ApiCache() { function ApiCache() {
let memCache = new MemoryCache(); let memCache = new MemoryCache();
@ -70,10 +92,10 @@ function ApiCache() {
/** /**
* Logs a message to the console if the `DEBUG` environment variable is set. * Logs a message to the console if the `DEBUG` environment variable is set.
* @param {string} a - The first argument to log. * @param {string} a The first argument to log.
* @param {string} b - The second argument to log. * @param {string} b The second argument to log.
* @param {string} c - The third 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} d The fourth argument to log, and so on... (optional)
* *
* Generated by Trelent * Generated by Trelent
*/ */
@ -90,8 +112,8 @@ function ApiCache() {
* Returns true if the given request and response should be logged. * Returns true if the given request and response should be logged.
* @param {Object} request The HTTP request object. * @param {Object} request The HTTP request object.
* @param {Object} response The HTTP response object. * @param {Object} response The HTTP response object.
* * @param {function(Object, Object):boolean} toggle
* Generated by Trelent * @returns {boolean}
*/ */
function shouldCacheResponse(request, response, toggle) { function shouldCacheResponse(request, response, toggle) {
let opt = globalOptions; let opt = globalOptions;
@ -116,10 +138,9 @@ function ApiCache() {
} }
/** /**
* Adds a key to the index. * Add key to index array
* @param {string} key The key to add. * @param {string} key Key to add
* * @param {Object} req Express request object
* Generated by Trelent
*/ */
function addIndexEntries(key, req) { function addIndexEntries(key, req) {
let groupName = req.apicacheGroup; let groupName = req.apicacheGroup;
@ -135,8 +156,11 @@ function ApiCache() {
/** /**
* Returns a new object containing only the whitelisted headers. * Returns a new object containing only the whitelisted headers.
* @param {Object} headers The original object of header names and values. * @param {Object} headers The original object of header names and
* @param {Array.<string>} globalOptions.headerWhitelist An array of strings representing the whitelisted header names to keep in the output object. * values.
* @param {string[]} globalOptions.headerWhitelist An array of
* strings representing the whitelisted header names to keep in the
* output object.
* *
* Generated by Trelent * Generated by Trelent
*/ */
@ -152,8 +176,10 @@ function ApiCache() {
} }
/** /**
* Create a cache object
* @param {Object} headers The response headers to filter. * @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 * Generated by Trelent
*/ */
@ -170,8 +196,9 @@ function ApiCache() {
/** /**
* Sets a cache value for the given key. * Sets a cache value for the given key.
* @param {string} key The cache key to set. * @param {string} key The cache key to set.
* @param {*} value The cache value to set. * @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). * @param {number} duration How long in milliseconds the cached
* response should be valid for (defaults to 1 hour).
* *
* Generated by Trelent * Generated by Trelent
*/ */
@ -199,7 +226,8 @@ function ApiCache() {
/** /**
* Appends content to the response. * 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 * Generated by Trelent
*/ */
@ -229,11 +257,15 @@ function ApiCache() {
} }
/** /**
* Monkeypatches the response object to add cache control headers and create a cache object. * Monkeypatches the response object to add cache control headers
* @param {Object} req - The request object. * and create a cache object.
* @param {Object} res - The response object. * @param {Object} req Express request object
* * @param {Object} res Express response object
* Generated by Trelent * @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) { function makeResponseCacheable(req, res, next, key, duration, strDuration, toggle) {
// monkeypatch res.end to create cache object // monkeypatch res.end to create cache object
@ -302,11 +334,15 @@ function ApiCache() {
} }
/** /**
* @param {Request} request * Send a cached response to client
* @param {Response} response * @param {Request} request Express request object
* @returns {boolean|undefined} true if the request should be cached, false otherwise. If undefined, defaults to true. * @param {Response} response Express response object
* * @param {object} cacheObject Cache object to send
* Generated by Trelent * @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) { function sendCachedResponse(request, response, cacheObject, toggle, next, duration) {
if (toggle && !toggle(request, response)) { if (toggle && !toggle(request, response)) {
@ -348,12 +384,19 @@ function ApiCache() {
return response.end(data, cacheObject.encoding); return response.end(data, cacheObject.encoding);
} }
/** Sync caching options */
function syncOptions() { function syncOptions() {
for (let i in middlewareOptions) { for (let i in middlewareOptions) {
Object.assign(middlewareOptions[i].options, globalOptions, middlewareOptions[i].localOptions); 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) { this.clear = function (target, isAutomatic) {
let group = index.groups[target]; let group = index.groups[target];
let redis = globalOptions.redisClient; let redis = globalOptions.redisClient;
@ -430,10 +473,11 @@ function ApiCache() {
/** /**
* Converts a duration string to an integer number of milliseconds. * Converts a duration string to an integer number of milliseconds.
* @param {string} duration - The string to convert. * @param {(string|number)} duration The string to convert.
* @returns {number} The converted value in milliseconds, or the defaultDuration if it can't be parsed. * @param {number} defaultDuration The default duration to return if
* * can't parse duration
* Generated by Trelent * @returns {number} The converted value in milliseconds, or the
* defaultDuration if it can't be parsed.
*/ */
function parseDuration(duration, defaultDuration) { function parseDuration(duration, defaultDuration) {
if (typeof duration === "number") { if (typeof duration === "number") {
@ -457,17 +501,24 @@ function ApiCache() {
return defaultDuration; return defaultDuration;
} }
/**
* Parse duration
* @param {(number|string)} duration
* @returns {number} Duration parsed to a number
*/
this.getDuration = function (duration) { this.getDuration = function (duration) {
return parseDuration(duration, globalOptions.defaultDuration); 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> * <code>
* app.get('/api/cache/performance', (req, res) => { * app.get('/api/cache/performance', (req, res) => {
* res.json(apicache.getPerformance()) * res.json(apicache.getPerformance())
* }) * })
* </code> * </code>
* @returns {any[]}
*/ */
this.getPerformance = function () { this.getPerformance = function () {
return performanceArray.map(function (p) { 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) { this.getIndex = function (group) {
if (group) { if (group) {
return index.groups[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) { this.middleware = function cache(strDuration, middlewareToggle, localOptions) {
let duration = instance.getDuration(strDuration); let duration = instance.getDuration(strDuration);
let opt = {}; let opt = {};
@ -513,35 +577,41 @@ function ApiCache() {
} }
/** /**
* 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() { function CachePerformance() {
/** /**
* Tracks the hit rate for the last 100 requests. * Tracks the hit rate for the last 100 requests. If there
* If there have been fewer than 100 requests, the hit rate just considers the requests that have happened. * 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 this.hitsLast100 = new Uint8Array(100 / 4); // each hit is 2 bits
/** /**
* Tracks the hit rate for the last 1000 requests. * Tracks the hit rate for the last 1000 requests. If there
* If there have been fewer than 1000 requests, the hit rate just considers the requests that have happened. * 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 this.hitsLast1000 = new Uint8Array(1000 / 4); // each hit is 2 bits
/** /**
* Tracks the hit rate for the last 10000 requests. * Tracks the hit rate for the last 10000 requests. If there
* If there have been fewer than 10000 requests, the hit rate just considers the requests that have happened. * 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 this.hitsLast10000 = new Uint8Array(10000 / 4); // each hit is 2 bits
/** /**
* Tracks the hit rate for the last 100000 requests. * Tracks the hit rate for the last 100000 requests. If
* If there have been fewer than 100000 requests, the hit rate just considers the requests that have happened. * 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 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; this.callCount = 0;
@ -551,17 +621,20 @@ function ApiCache() {
this.hitCount = 0; 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; 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; this.lastCacheMiss = null;
/** /**
* Return performance statistics * Return performance statistics
* @returns {Object}
*/ */
this.report = function () { this.report = function () {
return { return {
@ -579,9 +652,12 @@ function ApiCache() {
}; };
/** /**
* Computes a cache hit rate from an array of hits and misses. * Computes a cache hit rate from an array of hits and
* @param {Uint8Array} array An array representing hits and misses. * misses.
* @returns a number between 0 and 1, or null if the array has no hits or 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) { this.hitRate = function (array) {
let hits = 0; let hits = 0;
@ -608,15 +684,16 @@ function ApiCache() {
}; };
/** /**
* Record a hit or miss in the given array. It will be recorded at a position determined * Record a hit or miss in the given array. It will be
* by the current value of the callCount variable. * recorded at a position determined by the current value of
* @param {Uint8Array} array An array representing hits and misses. * the callCount variable.
* @param {Uint8Array} array An array representing hits and
* misses.
* @param {boolean} hit true for a hit, false for a miss * @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 element in the array is 8 bits, and encodes 4
* Each hit or miss is encoded as to bits as follows: * hit/miss records. Each hit or miss is encoded as to bits
* 00 means no hit or miss has been recorded in these bits * as follows: 00 means no hit or miss has been recorded in
* 01 encodes a hit * these bits 01 encodes a hit 10 encodes a miss
* 10 encodes a miss
*/ */
this.recordHitInArray = function (array, hit) { this.recordHitInArray = function (array, hit) {
let arrayIndex = ~~(this.callCount / 4) % array.length; let arrayIndex = ~~(this.callCount / 4) % array.length;
@ -627,8 +704,10 @@ function ApiCache() {
}; };
/** /**
* Records the hit or miss in the tracking arrays and increments the call count. * Records the hit or miss in the tracking arrays and
* @param {boolean} hit true records a hit, false records a miss * increments the call count.
* @param {boolean} hit true records a hit, false records a
* miss
*/ */
this.recordHit = function (hit) { this.recordHit = function (hit) {
this.recordHitInArray(this.hitsLast100, hit); this.recordHitInArray(this.hitsLast100, hit);
@ -664,6 +743,13 @@ function ApiCache() {
performanceArray.push(perf); 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) { let cache = function (req, res, next) {
function bypass() { function bypass() {
debug("bypass detected, skipping cache."); debug("bypass detected, skipping cache.");
@ -771,6 +857,11 @@ function ApiCache() {
return cache; return cache;
}; };
/**
* Process options
* @param {Object} options
* @returns {Object}
*/
this.options = function (options) { this.options = function (options) {
if (options) { if (options) {
Object.assign(globalOptions, options); Object.assign(globalOptions, options);
@ -791,6 +882,7 @@ function ApiCache() {
} }
}; };
/** Reset the index */
this.resetIndex = function () { this.resetIndex = function () {
index = { index = {
all: [], 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) { this.newInstance = function (config) {
let instance = new ApiCache(); let instance = new ApiCache();
@ -808,6 +905,7 @@ function ApiCache() {
return instance; return instance;
}; };
/** Clone this instance */
this.clone = function () { this.clone = function () {
return this.newInstance(this.options()); return this.newInstance(this.options());
}; };

View file

@ -3,6 +3,15 @@ function MemoryCache() {
this.size = 0; 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) { MemoryCache.prototype.add = function (key, value, time, timeoutCallback) {
let old = this.cache[key]; let old = this.cache[key];
let instance = this; let instance = this;
@ -22,6 +31,11 @@ MemoryCache.prototype.add = function (key, value, time, timeoutCallback) {
return entry; return entry;
}; };
/**
* Delete a cache entry
* @param {string} key Key to delete
* @returns {null}
*/
MemoryCache.prototype.delete = function (key) { MemoryCache.prototype.delete = function (key) {
let entry = this.cache[key]; let entry = this.cache[key];
@ -36,18 +50,32 @@ MemoryCache.prototype.delete = function (key) {
return null; return null;
}; };
/**
* Get value of key
* @param {string} key
* @returns {Object}
*/
MemoryCache.prototype.get = function (key) { MemoryCache.prototype.get = function (key) {
let entry = this.cache[key]; let entry = this.cache[key];
return entry; return entry;
}; };
/**
* Get value of cache entry
* @param {string} key
* @returns {any}
*/
MemoryCache.prototype.getValue = function (key) { MemoryCache.prototype.getValue = function (key) {
let entry = this.get(key); let entry = this.get(key);
return entry && entry.value; return entry && entry.value;
}; };
/**
* Clear cache
* @returns {boolean}
*/
MemoryCache.prototype.clear = function () { MemoryCache.prototype.clear = function () {
Object.keys(this.cache).forEach(function (key) { Object.keys(this.cache).forEach(function (key) {
this.delete(key); this.delete(key);

View file

@ -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) { async sendSms(notification, msgbody) {
let params = { let params = {
PhoneNumbers: notification.phonenumber, PhoneNumbers: notification.phonenumber,
@ -70,7 +76,12 @@ class AliyunSMS extends NotificationProvider {
return false; 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) { sign(param, AccessKeySecret) {
let param2 = {}; let param2 = {};
let data = []; let data = [];
@ -93,6 +104,11 @@ class AliyunSMS extends NotificationProvider {
.digest("base64"); .digest("base64");
} }
/**
* Convert status constant to string
* @param {const} status The status constant
* @returns {string}
*/
statusToString(status) { statusToString(status) {
switch (status) { switch (status) {
case DOWN: case DOWN:

View file

@ -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) { appendAdditionalParameters(postUrl) {
// grouping all our notifications // grouping all our notifications
postUrl += "?group=" + barkNotificationGroup; postUrl += "?group=" + barkNotificationGroup;
@ -55,7 +60,11 @@ class Bark extends NotificationProvider {
return postUrl; 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) { checkResult(result) {
if (result.status == null) { if (result.status == null) {
throw new Error("Bark notification failed with invalid response!"); 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) { async postNotification(title, subtitle, endpoint) {
// url encode title and subtitle // url encode title and subtitle
title = encodeURIComponent(title); title = encodeURIComponent(title);

View file

@ -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) { async sendToDingDing(notification, params) {
let timestamp = Date.now(); let timestamp = Date.now();
@ -56,7 +62,12 @@ class DingDing extends NotificationProvider {
return false; 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) { sign(timestamp, secretKey) {
return Crypto return Crypto
.createHmac("sha256", Buffer.from(secretKey, "utf8")) .createHmac("sha256", Buffer.from(secretKey, "utf8"))
@ -64,7 +75,13 @@ class DingDing extends NotificationProvider {
.digest("base64"); .digest("base64");
} }
/**
* Convert status constant to string
* @param {const} status The status constant
* @returns {string}
*/
statusToString(status) { statusToString(status) {
// TODO: Move to notification-provider.js to avoid repetition in classes
switch (status) { switch (status) {
case DOWN: case DOWN:
return "DOWN"; return "DOWN";

View file

@ -7,17 +7,23 @@ class NotificationProvider {
name = undefined; name = undefined;
/** /**
* @param notification : BeanModel * Send a notification
* @param msg : string General Message * @param {BeanModel} notification
* @param monitorJSON : object Monitor details (For Up/Down only) * @param {string} msg General Message
* @param heartbeatJSON : object Heartbeat details (For Up/Down only) * @param {?Object} monitorJSON Monitor details (For Up/Down only)
* @param {?Object} heartbeatJSON Heartbeat details (For Up/Down only)
* @returns {Promise<string>} Return Successful Message * @returns {Promise<string>} Return Successful Message
* Throw Error with fail msg * @throws Error with fail msg
*/ */
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
throw new Error("Have to override Notification.send(...)"); 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) { throwGeneralAxiosError(error) {
let msg = "Error: " + error + " "; let msg = "Error: " + error + " ";

View file

@ -10,6 +10,7 @@ class Slack extends NotificationProvider {
/** /**
* Deprecated property notification.slackbutton * Deprecated property notification.slackbutton
* Set it as primary base url if this is not yet set. * 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) { static async deprecateURL(url) {
let currentPrimaryBaseURL = await setting("primaryBaseURL"); let currentPrimaryBaseURL = await setting("primaryBaseURL");

View file

@ -5,6 +5,12 @@ const { DOWN, UP } = require("../../src/util");
class Teams extends NotificationProvider { class Teams extends NotificationProvider {
name = "teams"; name = "teams";
/**
* Generate the message to send
* @param {const} status The status constant
* @param {string} monitorName Name of monitor
* @returns {string}
*/
_statusMessageFactory = (status, monitorName) => { _statusMessageFactory = (status, monitorName) => {
if (status === DOWN) { if (status === DOWN) {
return `🔴 Application [${monitorName}] went down`; return `🔴 Application [${monitorName}] went down`;
@ -14,6 +20,11 @@ class Teams extends NotificationProvider {
return "Notification"; 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) => { _getThemeColor = (status) => {
if (status === DOWN) { if (status === DOWN) {
return "ff0000"; return "ff0000";
@ -24,6 +35,14 @@ class Teams extends NotificationProvider {
return "008cff"; 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 = ({ _notificationPayloadFactory = ({
status, status,
monitorMessage, 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) => { _sendNotification = async (webhookUrl, payload) => {
await axios.post(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) => { _handleGeneralNotification = (webhookUrl, msg) => {
const payload = this._notificationPayloadFactory({ const payload = this._notificationPayloadFactory({
monitorMessage: msg monitorMessage: msg

View file

@ -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) { composeMessage(heartbeatJSON, msg) {
let title; let title;
if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] === UP) { if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] === UP) {

View file

@ -38,6 +38,7 @@ class Notification {
providerList = {}; providerList = {};
/** Initialize the notification providers */
static init() { static init() {
log.info("notification", "Prepare Notification Providers"); log.info("notification", "Prepare Notification Providers");
@ -92,13 +93,13 @@ class Notification {
} }
/** /**
* * Send a notification
* @param notification : BeanModel * @param {BeanModel} notification
* @param msg : string General Message * @param {string} msg General Message
* @param monitorJSON : object Monitor details (For Up/Down only) * @param {Object} monitorJSON Monitor details (For Up/Down only)
* @param heartbeatJSON : object Heartbeat details (For Up/Down only) * @param {Object} heartbeatJSON Heartbeat details (For Up/Down only)
* @returns {Promise<string>} Successful msg * @returns {Promise<string>} Successful msg
* Throw Error with fail msg * @throws Error with fail msg
*/ */
static async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { static async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
if (this.providerList[notification.type]) { 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) { static async save(notification, notificationID, userID) {
let bean; let bean;
@ -138,6 +146,12 @@ class Notification {
return bean; 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) { static async delete(notificationID, userID) {
let bean = await R.findOne("notification", " id = ? AND user_id = ? ", [ let bean = await R.findOne("notification", " id = ? AND user_id = ? ", [
notificationID, notificationID,
@ -151,6 +165,10 @@ class Notification {
await R.trash(bean); await R.trash(bean);
} }
/**
* Check if apprise exists
* @returns {boolean} Does the command apprise exist?
*/
static checkApprise() { static checkApprise() {
let commandExistsSync = require("command-exists").sync; let commandExistsSync = require("command-exists").sync;
let exists = commandExistsSync("apprise"); let exists = commandExistsSync("apprise");
@ -160,11 +178,10 @@ class Notification {
} }
/** /**
* Adds a new monitor to the database. * Apply the notification to every monitor
* @param {number} userID The ID of the user that owns this monitor. * @param {number} notificationID ID of notification to apply
* @param {string} name The name of this monitor. * @param {number} userID ID of user who created notification
* * @returns {Promise<void>}
* Generated by Trelent
*/ */
async function applyNotificationEveryMonitor(notificationID, userID) { async function applyNotificationEveryMonitor(notificationID, userID) {
let monitors = await R.getAll("SELECT id FROM monitor WHERE user_id = ?", [ let monitors = await R.getAll("SELECT id FROM monitor WHERE user_id = ?", [

View file

@ -2,10 +2,21 @@ const passwordHashOld = require("password-hash");
const bcrypt = require("bcryptjs"); const bcrypt = require("bcryptjs");
const saltRounds = 10; const saltRounds = 10;
/**
* Hash a password
* @param {string} password
* @returns {string}
*/
exports.generate = function (password) { exports.generate = function (password) {
return bcrypt.hashSync(password, saltRounds); 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) { exports.verify = function (password, hash) {
if (isSHA1(hash)) { if (isSHA1(hash)) {
return passwordHashOld.verify(password, hash); return passwordHashOld.verify(password, hash);
@ -14,10 +25,19 @@ exports.verify = function (password, hash) {
return bcrypt.compareSync(password, hash); return bcrypt.compareSync(password, hash);
}; };
/**
* Is the hash a SHA1 hash
* @param {string} hash
* @returns {boolean}
*/
function isSHA1(hash) { function isSHA1(hash) {
return (typeof hash === "string" && hash.startsWith("sha1")); return (typeof hash === "string" && hash.startsWith("sha1"));
} }
/**
* Does the hash need to be rehashed?
* @returns {boolean}
*/
exports.needRehash = function (hash) { exports.needRehash = function (hash) {
return isSHA1(hash); return isSHA1(hash);
}; };

View file

@ -9,11 +9,10 @@ const util = require("./util-server");
module.exports = Ping; module.exports = Ping;
/** /**
* @param {string} host - The host to ping * Constructor for ping class
* @param {object} [options] - Options for the ping command * @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 * @param {array|string} [options.args] - Arguments to pass to the ping command
*
* Generated by Trelent
*/ */
function Ping(host, options) { function Ping(host, options) {
if (!host) { if (!host) {
@ -82,8 +81,17 @@ function Ping(host, options) {
Ping.prototype.__proto__ = events.EventEmitter.prototype; 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) { Ping.prototype.send = function (callback) {
let self = this; let self = this;
callback = callback || function (err, ms) { 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) { Ping.prototype.start = function (callback) {
let self = this; let self = this;
this._i = setInterval(function () { this._i = setInterval(function () {
@ -167,8 +177,7 @@ Ping.prototype.start = function (callback) {
self.send(callback); self.send(callback);
}; };
// STOP SENDING PINGS /** Stop sending pings */
// ==================
Ping.prototype.stop = function () { Ping.prototype.stop = function () {
clearInterval(this._i); 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 * 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 * Thank @pemassi
* https://github.com/louislam/uptime-kuma/issues/570#issuecomment-941984094 * https://github.com/louislam/uptime-kuma/issues/570#issuecomment-941984094
* @param data * @param {any} data
* @returns {string} * @returns {string}
*/ */
function convertOutput(data) { function convertOutput(data) {

View file

@ -35,6 +35,9 @@ const monitorStatus = new PrometheusClient.Gauge({
class Prometheus { class Prometheus {
monitorLabelValues = {} monitorLabelValues = {}
/**
* @param {Object} monitor Monitor object to monitor
*/
constructor(monitor) { constructor(monitor) {
this.monitorLabelValues = { this.monitorLabelValues = {
monitor_name: monitor.name, 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) { update(heartbeat, tlsInfo) {
if (typeof tlsInfo !== "undefined") { if (typeof tlsInfo !== "undefined") {

View file

@ -2,11 +2,26 @@ const { RateLimiter } = require("limiter");
const { log } = require("../src/util"); const { log } = require("../src/util");
class KumaRateLimiter { class KumaRateLimiter {
/**
* @param {Object} config Rate limiter configuration object
*/
constructor(config) { constructor(config) {
this.errorMessage = config.errorMessage; this.errorMessage = config.errorMessage;
this.rateLimiter = new RateLimiter(config); 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) { async pass(callback, num = 1) {
const remainingRequests = await this.removeTokens(num); const remainingRequests = await this.removeTokens(num);
log.info("rate-limit", "remaining requests: " + remainingRequests); log.info("rate-limit", "remaining requests: " + remainingRequests);
@ -22,6 +37,11 @@ class KumaRateLimiter {
return true; return true;
} }
/**
* Remove a given number of tokens
* @param {number} [num=1] Number of tokens to remove
* @returns {Promise<number>}
*/
async removeTokens(num = 1) { async removeTokens(num = 1) {
return await this.rateLimiter.removeTokens(num); return await this.rateLimiter.removeTokens(num);
} }

View file

@ -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 = "") { function send403(res, msg = "") {
res.status(403).json({ res.status(403).json({
"status": "fail", "status": "fail",

View file

@ -1473,11 +1473,11 @@ try {
})(); })();
/** /**
* Adds or removes notifications from a monitor. * Update notifications for a given monitor
* @param {number} monitorID The ID of the monitor to add/remove notifications from. * @param {number} monitorID ID of monitor to update
* @param {Array.<number>} notificationIDList An array of IDs for the notifications to add/remove. * @param {number[]} notificationIDList List of new notification
* * providers to add
* Generated by Trelent * @returns {Promise<void>}
*/ */
async function updateMonitorNotification(monitorID, notificationIDList) { async function updateMonitorNotification(monitorID, notificationIDList) {
await R.exec("DELETE FROM monitor_notification WHERE monitor_id = ? ", [ 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. * Check if a given user owns a specific monitor
* @param {number} monitorID - The ID of the monitor to check ownership for. * @param {number} userID
* @param {number} userID - The ID of the user who is trying to access this data. * @param {number} monitorID
* * @returns {Promise<void>}
* Generated by Trelent * @throws {Error} The specified user does not own the monitor
*/ */
async function checkOwner(userID, monitorID) { async function checkOwner(userID, monitorID) {
let row = await R.getRow("SELECT id FROM monitor WHERE id = ? AND user_id = ? ", [ 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. * 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) { async function afterLogin(socket, user) {
socket.userID = user.id; socket.userID = user.id;
@ -1542,9 +1545,10 @@ async function afterLogin(socket, user) {
} }
/** /**
* Connect to the database and patch it if necessary. * Initialize the database
* * @param {boolean} [testMode=false] Should the connection be
* Generated by Trelent * started in test mode?
* @returns {Promise<void>}
*/ */
async function initDatabase(testMode = false) { async function initDatabase(testMode = false) {
if (! fs.existsSync(Database.path)) { if (! fs.existsSync(Database.path)) {
@ -1581,11 +1585,10 @@ async function initDatabase(testMode = false) {
} }
/** /**
* Resume a monitor. * Start the specified monitor
* @param {string} userID - The ID of the user who owns the monitor. * @param {number} userID ID of user who owns monitor
* @param {string} monitorID - The ID of the monitor to resume. * @param {number} monitorID ID of monitor to start
* * @returns {Promise<void>}
* Generated by Trelent
*/ */
async function startMonitor(userID, monitorID) { async function startMonitor(userID, monitorID) {
await checkOwner(userID, monitorID); await checkOwner(userID, monitorID);
@ -1609,16 +1612,21 @@ async function startMonitor(userID, monitorID) {
monitor.start(io); 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) { async function restartMonitor(userID, monitorID) {
return await startMonitor(userID, monitorID); return await startMonitor(userID, monitorID);
} }
/** /**
* Pause a monitor. * Pause a given monitor
* @param {string} userID - The ID of the user who owns the monitor. * @param {number} userID ID of user who owns monitor
* @param {string} monitorID - The ID of the monitor to pause. * @param {number} monitorID ID of monitor to start
* * @returns {Promise<void>}
* Generated by Trelent
*/ */
async function pauseMonitor(userID, monitorID) { async function pauseMonitor(userID, monitorID) {
await checkOwner(userID, monitorID); await checkOwner(userID, monitorID);
@ -1635,9 +1643,7 @@ async function pauseMonitor(userID, monitorID) {
} }
} }
/** /** Resume active monitors */
* Resume active monitors
*/
async function startMonitors() { async function startMonitors() {
let list = await R.find("monitor", " active = 1 "); 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. * Stops all monitors and closes the database connection.
* @param {string} signal The signal that triggered this function to be called. * @param {string} signal The signal that triggered this function to be called.
* * @returns {Promise<void>}
* Generated by Trelent
*/ */
async function shutdownFunction(signal) { async function shutdownFunction(signal) {
log.info("server", "Shutdown requested"); log.info("server", "Shutdown requested");
@ -1678,6 +1684,7 @@ function getClientIp(socket) {
return socket.client.conn.remoteAddress.replace(/^.*:/, ""); return socket.client.conn.remoteAddress.replace(/^.*:/, "");
} }
/** Final function called before application exits */
function finalFunction() { function finalFunction() {
log.info("server", "Graceful shutdown successful!"); log.info("server", "Graceful shutdown successful!");
} }

View file

@ -6,15 +6,28 @@ const io = UptimeKumaServer.getInstance().io;
const prefix = "cloudflared_"; const prefix = "cloudflared_";
const cloudflared = new CloudflaredTunnel(); const cloudflared = new CloudflaredTunnel();
/**
* Change running state
* @param {string} running Is it running?
* @param {string} message Message to pass
*/
cloudflared.change = (running, message) => { cloudflared.change = (running, message) => {
io.to("cloudflared").emit(prefix + "running", running); io.to("cloudflared").emit(prefix + "running", running);
io.to("cloudflared").emit(prefix + "message", message); io.to("cloudflared").emit(prefix + "message", message);
}; };
/**
* Emit an error message
* @param {string} errorMessage
*/
cloudflared.error = (errorMessage) => { cloudflared.error = (errorMessage) => {
io.to("cloudflared").emit(prefix + "errorMessage", errorMessage); io.to("cloudflared").emit(prefix + "errorMessage", errorMessage);
}; };
/**
* Handler for cloudflared
* @param {Socket} socket Socket.io instance
*/
module.exports.cloudflaredSocketHandler = (socket) => { module.exports.cloudflaredSocketHandler = (socket) => {
socket.on(prefix + "join", async () => { 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) => { module.exports.autoStart = async (token) => {
if (!token) { if (!token) {
token = await setting("cloudflaredTunnelToken"); token = await setting("cloudflaredTunnelToken");
@ -85,6 +102,7 @@ module.exports.autoStart = async (token) => {
} }
}; };
/** Stop cloudflared */
module.exports.stop = async () => { module.exports.stop = async () => {
console.log("Stop cloudflared"); console.log("Stop cloudflared");
if (cloudflared) { if (cloudflared) {

View file

@ -1,6 +1,10 @@
const { checkLogin } = require("../util-server"); const { checkLogin } = require("../util-server");
const Database = require("../database"); const Database = require("../database");
/**
* Handlers for database
* @param {Socket} socket Socket.io instance
*/
module.exports = (socket) => { module.exports = (socket) => {
// Post or edit incident // Post or edit incident

View file

@ -4,6 +4,10 @@ const { sendProxyList } = require("../client");
const { UptimeKumaServer } = require("../uptime-kuma-server"); const { UptimeKumaServer } = require("../uptime-kuma-server");
const server = UptimeKumaServer.getInstance(); const server = UptimeKumaServer.getInstance();
/**
* Handlers for proxy
* @param {Socket} socket Socket.io instance
*/
module.exports.proxySocketHandler = (socket) => { module.exports.proxySocketHandler = (socket) => {
socket.on("addProxy", async (proxy, proxyID, callback) => { socket.on("addProxy", async (proxy, proxyID, callback) => {
try { try {

View file

@ -8,6 +8,10 @@ const apicache = require("../modules/apicache");
const StatusPage = require("../model/status_page"); const StatusPage = require("../model/status_page");
const { UptimeKumaServer } = require("../uptime-kuma-server"); 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) => { module.exports.statusPageSocketHandler = (socket) => {
// Post or edit incident // Post or edit incident
@ -338,6 +342,7 @@ module.exports.statusPageSocketHandler = (socket) => {
/** /**
* Check slug a-z, 0-9, - only * Check slug a-z, 0-9, - only
* Regex from: https://stackoverflow.com/questions/22454258/js-regex-string-validation-for-slug * Regex from: https://stackoverflow.com/questions/22454258/js-regex-string-validation-for-slug
* @param {string} slug Slug to test
*/ */
function checkSlug(slug) { function checkSlug(slug) {
if (typeof slug !== "string") { if (typeof slug !== "string") {

View file

@ -37,6 +37,12 @@ exports.initJWTSecret = async () => {
return jwtSecretBean; 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) { exports.tcping = function (hostname, port) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
tcpp.ping({ 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) => { exports.ping = async (hostname) => {
try { try {
return await exports.pingAsync(hostname); 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) { exports.pingAsync = function (hostname, ipv6 = false) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const ping = new Ping(hostname, { 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 = {}) { exports.mqttAsync = function (hostname, topic, okMessage, options = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const { port, username, password, interval = 20 } = options; 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) { exports.dnsResolve = function (hostname, resolverServer, rrtype) {
const resolver = new Resolver(); const resolver = new Resolver();
resolver.setServers([ resolverServer ]); 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) { exports.setting = async function (key) {
let value = await R.getCell("SELECT `value` FROM setting WHERE `key` = ? ", [ let value = await R.getCell("SELECT `value` FROM setting WHERE `key` = ? ", [
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) { exports.setSetting = async function (key, value, type = null) {
let bean = await R.findOne("setting", " `key` = ? ", [ let bean = await R.findOne("setting", " `key` = ? ", [
key, key,
@ -197,6 +242,11 @@ exports.setSetting = async function (key, value, type = null) {
await R.store(bean); await R.store(bean);
}; };
/**
* Get settings based on type
* @param {?string} type The type of setting
* @returns {Promise<Bean>}
*/
exports.getSettings = async function (type) { exports.getSettings = async function (type) {
let list = await R.getAll("SELECT `key`, `value` FROM setting WHERE `type` = ? ", [ let list = await R.getAll("SELECT `key`, `value` FROM setting WHERE `type` = ? ", [
type, type,
@ -215,6 +265,12 @@ exports.getSettings = async function (type) {
return result; 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) { exports.setSettings = async function (type, data) {
let keyList = Object.keys(data); let keyList = Object.keys(data);
@ -241,12 +297,23 @@ exports.setSettings = async function (type, data) {
}; };
// ssl-checker by @dyaa // ssl-checker by @dyaa
// param: res - response object from axios //https://github.com/dyaa/ssl-checker/blob/master/src/index.ts
// return an object containing the certificate information
/**
* Get number of days between two dates
* @param {Date} validFrom Start date
* @param {Date} validTo End date
* @returns {number}
*/
const getDaysBetween = (validFrom, validTo) => const getDaysBetween = (validFrom, validTo) =>
Math.round(Math.abs(+validFrom - +validTo) / 8.64e7); 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 getDaysRemaining = (validFrom, validTo) => {
const daysRemaining = getDaysBetween(validFrom, validTo); const daysRemaining = getDaysBetween(validFrom, validTo);
if (new Date(validTo).getTime() < new Date().getTime()) { if (new Date(validTo).getTime() < new Date().getTime()) {
@ -255,8 +322,11 @@ const getDaysRemaining = (validFrom, validTo) => {
return daysRemaining; 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) { const parseCertificateInfo = function (info) {
let link = info; let link = info;
let i = 0; let i = 0;
@ -296,6 +366,11 @@ const parseCertificateInfo = function (info) {
return 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) { exports.checkCertificate = function (res) {
const info = res.request.res.socket.getPeerCertificate(true); const info = res.request.res.socket.getPeerCertificate(true);
const valid = res.request.res.socket.authorized || false; 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 * Check if the provided status code is within the accepted ranges
// Param: accepted_codes - an array of accepted status codes * @param {string} status The status code to check
// Return: true if the status code is within the accepted ranges, false otherwise * @param {string[]} acceptedCodes An array of accepted status codes
// Will throw an error if the provided status code is not a valid range string or code string * @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) { exports.checkStatusCode = function (status, acceptedCodes) {
if (acceptedCodes == null || acceptedCodes.length === 0) { if (acceptedCodes == null || acceptedCodes.length === 0) {
return false; return false;
@ -338,6 +414,12 @@ exports.checkStatusCode = function (status, acceptedCodes) {
return false; 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) => { exports.getTotalClientInRoom = (io, roomName) => {
const sockets = io.sockets; 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) => { exports.allowDevAllOrigin = (res) => {
if (process.env.NODE_ENV === "development") { if (process.env.NODE_ENV === "development") {
exports.allowAllOrigin(res); exports.allowAllOrigin(res);
} }
}; };
/**
* Allow CORS all origins
* @param {Object} res Response object from axios
*/
exports.allowAllOrigin = (res) => { exports.allowAllOrigin = (res) => {
res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); 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) => { exports.checkLogin = (socket) => {
if (!socket.userID) { if (!socket.userID) {
throw new Error("You are not logged in."); throw new Error("You are not logged in.");
@ -380,8 +474,8 @@ exports.checkLogin = (socket) => {
/** /**
* For logged-in users, double-check the password * For logged-in users, double-check the password
* @param socket * @param {Socket} socket Socket.io instance
* @param currentPassword * @param {string} currentPassword
* @returns {Promise<Bean>} * @returns {Promise<Bean>}
*/ */
exports.doubleCheckPassword = async (socket, currentPassword) => { exports.doubleCheckPassword = async (socket, currentPassword) => {
@ -400,6 +494,7 @@ exports.doubleCheckPassword = async (socket, currentPassword) => {
return user; return user;
}; };
/** Start Unit tests */
exports.startUnitTest = async () => { exports.startUnitTest = async () => {
console.log("Starting unit test..."); console.log("Starting unit test...");
const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm"; 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} * @returns {string}
*/ */
exports.convertToUTF8 = (body) => { exports.convertToUTF8 = (body) => {
@ -437,6 +533,11 @@ try {
}); });
} catch (_) { } } 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) => { exports.errorLog = (error, outputToConsole = true) => {
try { try {
if (logFile) { if (logFile) {