mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-06-12 15:42:35 +02:00
Merge branch 'louislam:master' into master
This commit is contained in:
commit
a1a17a0d25
23 changed files with 722 additions and 227 deletions
93
package-lock.json
generated
93
package-lock.json
generated
|
@ -19,6 +19,7 @@
|
||||||
"axios": "~0.21.4",
|
"axios": "~0.21.4",
|
||||||
"bcryptjs": "~2.4.3",
|
"bcryptjs": "~2.4.3",
|
||||||
"bootstrap": "~5.1.1",
|
"bootstrap": "~5.1.1",
|
||||||
|
"chardet": "^1.3.0",
|
||||||
"chart.js": "~3.5.1",
|
"chart.js": "~3.5.1",
|
||||||
"chartjs-adapter-dayjs": "~1.0.0",
|
"chartjs-adapter-dayjs": "~1.0.0",
|
||||||
"command-exists": "~1.2.9",
|
"command-exists": "~1.2.9",
|
||||||
|
@ -28,6 +29,7 @@
|
||||||
"express-basic-auth": "~1.2.0",
|
"express-basic-auth": "~1.2.0",
|
||||||
"form-data": "~4.0.0",
|
"form-data": "~4.0.0",
|
||||||
"http-graceful-shutdown": "~3.1.4",
|
"http-graceful-shutdown": "~3.1.4",
|
||||||
|
"iconv-lite": "^0.6.3",
|
||||||
"jsonwebtoken": "~8.5.1",
|
"jsonwebtoken": "~8.5.1",
|
||||||
"nodemailer": "~6.6.5",
|
"nodemailer": "~6.6.5",
|
||||||
"notp": "~2.0.3",
|
"notp": "~2.0.3",
|
||||||
|
@ -64,7 +66,7 @@
|
||||||
"@vitejs/plugin-legacy": "~1.6.1",
|
"@vitejs/plugin-legacy": "~1.6.1",
|
||||||
"@vitejs/plugin-vue": "~1.9.2",
|
"@vitejs/plugin-vue": "~1.9.2",
|
||||||
"@vue/compiler-sfc": "~3.2.19",
|
"@vue/compiler-sfc": "~3.2.19",
|
||||||
"babel-plugin-rewire": "^1.2.0",
|
"babel-plugin-rewire": "~1.2.0",
|
||||||
"core-js": "~3.18.1",
|
"core-js": "~3.18.1",
|
||||||
"cross-env": "~7.0.3",
|
"cross-env": "~7.0.3",
|
||||||
"dns2": "~2.0.1",
|
"dns2": "~2.0.1",
|
||||||
|
@ -3551,6 +3553,17 @@
|
||||||
"ms": "2.0.0"
|
"ms": "2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/body-parser/node_modules/iconv-lite": {
|
||||||
|
"version": "0.4.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
|
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/body-parser/node_modules/ms": {
|
"node_modules/body-parser/node_modules/ms": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
|
@ -3813,6 +3826,11 @@
|
||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/chardet": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chardet/-/chardet-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-cyTQGGptIjIT+CMGT5J/0l9c6Fb+565GCFjjeUTKxUO7w3oR+FcNCMEKTn5xtVKaLFmladN7QF68IiQsv5Fbdw=="
|
||||||
|
},
|
||||||
"node_modules/chart.js": {
|
"node_modules/chart.js": {
|
||||||
"version": "3.5.1",
|
"version": "3.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.5.1.tgz",
|
||||||
|
@ -6303,11 +6321,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/iconv-lite": {
|
"node_modules/iconv-lite": {
|
||||||
"version": "0.4.24",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"safer-buffer": ">= 2.1.2 < 3"
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
|
@ -9527,6 +9545,17 @@
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/raw-body/node_modules/iconv-lite": {
|
||||||
|
"version": "0.4.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
|
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "17.0.2",
|
"version": "17.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||||
|
@ -11978,6 +12007,18 @@
|
||||||
"iconv-lite": "0.4.24"
|
"iconv-lite": "0.4.24"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/whatwg-encoding/node_modules/iconv-lite": {
|
||||||
|
"version": "0.4.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
|
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/whatwg-mimetype": {
|
"node_modules/whatwg-mimetype": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz",
|
||||||
|
@ -14905,6 +14946,14 @@
|
||||||
"ms": "2.0.0"
|
"ms": "2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"iconv-lite": {
|
||||||
|
"version": "0.4.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
|
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||||
|
"requires": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"ms": {
|
"ms": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
|
@ -15090,6 +15139,11 @@
|
||||||
"integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==",
|
"integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"chardet": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chardet/-/chardet-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-cyTQGGptIjIT+CMGT5J/0l9c6Fb+565GCFjjeUTKxUO7w3oR+FcNCMEKTn5xtVKaLFmladN7QF68IiQsv5Fbdw=="
|
||||||
|
},
|
||||||
"chart.js": {
|
"chart.js": {
|
||||||
"version": "3.5.1",
|
"version": "3.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.5.1.tgz",
|
||||||
|
@ -16967,11 +17021,11 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"iconv-lite": {
|
"iconv-lite": {
|
||||||
"version": "0.4.24",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"safer-buffer": ">= 2.1.2 < 3"
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ieee754": {
|
"ieee754": {
|
||||||
|
@ -19393,6 +19447,16 @@
|
||||||
"http-errors": "1.7.2",
|
"http-errors": "1.7.2",
|
||||||
"iconv-lite": "0.4.24",
|
"iconv-lite": "0.4.24",
|
||||||
"unpipe": "1.0.0"
|
"unpipe": "1.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"iconv-lite": {
|
||||||
|
"version": "0.4.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
|
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||||
|
"requires": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-is": {
|
"react-is": {
|
||||||
|
@ -21251,6 +21315,17 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"iconv-lite": "0.4.24"
|
"iconv-lite": "0.4.24"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"iconv-lite": {
|
||||||
|
"version": "0.4.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
|
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"whatwg-mimetype": {
|
"whatwg-mimetype": {
|
||||||
|
|
|
@ -60,9 +60,9 @@
|
||||||
"@popperjs/core": "~2.10.2",
|
"@popperjs/core": "~2.10.2",
|
||||||
"args-parser": "~1.3.0",
|
"args-parser": "~1.3.0",
|
||||||
"axios": "~0.21.4",
|
"axios": "~0.21.4",
|
||||||
"babel-plugin-rewire": "~1.2.0",
|
|
||||||
"bcryptjs": "~2.4.3",
|
"bcryptjs": "~2.4.3",
|
||||||
"bootstrap": "~5.1.1",
|
"bootstrap": "~5.1.1",
|
||||||
|
"chardet": "^1.3.0",
|
||||||
"chart.js": "~3.5.1",
|
"chart.js": "~3.5.1",
|
||||||
"chartjs-adapter-dayjs": "~1.0.0",
|
"chartjs-adapter-dayjs": "~1.0.0",
|
||||||
"command-exists": "~1.2.9",
|
"command-exists": "~1.2.9",
|
||||||
|
@ -72,6 +72,7 @@
|
||||||
"express-basic-auth": "~1.2.0",
|
"express-basic-auth": "~1.2.0",
|
||||||
"form-data": "~4.0.0",
|
"form-data": "~4.0.0",
|
||||||
"http-graceful-shutdown": "~3.1.4",
|
"http-graceful-shutdown": "~3.1.4",
|
||||||
|
"iconv-lite": "^0.6.3",
|
||||||
"jsonwebtoken": "~8.5.1",
|
"jsonwebtoken": "~8.5.1",
|
||||||
"nodemailer": "~6.6.5",
|
"nodemailer": "~6.6.5",
|
||||||
"notp": "~2.0.3",
|
"notp": "~2.0.3",
|
||||||
|
@ -108,6 +109,7 @@
|
||||||
"@vitejs/plugin-legacy": "~1.6.1",
|
"@vitejs/plugin-legacy": "~1.6.1",
|
||||||
"@vitejs/plugin-vue": "~1.9.2",
|
"@vitejs/plugin-vue": "~1.9.2",
|
||||||
"@vue/compiler-sfc": "~3.2.19",
|
"@vue/compiler-sfc": "~3.2.19",
|
||||||
|
"babel-plugin-rewire": "~1.2.0",
|
||||||
"core-js": "~3.18.1",
|
"core-js": "~3.18.1",
|
||||||
"cross-env": "~7.0.3",
|
"cross-env": "~7.0.3",
|
||||||
"dns2": "~2.0.1",
|
"dns2": "~2.0.1",
|
||||||
|
|
|
@ -292,54 +292,13 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
let beatInterval = this.interval;
|
let beatInterval = this.interval;
|
||||||
|
|
||||||
// * ? -> ANY STATUS = important [isFirstBeat]
|
let isImportant = Monitor.isImportantBeat(isFirstBeat, previousBeat.status, bean.status);
|
||||||
// UP -> PENDING = not important
|
|
||||||
// * UP -> DOWN = important
|
|
||||||
// UP -> UP = not important
|
|
||||||
// PENDING -> PENDING = not important
|
|
||||||
// * PENDING -> DOWN = important
|
|
||||||
// PENDING -> UP = not important
|
|
||||||
// DOWN -> PENDING = this case not exists
|
|
||||||
// DOWN -> DOWN = not important
|
|
||||||
// * DOWN -> UP = important
|
|
||||||
let isImportant = isFirstBeat ||
|
|
||||||
(previousBeat.status === UP && bean.status === DOWN) ||
|
|
||||||
(previousBeat.status === DOWN && bean.status === UP) ||
|
|
||||||
(previousBeat.status === PENDING && bean.status === DOWN);
|
|
||||||
|
|
||||||
// Mark as important if status changed, ignore pending pings,
|
// Mark as important if status changed, ignore pending pings,
|
||||||
// Don't notify if disrupted changes to up
|
// Don't notify if disrupted changes to up
|
||||||
if (isImportant) {
|
if (isImportant) {
|
||||||
bean.important = true;
|
bean.important = true;
|
||||||
|
await Monitor.sendNotification(isFirstBeat, this, bean);
|
||||||
// Send only if the first beat is DOWN
|
|
||||||
if (!isFirstBeat || bean.status === DOWN) {
|
|
||||||
let notificationList = await R.getAll("SELECT notification.* FROM notification, monitor_notification WHERE monitor_id = ? AND monitor_notification.notification_id = notification.id ", [
|
|
||||||
this.id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
let text;
|
|
||||||
if (bean.status === UP) {
|
|
||||||
text = "✅ Up";
|
|
||||||
} else {
|
|
||||||
text = "🔴 Down";
|
|
||||||
}
|
|
||||||
|
|
||||||
let msg = `[${this.name}] [${text}] ${bean.msg}`;
|
|
||||||
|
|
||||||
for (let notification of notificationList) {
|
|
||||||
try {
|
|
||||||
await Notification.send(JSON.parse(notification.config), msg, await this.toJSON(), bean.toJSON());
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Cannot send notification to " + notification.name);
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear Status Page Cache
|
|
||||||
apicache.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
bean.important = false;
|
bean.important = false;
|
||||||
}
|
}
|
||||||
|
@ -546,6 +505,53 @@ class Monitor extends BeanModel {
|
||||||
io.to(userID).emit("uptime", monitorID, duration, uptime);
|
io.to(userID).emit("uptime", monitorID, duration, uptime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static isImportantBeat(isFirstBeat, previousBeatStatus, currentBeatStatus) {
|
||||||
|
// * ? -> ANY STATUS = important [isFirstBeat]
|
||||||
|
// UP -> PENDING = not important
|
||||||
|
// * UP -> DOWN = important
|
||||||
|
// UP -> UP = not important
|
||||||
|
// PENDING -> PENDING = not important
|
||||||
|
// * PENDING -> DOWN = important
|
||||||
|
// PENDING -> UP = not important
|
||||||
|
// DOWN -> PENDING = this case not exists
|
||||||
|
// DOWN -> DOWN = not important
|
||||||
|
// * DOWN -> UP = important
|
||||||
|
let isImportant = isFirstBeat ||
|
||||||
|
(previousBeatStatus === UP && currentBeatStatus === DOWN) ||
|
||||||
|
(previousBeatStatus === DOWN && currentBeatStatus === UP) ||
|
||||||
|
(previousBeatStatus === PENDING && currentBeatStatus === DOWN);
|
||||||
|
return isImportant;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async sendNotification(isFirstBeat, monitor, bean) {
|
||||||
|
if (!isFirstBeat || bean.status === DOWN) {
|
||||||
|
let notificationList = await R.getAll("SELECT notification.* FROM notification, monitor_notification WHERE monitor_id = ? AND monitor_notification.notification_id = notification.id ", [
|
||||||
|
monitor.id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
let text;
|
||||||
|
if (bean.status === UP) {
|
||||||
|
text = "✅ Up";
|
||||||
|
} else {
|
||||||
|
text = "🔴 Down";
|
||||||
|
}
|
||||||
|
|
||||||
|
let msg = `[${monitor.name}] [${text}] ${bean.msg}`;
|
||||||
|
|
||||||
|
for (let notification of notificationList) {
|
||||||
|
try {
|
||||||
|
await Notification.send(JSON.parse(notification.config), msg, await monitor.toJSON(), bean.toJSON());
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Cannot send notification to " + notification.name);
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear Status Page Cache
|
||||||
|
apicache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Monitor;
|
module.exports = Monitor;
|
||||||
|
|
108
server/notification-providers/aliyun-sms.js
Normal file
108
server/notification-providers/aliyun-sms.js
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
const NotificationProvider = require("./notification-provider");
|
||||||
|
const { DOWN, UP } = require("../../src/util");
|
||||||
|
const { default: axios } = require("axios");
|
||||||
|
const Crypto = require("crypto");
|
||||||
|
const qs = require("qs");
|
||||||
|
|
||||||
|
class AliyunSMS extends NotificationProvider {
|
||||||
|
name = "AliyunSMS";
|
||||||
|
|
||||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||||
|
let okMsg = "Sent Successfully.";
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (heartbeatJSON != null) {
|
||||||
|
let msgBody = JSON.stringify({
|
||||||
|
name: monitorJSON["name"],
|
||||||
|
time: heartbeatJSON["time"],
|
||||||
|
status: this.statusToString(heartbeatJSON["status"]),
|
||||||
|
msg: heartbeatJSON["msg"],
|
||||||
|
});
|
||||||
|
if (this.sendSms(notification, msgBody)) {
|
||||||
|
return okMsg;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let msgBody = JSON.stringify({
|
||||||
|
name: "",
|
||||||
|
time: "",
|
||||||
|
status: "",
|
||||||
|
msg: msg,
|
||||||
|
});
|
||||||
|
if (this.sendSms(notification, msgBody)) {
|
||||||
|
return okMsg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.throwGeneralAxiosError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendSms(notification, msgbody) {
|
||||||
|
let params = {
|
||||||
|
PhoneNumbers: notification.phonenumber,
|
||||||
|
TemplateCode: notification.templateCode,
|
||||||
|
SignName: notification.signName,
|
||||||
|
TemplateParam: msgbody,
|
||||||
|
AccessKeyId: notification.accessKeyId,
|
||||||
|
Format: "JSON",
|
||||||
|
SignatureMethod: "HMAC-SHA1",
|
||||||
|
SignatureVersion: "1.0",
|
||||||
|
SignatureNonce: Math.random().toString(),
|
||||||
|
Timestamp: new Date().toISOString(),
|
||||||
|
Action: "SendSms",
|
||||||
|
Version: "2017-05-25",
|
||||||
|
};
|
||||||
|
|
||||||
|
params.Signature = this.sign(params, notification.secretAccessKey);
|
||||||
|
let config = {
|
||||||
|
method: "POST",
|
||||||
|
url: "http://dysmsapi.aliyuncs.com/",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
},
|
||||||
|
data: qs.stringify(params),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = await axios(config);
|
||||||
|
if (result.data.Message == "OK") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Aliyun request sign */
|
||||||
|
sign(param, AccessKeySecret) {
|
||||||
|
let param2 = {};
|
||||||
|
let data = [];
|
||||||
|
|
||||||
|
let oa = Object.keys(param).sort();
|
||||||
|
|
||||||
|
for (let i = 0; i < oa.length; i++) {
|
||||||
|
let key = oa[i];
|
||||||
|
param2[key] = param[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let key in param2) {
|
||||||
|
data.push(`${encodeURIComponent(key)}=${encodeURIComponent(param2[key])}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let StringToSign = `POST&${encodeURIComponent("/")}&${encodeURIComponent(data.join("&"))}`;
|
||||||
|
return Crypto
|
||||||
|
.createHmac("sha1", `${AccessKeySecret}&`)
|
||||||
|
.update(Buffer.from(StringToSign))
|
||||||
|
.digest("base64");
|
||||||
|
}
|
||||||
|
|
||||||
|
statusToString(status) {
|
||||||
|
switch (status) {
|
||||||
|
case DOWN:
|
||||||
|
return "DOWN";
|
||||||
|
case UP:
|
||||||
|
return "UP";
|
||||||
|
default:
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = AliyunSMS;
|
79
server/notification-providers/dingding.js
Normal file
79
server/notification-providers/dingding.js
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
const NotificationProvider = require("./notification-provider");
|
||||||
|
const { DOWN, UP } = require("../../src/util");
|
||||||
|
const { default: axios } = require("axios");
|
||||||
|
const Crypto = require("crypto");
|
||||||
|
|
||||||
|
class DingDing extends NotificationProvider {
|
||||||
|
name = "DingDing";
|
||||||
|
|
||||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||||
|
let okMsg = "Sent Successfully.";
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (heartbeatJSON != null) {
|
||||||
|
let params = {
|
||||||
|
msgtype: "markdown",
|
||||||
|
markdown: {
|
||||||
|
title: monitorJSON["name"],
|
||||||
|
text: `## [${this.statusToString(heartbeatJSON["status"])}] \n > ${heartbeatJSON["msg"]} \n > Time(UTC):${heartbeatJSON["time"]}`,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (this.sendToDingDing(notification, params)) {
|
||||||
|
return okMsg;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let params = {
|
||||||
|
msgtype: "text",
|
||||||
|
text: {
|
||||||
|
content: msg
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (this.sendToDingDing(notification, params)) {
|
||||||
|
return okMsg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.throwGeneralAxiosError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendToDingDing(notification, params) {
|
||||||
|
let timestamp = Date.now();
|
||||||
|
|
||||||
|
let config = {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
url: `${notification.webHookUrl}×tamp=${timestamp}&sign=${encodeURIComponent(this.sign(timestamp, notification.secretKey))}`,
|
||||||
|
data: JSON.stringify(params),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = await axios(config);
|
||||||
|
if (result.data.errmsg == "ok") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** DingDing sign */
|
||||||
|
sign(timestamp, secretKey) {
|
||||||
|
return Crypto
|
||||||
|
.createHmac("sha256", Buffer.from(secretKey, "utf8"))
|
||||||
|
.update(Buffer.from(`${timestamp}\n${secretKey}`, "utf8"))
|
||||||
|
.digest("base64");
|
||||||
|
}
|
||||||
|
|
||||||
|
statusToString(status) {
|
||||||
|
switch (status) {
|
||||||
|
case DOWN:
|
||||||
|
return "DOWN";
|
||||||
|
case UP:
|
||||||
|
return "UP";
|
||||||
|
default:
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = DingDing;
|
|
@ -39,8 +39,9 @@ class Slack extends NotificationProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
const time = heartbeatJSON["time"];
|
const time = heartbeatJSON["time"];
|
||||||
|
const textMsg = "Uptime Kuma Alert";
|
||||||
let data = {
|
let data = {
|
||||||
"text": "Uptime Kuma Alert",
|
"text": monitorJSON ? textMsg + `: ${monitorJSON.name}` : textMsg,
|
||||||
"channel": notification.slackchannel,
|
"channel": notification.slackchannel,
|
||||||
"username": notification.slackusername,
|
"username": notification.slackusername,
|
||||||
"icon_emoji": notification.slackiconemo,
|
"icon_emoji": notification.slackiconemo,
|
||||||
|
|
|
@ -1,5 +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");
|
||||||
|
|
||||||
class SMTP extends NotificationProvider {
|
class SMTP extends NotificationProvider {
|
||||||
|
|
||||||
|
@ -20,6 +21,56 @@ class SMTP extends NotificationProvider {
|
||||||
pass: notification.smtpPassword,
|
pass: notification.smtpPassword,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
// Lets start with default subject and empty string for custom one
|
||||||
|
let subject = msg;
|
||||||
|
|
||||||
|
// Change the subject if:
|
||||||
|
// - The msg ends with "Testing" or
|
||||||
|
// - Actual Up/Down Notification
|
||||||
|
if ((monitorJSON && heartbeatJSON) || msg.endsWith("Testing")) {
|
||||||
|
let customSubject = "";
|
||||||
|
|
||||||
|
// Our subject cannot end with whitespace it's often raise spam score
|
||||||
|
// Once I got "Cannot read property 'trim' of undefined", better be safe than sorry
|
||||||
|
if (notification.customSubject) {
|
||||||
|
customSubject = notification.customSubject.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If custom subject is not empty, change subject for notification
|
||||||
|
if (customSubject !== "") {
|
||||||
|
|
||||||
|
// Replace "MACROS" with corresponding variable
|
||||||
|
let replaceName = new RegExp("{{NAME}}", "g");
|
||||||
|
let replaceHostnameOrURL = new RegExp("{{HOSTNAME_OR_URL}}", "g");
|
||||||
|
let replaceStatus = new RegExp("{{STATUS}}", "g");
|
||||||
|
|
||||||
|
// Lets start with dummy values to simplify code
|
||||||
|
let monitorName = "Test";
|
||||||
|
let monitorHostnameOrURL = "testing.hostname";
|
||||||
|
let serviceStatus = "⚠️ Test";
|
||||||
|
|
||||||
|
if (monitorJSON !== null) {
|
||||||
|
monitorName = monitorJSON["name"];
|
||||||
|
|
||||||
|
if (monitorJSON["type"] === "http" || monitorJSON["type"] === "keyword") {
|
||||||
|
monitorHostnameOrURL = monitorJSON["url"];
|
||||||
|
} else {
|
||||||
|
monitorHostnameOrURL = monitorJSON["hostname"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (heartbeatJSON !== null) {
|
||||||
|
serviceStatus = (heartbeatJSON["status"] === DOWN) ? "🔴 Down" : "✅ Up";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Break replace to one by line for better readability
|
||||||
|
customSubject = customSubject.replace(replaceStatus, serviceStatus);
|
||||||
|
customSubject = customSubject.replace(replaceName, monitorName);
|
||||||
|
customSubject = customSubject.replace(replaceHostnameOrURL, monitorHostnameOrURL);
|
||||||
|
|
||||||
|
subject = customSubject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let transporter = nodemailer.createTransport(config);
|
let transporter = nodemailer.createTransport(config);
|
||||||
|
|
||||||
|
@ -34,7 +85,7 @@ class SMTP extends NotificationProvider {
|
||||||
cc: notification.smtpCC,
|
cc: notification.smtpCC,
|
||||||
bcc: notification.smtpBCC,
|
bcc: notification.smtpBCC,
|
||||||
to: notification.smtpTo,
|
to: notification.smtpTo,
|
||||||
subject: msg,
|
subject: subject,
|
||||||
text: bodyTextContent,
|
text: bodyTextContent,
|
||||||
tls: {
|
tls: {
|
||||||
rejectUnauthorized: notification.smtpIgnoreTLSError || false,
|
rejectUnauthorized: notification.smtpIgnoreTLSError || false,
|
||||||
|
|
|
@ -19,6 +19,8 @@ const Teams = require("./notification-providers/teams");
|
||||||
const Telegram = require("./notification-providers/telegram");
|
const Telegram = require("./notification-providers/telegram");
|
||||||
const Webhook = require("./notification-providers/webhook");
|
const Webhook = require("./notification-providers/webhook");
|
||||||
const Feishu = require("./notification-providers/feishu");
|
const Feishu = require("./notification-providers/feishu");
|
||||||
|
const AliyunSms = require("./notification-providers/aliyun-sms");
|
||||||
|
const DingDing = require("./notification-providers/dingding");
|
||||||
|
|
||||||
class Notification {
|
class Notification {
|
||||||
|
|
||||||
|
@ -31,6 +33,8 @@ class Notification {
|
||||||
|
|
||||||
const list = [
|
const list = [
|
||||||
new Apprise(),
|
new Apprise(),
|
||||||
|
new AliyunSms(),
|
||||||
|
new DingDing(),
|
||||||
new Discord(),
|
new Discord(),
|
||||||
new Teams(),
|
new Teams(),
|
||||||
new Gotify(),
|
new Gotify(),
|
||||||
|
|
|
@ -4,10 +4,7 @@ const net = require("net");
|
||||||
const spawn = require("child_process").spawn;
|
const spawn = require("child_process").spawn;
|
||||||
const events = require("events");
|
const events = require("events");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const WIN = /^win/.test(process.platform);
|
const util = require("./util-server");
|
||||||
const LIN = /^linux/.test(process.platform);
|
|
||||||
const MAC = /^darwin/.test(process.platform);
|
|
||||||
const FBSD = /^freebsd/.test(process.platform);
|
|
||||||
|
|
||||||
module.exports = Ping;
|
module.exports = Ping;
|
||||||
|
|
||||||
|
@ -23,12 +20,12 @@ function Ping(host, options) {
|
||||||
|
|
||||||
const timeout = 10;
|
const timeout = 10;
|
||||||
|
|
||||||
if (WIN) {
|
if (util.WIN) {
|
||||||
this._bin = "c:/windows/system32/ping.exe";
|
this._bin = "c:/windows/system32/ping.exe";
|
||||||
this._args = (options.args) ? options.args : [ "-n", "1", "-w", timeout * 1000, host ];
|
this._args = (options.args) ? options.args : [ "-n", "1", "-w", timeout * 1000, host ];
|
||||||
this._regmatch = /[><=]([0-9.]+?)ms/;
|
this._regmatch = /[><=]([0-9.]+?)ms/;
|
||||||
|
|
||||||
} else if (LIN) {
|
} else if (util.LIN) {
|
||||||
this._bin = "/bin/ping";
|
this._bin = "/bin/ping";
|
||||||
|
|
||||||
const defaultArgs = [ "-n", "-w", timeout, "-c", "1", host ];
|
const defaultArgs = [ "-n", "-w", timeout, "-c", "1", host ];
|
||||||
|
@ -40,7 +37,7 @@ function Ping(host, options) {
|
||||||
this._args = (options.args) ? options.args : defaultArgs;
|
this._args = (options.args) ? options.args : defaultArgs;
|
||||||
this._regmatch = /=([0-9.]+?) ms/;
|
this._regmatch = /=([0-9.]+?) ms/;
|
||||||
|
|
||||||
} else if (MAC) {
|
} else if (util.MAC) {
|
||||||
|
|
||||||
if (net.isIPv6(host) || options.ipv6) {
|
if (net.isIPv6(host) || options.ipv6) {
|
||||||
this._bin = "/sbin/ping6";
|
this._bin = "/sbin/ping6";
|
||||||
|
@ -51,7 +48,7 @@ function Ping(host, options) {
|
||||||
this._args = (options.args) ? options.args : [ "-n", "-t", timeout, "-c", "1", host ];
|
this._args = (options.args) ? options.args : [ "-n", "-t", timeout, "-c", "1", host ];
|
||||||
this._regmatch = /=([0-9.]+?) ms/;
|
this._regmatch = /=([0-9.]+?) ms/;
|
||||||
|
|
||||||
} else if (FBSD) {
|
} else if (util.FBSD) {
|
||||||
this._bin = "/sbin/ping";
|
this._bin = "/sbin/ping";
|
||||||
|
|
||||||
const defaultArgs = [ "-n", "-t", timeout, "-c", "1", host ];
|
const defaultArgs = [ "-n", "-t", timeout, "-c", "1", host ];
|
||||||
|
@ -101,6 +98,9 @@ Ping.prototype.send = function (callback) {
|
||||||
});
|
});
|
||||||
|
|
||||||
this._ping.stdout.on("data", function (data) { // log stdout
|
this._ping.stdout.on("data", function (data) { // log stdout
|
||||||
|
if (util.WIN) {
|
||||||
|
data = convertOutput(data);
|
||||||
|
}
|
||||||
this._stdout = (this._stdout || "") + data;
|
this._stdout = (this._stdout || "") + data;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -112,6 +112,9 @@ Ping.prototype.send = function (callback) {
|
||||||
});
|
});
|
||||||
|
|
||||||
this._ping.stderr.on("data", function (data) { // log stderr
|
this._ping.stderr.on("data", function (data) { // log stderr
|
||||||
|
if (util.WIN) {
|
||||||
|
data = convertOutput(data);
|
||||||
|
}
|
||||||
this._stderr = (this._stderr || "") + data;
|
this._stderr = (this._stderr || "") + data;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -157,3 +160,19 @@ Ping.prototype.start = function (callback) {
|
||||||
Ping.prototype.stop = function () {
|
Ping.prototype.stop = function () {
|
||||||
clearInterval(this._i);
|
clearInterval(this._i);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to convert to UTF-8 for Windows, as the ping's output on Windows is not UTF-8 and could be in other languages
|
||||||
|
* Thank @pemassi
|
||||||
|
* https://github.com/louislam/uptime-kuma/issues/570#issuecomment-941984094
|
||||||
|
* @param data
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function convertOutput(data) {
|
||||||
|
if (util.WIN) {
|
||||||
|
if (data) {
|
||||||
|
return util.convertToUTF8(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ 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 } = require("../../src/util");
|
const { UP, flipStatus, debug } = require("../../src/util");
|
||||||
let router = express.Router();
|
let router = express.Router();
|
||||||
|
|
||||||
let cache = apicache.middleware;
|
let cache = apicache.middleware;
|
||||||
|
@ -18,9 +18,10 @@ router.get("/api/entry-page", async (_, response) => {
|
||||||
|
|
||||||
router.get("/api/push/:pushToken", async (request, response) => {
|
router.get("/api/push/:pushToken", async (request, response) => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
let pushToken = request.params.pushToken;
|
let pushToken = request.params.pushToken;
|
||||||
let msg = request.query.msg || "OK";
|
let msg = request.query.msg || "OK";
|
||||||
let ping = request.query.ping;
|
let ping = request.query.ping || null;
|
||||||
|
|
||||||
let monitor = await R.findOne("monitor", " push_token = ? AND active = 1 ", [
|
let monitor = await R.findOne("monitor", " push_token = ? AND active = 1 ", [
|
||||||
pushToken
|
pushToken
|
||||||
|
@ -30,12 +31,40 @@ router.get("/api/push/:pushToken", async (request, response) => {
|
||||||
throw new Error("Monitor not found or not active.");
|
throw new Error("Monitor not found or not active.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const previousHeartbeat = await R.getRow(`
|
||||||
|
SELECT status, time FROM heartbeat
|
||||||
|
WHERE id = (select MAX(id) from heartbeat where monitor_id = ?)
|
||||||
|
`, [
|
||||||
|
monitor.id
|
||||||
|
]);
|
||||||
|
|
||||||
|
let status = UP;
|
||||||
|
if (monitor.isUpsideDown()) {
|
||||||
|
status = flipStatus(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
let isFirstBeat = true;
|
||||||
|
let previousStatus = status;
|
||||||
|
let duration = 0;
|
||||||
|
|
||||||
let bean = R.dispense("heartbeat");
|
let bean = R.dispense("heartbeat");
|
||||||
bean.monitor_id = monitor.id;
|
|
||||||
bean.time = R.isoDateTime(dayjs.utc());
|
bean.time = R.isoDateTime(dayjs.utc());
|
||||||
bean.status = UP;
|
|
||||||
|
if (previousHeartbeat) {
|
||||||
|
isFirstBeat = false;
|
||||||
|
previousStatus = previousHeartbeat.status;
|
||||||
|
duration = dayjs(bean.time).diff(dayjs(previousHeartbeat.time), "second");
|
||||||
|
}
|
||||||
|
|
||||||
|
debug("PreviousStatus: " + previousStatus);
|
||||||
|
debug("Current Status: " + status);
|
||||||
|
|
||||||
|
bean.important = Monitor.isImportantBeat(isFirstBeat, previousStatus, status);
|
||||||
|
bean.monitor_id = monitor.id;
|
||||||
|
bean.status = status;
|
||||||
bean.msg = msg;
|
bean.msg = msg;
|
||||||
bean.ping = ping;
|
bean.ping = ping;
|
||||||
|
bean.duration = duration;
|
||||||
|
|
||||||
await R.store(bean);
|
await R.store(bean);
|
||||||
|
|
||||||
|
@ -45,6 +74,11 @@ router.get("/api/push/:pushToken", async (request, response) => {
|
||||||
response.json({
|
response.json({
|
||||||
ok: true,
|
ok: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (bean.important) {
|
||||||
|
await Monitor.sendNotification(isFirstBeat, monitor, bean);
|
||||||
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
response.json({
|
response.json({
|
||||||
ok: false,
|
ok: false,
|
||||||
|
|
|
@ -43,7 +43,7 @@ console.log("Importing this project modules");
|
||||||
debug("Importing Monitor");
|
debug("Importing Monitor");
|
||||||
const Monitor = require("./model/monitor");
|
const Monitor = require("./model/monitor");
|
||||||
debug("Importing Settings");
|
debug("Importing Settings");
|
||||||
const { getSettings, setSettings, setting, initJWTSecret, checkLogin, startUnitTest } = require("./util-server");
|
const { getSettings, setSettings, setting, initJWTSecret, checkLogin, startUnitTest, FBSD } = require("./util-server");
|
||||||
|
|
||||||
debug("Importing Notification");
|
debug("Importing Notification");
|
||||||
const { Notification } = require("./notification");
|
const { Notification } = require("./notification");
|
||||||
|
@ -61,12 +61,22 @@ console.info("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 (::)
|
||||||
const hostname = process.env.HOST || args.host;
|
let hostname = process.env.UPTIME_KUMA_HOST || args.host;
|
||||||
const port = parseInt(process.env.PORT || args.port || 3001);
|
|
||||||
|
// Also read HOST if not FreeBSD, as HOST is a system environment variable in FreeBSD
|
||||||
|
if (!hostname && !FBSD) {
|
||||||
|
hostname = process.env.HOST;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hostname) {
|
||||||
|
console.log("Custom hostname: " + hostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
const port = parseInt(process.env.UPTIME_KUMA_PORT || process.env.PORT || args.port || 3001);
|
||||||
|
|
||||||
// SSL
|
// SSL
|
||||||
const sslKey = process.env.SSL_KEY || args["ssl-key"] || undefined;
|
const sslKey = process.env.UPTIME_KUMA_SSL_KEY || process.env.SSL_KEY || args["ssl-key"] || undefined;
|
||||||
const sslCert = process.env.SSL_CERT || args["ssl-cert"] || undefined;
|
const sslCert = process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || args["ssl-cert"] || undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run unit test after the server is ready
|
* Run unit test after the server is ready
|
||||||
|
|
|
@ -6,6 +6,14 @@ const passwordHash = require("./password-hash");
|
||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
const { Resolver } = require("dns");
|
const { Resolver } = require("dns");
|
||||||
const child_process = require("child_process");
|
const child_process = require("child_process");
|
||||||
|
const iconv = require("iconv-lite");
|
||||||
|
const chardet = require("chardet");
|
||||||
|
|
||||||
|
// From ping-lite
|
||||||
|
exports.WIN = /^win/.test(process.platform);
|
||||||
|
exports.LIN = /^linux/.test(process.platform);
|
||||||
|
exports.MAC = /^darwin/.test(process.platform);
|
||||||
|
exports.FBSD = /^freebsd/.test(process.platform);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Init or reset JWT secret
|
* Init or reset JWT secret
|
||||||
|
@ -312,3 +320,14 @@ exports.startUnitTest = async () => {
|
||||||
process.exit(code);
|
process.exit(code);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param body : Buffer
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
exports.convertToUTF8 = (body) => {
|
||||||
|
const guessEncoding = chardet.detect(body);
|
||||||
|
debug("Guess Encoding: " + guessEncoding);
|
||||||
|
const str = iconv.decode(body, guessEncoding);
|
||||||
|
return str.toString();
|
||||||
|
};
|
||||||
|
|
25
src/components/notifications/AliyunSms.vue
Normal file
25
src/components/notifications/AliyunSms.vue
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<template>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="accessKeyId" class="form-label">{{ $t("AccessKeyId") }}<span style="color: red;"><sup>*</sup></span></label>
|
||||||
|
<input id="accessKeyId" v-model="$parent.notification.accessKeyId" type="text" class="form-control" required>
|
||||||
|
|
||||||
|
<label for="secretAccessKey" class="form-label">{{ $t("SecretAccessKey") }}<span style="color: red;"><sup>*</sup></span></label>
|
||||||
|
<input id="secretAccessKey" v-model="$parent.notification.secretAccessKey" type="text" class="form-control" required>
|
||||||
|
|
||||||
|
<label for="phonenumber" class="form-label">{{ $t("Phonenumber") }}<span style="color: red;"><sup>*</sup></span></label>
|
||||||
|
<input id="phonenumber" v-model="$parent.notification.phonenumber" type="text" class="form-control" required>
|
||||||
|
|
||||||
|
<label for="templateCode" class="form-label">{{ $t("TemplateCode") }}<span style="color: red;"><sup>*</sup></span></label>
|
||||||
|
<input id="templateCode" v-model="$parent.notification.templateCode" type="text" class="form-control" required>
|
||||||
|
|
||||||
|
<label for="signName" class="form-label">{{ $t("SignName") }}<span style="color: red;"><sup>*</sup></span></label>
|
||||||
|
<input id="signName" v-model="$parent.notification.signName" type="text" class="form-control" required>
|
||||||
|
|
||||||
|
<div class="form-text">
|
||||||
|
<p>Sms template must contain parameters: <br> <code>${name} ${time} ${status} ${msg}</code></p>
|
||||||
|
<i18n-t tag="p" keypath="Read more:">
|
||||||
|
<a href="https://help.aliyun.com/document_detail/101414.html" target="_blank">https://help.aliyun.com/document_detail/101414.html</a>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
16
src/components/notifications/DingDing.vue
Normal file
16
src/components/notifications/DingDing.vue
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<template>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="WebHookUrl" class="form-label">{{ $t("WebHookUrl") }}<span style="color: red;"><sup>*</sup></span></label>
|
||||||
|
<input id="WebHookUrl" v-model="$parent.notification.webHookUrl" type="text" class="form-control" required>
|
||||||
|
|
||||||
|
<label for="secretKey" class="form-label">{{ $t("SecretKey") }}<span style="color: red;"><sup>*</sup></span></label>
|
||||||
|
<input id="secretKey" v-model="$parent.notification.secretKey" type="text" class="form-control" required>
|
||||||
|
|
||||||
|
<div class="form-text">
|
||||||
|
<p>For safety, must use secret key</p>
|
||||||
|
<i18n-t tag="p" keypath="Read more:">
|
||||||
|
<a href="https://developers.dingtalk.com/document/robots/custom-robot-access" target="_blank">https://developers.dingtalk.com/document/robots/custom-robot-access</a>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -5,7 +5,7 @@
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
<p><span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}</p>
|
<p><span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}</p>
|
||||||
</div>
|
</div>
|
||||||
<i18n-t tag="div" keypath="wayToGetTeamsURL" class="form-text">
|
<i18n-t tag="div" keypath="wayToGetTeamsURL" class="form-text">
|
||||||
<a
|
<a
|
||||||
href="https://www.feishu.cn/hc/zh-CN/articles/360024984973"
|
href="https://www.feishu.cn/hc/zh-CN/articles/360024984973"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|
|
@ -57,6 +57,18 @@
|
||||||
<label for="to-bcc" class="form-label">{{ $t("smtpBCC") }}</label>
|
<label for="to-bcc" class="form-label">{{ $t("smtpBCC") }}</label>
|
||||||
<input id="to-bcc" v-model="$parent.notification.smtpBCC" type="text" class="form-control" autocomplete="false" :required="!hasRecipient">
|
<input id="to-bcc" v-model="$parent.notification.smtpBCC" type="text" class="form-control" autocomplete="false" :required="!hasRecipient">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="subject-email" class="form-label">{{ $t("emailCustomSubject") }}</label>
|
||||||
|
<input id="subject-email" v-model="$parent.notification.customSubject" type="text" class="form-control" autocomplete="false" placeholder="">
|
||||||
|
<div v-pre class="form-text">
|
||||||
|
(leave blank for default one)<br />
|
||||||
|
{{NAME}}: Service Name<br />
|
||||||
|
{{HOSTNAME_OR_URL}}: Hostname or URL<br />
|
||||||
|
{{URL}}: URL<br />
|
||||||
|
{{STATUS}}: Status<br />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -18,6 +18,8 @@ import Pushbullet from "./Pushbullet.vue";
|
||||||
import Line from "./Line.vue";
|
import Line from "./Line.vue";
|
||||||
import Mattermost from "./Mattermost.vue";
|
import Mattermost from "./Mattermost.vue";
|
||||||
import Matrix from "./Matrix.vue";
|
import Matrix from "./Matrix.vue";
|
||||||
|
import AliyunSMS from "./AliyunSms.vue";
|
||||||
|
import DingDing from "./DingDing.vue";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manage all notification form.
|
* Manage all notification form.
|
||||||
|
@ -40,11 +42,13 @@ const NotificationFormList = {
|
||||||
"promosms": PromoSMS,
|
"promosms": PromoSMS,
|
||||||
"lunasea": LunaSea,
|
"lunasea": LunaSea,
|
||||||
"Feishu": Feishu,
|
"Feishu": Feishu,
|
||||||
|
"AliyunSMS": AliyunSMS,
|
||||||
"apprise": Apprise,
|
"apprise": Apprise,
|
||||||
"pushbullet": Pushbullet,
|
"pushbullet": Pushbullet,
|
||||||
"line": Line,
|
"line": Line,
|
||||||
"mattermost": Mattermost,
|
"mattermost": Mattermost,
|
||||||
"matrix": Matrix,
|
"matrix": Matrix,
|
||||||
|
"DingDing": DingDing
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NotificationFormList
|
export default NotificationFormList
|
||||||
|
|
|
@ -201,6 +201,7 @@ export default {
|
||||||
secureOptionTLS: "TLS (465)",
|
secureOptionTLS: "TLS (465)",
|
||||||
"Ignore TLS Error": "Ignore TLS Error",
|
"Ignore TLS Error": "Ignore TLS Error",
|
||||||
"From Email": "From Email",
|
"From Email": "From Email",
|
||||||
|
emailCustomSubject: "Custom Subject",
|
||||||
"To Email": "To Email",
|
"To Email": "To Email",
|
||||||
smtpCC: "CC",
|
smtpCC: "CC",
|
||||||
smtpBCC: "BCC",
|
smtpBCC: "BCC",
|
||||||
|
|
|
@ -116,39 +116,39 @@ export default {
|
||||||
Events: "Olaylar",
|
Events: "Olaylar",
|
||||||
Heartbeats: "Sağlık Durumları",
|
Heartbeats: "Sağlık Durumları",
|
||||||
"Auto Get": "Otomatik Al",
|
"Auto Get": "Otomatik Al",
|
||||||
retryCheckEverySecond: "Retry every {0} seconds.",
|
retryCheckEverySecond: "{0} Saniyede bir dene.",
|
||||||
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
|
enableDefaultNotificationDescription: "Bu bildirim her yeni serviste aktif olacaktır. Bildirimi servisler için ayrı ayrı deaktive edebilirsiniz. ",
|
||||||
importHandleDescription: "Choose 'Skip existing' if you want to skip every monitor or notification with the same name. 'Overwrite' will delete every existing monitor and notification.",
|
importHandleDescription: "Aynı isimdeki bütün servisleri ve bildirimleri atlamak için 'Var olanı atla' seçiniz. 'Üzerine yaz' var olan bütün servisleri ve bildirimleri silecektir. ",
|
||||||
confirmImportMsg: "Are you sure to import the backup? Please make sure you've selected the right import option.",
|
confirmImportMsg: "Yedeği içeri aktarmak istediğinize emin misiniz? Lütfen doğru içeri aktarma seçeneğini seçtiğinizden emin olunuz. ",
|
||||||
twoFAVerifyLabel: "Please type in your token to verify that 2FA is working",
|
twoFAVerifyLabel: "Lütfen tokeni yazarak 2FA doğrulamanın çalıştığından emin olunuz.",
|
||||||
tokenValidSettingsMsg: "Token is valid! You can now save the 2FA settings.",
|
tokenValidSettingsMsg: "Token geçerli! Şimdi 2FA ayarlarını kaydedebilirsiniz. ",
|
||||||
confirmEnableTwoFAMsg: "Are you sure you want to enable 2FA?",
|
confirmEnableTwoFAMsg: "2FA'ı etkinleştirmek istediğinizden emin misiniz?",
|
||||||
confirmDisableTwoFAMsg: "Are you sure you want to disable 2FA?",
|
confirmDisableTwoFAMsg: "2FA'ı devre dışı bırakmak istediğinize emin misiniz?",
|
||||||
"Heartbeat Retry Interval": "Heartbeat Retry Interval",
|
"Heartbeat Retry Interval": "Sağlık Dırımları Tekrar Deneme Sıklığı",
|
||||||
"Import Backup": "Import Backup",
|
"Import Backup": "Yedeği içe aktar",
|
||||||
"Export Backup": "Export Backup",
|
"Export Backup": "Yedeği dışa aktar",
|
||||||
Export: "Export",
|
Export: "Dışa aktar",
|
||||||
Import: "Import",
|
Import: "İçe aktar",
|
||||||
"Default enabled": "Default enabled",
|
"Default enabled": "Varsayılan etkinleştirilmiş",
|
||||||
"Apply on all existing monitors": "Apply on all existing monitors",
|
"Apply on all existing monitors": "Var olan bütün servislere uygula",
|
||||||
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
|
backupDescription: "Bütün servisleri ve bildirimleri JSON dosyasına yedekleyebilirsiniz.",
|
||||||
backupDescription2: "PS: History and event data is not included.",
|
backupDescription2: "Not: Geçmiş ve etkinlik verileri içinde değildir.",
|
||||||
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.",
|
backupDescription3: "Dışa aktarma dosyasında bildirim tokeni gibi hassas veriler bulunur, dikkatli bir şekilde saklayınız.",
|
||||||
alertNoFile: "Please select a file to import.",
|
alertNoFile: "İçeri aktarmak için bir dosya seçiniz.",
|
||||||
alertWrongFileType: "Please select a JSON file.",
|
alertWrongFileType: "Lütfen bir JSON dosyası seçiniz.",
|
||||||
"Clear all statistics": "Clear all Statistics",
|
"Clear all statistics": "Bütün istatistikleri temizle",
|
||||||
"Skip existing": "Skip existing",
|
"Skip existing": "Var olanı atla",
|
||||||
Overwrite: "Overwrite",
|
Overwrite: "Üzerine yaz",
|
||||||
Options: "Options",
|
Options: "Seçenekler",
|
||||||
"Keep both": "Keep both",
|
"Keep both": "İkisini sakla",
|
||||||
"Verify Token": "Verify Token",
|
"Verify Token": "Tokeni doğrula",
|
||||||
"Setup 2FA": "Setup 2FA",
|
"Setup 2FA": "2FA Kur",
|
||||||
"Enable 2FA": "Enable 2FA",
|
"Enable 2FA": "2FA Etkinleştir",
|
||||||
"Disable 2FA": "Disable 2FA",
|
"Disable 2FA": "2FA Devre dışı bırak",
|
||||||
"2FA Settings": "2FA Settings",
|
"2FA Settings": "2FA Ayarları",
|
||||||
"Two Factor Authentication": "Two Factor Authentication",
|
"Two Factor Authentication": "İki Faktörlü Kimlik Doğrulama (2FA)",
|
||||||
Active: "Active",
|
Active: "Aktif",
|
||||||
Inactive: "Inactive",
|
Inactive: "İnaktif",
|
||||||
Token: "Token",
|
Token: "Token",
|
||||||
"Show URI": "Show URI",
|
"Show URI": "Show URI",
|
||||||
Tags: "Tags",
|
Tags: "Tags",
|
||||||
|
|
|
@ -63,6 +63,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Hostname -->
|
||||||
<!-- TCP Port / Ping / DNS only -->
|
<!-- TCP Port / Ping / DNS only -->
|
||||||
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' " class="my-3">
|
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' " class="my-3">
|
||||||
<label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
|
<label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
|
||||||
|
@ -140,7 +141,7 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="monitor.type !== 'push'" class="my-3 form-check">
|
<div class="my-3 form-check">
|
||||||
<input id="upside-down" v-model="monitor.upsideDown" class="form-check-input" type="checkbox">
|
<input id="upside-down" v-model="monitor.upsideDown" class="form-check-input" type="checkbox">
|
||||||
<label class="form-check-label" for="upside-down">
|
<label class="form-check-label" for="upside-down">
|
||||||
{{ $t("Upside Down Mode") }}
|
{{ $t("Upside Down Mode") }}
|
||||||
|
|
236
src/util.js
236
src/util.js
|
@ -1,118 +1,118 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
// Common Util for frontend and backend
|
// Common Util for frontend and backend
|
||||||
//
|
//
|
||||||
// DOT NOT MODIFY util.js!
|
// DOT NOT MODIFY util.js!
|
||||||
// Need to run "tsc" to compile if there are any changes.
|
// Need to run "tsc" to compile if there are any changes.
|
||||||
//
|
//
|
||||||
// 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.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.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;
|
||||||
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";
|
||||||
exports.appName = "Uptime Kuma";
|
exports.appName = "Uptime Kuma";
|
||||||
exports.DOWN = 0;
|
exports.DOWN = 0;
|
||||||
exports.UP = 1;
|
exports.UP = 1;
|
||||||
exports.PENDING = 2;
|
exports.PENDING = 2;
|
||||||
exports.STATUS_PAGE_ALL_DOWN = 0;
|
exports.STATUS_PAGE_ALL_DOWN = 0;
|
||||||
exports.STATUS_PAGE_ALL_UP = 1;
|
exports.STATUS_PAGE_ALL_UP = 1;
|
||||||
exports.STATUS_PAGE_PARTIAL_DOWN = 2;
|
exports.STATUS_PAGE_PARTIAL_DOWN = 2;
|
||||||
function flipStatus(s) {
|
function flipStatus(s) {
|
||||||
if (s === exports.UP) {
|
if (s === exports.UP) {
|
||||||
return exports.DOWN;
|
return exports.DOWN;
|
||||||
}
|
}
|
||||||
if (s === exports.DOWN) {
|
if (s === exports.DOWN) {
|
||||||
return exports.UP;
|
return exports.UP;
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
exports.flipStatus = flipStatus;
|
exports.flipStatus = flipStatus;
|
||||||
function sleep(ms) {
|
function sleep(ms) {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
exports.sleep = sleep;
|
exports.sleep = sleep;
|
||||||
/**
|
/**
|
||||||
* PHP's ucfirst
|
* PHP's ucfirst
|
||||||
* @param str
|
* @param str
|
||||||
*/
|
*/
|
||||||
function ucfirst(str) {
|
function ucfirst(str) {
|
||||||
if (!str) {
|
if (!str) {
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
const firstLetter = str.substr(0, 1);
|
const firstLetter = str.substr(0, 1);
|
||||||
return firstLetter.toUpperCase() + str.substr(1);
|
return firstLetter.toUpperCase() + str.substr(1);
|
||||||
}
|
}
|
||||||
exports.ucfirst = ucfirst;
|
exports.ucfirst = ucfirst;
|
||||||
function debug(msg) {
|
function debug(msg) {
|
||||||
if (exports.isDev) {
|
if (exports.isDev) {
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.debug = debug;
|
exports.debug = debug;
|
||||||
function polyfill() {
|
function polyfill() {
|
||||||
/**
|
/**
|
||||||
* String.prototype.replaceAll() polyfill
|
* String.prototype.replaceAll() polyfill
|
||||||
* https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/
|
* https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/
|
||||||
* @author Chris Ferdinandi
|
* @author Chris Ferdinandi
|
||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
if (!String.prototype.replaceAll) {
|
if (!String.prototype.replaceAll) {
|
||||||
String.prototype.replaceAll = function (str, newStr) {
|
String.prototype.replaceAll = function (str, newStr) {
|
||||||
// If a regex pattern
|
// If a regex pattern
|
||||||
if (Object.prototype.toString.call(str).toLowerCase() === "[object regexp]") {
|
if (Object.prototype.toString.call(str).toLowerCase() === "[object regexp]") {
|
||||||
return this.replace(str, newStr);
|
return this.replace(str, newStr);
|
||||||
}
|
}
|
||||||
// If a string
|
// If a string
|
||||||
return this.replace(new RegExp(str, "g"), newStr);
|
return this.replace(new RegExp(str, "g"), newStr);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.polyfill = polyfill;
|
exports.polyfill = polyfill;
|
||||||
class TimeLogger {
|
class TimeLogger {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.startTime = dayjs().valueOf();
|
this.startTime = dayjs().valueOf();
|
||||||
}
|
}
|
||||||
print(name) {
|
print(name) {
|
||||||
if (exports.isDev && process.env.TIMELOGGER === "1") {
|
if (exports.isDev && process.env.TIMELOGGER === "1") {
|
||||||
console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms");
|
console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.TimeLogger = TimeLogger;
|
exports.TimeLogger = TimeLogger;
|
||||||
/**
|
/**
|
||||||
* Returns a random number between min (inclusive) and max (exclusive)
|
* Returns a random number between min (inclusive) and max (exclusive)
|
||||||
*/
|
*/
|
||||||
function getRandomArbitrary(min, max) {
|
function getRandomArbitrary(min, max) {
|
||||||
return Math.random() * (max - min) + min;
|
return Math.random() * (max - min) + min;
|
||||||
}
|
}
|
||||||
exports.getRandomArbitrary = getRandomArbitrary;
|
exports.getRandomArbitrary = getRandomArbitrary;
|
||||||
/**
|
/**
|
||||||
* From: https://stackoverflow.com/questions/1527803/generating-random-whole-numbers-in-javascript-in-a-specific-range
|
* From: https://stackoverflow.com/questions/1527803/generating-random-whole-numbers-in-javascript-in-a-specific-range
|
||||||
*
|
*
|
||||||
* Returns a random integer between min (inclusive) and max (inclusive).
|
* Returns a random integer between min (inclusive) and max (inclusive).
|
||||||
* The value is no lower than min (or the next integer greater than min
|
* The value is no lower than min (or the next integer greater than min
|
||||||
* if min isn't an integer) and no greater than max (or the next integer
|
* if min isn't an integer) and no greater than max (or the next integer
|
||||||
* lower than max if max isn't an integer).
|
* lower than max if max isn't an integer).
|
||||||
* Using Math.round() will give you a non-uniform distribution!
|
* Using Math.round() will give you a non-uniform distribution!
|
||||||
*/
|
*/
|
||||||
function getRandomInt(min, max) {
|
function getRandomInt(min, max) {
|
||||||
min = Math.ceil(min);
|
min = Math.ceil(min);
|
||||||
max = Math.floor(max);
|
max = Math.floor(max);
|
||||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
}
|
}
|
||||||
exports.getRandomInt = getRandomInt;
|
exports.getRandomInt = getRandomInt;
|
||||||
function genSecret(length = 64) {
|
function genSecret(length = 64) {
|
||||||
let secret = "";
|
let secret = "";
|
||||||
let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
let charsLength = chars.length;
|
let charsLength = chars.length;
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
secret += chars.charAt(Math.floor(Math.random() * charsLength));
|
secret += chars.charAt(Math.floor(Math.random() * charsLength));
|
||||||
}
|
}
|
||||||
return secret;
|
return secret;
|
||||||
}
|
}
|
||||||
exports.genSecret = genSecret;
|
exports.genSecret = genSecret;
|
||||||
function getMonitorRelativeURL(id) {
|
function getMonitorRelativeURL(id) {
|
||||||
return "/dashboard/" + id;
|
return "/dashboard/" + id;
|
||||||
}
|
}
|
||||||
exports.getMonitorRelativeURL = getMonitorRelativeURL;
|
exports.getMonitorRelativeURL = getMonitorRelativeURL;
|
||||||
|
|
|
@ -1,10 +1,37 @@
|
||||||
|
const { genSecret } = require("../src/util");
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("", () => {
|
describe("Test genSecret", () => {
|
||||||
|
|
||||||
it("should ", () => {
|
it("should be correct length", () => {
|
||||||
|
let secret = genSecret(-1);
|
||||||
|
expect(secret).toEqual("");
|
||||||
|
|
||||||
|
secret = genSecret(0);
|
||||||
|
expect(secret).toEqual("");
|
||||||
|
|
||||||
|
secret = genSecret(1);
|
||||||
|
expect(secret.length).toEqual(1);
|
||||||
|
|
||||||
|
secret = genSecret(2);
|
||||||
|
expect(secret.length).toEqual(2);
|
||||||
|
|
||||||
|
secret = genSecret(64);
|
||||||
|
expect(secret.length).toEqual(64);
|
||||||
|
|
||||||
|
secret = genSecret(9000);
|
||||||
|
expect(secret.length).toEqual(9000);
|
||||||
|
|
||||||
|
secret = genSecret(90000);
|
||||||
|
expect(secret.length).toEqual(90000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should contain first and last possible chars", () => {
|
||||||
|
let secret = genSecret(90000);
|
||||||
|
expect(secret).toContain("A");
|
||||||
|
expect(secret).toContain("9");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"compileOnSave": true,
|
"compileOnSave": true,
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"newLine": "LF",
|
||||||
"target": "es2018",
|
"target": "es2018",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"lib": [
|
"lib": [
|
||||||
|
|
Loading…
Add table
Reference in a new issue