diff --git a/.eslintrc.js b/.eslintrc.js index b0934d6d8..21e359104 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,4 +1,9 @@ module.exports = { + ignorePatterns: [ + "test/*", + "server/modules/apicache/*", + "src/util.js" + ], root: true, env: { browser: true, @@ -34,7 +39,7 @@ module.exports = { }, ], quotes: ["warn", "double"], - semi: "warn", + semi: "error", "vue/html-indent": ["warn", 4], // default: 2 "vue/max-attributes-per-line": "off", "vue/singleline-html-element-content-newline": "off", diff --git a/.github/workflows/auto-test.yml b/.github/workflows/auto-test.yml index e01c02cee..269e30854 100644 --- a/.github/workflows/auto-test.yml +++ b/.github/workflows/auto-test.yml @@ -20,6 +20,7 @@ jobs: # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: + - run: git config --global core.autocrlf false # Mainly for Windows - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} diff --git a/.stylelintrc b/.stylelintrc index aad673dbc..52301b542 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -1,9 +1,13 @@ { "extends": "stylelint-config-standard", + "customSyntax": "postcss-html", "rules": { "indentation": 4, "no-descending-specificity": null, "selector-list-comma-newline-after": null, - "declaration-empty-line-before": null + "declaration-empty-line-before": null, + "alpha-value-notation": "number", + "color-function-notation": "legacy", + "shorthand-property-no-redundant-values": null } } diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index e58ee6794..a6499ef9f 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -11,3 +11,4 @@ services: - ./uptime-kuma:/app/data ports: - 3001:3001 + restart: always diff --git a/ecosystem.config.js b/ecosystem.config.js index 5f4034007..bff3451f7 100644 --- a/ecosystem.config.js +++ b/ecosystem.config.js @@ -1,6 +1,6 @@ module.exports = { - apps: [{ - name: "uptime-kuma", - script: "./server/server.js", - }] -} + apps: [{ + name: "uptime-kuma", + script: "./server/server.js", + }] +}; diff --git a/extra/beta/update-version.js b/extra/beta/update-version.js index e452f32de..4fddcfeb3 100644 --- a/extra/beta/update-version.js +++ b/extra/beta/update-version.js @@ -1,6 +1,6 @@ const pkg = require("../../package.json"); const fs = require("fs"); -const child_process = require("child_process"); +const childProcess = require("child_process"); const util = require("../../src/util"); util.polyfill(); @@ -32,7 +32,7 @@ if (! exists) { function commit(version) { let msg = "Update to " + version; - let res = child_process.spawnSync("git", ["commit", "-m", msg, "-a"]); + let res = childProcess.spawnSync("git", ["commit", "-m", msg, "-a"]); let stdout = res.stdout.toString().trim(); console.log(stdout); @@ -40,15 +40,15 @@ function commit(version) { throw new Error("commit error"); } - res = child_process.spawnSync("git", ["push", "origin", "master"]); + res = childProcess.spawnSync("git", ["push", "origin", "master"]); console.log(res.stdout.toString().trim()); } function tag(version) { - let res = child_process.spawnSync("git", ["tag", version]); + let res = childProcess.spawnSync("git", ["tag", version]); console.log(res.stdout.toString().trim()); - res = child_process.spawnSync("git", ["push", "origin", version]); + res = childProcess.spawnSync("git", ["push", "origin", version]); console.log(res.stdout.toString().trim()); } @@ -57,7 +57,7 @@ function tagExists(version) { throw new Error("invalid version"); } - let res = child_process.spawnSync("git", ["tag", "-l", version]); + let res = childProcess.spawnSync("git", ["tag", "-l", version]); return res.stdout.toString().trim() === version; } diff --git a/extra/download-dist.js b/extra/download-dist.js index c184c2846..b04beec7a 100644 --- a/extra/download-dist.js +++ b/extra/download-dist.js @@ -12,6 +12,12 @@ const filename = "dist.tar.gz"; const url = `https://github.com/louislam/uptime-kuma/releases/download/${version}/${filename}`; download(url); +/** + * Downloads the latest version of the dist from a GitHub release. + * @param {string} url The URL to download from. + * + * Generated by Trelent + */ function download(url) { console.log(url); diff --git a/extra/mark-as-nightly.js b/extra/mark-as-nightly.js index 0316596bf..a55fd98f7 100644 --- a/extra/mark-as-nightly.js +++ b/extra/mark-as-nightly.js @@ -4,21 +4,21 @@ const util = require("../src/util"); util.polyfill(); -const oldVersion = pkg.version -const newVersion = oldVersion + "-nightly" +const oldVersion = pkg.version; +const newVersion = oldVersion + "-nightly"; -console.log("Old Version: " + oldVersion) -console.log("New Version: " + newVersion) +console.log("Old Version: " + oldVersion); +console.log("New Version: " + newVersion); if (newVersion) { // Process package.json - pkg.version = newVersion - pkg.scripts.setup = pkg.scripts.setup.replaceAll(oldVersion, newVersion) - pkg.scripts["build-docker"] = pkg.scripts["build-docker"].replaceAll(oldVersion, newVersion) - fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n") + pkg.version = newVersion; + pkg.scripts.setup = pkg.scripts.setup.replaceAll(oldVersion, newVersion); + pkg.scripts["build-docker"] = pkg.scripts["build-docker"].replaceAll(oldVersion, newVersion); + fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n"); // Process README.md if (fs.existsSync("README.md")) { - fs.writeFileSync("README.md", fs.readFileSync("README.md", "utf8").replaceAll(oldVersion, newVersion)) + fs.writeFileSync("README.md", fs.readFileSync("README.md", "utf8").replaceAll(oldVersion, newVersion)); } } diff --git a/extra/simple-dns-server.js b/extra/simple-dns-server.js index 5e5745f04..376dbdd04 100644 --- a/extra/simple-dns-server.js +++ b/extra/simple-dns-server.js @@ -26,7 +26,7 @@ server.on("request", (request, send, rinfo) => { ttl: 300, address: "1.2.3.4" }); - } if (question.type === Packet.TYPE.AAAA) { + } else if (question.type === Packet.TYPE.AAAA) { response.answers.push({ name: question.name, type: question.type, diff --git a/extra/update-version.js b/extra/update-version.js index 505bb2cb0..d72fee68f 100644 --- a/extra/update-version.js +++ b/extra/update-version.js @@ -33,6 +33,12 @@ if (! exists) { console.log("version exists"); } +/** + * Updates the version number in package.json and commits it to git. + * @param {string} version - The new version number + * + * Generated by Trelent + */ function commit(version) { let msg = "Update to " + version; @@ -50,6 +56,12 @@ function tag(version) { console.log(res.stdout.toString().trim()); } +/** + * Checks if a given version is already tagged in the git repository. + * @param {string} version - The version to check for. + * + * Generated by Trelent + */ function tagExists(version) { if (! version) { throw new Error("invalid version"); diff --git a/package-lock.json b/package-lock.json index 91ca4e527..5d183fc6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "uptime-kuma", - "version": "1.14.0-beta.0", + "version": "1.14.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "uptime-kuma", - "version": "1.14.0-beta.0", + "version": "1.14.0", "license": "MIT", "dependencies": { "@fortawesome/fontawesome-svg-core": "~1.2.36", @@ -85,6 +85,7 @@ "jest": "~27.2.5", "jest-puppeteer": "~6.0.3", "npm-check-updates": "^12.5.5", + "postcss-html": "^1.3.1", "puppeteer": "~13.1.3", "sass": "~1.42.1", "stylelint": "~14.2.0", @@ -5612,6 +5613,41 @@ "node": ">=6.0.0" } }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, "node_modules/domexception": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", @@ -5633,6 +5669,35 @@ "node": ">=8" } }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dot-prop": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", @@ -5817,6 +5882,18 @@ "node": ">=8.6" } }, + "node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -7557,6 +7634,25 @@ "node": ">=8" } }, + "node_modules/htmlparser2": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", + "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.2", + "domutils": "^2.8.0", + "entities": "^3.0.1" + } + }, "node_modules/http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", @@ -12505,6 +12601,20 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-html": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-1.3.1.tgz", + "integrity": "sha512-SJ7iRw+IngyZv3Z9lChlZU30a9y9MZjZZcoUJmx0T/nKE9S+hetJ8fAv/MRu4bPnGDsXhVlaFs5+umpK3yaaQQ==", + "dev": true, + "dependencies": { + "htmlparser2": "^7.1.2", + "postcss": "^8.4.0", + "postcss-safe-parser": "^6.0.0" + }, + "engines": { + "node": "^12 || >=14" + } + }, "node_modules/postcss-media-query-parser": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", @@ -20003,6 +20113,31 @@ "esutils": "^2.0.2" } }, + "dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "dependencies": { + "entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true + } + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true + }, "domexception": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", @@ -20020,6 +20155,26 @@ } } }, + "domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "requires": { + "domelementtype": "^2.2.0" + } + }, + "domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "requires": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + } + }, "dot-prop": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", @@ -20157,6 +20312,12 @@ "ansi-colors": "^4.1.1" } }, + "entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true + }, "env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -21437,6 +21598,18 @@ "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==", "dev": true }, + "htmlparser2": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", + "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.2", + "domutils": "^2.8.0", + "entities": "^3.0.1" + } + }, "http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", @@ -25173,6 +25346,17 @@ "source-map-js": "^1.0.2" } }, + "postcss-html": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-1.3.1.tgz", + "integrity": "sha512-SJ7iRw+IngyZv3Z9lChlZU30a9y9MZjZZcoUJmx0T/nKE9S+hetJ8fAv/MRu4bPnGDsXhVlaFs5+umpK3yaaQQ==", + "dev": true, + "requires": { + "htmlparser2": "^7.1.2", + "postcss": "^8.4.0", + "postcss-safe-parser": "^6.0.0" + } + }, "postcss-media-query-parser": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", diff --git a/package.json b/package.json index 28db266a6..730359221 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "start-server": "node server/server.js", "start-server-dev": "cross-env NODE_ENV=development node server/server.js", "build": "vite build --config ./config/vite.config.js", - "test": "node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/ --test", + "test": "npm run lint && node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/ --test", "test-with-build": "npm run build && npm test", "jest": "node test/prepare-jest.js && npm run jest-frontend && npm run jest-backend", "jest-frontend": "cross-env TEST_FRONTEND=1 jest --config=./config/jest-frontend.config.js", @@ -132,6 +132,7 @@ "jest": "~27.2.5", "jest-puppeteer": "~6.0.3", "npm-check-updates": "^12.5.5", + "postcss-html": "^1.3.1", "puppeteer": "~13.1.3", "sass": "~1.42.1", "stylelint": "~14.2.0", diff --git a/server/auth.js b/server/auth.js index c59d65492..d9412ae37 100644 --- a/server/auth.js +++ b/server/auth.js @@ -2,7 +2,6 @@ const basicAuth = require("express-basic-auth"); const passwordHash = require("./password-hash"); const { R } = require("redbean-node"); const { setting } = require("./util-server"); -const { debug } = require("../src/util"); const { loginRateLimiter } = require("./rate-limiter"); /** @@ -34,6 +33,13 @@ exports.login = async function (username, password) { return null; }; +/** + * 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 + */ function myAuthorizer(username, password, callback) { // Login Rate Limit loginRateLimiter.pass(null, 0).then((pass) => { diff --git a/server/client.js b/server/client.js index 2c07448b1..3a2d6df27 100644 --- a/server/client.js +++ b/server/client.js @@ -7,6 +7,12 @@ const { io } = require("./server"); 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 + */ async function sendNotificationList(socket) { const timeLogger = new TimeLogger(); @@ -100,6 +106,12 @@ async function sendProxyList(socket) { return list; } +/** + * Emits the version information to the client. + * @param {Socket} socket The socket object that is connected to the client. + * + * Generated by Trelent + */ async function sendInfo(socket) { socket.emit("info", { version: checkVersion.version, diff --git a/server/database.js b/server/database.js index b852cc985..b398101e0 100644 --- a/server/database.js +++ b/server/database.js @@ -1,7 +1,7 @@ const fs = require("fs"); const { R } = require("redbean-node"); const { setSetting, setting } = require("./util-server"); -const { debug, sleep } = require("../src/util"); +const { log, sleep } = require("../src/util"); const dayjs = require("dayjs"); const knex = require("knex"); @@ -80,7 +80,7 @@ class Database { fs.mkdirSync(Database.uploadDir, { recursive: true }); } - console.log(`Data Dir: ${Database.dataDir}`); + log.info("db", `Data Dir: ${Database.dataDir}`); } static async connect(testMode = false, autoloadModels = true, noLog = false) { @@ -135,10 +135,10 @@ class Database { await R.exec("PRAGMA synchronous = FULL"); if (!noLog) { - console.log("SQLite config:"); - console.log(await R.getAll("PRAGMA journal_mode")); - console.log(await R.getAll("PRAGMA cache_size")); - console.log("SQLite Version: " + await R.getCell("SELECT sqlite_version()")); + log.info("db", "SQLite config:"); + log.info("db", await R.getAll("PRAGMA journal_mode")); + log.info("db", await R.getAll("PRAGMA cache_size")); + log.info("db", "SQLite Version: " + await R.getCell("SELECT sqlite_version()")); } } @@ -149,15 +149,15 @@ class Database { version = 0; } - console.info("Your database version: " + version); - console.info("Latest database version: " + this.latestVersion); + log.info("db", "Your database version: " + version); + log.info("db", "Latest database version: " + this.latestVersion); if (version === this.latestVersion) { - console.info("Database patch not needed"); + log.info("db", "Database patch not needed"); } else if (version > this.latestVersion) { - console.info("Warning: Database version is newer than expected"); + log.info("db", "Warning: Database version is newer than expected"); } else { - console.info("Database patch is needed"); + log.info("db", "Database patch is needed"); this.backup(version); @@ -165,17 +165,17 @@ class Database { try { for (let i = version + 1; i <= this.latestVersion; i++) { const sqlFile = `./db/patch${i}.sql`; - console.info(`Patching ${sqlFile}`); + log.info("db", `Patching ${sqlFile}`); await Database.importSQLFile(sqlFile); - console.info(`Patched ${sqlFile}`); + log.info("db", `Patched ${sqlFile}`); await setSetting("database_version", i); } } catch (ex) { await Database.close(); - console.error(ex); - console.error("Start Uptime-Kuma failed due to issue patching the database"); - console.error("Please submit a bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues"); + log.error("db", ex); + log.error("db", "Start Uptime-Kuma failed due to issue patching the database"); + log.error("db", "Please submit a bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues"); this.restore(); process.exit(1); @@ -191,15 +191,15 @@ class Database { * @returns {Promise} */ static async patch2() { - console.log("Database Patch 2.0 Process"); + log.info("db", "Database Patch 2.0 Process"); let databasePatchedFiles = await setting("databasePatchedFiles"); if (! databasePatchedFiles) { databasePatchedFiles = {}; } - debug("Patched files:"); - debug(databasePatchedFiles); + log.debug("db", "Patched files:"); + log.debug("db", databasePatchedFiles); try { for (let sqlFilename in this.patchList) { @@ -207,15 +207,15 @@ class Database { } if (this.patched) { - console.log("Database Patched Successfully"); + log.info("db", "Database Patched Successfully"); } } catch (ex) { await Database.close(); - console.error(ex); - console.error("Start Uptime-Kuma failed due to issue patching the database"); - console.error("Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues"); + log.error("db", ex); + log.error("db", "Start Uptime-Kuma failed due to issue patching the database"); + log.error("db", "Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues"); this.restore(); @@ -302,16 +302,16 @@ class Database { let value = this.patchList[sqlFilename]; if (! value) { - console.log(sqlFilename + " skip"); + log.info("db", sqlFilename + " skip"); return; } // Check if patched if (! databasePatchedFiles[sqlFilename]) { - console.log(sqlFilename + " is not patched"); + log.info("db", sqlFilename + " is not patched"); if (value.parents) { - console.log(sqlFilename + " need parents"); + log.info("db", sqlFilename + " need parents"); for (let parentSQLFilename of value.parents) { await this.patch2Recursion(parentSQLFilename, databasePatchedFiles); } @@ -319,14 +319,14 @@ class Database { this.backup(dayjs().format("YYYYMMDDHHmmss")); - console.log(sqlFilename + " is patching"); + log.info("db", sqlFilename + " is patching"); this.patched = true; await this.importSQLFile("./db/" + sqlFilename); databasePatchedFiles[sqlFilename] = true; - console.log(sqlFilename + " was patched successfully"); + log.info("db", sqlFilename + " was patched successfully"); } else { - debug(sqlFilename + " is already patched, skip"); + log.debug("db", sqlFilename + " is already patched, skip"); } } @@ -378,7 +378,7 @@ class Database { }; process.addListener("unhandledRejection", listener); - console.log("Closing the database"); + log.info("db", "Closing the database"); while (true) { Database.noReject = true; @@ -388,10 +388,10 @@ class Database { if (Database.noReject) { break; } else { - console.log("Waiting to close the database"); + log.info("db", "Waiting to close the database"); } } - console.log("SQLite closed"); + log.info("db", "SQLite closed"); process.removeListener("unhandledRejection", listener); } @@ -403,7 +403,7 @@ class Database { */ static backup(version) { if (! this.backupPath) { - console.info("Backing up the database"); + log.info("db", "Backing up the database"); this.backupPath = this.dataDir + "kuma.db.bak" + version; fs.copyFileSync(Database.path, this.backupPath); @@ -426,7 +426,7 @@ class Database { */ static restore() { if (this.backupPath) { - console.error("Patching the database failed!!! Restoring the backup"); + log.error("db", "Patching the database failed!!! Restoring the backup"); const shmPath = Database.path + "-shm"; const walPath = Database.path + "-wal"; @@ -445,7 +445,7 @@ class Database { fs.unlinkSync(walPath); } } catch (e) { - console.log("Restore failed; you may need to restore the backup manually"); + log.error("db", "Restore failed; you may need to restore the backup manually"); process.exit(1); } @@ -461,14 +461,14 @@ class Database { } } else { - console.log("Nothing to restore"); + log.info("db", "Nothing to restore"); } } static getSize() { - debug("Database.getSize()"); + log.debug("db", "Database.getSize()"); let stats = fs.statSync(Database.path); - debug(stats); + log.debug("db", stats); return stats.size; } diff --git a/server/image-data-uri.js b/server/image-data-uri.js index 3ccaab7d5..fbff93408 100644 --- a/server/image-data-uri.js +++ b/server/image-data-uri.js @@ -3,12 +3,19 @@ Modified with 0 dependencies */ let fs = require("fs"); +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 + */ function decode(dataURI) { if (!/data:image\//.test(dataURI)) { - console.log("ImageDataURI :: Error :: It seems that it is not an Image Data URI. Couldn't match \"data:image/\""); + log.error("image-data-uri", "It seems that it is not an Image Data URI. Couldn't match \"data:image/\""); return null; } @@ -20,9 +27,16 @@ 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 + */ function encode(data, mediaType) { if (!data || !mediaType) { - console.log("ImageDataURI :: Error :: Missing some of the required params: data, mediaType "); + log.error("image-data-uri", "Missing some of the required params: data, mediaType"); return null; } @@ -33,6 +47,13 @@ let ImageDataURI = (() => { return dataImgBase64; } + /** + * 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 + */ function outputFile(dataURI, filePath) { filePath = filePath || "./"; return new Promise((resolve, reject) => { diff --git a/server/jobs.js b/server/jobs.js index d33adb98c..739e867d4 100644 --- a/server/jobs.js +++ b/server/jobs.js @@ -1,6 +1,7 @@ const path = require("path"); const Bree = require("bree"); const { SHARE_ENV } = require("worker_threads"); +const { log } = require("../src/util"); let bree; const jobs = [ { @@ -18,7 +19,7 @@ const initBackgroundJobs = function (args) { workerData: args, }, workerMessageHandler: (message) => { - console.log("[Background Job]:", message); + log.info("jobs", message); } }); diff --git a/server/logger.js b/server/logger.js new file mode 100644 index 000000000..e69de29bb diff --git a/server/model/monitor.js b/server/model/monitor.js index aef213c8e..d61046df8 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -6,7 +6,7 @@ dayjs.extend(utc); dayjs.extend(timezone); const axios = require("axios"); const { Prometheus } = require("../prometheus"); -const { debug, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util"); +const { log, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util"); const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, errorLog } = require("../util-server"); const { R } = require("redbean-node"); const { BeanModel } = require("redbean-node/dist/bean-model"); @@ -193,7 +193,7 @@ class Monitor extends BeanModel { rejectUnauthorized: !this.getIgnoreTls(), }; - debug(`[${this.name}] Prepare Options for axios`); + log.debug("monitor", `[${this.name}] Prepare Options for axios`); const options = { url: this.url, @@ -230,8 +230,8 @@ class Monitor extends BeanModel { options.httpsAgent = new https.Agent(httpsAgentOptions); } - debug(`[${this.name}] Axios Options: ${JSON.stringify(options)}`); - debug(`[${this.name}] Axios Request`); + log.debug("monitor", `[${this.name}] Axios Options: ${JSON.stringify(options)}`); + log.debug("monitor", `[${this.name}] Axios Request`); let res = await axios.request(options); bean.msg = `${res.status} - ${res.statusText}`; @@ -240,29 +240,30 @@ class Monitor extends BeanModel { // Check certificate if https is used let certInfoStartTime = dayjs().valueOf(); if (this.getUrl()?.protocol === "https:") { - debug(`[${this.name}] Check cert`); + log.debug("monitor", `[${this.name}] Check cert`); try { let tlsInfoObject = checkCertificate(res); tlsInfo = await this.updateTlsInfo(tlsInfoObject); if (!this.getIgnoreTls() && this.isEnabledExpiryNotification()) { - debug(`[${this.name}] call sendCertNotification`); + log.debug("monitor", `[${this.name}] call sendCertNotification`); await this.sendCertNotification(tlsInfoObject); } } catch (e) { if (e.message !== "No TLS certificate in response") { - console.error(e.message); + log.error("monitor", "Caught error"); + log.error("monitor", e.message); } } } if (process.env.TIMELOGGER === "1") { - debug("Cert Info Query Time: " + (dayjs().valueOf() - certInfoStartTime) + "ms"); + log.debug("monitor", "Cert Info Query Time: " + (dayjs().valueOf() - certInfoStartTime) + "ms"); } if (process.env.UPTIME_KUMA_LOG_RESPONSE_BODY_MONITOR_ID == this.id) { - console.log(res.data); + log.info("monitor", res.data); } if (this.type === "http") { @@ -342,7 +343,7 @@ class Monitor extends BeanModel { time ]); - debug("heartbeatCount" + heartbeatCount + " " + time); + log.debug("monitor", "heartbeatCount" + heartbeatCount + " " + time); if (heartbeatCount <= 0) { // Fix #922, since previous heartbeat could be inserted by api, it should get from database @@ -426,7 +427,7 @@ class Monitor extends BeanModel { } } - debug(`[${this.name}] Check isImportant`); + log.debug("monitor", `[${this.name}] Check isImportant`); let isImportant = Monitor.isImportantBeat(isFirstBeat, previousBeat?.status, bean.status); // Mark as important if status changed, ignore pending pings, @@ -434,11 +435,11 @@ class Monitor extends BeanModel { if (isImportant) { bean.important = true; - debug(`[${this.name}] sendNotification`); + log.debug("monitor", `[${this.name}] sendNotification`); await Monitor.sendNotification(isFirstBeat, this, bean); // Clear Status Page Cache - debug(`[${this.name}] apicache clear`); + log.debug("monitor", `[${this.name}] apicache clear`); apicache.clear(); } else { @@ -446,33 +447,33 @@ class Monitor extends BeanModel { } if (bean.status === UP) { - console.info(`Monitor #${this.id} '${this.name}': Successful Response: ${bean.ping} ms | Interval: ${beatInterval} seconds | Type: ${this.type}`); + log.info("monitor", `Monitor #${this.id} '${this.name}': Successful Response: ${bean.ping} ms | Interval: ${beatInterval} seconds | Type: ${this.type}`); } else if (bean.status === PENDING) { if (this.retryInterval > 0) { beatInterval = this.retryInterval; } - console.warn(`Monitor #${this.id} '${this.name}': Pending: ${bean.msg} | Max retries: ${this.maxretries} | Retry: ${retries} | Retry Interval: ${beatInterval} seconds | Type: ${this.type}`); + log.warn("monitor", `Monitor #${this.id} '${this.name}': Pending: ${bean.msg} | Max retries: ${this.maxretries} | Retry: ${retries} | Retry Interval: ${beatInterval} seconds | Type: ${this.type}`); } else { - console.warn(`Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Interval: ${beatInterval} seconds | Type: ${this.type}`); + log.warn("monitor", `Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Interval: ${beatInterval} seconds | Type: ${this.type}`); } - debug(`[${this.name}] Send to socket`); + log.debug("monitor", `[${this.name}] Send to socket`); io.to(this.user_id).emit("heartbeat", bean.toJSON()); Monitor.sendStats(io, this.id, this.user_id); - debug(`[${this.name}] Store`); + log.debug("monitor", `[${this.name}] Store`); await R.store(bean); - debug(`[${this.name}] prometheus.update`); + log.debug("monitor", `[${this.name}] prometheus.update`); prometheus.update(bean, tlsInfo); previousBeat = bean; if (! this.isStop) { - debug(`[${this.name}] SetTimeout for next check.`); + log.debug("monitor", `[${this.name}] SetTimeout for next check.`); this.heartbeatInterval = setTimeout(safeBeat, beatInterval * 1000); } else { - console.log(`[${this.name}] isStop = true, no next check.`); + log.info("monitor", `[${this.name}] isStop = true, no next check.`); } }; @@ -483,10 +484,10 @@ class Monitor extends BeanModel { } catch (e) { console.trace(e); errorLog(e, false); - console.error("Please report to https://github.com/louislam/uptime-kuma/issues"); + log.error("monitor", "Please report to https://github.com/louislam/uptime-kuma/issues"); if (! this.isStop) { - console.log("Try to restart the monitor"); + log.info("monitor", "Try to restart the monitor"); this.heartbeatInterval = setTimeout(safeBeat, this.interval * 1000); } } @@ -533,41 +534,41 @@ class Monitor extends BeanModel { * @returns {Promise} */ async updateTlsInfo(checkCertificateResult) { - let tls_info_bean = await R.findOne("monitor_tls_info", "monitor_id = ?", [ + let tlsInfoBean = await R.findOne("monitor_tls_info", "monitor_id = ?", [ this.id, ]); - if (tls_info_bean == null) { - tls_info_bean = R.dispense("monitor_tls_info"); - tls_info_bean.monitor_id = this.id; + if (tlsInfoBean == null) { + tlsInfoBean = R.dispense("monitor_tls_info"); + tlsInfoBean.monitor_id = this.id; } else { // Clear sent history if the cert changed. try { - let oldCertInfo = JSON.parse(tls_info_bean.info_json); + let oldCertInfo = JSON.parse(tlsInfoBean.info_json); let isValidObjects = oldCertInfo && oldCertInfo.certInfo && checkCertificateResult && checkCertificateResult.certInfo; if (isValidObjects) { if (oldCertInfo.certInfo.fingerprint256 !== checkCertificateResult.certInfo.fingerprint256) { - debug("Resetting sent_history"); + log.debug("monitor", "Resetting sent_history"); await R.exec("DELETE FROM notification_sent_history WHERE type = 'certificate' AND monitor_id = ?", [ this.id ]); } else { - debug("No need to reset sent_history"); - debug(oldCertInfo.certInfo.fingerprint256); - debug(checkCertificateResult.certInfo.fingerprint256); + log.debug("monitor", "No need to reset sent_history"); + log.debug("monitor", oldCertInfo.certInfo.fingerprint256); + log.debug("monitor", checkCertificateResult.certInfo.fingerprint256); } } else { - debug("Not valid object"); + log.debug("monitor", "Not valid object"); } } catch (e) { } } - tls_info_bean.info_json = JSON.stringify(checkCertificateResult); - await R.store(tls_info_bean); + tlsInfoBean.info_json = JSON.stringify(checkCertificateResult); + await R.store(tlsInfoBean); return checkCertificateResult; } @@ -581,7 +582,7 @@ class Monitor extends BeanModel { await Monitor.sendUptime(24 * 30, io, monitorID, userID); await Monitor.sendCertInfo(io, monitorID, userID); } else { - debug("No clients in the room, no need to send stats"); + log.debug("monitor", "No clients in the room, no need to send stats"); } } @@ -728,8 +729,8 @@ class Monitor extends BeanModel { try { await Notification.send(JSON.parse(notification.config), msg, await monitor.toJSON(), bean.toJSON()); } catch (e) { - console.error("Cannot send notification to " + notification.name); - console.log(e); + log.error("monitor", "Cannot send notification to " + notification.name); + log.error("monitor", e); } } } @@ -746,7 +747,7 @@ class Monitor extends BeanModel { if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) { const notificationList = await Monitor.getNotificationList(this); - debug("call sendCertNotificationByTargetDays"); + log.debug("monitor", "call sendCertNotificationByTargetDays"); await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 21, notificationList); await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 14, notificationList); await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 7, notificationList); @@ -756,7 +757,7 @@ class Monitor extends BeanModel { async sendCertNotificationByTargetDays(daysRemaining, targetDays, notificationList) { if (daysRemaining > targetDays) { - debug(`No need to send cert notification. ${daysRemaining} > ${targetDays}`); + log.debug("monitor", `No need to send cert notification. ${daysRemaining} > ${targetDays}`); return; } @@ -770,21 +771,21 @@ class Monitor extends BeanModel { // Sent already, no need to send again if (row) { - debug("Sent already, no need to send again"); + log.debug("monitor", "Sent already, no need to send again"); return; } let sent = false; - debug("Send certificate notification"); + log.debug("monitor", "Send certificate notification"); for (let notification of notificationList) { try { - debug("Sending to " + notification.name); + log.debug("monitor", "Sending to " + notification.name); await Notification.send(JSON.parse(notification.config), `[${this.name}][${this.url}] Certificate will be expired in ${daysRemaining} days`); sent = true; } catch (e) { - console.error("Cannot send cert notification to " + notification.name); - console.error(e); + log.error("monitor", "Cannot send cert notification to " + notification.name); + log.error("monitor", e); } } @@ -796,7 +797,7 @@ class Monitor extends BeanModel { ]); } } else { - debug("No notification, no need to send cert notification"); + log.debug("monitor", "No notification, no need to send cert notification"); } } diff --git a/server/modules/apicache/apicache.js b/server/modules/apicache/apicache.js index 22d1fed71..25f0a54f5 100644 --- a/server/modules/apicache/apicache.js +++ b/server/modules/apicache/apicache.js @@ -68,6 +68,15 @@ function ApiCache() { instances.push(this); this.id = instances.length; + /** + * 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) + * + * Generated by Trelent + */ function debug(a, b, c, d) { let arr = ["\x1b[36m[apicache]\x1b[0m", a, b, c, d].filter(function (arg) { return arg !== undefined; @@ -77,6 +86,13 @@ function ApiCache() { return (globalOptions.debug || debugEnv) && console.log.apply(null, arr); } + /** + * 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 + */ function shouldCacheResponse(request, response, toggle) { let opt = globalOptions; let codes = opt.statusCodes; @@ -99,6 +115,12 @@ function ApiCache() { return true; } + /** + * Adds a key to the index. + * @param {string} key The key to add. + * + * Generated by Trelent + */ function addIndexEntries(key, req) { let groupName = req.apicacheGroup; @@ -111,6 +133,13 @@ function ApiCache() { index.all.unshift(key); } + /** + * Returns a new object containing only the whitelisted headers. + * @param {Object} headers The original object of header names and values. + * @param {Array.} globalOptions.headerWhitelist An array of strings representing the whitelisted header names to keep in the output object. + * + * Generated by Trelent + */ function filterBlacklistedHeaders(headers) { return Object.keys(headers) .filter(function (key) { @@ -122,6 +151,12 @@ function ApiCache() { }, {}); } + /** + * @param {Object} headers The response headers to filter. + * @returns {Object} A new object containing only the whitelisted response headers. + * + * Generated by Trelent + */ function createCacheObject(status, headers, data, encoding) { return { status: status, @@ -132,6 +167,14 @@ 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). + * + * Generated by Trelent + */ function cacheResponse(key, value, duration) { let redis = globalOptions.redisClient; let expireCallback = globalOptions.events.expire; @@ -154,6 +197,12 @@ function ApiCache() { }, Math.min(duration, 2147483647)); } + /** + * Appends content to the response. + * @param {string|Buffer} content The content to append. + * + * Generated by Trelent + */ function accumulateContent(res, content) { if (content) { if (typeof content == "string") { @@ -179,6 +228,13 @@ 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 + */ function makeResponseCacheable(req, res, next, key, duration, strDuration, toggle) { // monkeypatch res.end to create cache object res._apicache = { @@ -245,6 +301,13 @@ function ApiCache() { next(); } + /** + * @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 + */ function sendCachedResponse(request, response, cacheObject, toggle, next, duration) { if (toggle && !toggle(request, response)) { return next(); @@ -365,6 +428,13 @@ function ApiCache() { return this.getIndex(); }; + /** + * 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 + */ function parseDuration(duration, defaultDuration) { if (typeof duration === "number") { return duration; diff --git a/server/notification-providers/apprise.js b/server/notification-providers/apprise.js index fdcd8d61b..692483d86 100644 --- a/server/notification-providers/apprise.js +++ b/server/notification-providers/apprise.js @@ -6,7 +6,7 @@ class Apprise extends NotificationProvider { name = "apprise"; async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { - let s = child_process.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL]) + let s = child_process.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL]); let output = (s.stdout) ? s.stdout.toString() : "ERROR: maybe apprise not found"; @@ -16,7 +16,7 @@ class Apprise extends NotificationProvider { return "Sent Successfully"; } - throw new Error(output) + throw new Error(output); } else { return "No output from apprise"; } diff --git a/server/notification-providers/bark.js b/server/notification-providers/bark.js index 4ebe978ad..a4c525120 100644 --- a/server/notification-providers/bark.js +++ b/server/notification-providers/bark.js @@ -21,31 +21,26 @@ class Bark extends NotificationProvider { name = "Bark"; async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { - try { - var barkEndpoint = notification.barkEndpoint; + let barkEndpoint = notification.barkEndpoint; - // check if the endpoint has a "/" suffix, if so, delete it first - if (barkEndpoint.endsWith("/")) { - barkEndpoint = barkEndpoint.substring(0, barkEndpoint.length - 1); - } + // check if the endpoint has a "/" suffix, if so, delete it first + if (barkEndpoint.endsWith("/")) { + barkEndpoint = barkEndpoint.substring(0, barkEndpoint.length - 1); + } - if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] == UP) { - let title = "UptimeKuma Monitor Up"; - return await this.postNotification(title, msg, barkEndpoint); - } + if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] == UP) { + let title = "UptimeKuma Monitor Up"; + return await this.postNotification(title, msg, barkEndpoint); + } - if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] == DOWN) { - let title = "UptimeKuma Monitor Down"; - return await this.postNotification(title, msg, barkEndpoint); - } + if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] == DOWN) { + let title = "UptimeKuma Monitor Down"; + return await this.postNotification(title, msg, barkEndpoint); + } - if (msg != null) { - let title = "UptimeKuma Message"; - return await this.postNotification(title, msg, barkEndpoint); - } - - } catch (error) { - throw error; + if (msg != null) { + let title = "UptimeKuma Message"; + return await this.postNotification(title, msg, barkEndpoint); } } diff --git a/server/notification-providers/clicksendsms.js b/server/notification-providers/clicksendsms.js index 74e2f4c59..e66b982c8 100644 --- a/server/notification-providers/clicksendsms.js +++ b/server/notification-providers/clicksendsms.js @@ -12,7 +12,7 @@ class ClickSendSMS extends NotificationProvider { let config = { headers: { "Content-Type": "application/json", - "Authorization": "Basic " + Buffer.from(notification.clicksendsmsLogin + ":" + notification.clicksendsmsPassword).toString('base64'), + "Authorization": "Basic " + Buffer.from(notification.clicksendsmsLogin + ":" + notification.clicksendsmsPassword).toString("base64"), "Accept": "text/json", } }; diff --git a/server/notification-providers/discord.js b/server/notification-providers/discord.js index 881ad2113..f5c22a446 100644 --- a/server/notification-providers/discord.js +++ b/server/notification-providers/discord.js @@ -17,8 +17,8 @@ class Discord extends NotificationProvider { let discordtestdata = { username: discordDisplayName, content: msg, - } - await axios.post(notification.discordWebhookUrl, discordtestdata) + }; + await axios.post(notification.discordWebhookUrl, discordtestdata); return okMsg; } @@ -61,13 +61,13 @@ class Discord extends NotificationProvider { }, ], }], - } + }; if (notification.discordPrefixMessage) { discorddowndata.content = notification.discordPrefixMessage; } - await axios.post(notification.discordWebhookUrl, discorddowndata) + await axios.post(notification.discordWebhookUrl, discorddowndata); return okMsg; } else if (heartbeatJSON["status"] == UP) { @@ -96,17 +96,17 @@ class Discord extends NotificationProvider { }, ], }], - } + }; if (notification.discordPrefixMessage) { discordupdata.content = notification.discordPrefixMessage; } - await axios.post(notification.discordWebhookUrl, discordupdata) + await axios.post(notification.discordWebhookUrl, discordupdata); return okMsg; } } catch (error) { - this.throwGeneralAxiosError(error) + this.throwGeneralAxiosError(error); } } diff --git a/server/notification-providers/google-chat.js b/server/notification-providers/google-chat.js index 6fb324288..02cb4823c 100644 --- a/server/notification-providers/google-chat.js +++ b/server/notification-providers/google-chat.js @@ -13,11 +13,11 @@ class GoogleChat extends NotificationProvider { try { // Google Chat message formatting: https://developers.google.com/chat/api/guides/message-formats/basic - let textMsg = '' + let textMsg = ""; if (heartbeatJSON && heartbeatJSON.status === UP) { - textMsg = `✅ Application is back online\n`; + textMsg = "✅ Application is back online\n"; } else if (heartbeatJSON && heartbeatJSON.status === DOWN) { - textMsg = `🔴 Application went down\n`; + textMsg = "🔴 Application went down\n"; } if (monitorJSON && monitorJSON.name) { diff --git a/server/notification-providers/gotify.js b/server/notification-providers/gotify.js index 085261897..0c5244939 100644 --- a/server/notification-providers/gotify.js +++ b/server/notification-providers/gotify.js @@ -15,7 +15,7 @@ class Gotify extends NotificationProvider { "message": msg, "priority": notification.gotifyPriority || 8, "title": "Uptime-Kuma", - }) + }); return okMsg; diff --git a/server/notification-providers/line.js b/server/notification-providers/line.js index 327696edd..6a09b5024 100644 --- a/server/notification-providers/line.js +++ b/server/notification-providers/line.js @@ -25,8 +25,8 @@ class Line extends NotificationProvider { "text": "Test Successful!" } ] - } - await axios.post(lineAPIUrl, testMessage, config) + }; + await axios.post(lineAPIUrl, testMessage, config); } else if (heartbeatJSON["status"] == DOWN) { let downMessage = { "to": notification.lineUserID, @@ -36,8 +36,8 @@ class Line extends NotificationProvider { "text": "UptimeKuma Alert: [🔴 Down]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"] } ] - } - await axios.post(lineAPIUrl, downMessage, config) + }; + await axios.post(lineAPIUrl, downMessage, config); } else if (heartbeatJSON["status"] == UP) { let upMessage = { "to": notification.lineUserID, @@ -47,12 +47,12 @@ class Line extends NotificationProvider { "text": "UptimeKuma Alert: [✅ Up]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"] } ] - } - await axios.post(lineAPIUrl, upMessage, config) + }; + await axios.post(lineAPIUrl, upMessage, config); } return okMsg; } catch (error) { - this.throwGeneralAxiosError(error) + this.throwGeneralAxiosError(error); } } } diff --git a/server/notification-providers/lunasea.js b/server/notification-providers/lunasea.js index c41f400e2..82d2fde5b 100644 --- a/server/notification-providers/lunasea.js +++ b/server/notification-providers/lunasea.js @@ -8,15 +8,15 @@ class LunaSea extends NotificationProvider { async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { let okMsg = "Sent Successfully."; - let lunaseadevice = "https://notify.lunasea.app/v1/custom/device/" + notification.lunaseaDevice + let lunaseadevice = "https://notify.lunasea.app/v1/custom/device/" + notification.lunaseaDevice; try { if (heartbeatJSON == null) { let testdata = { "title": "Uptime Kuma Alert", "body": "Testing Successful.", - } - await axios.post(lunaseadevice, testdata) + }; + await axios.post(lunaseadevice, testdata); return okMsg; } @@ -24,8 +24,8 @@ class LunaSea extends NotificationProvider { let downdata = { "title": "UptimeKuma Alert: " + monitorJSON["name"], "body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], - } - await axios.post(lunaseadevice, downdata) + }; + await axios.post(lunaseadevice, downdata); return okMsg; } @@ -33,13 +33,13 @@ class LunaSea extends NotificationProvider { let updata = { "title": "UptimeKuma Alert: " + monitorJSON["name"], "body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], - } - await axios.post(lunaseadevice, updata) + }; + await axios.post(lunaseadevice, updata); return okMsg; } } catch (error) { - this.throwGeneralAxiosError(error) + this.throwGeneralAxiosError(error); } } diff --git a/server/notification-providers/matrix.js b/server/notification-providers/matrix.js index c1054fce6..915c772d7 100644 --- a/server/notification-providers/matrix.js +++ b/server/notification-providers/matrix.js @@ -1,7 +1,7 @@ const NotificationProvider = require("./notification-provider"); const axios = require("axios"); const Crypto = require("crypto"); -const { debug } = require("../../src/util"); +const { log } = require("../../src/util"); class Matrix extends NotificationProvider { name = "matrix"; @@ -17,11 +17,11 @@ class Matrix extends NotificationProvider { .slice(0, size) ); - debug("Random String: " + randomString); + log.debug("notification", "Random String: " + randomString); const roomId = encodeURIComponent(notification.internalRoomId); - debug("Matrix Room ID: " + roomId); + log.debug("notification", "Matrix Room ID: " + roomId); try { let config = { diff --git a/server/notification-providers/notification-provider.js b/server/notification-providers/notification-provider.js index 61c6242d7..87687baaf 100644 --- a/server/notification-providers/notification-provider.js +++ b/server/notification-providers/notification-provider.js @@ -25,11 +25,11 @@ class NotificationProvider { if (typeof error.response.data === "string") { msg += error.response.data; } else { - msg += JSON.stringify(error.response.data) + msg += JSON.stringify(error.response.data); } } - throw new Error(msg) + throw new Error(msg); } } diff --git a/server/notification-providers/octopush.js b/server/notification-providers/octopush.js index 9d77aa5b4..68416b9a9 100644 --- a/server/notification-providers/octopush.js +++ b/server/notification-providers/octopush.js @@ -30,7 +30,7 @@ class Octopush extends NotificationProvider { "purpose": "alert", "sender": notification.octopushSenderName }; - await axios.post("https://api.octopush.com/v1/public/sms-campaign/send", data, config) + await axios.post("https://api.octopush.com/v1/public/sms-campaign/send", data, config); } else if (notification.octopushVersion == 1) { let data = { "user_login": notification.octopushDMLogin, @@ -49,7 +49,7 @@ class Octopush extends NotificationProvider { }, params: data }; - await axios.post("https://www.octopush-dm.com/api/sms/json", {}, config) + await axios.post("https://www.octopush-dm.com/api/sms/json", {}, config); } else { throw new Error("Unknown Octopush version!"); } diff --git a/server/notification-providers/promosms.js b/server/notification-providers/promosms.js index 362ef714a..4f7e8f901 100644 --- a/server/notification-providers/promosms.js +++ b/server/notification-providers/promosms.js @@ -12,7 +12,7 @@ class PromoSMS extends NotificationProvider { let config = { headers: { "Content-Type": "application/json", - "Authorization": "Basic " + Buffer.from(notification.promosmsLogin + ":" + notification.promosmsPassword).toString('base64'), + "Authorization": "Basic " + Buffer.from(notification.promosmsLogin + ":" + notification.promosmsPassword).toString("base64"), "Accept": "text/json", } }; @@ -30,7 +30,7 @@ class PromoSMS extends NotificationProvider { let error = "Something gone wrong. Api returned " + resp.data.response.status + "."; this.throwGeneralAxiosError(error); } - + return okMsg; } catch (error) { this.throwGeneralAxiosError(error); diff --git a/server/notification-providers/pushbullet.js b/server/notification-providers/pushbullet.js index c7b824a2c..07b4ed682 100644 --- a/server/notification-providers/pushbullet.js +++ b/server/notification-providers/pushbullet.js @@ -23,26 +23,26 @@ class Pushbullet extends NotificationProvider { "type": "note", "title": "Uptime Kuma Alert", "body": "Testing Successful.", - } - await axios.post(pushbulletUrl, testdata, config) + }; + await axios.post(pushbulletUrl, testdata, config); } else if (heartbeatJSON["status"] == DOWN) { let downdata = { "type": "note", "title": "UptimeKuma Alert: " + monitorJSON["name"], "body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], - } - await axios.post(pushbulletUrl, downdata, config) + }; + await axios.post(pushbulletUrl, downdata, config); } else if (heartbeatJSON["status"] == UP) { let updata = { "type": "note", "title": "UptimeKuma Alert: " + monitorJSON["name"], "body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], - } - await axios.post(pushbulletUrl, updata, config) + }; + await axios.post(pushbulletUrl, updata, config); } return okMsg; } catch (error) { - this.throwGeneralAxiosError(error) + this.throwGeneralAxiosError(error); } } } diff --git a/server/notification-providers/pushy.js b/server/notification-providers/pushy.js index 2bb899349..1d6e7f32f 100644 --- a/server/notification-providers/pushy.js +++ b/server/notification-providers/pushy.js @@ -19,10 +19,10 @@ class Pushy extends NotificationProvider { "badge": 1, "sound": "ping.aiff" } - }) + }); return okMsg; } catch (error) { - this.throwGeneralAxiosError(error) + this.throwGeneralAxiosError(error); } } } diff --git a/server/notification-providers/rocket-chat.js b/server/notification-providers/rocket-chat.js index 25b0b945f..fb48ce1a6 100644 --- a/server/notification-providers/rocket-chat.js +++ b/server/notification-providers/rocket-chat.js @@ -2,7 +2,7 @@ const NotificationProvider = require("./notification-provider"); const axios = require("axios"); const Slack = require("./slack"); const { setting } = require("../util-server"); -const { getMonitorRelativeURL, UP, DOWN } = require("../../src/util"); +const { getMonitorRelativeURL, DOWN } = require("../../src/util"); class RocketChat extends NotificationProvider { diff --git a/server/notification-providers/signal.js b/server/notification-providers/signal.js index fee65754e..677208ee6 100644 --- a/server/notification-providers/signal.js +++ b/server/notification-providers/signal.js @@ -16,10 +16,10 @@ class Signal extends NotificationProvider { }; let config = {}; - await axios.post(notification.signalURL, data, config) + await axios.post(notification.signalURL, data, config); return okMsg; } catch (error) { - this.throwGeneralAxiosError(error) + this.throwGeneralAxiosError(error); } } } diff --git a/server/notification-providers/techulus-push.js b/server/notification-providers/techulus-push.js index f844d17ce..751ff4c3a 100644 --- a/server/notification-providers/techulus-push.js +++ b/server/notification-providers/techulus-push.js @@ -12,10 +12,10 @@ class TechulusPush extends NotificationProvider { await axios.post(`https://push.techulus.com/api/v1/notify/${notification.pushAPIKey}`, { "title": "Uptime-Kuma", "body": msg, - }) + }); return okMsg; } catch (error) { - this.throwGeneralAxiosError(error) + this.throwGeneralAxiosError(error); } } } diff --git a/server/notification-providers/telegram.js b/server/notification-providers/telegram.js index 54d33bfbd..2b0576224 100644 --- a/server/notification-providers/telegram.js +++ b/server/notification-providers/telegram.js @@ -14,12 +14,12 @@ class Telegram extends NotificationProvider { chat_id: notification.telegramChatID, text: msg, }, - }) + }); return okMsg; } catch (error) { - let msg = (error.response.data.description) ? error.response.data.description : "Error without description" - throw new Error(msg) + let msg = (error.response.data.description) ? error.response.data.description : "Error without description"; + throw new Error(msg); } } } diff --git a/server/notification-providers/webhook.js b/server/notification-providers/webhook.js index 9cb361f30..d4933cf02 100644 --- a/server/notification-providers/webhook.js +++ b/server/notification-providers/webhook.js @@ -24,17 +24,17 @@ class Webhook extends NotificationProvider { config = { headers: finalData.getHeaders(), - } + }; } else { finalData = data; } - await axios.post(notification.webhookURL, finalData, config) + await axios.post(notification.webhookURL, finalData, config); return okMsg; } catch (error) { - this.throwGeneralAxiosError(error) + this.throwGeneralAxiosError(error); } } diff --git a/server/notification-providers/wecom.js b/server/notification-providers/wecom.js index 7ba8c3783..b377cedd9 100644 --- a/server/notification-providers/wecom.js +++ b/server/notification-providers/wecom.js @@ -26,7 +26,7 @@ class WeCom extends NotificationProvider { composeMessage(heartbeatJSON, msg) { let title; - if (msg != null && heartbeatJSON != null && heartbeatJSON['status'] == UP) { + if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] == UP) { title = "UptimeKuma Monitor Up"; } if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] == DOWN) { diff --git a/server/notification.js b/server/notification.js index 30f83b0e0..359e4c9ec 100644 --- a/server/notification.js +++ b/server/notification.js @@ -24,6 +24,7 @@ const Feishu = require("./notification-providers/feishu"); const AliyunSms = require("./notification-providers/aliyun-sms"); const DingDing = require("./notification-providers/dingding"); const Bark = require("./notification-providers/bark"); +const { log } = require("../src/util"); const SerwerSMS = require("./notification-providers/serwersms"); const Stackfield = require("./notification-providers/stackfield"); const WeCom = require("./notification-providers/wecom"); @@ -36,7 +37,7 @@ class Notification { providerList = {}; static init() { - console.log("Prepare Notification Providers"); + log.info("notification", "Prepare Notification Providers"); this.providerList = {}; @@ -104,27 +105,27 @@ class Notification { } static async save(notification, notificationID, userID) { - let bean + let bean; if (notificationID) { bean = await R.findOne("notification", " id = ? AND user_id = ? ", [ notificationID, userID, - ]) + ]); if (! bean) { - throw new Error("notification not found") + throw new Error("notification not found"); } } else { - bean = R.dispense("notification") + bean = R.dispense("notification"); } bean.name = notification.name; bean.user_id = userID; bean.config = JSON.stringify(notification); bean.is_default = notification.isDefault || false; - await R.store(bean) + await R.store(bean); if (notification.applyExisting) { await applyNotificationEveryMonitor(bean.id, userID); @@ -137,13 +138,13 @@ class Notification { let bean = await R.findOne("notification", " id = ? AND user_id = ? ", [ notificationID, userID, - ]) + ]); if (! bean) { - throw new Error("notification not found") + throw new Error("notification not found"); } - await R.trash(bean) + await R.trash(bean); } static checkApprise() { @@ -154,6 +155,13 @@ 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 + */ async function applyNotificationEveryMonitor(notificationID, userID) { let monitors = await R.getAll("SELECT id FROM monitor WHERE user_id = ?", [ userID @@ -163,17 +171,17 @@ async function applyNotificationEveryMonitor(notificationID, userID) { let checkNotification = await R.findOne("monitor_notification", " monitor_id = ? AND notification_id = ? ", [ monitors[i].id, notificationID, - ]) + ]); if (! checkNotification) { let relation = R.dispense("monitor_notification"); relation.monitor_id = monitors[i].id; relation.notification_id = notificationID; - await R.store(relation) + await R.store(relation); } } } module.exports = { Notification, -} +}; diff --git a/server/password-hash.js b/server/password-hash.js index 91e5e1add..a42b4df00 100644 --- a/server/password-hash.js +++ b/server/password-hash.js @@ -4,20 +4,20 @@ const saltRounds = 10; exports.generate = function (password) { return bcrypt.hashSync(password, saltRounds); -} +}; exports.verify = function (password, hash) { if (isSHA1(hash)) { - return passwordHashOld.verify(password, hash) + return passwordHashOld.verify(password, hash); } return bcrypt.compareSync(password, hash); -} +}; function isSHA1(hash) { - return (typeof hash === "string" && hash.startsWith("sha1")) + return (typeof hash === "string" && hash.startsWith("sha1")); } exports.needRehash = function (hash) { return isSHA1(hash); -} +}; diff --git a/server/ping-lite.js b/server/ping-lite.js index 0075e80bd..5f15b6d3a 100644 --- a/server/ping-lite.js +++ b/server/ping-lite.js @@ -8,6 +8,13 @@ const util = require("./util-server"); module.exports = Ping; +/** + * @param {string} host - The 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) { throw new Error("You must specify a host to ping!"); @@ -125,6 +132,11 @@ Ping.prototype.send = function (callback) { } }); + /** + * @param {Function} callback + * + * Generated by Trelent + */ function onEnd() { let stdout = this.stdout._stdout; let stderr = this.stderr._stderr; diff --git a/server/prometheus.js b/server/prometheus.js index 86ad381b5..9634c3080 100644 --- a/server/prometheus.js +++ b/server/prometheus.js @@ -1,4 +1,5 @@ const PrometheusClient = require("prom-client"); +const { log } = require("../src/util"); const commonLabels = [ "monitor_name", @@ -48,15 +49,16 @@ class Prometheus { if (typeof tlsInfo !== "undefined") { try { - let is_valid = 0; + let isValid = 0; if (tlsInfo.valid == true) { - is_valid = 1; + isValid = 1; } else { - is_valid = 0; + isValid = 0; } - monitor_cert_is_valid.set(this.monitorLabelValues, is_valid); + monitor_cert_is_valid.set(this.monitorLabelValues, isValid); } catch (e) { - console.error(e); + log.error("prometheus", "Caught error"); + log.error("prometheus", e); } try { @@ -64,14 +66,16 @@ class Prometheus { monitor_cert_days_remaining.set(this.monitorLabelValues, tlsInfo.certInfo.daysRemaining); } } catch (e) { - console.error(e); + log.error("prometheus", "Caught error"); + log.error("prometheus", e); } } try { monitor_status.set(this.monitorLabelValues, heartbeat.status); } catch (e) { - console.error(e); + log.error("prometheus", "Caught error"); + log.error("prometheus", e); } try { @@ -82,7 +86,8 @@ class Prometheus { monitor_response_time.set(this.monitorLabelValues, -1); } } catch (e) { - console.error(e); + log.error("prometheus", "Caught error"); + log.error("prometheus", e); } } diff --git a/server/rate-limiter.js b/server/rate-limiter.js index 6422af8d2..f58f16cb6 100644 --- a/server/rate-limiter.js +++ b/server/rate-limiter.js @@ -1,5 +1,5 @@ const { RateLimiter } = require("limiter"); -const { debug } = require("../src/util"); +const { log } = require("../src/util"); class KumaRateLimiter { constructor(config) { @@ -9,7 +9,7 @@ class KumaRateLimiter { async pass(callback, num = 1) { const remainingRequests = await this.removeTokens(num); - debug("Rate Limit (remainingRequests):" + remainingRequests); + log.info("rate-limit", "remaining requests: " + remainingRequests); if (remainingRequests < 0) { if (callback) { callback({ diff --git a/server/routers/api-router.js b/server/routers/api-router.js index 6f463b6b0..ad1a671b2 100644 --- a/server/routers/api-router.js +++ b/server/routers/api-router.js @@ -5,7 +5,7 @@ const server = require("../server"); const apicache = require("../modules/apicache"); const Monitor = require("../model/monitor"); const dayjs = require("dayjs"); -const { UP, flipStatus, debug } = require("../../src/util"); +const { UP, flipStatus, log } = require("../../src/util"); const StatusPage = require("../model/status_page"); let router = express.Router(); @@ -62,8 +62,8 @@ router.get("/api/push/:pushToken", async (request, response) => { duration = dayjs(bean.time).diff(dayjs(previousHeartbeat.time), "second"); } - debug("PreviousStatus: " + previousStatus); - debug("Current Status: " + status); + log.debug("router", "PreviousStatus: " + previousStatus); + log.debug("router", "Current Status: " + status); bean.important = Monitor.isImportantBeat(isFirstBeat, previousStatus, status); bean.monitor_id = monitor.id; @@ -124,7 +124,7 @@ router.get("/api/status-page/:slug", cache("5 minutes"), async (request, respons // Public Group List const publicGroupList = []; const showTags = !!statusPage.show_tags; - debug("Show Tags???" + showTags); + const list = await R.find("group", " public = 1 AND status_page_id = ? ORDER BY weight ", [ statusPage.id ]); diff --git a/server/server.js b/server/server.js index 31cf7cdb8..109221e2d 100644 --- a/server/server.js +++ b/server/server.js @@ -11,40 +11,42 @@ if (nodeVersion < requiredVersion) { } const args = require("args-parser")(process.argv); -const { sleep, debug, getRandomInt, genSecret } = require("../src/util"); +const { sleep, log, getRandomInt, genSecret, debug } = require("../src/util"); const config = require("./config"); -debug(args); +log.info("server", "Welcome to Uptime Kuma"); +log.debug("server", "Arguments"); +log.debug("server", args); if (! process.env.NODE_ENV) { process.env.NODE_ENV = "production"; } -console.log("Node Env: " + process.env.NODE_ENV); +log.info("server", "Node Env: " + process.env.NODE_ENV); -console.log("Importing Node libraries"); +log.info("server", "Importing Node libraries"); const fs = require("fs"); const http = require("http"); const https = require("https"); -console.log("Importing 3rd-party libraries"); -debug("Importing express"); +log.info("server", "Importing 3rd-party libraries"); +log.debug("server", "Importing express"); const express = require("express"); -debug("Importing socket.io"); +log.debug("server", "Importing socket.io"); const { Server } = require("socket.io"); -debug("Importing redbean-node"); +log.debug("server", "Importing redbean-node"); const { R } = require("redbean-node"); -debug("Importing jsonwebtoken"); +log.debug("server", "Importing jsonwebtoken"); const jwt = require("jsonwebtoken"); -debug("Importing http-graceful-shutdown"); +log.debug("server", "Importing http-graceful-shutdown"); const gracefulShutdown = require("http-graceful-shutdown"); -debug("Importing prometheus-api-metrics"); +log.debug("server", "Importing prometheus-api-metrics"); const prometheusAPIMetrics = require("prometheus-api-metrics"); -debug("Importing compare-versions"); +log.debug("server", "Importing compare-versions"); const compareVersions = require("compare-versions"); const { passwordStrength } = require("check-password-strength"); -debug("Importing 2FA Modules"); +log.debug("server", "Importing 2FA Modules"); const notp = require("notp"); const base32 = require("thirty-two"); @@ -69,23 +71,23 @@ class UptimeKumaServer { const server = module.exports = new UptimeKumaServer(); -console.log("Importing this project modules"); -debug("Importing Monitor"); +log.info("server", "Importing this project modules"); +log.debug("server", "Importing Monitor"); const Monitor = require("./model/monitor"); -debug("Importing Settings"); +log.debug("server", "Importing Settings"); const { getSettings, setSettings, setting, initJWTSecret, checkLogin, startUnitTest, FBSD, errorLog, doubleCheckPassword } = require("./util-server"); -debug("Importing Notification"); +log.debug("server", "Importing Notification"); const { Notification } = require("./notification"); Notification.init(); -debug("Importing Proxy"); +log.debug("server", "Importing Proxy"); const { Proxy } = require("./proxy"); -debug("Importing Database"); +log.debug("server", "Importing Database"); const Database = require("./database"); -debug("Importing Background Jobs"); +log.debug("server", "Importing Background Jobs"); const { initBackgroundJobs, stopBackgroundJobs } = require("./jobs"); const { loginRateLimiter, twoFaRateLimiter } = require("./rate-limiter"); @@ -94,7 +96,7 @@ const { login } = require("./auth"); const passwordHash = require("./password-hash"); const checkVersion = require("./check-version"); -console.info("Version: " + checkVersion.version); +log.info("server", "Version: " + checkVersion.version); // If host is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available and the unspecified IPv4 address (0.0.0.0) otherwise. // Dual-stack support for (::) @@ -103,7 +105,7 @@ let hostEnv = FBSD ? null : process.env.HOST; let hostname = args.host || process.env.UPTIME_KUMA_HOST || hostEnv; if (hostname) { - console.log("Custom hostname: " + hostname); + log.info("server", "Custom hostname: " + hostname); } const port = [args.port, process.env.UPTIME_KUMA_PORT, process.env.PORT, 3001] @@ -129,22 +131,22 @@ const twofa_verification_opts = { const testMode = !!args["test"] || false; if (config.demoMode) { - console.log("==== Demo Mode ===="); + log.info("server", "==== Demo Mode ===="); } -console.log("Creating express and socket.io instance"); +log.info("server", "Creating express and socket.io instance"); const app = express(); let httpServer; if (sslKey && sslCert) { - console.log("Server Type: HTTPS"); + log.info("server", "Server Type: HTTPS"); httpServer = https.createServer({ key: fs.readFileSync(sslKey), cert: fs.readFileSync(sslCert) }, app); } else { - console.log("Server Type: HTTP"); + log.info("server", "Server Type: HTTP"); httpServer = http.createServer(app); } @@ -200,7 +202,7 @@ try { } catch (e) { // "dist/index.html" is not necessary for development if (process.env.NODE_ENV !== "development") { - console.error("Error: Cannot find 'dist/index.html', did you install correctly?"); + log.error("server", "Error: Cannot find 'dist/index.html', did you install correctly?"); process.exit(1); } } @@ -212,7 +214,7 @@ try { exports.entryPage = await setting("entryPage"); await StatusPage.loadDomainMappingList(); - console.log("Adding route"); + log.info("server", "Adding route"); // *************************** // Normal Router here @@ -270,7 +272,7 @@ try { } }); - console.log("Adding socket handler"); + log.info("server", "Adding socket handler"); io.on("connection", async (socket) => { sendInfo(socket); @@ -278,7 +280,7 @@ try { totalClient++; if (needSetup) { - console.log("Redirect to setup page"); + log.info("server", "Redirect to setup page"); socket.emit("setup"); } @@ -291,33 +293,40 @@ try { // *************************** socket.on("loginByToken", async (token, callback) => { + log.info("auth", `Login by token. IP=${getClientIp(socket)}`); try { let decoded = jwt.verify(token, jwtSecret); - console.log("Username from JWT: " + decoded.username); + log.info("auth", "Username from JWT: " + decoded.username); let user = await R.findOne("user", " username = ? AND active = 1 ", [ decoded.username, ]); if (user) { - debug("afterLogin"); - + log.debug("auth", "afterLogin"); afterLogin(socket, user); + log.debug("auth", "afterLogin ok"); - debug("afterLogin ok"); + log.info("auth", `Successfully logged in user ${decoded.username}. IP=${getClientIp(socket)}`); callback({ ok: true, }); } else { + + log.info("auth", `Inactive or deleted user ${decoded.username}. IP=${getClientIp(socket)}`); + callback({ ok: false, msg: "The user is inactive or deleted.", }); } } catch (error) { + + log.error("auth", `Invalid token. IP=${getClientIp(socket)}`); + callback({ ok: false, msg: "Invalid token.", @@ -327,7 +336,7 @@ try { }); socket.on("login", async (data, callback) => { - console.log("Login"); + log.info("auth", `Login by username + password. IP=${getClientIp(socket)}`); // Checking if (typeof callback !== "function") { @@ -340,6 +349,7 @@ try { // Login Rate Limit if (! await loginRateLimiter.pass(callback)) { + log.info("auth", `Too many failed requests for user ${data.username}. IP=${getClientIp(socket)}`); return; } @@ -348,6 +358,9 @@ try { if (user) { if (user.twofa_status == 0) { afterLogin(socket, user); + + log.info("auth", `Successfully logged in user ${data.username}. IP=${getClientIp(socket)}`); + callback({ ok: true, token: jwt.sign({ @@ -357,6 +370,9 @@ try { } if (user.twofa_status == 1 && !data.token) { + + log.info("auth", `2FA token required for user ${data.username}. IP=${getClientIp(socket)}`); + callback({ tokenRequired: true, }); @@ -373,6 +389,8 @@ try { socket.userID, ]); + log.info("auth", `Successfully logged in user ${data.username}. IP=${getClientIp(socket)}`); + callback({ ok: true, token: jwt.sign({ @@ -380,6 +398,9 @@ try { }, jwtSecret), }); } else { + + log.warn("auth", `Invalid token provided for user ${data.username}. IP=${getClientIp(socket)}`); + callback({ ok: false, msg: "Invalid Token!", @@ -387,6 +408,9 @@ try { } } } else { + + log.warn("auth", `Incorrect username or password for user ${data.username}. IP=${getClientIp(socket)}`); + callback({ ok: false, msg: "Incorrect username or password.", @@ -469,11 +493,16 @@ try { socket.userID, ]); + log.info("auth", `Saved 2FA token. IP=${getClientIp(socket)}`); + callback({ ok: true, msg: "2FA Enabled.", }); } catch (error) { + + log.error("auth", `Error changing 2FA token. IP=${getClientIp(socket)}`); + callback({ ok: false, msg: error.message, @@ -491,11 +520,16 @@ try { await doubleCheckPassword(socket, currentPassword); await TwoFA.disable2FA(socket.userID); + log.info("auth", `Disabled 2FA token. IP=${getClientIp(socket)}`); + callback({ ok: true, msg: "2FA Disabled.", }); } catch (error) { + + log.error("auth", `Error disabling 2FA token. IP=${getClientIp(socket)}`); + callback({ ok: false, msg: error.message, @@ -622,6 +656,8 @@ try { await server.sendMonitorList(socket); await startMonitor(socket.userID, bean.id); + log.info("monitor", `Added Monitor: ${monitor.id} User ID: ${socket.userID}`); + callback({ ok: true, msg: "Added Successfully.", @@ -629,6 +665,9 @@ try { }); } catch (e) { + + log.error("monitor", `Error adding Monitor: ${monitor.id} User ID: ${socket.userID}`); + callback({ ok: false, msg: e.message, @@ -691,7 +730,7 @@ try { }); } catch (e) { - console.error(e); + log.error("monitor", e); callback({ ok: false, msg: e.message, @@ -707,7 +746,7 @@ try { ok: true, }); } catch (e) { - console.error(e); + log.error("monitor", e); callback({ ok: false, msg: e.message, @@ -719,7 +758,7 @@ try { try { checkLogin(socket); - console.log(`Get Monitor: ${monitorID} User ID: ${socket.userID}`); + log.info("monitor", `Get Monitor: ${monitorID} User ID: ${socket.userID}`); let bean = await R.findOne("monitor", " id = ? AND user_id = ? ", [ monitorID, @@ -743,7 +782,7 @@ try { try { checkLogin(socket); - console.log(`Get Monitor Beats: ${monitorID} User ID: ${socket.userID}`); + log.info("monitor", `Get Monitor Beats: ${monitorID} User ID: ${socket.userID}`); if (period == null) { throw new Error("Invalid period."); @@ -814,7 +853,7 @@ try { try { checkLogin(socket); - console.log(`Delete Monitor: ${monitorID} User ID: ${socket.userID}`); + log.info("manage", `Delete Monitor: ${monitorID} User ID: ${socket.userID}`); if (monitorID in server.monitorList) { server.monitorList[monitorID].stop(); @@ -1146,7 +1185,7 @@ try { let backupData = JSON.parse(uploadedJSON); - console.log(`Importing Backup, User ID: ${socket.userID}, Version: ${backupData.version}`); + log.info("manage", `Importing Backup, User ID: ${socket.userID}, Version: ${backupData.version}`); let notificationListData = backupData.notificationList; let proxyListData = backupData.proxyList; @@ -1341,7 +1380,7 @@ try { try { checkLogin(socket); - console.log(`Clear Events Monitor: ${monitorID} User ID: ${socket.userID}`); + log.info("manage", `Clear Events Monitor: ${monitorID} User ID: ${socket.userID}`); await R.exec("UPDATE heartbeat SET msg = ?, important = ? WHERE monitor_id = ? ", [ "", @@ -1367,7 +1406,7 @@ try { try { checkLogin(socket); - console.log(`Clear Heartbeats Monitor: ${monitorID} User ID: ${socket.userID}`); + log.info("manage", `Clear Heartbeats Monitor: ${monitorID} User ID: ${socket.userID}`); await R.exec("DELETE FROM heartbeat WHERE monitor_id = ?", [ monitorID @@ -1391,7 +1430,7 @@ try { try { checkLogin(socket); - console.log(`Clear Statistics User ID: ${socket.userID}`); + log.info("manage", `Clear Statistics User ID: ${socket.userID}`); await R.exec("DELETE FROM heartbeat"); @@ -1413,24 +1452,24 @@ try { databaseSocketHandler(socket); proxySocketHandler(socket); - debug("added all socket handlers"); + log.debug("server", "added all socket handlers"); // *************************** // Better do anything after added all socket handlers here // *************************** - debug("check auto login"); + log.debug("auth", "check auto login"); if (await setting("disableAuth")) { - console.log("Disabled Auth: auto login to admin"); + log.info("auth", "Disabled Auth: auto login to admin"); afterLogin(socket, await R.findOne("user")); socket.emit("autoLogin"); } else { - debug("need auth"); + log.debug("auth", "need auth"); } }); - console.log("Init the server"); + log.info("server", "Init the server"); httpServer.once("error", async (err) => { console.error("Cannot listen: " + err.message); @@ -1439,9 +1478,9 @@ try { httpServer.listen(port, hostname, () => { if (hostname) { - console.log(`Listening on ${hostname}:${port}`); + log.info("server", `Listening on ${hostname}:${port}`); } else { - console.log(`Listening on ${port}`); + log.info("server", `Listening on ${port}`); } startMonitors(); checkVersion.startInterval(); @@ -1458,6 +1497,13 @@ try { })(); +/** + * Adds or removes notifications from a monitor. + * @param {number} monitorID The ID of the monitor to add/remove notifications from. + * @param {Array.} notificationIDList An array of IDs for the notifications to add/remove. + * + * Generated by Trelent + */ async function updateMonitorNotification(monitorID, notificationIDList) { await R.exec("DELETE FROM monitor_notification WHERE monitor_id = ? ", [ monitorID, @@ -1473,6 +1519,13 @@ 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 + */ async function checkOwner(userID, monitorID) { let row = await R.getRow("SELECT id FROM monitor WHERE id = ? AND user_id = ? ", [ monitorID, @@ -1484,6 +1537,10 @@ async function checkOwner(userID, monitorID) { } } +/** + * 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. + */ async function afterLogin(socket, user) { socket.userID = user.id; socket.join(user.id); @@ -1509,6 +1566,13 @@ async function afterLogin(socket, user) { } } +/** + * Get a list of monitors for the given user. + * @param {string} userID - The ID of the user to get monitors for. + * @returns {Promise} A promise that resolves to an object with monitor IDs as keys and monitor objects as values. + * + * Generated by Trelent + */ async function getMonitorJSONList(userID) { let result = {}; @@ -1523,15 +1587,20 @@ async function getMonitorJSONList(userID) { return result; } +/** + * Connect to the database and patch it if necessary. + * + * Generated by Trelent + */ async function initDatabase(testMode = false) { if (! fs.existsSync(Database.path)) { - console.log("Copying Database"); + log.info("server", "Copying Database"); fs.copyFileSync(Database.templatePath, Database.path); } - console.log("Connecting to the Database"); + log.info("server", "Connecting to the Database"); await Database.connect(testMode); - console.log("Connected"); + log.info("server", "Connected"); // Patch the database await Database.patch(); @@ -1541,26 +1610,33 @@ async function initDatabase(testMode = false) { ]); if (! jwtSecretBean) { - console.log("JWT secret is not found, generate one."); + log.info("server", "JWT secret is not found, generate one."); jwtSecretBean = await initJWTSecret(); - console.log("Stored JWT secret into database"); + log.info("server", "Stored JWT secret into database"); } else { - console.log("Load JWT secret from database."); + log.info("server", "Load JWT secret from database."); } // If there is no record in user table, it is a new Uptime Kuma instance, need to setup if ((await R.count("user")) === 0) { - console.log("No user, need setup"); + log.info("server", "No user, need setup"); needSetup = true; } jwtSecret = jwtSecretBean.value; } +/** + * 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 + */ async function startMonitor(userID, monitorID) { await checkOwner(userID, monitorID); - console.log(`Resume Monitor: ${monitorID} User ID: ${userID}`); + log.info("manage", `Resume Monitor: ${monitorID} User ID: ${userID}`); await R.exec("UPDATE monitor SET active = 1 WHERE id = ? AND user_id = ? ", [ monitorID, @@ -1583,10 +1659,17 @@ 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 + */ async function pauseMonitor(userID, monitorID) { await checkOwner(userID, monitorID); - console.log(`Pause Monitor: ${monitorID} User ID: ${userID}`); + log.info("manage", `Pause Monitor: ${monitorID} User ID: ${userID}`); await R.exec("UPDATE monitor SET active = 0 WHERE id = ? AND user_id = ? ", [ monitorID, @@ -1615,11 +1698,17 @@ async function startMonitors() { } } +/** + * Stops all monitors and closes the database connection. + * @param {string} signal The signal that triggered this function to be called. + * + * Generated by Trelent + */ async function shutdownFunction(signal) { - console.log("Shutdown requested"); - console.log("Called signal: " + signal); + log.info("server", "Shutdown requested"); + log.info("server", "Called signal: " + signal); - console.log("Stopping all monitors"); + log.info("server", "Stopping all monitors"); for (let id in server.monitorList) { let monitor = server.monitorList[id]; monitor.stop(); @@ -1631,8 +1720,12 @@ async function shutdownFunction(signal) { await cloudflaredStop(); } +function getClientIp(socket) { + return socket.client.conn.remoteAddress.replace(/^.*:/, ""); +} + function finalFunction() { - console.log("Graceful shutdown successful!"); + log.info("server", "Graceful shutdown successful!"); } gracefulShutdown(httpServer, { diff --git a/server/socket-handlers/status-page-socket-handler.js b/server/socket-handlers/status-page-socket-handler.js index c844136ea..36e90fb93 100644 --- a/server/socket-handlers/status-page-socket-handler.js +++ b/server/socket-handlers/status-page-socket-handler.js @@ -1,7 +1,7 @@ const { R } = require("redbean-node"); -const { checkLogin, setSettings, setSetting } = require("../util-server"); +const { checkLogin, setSetting } = require("../util-server"); const dayjs = require("dayjs"); -const { debug } = require("../../src/util"); +const { log } = require("../../src/util"); const ImageDataURI = require("../image-data-uri"); const Database = require("../database"); const apicache = require("../modules/apicache"); @@ -202,8 +202,8 @@ module.exports.statusPageSocketHandler = (socket) => { group.id = groupBean.id; } - // Delete groups that not in the list - debug("Delete groups that not in the list"); + // Delete groups that are not in the list + log.debug("socket", "Delete groups that are not in the list"); const slots = groupIDList.map(() => "?").join(","); const data = [ @@ -226,7 +226,7 @@ module.exports.statusPageSocketHandler = (socket) => { }); } catch (error) { - console.error(error); + log.error("socket", error); callback({ ok: false, diff --git a/server/util-server.js b/server/util-server.js index b2c70d92f..61b2ed129 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -1,10 +1,10 @@ const tcpp = require("tcp-ping"); const Ping = require("./ping-lite"); const { R } = require("redbean-node"); -const { debug, genSecret } = require("../src/util"); +const { log, genSecret } = require("../src/util"); const passwordHash = require("./password-hash"); const { Resolver } = require("dns"); -const child_process = require("child_process"); +const childProcess = require("child_process"); const iconv = require("iconv-lite"); const chardet = require("chardet"); const fs = require("fs"); @@ -119,7 +119,7 @@ exports.setting = async function (key) { try { const v = JSON.parse(value); - debug(`Get Setting: ${key}: ${v}`); + log.debug("util", `Get Setting: ${key}: ${v}`); return v; } catch (e) { return value; @@ -206,7 +206,7 @@ const parseCertificateInfo = function (info) { const existingList = {}; while (link) { - debug(`[${i}] ${link.fingerprint}`); + log.debug("util", `[${i}] ${link.fingerprint}`); if (!link.valid_from || !link.valid_to) { break; @@ -221,7 +221,7 @@ const parseCertificateInfo = function (info) { if (link.issuerCertificate == null) { break; } else if (link.issuerCertificate.fingerprint in existingList) { - debug(`[Last] ${link.issuerCertificate.fingerprint}`); + log.debug("util", `[Last] ${link.issuerCertificate.fingerprint}`); link.issuerCertificate = null; break; } else { @@ -242,7 +242,7 @@ exports.checkCertificate = function (res) { const info = res.request.res.socket.getPeerCertificate(true); const valid = res.request.res.socket.authorized || false; - debug("Parsing Certificate Info"); + log.debug("util", "Parsing Certificate Info"); const parsedInfo = parseCertificateInfo(info); return { @@ -345,7 +345,7 @@ exports.doubleCheckPassword = async (socket, currentPassword) => { exports.startUnitTest = async () => { console.log("Starting unit test..."); const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm"; - const child = child_process.spawn(npm, ["run", "jest"]); + const child = childProcess.spawn(npm, ["run", "jest"]); child.stdout.on("data", (data) => { console.log(data.toString()); @@ -367,7 +367,6 @@ exports.startUnitTest = async () => { */ exports.convertToUTF8 = (body) => { const guessEncoding = chardet.detect(body); - //debug("Guess Encoding: " + guessEncoding); const str = iconv.decode(body, guessEncoding); return str.toString(); }; diff --git a/src/App.vue b/src/App.vue index 099450d41..f102360c1 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,12 +1,12 @@ diff --git a/src/components/Confirm.vue b/src/components/Confirm.vue index 391155f43..994075830 100644 --- a/src/components/Confirm.vue +++ b/src/components/Confirm.vue @@ -25,7 +25,7 @@ diff --git a/src/components/CountUp.vue b/src/components/CountUp.vue index b321fde19..5a0deb745 100644 --- a/src/components/CountUp.vue +++ b/src/components/CountUp.vue @@ -5,7 +5,7 @@ diff --git a/src/components/Datetime.vue b/src/components/Datetime.vue index 1df982cb1..8662e6d8a 100644 --- a/src/components/Datetime.vue +++ b/src/components/Datetime.vue @@ -4,12 +4,12 @@ diff --git a/src/components/HeartbeatBar.vue b/src/components/HeartbeatBar.vue index be0b122ed..245a8512c 100644 --- a/src/components/HeartbeatBar.vue +++ b/src/components/HeartbeatBar.vue @@ -38,7 +38,7 @@ export default { beatMargin: 4, move: false, maxBeat: -1, - } + }; }, computed: { @@ -69,12 +69,12 @@ export default { if (start < 0) { // Add empty placeholder for (let i = start; i < 0; i++) { - placeholders.push(0) + placeholders.push(0); } start = 0; } - return placeholders.concat(this.beatList.slice(start)) + return placeholders.concat(this.beatList.slice(start)); }, wrapStyle() { @@ -84,7 +84,7 @@ export default { return { padding: `${topBottom}px ${leftRight}px`, width: "100%", - } + }; }, barStyle() { @@ -94,12 +94,12 @@ export default { return { transition: "all ease-in-out 0.25s", transform: `translateX(${width}px)`, - } + }; } return { transform: "translateX(0)", - } + }; }, @@ -109,7 +109,7 @@ export default { height: this.beatHeight + "px", margin: this.beatMargin + "px", "--hover-scale": this.hoverScale, - } + }; }, }, @@ -120,7 +120,7 @@ export default { setTimeout(() => { this.move = false; - }, 300) + }, 300); }, deep: true, }, @@ -162,15 +162,15 @@ export default { methods: { resize() { if (this.$refs.wrap) { - this.maxBeat = Math.floor(this.$refs.wrap.clientWidth / (this.beatWidth + this.beatMargin * 2)) + this.maxBeat = Math.floor(this.$refs.wrap.clientWidth / (this.beatWidth + this.beatMargin * 2)); } }, getBeatTitle(beat) { - return `${this.$root.datetime(beat.time)}` + ((beat.msg) ? ` - ${beat.msg}` : ``); + return `${this.$root.datetime(beat.time)}` + ((beat.msg) ? ` - ${beat.msg}` : ""); } }, -} +}; diff --git a/src/components/settings/MonitorHistory.vue b/src/components/settings/MonitorHistory.vue index 9b5b8bd78..0092727f4 100644 --- a/src/components/settings/MonitorHistory.vue +++ b/src/components/settings/MonitorHistory.vue @@ -52,7 +52,7 @@ - - diff --git a/src/components/settings/Security.vue b/src/components/settings/Security.vue index 1459519f5..e59600046 100644 --- a/src/components/settings/Security.vue +++ b/src/components/settings/Security.vue @@ -355,7 +355,7 @@ export default {