Merge branch 'master' into customstatuspage

# Conflicts:
#	src/languages/de-DE.js
pull/1083/head
Louis Lam 3 years ago
commit 1bc01d1077

@ -1,4 +1,9 @@
module.exports = { module.exports = {
ignorePatterns: [
"test/*",
"server/modules/apicache/*",
"src/util.js"
],
root: true, root: true,
env: { env: {
browser: true, browser: true,
@ -22,9 +27,9 @@ module.exports = {
"properties": "never", "properties": "never",
"ignoreImports": true "ignoreImports": true
}], }],
// override/add rules settings here, such as: "no-unused-vars": ["warn", {
// 'vue/no-unused-vars': 'error' "args": "none"
"no-unused-vars": "warn", }],
indent: [ indent: [
"error", "error",
4, 4,
@ -34,7 +39,7 @@ module.exports = {
}, },
], ],
quotes: ["warn", "double"], quotes: ["warn", "double"],
semi: "warn", semi: "error",
"vue/html-indent": ["warn", 4], // default: 2 "vue/html-indent": ["warn", 4], // default: 2
"vue/max-attributes-per-line": "off", "vue/max-attributes-per-line": "off",
"vue/singleline-html-element-content-newline": "off", "vue/singleline-html-element-content-newline": "off",

@ -20,6 +20,7 @@ jobs:
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/ # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps: steps:
- run: git config --global core.autocrlf false # Mainly for Windows
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}

@ -1,9 +1,13 @@
{ {
"extends": "stylelint-config-standard", "extends": "stylelint-config-standard",
"customSyntax": "postcss-html",
"rules": { "rules": {
"indentation": 4, "indentation": 4,
"no-descending-specificity": null, "no-descending-specificity": null,
"selector-list-comma-newline-after": 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
} }
} }

@ -11,3 +11,4 @@ services:
- ./uptime-kuma:/app/data - ./uptime-kuma:/app/data
ports: ports:
- 3001:3001 - 3001:3001
restart: always

@ -1,6 +1,6 @@
module.exports = { module.exports = {
apps: [{ apps: [{
name: "uptime-kuma", name: "uptime-kuma",
script: "./server/server.js", script: "./server/server.js",
}] }]
} };

@ -1,11 +1,10 @@
const pkg = require("../../package.json"); const pkg = require("../../package.json");
const fs = require("fs"); const fs = require("fs");
const child_process = require("child_process"); const childProcess = require("child_process");
const util = require("../../src/util"); const util = require("../../src/util");
util.polyfill(); util.polyfill();
const oldVersion = pkg.version;
const version = process.env.VERSION; const version = process.env.VERSION;
console.log("Beta Version: " + version); console.log("Beta Version: " + version);
@ -32,7 +31,7 @@ if (! exists) {
function commit(version) { function commit(version) {
let msg = "Update to " + 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(); let stdout = res.stdout.toString().trim();
console.log(stdout); console.log(stdout);
@ -40,15 +39,15 @@ function commit(version) {
throw new Error("commit error"); 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()); console.log(res.stdout.toString().trim());
} }
function tag(version) { function tag(version) {
let res = child_process.spawnSync("git", ["tag", version]); let res = childProcess.spawnSync("git", ["tag", version]);
console.log(res.stdout.toString().trim()); 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()); console.log(res.stdout.toString().trim());
} }
@ -57,15 +56,7 @@ function tagExists(version) {
throw new Error("invalid 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; return res.stdout.toString().trim() === version;
} }
function safeDelete(dir) {
if (fs.existsSync(dir)) {
fs.rmdirSync(dir, {
recursive: true,
});
}
}

@ -4,21 +4,21 @@ const util = require("../src/util");
util.polyfill(); util.polyfill();
const oldVersion = pkg.version const oldVersion = pkg.version;
const newVersion = oldVersion + "-nightly" const newVersion = oldVersion + "-nightly";
console.log("Old Version: " + oldVersion) console.log("Old Version: " + oldVersion);
console.log("New Version: " + newVersion) console.log("New Version: " + newVersion);
if (newVersion) { if (newVersion) {
// Process package.json // Process package.json
pkg.version = newVersion pkg.version = newVersion;
pkg.scripts.setup = pkg.scripts.setup.replaceAll(oldVersion, newVersion) pkg.scripts.setup = pkg.scripts.setup.replaceAll(oldVersion, newVersion);
pkg.scripts["build-docker"] = pkg.scripts["build-docker"].replaceAll(oldVersion, newVersion) pkg.scripts["build-docker"] = pkg.scripts["build-docker"].replaceAll(oldVersion, newVersion);
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n") fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n");
// Process README.md // Process README.md
if (fs.existsSync("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));
} }
} }

@ -26,7 +26,7 @@ server.on("request", (request, send, rinfo) => {
ttl: 300, ttl: 300,
address: "1.2.3.4" address: "1.2.3.4"
}); });
} if (question.type === Packet.TYPE.AAAA) { } else if (question.type === Packet.TYPE.AAAA) {
response.answers.push({ response.answers.push({
name: question.name, name: question.name,
type: question.type, type: question.type,

@ -1,7 +1,6 @@
const pkg = require("../package.json"); const pkg = require("../package.json");
const fs = require("fs"); const fs = require("fs");
const rmSync = require("./fs-rmSync.js"); const childProcess = require("child_process");
const child_process = require("child_process");
const util = require("../src/util"); const util = require("../src/util");
util.polyfill(); util.polyfill();
@ -42,7 +41,7 @@ if (! exists) {
function commit(version) { function commit(version) {
let msg = "Update to " + 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(); let stdout = res.stdout.toString().trim();
console.log(stdout); console.log(stdout);
@ -52,7 +51,7 @@ function commit(version) {
} }
function tag(version) { function tag(version) {
let res = child_process.spawnSync("git", ["tag", version]); let res = childProcess.spawnSync("git", ["tag", version]);
console.log(res.stdout.toString().trim()); console.log(res.stdout.toString().trim());
} }
@ -67,7 +66,7 @@ function tagExists(version) {
throw new Error("invalid 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; return res.stdout.toString().trim() === version;
} }

@ -1,4 +1,4 @@
const child_process = require("child_process"); const childProcess = require("child_process");
const fs = require("fs"); const fs = require("fs");
const newVersion = process.env.VERSION; const newVersion = process.env.VERSION;
@ -16,23 +16,23 @@ function updateWiki(newVersion) {
safeDelete(wikiDir); safeDelete(wikiDir);
child_process.spawnSync("git", ["clone", "https://github.com/louislam/uptime-kuma.wiki.git", wikiDir]); childProcess.spawnSync("git", ["clone", "https://github.com/louislam/uptime-kuma.wiki.git", wikiDir]);
let content = fs.readFileSync(howToUpdateFilename).toString(); let content = fs.readFileSync(howToUpdateFilename).toString();
// Replace the version: https://regex101.com/r/hmj2Bc/1 // Replace the version: https://regex101.com/r/hmj2Bc/1
content = content.replace(/(git checkout )([^\s]+)/, `$1${newVersion}`); content = content.replace(/(git checkout )([^\s]+)/, `$1${newVersion}`);
fs.writeFileSync(howToUpdateFilename, content); fs.writeFileSync(howToUpdateFilename, content);
child_process.spawnSync("git", ["add", "-A"], { childProcess.spawnSync("git", ["add", "-A"], {
cwd: wikiDir, cwd: wikiDir,
}); });
child_process.spawnSync("git", ["commit", "-m", `Update to ${newVersion}`], { childProcess.spawnSync("git", ["commit", "-m", `Update to ${newVersion}`], {
cwd: wikiDir, cwd: wikiDir,
}); });
console.log("Pushing to Github"); console.log("Pushing to Github");
child_process.spawnSync("git", ["push"], { childProcess.spawnSync("git", ["push"], {
cwd: wikiDir, cwd: wikiDir,
}); });

188
package-lock.json generated

@ -1,12 +1,12 @@
{ {
"name": "uptime-kuma", "name": "uptime-kuma",
"version": "1.14.0-beta.0", "version": "1.14.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "uptime-kuma", "name": "uptime-kuma",
"version": "1.14.0-beta.0", "version": "1.14.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "~1.2.36", "@fortawesome/fontawesome-svg-core": "~1.2.36",
@ -85,6 +85,7 @@
"jest": "~27.2.5", "jest": "~27.2.5",
"jest-puppeteer": "~6.0.3", "jest-puppeteer": "~6.0.3",
"npm-check-updates": "^12.5.5", "npm-check-updates": "^12.5.5",
"postcss-html": "^1.3.1",
"puppeteer": "~13.1.3", "puppeteer": "~13.1.3",
"sass": "~1.42.1", "sass": "~1.42.1",
"stylelint": "~14.2.0", "stylelint": "~14.2.0",
@ -5612,6 +5613,41 @@
"node": ">=6.0.0" "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": { "node_modules/domexception": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz",
@ -5633,6 +5669,35 @@
"node": ">=8" "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": { "node_modules/dot-prop": {
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
@ -5817,6 +5882,18 @@
"node": ">=8.6" "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": { "node_modules/env-paths": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
@ -7557,6 +7634,25 @@
"node": ">=8" "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": { "node_modules/http-cache-semantics": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
@ -12505,6 +12601,20 @@
"node": "^10 || ^12 || >=14" "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": { "node_modules/postcss-media-query-parser": {
"version": "0.2.3", "version": "0.2.3",
"resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", "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" "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": { "domexception": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", "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": { "dot-prop": {
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
@ -20157,6 +20312,12 @@
"ansi-colors": "^4.1.1" "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": { "env-paths": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
@ -21437,6 +21598,18 @@
"integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==", "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==",
"dev": true "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": { "http-cache-semantics": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", "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" "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": { "postcss-media-query-parser": {
"version": "0.2.3", "version": "0.2.3",
"resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz",

@ -20,7 +20,7 @@
"start-server": "node server/server.js", "start-server": "node server/server.js",
"start-server-dev": "cross-env NODE_ENV=development node server/server.js", "start-server-dev": "cross-env NODE_ENV=development node server/server.js",
"build": "vite build --config ./config/vite.config.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", "test-with-build": "npm run build && npm test",
"jest": "node test/prepare-jest.js && npm run jest-frontend && npm run jest-backend", "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", "jest-frontend": "cross-env TEST_FRONTEND=1 jest --config=./config/jest-frontend.config.js",
@ -132,6 +132,7 @@
"jest": "~27.2.5", "jest": "~27.2.5",
"jest-puppeteer": "~6.0.3", "jest-puppeteer": "~6.0.3",
"npm-check-updates": "^12.5.5", "npm-check-updates": "^12.5.5",
"postcss-html": "^1.3.1",
"puppeteer": "~13.1.3", "puppeteer": "~13.1.3",
"sass": "~1.42.1", "sass": "~1.42.1",
"stylelint": "~14.2.0", "stylelint": "~14.2.0",

@ -1,4 +1,3 @@
const { checkLogin } = require("./util-server");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
class TwoFA { class TwoFA {

@ -2,7 +2,6 @@ const basicAuth = require("express-basic-auth");
const passwordHash = require("./password-hash"); const passwordHash = require("./password-hash");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const { setting } = require("./util-server"); const { setting } = require("./util-server");
const { debug } = require("../src/util");
const { loginRateLimiter } = require("./rate-limiter"); const { loginRateLimiter } = require("./rate-limiter");
/** /**

@ -1,7 +1,7 @@
const fs = require("fs"); const fs = require("fs");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const { setSetting, setting } = require("./util-server"); const { setSetting, setting } = require("./util-server");
const { debug, sleep } = require("../src/util"); const { log, sleep } = require("../src/util");
const dayjs = require("dayjs"); const dayjs = require("dayjs");
const knex = require("knex"); const knex = require("knex");
@ -80,7 +80,7 @@ class Database {
fs.mkdirSync(Database.uploadDir, { recursive: true }); 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) { static async connect(testMode = false, autoloadModels = true, noLog = false) {
@ -135,10 +135,10 @@ class Database {
await R.exec("PRAGMA synchronous = FULL"); await R.exec("PRAGMA synchronous = FULL");
if (!noLog) { if (!noLog) {
console.log("SQLite config:"); log.info("db", "SQLite config:");
console.log(await R.getAll("PRAGMA journal_mode")); log.info("db", await R.getAll("PRAGMA journal_mode"));
console.log(await R.getAll("PRAGMA cache_size")); log.info("db", await R.getAll("PRAGMA cache_size"));
console.log("SQLite Version: " + await R.getCell("SELECT sqlite_version()")); log.info("db", "SQLite Version: " + await R.getCell("SELECT sqlite_version()"));
} }
} }
@ -149,15 +149,15 @@ class Database {
version = 0; version = 0;
} }
console.info("Your database version: " + version); log.info("db", "Your database version: " + version);
console.info("Latest database version: " + this.latestVersion); log.info("db", "Latest database version: " + this.latestVersion);
if (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) { } 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 { } else {
console.info("Database patch is needed"); log.info("db", "Database patch is needed");
this.backup(version); this.backup(version);
@ -165,17 +165,17 @@ class Database {
try { try {
for (let i = version + 1; i <= this.latestVersion; i++) { for (let i = version + 1; i <= this.latestVersion; i++) {
const sqlFile = `./db/patch${i}.sql`; const sqlFile = `./db/patch${i}.sql`;
console.info(`Patching ${sqlFile}`); log.info("db", `Patching ${sqlFile}`);
await Database.importSQLFile(sqlFile); await Database.importSQLFile(sqlFile);
console.info(`Patched ${sqlFile}`); log.info("db", `Patched ${sqlFile}`);
await setSetting("database_version", i); await setSetting("database_version", i);
} }
} catch (ex) { } catch (ex) {
await Database.close(); await Database.close();
console.error(ex); log.error("db", ex);
console.error("Start Uptime-Kuma failed due to issue patching the database"); log.error("db", "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", "Please submit a bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues");
this.restore(); this.restore();
process.exit(1); process.exit(1);
@ -191,15 +191,15 @@ class Database {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
static async patch2() { static async patch2() {
console.log("Database Patch 2.0 Process"); log.info("db", "Database Patch 2.0 Process");
let databasePatchedFiles = await setting("databasePatchedFiles"); let databasePatchedFiles = await setting("databasePatchedFiles");
if (! databasePatchedFiles) { if (! databasePatchedFiles) {
databasePatchedFiles = {}; databasePatchedFiles = {};
} }
debug("Patched files:"); log.debug("db", "Patched files:");
debug(databasePatchedFiles); log.debug("db", databasePatchedFiles);
try { try {
for (let sqlFilename in this.patchList) { for (let sqlFilename in this.patchList) {
@ -207,15 +207,15 @@ class Database {
} }
if (this.patched) { if (this.patched) {
console.log("Database Patched Successfully"); log.info("db", "Database Patched Successfully");
} }
} catch (ex) { } catch (ex) {
await Database.close(); await Database.close();
console.error(ex); log.error("db", ex);
console.error("Start Uptime-Kuma failed due to issue patching the database"); log.error("db", "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", "Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues");
this.restore(); this.restore();
@ -302,16 +302,16 @@ class Database {
let value = this.patchList[sqlFilename]; let value = this.patchList[sqlFilename];
if (! value) { if (! value) {
console.log(sqlFilename + " skip"); log.info("db", sqlFilename + " skip");
return; return;
} }
// Check if patched // Check if patched
if (! databasePatchedFiles[sqlFilename]) { if (! databasePatchedFiles[sqlFilename]) {
console.log(sqlFilename + " is not patched"); log.info("db", sqlFilename + " is not patched");
if (value.parents) { if (value.parents) {
console.log(sqlFilename + " need parents"); log.info("db", sqlFilename + " need parents");
for (let parentSQLFilename of value.parents) { for (let parentSQLFilename of value.parents) {
await this.patch2Recursion(parentSQLFilename, databasePatchedFiles); await this.patch2Recursion(parentSQLFilename, databasePatchedFiles);
} }
@ -319,14 +319,14 @@ class Database {
this.backup(dayjs().format("YYYYMMDDHHmmss")); this.backup(dayjs().format("YYYYMMDDHHmmss"));
console.log(sqlFilename + " is patching"); log.info("db", sqlFilename + " is patching");
this.patched = true; this.patched = true;
await this.importSQLFile("./db/" + sqlFilename); await this.importSQLFile("./db/" + sqlFilename);
databasePatchedFiles[sqlFilename] = true; databasePatchedFiles[sqlFilename] = true;
console.log(sqlFilename + " was patched successfully"); log.info("db", sqlFilename + " was patched successfully");
} else { } 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); process.addListener("unhandledRejection", listener);
console.log("Closing the database"); log.info("db", "Closing the database");
while (true) { while (true) {
Database.noReject = true; Database.noReject = true;
@ -388,10 +388,10 @@ class Database {
if (Database.noReject) { if (Database.noReject) {
break; break;
} else { } 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); process.removeListener("unhandledRejection", listener);
} }
@ -403,7 +403,7 @@ class Database {
*/ */
static backup(version) { static backup(version) {
if (! this.backupPath) { if (! this.backupPath) {
console.info("Backing up the database"); log.info("db", "Backing up the database");
this.backupPath = this.dataDir + "kuma.db.bak" + version; this.backupPath = this.dataDir + "kuma.db.bak" + version;
fs.copyFileSync(Database.path, this.backupPath); fs.copyFileSync(Database.path, this.backupPath);
@ -426,7 +426,7 @@ class Database {
*/ */
static restore() { static restore() {
if (this.backupPath) { 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 shmPath = Database.path + "-shm";
const walPath = Database.path + "-wal"; const walPath = Database.path + "-wal";
@ -445,7 +445,7 @@ class Database {
fs.unlinkSync(walPath); fs.unlinkSync(walPath);
} }
} catch (e) { } 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); process.exit(1);
} }
@ -461,14 +461,14 @@ class Database {
} }
} else { } else {
console.log("Nothing to restore"); log.info("db", "Nothing to restore");
} }
} }
static getSize() { static getSize() {
debug("Database.getSize()"); log.debug("db", "Database.getSize()");
let stats = fs.statSync(Database.path); let stats = fs.statSync(Database.path);
debug(stats); log.debug("db", stats);
return stats.size; return stats.size;
} }

@ -3,6 +3,7 @@
Modified with 0 dependencies Modified with 0 dependencies
*/ */
let fs = require("fs"); let fs = require("fs");
const { log } = require("../src/util");
let ImageDataURI = (() => { let ImageDataURI = (() => {
@ -14,7 +15,7 @@ let ImageDataURI = (() => {
*/ */
function decode(dataURI) { function decode(dataURI) {
if (!/data:image\//.test(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; return null;
} }
@ -35,7 +36,7 @@ let ImageDataURI = (() => {
*/ */
function encode(data, mediaType) { function encode(data, mediaType) {
if (!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; return null;
} }

@ -1,6 +1,7 @@
const path = require("path"); const path = require("path");
const Bree = require("bree"); const Bree = require("bree");
const { SHARE_ENV } = require("worker_threads"); const { SHARE_ENV } = require("worker_threads");
const { log } = require("../src/util");
let bree; let bree;
const jobs = [ const jobs = [
{ {
@ -18,7 +19,7 @@ const initBackgroundJobs = function (args) {
workerData: args, workerData: args,
}, },
workerMessageHandler: (message) => { workerMessageHandler: (message) => {
console.log("[Background Job]:", message); log.info("jobs", message);
} }
}); });

@ -6,7 +6,7 @@ dayjs.extend(utc);
dayjs.extend(timezone); dayjs.extend(timezone);
const axios = require("axios"); const axios = require("axios");
const { Prometheus } = require("../prometheus"); 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 { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, errorLog } = require("../util-server");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const { BeanModel } = require("redbean-node/dist/bean-model"); const { BeanModel } = require("redbean-node/dist/bean-model");
@ -193,7 +193,7 @@ class Monitor extends BeanModel {
rejectUnauthorized: !this.getIgnoreTls(), rejectUnauthorized: !this.getIgnoreTls(),
}; };
debug(`[${this.name}] Prepare Options for axios`); log.debug("monitor", `[${this.name}] Prepare Options for axios`);
const options = { const options = {
url: this.url, url: this.url,
@ -230,8 +230,8 @@ class Monitor extends BeanModel {
options.httpsAgent = new https.Agent(httpsAgentOptions); options.httpsAgent = new https.Agent(httpsAgentOptions);
} }
debug(`[${this.name}] Axios Options: ${JSON.stringify(options)}`); log.debug("monitor", `[${this.name}] Axios Options: ${JSON.stringify(options)}`);
debug(`[${this.name}] Axios Request`); log.debug("monitor", `[${this.name}] Axios Request`);
let res = await axios.request(options); let res = await axios.request(options);
bean.msg = `${res.status} - ${res.statusText}`; bean.msg = `${res.status} - ${res.statusText}`;
@ -240,29 +240,30 @@ class Monitor extends BeanModel {
// Check certificate if https is used // Check certificate if https is used
let certInfoStartTime = dayjs().valueOf(); let certInfoStartTime = dayjs().valueOf();
if (this.getUrl()?.protocol === "https:") { if (this.getUrl()?.protocol === "https:") {
debug(`[${this.name}] Check cert`); log.debug("monitor", `[${this.name}] Check cert`);
try { try {
let tlsInfoObject = checkCertificate(res); let tlsInfoObject = checkCertificate(res);
tlsInfo = await this.updateTlsInfo(tlsInfoObject); tlsInfo = await this.updateTlsInfo(tlsInfoObject);
if (!this.getIgnoreTls() && this.isEnabledExpiryNotification()) { if (!this.getIgnoreTls() && this.isEnabledExpiryNotification()) {
debug(`[${this.name}] call sendCertNotification`); log.debug("monitor", `[${this.name}] call sendCertNotification`);
await this.sendCertNotification(tlsInfoObject); await this.sendCertNotification(tlsInfoObject);
} }
} catch (e) { } catch (e) {
if (e.message !== "No TLS certificate in response") { 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") { 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) { 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") { if (this.type === "http") {
@ -342,7 +343,7 @@ class Monitor extends BeanModel {
time time
]); ]);
debug("heartbeatCount" + heartbeatCount + " " + time); log.debug("monitor", "heartbeatCount" + heartbeatCount + " " + time);
if (heartbeatCount <= 0) { if (heartbeatCount <= 0) {
// Fix #922, since previous heartbeat could be inserted by api, it should get from database // 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); let isImportant = Monitor.isImportantBeat(isFirstBeat, previousBeat?.status, bean.status);
// Mark as important if status changed, ignore pending pings, // Mark as important if status changed, ignore pending pings,
@ -434,11 +435,11 @@ class Monitor extends BeanModel {
if (isImportant) { if (isImportant) {
bean.important = true; bean.important = true;
debug(`[${this.name}] sendNotification`); log.debug("monitor", `[${this.name}] sendNotification`);
await Monitor.sendNotification(isFirstBeat, this, bean); await Monitor.sendNotification(isFirstBeat, this, bean);
// Clear Status Page Cache // Clear Status Page Cache
debug(`[${this.name}] apicache clear`); log.debug("monitor", `[${this.name}] apicache clear`);
apicache.clear(); apicache.clear();
} else { } else {
@ -446,33 +447,33 @@ class Monitor extends BeanModel {
} }
if (bean.status === UP) { 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) { } else if (bean.status === PENDING) {
if (this.retryInterval > 0) { if (this.retryInterval > 0) {
beatInterval = this.retryInterval; 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 { } 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()); io.to(this.user_id).emit("heartbeat", bean.toJSON());
Monitor.sendStats(io, this.id, this.user_id); Monitor.sendStats(io, this.id, this.user_id);
debug(`[${this.name}] Store`); log.debug("monitor", `[${this.name}] Store`);
await R.store(bean); await R.store(bean);
debug(`[${this.name}] prometheus.update`); log.debug("monitor", `[${this.name}] prometheus.update`);
prometheus.update(bean, tlsInfo); prometheus.update(bean, tlsInfo);
previousBeat = bean; previousBeat = bean;
if (! this.isStop) { 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); this.heartbeatInterval = setTimeout(safeBeat, beatInterval * 1000);
} else { } 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) { } catch (e) {
console.trace(e); console.trace(e);
errorLog(e, false); 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) { 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); this.heartbeatInterval = setTimeout(safeBeat, this.interval * 1000);
} }
} }
@ -533,41 +534,41 @@ class Monitor extends BeanModel {
* @returns {Promise<object>} * @returns {Promise<object>}
*/ */
async updateTlsInfo(checkCertificateResult) { 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, this.id,
]); ]);
if (tls_info_bean == null) { if (tlsInfoBean == null) {
tls_info_bean = R.dispense("monitor_tls_info"); tlsInfoBean = R.dispense("monitor_tls_info");
tls_info_bean.monitor_id = this.id; tlsInfoBean.monitor_id = this.id;
} else { } else {
// Clear sent history if the cert changed. // Clear sent history if the cert changed.
try { 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; let isValidObjects = oldCertInfo && oldCertInfo.certInfo && checkCertificateResult && checkCertificateResult.certInfo;
if (isValidObjects) { if (isValidObjects) {
if (oldCertInfo.certInfo.fingerprint256 !== checkCertificateResult.certInfo.fingerprint256) { 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 = ?", [ await R.exec("DELETE FROM notification_sent_history WHERE type = 'certificate' AND monitor_id = ?", [
this.id this.id
]); ]);
} else { } else {
debug("No need to reset sent_history"); log.debug("monitor", "No need to reset sent_history");
debug(oldCertInfo.certInfo.fingerprint256); log.debug("monitor", oldCertInfo.certInfo.fingerprint256);
debug(checkCertificateResult.certInfo.fingerprint256); log.debug("monitor", checkCertificateResult.certInfo.fingerprint256);
} }
} else { } else {
debug("Not valid object"); log.debug("monitor", "Not valid object");
} }
} catch (e) { } } catch (e) { }
} }
tls_info_bean.info_json = JSON.stringify(checkCertificateResult); tlsInfoBean.info_json = JSON.stringify(checkCertificateResult);
await R.store(tls_info_bean); await R.store(tlsInfoBean);
return checkCertificateResult; return checkCertificateResult;
} }
@ -581,7 +582,7 @@ class Monitor extends BeanModel {
await Monitor.sendUptime(24 * 30, io, monitorID, userID); await Monitor.sendUptime(24 * 30, io, monitorID, userID);
await Monitor.sendCertInfo(io, monitorID, userID); await Monitor.sendCertInfo(io, monitorID, userID);
} else { } 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");
} }
} }
@ -608,11 +609,11 @@ class Monitor extends BeanModel {
} }
static async sendCertInfo(io, monitorID, userID) { static async sendCertInfo(io, monitorID, userID) {
let tls_info = await R.findOne("monitor_tls_info", "monitor_id = ?", [ let tlsInfo = await R.findOne("monitor_tls_info", "monitor_id = ?", [
monitorID, monitorID,
]); ]);
if (tls_info != null) { if (tlsInfo != null) {
io.to(userID).emit("certInfo", monitorID, tls_info.info_json); io.to(userID).emit("certInfo", monitorID, tlsInfo.info_json);
} }
} }
@ -728,8 +729,8 @@ class Monitor extends BeanModel {
try { try {
await Notification.send(JSON.parse(notification.config), msg, await monitor.toJSON(), bean.toJSON()); await Notification.send(JSON.parse(notification.config), msg, await monitor.toJSON(), bean.toJSON());
} catch (e) { } catch (e) {
console.error("Cannot send notification to " + notification.name); log.error("monitor", "Cannot send notification to " + notification.name);
console.log(e); log.error("monitor", e);
} }
} }
} }
@ -746,7 +747,7 @@ class Monitor extends BeanModel {
if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) { if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) {
const notificationList = await Monitor.getNotificationList(this); const notificationList = await Monitor.getNotificationList(this);
debug("call sendCertNotificationByTargetDays"); log.debug("monitor", "call sendCertNotificationByTargetDays");
await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 21, notificationList); await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 21, notificationList);
await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 14, notificationList); await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 14, notificationList);
await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 7, notificationList); await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 7, notificationList);
@ -756,7 +757,7 @@ class Monitor extends BeanModel {
async sendCertNotificationByTargetDays(daysRemaining, targetDays, notificationList) { async sendCertNotificationByTargetDays(daysRemaining, targetDays, notificationList) {
if (daysRemaining > targetDays) { if (daysRemaining > targetDays) {
debug(`No need to send cert notification. ${daysRemaining} > ${targetDays}`); log.debug("monitor", `No need to send cert notification. ${daysRemaining} > ${targetDays}`);
return; return;
} }
@ -770,21 +771,21 @@ class Monitor extends BeanModel {
// Sent already, no need to send again // Sent already, no need to send again
if (row) { if (row) {
debug("Sent already, no need to send again"); log.debug("monitor", "Sent already, no need to send again");
return; return;
} }
let sent = false; let sent = false;
debug("Send certificate notification"); log.debug("monitor", "Send certificate notification");
for (let notification of notificationList) { for (let notification of notificationList) {
try { 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`); await Notification.send(JSON.parse(notification.config), `[${this.name}][${this.url}] Certificate will be expired in ${daysRemaining} days`);
sent = true; sent = true;
} catch (e) { } catch (e) {
console.error("Cannot send cert notification to " + notification.name); log.error("monitor", "Cannot send cert notification to " + notification.name);
console.error(e); log.error("monitor", e);
} }
} }
@ -796,7 +797,7 @@ class Monitor extends BeanModel {
]); ]);
} }
} else { } else {
debug("No notification, no need to send cert notification"); log.debug("monitor", "No notification, no need to send cert notification");
} }
} }

@ -1,12 +1,12 @@
const NotificationProvider = require("./notification-provider"); const NotificationProvider = require("./notification-provider");
const child_process = require("child_process"); const childProcess = require("child_process");
class Apprise extends NotificationProvider { class Apprise extends NotificationProvider {
name = "apprise"; name = "apprise";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let s = child_process.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL]) let s = childProcess.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL]);
let output = (s.stdout) ? s.stdout.toString() : "ERROR: maybe apprise not found"; let output = (s.stdout) ? s.stdout.toString() : "ERROR: maybe apprise not found";
@ -16,7 +16,7 @@ class Apprise extends NotificationProvider {
return "Sent Successfully"; return "Sent Successfully";
} }
throw new Error(output) throw new Error(output);
} else { } else {
return "No output from apprise"; return "No output from apprise";
} }

@ -21,31 +21,26 @@ class Bark extends NotificationProvider {
name = "Bark"; name = "Bark";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
try { let barkEndpoint = notification.barkEndpoint;
var barkEndpoint = notification.barkEndpoint;
// check if the endpoint has a "/" suffix, if so, delete it first // check if the endpoint has a "/" suffix, if so, delete it first
if (barkEndpoint.endsWith("/")) { if (barkEndpoint.endsWith("/")) {
barkEndpoint = barkEndpoint.substring(0, barkEndpoint.length - 1); 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"] == DOWN) { if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] == UP) {
let title = "UptimeKuma Monitor Down"; let title = "UptimeKuma Monitor Up";
return await this.postNotification(title, msg, barkEndpoint); return await this.postNotification(title, msg, barkEndpoint);
} }
if (msg != null) { if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] == DOWN) {
let title = "UptimeKuma Message"; let title = "UptimeKuma Monitor Down";
return await this.postNotification(title, msg, barkEndpoint); return await this.postNotification(title, msg, barkEndpoint);
} }
} catch (error) { if (msg != null) {
throw error; let title = "UptimeKuma Message";
return await this.postNotification(title, msg, barkEndpoint);
} }
} }

@ -12,7 +12,7 @@ class ClickSendSMS extends NotificationProvider {
let config = { let config = {
headers: { headers: {
"Content-Type": "application/json", "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", "Accept": "text/json",
} }
}; };

@ -17,8 +17,8 @@ class Discord extends NotificationProvider {
let discordtestdata = { let discordtestdata = {
username: discordDisplayName, username: discordDisplayName,
content: msg, content: msg,
} };
await axios.post(notification.discordWebhookUrl, discordtestdata) await axios.post(notification.discordWebhookUrl, discordtestdata);
return okMsg; return okMsg;
} }
@ -61,13 +61,13 @@ class Discord extends NotificationProvider {
}, },
], ],
}], }],
} };
if (notification.discordPrefixMessage) { if (notification.discordPrefixMessage) {
discorddowndata.content = notification.discordPrefixMessage; discorddowndata.content = notification.discordPrefixMessage;
} }
await axios.post(notification.discordWebhookUrl, discorddowndata) await axios.post(notification.discordWebhookUrl, discorddowndata);
return okMsg; return okMsg;
} else if (heartbeatJSON["status"] == UP) { } else if (heartbeatJSON["status"] == UP) {
@ -96,17 +96,17 @@ class Discord extends NotificationProvider {
}, },
], ],
}], }],
} };
if (notification.discordPrefixMessage) { if (notification.discordPrefixMessage) {
discordupdata.content = notification.discordPrefixMessage; discordupdata.content = notification.discordPrefixMessage;
} }
await axios.post(notification.discordWebhookUrl, discordupdata) await axios.post(notification.discordWebhookUrl, discordupdata);
return okMsg; return okMsg;
} }
} catch (error) { } catch (error) {
this.throwGeneralAxiosError(error) this.throwGeneralAxiosError(error);
} }
} }

@ -13,11 +13,11 @@ class GoogleChat extends NotificationProvider {
try { try {
// Google Chat message formatting: https://developers.google.com/chat/api/guides/message-formats/basic // Google Chat message formatting: https://developers.google.com/chat/api/guides/message-formats/basic
let textMsg = '' let textMsg = "";
if (heartbeatJSON && heartbeatJSON.status === UP) { if (heartbeatJSON && heartbeatJSON.status === UP) {
textMsg = `✅ Application is back online\n`; textMsg = "✅ Application is back online\n";
} else if (heartbeatJSON && heartbeatJSON.status === DOWN) { } else if (heartbeatJSON && heartbeatJSON.status === DOWN) {
textMsg = `🔴 Application went down\n`; textMsg = "🔴 Application went down\n";
} }
if (monitorJSON && monitorJSON.name) { if (monitorJSON && monitorJSON.name) {

@ -15,7 +15,7 @@ class Gotify extends NotificationProvider {
"message": msg, "message": msg,
"priority": notification.gotifyPriority || 8, "priority": notification.gotifyPriority || 8,
"title": "Uptime-Kuma", "title": "Uptime-Kuma",
}) });
return okMsg; return okMsg;

@ -25,8 +25,8 @@ class Line extends NotificationProvider {
"text": "Test Successful!" "text": "Test Successful!"
} }
] ]
} };
await axios.post(lineAPIUrl, testMessage, config) await axios.post(lineAPIUrl, testMessage, config);
} else if (heartbeatJSON["status"] == DOWN) { } else if (heartbeatJSON["status"] == DOWN) {
let downMessage = { let downMessage = {
"to": notification.lineUserID, "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"] "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) { } else if (heartbeatJSON["status"] == UP) {
let upMessage = { let upMessage = {
"to": notification.lineUserID, "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"] "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; return okMsg;
} catch (error) { } catch (error) {
this.throwGeneralAxiosError(error) this.throwGeneralAxiosError(error);
} }
} }
} }

@ -8,15 +8,15 @@ class LunaSea extends NotificationProvider {
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully."; 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 { try {
if (heartbeatJSON == null) { if (heartbeatJSON == null) {
let testdata = { let testdata = {
"title": "Uptime Kuma Alert", "title": "Uptime Kuma Alert",
"body": "Testing Successful.", "body": "Testing Successful.",
} };
await axios.post(lunaseadevice, testdata) await axios.post(lunaseadevice, testdata);
return okMsg; return okMsg;
} }
@ -24,8 +24,8 @@ class LunaSea extends NotificationProvider {
let downdata = { let downdata = {
"title": "UptimeKuma Alert: " + monitorJSON["name"], "title": "UptimeKuma Alert: " + monitorJSON["name"],
"body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], "body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
} };
await axios.post(lunaseadevice, downdata) await axios.post(lunaseadevice, downdata);
return okMsg; return okMsg;
} }
@ -33,13 +33,13 @@ class LunaSea extends NotificationProvider {
let updata = { let updata = {
"title": "UptimeKuma Alert: " + monitorJSON["name"], "title": "UptimeKuma Alert: " + monitorJSON["name"],
"body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], "body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
} };
await axios.post(lunaseadevice, updata) await axios.post(lunaseadevice, updata);
return okMsg; return okMsg;
} }
} catch (error) { } catch (error) {
this.throwGeneralAxiosError(error) this.throwGeneralAxiosError(error);
} }
} }

@ -1,7 +1,7 @@
const NotificationProvider = require("./notification-provider"); const NotificationProvider = require("./notification-provider");
const axios = require("axios"); const axios = require("axios");
const Crypto = require("crypto"); const Crypto = require("crypto");
const { debug } = require("../../src/util"); const { log } = require("../../src/util");
class Matrix extends NotificationProvider { class Matrix extends NotificationProvider {
name = "matrix"; name = "matrix";
@ -17,11 +17,11 @@ class Matrix extends NotificationProvider {
.slice(0, size) .slice(0, size)
); );
debug("Random String: " + randomString); log.debug("notification", "Random String: " + randomString);
const roomId = encodeURIComponent(notification.internalRoomId); const roomId = encodeURIComponent(notification.internalRoomId);
debug("Matrix Room ID: " + roomId); log.debug("notification", "Matrix Room ID: " + roomId);
try { try {
let config = { let config = {

@ -25,11 +25,11 @@ class NotificationProvider {
if (typeof error.response.data === "string") { if (typeof error.response.data === "string") {
msg += error.response.data; msg += error.response.data;
} else { } else {
msg += JSON.stringify(error.response.data) msg += JSON.stringify(error.response.data);
} }
} }
throw new Error(msg) throw new Error(msg);
} }
} }

@ -30,7 +30,7 @@ class Octopush extends NotificationProvider {
"purpose": "alert", "purpose": "alert",
"sender": notification.octopushSenderName "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) { } else if (notification.octopushVersion == 1) {
let data = { let data = {
"user_login": notification.octopushDMLogin, "user_login": notification.octopushDMLogin,
@ -49,7 +49,7 @@ class Octopush extends NotificationProvider {
}, },
params: data 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 { } else {
throw new Error("Unknown Octopush version!"); throw new Error("Unknown Octopush version!");
} }

@ -0,0 +1,45 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
class OneBot extends NotificationProvider {
name = "OneBot";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully.";
try {
let httpAddr = notification.httpAddr;
if (!httpAddr.startsWith("http")) {
httpAddr = "http://" + httpAddr;
}
if (!httpAddr.endsWith("/")) {
httpAddr += "/";
}
let onebotAPIUrl = httpAddr + "send_msg";
let config = {
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + notification.accessToken,
}
};
let pushText = "UptimeKuma Alert: " + msg;
let data = {
"auto_escape": true,
"message": pushText,
};
if (notification.msgType == "group") {
data["message_type"] = "group";
data["group_id"] = notification.recieverId;
} else {
data["message_type"] = "private";
data["user_id"] = notification.recieverId;
}
await axios.post(onebotAPIUrl, data, config);
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = OneBot;

@ -12,7 +12,7 @@ class PromoSMS extends NotificationProvider {
let config = { let config = {
headers: { headers: {
"Content-Type": "application/json", "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", "Accept": "text/json",
} }
}; };

@ -23,26 +23,26 @@ class Pushbullet extends NotificationProvider {
"type": "note", "type": "note",
"title": "Uptime Kuma Alert", "title": "Uptime Kuma Alert",
"body": "Testing Successful.", "body": "Testing Successful.",
} };
await axios.post(pushbulletUrl, testdata, config) await axios.post(pushbulletUrl, testdata, config);
} else if (heartbeatJSON["status"] == DOWN) { } else if (heartbeatJSON["status"] == DOWN) {
let downdata = { let downdata = {
"type": "note", "type": "note",
"title": "UptimeKuma Alert: " + monitorJSON["name"], "title": "UptimeKuma Alert: " + monitorJSON["name"],
"body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], "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) { } else if (heartbeatJSON["status"] == UP) {
let updata = { let updata = {
"type": "note", "type": "note",
"title": "UptimeKuma Alert: " + monitorJSON["name"], "title": "UptimeKuma Alert: " + monitorJSON["name"],
"body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], "body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
} };
await axios.post(pushbulletUrl, updata, config) await axios.post(pushbulletUrl, updata, config);
} }
return okMsg; return okMsg;
} catch (error) { } catch (error) {
this.throwGeneralAxiosError(error) this.throwGeneralAxiosError(error);
} }
} }
} }

@ -0,0 +1,52 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { DOWN, UP } = require("../../src/util");
class PushDeer extends NotificationProvider {
name = "PushDeer";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let okMsg = "Sent Successfully.";
let pushdeerlink = "https://api2.pushdeer.com/message/push";
let valid = msg != null && monitorJSON != null && heartbeatJSON != null;
let title;
if (valid && heartbeatJSON.status == UP) {
title = "## Uptime Kuma: " + monitorJSON.name + " up";
} else if (valid && heartbeatJSON.status == DOWN) {
title = "## Uptime Kuma: " + monitorJSON.name + " down";
} else {
title = "## Uptime Kuma Message";
}
let data = {
"pushkey": notification.pushdeerKey,
"text": title,
"desp": msg.replace(/\n/g, "\n\n"),
"type": "markdown",
};
try {
let res = await axios.post(pushdeerlink, data);
if ("error" in res.data) {
let error = res.data.error;
this.throwGeneralAxiosError(error);
}
if (res.data.content.result.length === 0) {
let error = "Invalid PushDeer key";
this.throwGeneralAxiosError(error);
} else if (JSON.parse(res.data.content.result[0]).success != "ok") {
let error = "Unknown error";
this.throwGeneralAxiosError(error);
}
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = PushDeer;

@ -19,10 +19,10 @@ class Pushy extends NotificationProvider {
"badge": 1, "badge": 1,
"sound": "ping.aiff" "sound": "ping.aiff"
} }
}) });
return okMsg; return okMsg;
} catch (error) { } catch (error) {
this.throwGeneralAxiosError(error) this.throwGeneralAxiosError(error);
} }
} }
} }

@ -2,7 +2,7 @@ const NotificationProvider = require("./notification-provider");
const axios = require("axios"); const axios = require("axios");
const Slack = require("./slack"); const Slack = require("./slack");
const { setting } = require("../util-server"); const { setting } = require("../util-server");
const { getMonitorRelativeURL, UP, DOWN } = require("../../src/util"); const { getMonitorRelativeURL, DOWN } = require("../../src/util");
class RocketChat extends NotificationProvider { class RocketChat extends NotificationProvider {

@ -16,10 +16,10 @@ class Signal extends NotificationProvider {
}; };
let config = {}; let config = {};
await axios.post(notification.signalURL, data, config) await axios.post(notification.signalURL, data, config);
return okMsg; return okMsg;
} catch (error) { } catch (error) {
this.throwGeneralAxiosError(error) this.throwGeneralAxiosError(error);
} }
} }
} }

@ -1,6 +1,6 @@
const nodemailer = require("nodemailer"); const nodemailer = require("nodemailer");
const NotificationProvider = require("./notification-provider"); const NotificationProvider = require("./notification-provider");
const { DOWN, UP } = require("../../src/util"); const { DOWN } = require("../../src/util");
class SMTP extends NotificationProvider { class SMTP extends NotificationProvider {

@ -12,10 +12,10 @@ class TechulusPush extends NotificationProvider {
await axios.post(`https://push.techulus.com/api/v1/notify/${notification.pushAPIKey}`, { await axios.post(`https://push.techulus.com/api/v1/notify/${notification.pushAPIKey}`, {
"title": "Uptime-Kuma", "title": "Uptime-Kuma",
"body": msg, "body": msg,
}) });
return okMsg; return okMsg;
} catch (error) { } catch (error) {
this.throwGeneralAxiosError(error) this.throwGeneralAxiosError(error);
} }
} }
} }

@ -14,12 +14,12 @@ class Telegram extends NotificationProvider {
chat_id: notification.telegramChatID, chat_id: notification.telegramChatID,
text: msg, text: msg,
}, },
}) });
return okMsg; return okMsg;
} catch (error) { } catch (error) {
let msg = (error.response.data.description) ? error.response.data.description : "Error without description" let msg = (error.response.data.description) ? error.response.data.description : "Error without description";
throw new Error(msg) throw new Error(msg);
} }
} }
} }

@ -24,17 +24,17 @@ class Webhook extends NotificationProvider {
config = { config = {
headers: finalData.getHeaders(), headers: finalData.getHeaders(),
} };
} else { } else {
finalData = data; finalData = data;
} }
await axios.post(notification.webhookURL, finalData, config) await axios.post(notification.webhookURL, finalData, config);
return okMsg; return okMsg;
} catch (error) { } catch (error) {
this.throwGeneralAxiosError(error) this.throwGeneralAxiosError(error);
} }
} }

@ -26,7 +26,7 @@ class WeCom extends NotificationProvider {
composeMessage(heartbeatJSON, msg) { composeMessage(heartbeatJSON, msg) {
let title; let title;
if (msg != null && heartbeatJSON != null && heartbeatJSON['status'] == UP) { if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] == UP) {
title = "UptimeKuma Monitor Up"; title = "UptimeKuma Monitor Up";
} }
if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] == DOWN) { if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] == DOWN) {

@ -24,19 +24,22 @@ const Feishu = require("./notification-providers/feishu");
const AliyunSms = require("./notification-providers/aliyun-sms"); const AliyunSms = require("./notification-providers/aliyun-sms");
const DingDing = require("./notification-providers/dingding"); const DingDing = require("./notification-providers/dingding");
const Bark = require("./notification-providers/bark"); const Bark = require("./notification-providers/bark");
const { log } = require("../src/util");
const SerwerSMS = require("./notification-providers/serwersms"); const SerwerSMS = require("./notification-providers/serwersms");
const Stackfield = require("./notification-providers/stackfield"); const Stackfield = require("./notification-providers/stackfield");
const WeCom = require("./notification-providers/wecom"); const WeCom = require("./notification-providers/wecom");
const GoogleChat = require("./notification-providers/google-chat"); const GoogleChat = require("./notification-providers/google-chat");
const Gorush = require("./notification-providers/gorush"); const Gorush = require("./notification-providers/gorush");
const Alerta = require("./notification-providers/alerta"); const Alerta = require("./notification-providers/alerta");
const OneBot = require("./notification-providers/onebot");
const PushDeer = require("./notification-providers/pushdeer");
class Notification { class Notification {
providerList = {}; providerList = {};
static init() { static init() {
console.log("Prepare Notification Providers"); log.info("notification", "Prepare Notification Providers");
this.providerList = {}; this.providerList = {};
@ -72,6 +75,8 @@ class Notification {
new GoogleChat(), new GoogleChat(),
new Gorush(), new Gorush(),
new Alerta(), new Alerta(),
new OneBot(),
new PushDeer(),
]; ];
for (let item of list) { for (let item of list) {
@ -104,27 +109,27 @@ class Notification {
} }
static async save(notification, notificationID, userID) { static async save(notification, notificationID, userID) {
let bean let bean;
if (notificationID) { if (notificationID) {
bean = await R.findOne("notification", " id = ? AND user_id = ? ", [ bean = await R.findOne("notification", " id = ? AND user_id = ? ", [
notificationID, notificationID,
userID, userID,
]) ]);
if (! bean) { if (! bean) {
throw new Error("notification not found") throw new Error("notification not found");
} }
} else { } else {
bean = R.dispense("notification") bean = R.dispense("notification");
} }
bean.name = notification.name; bean.name = notification.name;
bean.user_id = userID; bean.user_id = userID;
bean.config = JSON.stringify(notification); bean.config = JSON.stringify(notification);
bean.is_default = notification.isDefault || false; bean.is_default = notification.isDefault || false;
await R.store(bean) await R.store(bean);
if (notification.applyExisting) { if (notification.applyExisting) {
await applyNotificationEveryMonitor(bean.id, userID); await applyNotificationEveryMonitor(bean.id, userID);
@ -137,13 +142,13 @@ class Notification {
let bean = await R.findOne("notification", " id = ? AND user_id = ? ", [ let bean = await R.findOne("notification", " id = ? AND user_id = ? ", [
notificationID, notificationID,
userID, userID,
]) ]);
if (! bean) { if (! bean) {
throw new Error("notification not found") throw new Error("notification not found");
} }
await R.trash(bean) await R.trash(bean);
} }
static checkApprise() { static checkApprise() {
@ -170,17 +175,17 @@ async function applyNotificationEveryMonitor(notificationID, userID) {
let checkNotification = await R.findOne("monitor_notification", " monitor_id = ? AND notification_id = ? ", [ let checkNotification = await R.findOne("monitor_notification", " monitor_id = ? AND notification_id = ? ", [
monitors[i].id, monitors[i].id,
notificationID, notificationID,
]) ]);
if (! checkNotification) { if (! checkNotification) {
let relation = R.dispense("monitor_notification"); let relation = R.dispense("monitor_notification");
relation.monitor_id = monitors[i].id; relation.monitor_id = monitors[i].id;
relation.notification_id = notificationID; relation.notification_id = notificationID;
await R.store(relation) await R.store(relation);
} }
} }
} }
module.exports = { module.exports = {
Notification, Notification,
} };

@ -4,20 +4,20 @@ const saltRounds = 10;
exports.generate = function (password) { exports.generate = function (password) {
return bcrypt.hashSync(password, saltRounds); return bcrypt.hashSync(password, saltRounds);
} };
exports.verify = function (password, hash) { exports.verify = function (password, hash) {
if (isSHA1(hash)) { if (isSHA1(hash)) {
return passwordHashOld.verify(password, hash) return passwordHashOld.verify(password, hash);
} }
return bcrypt.compareSync(password, hash); return bcrypt.compareSync(password, hash);
} };
function isSHA1(hash) { function isSHA1(hash) {
return (typeof hash === "string" && hash.startsWith("sha1")) return (typeof hash === "string" && hash.startsWith("sha1"));
} }
exports.needRehash = function (hash) { exports.needRehash = function (hash) {
return isSHA1(hash); return isSHA1(hash);
} };

@ -1,4 +1,5 @@
const PrometheusClient = require("prom-client"); const PrometheusClient = require("prom-client");
const { log } = require("../src/util");
const commonLabels = [ const commonLabels = [
"monitor_name", "monitor_name",
@ -8,24 +9,24 @@ const commonLabels = [
"monitor_port", "monitor_port",
]; ];
const monitor_cert_days_remaining = new PrometheusClient.Gauge({ const monitorCertDaysRemaining = new PrometheusClient.Gauge({
name: "monitor_cert_days_remaining", name: "monitor_cert_days_remaining",
help: "The number of days remaining until the certificate expires", help: "The number of days remaining until the certificate expires",
labelNames: commonLabels labelNames: commonLabels
}); });
const monitor_cert_is_valid = new PrometheusClient.Gauge({ const monitorCertIsValid = new PrometheusClient.Gauge({
name: "monitor_cert_is_valid", name: "monitor_cert_is_valid",
help: "Is the certificate still valid? (1 = Yes, 0= No)", help: "Is the certificate still valid? (1 = Yes, 0= No)",
labelNames: commonLabels labelNames: commonLabels
}); });
const monitor_response_time = new PrometheusClient.Gauge({ const monitorResponseTime = new PrometheusClient.Gauge({
name: "monitor_response_time", name: "monitor_response_time",
help: "Monitor Response Time (ms)", help: "Monitor Response Time (ms)",
labelNames: commonLabels labelNames: commonLabels
}); });
const monitor_status = new PrometheusClient.Gauge({ const monitorStatus = new PrometheusClient.Gauge({
name: "monitor_status", name: "monitor_status",
help: "Monitor Status (1 = UP, 0= DOWN)", help: "Monitor Status (1 = UP, 0= DOWN)",
labelNames: commonLabels labelNames: commonLabels
@ -48,50 +49,54 @@ class Prometheus {
if (typeof tlsInfo !== "undefined") { if (typeof tlsInfo !== "undefined") {
try { try {
let is_valid = 0; let isValid = 0;
if (tlsInfo.valid == true) { if (tlsInfo.valid == true) {
is_valid = 1; isValid = 1;
} else { } else {
is_valid = 0; isValid = 0;
} }
monitor_cert_is_valid.set(this.monitorLabelValues, is_valid); monitorCertIsValid.set(this.monitorLabelValues, isValid);
} catch (e) { } catch (e) {
console.error(e); log.error("prometheus", "Caught error");
log.error("prometheus", e);
} }
try { try {
if (tlsInfo.certInfo != null) { if (tlsInfo.certInfo != null) {
monitor_cert_days_remaining.set(this.monitorLabelValues, tlsInfo.certInfo.daysRemaining); monitorCertDaysRemaining.set(this.monitorLabelValues, tlsInfo.certInfo.daysRemaining);
} }
} catch (e) { } catch (e) {
console.error(e); log.error("prometheus", "Caught error");
log.error("prometheus", e);
} }
} }
try { try {
monitor_status.set(this.monitorLabelValues, heartbeat.status); monitorStatus.set(this.monitorLabelValues, heartbeat.status);
} catch (e) { } catch (e) {
console.error(e); log.error("prometheus", "Caught error");
log.error("prometheus", e);
} }
try { try {
if (typeof heartbeat.ping === "number") { if (typeof heartbeat.ping === "number") {
monitor_response_time.set(this.monitorLabelValues, heartbeat.ping); monitorResponseTime.set(this.monitorLabelValues, heartbeat.ping);
} else { } else {
// Is it good? // Is it good?
monitor_response_time.set(this.monitorLabelValues, -1); monitorResponseTime.set(this.monitorLabelValues, -1);
} }
} catch (e) { } catch (e) {
console.error(e); log.error("prometheus", "Caught error");
log.error("prometheus", e);
} }
} }
remove() { remove() {
try { try {
monitor_cert_days_remaining.remove(this.monitorLabelValues); monitorCertDaysRemaining.remove(this.monitorLabelValues);
monitor_cert_is_valid.remove(this.monitorLabelValues); monitorCertIsValid.remove(this.monitorLabelValues);
monitor_response_time.remove(this.monitorLabelValues); monitorResponseTime.remove(this.monitorLabelValues);
monitor_status.remove(this.monitorLabelValues); monitorStatus.remove(this.monitorLabelValues);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }

@ -1,5 +1,5 @@
const { RateLimiter } = require("limiter"); const { RateLimiter } = require("limiter");
const { debug } = require("../src/util"); const { log } = require("../src/util");
class KumaRateLimiter { class KumaRateLimiter {
constructor(config) { constructor(config) {
@ -9,7 +9,7 @@ class KumaRateLimiter {
async pass(callback, num = 1) { async pass(callback, num = 1) {
const remainingRequests = await this.removeTokens(num); const remainingRequests = await this.removeTokens(num);
debug("Rate Limit (remainingRequests):" + remainingRequests); log.info("rate-limit", "remaining requests: " + remainingRequests);
if (remainingRequests < 0) { if (remainingRequests < 0) {
if (callback) { if (callback) {
callback({ callback({

@ -1,11 +1,11 @@
let express = require("express"); let express = require("express");
const { allowDevAllOrigin, getSettings, setting } = require("../util-server"); const { allowDevAllOrigin } = require("../util-server");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const server = require("../server"); const server = require("../server");
const apicache = require("../modules/apicache"); const apicache = require("../modules/apicache");
const Monitor = require("../model/monitor"); const Monitor = require("../model/monitor");
const dayjs = require("dayjs"); const dayjs = require("dayjs");
const { UP, flipStatus, debug } = require("../../src/util"); const { UP, flipStatus, log } = require("../../src/util");
const StatusPage = require("../model/status_page"); const StatusPage = require("../model/status_page");
let router = express.Router(); 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"); duration = dayjs(bean.time).diff(dayjs(previousHeartbeat.time), "second");
} }
debug("PreviousStatus: " + previousStatus); log.debug("router", "PreviousStatus: " + previousStatus);
debug("Current Status: " + status); log.debug("router", "Current Status: " + status);
bean.important = Monitor.isImportantBeat(isFirstBeat, previousStatus, status); bean.important = Monitor.isImportantBeat(isFirstBeat, previousStatus, status);
bean.monitor_id = monitor.id; bean.monitor_id = monitor.id;
@ -124,7 +124,7 @@ router.get("/api/status-page/:slug", cache("5 minutes"), async (request, respons
// Public Group List // Public Group List
const publicGroupList = []; const publicGroupList = [];
const showTags = !!statusPage.show_tags; const showTags = !!statusPage.show_tags;
debug("Show Tags???" + showTags);
const list = await R.find("group", " public = 1 AND status_page_id = ? ORDER BY weight ", [ const list = await R.find("group", " public = 1 AND status_page_id = ? ORDER BY weight ", [
statusPage.id statusPage.id
]); ]);
@ -195,14 +195,6 @@ router.get("/api/status-page/heartbeat/:slug", cache("1 minutes"), async (reques
} }
}); });
/**
* Default is published
* @returns {Promise<boolean>}
*/
async function isPublished() {
return true;
}
function send403(res, msg = "") { function send403(res, msg = "") {
res.status(403).json({ res.status(403).json({
"status": "fail", "status": "fail",

@ -11,40 +11,42 @@ if (nodeVersion < requiredVersion) {
} }
const args = require("args-parser")(process.argv); 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"); 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) { if (! process.env.NODE_ENV) {
process.env.NODE_ENV = "production"; 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 fs = require("fs");
const http = require("http"); const http = require("http");
const https = require("https"); const https = require("https");
console.log("Importing 3rd-party libraries"); log.info("server", "Importing 3rd-party libraries");
debug("Importing express"); log.debug("server", "Importing express");
const express = require("express"); const express = require("express");
debug("Importing socket.io"); log.debug("server", "Importing socket.io");
const { Server } = require("socket.io"); const { Server } = require("socket.io");
debug("Importing redbean-node"); log.debug("server", "Importing redbean-node");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
debug("Importing jsonwebtoken"); log.debug("server", "Importing jsonwebtoken");
const jwt = require("jsonwebtoken"); const jwt = require("jsonwebtoken");
debug("Importing http-graceful-shutdown"); log.debug("server", "Importing http-graceful-shutdown");
const gracefulShutdown = require("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"); const prometheusAPIMetrics = require("prometheus-api-metrics");
debug("Importing compare-versions"); log.debug("server", "Importing compare-versions");
const compareVersions = require("compare-versions"); const compareVersions = require("compare-versions");
const { passwordStrength } = require("check-password-strength"); const { passwordStrength } = require("check-password-strength");
debug("Importing 2FA Modules"); log.debug("server", "Importing 2FA Modules");
const notp = require("notp"); const notp = require("notp");
const base32 = require("thirty-two"); const base32 = require("thirty-two");
@ -69,23 +71,23 @@ class UptimeKumaServer {
const server = module.exports = new UptimeKumaServer(); const server = module.exports = new UptimeKumaServer();
console.log("Importing this project modules"); log.info("server", "Importing this project modules");
debug("Importing Monitor"); log.debug("server", "Importing Monitor");
const Monitor = require("./model/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"); 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"); const { Notification } = require("./notification");
Notification.init(); Notification.init();
debug("Importing Proxy"); log.debug("server", "Importing Proxy");
const { Proxy } = require("./proxy"); const { Proxy } = require("./proxy");
debug("Importing Database"); log.debug("server", "Importing Database");
const Database = require("./database"); const Database = require("./database");
debug("Importing Background Jobs"); log.debug("server", "Importing Background Jobs");
const { initBackgroundJobs, stopBackgroundJobs } = require("./jobs"); const { initBackgroundJobs, stopBackgroundJobs } = require("./jobs");
const { loginRateLimiter, twoFaRateLimiter } = require("./rate-limiter"); const { loginRateLimiter, twoFaRateLimiter } = require("./rate-limiter");
@ -94,7 +96,7 @@ const { login } = require("./auth");
const passwordHash = require("./password-hash"); const passwordHash = require("./password-hash");
const checkVersion = require("./check-version"); 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. // 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 (::) // 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; let hostname = args.host || process.env.UPTIME_KUMA_HOST || hostEnv;
if (hostname) { 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] const port = [args.port, process.env.UPTIME_KUMA_PORT, process.env.PORT, 3001]
@ -117,7 +119,7 @@ const disableFrameSameOrigin = args["disable-frame-sameorigin"] || !!process.env
const cloudflaredToken = args["cloudflared-token"] || process.env.UPTIME_KUMA_CLOUDFLARED_TOKEN || undefined; const cloudflaredToken = args["cloudflared-token"] || process.env.UPTIME_KUMA_CLOUDFLARED_TOKEN || undefined;
// 2FA / notp verification defaults // 2FA / notp verification defaults
const twofa_verification_opts = { const twoFAVerifyOptions = {
"window": 1, "window": 1,
"time": 30 "time": 30
}; };
@ -129,22 +131,22 @@ const twofa_verification_opts = {
const testMode = !!args["test"] || false; const testMode = !!args["test"] || false;
if (config.demoMode) { 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(); const app = express();
let httpServer; let httpServer;
if (sslKey && sslCert) { if (sslKey && sslCert) {
console.log("Server Type: HTTPS"); log.info("server", "Server Type: HTTPS");
httpServer = https.createServer({ httpServer = https.createServer({
key: fs.readFileSync(sslKey), key: fs.readFileSync(sslKey),
cert: fs.readFileSync(sslCert) cert: fs.readFileSync(sslCert)
}, app); }, app);
} else { } else {
console.log("Server Type: HTTP"); log.info("server", "Server Type: HTTP");
httpServer = http.createServer(app); httpServer = http.createServer(app);
} }
@ -173,6 +175,7 @@ app.use(function (req, res, next) {
/** /**
* Total WebSocket client connected to server currently, no actual use * Total WebSocket client connected to server currently, no actual use
*
* @type {number} * @type {number}
*/ */
let totalClient = 0; let totalClient = 0;
@ -200,7 +203,7 @@ try {
} catch (e) { } catch (e) {
// "dist/index.html" is not necessary for development // "dist/index.html" is not necessary for development
if (process.env.NODE_ENV !== "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); process.exit(1);
} }
} }
@ -212,7 +215,7 @@ try {
exports.entryPage = await setting("entryPage"); exports.entryPage = await setting("entryPage");
await StatusPage.loadDomainMappingList(); await StatusPage.loadDomainMappingList();
console.log("Adding route"); log.info("server", "Adding route");
// *************************** // ***************************
// Normal Router here // Normal Router here
@ -270,7 +273,7 @@ try {
} }
}); });
console.log("Adding socket handler"); log.info("server", "Adding socket handler");
io.on("connection", async (socket) => { io.on("connection", async (socket) => {
sendInfo(socket); sendInfo(socket);
@ -278,7 +281,7 @@ try {
totalClient++; totalClient++;
if (needSetup) { if (needSetup) {
console.log("Redirect to setup page"); log.info("server", "Redirect to setup page");
socket.emit("setup"); socket.emit("setup");
} }
@ -291,33 +294,40 @@ try {
// *************************** // ***************************
socket.on("loginByToken", async (token, callback) => { socket.on("loginByToken", async (token, callback) => {
log.info("auth", `Login by token. IP=${getClientIp(socket)}`);
try { try {
let decoded = jwt.verify(token, jwtSecret); 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 ", [ let user = await R.findOne("user", " username = ? AND active = 1 ", [
decoded.username, decoded.username,
]); ]);
if (user) { if (user) {
debug("afterLogin"); log.debug("auth", "afterLogin");
afterLogin(socket, user); afterLogin(socket, user);
log.debug("auth", "afterLogin ok");
debug("afterLogin ok"); log.info("auth", `Successfully logged in user ${decoded.username}. IP=${getClientIp(socket)}`);
callback({ callback({
ok: true, ok: true,
}); });
} else { } else {
log.info("auth", `Inactive or deleted user ${decoded.username}. IP=${getClientIp(socket)}`);
callback({ callback({
ok: false, ok: false,
msg: "The user is inactive or deleted.", msg: "The user is inactive or deleted.",
}); });
} }
} catch (error) { } catch (error) {
log.error("auth", `Invalid token. IP=${getClientIp(socket)}`);
callback({ callback({
ok: false, ok: false,
msg: "Invalid token.", msg: "Invalid token.",
@ -327,7 +337,7 @@ try {
}); });
socket.on("login", async (data, callback) => { socket.on("login", async (data, callback) => {
console.log("Login"); log.info("auth", `Login by username + password. IP=${getClientIp(socket)}`);
// Checking // Checking
if (typeof callback !== "function") { if (typeof callback !== "function") {
@ -340,6 +350,7 @@ try {
// Login Rate Limit // Login Rate Limit
if (! await loginRateLimiter.pass(callback)) { if (! await loginRateLimiter.pass(callback)) {
log.info("auth", `Too many failed requests for user ${data.username}. IP=${getClientIp(socket)}`);
return; return;
} }
@ -348,6 +359,9 @@ try {
if (user) { if (user) {
if (user.twofa_status == 0) { if (user.twofa_status == 0) {
afterLogin(socket, user); afterLogin(socket, user);
log.info("auth", `Successfully logged in user ${data.username}. IP=${getClientIp(socket)}`);
callback({ callback({
ok: true, ok: true,
token: jwt.sign({ token: jwt.sign({
@ -357,13 +371,16 @@ try {
} }
if (user.twofa_status == 1 && !data.token) { if (user.twofa_status == 1 && !data.token) {
log.info("auth", `2FA token required for user ${data.username}. IP=${getClientIp(socket)}`);
callback({ callback({
tokenRequired: true, tokenRequired: true,
}); });
} }
if (data.token) { if (data.token) {
let verify = notp.totp.verify(data.token, user.twofa_secret, twofa_verification_opts); let verify = notp.totp.verify(data.token, user.twofa_secret, twoFAVerifyOptions);
if (user.twofa_last_token !== data.token && verify) { if (user.twofa_last_token !== data.token && verify) {
afterLogin(socket, user); afterLogin(socket, user);
@ -373,6 +390,8 @@ try {
socket.userID, socket.userID,
]); ]);
log.info("auth", `Successfully logged in user ${data.username}. IP=${getClientIp(socket)}`);
callback({ callback({
ok: true, ok: true,
token: jwt.sign({ token: jwt.sign({
@ -380,6 +399,9 @@ try {
}, jwtSecret), }, jwtSecret),
}); });
} else { } else {
log.warn("auth", `Invalid token provided for user ${data.username}. IP=${getClientIp(socket)}`);
callback({ callback({
ok: false, ok: false,
msg: "Invalid Token!", msg: "Invalid Token!",
@ -387,6 +409,9 @@ try {
} }
} }
} else { } else {
log.warn("auth", `Incorrect username or password for user ${data.username}. IP=${getClientIp(socket)}`);
callback({ callback({
ok: false, ok: false,
msg: "Incorrect username or password.", msg: "Incorrect username or password.",
@ -469,11 +494,16 @@ try {
socket.userID, socket.userID,
]); ]);
log.info("auth", `Saved 2FA token. IP=${getClientIp(socket)}`);
callback({ callback({
ok: true, ok: true,
msg: "2FA Enabled.", msg: "2FA Enabled.",
}); });
} catch (error) { } catch (error) {
log.error("auth", `Error changing 2FA token. IP=${getClientIp(socket)}`);
callback({ callback({
ok: false, ok: false,
msg: error.message, msg: error.message,
@ -491,11 +521,16 @@ try {
await doubleCheckPassword(socket, currentPassword); await doubleCheckPassword(socket, currentPassword);
await TwoFA.disable2FA(socket.userID); await TwoFA.disable2FA(socket.userID);
log.info("auth", `Disabled 2FA token. IP=${getClientIp(socket)}`);
callback({ callback({
ok: true, ok: true,
msg: "2FA Disabled.", msg: "2FA Disabled.",
}); });
} catch (error) { } catch (error) {
log.error("auth", `Error disabling 2FA token. IP=${getClientIp(socket)}`);
callback({ callback({
ok: false, ok: false,
msg: error.message, msg: error.message,
@ -512,7 +547,7 @@ try {
socket.userID, socket.userID,
]); ]);
let verify = notp.totp.verify(token, user.twofa_secret, twofa_verification_opts); let verify = notp.totp.verify(token, user.twofa_secret, twoFAVerifyOptions);
if (user.twofa_last_token !== token && verify) { if (user.twofa_last_token !== token && verify) {
callback({ callback({
@ -622,6 +657,8 @@ try {
await server.sendMonitorList(socket); await server.sendMonitorList(socket);
await startMonitor(socket.userID, bean.id); await startMonitor(socket.userID, bean.id);
log.info("monitor", `Added Monitor: ${monitor.id} User ID: ${socket.userID}`);
callback({ callback({
ok: true, ok: true,
msg: "Added Successfully.", msg: "Added Successfully.",
@ -629,6 +666,9 @@ try {
}); });
} catch (e) { } catch (e) {
log.error("monitor", `Error adding Monitor: ${monitor.id} User ID: ${socket.userID}`);
callback({ callback({
ok: false, ok: false,
msg: e.message, msg: e.message,
@ -691,7 +731,7 @@ try {
}); });
} catch (e) { } catch (e) {
console.error(e); log.error("monitor", e);
callback({ callback({
ok: false, ok: false,
msg: e.message, msg: e.message,
@ -707,7 +747,7 @@ try {
ok: true, ok: true,
}); });
} catch (e) { } catch (e) {
console.error(e); log.error("monitor", e);
callback({ callback({
ok: false, ok: false,
msg: e.message, msg: e.message,
@ -719,7 +759,7 @@ try {
try { try {
checkLogin(socket); 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 = ? ", [ let bean = await R.findOne("monitor", " id = ? AND user_id = ? ", [
monitorID, monitorID,
@ -743,7 +783,7 @@ try {
try { try {
checkLogin(socket); 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) { if (period == null) {
throw new Error("Invalid period."); throw new Error("Invalid period.");
@ -814,7 +854,7 @@ try {
try { try {
checkLogin(socket); 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) { if (monitorID in server.monitorList) {
server.monitorList[monitorID].stop(); server.monitorList[monitorID].stop();
@ -1146,7 +1186,7 @@ try {
let backupData = JSON.parse(uploadedJSON); 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 notificationListData = backupData.notificationList;
let proxyListData = backupData.proxyList; let proxyListData = backupData.proxyList;
@ -1189,7 +1229,7 @@ try {
} }
// Only starts importing if the backup file contains at least one proxy // Only starts importing if the backup file contains at least one proxy
if (proxyListData.length >= 1) { if (proxyListData && proxyListData.length >= 1) {
const proxies = await R.findAll("proxy"); const proxies = await R.findAll("proxy");
// Loop over proxy list and save proxies // Loop over proxy list and save proxies
@ -1341,7 +1381,7 @@ try {
try { try {
checkLogin(socket); 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 = ? ", [ await R.exec("UPDATE heartbeat SET msg = ?, important = ? WHERE monitor_id = ? ", [
"", "",
@ -1367,7 +1407,7 @@ try {
try { try {
checkLogin(socket); 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 = ?", [ await R.exec("DELETE FROM heartbeat WHERE monitor_id = ?", [
monitorID monitorID
@ -1391,7 +1431,7 @@ try {
try { try {
checkLogin(socket); 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"); await R.exec("DELETE FROM heartbeat");
@ -1413,24 +1453,24 @@ try {
databaseSocketHandler(socket); databaseSocketHandler(socket);
proxySocketHandler(socket); proxySocketHandler(socket);
debug("added all socket handlers"); log.debug("server", "added all socket handlers");
// *************************** // ***************************
// Better do anything after added all socket handlers here // Better do anything after added all socket handlers here
// *************************** // ***************************
debug("check auto login"); log.debug("auth", "check auto login");
if (await setting("disableAuth")) { 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")); afterLogin(socket, await R.findOne("user"));
socket.emit("autoLogin"); socket.emit("autoLogin");
} else { } 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) => { httpServer.once("error", async (err) => {
console.error("Cannot listen: " + err.message); console.error("Cannot listen: " + err.message);
@ -1439,9 +1479,9 @@ try {
httpServer.listen(port, hostname, () => { httpServer.listen(port, hostname, () => {
if (hostname) { if (hostname) {
console.log(`Listening on ${hostname}:${port}`); log.info("server", `Listening on ${hostname}:${port}`);
} else { } else {
console.log(`Listening on ${port}`); log.info("server", `Listening on ${port}`);
} }
startMonitors(); startMonitors();
checkVersion.startInterval(); checkVersion.startInterval();
@ -1555,13 +1595,13 @@ async function getMonitorJSONList(userID) {
*/ */
async function initDatabase(testMode = false) { async function initDatabase(testMode = false) {
if (! fs.existsSync(Database.path)) { if (! fs.existsSync(Database.path)) {
console.log("Copying Database"); log.info("server", "Copying Database");
fs.copyFileSync(Database.templatePath, Database.path); fs.copyFileSync(Database.templatePath, Database.path);
} }
console.log("Connecting to the Database"); log.info("server", "Connecting to the Database");
await Database.connect(testMode); await Database.connect(testMode);
console.log("Connected"); log.info("server", "Connected");
// Patch the database // Patch the database
await Database.patch(); await Database.patch();
@ -1571,16 +1611,16 @@ async function initDatabase(testMode = false) {
]); ]);
if (! jwtSecretBean) { if (! jwtSecretBean) {
console.log("JWT secret is not found, generate one."); log.info("server", "JWT secret is not found, generate one.");
jwtSecretBean = await initJWTSecret(); jwtSecretBean = await initJWTSecret();
console.log("Stored JWT secret into database"); log.info("server", "Stored JWT secret into database");
} else { } 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 there is no record in user table, it is a new Uptime Kuma instance, need to setup
if ((await R.count("user")) === 0) { if ((await R.count("user")) === 0) {
console.log("No user, need setup"); log.info("server", "No user, need setup");
needSetup = true; needSetup = true;
} }
@ -1597,7 +1637,7 @@ async function initDatabase(testMode = false) {
async function startMonitor(userID, monitorID) { async function startMonitor(userID, monitorID) {
await checkOwner(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 = ? ", [ await R.exec("UPDATE monitor SET active = 1 WHERE id = ? AND user_id = ? ", [
monitorID, monitorID,
@ -1630,7 +1670,7 @@ async function restartMonitor(userID, monitorID) {
async function pauseMonitor(userID, monitorID) { async function pauseMonitor(userID, monitorID) {
await checkOwner(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 = ? ", [ await R.exec("UPDATE monitor SET active = 0 WHERE id = ? AND user_id = ? ", [
monitorID, monitorID,
@ -1666,10 +1706,10 @@ async function startMonitors() {
* Generated by Trelent * Generated by Trelent
*/ */
async function shutdownFunction(signal) { async function shutdownFunction(signal) {
console.log("Shutdown requested"); log.info("server", "Shutdown requested");
console.log("Called signal: " + signal); log.info("server", "Called signal: " + signal);
console.log("Stopping all monitors"); log.info("server", "Stopping all monitors");
for (let id in server.monitorList) { for (let id in server.monitorList) {
let monitor = server.monitorList[id]; let monitor = server.monitorList[id];
monitor.stop(); monitor.stop();
@ -1681,8 +1721,12 @@ async function shutdownFunction(signal) {
await cloudflaredStop(); await cloudflaredStop();
} }
function getClientIp(socket) {
return socket.client.conn.remoteAddress.replace(/^.*:/, "");
}
function finalFunction() { function finalFunction() {
console.log("Graceful shutdown successful!"); log.info("server", "Graceful shutdown successful!");
} }
gracefulShutdown(httpServer, { gracefulShutdown(httpServer, {

@ -1,7 +1,7 @@
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const { checkLogin, setSettings, setSetting } = require("../util-server"); const { checkLogin, setSetting } = require("../util-server");
const dayjs = require("dayjs"); const dayjs = require("dayjs");
const { debug } = require("../../src/util"); const { log } = require("../../src/util");
const ImageDataURI = require("../image-data-uri"); const ImageDataURI = require("../image-data-uri");
const Database = require("../database"); const Database = require("../database");
const apicache = require("../modules/apicache"); const apicache = require("../modules/apicache");
@ -202,8 +202,8 @@ module.exports.statusPageSocketHandler = (socket) => {
group.id = groupBean.id; group.id = groupBean.id;
} }
// Delete groups that not in the list // Delete groups that are not in the list
debug("Delete groups that not in the list"); log.debug("socket", "Delete groups that are not in the list");
const slots = groupIDList.map(() => "?").join(","); const slots = groupIDList.map(() => "?").join(",");
const data = [ const data = [
@ -226,7 +226,7 @@ module.exports.statusPageSocketHandler = (socket) => {
}); });
} catch (error) { } catch (error) {
console.error(error); log.error("socket", error);
callback({ callback({
ok: false, ok: false,

@ -1,10 +1,10 @@
const tcpp = require("tcp-ping"); const tcpp = require("tcp-ping");
const Ping = require("./ping-lite"); const Ping = require("./ping-lite");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const { debug, genSecret } = require("../src/util"); const { log, genSecret } = require("../src/util");
const passwordHash = require("./password-hash"); const passwordHash = require("./password-hash");
const { Resolver } = require("dns"); const { Resolver } = require("dns");
const child_process = require("child_process"); const childProcess = require("child_process");
const iconv = require("iconv-lite"); const iconv = require("iconv-lite");
const chardet = require("chardet"); const chardet = require("chardet");
const fs = require("fs"); const fs = require("fs");
@ -88,9 +88,9 @@ exports.pingAsync = function (hostname, ipv6 = false) {
}); });
}; };
exports.dnsResolve = function (hostname, resolver_server, rrtype) { exports.dnsResolve = function (hostname, resolverServer, rrtype) {
const resolver = new Resolver(); const resolver = new Resolver();
resolver.setServers([resolver_server]); resolver.setServers([resolverServer]);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (rrtype == "PTR") { if (rrtype == "PTR") {
resolver.reverse(hostname, (err, records) => { resolver.reverse(hostname, (err, records) => {
@ -119,7 +119,7 @@ exports.setting = async function (key) {
try { try {
const v = JSON.parse(value); const v = JSON.parse(value);
debug(`Get Setting: ${key}: ${v}`); log.debug("util", `Get Setting: ${key}: ${v}`);
return v; return v;
} catch (e) { } catch (e) {
return value; return value;
@ -206,7 +206,7 @@ const parseCertificateInfo = function (info) {
const existingList = {}; const existingList = {};
while (link) { while (link) {
debug(`[${i}] ${link.fingerprint}`); log.debug("util", `[${i}] ${link.fingerprint}`);
if (!link.valid_from || !link.valid_to) { if (!link.valid_from || !link.valid_to) {
break; break;
@ -221,7 +221,7 @@ const parseCertificateInfo = function (info) {
if (link.issuerCertificate == null) { if (link.issuerCertificate == null) {
break; break;
} else if (link.issuerCertificate.fingerprint in existingList) { } else if (link.issuerCertificate.fingerprint in existingList) {
debug(`[Last] ${link.issuerCertificate.fingerprint}`); log.debug("util", `[Last] ${link.issuerCertificate.fingerprint}`);
link.issuerCertificate = null; link.issuerCertificate = null;
break; break;
} else { } else {
@ -242,7 +242,7 @@ exports.checkCertificate = function (res) {
const info = res.request.res.socket.getPeerCertificate(true); const info = res.request.res.socket.getPeerCertificate(true);
const valid = res.request.res.socket.authorized || false; const valid = res.request.res.socket.authorized || false;
debug("Parsing Certificate Info"); log.debug("util", "Parsing Certificate Info");
const parsedInfo = parseCertificateInfo(info); const parsedInfo = parseCertificateInfo(info);
return { return {
@ -257,19 +257,19 @@ exports.checkCertificate = function (res) {
// Return: true if the status code is within the accepted ranges, false otherwise // Return: true if the status code is within the accepted ranges, false otherwise
// Will throw an error if the provided status code is not a valid range string or code string // Will throw an error if the provided status code is not a valid range string or code string
exports.checkStatusCode = function (status, accepted_codes) { exports.checkStatusCode = function (status, acceptedCodes) {
if (accepted_codes == null || accepted_codes.length === 0) { if (acceptedCodes == null || acceptedCodes.length === 0) {
return false; return false;
} }
for (const code_range of accepted_codes) { for (const codeRange of acceptedCodes) {
const code_range_split = code_range.split("-").map(string => parseInt(string)); const codeRangeSplit = codeRange.split("-").map(string => parseInt(string));
if (code_range_split.length === 1) { if (codeRangeSplit.length === 1) {
if (status === code_range_split[0]) { if (status === codeRangeSplit[0]) {
return true; return true;
} }
} else if (code_range_split.length === 2) { } else if (codeRangeSplit.length === 2) {
if (status >= code_range_split[0] && status <= code_range_split[1]) { if (status >= codeRangeSplit[0] && status <= codeRangeSplit[1]) {
return true; return true;
} }
} else { } else {
@ -345,7 +345,7 @@ exports.doubleCheckPassword = async (socket, currentPassword) => {
exports.startUnitTest = async () => { exports.startUnitTest = async () => {
console.log("Starting unit test..."); console.log("Starting unit test...");
const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm"; const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm";
const child = child_process.spawn(npm, ["run", "jest"]); const child = childProcess.spawn(npm, ["run", "jest"]);
child.stdout.on("data", (data) => { child.stdout.on("data", (data) => {
console.log(data.toString()); console.log(data.toString());
@ -367,7 +367,6 @@ exports.startUnitTest = async () => {
*/ */
exports.convertToUTF8 = (body) => { exports.convertToUTF8 = (body) => {
const guessEncoding = chardet.detect(body); const guessEncoding = chardet.detect(body);
//debug("Guess Encoding: " + guessEncoding);
const str = iconv.decode(body, guessEncoding); const str = iconv.decode(body, guessEncoding);
return str.toString(); return str.toString();
}; };

@ -1,12 +1,12 @@
<template> <template>
<router-view /> <router-view />
</template> </template>
<script> <script>
import { setPageLocale } from "./util-frontend"; import { setPageLocale } from "./util-frontend";
export default { export default {
created() { created() {
setPageLocale(); setPageLocale();
}, },
}; };
</script> </script>

@ -25,7 +25,7 @@
</template> </template>
<script> <script>
import { Modal } from "bootstrap" import { Modal } from "bootstrap";
export default { export default {
props: { props: {
@ -42,19 +42,20 @@ export default {
default: "No", default: "No",
}, },
}, },
emits: ["yes"],
data: () => ({ data: () => ({
modal: null, modal: null,
}), }),
mounted() { mounted() {
this.modal = new Modal(this.$refs.modal) this.modal = new Modal(this.$refs.modal);
}, },
methods: { methods: {
show() { show() {
this.modal.show() this.modal.show();
}, },
yes() { yes() {
this.$emit("yes"); this.$emit("yes");
}, },
}, },
} };
</script> </script>

@ -57,6 +57,7 @@ export default {
default: undefined, default: undefined,
}, },
}, },
emits: ["update:modelValue"],
data() { data() {
return { return {
visibility: "password", visibility: "password",

@ -5,7 +5,7 @@
<script lang="ts"> <script lang="ts">
import { sleep } from "../util.ts" import { sleep } from "../util.ts";
export default { export default {
@ -25,12 +25,12 @@ export default {
return { return {
output: "", output: "",
frameDuration: 30, frameDuration: 30,
} };
}, },
computed: { computed: {
isNum() { isNum() {
return typeof this.value === "number" return typeof this.value === "number";
}, },
}, },
@ -45,7 +45,7 @@ export default {
} else { } else {
for (let i = 1; i < frames; i++) { for (let i = 1; i < frames; i++) {
this.output += step; this.output += step;
await sleep(15) await sleep(15);
} }
} }
@ -59,5 +59,5 @@ export default {
methods: {}, methods: {},
} };
</script> </script>

@ -4,12 +4,12 @@
<script> <script>
import dayjs from "dayjs"; import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime" import relativeTime from "dayjs/plugin/relativeTime";
import utc from "dayjs/plugin/utc" import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone" // dependent on utc plugin import timezone from "dayjs/plugin/timezone"; // dependent on utc plugin
dayjs.extend(utc) dayjs.extend(utc);
dayjs.extend(timezone) dayjs.extend(timezone);
dayjs.extend(relativeTime) dayjs.extend(relativeTime);
export default { export default {
props: { props: {
@ -29,5 +29,5 @@ export default {
} }
}, },
}, },
} };
</script> </script>

@ -38,7 +38,7 @@ export default {
beatMargin: 4, beatMargin: 4,
move: false, move: false,
maxBeat: -1, maxBeat: -1,
} };
}, },
computed: { computed: {
@ -69,12 +69,12 @@ export default {
if (start < 0) { if (start < 0) {
// Add empty placeholder // Add empty placeholder
for (let i = start; i < 0; i++) { for (let i = start; i < 0; i++) {
placeholders.push(0) placeholders.push(0);
} }
start = 0; start = 0;
} }
return placeholders.concat(this.beatList.slice(start)) return placeholders.concat(this.beatList.slice(start));
}, },
wrapStyle() { wrapStyle() {
@ -84,7 +84,7 @@ export default {
return { return {
padding: `${topBottom}px ${leftRight}px`, padding: `${topBottom}px ${leftRight}px`,
width: "100%", width: "100%",
} };
}, },
barStyle() { barStyle() {
@ -94,12 +94,12 @@ export default {
return { return {
transition: "all ease-in-out 0.25s", transition: "all ease-in-out 0.25s",
transform: `translateX(${width}px)`, transform: `translateX(${width}px)`,
} };
} }
return { return {
transform: "translateX(0)", transform: "translateX(0)",
} };
}, },
@ -109,7 +109,7 @@ export default {
height: this.beatHeight + "px", height: this.beatHeight + "px",
margin: this.beatMargin + "px", margin: this.beatMargin + "px",
"--hover-scale": this.hoverScale, "--hover-scale": this.hoverScale,
} };
}, },
}, },
@ -120,7 +120,7 @@ export default {
setTimeout(() => { setTimeout(() => {
this.move = false; this.move = false;
}, 300) }, 300);
}, },
deep: true, deep: true,
}, },
@ -162,15 +162,15 @@ export default {
methods: { methods: {
resize() { resize() {
if (this.$refs.wrap) { 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) { getBeatTitle(beat) {
return `${this.$root.datetime(beat.time)}` + ((beat.msg) ? ` - ${beat.msg}` : ``); return `${this.$root.datetime(beat.time)}` + ((beat.msg) ? ` - ${beat.msg}` : "");
} }
}, },
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

@ -48,18 +48,19 @@ export default {
default: undefined, default: undefined,
}, },
}, },
emits: ["update:modelValue"],
data() { data() {
return { return {
visibility: "password", visibility: "password",
} };
}, },
computed: { computed: {
model: { model: {
get() { get() {
return this.modelValue return this.modelValue;
}, },
set(value) { set(value) {
this.$emit("update:modelValue", value) this.$emit("update:modelValue", value);
} }
} }
}, },
@ -74,5 +75,5 @@ export default {
this.visibility = "password"; this.visibility = "password";
}, },
} }
} };
</script> </script>

@ -21,7 +21,7 @@
<router-link v-for="(item, index) in sortedMonitorList" :key="index" :to="monitorURL(item.id)" class="item" :class="{ 'disabled': ! item.active }"> <router-link v-for="(item, index) in sortedMonitorList" :key="index" :to="monitorURL(item.id)" class="item" :class="{ 'disabled': ! item.active }">
<div class="row"> <div class="row">
<div class="col-9 col-md-8 small-padding" :class="{ 'monitorItem': $root.userHeartbeatBar == 'bottom' || $root.userHeartbeatBar == 'none' }"> <div class="col-9 col-md-8 small-padding" :class="{ 'monitor-item': $root.userHeartbeatBar == 'bottom' || $root.userHeartbeatBar == 'none' }">
<div class="info"> <div class="info">
<Uptime :monitor="item" type="24" :pill="true" /> <Uptime :monitor="item" type="24" :pill="true" />
{{ item.name }} {{ item.name }}
@ -172,7 +172,7 @@ export default {
.dark { .dark {
.footer { .footer {
// background-color: $dark-bg; // background-color: $dark-bg;
} }
} }
@ -198,7 +198,7 @@ export default {
max-width: 15em; max-width: 15em;
} }
.monitorItem { .monitor-item {
width: 100%; width: 100%;
} }

@ -69,7 +69,6 @@
<script lang="ts"> <script lang="ts">
import { Modal } from "bootstrap"; import { Modal } from "bootstrap";
import { ucfirst } from "../util.ts";
import Confirm from "./Confirm.vue"; import Confirm from "./Confirm.vue";
import NotificationFormList from "./notifications"; import NotificationFormList from "./notifications";

@ -24,7 +24,7 @@ import timezone from "dayjs/plugin/timezone";
import "chartjs-adapter-dayjs"; import "chartjs-adapter-dayjs";
import { LineChart } from "vue-chart-3"; import { LineChart } from "vue-chart-3";
import { useToast } from "vue-toastification"; import { useToast } from "vue-toastification";
import { UP, DOWN, PENDING } from "../util.ts"; import { DOWN } from "../util.ts";
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(timezone); dayjs.extend(timezone);
@ -220,6 +220,7 @@ export default {
if (newPeriod == "0") { if (newPeriod == "0") {
newPeriod = null; newPeriod = null;
this.heartbeatList = null; this.heartbeatList = null;
this.$root.storage().removeItem(`chart-period-${this.monitorId}`);
} else { } else {
this.loading = true; this.loading = true;
@ -228,6 +229,7 @@ export default {
toast.error(res.msg); toast.error(res.msg);
} else { } else {
this.heartbeatList = res.data; this.heartbeatList = res.data;
this.$root.storage()[`chart-period-${this.monitorId}`] = newPeriod;
} }
this.loading = false; this.loading = false;
}); });
@ -248,6 +250,12 @@ export default {
}, },
{ deep: true } { deep: true }
); );
// Load chart period from storage if saved
let period = this.$root.storage()[`chart-period-${this.monitorId}`];
if (period != null) {
this.chartPeriodHrs = Math.min(period, 6);
}
} }
}; };
</script> </script>
@ -278,7 +286,7 @@ export default {
.dropdown-item { .dropdown-item {
border-radius: 0.3rem; border-radius: 0.3rem;
padding: 2px 16px 4px 16px; padding: 2px 16px 4px;
.dark & { .dark & {
background: $dark-bg; background: $dark-bg;
@ -286,6 +294,7 @@ export default {
.dark &:hover { .dark &:hover {
background: $dark-font-color; background: $dark-font-color;
color: $dark-font-color2;
} }
} }

@ -25,7 +25,7 @@
<label for="proxy-host" class="form-label">{{ $t("Proxy Server") }}</label> <label for="proxy-host" class="form-label">{{ $t("Proxy Server") }}</label>
<div class="d-flex"> <div class="d-flex">
<input id="proxy-host" v-model="proxy.host" type="text" class="form-control" required :placeholder="$t('Server Address')"> <input id="proxy-host" v-model="proxy.host" type="text" class="form-control" required :placeholder="$t('Server Address')">
<input v-model="proxy.port" type="number" class="form-control ms-2" style="width: 100px" required min="1" max="65535" :placeholder="$t('Port')"> <input v-model="proxy.port" type="number" class="form-control ms-2" style="width: 100px;" required min="1" max="65535" :placeholder="$t('Port')">
</div> </div>
</div> </div>

@ -145,7 +145,7 @@ export default {
.mobile { .mobile {
.item { .item {
padding: 13px 0 10px 0; padding: 13px 0 10px;
} }
} }

@ -41,7 +41,7 @@ export default {
} }
} }
} }
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

@ -49,7 +49,7 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
@import "../assets/vars.scss"; @import "../assets/vars.scss";
h5:after { h5::after {
content: ""; content: "";
display: block; display: block;
width: 50%; width: 50%;

@ -46,7 +46,7 @@
<input v-model="token" type="text" maxlength="6" class="form-control"> <input v-model="token" type="text" maxlength="6" class="form-control">
<button class="btn btn-outline-primary" type="button" @click="verifyToken()">{{ $t("Verify Token") }}</button> <button class="btn btn-outline-primary" type="button" @click="verifyToken()">{{ $t("Verify Token") }}</button>
</div> </div>
<p v-show="tokenValid" class="mt-2" style="color: green">{{ $t("tokenValidSettingsMsg") }}</p> <p v-show="tokenValid" class="mt-2" style="color: green;">{{ $t("tokenValidSettingsMsg") }}</p>
</div> </div>
</div> </div>
</div> </div>

@ -22,33 +22,33 @@ export default {
return Math.round(this.$root.uptimeList[key] * 10000) / 100 + "%"; return Math.round(this.$root.uptimeList[key] * 10000) / 100 + "%";
} }
return this.$t("notAvailableShort") return this.$t("notAvailableShort");
}, },
color() { color() {
if (this.lastHeartBeat.status === 0) { if (this.lastHeartBeat.status === 0) {
return "danger" return "danger";
} }
if (this.lastHeartBeat.status === 1) { if (this.lastHeartBeat.status === 1) {
return "primary" return "primary";
} }
if (this.lastHeartBeat.status === 2) { if (this.lastHeartBeat.status === 2) {
return "warning" return "warning";
} }
return "secondary" return "secondary";
}, },
lastHeartBeat() { lastHeartBeat() {
if (this.monitor.id in this.$root.lastHeartbeatList && this.$root.lastHeartbeatList[this.monitor.id]) { if (this.monitor.id in this.$root.lastHeartbeatList && this.$root.lastHeartbeatList[this.monitor.id]) {
return this.$root.lastHeartbeatList[this.monitor.id] return this.$root.lastHeartbeatList[this.monitor.id];
} }
return { return {
status: -1, status: -1,
} };
}, },
className() { className() {
@ -59,7 +59,7 @@ export default {
return ""; return "";
}, },
}, },
} };
</script> </script>
<style> <style>

@ -28,5 +28,5 @@ export default {
this.$parent.notification.gotifyPriority = 8; this.$parent.notification.gotifyPriority = 8;
} }
}, },
} };
</script> </script>

@ -1,6 +1,6 @@
<template> <template>
<div class="mb-3"> <div class="mb-3">
<label for="mattermost-webhook-url" class="form-label">{{ $t("Webhook URL") }}<span style="color:red;"><sup>*</sup></span></label> <label for="mattermost-webhook-url" class="form-label">{{ $t("Webhook URL") }}<span style="color: red;"><sup>*</sup></span></label>
<input id="mattermost-webhook-url" v-model="$parent.notification.mattermostWebhookUrl" type="text" class="form-control" required> <input id="mattermost-webhook-url" v-model="$parent.notification.mattermostWebhookUrl" type="text" class="form-control" required>
<label for="mattermost-username" class="form-label">{{ $t("Username") }}</label> <label for="mattermost-username" class="form-label">{{ $t("Username") }}</label>
<input id="mattermost-username" v-model="$parent.notification.mattermostusername" type="text" class="form-control"> <input id="mattermost-username" v-model="$parent.notification.mattermostusername" type="text" class="form-control">
@ -11,7 +11,7 @@
<label for="mattermost-channel" class="form-label">{{ $t("Channel Name") }}</label> <label for="mattermost-channel" class="form-label">{{ $t("Channel Name") }}</label>
<input id="mattermost-channel-name" v-model="$parent.notification.mattermostchannel" type="text" class="form-control"> <input id="mattermost-channel-name" v-model="$parent.notification.mattermostchannel" type="text" class="form-control">
<div class="form-text"> <div class="form-text">
<span style="color:red;"><sup>*</sup></span>{{ $t("Required") }} <span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}
<i18n-t tag="p" keypath="aboutWebhooks" style="margin-top: 8px;"> <i18n-t tag="p" keypath="aboutWebhooks" style="margin-top: 8px;">
<a href="https://docs.mattermost.com/developer/webhooks-incoming.html" target="_blank">https://docs.mattermost.com/developer/webhooks-incoming.html</a> <a href="https://docs.mattermost.com/developer/webhooks-incoming.html" target="_blank">https://docs.mattermost.com/developer/webhooks-incoming.html</a>
</i18n-t> </i18n-t>

@ -0,0 +1,34 @@
<template>
<div class="mb-3">
<div class="mb-3">
<label for="onebot-http-addr" class="form-label">{{ $t("onebotHttpAddress") }}<span style="color: red;"><sup>*</sup></span></label>
<input id="HttpUrl" v-model="$parent.notification.httpAddr" type="text" class="form-control" required>
</div>
<div class="mb-3">
<label for="onebot-access-token" class="form-label">AccessToken<span style="color: red;"><sup>*</sup></span></label>
<input id="HttpUrl" v-model="$parent.notification.accessToken" type="text" class="form-control" required>
<div class="form-text">
<p>{{ $t("onebotSafetyTips") }}</p>
</div>
</div>
<div class="mb-3">
<label for="onebot-msg-type" class="form-label">{{ $t("onebotMessageType") }}</label>
<select id="onebot-msg-type" v-model="$parent.notification.msgType" class="form-select">
<option value="group">{{ $t("onebotGroupMessage") }}</option>
<option value="private">{{ $t("onebotPrivateMessage") }}</option>
</select>
</div>
<div class="mb-3">
<label for="onebot-reciever-id" class="form-label">{{ $t("onebotUserOrGroupId") }}<span style="color: red;"><sup>*</sup></span></label>
<input id="secretKey" v-model="$parent.notification.recieverId" type="text" class="form-control" required>
</div>
<div class="form-text">
<i18n-t tag="p" keypath="Read more:">
<a href="https://github.com/botuniverse/onebot-11" target="_blank">https://github.com/botuniverse/onebot-11</a>
</i18n-t>
</div>
</div>
</template>

@ -0,0 +1,19 @@
<template>
<div class="mb-3">
<label for="pushdeer-key" class="form-label">{{ $t("PushDeer Key") }}</label>
<HiddenInput id="pushdeer-key" v-model="$parent.notification.pushdeerKey" :required="true" autocomplete="one-time-code" placeholder="PDUxxxx"></HiddenInput>
</div>
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;">
<a href="http://www.pushdeer.com/" rel="noopener noreferrer" target="_blank">http://www.pushdeer.com/</a>
</i18n-t>
</template>
<script>
import HiddenInput from "../HiddenInput.vue";
export default {
components: {
HiddenInput,
},
};
</script>

@ -63,5 +63,5 @@ export default {
components: { components: {
HiddenInput, HiddenInput,
}, },
} };
</script> </script>

@ -24,11 +24,13 @@ import AliyunSMS from "./AliyunSms.vue";
import DingDing from "./DingDing.vue"; import DingDing from "./DingDing.vue";
import Bark from "./Bark.vue"; import Bark from "./Bark.vue";
import SerwerSMS from "./SerwerSMS.vue"; import SerwerSMS from "./SerwerSMS.vue";
import Stackfield from './Stackfield.vue'; import Stackfield from "./Stackfield.vue";
import WeCom from "./WeCom.vue"; import WeCom from "./WeCom.vue";
import GoogleChat from "./GoogleChat.vue"; import GoogleChat from "./GoogleChat.vue";
import Gorush from "./Gorush.vue"; import Gorush from "./Gorush.vue";
import Alerta from "./Alerta.vue"; import Alerta from "./Alerta.vue";
import OneBot from "./OneBot.vue";
import PushDeer from "./PushDeer.vue";
/** /**
* Manage all notification form. * Manage all notification form.
@ -67,6 +69,8 @@ const NotificationFormList = {
"GoogleChat": GoogleChat, "GoogleChat": GoogleChat,
"gorush": Gorush, "gorush": Gorush,
"alerta": Alerta, "alerta": Alerta,
"OneBot": OneBot,
"PushDeer": PushDeer,
}; };
export default NotificationFormList; export default NotificationFormList;

@ -44,6 +44,7 @@ export default {
.logo { .logo {
margin: 4em 1em; margin: 4em 1em;
} }
.update-link { .update-link {
font-size: 0.9em; font-size: 0.9em;
} }

@ -69,7 +69,7 @@
<div class="mb-2"> <div class="mb-2">
<input <input
id="importBackup" id="import-backend"
type="file" type="file"
class="form-control" class="form-control"
accept="application/json" accept="application/json"
@ -94,7 +94,7 @@
<div <div
v-if="importAlert" v-if="importAlert"
class="alert alert-danger mt-3" class="alert alert-danger mt-3"
style="padding: 6px 16px" style="padding: 6px 16px;"
> >
{{ importAlert }} {{ importAlert }}
</div> </div>
@ -159,7 +159,7 @@ export default {
importBackup() { importBackup() {
this.processing = true; this.processing = true;
let uploadItem = document.getElementById("importBackup").files; let uploadItem = document.getElementById("import-backend").files;
if (uploadItem.length <= 0) { if (uploadItem.length <= 0) {
this.processing = false; this.processing = false;
@ -198,7 +198,7 @@ export default {
@import "../../assets/vars.scss"; @import "../../assets/vars.scss";
.dark { .dark {
#importBackup { #import-backend {
&::file-selector-button { &::file-selector-button {
color: $primary; color: $primary;
background-color: $dark-bg; background-color: $dark-bg;

@ -189,4 +189,3 @@ export default {
}; };
</script> </script>
<style></style>

@ -52,7 +52,7 @@
<script> <script>
import Confirm from "../../components/Confirm.vue"; import Confirm from "../../components/Confirm.vue";
import { debug } from "../../util.ts"; import { log } from "../../util.ts";
import { useToast } from "vue-toastification"; import { useToast } from "vue-toastification";
const toast = useToast(); const toast = useToast();
@ -91,13 +91,13 @@ export default {
methods: { methods: {
loadDatabaseSize() { loadDatabaseSize() {
debug("load database size"); log.debug("monitorhistory", "load database size");
this.$root.getSocket().emit("getDatabaseSize", (res) => { this.$root.getSocket().emit("getDatabaseSize", (res) => {
if (res.ok) { if (res.ok) {
this.databaseSize = res.size; this.databaseSize = res.size;
debug("database size: " + res.size); log.debug("monitorhistory", "database size: " + res.size);
} else { } else {
debug(res); log.debug("monitorhistory", res);
} }
}); });
}, },
@ -108,7 +108,7 @@ export default {
this.loadDatabaseSize(); this.loadDatabaseSize();
toast.success("Done"); toast.success("Done");
} else { } else {
debug(res); log.debug("monitorhistory", res);
} }
}); });
}, },
@ -129,5 +129,3 @@ export default {
}, },
}; };
</script> </script>
<style></style>

@ -355,7 +355,7 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
@import "../../assets/vars.scss"; @import "../../assets/vars.scss";
h5:after { h5::after {
content: ""; content: "";
display: block; display: block;
width: 50%; width: 50%;

@ -343,7 +343,7 @@ export default {
"No Monitors": "Няма монитори", "No Monitors": "Няма монитори",
"Untitled Group": "Група без заглавие", "Untitled Group": "Група без заглавие",
Services: "Услуги", Services: "Услуги",
Discard: "Премахни", Discard: "Отмени",
Cancel: "Отмени", Cancel: "Отмени",
"Powered by": "Създадено чрез", "Powered by": "Създадено чрез",
serwersms: "SerwerSMS.pl", serwersms: "SerwerSMS.pl",

@ -350,7 +350,7 @@ export default {
serwersmsAPIPassword: "API Passwort", serwersmsAPIPassword: "API Passwort",
serwersmsPhoneNumber: "Telefonnummer", serwersmsPhoneNumber: "Telefonnummer",
serwersmsSenderName: "Name des SMS-Absenders (über Kundenportal registriert)", serwersmsSenderName: "Name des SMS-Absenders (über Kundenportal registriert)",
"stackfield": "Stackfield", stackfield: "Stackfield",
clicksendsms: "ClickSend SMS", clicksendsms: "ClickSend SMS",
apiCredentials: "API Zugangsdaten", apiCredentials: "API Zugangsdaten",
smtpDkimSettings: "DKIM Einstellungen", smtpDkimSettings: "DKIM Einstellungen",
@ -362,6 +362,86 @@ export default {
smtpDkimHashAlgo: "Hash-Algorithmus (Optional)", smtpDkimHashAlgo: "Hash-Algorithmus (Optional)",
smtpDkimheaderFieldNames: "Zu validierende Header-Schlüssel (optional)", smtpDkimheaderFieldNames: "Zu validierende Header-Schlüssel (optional)",
smtpDkimskipFields: "Zu ignorierende Header Schlüssel (optional)", smtpDkimskipFields: "Zu ignorierende Header Schlüssel (optional)",
PushByTechulus: "Push by Techulus",
gorush: "Gorush",
alerta: "Alerta",
alertaApiEndpoint: "API Endpunkt",
alertaEnvironment: "Umgebung",
alertaApiKey: "API Schlüssel",
alertaAlertState: "Alarmstatus",
alertaRecoverState: "Wiederherstellungsstatus",
deleteStatusPageMsg: "Bist du sicher, dass du diese Status-Seite löschen willst?",
Proxies: "Proxies",
default: "Standard",
enabled: "Aktiviert",
setAsDefault: "Als Standard setzen",
deleteProxyMsg: "Bist du sicher, dass du diesen Proxy für alle Monitore löschen willst?",
proxyDescription: "Proxies müssen einem Monitor zugewiesen werden, um zu funktionieren.",
enableProxyDescription: "Dieser Proxy wird keinen Effekt auf Monitor-Anfragen haben, bis er aktiviert ist. Du kannst ihn temporär von allen Monitoren nach Aktivierungsstatus deaktivieren.",
setAsDefaultProxyDescription: "Dieser Proxy wird standardmäßig für alle neuen Monitore aktiviert sein. Du kannst den Proxy immernoch für jeden Monitor einzeln deaktivieren.",
"Certificate Chain": "Zertifikatskette",
Valid: "Gültig",
Invalid: "Ungültig",
AccessKeyId: "AccessKey ID",
SecretAccessKey: "AccessKey Secret",
PhoneNumbers: "Telefonnummern",
TemplateCode: "Vorlagencode",
SignName: "Signaturname",
"Sms template must contain parameters: ": "SMS Vorlage muss folgende Parameter enthalten: ",
"Bark Endpoint": "Bark Endpunkt",
WebHookUrl: "Webhook URL",
SecretKey: "Geheimer Schlüssel",
"For safety, must use secret key": "Zur Sicherheit muss ein geheimer Schlüssel verwendet werden",
"Device Token": "Gerätetoken",
Platform: "Platform",
iOS: "iOS",
Android: "Android",
Huawei: "Huawei",
High: "Hoch",
Retry: "Wiederholungen",
Topic: "Thema",
"WeCom Bot Key": "WeCom Bot Schlüssel",
"Setup Proxy": "Proxy einrichten",
"Proxy Protocol": "Proxy Protokoll",
"Proxy Server": "Proxy Server",
"Proxy server has authentication": "Proxy server hat Authentifizierung",
User: "Benutzer",
Installed: "Installiert",
"Not installed": "Nicht installiert",
Running: "Läuft",
"Not running": "Gestoppt",
"Remove Token": "Token entfernen",
Start: "Start",
Stop: "Stop",
"Uptime Kuma": "Uptime Kuma",
"Add New Status Page": "Neue Status-Seite hinzufügen",
Slug: "Slug",
"Accept characters:": "Akzeptierte Zeichen:",
startOrEndWithOnly: "Nur mit {0} anfangen und enden",
"No consecutive dashes": "Keine aufeinanderfolgenden Bindestriche",
Next: "Weiter",
"The slug is already taken. Please choose another slug.": "Der Slug ist bereits in Verwendung. Bitte wähle einen anderen.",
"No Proxy": "Kein Proxy",
"HTTP Basic Auth": "HTTP Basisauthentifizierung",
"New Status Page": "Neue Status-Seite",
"Page Not Found": "Seite nicht gefunden",
"Reverse Proxy": "Reverse Proxy",
Backup: "Sicherung",
About: "Über",
wayToGetCloudflaredURL: "(Lade cloudflared von {0} herunter)",
cloudflareWebsite: "Cloudflare Website",
"Message:": "Nachricht:",
"Don't know how to get the token? Please read the guide:": "Du weißt nicht, wie man den Token bekommt? Lies die Anleitung dazu:",
"The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.": "Die aktuelle Verbindung kann unterbrochen werden, wenn du aktuell über Cloudflare Tunnel verbunden bist. Bist du sicher, dass du es stoppen willst? Gib zur Bestätigung dein aktuelles Passwort ein.",
"Other Software": "Andere Software",
"For example: nginx, Apache and Traefik.": "Zum Beispiel: nginx, Apache und Traefik.",
"Please read": "Bitte lesen",
"Subject:": "Betreff:",
"Valid To:": "Gültig bis:",
"Days Remaining:": "Tage verbleibend:",
"Issuer:": "Aussteller:",
"Fingerprint:": "Fingerabdruck:",
"No status pages": "Keine Status-Seiten",
Customize: "Anpassen", Customize: "Anpassen",
"Custom Footer": "Eigener Footer (Leerlassen für Standard)", "Custom Footer": "Eigener Footer (Leerlassen für Standard)",
"Custom CSS": "Eigenes CSS", "Custom CSS": "Eigenes CSS",

@ -445,4 +445,14 @@ export default {
"Issuer:": "Issuer:", "Issuer:": "Issuer:",
"Fingerprint:": "Fingerprint:", "Fingerprint:": "Fingerprint:",
"No status pages": "No status pages", "No status pages": "No status pages",
"Domain Name Expiry Notification": "Domain Name Expiry Notification",
"Proxy": "Proxy",
"Date Created": "Date Created",
onebotHttpAddress: "OneBot HTTP Address",
onebotMessageType: "OneBot Message Type",
onebotGroupMessage: "Group",
onebotPrivateMessage: "Private",
onebotUserOrGroupId: "Group/User ID",
onebotSafetyTips: "For safety, must set access token",
"PushDeer Key": "PushDeer Key",
}; };

@ -374,8 +374,8 @@ export default {
serwersmsSenderName: "SMS Имя Отправителя (регистрированный через пользовательский портал)", serwersmsSenderName: "SMS Имя Отправителя (регистрированный через пользовательский портал)",
stackfield: "Stackfield", stackfield: "Stackfield",
smtpDkimSettings: "DKIM Настройки", smtpDkimSettings: "DKIM Настройки",
smtpDkimDesc: "Please refer to the Nodemailer DKIM {0} for usage.", smtpDkimDesc: "Пожалуйста ознакомьтесь с {0} Nodemailer DKIM для использования.",
documentation: "документация", documentation: "документацией",
smtpDkimDomain: "Имя Домена", smtpDkimDomain: "Имя Домена",
smtpDkimKeySelector: "Ключ", smtpDkimKeySelector: "Ключ",
smtpDkimPrivateKey: "Приватный ключ", smtpDkimPrivateKey: "Приватный ключ",
@ -389,4 +389,12 @@ export default {
alertaApiKey: "Ключ API", alertaApiKey: "Ключ API",
alertaAlertState: "Состояние алерта", alertaAlertState: "Состояние алерта",
alertaRecoverState: "Состояние восстановления", alertaRecoverState: "Состояние восстановления",
Proxies: "Прокси",
default: "По умолчанию",
enabled: "Включено",
setAsDefault: "Установлено по умолчанию",
deleteProxyMsg: "Вы действительно хотите удалить этот прокси для всех мониторов?",
proxyDescription: "Прокси должны быть привязаны к монитору, чтобы работать.",
enableProxyDescription: "Этот прокси не будет влиять на запросы монитора, пока не будет активирован. Вы можете контролировать временное отключение прокси для всех мониторов через статус активации.",
setAsDefaultProxyDescription: "Этот прокси будет по умолчанию включен для новых мониторов. Вы всё ещё можете отдельно отключать прокси в каждом мониторе.",
}; };

@ -449,4 +449,13 @@ export default {
"Issuer:": "颁发者:", "Issuer:": "颁发者:",
"Fingerprint:": "指纹:", "Fingerprint:": "指纹:",
"No status pages": "无状态页", "No status pages": "无状态页",
"Domain Name Expiry Notification": "域名到期时通知",
"Proxy": "代理",
"Date Created": "创建于",
onebotHttpAddress: "OneBot HTTP 地址",
onebotMessageType: "OneBot 消息类型",
onebotGroupMessage: "群聊",
onebotPrivateMessage: "私聊",
onebotUserOrGroupId: "群组/用户ID",
onebotSafetyTips: "出于安全原因请务必设置AccessToken",
}; };

@ -3,5 +3,6 @@
</template> </template>
<script> <script>
export default {} export default {};
</script> </script>

@ -1,6 +1,6 @@
import { io } from "socket.io-client"; import { io } from "socket.io-client";
import { useToast } from "vue-toastification"; import { useToast } from "vue-toastification";
import jwt_decode from "jwt-decode"; import jwtDecode from "jwt-decode";
import Favico from "favico.js"; import Favico from "favico.js";
const toast = useToast(); const toast = useToast();
@ -266,7 +266,7 @@ export default {
const jwtToken = this.$root.storage().token; const jwtToken = this.$root.storage().token;
if (jwtToken && jwtToken !== "autoLogin") { if (jwtToken && jwtToken !== "autoLogin") {
return jwt_decode(jwtToken); return jwtDecode(jwtToken);
} }
return undefined; return undefined;
}, },

@ -25,9 +25,9 @@ export default {
MonitorList, MonitorList,
}, },
data() { data() {
return {} return {};
}, },
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

@ -118,6 +118,7 @@ export default {
return 0; return 0;
}); });
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
this.heartBeatList = result; this.heartBeatList = result;
return result; return result;

@ -11,6 +11,6 @@ export default {
components: { components: {
MonitorList, MonitorList,
}, },
} };
</script> </script>

@ -92,7 +92,6 @@ export default {
} }
.info { .info {
.title { .title {
font-weight: bold; font-weight: bold;
font-size: 20px; font-size: 20px;

@ -283,6 +283,7 @@ const toast = useToast();
const leavePageMsg = "Do you really want to leave? you have unsaved changes!"; const leavePageMsg = "Do you really want to leave? you have unsaved changes!";
// eslint-disable-next-line no-unused-vars
let feedInterval; let feedInterval;
const favicon = new Favico({ const favicon = new Favico({
@ -864,7 +865,7 @@ footer {
.incident, .customize { .incident, .customize {
.content { .content {
&[contenteditable=true] { &[contenteditable="true"] {
min-height: 60px; min-height: 60px;
} }
} }

@ -7,7 +7,7 @@
// Backend uses the compiled file util.js // Backend uses the compiled file util.js
// Frontend uses util.ts // Frontend uses util.ts
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0; exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0;
const _dayjs = require("dayjs"); const _dayjs = require("dayjs");
const dayjs = _dayjs; const dayjs = _dayjs;
exports.isDev = process.env.NODE_ENV === "development"; exports.isDev = process.env.NODE_ENV === "development";
@ -44,12 +44,60 @@ function ucfirst(str) {
return firstLetter.toUpperCase() + str.substr(1); return firstLetter.toUpperCase() + str.substr(1);
} }
exports.ucfirst = ucfirst; exports.ucfirst = ucfirst;
/**
* @deprecated Use log.debug
* @since https://github.com/louislam/uptime-kuma/pull/910
* @param msg
*/
function debug(msg) { function debug(msg) {
if (exports.isDev) { exports.log.log("", msg, "debug");
console.log(msg);
}
} }
exports.debug = debug; exports.debug = debug;
class Logger {
log(module, msg, level) {
module = module.toUpperCase();
level = level.toUpperCase();
const now = new Date().toISOString();
const formattedMessage = (typeof msg === "string") ? `${now} [${module}] ${level}: ${msg}` : msg;
if (level === "INFO") {
console.info(formattedMessage);
}
else if (level === "WARN") {
console.warn(formattedMessage);
}
else if (level === "ERROR") {
console.error(formattedMessage);
}
else if (level === "DEBUG") {
if (exports.isDev) {
console.debug(formattedMessage);
}
}
else {
console.log(formattedMessage);
}
}
info(module, msg) {
this.log(module, msg, "info");
}
warn(module, msg) {
this.log(module, msg, "warn");
}
error(module, msg) {
this.log(module, msg, "error");
}
debug(module, msg) {
this.log(module, msg, "debug");
}
exception(module, exception, msg) {
let finalMessage = exception;
if (msg) {
finalMessage = `${msg}: ${exception}`;
}
this.log(module, finalMessage, "error");
}
}
exports.log = new Logger();
function polyfill() { function polyfill() {
/** /**
* String.prototype.replaceAll() polyfill * String.prototype.replaceAll() polyfill
@ -121,13 +169,6 @@ let getRandomBytes = ((typeof window !== 'undefined' && window.crypto)
: function () { : function () {
return require("crypto").randomBytes; return require("crypto").randomBytes;
})(); })();
/**
* Returns a random integer between min (inclusive) and max (exclusive).
* @param {number} min The minimum value.
* @param {number} max The maximum value.
*
* Generated by Trelent
*/
function getCryptoRandomInt(min, max) { function getCryptoRandomInt(min, max) {
// synchronous version of: https://github.com/joepie91/node-random-number-csprng // synchronous version of: https://github.com/joepie91/node-random-number-csprng
const range = max - min; const range = max - min;
@ -158,13 +199,6 @@ function getCryptoRandomInt(min, max) {
} }
} }
exports.getCryptoRandomInt = getCryptoRandomInt; exports.getCryptoRandomInt = getCryptoRandomInt;
/**
* Generates a random string of length `length` from the characters in `chars`.
* @param {number} length The number of characters to generate.
* @param {string} chars A string containing all the possible characters to use for generating the random string.
*
* Generated by Trelent
*/
function genSecret(length = 64) { function genSecret(length = 64) {
let secret = ""; let secret = "";
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

@ -49,12 +49,66 @@ export function ucfirst(str: string) {
return firstLetter.toUpperCase() + str.substr(1); return firstLetter.toUpperCase() + str.substr(1);
} }
/**
* @deprecated Use log.debug
* @since https://github.com/louislam/uptime-kuma/pull/910
* @param msg
*/
export function debug(msg: any) { export function debug(msg: any) {
if (isDev) { log.log("", msg, "debug");
console.log(msg); }
class Logger {
log(module: string, msg: any, level: string) {
module = module.toUpperCase();
level = level.toUpperCase();
const now = new Date().toISOString();
const formattedMessage = (typeof msg === "string") ? `${now} [${module}] ${level}: ${msg}` : msg;
if (level === "INFO") {
console.info(formattedMessage);
} else if (level === "WARN") {
console.warn(formattedMessage);
} else if (level === "ERROR") {
console.error(formattedMessage);
} else if (level === "DEBUG") {
if (isDev) {
console.debug(formattedMessage);
}
} else {
console.log(formattedMessage);
}
}
info(module: string, msg: any) {
this.log(module, msg, "info");
}
warn(module: string, msg: any) {
this.log(module, msg, "warn");
}
error(module: string, msg: any) {
this.log(module, msg, "error");
}
debug(module: string, msg: any) {
this.log(module, msg, "debug");
}
exception(module: string, exception: any, msg: any) {
let finalMessage = exception
if (msg) {
finalMessage = `${msg}: ${exception}`
}
this.log(module, finalMessage , "error");
} }
} }
export const log = new Logger();
declare global { interface String { replaceAll(str: string, newStr: string): string; } } declare global { interface String { replaceAll(str: string, newStr: string): string; } }

@ -1,4 +1,4 @@
const { genSecret, sleep } = require("../src/util"); const { genSecret } = require("../src/util");
const utilServerRewire = require("../server/util-server"); const utilServerRewire = require("../server/util-server");
describe("Test parseCertificateInfo", () => { describe("Test parseCertificateInfo", () => {

@ -1,7 +1,6 @@
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const { Page, Browser } = require("puppeteer"); const { Page, Browser } = require("puppeteer");
const { sleep } = require("../src/util"); const { sleep } = require("../src/util");
const axios = require("axios");
/** /**
* Set back the correct data type for page object * Set back the correct data type for page object

Loading…
Cancel
Save