diff --git a/README.md b/README.md
index 1dc492bf5..6caa1a85d 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ Try it!
https://demo.uptime.kuma.pet
-It is a 10 minutes live demo, all data will be deleted after that. The server is located at Tokyo, if you live far away from here, it may affact your experience. I suggest that you should install to try it.
+It is a temporary live demo, all data will be deleted after 10 minutes. The server is located at Tokyo, so if you live far from there it may affect your experience. I suggest that you should install and try it out for the best demo experience.
VPS is sponsored by Uptime Kuma sponsors on [Open Collective](https://opencollective.com/uptime-kuma)! Thank you so much!
@@ -25,7 +25,7 @@ VPS is sponsored by Uptime Kuma sponsors on [Open Collective](https://opencollec
* Monitoring uptime for HTTP(s) / TCP / Ping / DNS Record / Push.
* Fancy, Reactive, Fast UI/UX.
* Notifications via Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP), and [70+ notification services, click here for the full list](https://github.com/louislam/uptime-kuma/tree/master/src/components/notifications).
-* 20 seconds interval.
+* 20 second intervals.
* [Multi Languages](https://github.com/louislam/uptime-kuma/tree/master/src/languages)
* Simple Status Page
* Ping Chart
@@ -40,7 +40,7 @@ docker volume create uptime-kuma
docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name uptime-kuma louislam/uptime-kuma:1
```
-Browse to http://localhost:3001 after started.
+Browse to http://localhost:3001 after starting.
### 💪🏻 Without Docker
@@ -58,11 +58,11 @@ npm run setup
node server/server.js
# (Recommended) Option 2. Run in background using PM2
-# Install PM2 if you don't have: npm install pm2 -g
+# Install PM2 if you don't have it: npm install pm2 -g
pm2 start server/server.js --name uptime-kuma
```
-Browse to http://localhost:3001 after started.
+Browse to http://localhost:3001 after starting.
### Advanced Installation
@@ -124,7 +124,7 @@ You can discuss or ask for help in [Issues](https://github.com/louislam/uptime-k
### Subreddit
My Reddit account: louislamlam
-You can mention me if you ask question on Reddit.
+You can mention me if you ask a question on Reddit.
https://www.reddit.com/r/UptimeKuma/
## Contribute
diff --git a/SECURITY.md b/SECURITY.md
index 48ab9a72f..a0b2562f4 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -9,8 +9,8 @@ currently being supported with security updates.
| Version | Supported |
| ------- | ------------------ |
-| 1.7.X | :white_check_mark: |
-| < 1.7 | ❌ |
+| 1.8.X | :white_check_mark: |
+| <= 1.7.X | ❌ |
### Upgradable Docker Tags
diff --git a/db/patch-http-monitor-method-body-and-headers.sql b/db/patch-http-monitor-method-body-and-headers.sql
new file mode 100644
index 000000000..dc2526b4f
--- /dev/null
+++ b/db/patch-http-monitor-method-body-and-headers.sql
@@ -0,0 +1,13 @@
+-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
+BEGIN TRANSACTION;
+
+ALTER TABLE monitor
+ ADD method TEXT default 'GET' not null;
+
+ALTER TABLE monitor
+ ADD body TEXT default null;
+
+ALTER TABLE monitor
+ ADD headers TEXT default null;
+
+COMMIT;
diff --git a/extra/reset-password.js b/extra/reset-password.js
index be0395897..1b48dffd7 100644
--- a/extra/reset-password.js
+++ b/extra/reset-password.js
@@ -12,50 +12,59 @@ const rl = readline.createInterface({
output: process.stdout
});
-(async () => {
+const main = async () => {
Database.init(args);
await Database.connect();
try {
- const user = await R.findOne("user");
-
- if (! user) {
- throw new Error("user not found, have you installed?");
- }
-
- console.log("Found user: " + user.username);
-
- while (true) {
- let password = await question("New Password: ");
- let confirmPassword = await question("Confirm New Password: ");
-
- if (password === confirmPassword) {
- await user.resetPassword(password);
-
- // Reset all sessions by reset jwt secret
- await initJWTSecret();
-
- rl.close();
- break;
- } else {
- console.log("Passwords do not match, please try again.");
+ // No need to actually reset the password for testing, just make sure no connection problem. It is ok for now.
+ if (!process.env.TEST_BACKEND) {
+ const user = await R.findOne("user");
+ if (! user) {
+ throw new Error("user not found, have you installed?");
}
- }
- console.log("Password reset successfully.");
+ console.log("Found user: " + user.username);
+
+ while (true) {
+ let password = await question("New Password: ");
+ let confirmPassword = await question("Confirm New Password: ");
+
+ if (password === confirmPassword) {
+ await user.resetPassword(password);
+
+ // Reset all sessions by reset jwt secret
+ await initJWTSecret();
+
+ break;
+ } else {
+ console.log("Passwords do not match, please try again.");
+ }
+ }
+ console.log("Password reset successfully.");
+ }
} catch (e) {
console.error("Error: " + e.message);
}
await Database.close();
+ rl.close();
- console.log("Finished. You should restart the Uptime Kuma server.")
-})();
+ console.log("Finished.");
+};
function question(question) {
return new Promise((resolve) => {
rl.question(question, (answer) => {
resolve(answer);
- })
+ });
});
}
+
+if (!process.env.TEST_BACKEND) {
+ main();
+}
+
+module.exports = {
+ main,
+};
diff --git a/extra/update-language-files/index.js b/extra/update-language-files/index.js
index a90f9f363..7ba30cc05 100644
--- a/extra/update-language-files/index.js
+++ b/extra/update-language-files/index.js
@@ -26,10 +26,12 @@ const copyRecursiveSync = function (src, dest) {
}
};
-console.log("Arguments:", process.argv)
+console.log("Arguments:", process.argv);
const baseLangCode = process.argv[2] || "en";
console.log("Base Lang: " + baseLangCode);
-fs.rmdirSync("./languages", { recursive: true });
+if (fs.existsSync("./languages")) {
+ fs.rmdirSync("./languages", { recursive: true });
+}
copyRecursiveSync("../../src/languages", "./languages");
const en = (await import("./languages/en.js")).default;
@@ -39,7 +41,7 @@ console.log("Files:", files);
for (const file of files) {
if (!file.endsWith(".js")) {
- console.log("Skipping " + file)
+ console.log("Skipping " + file);
continue;
}
diff --git a/package-lock.json b/package-lock.json
index 296362633..22b45db48 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,6 +19,8 @@
"axios": "~0.21.4",
"bcryptjs": "~2.4.3",
"bootstrap": "~5.1.1",
+ "bree": "~6.3.1",
+ "chardet": "^1.3.0",
"chart.js": "~3.5.1",
"chartjs-adapter-dayjs": "~1.0.0",
"command-exists": "~1.2.9",
@@ -28,6 +30,7 @@
"express-basic-auth": "~1.2.0",
"form-data": "~4.0.0",
"http-graceful-shutdown": "~3.1.4",
+ "iconv-lite": "^0.6.3",
"jsonwebtoken": "~8.5.1",
"nodemailer": "~6.6.5",
"notp": "~2.0.3",
@@ -64,7 +67,7 @@
"@vitejs/plugin-legacy": "~1.6.1",
"@vitejs/plugin-vue": "~1.9.2",
"@vue/compiler-sfc": "~3.2.19",
- "babel-plugin-rewire": "^1.2.0",
+ "babel-plugin-rewire": "~1.2.0",
"core-js": "~3.18.1",
"cross-env": "~7.0.3",
"dns2": "~2.0.1",
@@ -1714,7 +1717,6 @@
"version": "7.15.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz",
"integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==",
- "dev": true,
"dependencies": {
"regenerator-runtime": "^0.13.4"
},
@@ -1817,6 +1819,14 @@
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
"dev": true
},
+ "node_modules/@breejs/later": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@breejs/later/-/later-4.0.2.tgz",
+ "integrity": "sha512-EN0SlbyYouBdtZis1htdsgGlwFePzkXPwdIeqaBaavxkJT1G2/bitc2LSixjv45z2njXslxlJI1mW2O/Gmrb+A==",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/@eslint/eslintrc": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz",
@@ -2662,6 +2672,11 @@
"@types/koa": "*"
}
},
+ "node_modules/@types/lodash": {
+ "version": "4.14.175",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.175.tgz",
+ "integrity": "sha512-XmdEOrKQ8a1Y/yxQFOMbC47G/V2VDO1GvMRnl4O75M4GW/abC5tnfzadQYkqEveqRM1dEJGFFegfPNA2vvx2iw=="
+ },
"node_modules/@types/mdast": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz",
@@ -3551,11 +3566,27 @@
"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": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
+ "node_modules/boolean": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.1.4.tgz",
+ "integrity": "sha512-3hx0kwU3uzG6ReQ3pnaFQPSktpBw6RHN3/ivDKEuU8g1XSfafowyvDnadjv1xp8IZqhtSukxlwv9bF6FhX8m0w=="
+ },
"node_modules/bootstrap": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.1.tgz",
@@ -3589,6 +3620,29 @@
"node": ">=8"
}
},
+ "node_modules/bree": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/bree/-/bree-6.3.1.tgz",
+ "integrity": "sha512-FADpEV5c+3ZuFIBothyyRUxZClJD2PetIo0lmqAFJ3ZMI9WsSmQmmstZ86Dy0G4Gyw3nPNdfYTjV7+9pPtlB8g==",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@breejs/later": "^4.0.2",
+ "boolean": "^3.0.2",
+ "bthreads": "^0.5.1",
+ "combine-errors": "^3.0.3",
+ "cron-validate": "^1.4.1",
+ "debug": "^4.3.1",
+ "human-interval": "^2.0.0",
+ "is-string-and-not-blank": "^0.0.2",
+ "is-valid-path": "^0.1.1",
+ "ms": "^2.1.2",
+ "p-wait-for": "3.1.0",
+ "safe-timers": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/browser-process-hrtime": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz",
@@ -3627,6 +3681,17 @@
"node-int64": "^0.4.0"
}
},
+ "node_modules/bthreads": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/bthreads/-/bthreads-0.5.1.tgz",
+ "integrity": "sha512-nK7Jo9ll+r1FRMNPWEFRTZMQrX6HhX8JjPAofxmbTNILHqWVIJPmWzCi9JlX/K0DL5AKZTFZg2Qser5C6gVs9A==",
+ "dependencies": {
+ "bufio": "~1.0.5"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
@@ -3688,6 +3753,14 @@
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
},
+ "node_modules/bufio": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/bufio/-/bufio-1.0.7.tgz",
+ "integrity": "sha512-bd1dDQhiC+bEbEfg56IdBv7faWa6OipMs/AFFFvtFnB3wAYjlwQpQRZ0pm6ZkgtfL0pILRXhKxOiQj6UzoMR7A==",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/bytes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
@@ -3813,6 +3886,11 @@
"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": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.5.1.tgz",
@@ -3975,6 +4053,15 @@
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz",
"integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw=="
},
+ "node_modules/combine-errors": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/combine-errors/-/combine-errors-3.0.3.tgz",
+ "integrity": "sha1-9N9nQAg+VwOjGBEQwrEFUfAD2oY=",
+ "dependencies": {
+ "custom-error-instance": "2.1.1",
+ "lodash.uniqby": "4.5.0"
+ }
+ },
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -4128,6 +4215,14 @@
"node": ">=10"
}
},
+ "node_modules/cron-validate": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/cron-validate/-/cron-validate-1.4.3.tgz",
+ "integrity": "sha512-N+qKw019oQBEPIP5Qwi8Z5XelQ00ThN6Maahwv+9UGu2u/b/MPb35zngMQI0T8pBoNiBrIXGlhvsmspNSYae/w==",
+ "dependencies": {
+ "yup": "0.32.9"
+ }
+ },
"node_modules/cross-env": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
@@ -4201,6 +4296,11 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.18.tgz",
"integrity": "sha512-RSU6Hyeg14am3Ah4VZEmeX8H7kLwEEirXe6aU2IPfKNvhXwTflK5HQRDNI0ypQXoqmm+QPyG2IaPuQE5zMwSIQ=="
},
+ "node_modules/custom-error-instance": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/custom-error-instance/-/custom-error-instance-2.1.1.tgz",
+ "integrity": "sha1-PPY5FIemYppiR+sMoM4ACBt+Nho="
+ },
"node_modules/cwd": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/cwd/-/cwd-0.10.0.tgz",
@@ -6293,6 +6393,14 @@
"node": ">= 6"
}
},
+ "node_modules/human-interval": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/human-interval/-/human-interval-2.0.1.tgz",
+ "integrity": "sha512-r4Aotzf+OtKIGQCB3odUowy4GfUDTy3aTWTfLd7ZF2gBCy3XW3v/dJLRefZnOFFnjqs5B1TypvS8WarpBkYUNQ==",
+ "dependencies": {
+ "numbered": "^1.1.0"
+ }
+ },
"node_modules/human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@@ -6303,11 +6411,11 @@
}
},
"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==",
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"dependencies": {
- "safer-buffer": ">= 2.1.2 < 3"
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
@@ -6577,6 +6685,36 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/is-invalid-path": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/is-invalid-path/-/is-invalid-path-0.1.0.tgz",
+ "integrity": "sha1-MHqFWzzxqTi0TqcNLGEQYFNxTzQ=",
+ "dependencies": {
+ "is-glob": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-invalid-path/node_modules/is-extglob": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
+ "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-invalid-path/node_modules/is-glob": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
+ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
+ "dependencies": {
+ "is-extglob": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@@ -6634,6 +6772,22 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/is-string-and-not-blank": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/is-string-and-not-blank/-/is-string-and-not-blank-0.0.2.tgz",
+ "integrity": "sha512-FyPGAbNVyZpTeDCTXnzuwbu9/WpNXbCfbHXLpCRpN4GANhS00eEIP5Ef+k5HYSNIzIhdN9zRDoBj6unscECvtQ==",
+ "dependencies": {
+ "is-string-blank": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=6.4.0"
+ }
+ },
+ "node_modules/is-string-blank": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-string-blank/-/is-string-blank-1.0.1.tgz",
+ "integrity": "sha512-9H+ZBCVs3L9OYqv8nuUAzpcT9OTgMD1yAWrG7ihlnibdkbtB850heAmYWxHuXc4CHy4lKeK69tN+ny1K7gBIrw=="
+ },
"node_modules/is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
@@ -6652,6 +6806,17 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/is-valid-path": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-valid-path/-/is-valid-path-0.1.1.tgz",
+ "integrity": "sha1-EQ+f90w39mPh7HkV60UfLbk6yd8=",
+ "dependencies": {
+ "is-invalid-path": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/is-windows": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz",
@@ -7809,6 +7974,51 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
+ "node_modules/lodash-es": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+ },
+ "node_modules/lodash._baseiteratee": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/lodash._baseiteratee/-/lodash._baseiteratee-4.7.0.tgz",
+ "integrity": "sha1-NKm1VDVycnw9sueO2uPA6eZr0QI=",
+ "dependencies": {
+ "lodash._stringtopath": "~4.8.0"
+ }
+ },
+ "node_modules/lodash._basetostring": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-4.12.0.tgz",
+ "integrity": "sha1-kyfJ3FFYhmt/pLnUL0Y45XZt2d8="
+ },
+ "node_modules/lodash._baseuniq": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz",
+ "integrity": "sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg=",
+ "dependencies": {
+ "lodash._createset": "~4.0.0",
+ "lodash._root": "~3.0.0"
+ }
+ },
+ "node_modules/lodash._createset": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/lodash._createset/-/lodash._createset-4.0.3.tgz",
+ "integrity": "sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY="
+ },
+ "node_modules/lodash._root": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz",
+ "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI="
+ },
+ "node_modules/lodash._stringtopath": {
+ "version": "4.8.0",
+ "resolved": "https://registry.npmjs.org/lodash._stringtopath/-/lodash._stringtopath-4.8.0.tgz",
+ "integrity": "sha1-lBvPDmQmbl/B1m/tCmlZVExXaCQ=",
+ "dependencies": {
+ "lodash._basetostring": "~4.12.0"
+ }
+ },
"node_modules/lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
@@ -7873,6 +8083,15 @@
"integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=",
"dev": true
},
+ "node_modules/lodash.uniqby": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.5.0.tgz",
+ "integrity": "sha1-o6F7v2LutiQPSRhG6XwcTipeHiE=",
+ "dependencies": {
+ "lodash._baseiteratee": "~4.7.0",
+ "lodash._baseuniq": "~4.6.0"
+ }
+ },
"node_modules/log-symbols": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
@@ -8283,6 +8502,11 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
+ "node_modules/nanoclone": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz",
+ "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA=="
+ },
"node_modules/nanoid": {
"version": "3.1.28",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.28.tgz",
@@ -8524,6 +8748,11 @@
"node": ">=0.10.0"
}
},
+ "node_modules/numbered": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/numbered/-/numbered-1.1.0.tgz",
+ "integrity": "sha512-pv/ue2Odr7IfYOO0byC1KgBI10wo5YDauLhxY6/saNzAdAs0r1SotGCPzzCLNPL0xtrAwWRialLu23AAu9xO1g=="
+ },
"node_modules/nwsapi": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz",
@@ -8634,6 +8863,14 @@
"node": ">=0.10.0"
}
},
+ "node_modules/p-finally": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
@@ -8660,6 +8897,17 @@
"node": ">=8"
}
},
+ "node_modules/p-timeout": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz",
+ "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==",
+ "dependencies": {
+ "p-finally": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
@@ -8668,6 +8916,17 @@
"node": ">=6"
}
},
+ "node_modules/p-wait-for": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-wait-for/-/p-wait-for-3.1.0.tgz",
+ "integrity": "sha512-0Uy19uhxbssHelu9ynDMcON6BmMk6pH8551CvxROhiz3Vx+yC4RqxjyIDk2V4ll0g9177RKT++PK4zcV58uJ7A==",
+ "dependencies": {
+ "p-timeout": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -9160,6 +9419,11 @@
"node": ">= 6"
}
},
+ "node_modules/property-expr": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.4.tgz",
+ "integrity": "sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg=="
+ },
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -9527,6 +9791,17 @@
"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": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
@@ -9704,8 +9979,7 @@
"node_modules/regenerator-runtime": {
"version": "0.13.9",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
- "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==",
- "dev": true
+ "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
},
"node_modules/regenerator-transform": {
"version": "0.14.5",
@@ -10124,6 +10398,11 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
+ "node_modules/safe-timers": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-timers/-/safe-timers-1.1.0.tgz",
+ "integrity": "sha1-xYroMl2407BnMi8KTvOgytZ6rYM="
+ },
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -11187,6 +11466,11 @@
"node": ">=0.6"
}
},
+ "node_modules/toposort": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
+ "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA="
+ },
"node_modules/tough-cookie": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz",
@@ -11978,6 +12262,18 @@
"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": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz",
@@ -12232,6 +12528,23 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/yup": {
+ "version": "0.32.9",
+ "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.9.tgz",
+ "integrity": "sha512-Ci1qN+i2H0XpY7syDQ0k5zKQ/DoxO0LzPg8PAR/X4Mpj6DqaeCoIYEEjDJwhArh3Fa7GWbQQVDZKeXYlSH4JMg==",
+ "dependencies": {
+ "@babel/runtime": "^7.10.5",
+ "@types/lodash": "^4.14.165",
+ "lodash": "^4.17.20",
+ "lodash-es": "^4.17.15",
+ "nanoclone": "^0.2.1",
+ "property-expr": "^2.0.4",
+ "toposort": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/zwitch": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz",
@@ -13386,7 +13699,6 @@
"version": "7.15.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz",
"integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==",
- "dev": true,
"requires": {
"regenerator-runtime": "^0.13.4"
}
@@ -13469,6 +13781,11 @@
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
"dev": true
},
+ "@breejs/later": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@breejs/later/-/later-4.0.2.tgz",
+ "integrity": "sha512-EN0SlbyYouBdtZis1htdsgGlwFePzkXPwdIeqaBaavxkJT1G2/bitc2LSixjv45z2njXslxlJI1mW2O/Gmrb+A=="
+ },
"@eslint/eslintrc": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz",
@@ -14170,6 +14487,11 @@
"@types/koa": "*"
}
},
+ "@types/lodash": {
+ "version": "4.14.175",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.175.tgz",
+ "integrity": "sha512-XmdEOrKQ8a1Y/yxQFOMbC47G/V2VDO1GvMRnl4O75M4GW/abC5tnfzadQYkqEveqRM1dEJGFFegfPNA2vvx2iw=="
+ },
"@types/mdast": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz",
@@ -14905,6 +15227,14 @@
"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": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@@ -14912,6 +15242,11 @@
}
}
},
+ "boolean": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.1.4.tgz",
+ "integrity": "sha512-3hx0kwU3uzG6ReQ3pnaFQPSktpBw6RHN3/ivDKEuU8g1XSfafowyvDnadjv1xp8IZqhtSukxlwv9bF6FhX8m0w=="
+ },
"bootstrap": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.1.tgz",
@@ -14936,6 +15271,26 @@
"fill-range": "^7.0.1"
}
},
+ "bree": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/bree/-/bree-6.3.1.tgz",
+ "integrity": "sha512-FADpEV5c+3ZuFIBothyyRUxZClJD2PetIo0lmqAFJ3ZMI9WsSmQmmstZ86Dy0G4Gyw3nPNdfYTjV7+9pPtlB8g==",
+ "requires": {
+ "@babel/runtime": "^7.12.5",
+ "@breejs/later": "^4.0.2",
+ "boolean": "^3.0.2",
+ "bthreads": "^0.5.1",
+ "combine-errors": "^3.0.3",
+ "cron-validate": "^1.4.1",
+ "debug": "^4.3.1",
+ "human-interval": "^2.0.0",
+ "is-string-and-not-blank": "^0.0.2",
+ "is-valid-path": "^0.1.1",
+ "ms": "^2.1.2",
+ "p-wait-for": "3.1.0",
+ "safe-timers": "^1.1.0"
+ }
+ },
"browser-process-hrtime": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz",
@@ -14964,6 +15319,14 @@
"node-int64": "^0.4.0"
}
},
+ "bthreads": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/bthreads/-/bthreads-0.5.1.tgz",
+ "integrity": "sha512-nK7Jo9ll+r1FRMNPWEFRTZMQrX6HhX8JjPAofxmbTNILHqWVIJPmWzCi9JlX/K0DL5AKZTFZg2Qser5C6gVs9A==",
+ "requires": {
+ "bufio": "~1.0.5"
+ }
+ },
"buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
@@ -15008,6 +15371,11 @@
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
},
+ "bufio": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/bufio/-/bufio-1.0.7.tgz",
+ "integrity": "sha512-bd1dDQhiC+bEbEfg56IdBv7faWa6OipMs/AFFFvtFnB3wAYjlwQpQRZ0pm6ZkgtfL0pILRXhKxOiQj6UzoMR7A=="
+ },
"bytes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
@@ -15090,6 +15458,11 @@
"integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==",
"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": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.5.1.tgz",
@@ -15221,6 +15594,15 @@
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz",
"integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw=="
},
+ "combine-errors": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/combine-errors/-/combine-errors-3.0.3.tgz",
+ "integrity": "sha1-9N9nQAg+VwOjGBEQwrEFUfAD2oY=",
+ "requires": {
+ "custom-error-instance": "2.1.1",
+ "lodash.uniqby": "4.5.0"
+ }
+ },
"combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -15343,6 +15725,14 @@
"yaml": "^1.10.0"
}
},
+ "cron-validate": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/cron-validate/-/cron-validate-1.4.3.tgz",
+ "integrity": "sha512-N+qKw019oQBEPIP5Qwi8Z5XelQ00ThN6Maahwv+9UGu2u/b/MPb35zngMQI0T8pBoNiBrIXGlhvsmspNSYae/w==",
+ "requires": {
+ "yup": "0.32.9"
+ }
+ },
"cross-env": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
@@ -15397,6 +15787,11 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.18.tgz",
"integrity": "sha512-RSU6Hyeg14am3Ah4VZEmeX8H7kLwEEirXe6aU2IPfKNvhXwTflK5HQRDNI0ypQXoqmm+QPyG2IaPuQE5zMwSIQ=="
},
+ "custom-error-instance": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/custom-error-instance/-/custom-error-instance-2.1.1.tgz",
+ "integrity": "sha1-PPY5FIemYppiR+sMoM4ACBt+Nho="
+ },
"cwd": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/cwd/-/cwd-0.10.0.tgz",
@@ -16960,6 +17355,14 @@
"debug": "4"
}
},
+ "human-interval": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/human-interval/-/human-interval-2.0.1.tgz",
+ "integrity": "sha512-r4Aotzf+OtKIGQCB3odUowy4GfUDTy3aTWTfLd7ZF2gBCy3XW3v/dJLRefZnOFFnjqs5B1TypvS8WarpBkYUNQ==",
+ "requires": {
+ "numbered": "^1.1.0"
+ }
+ },
"human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@@ -16967,11 +17370,11 @@
"dev": true
},
"iconv-lite": {
- "version": "0.4.24",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
- "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"requires": {
- "safer-buffer": ">= 2.1.2 < 3"
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
}
},
"ieee754": {
@@ -17154,6 +17557,29 @@
"integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==",
"dev": true
},
+ "is-invalid-path": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/is-invalid-path/-/is-invalid-path-0.1.0.tgz",
+ "integrity": "sha1-MHqFWzzxqTi0TqcNLGEQYFNxTzQ=",
+ "requires": {
+ "is-glob": "^2.0.0"
+ },
+ "dependencies": {
+ "is-extglob": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
+ "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA="
+ },
+ "is-glob": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
+ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
+ "requires": {
+ "is-extglob": "^1.0.0"
+ }
+ }
+ }
+ },
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@@ -17193,6 +17619,19 @@
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"dev": true
},
+ "is-string-and-not-blank": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/is-string-and-not-blank/-/is-string-and-not-blank-0.0.2.tgz",
+ "integrity": "sha512-FyPGAbNVyZpTeDCTXnzuwbu9/WpNXbCfbHXLpCRpN4GANhS00eEIP5Ef+k5HYSNIzIhdN9zRDoBj6unscECvtQ==",
+ "requires": {
+ "is-string-blank": "^1.0.1"
+ }
+ },
+ "is-string-blank": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-string-blank/-/is-string-blank-1.0.1.tgz",
+ "integrity": "sha512-9H+ZBCVs3L9OYqv8nuUAzpcT9OTgMD1yAWrG7ihlnibdkbtB850heAmYWxHuXc4CHy4lKeK69tN+ny1K7gBIrw=="
+ },
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
@@ -17205,6 +17644,14 @@
"integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
"dev": true
},
+ "is-valid-path": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-valid-path/-/is-valid-path-0.1.1.tgz",
+ "integrity": "sha1-EQ+f90w39mPh7HkV60UfLbk6yd8=",
+ "requires": {
+ "is-invalid-path": "^0.1.0"
+ }
+ },
"is-windows": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz",
@@ -18113,6 +18560,51 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
+ "lodash-es": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+ },
+ "lodash._baseiteratee": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/lodash._baseiteratee/-/lodash._baseiteratee-4.7.0.tgz",
+ "integrity": "sha1-NKm1VDVycnw9sueO2uPA6eZr0QI=",
+ "requires": {
+ "lodash._stringtopath": "~4.8.0"
+ }
+ },
+ "lodash._basetostring": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-4.12.0.tgz",
+ "integrity": "sha1-kyfJ3FFYhmt/pLnUL0Y45XZt2d8="
+ },
+ "lodash._baseuniq": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz",
+ "integrity": "sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg=",
+ "requires": {
+ "lodash._createset": "~4.0.0",
+ "lodash._root": "~3.0.0"
+ }
+ },
+ "lodash._createset": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/lodash._createset/-/lodash._createset-4.0.3.tgz",
+ "integrity": "sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY="
+ },
+ "lodash._root": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz",
+ "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI="
+ },
+ "lodash._stringtopath": {
+ "version": "4.8.0",
+ "resolved": "https://registry.npmjs.org/lodash._stringtopath/-/lodash._stringtopath-4.8.0.tgz",
+ "integrity": "sha1-lBvPDmQmbl/B1m/tCmlZVExXaCQ=",
+ "requires": {
+ "lodash._basetostring": "~4.12.0"
+ }
+ },
"lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
@@ -18177,6 +18669,15 @@
"integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=",
"dev": true
},
+ "lodash.uniqby": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.5.0.tgz",
+ "integrity": "sha1-o6F7v2LutiQPSRhG6XwcTipeHiE=",
+ "requires": {
+ "lodash._baseiteratee": "~4.7.0",
+ "lodash._baseuniq": "~4.6.0"
+ }
+ },
"log-symbols": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
@@ -18470,6 +18971,11 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
+ "nanoclone": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz",
+ "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA=="
+ },
"nanoid": {
"version": "3.1.28",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.28.tgz",
@@ -18657,6 +19163,11 @@
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
},
+ "numbered": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/numbered/-/numbered-1.1.0.tgz",
+ "integrity": "sha512-pv/ue2Odr7IfYOO0byC1KgBI10wo5YDauLhxY6/saNzAdAs0r1SotGCPzzCLNPL0xtrAwWRialLu23AAu9xO1g=="
+ },
"nwsapi": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz",
@@ -18737,6 +19248,11 @@
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
"dev": true
},
+ "p-finally": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
+ },
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
@@ -18754,11 +19270,27 @@
"p-limit": "^2.2.0"
}
},
+ "p-timeout": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz",
+ "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==",
+ "requires": {
+ "p-finally": "^1.0.0"
+ }
+ },
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
},
+ "p-wait-for": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-wait-for/-/p-wait-for-3.1.0.tgz",
+ "integrity": "sha512-0Uy19uhxbssHelu9ynDMcON6BmMk6pH8551CvxROhiz3Vx+yC4RqxjyIDk2V4ll0g9177RKT++PK4zcV58uJ7A==",
+ "requires": {
+ "p-timeout": "^3.0.0"
+ }
+ },
"parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -19124,6 +19656,11 @@
"sisteransi": "^1.0.5"
}
},
+ "property-expr": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.4.tgz",
+ "integrity": "sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg=="
+ },
"proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -19393,6 +19930,16 @@
"http-errors": "1.7.2",
"iconv-lite": "0.4.24",
"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": {
@@ -19550,8 +20097,7 @@
"regenerator-runtime": {
"version": "0.13.9",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
- "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==",
- "dev": true
+ "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
},
"regenerator-transform": {
"version": "0.14.5",
@@ -19856,6 +20402,11 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
+ "safe-timers": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-timers/-/safe-timers-1.1.0.tgz",
+ "integrity": "sha1-xYroMl2407BnMi8KTvOgytZ6rYM="
+ },
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -20717,6 +21268,11 @@
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
},
+ "toposort": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
+ "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA="
+ },
"tough-cookie": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz",
@@ -21251,6 +21807,17 @@
"dev": true,
"requires": {
"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": {
@@ -21446,6 +22013,20 @@
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
},
+ "yup": {
+ "version": "0.32.9",
+ "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.9.tgz",
+ "integrity": "sha512-Ci1qN+i2H0XpY7syDQ0k5zKQ/DoxO0LzPg8PAR/X4Mpj6DqaeCoIYEEjDJwhArh3Fa7GWbQQVDZKeXYlSH4JMg==",
+ "requires": {
+ "@babel/runtime": "^7.10.5",
+ "@types/lodash": "^4.14.165",
+ "lodash": "^4.17.20",
+ "lodash-es": "^4.17.15",
+ "nanoclone": "^0.2.1",
+ "property-expr": "^2.0.4",
+ "toposort": "^2.0.2"
+ }
+ },
"zwitch": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz",
diff --git a/package.json b/package.json
index 03112518a..ca64dbe58 100644
--- a/package.json
+++ b/package.json
@@ -60,9 +60,10 @@
"@popperjs/core": "~2.10.2",
"args-parser": "~1.3.0",
"axios": "~0.21.4",
- "babel-plugin-rewire": "~1.2.0",
"bcryptjs": "~2.4.3",
"bootstrap": "~5.1.1",
+ "chardet": "^1.3.0",
+ "bree": "~6.3.1",
"chart.js": "~3.5.1",
"chartjs-adapter-dayjs": "~1.0.0",
"command-exists": "~1.2.9",
@@ -72,6 +73,7 @@
"express-basic-auth": "~1.2.0",
"form-data": "~4.0.0",
"http-graceful-shutdown": "~3.1.4",
+ "iconv-lite": "^0.6.3",
"jsonwebtoken": "~8.5.1",
"nodemailer": "~6.6.5",
"notp": "~2.0.3",
@@ -108,6 +110,7 @@
"@vitejs/plugin-legacy": "~1.6.1",
"@vitejs/plugin-vue": "~1.9.2",
"@vue/compiler-sfc": "~3.2.19",
+ "babel-plugin-rewire": "~1.2.0",
"core-js": "~3.18.1",
"cross-env": "~7.0.3",
"dns2": "~2.0.1",
diff --git a/server/config.js b/server/config.js
new file mode 100644
index 000000000..24ccfaa14
--- /dev/null
+++ b/server/config.js
@@ -0,0 +1,7 @@
+const args = require("args-parser")(process.argv);
+const demoMode = args["demo"] || false;
+
+module.exports = {
+ args,
+ demoMode
+};
diff --git a/server/database.js b/server/database.js
index 47eca2835..1030ffdd9 100644
--- a/server/database.js
+++ b/server/database.js
@@ -49,10 +49,11 @@ class Database {
"patch-incident-table.sql": true,
"patch-group-table.sql": true,
"patch-monitor-push_token.sql": true,
+ "patch-http-monitor-method-body-and-headers.sql": true,
}
/**
- * The finally version should be 10 after merged tag feature
+ * The final version should be 10 after merged tag feature
* @deprecated Use patchList for any new feature
*/
static latestVersion = 10;
diff --git a/server/jobs.js b/server/jobs.js
new file mode 100644
index 000000000..8a768b91b
--- /dev/null
+++ b/server/jobs.js
@@ -0,0 +1,31 @@
+const path = require("path");
+const Bree = require("bree");
+const { SHARE_ENV } = require("worker_threads");
+
+const jobs = [
+ {
+ name: "clear-old-data",
+ interval: "at 03:14",
+ }
+];
+
+const initBackgroundJobs = function (args) {
+ const bree = new Bree({
+ root: path.resolve("server", "jobs"),
+ jobs,
+ worker: {
+ env: SHARE_ENV,
+ workerData: args,
+ },
+ workerMessageHandler: (message) => {
+ console.log("[Background Job]:", message);
+ }
+ });
+
+ bree.start();
+ return bree;
+};
+
+module.exports = {
+ initBackgroundJobs
+};
diff --git a/server/jobs/clear-old-data.js b/server/jobs/clear-old-data.js
new file mode 100644
index 000000000..7c368014d
--- /dev/null
+++ b/server/jobs/clear-old-data.js
@@ -0,0 +1,40 @@
+const { log, exit, connectDb } = require("./util-worker");
+const { R } = require("redbean-node");
+const { setSetting, setting } = require("../util-server");
+
+const DEFAULT_KEEP_PERIOD = 180;
+
+(async () => {
+ await connectDb();
+
+ let period = await setting("keepDataPeriodDays");
+
+ // Set Default Period
+ if (period == null) {
+ await setSetting("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general");
+ period = DEFAULT_KEEP_PERIOD;
+ }
+
+ // Try parse setting
+ let parsedPeriod;
+ try {
+ parsedPeriod = parseInt(period);
+ } catch (_) {
+ log("Failed to parse setting, resetting to default..");
+ await setSetting("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general");
+ parsedPeriod = DEFAULT_KEEP_PERIOD;
+ }
+
+ log(`Clearing Data older than ${parsedPeriod} days...`);
+
+ try {
+ await R.exec(
+ "DELETE FROM heartbeat WHERE time < DATETIME('now', '-' || ? || ' days') ",
+ [parsedPeriod]
+ );
+ } catch (e) {
+ log(`Failed to clear old data: ${e.message}`);
+ }
+
+ exit();
+})();
diff --git a/server/jobs/util-worker.js b/server/jobs/util-worker.js
new file mode 100644
index 000000000..9426840d7
--- /dev/null
+++ b/server/jobs/util-worker.js
@@ -0,0 +1,39 @@
+const { parentPort, workerData } = require("worker_threads");
+const Database = require("../database");
+const path = require("path");
+
+const log = function (any) {
+ if (parentPort) {
+ parentPort.postMessage(any);
+ }
+};
+
+const exit = function (error) {
+ if (error && error != 0) {
+ process.exit(error);
+ } else {
+ if (parentPort) {
+ parentPort.postMessage("done");
+ } else {
+ process.exit(0);
+ }
+ }
+};
+
+const connectDb = async function () {
+ const dbPath = path.join(
+ process.env.DATA_DIR || workerData["data-dir"] || "./data/"
+ );
+
+ Database.init({
+ "data-dir": dbPath,
+ });
+
+ await Database.connect();
+};
+
+module.exports = {
+ log,
+ exit,
+ connectDb,
+};
diff --git a/server/model/monitor.js b/server/model/monitor.js
index 161815277..fb736291c 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -7,11 +7,11 @@ dayjs.extend(timezone);
const axios = require("axios");
const { Prometheus } = require("../prometheus");
const { debug, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util");
-const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom } = require("../util-server");
+const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting } = require("../util-server");
const { R } = require("redbean-node");
const { BeanModel } = require("redbean-node/dist/bean-model");
const { Notification } = require("../notification");
-const { demoMode } = require("../server");
+const { demoMode } = require("../config");
const version = require("../../package.json").version;
const apicache = require("../modules/apicache");
@@ -55,6 +55,9 @@ class Monitor extends BeanModel {
id: this.id,
name: this.name,
url: this.url,
+ method: this.method,
+ body: this.body,
+ headers: this.headers,
hostname: this.hostname,
port: this.port,
maxretries: this.maxretries,
@@ -138,11 +141,15 @@ class Monitor extends BeanModel {
// Do not do any queries/high loading things before the "bean.ping"
let startTime = dayjs().valueOf();
- let res = await axios.get(this.url, {
+ const options = {
+ url: this.url,
+ method: (this.method || "get").toLowerCase(),
+ ...(this.body ? { data: JSON.parse(this.body) } : {}),
timeout: this.interval * 1000 * 0.8,
headers: {
"Accept": "*/*",
"User-Agent": "Uptime-Kuma/" + version,
+ ...(this.headers ? JSON.parse(this.headers) : {}),
},
httpsAgent: new https.Agent({
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
@@ -152,7 +159,8 @@ class Monitor extends BeanModel {
validateStatus: (status) => {
return checkStatusCode(status, this.getAcceptedStatuscodes());
},
- });
+ };
+ let res = await axios.request(options);
bean.msg = `${res.status} - ${res.statusText}`;
bean.ping = dayjs().valueOf() - startTime;
@@ -172,6 +180,10 @@ class Monitor extends BeanModel {
debug("Cert Info Query Time: " + (dayjs().valueOf() - certInfoStartTime) + "ms");
}
+ if (process.env.UPTIME_KUMA_LOG_RESPONSE_BODY_MONITOR_ID == this.id) {
+ console.log(res.data);
+ }
+
if (this.type === "http") {
bean.status = UP;
} else {
@@ -260,6 +272,46 @@ class Monitor extends BeanModel {
return;
}
+ } else if (this.type === "steam") {
+ const steamApiUrl = "https://api.steampowered.com/IGameServersService/GetServerList/v1/";
+ const steamAPIKey = await setting("steamAPIKey");
+ const filter = `addr\\${this.hostname}:${this.port}`;
+
+ if (!steamAPIKey) {
+ throw new Error("Steam API Key not found");
+ }
+
+ let res = await axios.get(steamApiUrl, {
+ timeout: this.interval * 1000 * 0.8,
+ headers: {
+ "Accept": "*/*",
+ "User-Agent": "Uptime-Kuma/" + version,
+ },
+ httpsAgent: new https.Agent({
+ maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
+ rejectUnauthorized: ! this.getIgnoreTls(),
+ }),
+ maxRedirects: this.maxredirects,
+ validateStatus: (status) => {
+ return checkStatusCode(status, this.getAcceptedStatuscodes());
+ },
+ params: {
+ filter: filter,
+ key: steamAPIKey,
+ }
+ });
+
+ if (res.data.response && res.data.response.servers && res.data.response.servers.length > 0) {
+ bean.status = UP;
+ bean.msg = res.data.response.servers[0].name;
+
+ try {
+ bean.ping = await ping(this.hostname);
+ } catch (_) { }
+ } else {
+ throw new Error("Server not found on Steam");
+ }
+
} else {
bean.msg = "Unknown Monitor Type";
bean.status = PENDING;
@@ -292,54 +344,13 @@ class Monitor extends BeanModel {
let beatInterval = this.interval;
- // * ? -> 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 ||
- (previousBeat.status === UP && bean.status === DOWN) ||
- (previousBeat.status === DOWN && bean.status === UP) ||
- (previousBeat.status === PENDING && bean.status === DOWN);
+ let isImportant = Monitor.isImportantBeat(isFirstBeat, previousBeat?.status, bean.status);
// Mark as important if status changed, ignore pending pings,
// Don't notify if disrupted changes to up
if (isImportant) {
bean.important = true;
-
- // 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();
- }
-
+ await Monitor.sendNotification(isFirstBeat, this, bean);
} else {
bean.important = false;
}
@@ -546,6 +557,53 @@ class Monitor extends BeanModel {
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;
diff --git a/server/notification-providers/aliyun-sms.js b/server/notification-providers/aliyun-sms.js
new file mode 100644
index 000000000..6a2063200
--- /dev/null
+++ b/server/notification-providers/aliyun-sms.js
@@ -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;
diff --git a/server/notification-providers/dingding.js b/server/notification-providers/dingding.js
new file mode 100644
index 000000000..f099192d8
--- /dev/null
+++ b/server/notification-providers/dingding.js
@@ -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;
diff --git a/server/notification-providers/slack.js b/server/notification-providers/slack.js
index 5132ba977..b4dad6fe3 100644
--- a/server/notification-providers/slack.js
+++ b/server/notification-providers/slack.js
@@ -39,8 +39,9 @@ class Slack extends NotificationProvider {
}
const time = heartbeatJSON["time"];
+ const textMsg = "Uptime Kuma Alert";
let data = {
- "text": "Uptime Kuma Alert",
+ "text": monitorJSON ? textMsg + `: ${monitorJSON.name}` : textMsg,
"channel": notification.slackchannel,
"username": notification.slackusername,
"icon_emoji": notification.slackiconemo,
diff --git a/server/notification-providers/smtp.js b/server/notification-providers/smtp.js
index ecb583eb7..60068eb77 100644
--- a/server/notification-providers/smtp.js
+++ b/server/notification-providers/smtp.js
@@ -1,5 +1,6 @@
const nodemailer = require("nodemailer");
const NotificationProvider = require("./notification-provider");
+const { DOWN, UP } = require("../../src/util");
class SMTP extends NotificationProvider {
@@ -20,6 +21,56 @@ class SMTP extends NotificationProvider {
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);
@@ -34,7 +85,7 @@ class SMTP extends NotificationProvider {
cc: notification.smtpCC,
bcc: notification.smtpBCC,
to: notification.smtpTo,
- subject: msg,
+ subject: subject,
text: bodyTextContent,
tls: {
rejectUnauthorized: notification.smtpIgnoreTLSError || false,
diff --git a/server/notification.js b/server/notification.js
index 41a0063c3..658216f91 100644
--- a/server/notification.js
+++ b/server/notification.js
@@ -19,6 +19,8 @@ const Teams = require("./notification-providers/teams");
const Telegram = require("./notification-providers/telegram");
const Webhook = require("./notification-providers/webhook");
const Feishu = require("./notification-providers/feishu");
+const AliyunSms = require("./notification-providers/aliyun-sms");
+const DingDing = require("./notification-providers/dingding");
class Notification {
@@ -31,6 +33,8 @@ class Notification {
const list = [
new Apprise(),
+ new AliyunSms(),
+ new DingDing(),
new Discord(),
new Teams(),
new Gotify(),
diff --git a/server/ping-lite.js b/server/ping-lite.js
index 0af0e9706..b2d6405ad 100644
--- a/server/ping-lite.js
+++ b/server/ping-lite.js
@@ -4,10 +4,7 @@ const net = require("net");
const spawn = require("child_process").spawn;
const events = require("events");
const fs = require("fs");
-const WIN = /^win/.test(process.platform);
-const LIN = /^linux/.test(process.platform);
-const MAC = /^darwin/.test(process.platform);
-const FBSD = /^freebsd/.test(process.platform);
+const util = require("./util-server");
module.exports = Ping;
@@ -23,12 +20,12 @@ function Ping(host, options) {
const timeout = 10;
- if (WIN) {
+ if (util.WIN) {
this._bin = "c:/windows/system32/ping.exe";
this._args = (options.args) ? options.args : [ "-n", "1", "-w", timeout * 1000, host ];
this._regmatch = /[><=]([0-9.]+?)ms/;
- } else if (LIN) {
+ } else if (util.LIN) {
this._bin = "/bin/ping";
const defaultArgs = [ "-n", "-w", timeout, "-c", "1", host ];
@@ -40,7 +37,7 @@ function Ping(host, options) {
this._args = (options.args) ? options.args : defaultArgs;
this._regmatch = /=([0-9.]+?) ms/;
- } else if (MAC) {
+ } else if (util.MAC) {
if (net.isIPv6(host) || options.ipv6) {
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._regmatch = /=([0-9.]+?) ms/;
- } else if (FBSD) {
+ } else if (util.FBSD) {
this._bin = "/sbin/ping";
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
+ if (util.WIN) {
+ data = convertOutput(data);
+ }
this._stdout = (this._stdout || "") + data;
});
@@ -112,6 +112,9 @@ Ping.prototype.send = function (callback) {
});
this._ping.stderr.on("data", function (data) { // log stderr
+ if (util.WIN) {
+ data = convertOutput(data);
+ }
this._stderr = (this._stderr || "") + data;
});
@@ -157,3 +160,19 @@ Ping.prototype.start = function (callback) {
Ping.prototype.stop = function () {
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;
+}
diff --git a/server/routers/api-router.js b/server/routers/api-router.js
index 0da1fd705..fbe8136e5 100644
--- a/server/routers/api-router.js
+++ b/server/routers/api-router.js
@@ -5,7 +5,7 @@ const server = require("../server");
const apicache = require("../modules/apicache");
const Monitor = require("../model/monitor");
const dayjs = require("dayjs");
-const { UP } = require("../../src/util");
+const { UP, flipStatus, debug } = require("../../src/util");
let router = express.Router();
let cache = apicache.middleware;
@@ -18,9 +18,10 @@ router.get("/api/entry-page", async (_, response) => {
router.get("/api/push/:pushToken", async (request, response) => {
try {
+
let pushToken = request.params.pushToken;
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 ", [
pushToken
@@ -30,12 +31,40 @@ router.get("/api/push/:pushToken", async (request, response) => {
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");
- bean.monitor_id = monitor.id;
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.ping = ping;
+ bean.duration = duration;
await R.store(bean);
@@ -45,6 +74,11 @@ router.get("/api/push/:pushToken", async (request, response) => {
response.json({
ok: true,
});
+
+ if (bean.important) {
+ await Monitor.sendNotification(isFirstBeat, monitor, bean);
+ }
+
} catch (e) {
response.json({
ok: false,
diff --git a/server/server.js b/server/server.js
index 67095ff53..c4d18869e 100644
--- a/server/server.js
+++ b/server/server.js
@@ -1,6 +1,7 @@
console.log("Welcome to Uptime Kuma");
const args = require("args-parser")(process.argv);
const { sleep, debug, getRandomInt, genSecret } = require("../src/util");
+const config = require("./config");
debug(args);
@@ -8,10 +9,6 @@ if (! process.env.NODE_ENV) {
process.env.NODE_ENV = "production";
}
-// Demo Mode?
-const demoMode = args["demo"] || false;
-exports.demoMode = demoMode;
-
console.log("Node Env: " + process.env.NODE_ENV);
console.log("Importing Node libraries");
@@ -43,7 +40,7 @@ console.log("Importing this project modules");
debug("Importing Monitor");
const Monitor = require("./model/monitor");
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");
const { Notification } = require("./notification");
@@ -52,6 +49,9 @@ Notification.init();
debug("Importing Database");
const Database = require("./database");
+debug("Importing Background Jobs");
+const { initBackgroundJobs } = require("./jobs");
+
const { basicAuth } = require("./auth");
const { login } = require("./auth");
const passwordHash = require("./password-hash");
@@ -61,12 +61,28 @@ 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.
// Dual-stack support for (::)
-const hostname = process.env.HOST || args.host;
-const port = parseInt(process.env.PORT || args.port || 3001);
+let hostname = process.env.UPTIME_KUMA_HOST || args.host;
+
+// 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
-const sslKey = process.env.SSL_KEY || args["ssl-key"] || undefined;
-const sslCert = process.env.SSL_CERT || args["ssl-cert"] || undefined;
+const sslKey = process.env.UPTIME_KUMA_SSL_KEY || process.env.SSL_KEY || args["ssl-key"] || undefined;
+const sslCert = process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || args["ssl-cert"] || undefined;
+
+// 2FA / notp verification defaults
+const twofa_verification_opts = {
+ "window": 1,
+ "time": 30
+};
/**
* Run unit test after the server is ready
@@ -74,7 +90,7 @@ const sslCert = process.env.SSL_CERT || args["ssl-cert"] || undefined;
*/
const testMode = !!args["test"] || false;
-if (demoMode) {
+if (config.demoMode) {
console.log("==== Demo Mode ====");
}
@@ -265,7 +281,7 @@ exports.entryPage = "dashboard";
}
if (data.token) {
- let verify = notp.totp.verify(data.token, user.twofa_secret);
+ let verify = notp.totp.verify(data.token, user.twofa_secret, twofa_verification_opts);
if (verify && verify.delta == 0) {
callback({
@@ -383,7 +399,7 @@ exports.entryPage = "dashboard";
socket.userID,
]);
- let verify = notp.totp.verify(token, user.twofa_secret);
+ let verify = notp.totp.verify(token, user.twofa_secret, twofa_verification_opts);
if (verify && verify.delta == 0) {
callback({
@@ -509,6 +525,9 @@ exports.entryPage = "dashboard";
bean.name = monitor.name;
bean.type = monitor.type;
bean.url = monitor.url;
+ bean.method = monitor.method;
+ bean.body = monitor.body;
+ bean.headers = monitor.headers;
bean.interval = monitor.interval;
bean.retryInterval = monitor.retryInterval;
bean.hostname = monitor.hostname;
@@ -1034,6 +1053,9 @@ exports.entryPage = "dashboard";
name: monitorListData[i].name,
type: monitorListData[i].type,
url: monitorListData[i].url,
+ method: monitorListData[i].method || "GET",
+ body: monitorListData[i].body,
+ headers: monitorListData[i].headers,
interval: monitorListData[i].interval,
retryInterval: retryInterval,
hostname: monitorListData[i].hostname,
@@ -1239,6 +1261,8 @@ exports.entryPage = "dashboard";
}
});
+ initBackgroundJobs(args);
+
})();
async function updateMonitorNotification(monitorID, notificationIDList) {
diff --git a/server/util-server.js b/server/util-server.js
index 5620d674b..7be922dd5 100644
--- a/server/util-server.js
+++ b/server/util-server.js
@@ -6,6 +6,14 @@ const passwordHash = require("./password-hash");
const dayjs = require("dayjs");
const { Resolver } = require("dns");
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
@@ -116,7 +124,7 @@ exports.setting = async function (key) {
}
};
-exports.setSetting = async function (key, value) {
+exports.setSetting = async function (key, value, type = null) {
let bean = await R.findOne("setting", " `key` = ? ", [
key,
]);
@@ -124,6 +132,7 @@ exports.setSetting = async function (key, value) {
bean = R.dispense("setting");
bean.key = key;
}
+ bean.type = type;
bean.value = JSON.stringify(value);
await R.store(bean);
};
@@ -312,3 +321,14 @@ exports.startUnitTest = async () => {
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();
+};
diff --git a/src/assets/app.scss b/src/assets/app.scss
index 2cbec5c01..e1a5d052d 100644
--- a/src/assets/app.scss
+++ b/src/assets/app.scss
@@ -14,6 +14,10 @@ h2 {
font-size: 26px;
}
+textarea.form-control {
+ border-radius: 19px;
+}
+
::-webkit-scrollbar {
width: 10px;
}
diff --git a/src/assets/multiselect.scss b/src/assets/multiselect.scss
index 300230769..53b47c16e 100644
--- a/src/assets/multiselect.scss
+++ b/src/assets/multiselect.scss
@@ -21,7 +21,7 @@
}
.multiselect__tag {
- border-radius: 50rem;
+ border-radius: $border-radius;
margin-bottom: 0;
padding: 6px 26px 6px 10px;
background: $primary !important;
diff --git a/src/components/HeartbeatBar.vue b/src/components/HeartbeatBar.vue
index 4dc2c712c..e62b95dfb 100644
--- a/src/components/HeartbeatBar.vue
+++ b/src/components/HeartbeatBar.vue
@@ -186,7 +186,7 @@ export default {
.beat {
display: inline-block;
background-color: $primary;
- border-radius: 50rem;
+ border-radius: $border-radius;
&.empty {
background-color: aliceblue;
diff --git a/src/components/notifications/AliyunSms.vue b/src/components/notifications/AliyunSms.vue
new file mode 100644
index 000000000..2c25a3a9c
--- /dev/null
+++ b/src/components/notifications/AliyunSms.vue
@@ -0,0 +1,25 @@
+
+ Sms template must contain parameters: For safety, must use secret key
${name} ${time} ${status} ${msg}
*{{ $t("Required") }}
- You can find the internal room ID by looking in the advanced section of the room settings in your Matrix client. It should look like !QMdRCpUIfLwsfjxye6:home.server. -
-
- It is highly recommended you create a new user and do not use your own Matrix user's access token as it will allow full access to your account and all the rooms you joined. Instead, create a new user and only invite it to the room that you want to receive the notification in. You can get the access token by running curl -XPOST -d '{"type": "m.login.password", "identifier": {"user": "botusername", "type": "m.id.user"}, "password": "passwordforuser"}' "https://home.server/_matrix/client/r0/login"
.
+ {{ $t("matrixDesc1") }}
curl -XPOST -d '{"type": "m.login.password", "identifier": {"user": "botusername", "type": "m.id.user"}, "password": "passwordforuser"}' "https://home.server/_matrix/client/r0/login"
.
+