mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-11-27 16:54:04 +00:00
Merge branch 'master' into fix-weblate-conflict2
# Conflicts: # src/lang/bg-BG.json # src/lang/cs-CZ.json # src/lang/de-CH.json # src/lang/de-DE.json # src/lang/fr-FR.json # src/lang/ga.json # src/lang/id-ID.json # src/lang/tr-TR.json # src/lang/uk-UA.json # src/lang/zh-CN.json
This commit is contained in:
commit
b719d11500
60 changed files with 914 additions and 458 deletions
8
.github/workflows/auto-test.yml
vendored
8
.github/workflows/auto-test.yml
vendored
|
@ -15,14 +15,14 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
auto-test:
|
auto-test:
|
||||||
needs: [ check-linters, e2e-test ]
|
needs: [ check-linters ]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [macos-latest, ubuntu-latest, windows-latest, ARM64]
|
os: [macos-latest, ubuntu-latest, windows-latest, ARM64]
|
||||||
node: [ 18, 20.5 ]
|
node: [ 18, 20 ]
|
||||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
@ -42,7 +42,7 @@ jobs:
|
||||||
|
|
||||||
# As a lot of dev dependencies are not supported on ARMv7, we have to test it separately and just test if `npm ci --production` works
|
# As a lot of dev dependencies are not supported on ARMv7, we have to test it separately and just test if `npm ci --production` works
|
||||||
armv7-simple-test:
|
armv7-simple-test:
|
||||||
needs: [ check-linters ]
|
needs: [ ]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
if: ${{ github.repository == 'louislam/uptime-kuma' }}
|
if: ${{ github.repository == 'louislam/uptime-kuma' }}
|
||||||
|
@ -77,7 +77,7 @@ jobs:
|
||||||
- run: npm run lint:prod
|
- run: npm run lint:prod
|
||||||
|
|
||||||
e2e-test:
|
e2e-test:
|
||||||
needs: [ check-linters ]
|
needs: [ ]
|
||||||
runs-on: ARM64
|
runs-on: ARM64
|
||||||
steps:
|
steps:
|
||||||
- run: git config --global core.autocrlf false # Mainly for Windows
|
- run: git config --global core.autocrlf false # Mainly for Windows
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Project Info
|
# Project Info
|
||||||
|
|
||||||
First of all, I want to thank everyone who have wrote issues or shared pull requests for Uptime Kuma.
|
First of all, I want to thank everyone who has submitted issues or shared pull requests for Uptime Kuma.
|
||||||
I never thought the GitHub community would be so nice!
|
I never thought the GitHub community would be so nice!
|
||||||
Because of this, I also never thought that other people would actually read and edit my code.
|
Because of this, I also never thought that other people would actually read and edit my code.
|
||||||
Parts of the code are not very well-structured or commented, sorry about that.
|
Parts of the code are not very well-structured or commented, sorry about that.
|
||||||
|
@ -9,7 +9,7 @@ The project was created with `vite.js` and is written in `vue3`.
|
||||||
Our backend lives in the `server`-directory and mostly communicates via websockets.
|
Our backend lives in the `server`-directory and mostly communicates via websockets.
|
||||||
Both frontend and backend share the same `package.json`.
|
Both frontend and backend share the same `package.json`.
|
||||||
|
|
||||||
For production, the frontend is build into `dist`-directory and the server (`express.js`) exposes the `dist` directory as the root of the endpoint.
|
For production, the frontend is built into the `dist`-directory and the server (`express.js`) exposes the `dist` directory as the root of the endpoint.
|
||||||
For development, we run vite in development mode on another port.
|
For development, we run vite in development mode on another port.
|
||||||
|
|
||||||
## Directories
|
## Directories
|
||||||
|
@ -28,7 +28,7 @@ For development, we run vite in development mode on another port.
|
||||||
## Can I create a pull request for Uptime Kuma?
|
## Can I create a pull request for Uptime Kuma?
|
||||||
|
|
||||||
Yes or no, it depends on what you will try to do.
|
Yes or no, it depends on what you will try to do.
|
||||||
Both your and our maintainers time is precious, and we don't want to waste both time.
|
Both yours and our maintainers' time is precious, and we don't want to waste either.
|
||||||
|
|
||||||
If you have any questions about any process/.. is not clear, you are likely not alone => please ask them ^^
|
If you have any questions about any process/.. is not clear, you are likely not alone => please ask them ^^
|
||||||
|
|
||||||
|
@ -49,11 +49,11 @@ Different guidelines exist for different types of pull requests (PRs):
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
If you come across a bug and think you can solve, we appreciate your work.
|
If you come across a bug and think you can solve, we appreciate your work.
|
||||||
Please make sure that you follow by these rules:
|
Please make sure that you follow these rules:
|
||||||
- keep the PR as small as possible, fix only one thing at a time => keeping it reviewable
|
- keep the PR as small as possible, fix only one thing at a time => keeping it reviewable
|
||||||
- test that your code does what you came it does.
|
- test that your code does what you claim it does.
|
||||||
|
|
||||||
<sub>Because maintainer time is precious junior maintainers may merge uncontroversial PRs in this area.</sub>
|
<sub>Because maintainer time is precious, junior maintainers may merge uncontroversial PRs in this area.</sub>
|
||||||
</p>
|
</p>
|
||||||
</details>
|
</details>
|
||||||
- <details><summary><b>translations / internationalisation (i18n)</b></summary>
|
- <details><summary><b>translations / internationalisation (i18n)</b></summary>
|
||||||
|
@ -68,7 +68,7 @@ Different guidelines exist for different types of pull requests (PRs):
|
||||||
- language keys need to be **added to `en.json`** to be visible in weblate. If this has not happened, a PR is appreciated.
|
- language keys need to be **added to `en.json`** to be visible in weblate. If this has not happened, a PR is appreciated.
|
||||||
- **Adding a new language** requires a new file see [these instructions](https://github.com/louislam/uptime-kuma/blob/master/src/lang/README.md)
|
- **Adding a new language** requires a new file see [these instructions](https://github.com/louislam/uptime-kuma/blob/master/src/lang/README.md)
|
||||||
|
|
||||||
<sub>Because maintainer time is precious junior maintainers may merge uncontroversial PRs in this area.</sub>
|
<sub>Because maintainer time is precious, junior maintainers may merge uncontroversial PRs in this area.</sub>
|
||||||
</p>
|
</p>
|
||||||
</details>
|
</details>
|
||||||
- <details><summary><b>new notification providers</b></summary>
|
- <details><summary><b>new notification providers</b></summary>
|
||||||
|
@ -102,7 +102,7 @@ Different guidelines exist for different types of pull requests (PRs):
|
||||||
Therefore, making sure that they work is also really important.
|
Therefore, making sure that they work is also really important.
|
||||||
Because testing notification providers is quite time intensive, we mostly offload this onto the person contributing a notification provider.
|
Because testing notification providers is quite time intensive, we mostly offload this onto the person contributing a notification provider.
|
||||||
|
|
||||||
To make shure you have tested the notification provider, please include screenshots of the following events in the pull-request description:
|
To make sure you have tested the notification provider, please include screenshots of the following events in the pull-request description:
|
||||||
- `UP`/`DOWN`
|
- `UP`/`DOWN`
|
||||||
- Certificate Expiry via https://expired.badssl.com/
|
- Certificate Expiry via https://expired.badssl.com/
|
||||||
- Testing (the test button on the notification provider setup page)
|
- Testing (the test button on the notification provider setup page)
|
||||||
|
@ -117,7 +117,7 @@ Different guidelines exist for different types of pull requests (PRs):
|
||||||
| Testing | paste-image-here | paste-image-here |
|
| Testing | paste-image-here | paste-image-here |
|
||||||
```
|
```
|
||||||
|
|
||||||
<sub>Because maintainer time is precious junior maintainers may merge uncontroversial PRs in this area.</sub>
|
<sub>Because maintainer time is precious, junior maintainers may merge uncontroversial PRs in this area.</sub>
|
||||||
</p>
|
</p>
|
||||||
</details>
|
</details>
|
||||||
- <details><summary><b>new monitoring types</b></summary>
|
- <details><summary><b>new monitoring types</b></summary>
|
||||||
|
@ -138,14 +138,14 @@ Different guidelines exist for different types of pull requests (PRs):
|
||||||
-
|
-
|
||||||
|
|
||||||
|
|
||||||
<sub>Because maintainer time is precious junior maintainers may merge uncontroversial PRs in this area.</sub>
|
<sub>Because maintainer time is precious, junior maintainers may merge uncontroversial PRs in this area.</sub>
|
||||||
</p>
|
</p>
|
||||||
</details>
|
</details>
|
||||||
- <details><summary><b>new features/ major changes / breaking bugfixes</b></summary>
|
- <details><summary><b>new features/ major changes / breaking bugfixes</b></summary>
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
be sure to **create an empty draft pull request or open an issue, so we can have a discussion first**.
|
be sure to **create an empty draft pull request or open an issue, so we can have a discussion first**.
|
||||||
This is especially important for a large pull request or you don't know if it will be merged or not.
|
This is especially important for a large pull request or when you don't know if it will be merged or not.
|
||||||
|
|
||||||
<sub>Because of the large impact of this work, only senior maintainers may merge PRs in this area.</sub>
|
<sub>Because of the large impact of this work, only senior maintainers may merge PRs in this area.</sub>
|
||||||
</p>
|
</p>
|
||||||
|
@ -201,7 +201,7 @@ The rationale behind this is that we can align the direction and scope of the fe
|
||||||
|
|
||||||
## Project Styles
|
## Project Styles
|
||||||
|
|
||||||
I personally do not like something that requires so many configurations before you can finally start the app.
|
I personally do not like something that requires a lot of configuration before you can finally start the app.
|
||||||
The goal is to make the Uptime Kuma installation as easy as installing a mobile app.
|
The goal is to make the Uptime Kuma installation as easy as installing a mobile app.
|
||||||
|
|
||||||
- Easy to install for non-Docker users
|
- Easy to install for non-Docker users
|
||||||
|
@ -260,7 +260,7 @@ Port `3000` and port `3001` will be used.
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
But sometimes, you would like to restart the server, but not the frontend, you can run these commands in two terminals:
|
But sometimes you may want to restart the server without restarting the frontend. In that case, you can run these commands in two terminals:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run start-frontend-dev
|
npm run start-frontend-dev
|
||||||
|
@ -409,7 +409,7 @@ https://github.com/louislam/uptime-kuma/issues?q=sort%3Aupdated-desc
|
||||||
|
|
||||||
### What is a maintainer and what are their roles?
|
### What is a maintainer and what are their roles?
|
||||||
|
|
||||||
This project has multiple maintainers which specialise in different areas.
|
This project has multiple maintainers who specialise in different areas.
|
||||||
Currently, there are 3 maintainers:
|
Currently, there are 3 maintainers:
|
||||||
|
|
||||||
| Person | Role | Main Area |
|
| Person | Role | Main Area |
|
||||||
|
|
|
@ -16,9 +16,7 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
define: {
|
define: {
|
||||||
"FRONTEND_VERSION": JSON.stringify(process.env.npm_package_version),
|
"FRONTEND_VERSION": JSON.stringify(process.env.npm_package_version),
|
||||||
"DEVCONTAINER": JSON.stringify(process.env.DEVCONTAINER),
|
"process.env": {},
|
||||||
"GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN": JSON.stringify(process.env.GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN),
|
|
||||||
"CODESPACE_NAME": JSON.stringify(process.env.CODESPACE_NAME),
|
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
|
|
|
@ -4,7 +4,6 @@ const tar = require("tar");
|
||||||
|
|
||||||
const packageJSON = require("../package.json");
|
const packageJSON = require("../package.json");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const rmSync = require("./fs-rmSync.js");
|
|
||||||
const version = packageJSON.version;
|
const version = packageJSON.version;
|
||||||
|
|
||||||
const filename = "dist.tar.gz";
|
const filename = "dist.tar.gz";
|
||||||
|
@ -29,8 +28,9 @@ function download(url) {
|
||||||
if (fs.existsSync("./dist")) {
|
if (fs.existsSync("./dist")) {
|
||||||
|
|
||||||
if (fs.existsSync("./dist-backup")) {
|
if (fs.existsSync("./dist-backup")) {
|
||||||
rmSync("./dist-backup", {
|
fs.rmSync("./dist-backup", {
|
||||||
recursive: true
|
recursive: true,
|
||||||
|
force: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,8 +43,9 @@ function download(url) {
|
||||||
|
|
||||||
tarStream.on("close", () => {
|
tarStream.on("close", () => {
|
||||||
if (fs.existsSync("./dist-backup")) {
|
if (fs.existsSync("./dist-backup")) {
|
||||||
rmSync("./dist-backup", {
|
fs.rmSync("./dist-backup", {
|
||||||
recursive: true
|
recursive: true,
|
||||||
|
force: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log("Done");
|
console.log("Done");
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
const fs = require("fs");
|
|
||||||
/**
|
|
||||||
* Detect if `fs.rmSync` is available
|
|
||||||
* to avoid the runtime deprecation warning triggered for using `fs.rmdirSync` with `{ recursive: true }` in Node.js v16,
|
|
||||||
* or the `recursive` property removing completely in the future Node.js version.
|
|
||||||
* See the link below.
|
|
||||||
* @todo Once we drop the support for Node.js v14 (or at least versions before v14.14.0), we can safely replace this function with `fs.rmSync`, since `fs.rmSync` was add in Node.js v14.14.0 and currently we supports all the Node.js v14 versions that include the versions before the v14.14.0, and this function have almost the same signature with `fs.rmSync`.
|
|
||||||
* @link https://nodejs.org/docs/latest-v16.x/api/deprecations.html#dep0147-fsrmdirpath--recursive-true- the deprecation information of `fs.rmdirSync`
|
|
||||||
* @link https://nodejs.org/docs/latest-v16.x/api/fs.html#fsrmsyncpath-options the document of `fs.rmSync`
|
|
||||||
* @param {fs.PathLike} path Valid types for path values in "fs".
|
|
||||||
* @param {fs.RmDirOptions} options options for `fs.rmdirSync`, if `fs.rmSync` is available and property `recursive` is true, it will automatically have property `force` with value `true`.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
const rmSync = (path, options) => {
|
|
||||||
if (typeof fs.rmSync === "function") {
|
|
||||||
if (options.recursive) {
|
|
||||||
options.force = true;
|
|
||||||
}
|
|
||||||
return fs.rmSync(path, options);
|
|
||||||
}
|
|
||||||
return fs.rmdirSync(path, options);
|
|
||||||
};
|
|
||||||
module.exports = rmSync;
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import util from "util";
|
import util from "util";
|
||||||
import rmSync from "../fs-rmSync.js";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy across the required language files
|
* Copy across the required language files
|
||||||
|
@ -16,7 +15,10 @@ import rmSync from "../fs-rmSync.js";
|
||||||
*/
|
*/
|
||||||
function copyFiles(langCode, baseLang) {
|
function copyFiles(langCode, baseLang) {
|
||||||
if (fs.existsSync("./languages")) {
|
if (fs.existsSync("./languages")) {
|
||||||
rmSync("./languages", { recursive: true });
|
fs.rmSync("./languages", {
|
||||||
|
recursive: true,
|
||||||
|
force: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
fs.mkdirSync("./languages");
|
fs.mkdirSync("./languages");
|
||||||
|
|
||||||
|
@ -93,6 +95,9 @@ console.log("Updating: " + langCode);
|
||||||
|
|
||||||
copyFiles(langCode, baseLangCode);
|
copyFiles(langCode, baseLangCode);
|
||||||
await updateLanguage(langCode, baseLangCode);
|
await updateLanguage(langCode, baseLangCode);
|
||||||
rmSync("./languages", { recursive: true });
|
fs.rmSync("./languages", {
|
||||||
|
recursive: true,
|
||||||
|
force: true,
|
||||||
|
});
|
||||||
|
|
||||||
console.log("Done. Fixing formatting by ESLint...");
|
console.log("Done. Fixing formatting by ESLint...");
|
||||||
|
|
|
@ -68,8 +68,7 @@
|
||||||
"sort-contributors": "node extra/sort-contributors.js",
|
"sort-contributors": "node extra/sort-contributors.js",
|
||||||
"quick-run-nightly": "docker run --rm --env NODE_ENV=development -p 3001:3001 louislam/uptime-kuma:nightly2",
|
"quick-run-nightly": "docker run --rm --env NODE_ENV=development -p 3001:3001 louislam/uptime-kuma:nightly2",
|
||||||
"start-dev-container": "cd docker && docker-compose -f docker-compose-dev.yml up --force-recreate",
|
"start-dev-container": "cd docker && docker-compose -f docker-compose-dev.yml up --force-recreate",
|
||||||
"rebase-pr-to-1.23.X": "node extra/rebase-pr.js 1.23.X",
|
"rebase-pr-to-1.23.X": "node extra/rebase-pr.js 1.23.X"
|
||||||
"start-server-node14-win": "private\\node14\\node.exe server/server.js"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@grpc/grpc-js": "~1.8.22",
|
"@grpc/grpc-js": "~1.8.22",
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const basicAuth = require("express-basic-auth");
|
const basicAuth = require("express-basic-auth");
|
||||||
const passwordHash = require("./password-hash");
|
const passwordHash = require("./password-hash");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
|
const { setting } = require("./util-server");
|
||||||
const { log } = require("../src/util");
|
const { log } = require("../src/util");
|
||||||
const { loginRateLimiter, apiRateLimiter } = require("./rate-limiter");
|
const { loginRateLimiter, apiRateLimiter } = require("./rate-limiter");
|
||||||
const { Settings } = require("./settings");
|
const { Settings } = require("./settings");
|
||||||
|
@ -138,7 +139,7 @@ exports.basicAuth = async function (req, res, next) {
|
||||||
challenge: true,
|
challenge: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const disabledAuth = await Settings.get("disableAuth");
|
const disabledAuth = await setting("disableAuth");
|
||||||
|
|
||||||
if (!disabledAuth) {
|
if (!disabledAuth) {
|
||||||
middleware(req, res, next);
|
middleware(req, res, next);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
const { setSetting, setting } = require("./util-server");
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
const compareVersions = require("compare-versions");
|
const compareVersions = require("compare-versions");
|
||||||
const { log } = require("../src/util");
|
const { log } = require("../src/util");
|
||||||
const { Settings } = require("./settings");
|
|
||||||
|
|
||||||
exports.version = require("../package.json").version;
|
exports.version = require("../package.json").version;
|
||||||
exports.latestVersion = null;
|
exports.latestVersion = null;
|
||||||
|
@ -14,7 +14,7 @@ let interval;
|
||||||
|
|
||||||
exports.startInterval = () => {
|
exports.startInterval = () => {
|
||||||
let check = async () => {
|
let check = async () => {
|
||||||
if (await Settings.get("checkUpdate") === false) {
|
if (await setting("checkUpdate") === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ exports.startInterval = () => {
|
||||||
res.data.slow = "1000.0.0";
|
res.data.slow = "1000.0.0";
|
||||||
}
|
}
|
||||||
|
|
||||||
let checkBeta = await Settings.get("checkBeta");
|
let checkBeta = await setting("checkBeta");
|
||||||
|
|
||||||
if (checkBeta && res.data.beta) {
|
if (checkBeta && res.data.beta) {
|
||||||
if (compareVersions.compare(res.data.beta, res.data.slow, ">")) {
|
if (compareVersions.compare(res.data.beta, res.data.slow, ">")) {
|
||||||
|
@ -57,7 +57,7 @@ exports.startInterval = () => {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
exports.enableCheckUpdate = async (value) => {
|
exports.enableCheckUpdate = async (value) => {
|
||||||
await Settings.set("checkUpdate", value);
|
await setSetting("checkUpdate", value);
|
||||||
|
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@ const { R } = require("redbean-node");
|
||||||
const { UptimeKumaServer } = require("./uptime-kuma-server");
|
const { UptimeKumaServer } = require("./uptime-kuma-server");
|
||||||
const server = UptimeKumaServer.getInstance();
|
const server = UptimeKumaServer.getInstance();
|
||||||
const io = server.io;
|
const io = server.io;
|
||||||
|
const { setting } = require("./util-server");
|
||||||
const checkVersion = require("./check-version");
|
const checkVersion = require("./check-version");
|
||||||
const { Settings } = require("./settings");
|
|
||||||
const Database = require("./database");
|
const Database = require("./database");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -158,8 +158,8 @@ async function sendInfo(socket, hideVersion = false) {
|
||||||
version,
|
version,
|
||||||
latestVersion,
|
latestVersion,
|
||||||
isContainer,
|
isContainer,
|
||||||
primaryBaseURL: await Settings.get("primaryBaseURL"),
|
|
||||||
dbType,
|
dbType,
|
||||||
|
primaryBaseURL: await setting("primaryBaseURL"),
|
||||||
serverTimezone: await server.getTimezone(),
|
serverTimezone: await server.getTimezone(),
|
||||||
serverTimezoneOffset: server.getTimezoneOffset(),
|
serverTimezoneOffset: server.getTimezoneOffset(),
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
|
const { setSetting, setting } = require("./util-server");
|
||||||
const { log, sleep } = require("../src/util");
|
const { log, sleep } = require("../src/util");
|
||||||
const knex = require("knex");
|
const knex = require("knex");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const { EmbeddedMariaDB } = require("./embedded-mariadb");
|
const { EmbeddedMariaDB } = require("./embedded-mariadb");
|
||||||
const mysql = require("mysql2/promise");
|
const mysql = require("mysql2/promise");
|
||||||
const { Settings } = require("./settings");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Database & App Data Folder
|
* Database & App Data Folder
|
||||||
|
@ -420,7 +420,7 @@ class Database {
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
static async patchSqlite() {
|
static async patchSqlite() {
|
||||||
let version = parseInt(await Settings.get("database_version"));
|
let version = parseInt(await setting("database_version"));
|
||||||
|
|
||||||
if (! version) {
|
if (! version) {
|
||||||
version = 0;
|
version = 0;
|
||||||
|
@ -445,7 +445,7 @@ class Database {
|
||||||
log.info("db", `Patching ${sqlFile}`);
|
log.info("db", `Patching ${sqlFile}`);
|
||||||
await Database.importSQLFile(sqlFile);
|
await Database.importSQLFile(sqlFile);
|
||||||
log.info("db", `Patched ${sqlFile}`);
|
log.info("db", `Patched ${sqlFile}`);
|
||||||
await Settings.set("database_version", i);
|
await setSetting("database_version", i);
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
await Database.close();
|
await Database.close();
|
||||||
|
@ -471,7 +471,7 @@ class Database {
|
||||||
*/
|
*/
|
||||||
static async patchSqlite2() {
|
static async patchSqlite2() {
|
||||||
log.debug("db", "Database Patch 2.0 Process");
|
log.debug("db", "Database Patch 2.0 Process");
|
||||||
let databasePatchedFiles = await Settings.get("databasePatchedFiles");
|
let databasePatchedFiles = await setting("databasePatchedFiles");
|
||||||
|
|
||||||
if (! databasePatchedFiles) {
|
if (! databasePatchedFiles) {
|
||||||
databasePatchedFiles = {};
|
databasePatchedFiles = {};
|
||||||
|
@ -499,7 +499,7 @@ class Database {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
await Settings.set("databasePatchedFiles", databasePatchedFiles);
|
await setSetting("databasePatchedFiles", databasePatchedFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -512,27 +512,27 @@ class Database {
|
||||||
// Fix 1.13.0 empty slug bug
|
// Fix 1.13.0 empty slug bug
|
||||||
await R.exec("UPDATE status_page SET slug = 'empty-slug-recover' WHERE TRIM(slug) = ''");
|
await R.exec("UPDATE status_page SET slug = 'empty-slug-recover' WHERE TRIM(slug) = ''");
|
||||||
|
|
||||||
let title = await Settings.get("title");
|
let title = await setting("title");
|
||||||
|
|
||||||
if (title) {
|
if (title) {
|
||||||
log.info("database", "Migrating Status Page");
|
console.log("Migrating Status Page");
|
||||||
|
|
||||||
let statusPageCheck = await R.findOne("status_page", " slug = 'default' ");
|
let statusPageCheck = await R.findOne("status_page", " slug = 'default' ");
|
||||||
|
|
||||||
if (statusPageCheck !== null) {
|
if (statusPageCheck !== null) {
|
||||||
log.info("database", "Migrating Status Page - Skip, default slug record is already existing");
|
console.log("Migrating Status Page - Skip, default slug record is already existing");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let statusPage = R.dispense("status_page");
|
let statusPage = R.dispense("status_page");
|
||||||
statusPage.slug = "default";
|
statusPage.slug = "default";
|
||||||
statusPage.title = title;
|
statusPage.title = title;
|
||||||
statusPage.description = await Settings.get("description");
|
statusPage.description = await setting("description");
|
||||||
statusPage.icon = await Settings.get("icon");
|
statusPage.icon = await setting("icon");
|
||||||
statusPage.theme = await Settings.get("statusPageTheme");
|
statusPage.theme = await setting("statusPageTheme");
|
||||||
statusPage.published = !!await Settings.get("statusPagePublished");
|
statusPage.published = !!await setting("statusPagePublished");
|
||||||
statusPage.search_engine_index = !!await Settings.get("searchEngineIndex");
|
statusPage.search_engine_index = !!await setting("searchEngineIndex");
|
||||||
statusPage.show_tags = !!await Settings.get("statusPageTags");
|
statusPage.show_tags = !!await setting("statusPageTags");
|
||||||
statusPage.password = null;
|
statusPage.password = null;
|
||||||
|
|
||||||
if (!statusPage.title) {
|
if (!statusPage.title) {
|
||||||
|
@ -560,13 +560,13 @@ class Database {
|
||||||
await R.exec("DELETE FROM setting WHERE type = 'statusPage'");
|
await R.exec("DELETE FROM setting WHERE type = 'statusPage'");
|
||||||
|
|
||||||
// Migrate Entry Page if it is status page
|
// Migrate Entry Page if it is status page
|
||||||
let entryPage = await Settings.get("entryPage");
|
let entryPage = await setting("entryPage");
|
||||||
|
|
||||||
if (entryPage === "statusPage") {
|
if (entryPage === "statusPage") {
|
||||||
await Settings.set("entryPage", "statusPage-default", "general");
|
await setSetting("entryPage", "statusPage-default", "general");
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("database", "Migrating Status Page - Done");
|
console.log("Migrating Status Page - Done");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { log } = require("../../src/util");
|
const { log } = require("../../src/util");
|
||||||
|
const { setSetting, setting } = require("../util-server");
|
||||||
const Database = require("../database");
|
const Database = require("../database");
|
||||||
const { Settings } = require("../settings");
|
|
||||||
|
|
||||||
const DEFAULT_KEEP_PERIOD = 180;
|
const DEFAULT_KEEP_PERIOD = 180;
|
||||||
|
|
||||||
|
@ -11,11 +11,11 @@ const DEFAULT_KEEP_PERIOD = 180;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const clearOldData = async () => {
|
const clearOldData = async () => {
|
||||||
let period = await Settings.get("keepDataPeriodDays");
|
let period = await setting("keepDataPeriodDays");
|
||||||
|
|
||||||
// Set Default Period
|
// Set Default Period
|
||||||
if (period == null) {
|
if (period == null) {
|
||||||
await Settings.set("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general");
|
await setSetting("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general");
|
||||||
period = DEFAULT_KEEP_PERIOD;
|
period = DEFAULT_KEEP_PERIOD;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ const clearOldData = async () => {
|
||||||
parsedPeriod = parseInt(period);
|
parsedPeriod = parseInt(period);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
log.warn("clearOldData", "Failed to parse setting, resetting to default..");
|
log.warn("clearOldData", "Failed to parse setting, resetting to default..");
|
||||||
await Settings.set("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general");
|
await setSetting("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general");
|
||||||
parsedPeriod = DEFAULT_KEEP_PERIOD;
|
parsedPeriod = DEFAULT_KEEP_PERIOD;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ const { Prometheus } = require("../prometheus");
|
||||||
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND,
|
const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND,
|
||||||
SQL_DATETIME_FORMAT, evaluateJsonQuery
|
SQL_DATETIME_FORMAT, evaluateJsonQuery
|
||||||
} = require("../../src/util");
|
} = require("../../src/util");
|
||||||
const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, mssqlQuery, postgresQuery, mysqlQuery, httpNtlm, radius, grpcQuery,
|
const { tcping, ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, setSetting, httpNtlm, radius, grpcQuery,
|
||||||
redisPingAsync, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal
|
redisPingAsync, kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal
|
||||||
} = require("../util-server");
|
} = require("../util-server");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
|
@ -24,7 +24,6 @@ const { CookieJar } = require("tough-cookie");
|
||||||
const { HttpsCookieAgent } = require("http-cookie-agent/http");
|
const { HttpsCookieAgent } = require("http-cookie-agent/http");
|
||||||
const https = require("https");
|
const https = require("https");
|
||||||
const http = require("http");
|
const http = require("http");
|
||||||
const { Settings } = require("../settings");
|
|
||||||
|
|
||||||
const rootCertificates = rootCertificatesFingerprints();
|
const rootCertificates = rootCertificatesFingerprints();
|
||||||
|
|
||||||
|
@ -72,23 +71,12 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an object that ready to parse to JSON
|
* Return an object that ready to parse to JSON
|
||||||
|
* @param {object} preloadData to prevent n+1 problems, we query the data in a batch outside of this function
|
||||||
* @param {boolean} includeSensitiveData Include sensitive data in
|
* @param {boolean} includeSensitiveData Include sensitive data in
|
||||||
* JSON
|
* JSON
|
||||||
* @returns {Promise<object>} Object ready to parse
|
* @returns {object} Object ready to parse
|
||||||
*/
|
*/
|
||||||
async toJSON(includeSensitiveData = true) {
|
toJSON(preloadData = {}, includeSensitiveData = true) {
|
||||||
|
|
||||||
let notificationIDList = {};
|
|
||||||
|
|
||||||
let list = await R.find("monitor_notification", " monitor_id = ? ", [
|
|
||||||
this.id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
for (let bean of list) {
|
|
||||||
notificationIDList[bean.notification_id] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tags = await this.getTags();
|
|
||||||
|
|
||||||
let screenshot = null;
|
let screenshot = null;
|
||||||
|
|
||||||
|
@ -96,7 +84,7 @@ class Monitor extends BeanModel {
|
||||||
screenshot = "/screenshots/" + jwt.sign(this.id, UptimeKumaServer.getInstance().jwtSecret) + ".png";
|
screenshot = "/screenshots/" + jwt.sign(this.id, UptimeKumaServer.getInstance().jwtSecret) + ".png";
|
||||||
}
|
}
|
||||||
|
|
||||||
const path = await this.getPath();
|
const path = preloadData.paths.get(this.id) || [];
|
||||||
const pathName = path.join(" / ");
|
const pathName = path.join(" / ");
|
||||||
|
|
||||||
let data = {
|
let data = {
|
||||||
|
@ -106,15 +94,15 @@ class Monitor extends BeanModel {
|
||||||
path,
|
path,
|
||||||
pathName,
|
pathName,
|
||||||
parent: this.parent,
|
parent: this.parent,
|
||||||
childrenIDs: await Monitor.getAllChildrenIDs(this.id),
|
childrenIDs: preloadData.childrenIDs.get(this.id) || [],
|
||||||
url: this.url,
|
url: this.url,
|
||||||
method: this.method,
|
method: this.method,
|
||||||
hostname: this.hostname,
|
hostname: this.hostname,
|
||||||
port: this.port,
|
port: this.port,
|
||||||
maxretries: this.maxretries,
|
maxretries: this.maxretries,
|
||||||
weight: this.weight,
|
weight: this.weight,
|
||||||
active: await this.isActive(),
|
active: preloadData.activeStatus.get(this.id),
|
||||||
forceInactive: !await Monitor.isParentActive(this.id),
|
forceInactive: preloadData.forceInactive.get(this.id),
|
||||||
type: this.type,
|
type: this.type,
|
||||||
timeout: this.timeout,
|
timeout: this.timeout,
|
||||||
interval: this.interval,
|
interval: this.interval,
|
||||||
|
@ -134,9 +122,9 @@ class Monitor extends BeanModel {
|
||||||
docker_container: this.docker_container,
|
docker_container: this.docker_container,
|
||||||
docker_host: this.docker_host,
|
docker_host: this.docker_host,
|
||||||
proxyId: this.proxy_id,
|
proxyId: this.proxy_id,
|
||||||
notificationIDList,
|
notificationIDList: preloadData.notifications.get(this.id) || {},
|
||||||
tags: tags,
|
tags: preloadData.tags.get(this.id) || [],
|
||||||
maintenance: await Monitor.isUnderMaintenance(this.id),
|
maintenance: preloadData.maintenanceStatus.get(this.id),
|
||||||
mqttTopic: this.mqttTopic,
|
mqttTopic: this.mqttTopic,
|
||||||
mqttSuccessMessage: this.mqttSuccessMessage,
|
mqttSuccessMessage: this.mqttSuccessMessage,
|
||||||
mqttCheckType: this.mqttCheckType,
|
mqttCheckType: this.mqttCheckType,
|
||||||
|
@ -202,16 +190,6 @@ class Monitor extends BeanModel {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the monitor is active based on itself and its parents
|
|
||||||
* @returns {Promise<boolean>} Is the monitor active?
|
|
||||||
*/
|
|
||||||
async isActive() {
|
|
||||||
const parentActive = await Monitor.isParentActive(this.id);
|
|
||||||
|
|
||||||
return (this.active === 1) && parentActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all tags applied to this monitor
|
* Get all tags applied to this monitor
|
||||||
* @returns {Promise<LooseObject<any>[]>} List of tags on the
|
* @returns {Promise<LooseObject<any>[]>} List of tags on the
|
||||||
|
@ -347,7 +325,7 @@ class Monitor extends BeanModel {
|
||||||
let previousBeat = null;
|
let previousBeat = null;
|
||||||
let retries = 0;
|
let retries = 0;
|
||||||
|
|
||||||
this.prometheus = await Prometheus.createAndInitMetrics(this);
|
this.prometheus = new Prometheus(this);
|
||||||
|
|
||||||
const beat = async () => {
|
const beat = async () => {
|
||||||
|
|
||||||
|
@ -673,7 +651,7 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
} else if (this.type === "steam") {
|
} else if (this.type === "steam") {
|
||||||
const steamApiUrl = "https://api.steampowered.com/IGameServersService/GetServerList/v1/";
|
const steamApiUrl = "https://api.steampowered.com/IGameServersService/GetServerList/v1/";
|
||||||
const steamAPIKey = await Settings.get("steamAPIKey");
|
const steamAPIKey = await setting("steamAPIKey");
|
||||||
const filter = `addr\\${this.hostname}:${this.port}`;
|
const filter = `addr\\${this.hostname}:${this.port}`;
|
||||||
|
|
||||||
if (!steamAPIKey) {
|
if (!steamAPIKey) {
|
||||||
|
@ -999,7 +977,7 @@ class Monitor extends BeanModel {
|
||||||
await R.store(bean);
|
await R.store(bean);
|
||||||
|
|
||||||
log.debug("monitor", `[${this.name}] prometheus.update`);
|
log.debug("monitor", `[${this.name}] prometheus.update`);
|
||||||
await this.prometheus?.update(bean, tlsInfo);
|
this.prometheus?.update(bean, tlsInfo);
|
||||||
|
|
||||||
previousBeat = bean;
|
previousBeat = bean;
|
||||||
|
|
||||||
|
@ -1197,6 +1175,18 @@ class Monitor extends BeanModel {
|
||||||
return checkCertificateResult;
|
return checkCertificateResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the monitor is active based on itself and its parents
|
||||||
|
* @param {number} monitorID ID of monitor to send
|
||||||
|
* @param {boolean} active is active
|
||||||
|
* @returns {Promise<boolean>} Is the monitor active?
|
||||||
|
*/
|
||||||
|
static async isActive(monitorID, active) {
|
||||||
|
const parentActive = await Monitor.isParentActive(monitorID);
|
||||||
|
|
||||||
|
return (active === 1) && parentActive;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send statistics to clients
|
* Send statistics to clients
|
||||||
* @param {Server} io Socket server instance
|
* @param {Server} io Socket server instance
|
||||||
|
@ -1333,7 +1323,10 @@ class Monitor extends BeanModel {
|
||||||
for (let notification of notificationList) {
|
for (let notification of notificationList) {
|
||||||
try {
|
try {
|
||||||
const heartbeatJSON = bean.toJSON();
|
const heartbeatJSON = bean.toJSON();
|
||||||
|
const monitorData = [{ id: monitor.id,
|
||||||
|
active: monitor.active
|
||||||
|
}];
|
||||||
|
const preloadData = await Monitor.preparePreloadData(monitorData);
|
||||||
// Prevent if the msg is undefined, notifications such as Discord cannot send out.
|
// Prevent if the msg is undefined, notifications such as Discord cannot send out.
|
||||||
if (!heartbeatJSON["msg"]) {
|
if (!heartbeatJSON["msg"]) {
|
||||||
heartbeatJSON["msg"] = "N/A";
|
heartbeatJSON["msg"] = "N/A";
|
||||||
|
@ -1344,7 +1337,7 @@ class Monitor extends BeanModel {
|
||||||
heartbeatJSON["timezoneOffset"] = UptimeKumaServer.getInstance().getTimezoneOffset();
|
heartbeatJSON["timezoneOffset"] = UptimeKumaServer.getInstance().getTimezoneOffset();
|
||||||
heartbeatJSON["localDateTime"] = dayjs.utc(heartbeatJSON["time"]).tz(heartbeatJSON["timezone"]).format(SQL_DATETIME_FORMAT);
|
heartbeatJSON["localDateTime"] = dayjs.utc(heartbeatJSON["time"]).tz(heartbeatJSON["timezone"]).format(SQL_DATETIME_FORMAT);
|
||||||
|
|
||||||
await Notification.send(JSON.parse(notification.config), msg, await monitor.toJSON(false), heartbeatJSON);
|
await Notification.send(JSON.parse(notification.config), msg, monitor.toJSON(preloadData, false), heartbeatJSON);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("monitor", "Cannot send notification to " + notification.name);
|
log.error("monitor", "Cannot send notification to " + notification.name);
|
||||||
log.error("monitor", e);
|
log.error("monitor", e);
|
||||||
|
@ -1380,12 +1373,11 @@ class Monitor extends BeanModel {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let notifyDays = await Settings.get("tlsExpiryNotifyDays");
|
let notifyDays = await setting("tlsExpiryNotifyDays");
|
||||||
if (notifyDays == null || !Array.isArray(notifyDays)) {
|
if (notifyDays == null || !Array.isArray(notifyDays)) {
|
||||||
// Reset Default
|
// Reset Default
|
||||||
await Settings.set("tlsExpiryNotifyDays", [ 7, 14, 21 ], "general");
|
await setSetting("tlsExpiryNotifyDays", [ 7, 14, 21 ], "general");
|
||||||
notifyDays = [ 7, 14, 21 ];
|
notifyDays = [ 7, 14, 21 ];
|
||||||
await Settings.set("tlsExpiryNotifyDays", notifyDays, "general");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(notifyDays)) {
|
if (Array.isArray(notifyDays)) {
|
||||||
|
@ -1507,6 +1499,111 @@ class Monitor extends BeanModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets monitor notification of multiple monitor
|
||||||
|
* @param {Array} monitorIDs IDs of monitor to get
|
||||||
|
* @returns {Promise<LooseObject<any>>} object
|
||||||
|
*/
|
||||||
|
static async getMonitorNotification(monitorIDs) {
|
||||||
|
return await R.getAll(`
|
||||||
|
SELECT monitor_notification.monitor_id, monitor_notification.notification_id
|
||||||
|
FROM monitor_notification
|
||||||
|
WHERE monitor_notification.monitor_id IN (?)
|
||||||
|
`, [
|
||||||
|
monitorIDs,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets monitor tags of multiple monitor
|
||||||
|
* @param {Array} monitorIDs IDs of monitor to get
|
||||||
|
* @returns {Promise<LooseObject<any>>} object
|
||||||
|
*/
|
||||||
|
static async getMonitorTag(monitorIDs) {
|
||||||
|
return await R.getAll(`
|
||||||
|
SELECT monitor_tag.monitor_id, tag.name, tag.color
|
||||||
|
FROM monitor_tag
|
||||||
|
JOIN tag ON monitor_tag.tag_id = tag.id
|
||||||
|
WHERE monitor_tag.monitor_id IN (?)
|
||||||
|
`, [
|
||||||
|
monitorIDs,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* prepare preloaded data for efficient access
|
||||||
|
* @param {Array} monitorData IDs & active field of monitor to get
|
||||||
|
* @returns {Promise<LooseObject<any>>} object
|
||||||
|
*/
|
||||||
|
static async preparePreloadData(monitorData) {
|
||||||
|
|
||||||
|
const notificationsMap = new Map();
|
||||||
|
const tagsMap = new Map();
|
||||||
|
const maintenanceStatusMap = new Map();
|
||||||
|
const childrenIDsMap = new Map();
|
||||||
|
const activeStatusMap = new Map();
|
||||||
|
const forceInactiveMap = new Map();
|
||||||
|
const pathsMap = new Map();
|
||||||
|
|
||||||
|
if (monitorData.length > 0) {
|
||||||
|
const monitorIDs = monitorData.map(monitor => monitor.id);
|
||||||
|
const notifications = await Monitor.getMonitorNotification(monitorIDs);
|
||||||
|
const tags = await Monitor.getMonitorTag(monitorIDs);
|
||||||
|
const maintenanceStatuses = await Promise.all(monitorData.map(monitor => Monitor.isUnderMaintenance(monitor.id)));
|
||||||
|
const childrenIDs = await Promise.all(monitorData.map(monitor => Monitor.getAllChildrenIDs(monitor.id)));
|
||||||
|
const activeStatuses = await Promise.all(monitorData.map(monitor => Monitor.isActive(monitor.id, monitor.active)));
|
||||||
|
const forceInactiveStatuses = await Promise.all(monitorData.map(monitor => Monitor.isParentActive(monitor.id)));
|
||||||
|
const paths = await Promise.all(monitorData.map(monitor => Monitor.getAllPath(monitor.id, monitor.name)));
|
||||||
|
|
||||||
|
notifications.forEach(row => {
|
||||||
|
if (!notificationsMap.has(row.monitor_id)) {
|
||||||
|
notificationsMap.set(row.monitor_id, {});
|
||||||
|
}
|
||||||
|
notificationsMap.get(row.monitor_id)[row.notification_id] = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
tags.forEach(row => {
|
||||||
|
if (!tagsMap.has(row.monitor_id)) {
|
||||||
|
tagsMap.set(row.monitor_id, []);
|
||||||
|
}
|
||||||
|
tagsMap.get(row.monitor_id).push({
|
||||||
|
name: row.name,
|
||||||
|
color: row.color
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
monitorData.forEach((monitor, index) => {
|
||||||
|
maintenanceStatusMap.set(monitor.id, maintenanceStatuses[index]);
|
||||||
|
});
|
||||||
|
|
||||||
|
monitorData.forEach((monitor, index) => {
|
||||||
|
childrenIDsMap.set(monitor.id, childrenIDs[index]);
|
||||||
|
});
|
||||||
|
|
||||||
|
monitorData.forEach((monitor, index) => {
|
||||||
|
activeStatusMap.set(monitor.id, activeStatuses[index]);
|
||||||
|
});
|
||||||
|
|
||||||
|
monitorData.forEach((monitor, index) => {
|
||||||
|
forceInactiveMap.set(monitor.id, !forceInactiveStatuses[index]);
|
||||||
|
});
|
||||||
|
|
||||||
|
monitorData.forEach((monitor, index) => {
|
||||||
|
pathsMap.set(monitor.id, paths[index]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
notifications: notificationsMap,
|
||||||
|
tags: tagsMap,
|
||||||
|
maintenanceStatus: maintenanceStatusMap,
|
||||||
|
childrenIDs: childrenIDsMap,
|
||||||
|
activeStatus: activeStatusMap,
|
||||||
|
forceInactive: forceInactiveMap,
|
||||||
|
paths: pathsMap,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets Parent of the monitor
|
* Gets Parent of the monitor
|
||||||
* @param {number} monitorID ID of monitor to get
|
* @param {number} monitorID ID of monitor to get
|
||||||
|
@ -1539,16 +1636,18 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the full path
|
* Gets the full path
|
||||||
|
* @param {number} monitorID ID of the monitor to get
|
||||||
|
* @param {string} name of the monitor to get
|
||||||
* @returns {Promise<string[]>} Full path (includes groups and the name) of the monitor
|
* @returns {Promise<string[]>} Full path (includes groups and the name) of the monitor
|
||||||
*/
|
*/
|
||||||
async getPath() {
|
static async getAllPath(monitorID, name) {
|
||||||
const path = [ this.name ];
|
const path = [ name ];
|
||||||
|
|
||||||
if (this.parent === null) {
|
if (this.parent === null) {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
let parent = await Monitor.getParent(this.id);
|
let parent = await Monitor.getParent(monitorID);
|
||||||
while (parent !== null) {
|
while (parent !== null) {
|
||||||
path.unshift(parent.name);
|
path.unshift(parent.name);
|
||||||
parent = await Monitor.getParent(parent.id);
|
parent = await Monitor.getParent(parent.id);
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
let url = require("url");
|
let url = require("url");
|
||||||
let MemoryCache = require("./memory-cache");
|
let MemoryCache = require("./memory-cache");
|
||||||
const { log } = require("../../../src/util");
|
|
||||||
|
|
||||||
let t = {
|
let t = {
|
||||||
ms: 1,
|
ms: 1,
|
||||||
|
@ -91,6 +90,24 @@ function ApiCache() {
|
||||||
instances.push(this);
|
instances.push(this);
|
||||||
this.id = instances.length;
|
this.id = instances.length;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs a message to the console if the `DEBUG` environment variable is set.
|
||||||
|
* @param {string} a The first argument to log.
|
||||||
|
* @param {string} b The second argument to log.
|
||||||
|
* @param {string} c The third argument to log.
|
||||||
|
* @param {string} d The fourth argument to log, and so on... (optional)
|
||||||
|
*
|
||||||
|
* Generated by Trelent
|
||||||
|
*/
|
||||||
|
function debug(a, b, c, d) {
|
||||||
|
let arr = ["\x1b[36m[apicache]\x1b[0m", a, b, c, d].filter(function (arg) {
|
||||||
|
return arg !== undefined;
|
||||||
|
});
|
||||||
|
let debugEnv = process.env.DEBUG && process.env.DEBUG.split(",").indexOf("apicache") !== -1;
|
||||||
|
|
||||||
|
return (globalOptions.debug || debugEnv) && console.log.apply(null, arr);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the given request and response should be logged.
|
* Returns true if the given request and response should be logged.
|
||||||
* @param {Object} request The HTTP request object.
|
* @param {Object} request The HTTP request object.
|
||||||
|
@ -129,7 +146,7 @@ function ApiCache() {
|
||||||
let groupName = req.apicacheGroup;
|
let groupName = req.apicacheGroup;
|
||||||
|
|
||||||
if (groupName) {
|
if (groupName) {
|
||||||
log.debug("apicache", `group detected "${groupName}"`);
|
debug("group detected \"" + groupName + "\"");
|
||||||
let group = (index.groups[groupName] = index.groups[groupName] || []);
|
let group = (index.groups[groupName] = index.groups[groupName] || []);
|
||||||
group.unshift(key);
|
group.unshift(key);
|
||||||
}
|
}
|
||||||
|
@ -195,7 +212,7 @@ function ApiCache() {
|
||||||
redis.hset(key, "duration", duration);
|
redis.hset(key, "duration", duration);
|
||||||
redis.expire(key, duration / 1000, expireCallback || function () {});
|
redis.expire(key, duration / 1000, expireCallback || function () {});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.debug("apicache", `error in redis.hset(): ${err}`);
|
debug("[apicache] error in redis.hset()");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
memCache.add(key, value, duration, expireCallback);
|
memCache.add(key, value, duration, expireCallback);
|
||||||
|
@ -303,10 +320,10 @@ function ApiCache() {
|
||||||
|
|
||||||
// display log entry
|
// display log entry
|
||||||
let elapsed = new Date() - req.apicacheTimer;
|
let elapsed = new Date() - req.apicacheTimer;
|
||||||
log.debug("apicache", `adding cache entry for "${key}" @ ${strDuration} ${logDuration(elapsed)}`);
|
debug("adding cache entry for \"" + key + "\" @ " + strDuration, logDuration(elapsed));
|
||||||
log.debug("apicache", `_apicache.headers: ${JSON.stringify(res._apicache.headers)}`);
|
debug("_apicache.headers: ", res._apicache.headers);
|
||||||
log.debug("apicache", `res.getHeaders(): ${JSON.stringify(getSafeHeaders(res))}`);
|
debug("res.getHeaders(): ", getSafeHeaders(res));
|
||||||
log.debug("apicache", `cacheObject: ${JSON.stringify(cacheObject)}`);
|
debug("cacheObject: ", cacheObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -385,10 +402,10 @@ function ApiCache() {
|
||||||
let redis = globalOptions.redisClient;
|
let redis = globalOptions.redisClient;
|
||||||
|
|
||||||
if (group) {
|
if (group) {
|
||||||
log.debug("apicache", `clearing group "${target}"`);
|
debug("clearing group \"" + target + "\"");
|
||||||
|
|
||||||
group.forEach(function (key) {
|
group.forEach(function (key) {
|
||||||
log.debug("apicache", `clearing cached entry for "${key}"`);
|
debug("clearing cached entry for \"" + key + "\"");
|
||||||
clearTimeout(timers[key]);
|
clearTimeout(timers[key]);
|
||||||
delete timers[key];
|
delete timers[key];
|
||||||
if (!globalOptions.redisClient) {
|
if (!globalOptions.redisClient) {
|
||||||
|
@ -397,7 +414,7 @@ function ApiCache() {
|
||||||
try {
|
try {
|
||||||
redis.del(key);
|
redis.del(key);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.info("apicache", "error in redis.del(\"" + key + "\")");
|
console.log("[apicache] error in redis.del(\"" + key + "\")");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
index.all = index.all.filter(doesntMatch(key));
|
index.all = index.all.filter(doesntMatch(key));
|
||||||
|
@ -405,7 +422,7 @@ function ApiCache() {
|
||||||
|
|
||||||
delete index.groups[target];
|
delete index.groups[target];
|
||||||
} else if (target) {
|
} else if (target) {
|
||||||
log.debug("apicache", `clearing ${isAutomatic ? "expired" : "cached"} entry for "${target}"`);
|
debug("clearing " + (isAutomatic ? "expired" : "cached") + " entry for \"" + target + "\"");
|
||||||
clearTimeout(timers[target]);
|
clearTimeout(timers[target]);
|
||||||
delete timers[target];
|
delete timers[target];
|
||||||
// clear actual cached entry
|
// clear actual cached entry
|
||||||
|
@ -415,7 +432,7 @@ function ApiCache() {
|
||||||
try {
|
try {
|
||||||
redis.del(target);
|
redis.del(target);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error("apicache", "error in redis.del(\"" + target + "\")");
|
console.log("[apicache] error in redis.del(\"" + target + "\")");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -432,7 +449,7 @@ function ApiCache() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log.debug("apicache", "clearing entire index");
|
debug("clearing entire index");
|
||||||
|
|
||||||
if (!redis) {
|
if (!redis) {
|
||||||
memCache.clear();
|
memCache.clear();
|
||||||
|
@ -444,7 +461,7 @@ function ApiCache() {
|
||||||
try {
|
try {
|
||||||
redis.del(key);
|
redis.del(key);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error("apicache", `error in redis.del("${key}"): ${err}`);
|
console.log("[apicache] error in redis.del(\"" + key + "\")");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -735,7 +752,7 @@ function ApiCache() {
|
||||||
*/
|
*/
|
||||||
let cache = function (req, res, next) {
|
let cache = function (req, res, next) {
|
||||||
function bypass() {
|
function bypass() {
|
||||||
log.debug("apicache", "bypass detected, skipping cache.");
|
debug("bypass detected, skipping cache.");
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -788,7 +805,7 @@ function ApiCache() {
|
||||||
// send if cache hit from memory-cache
|
// send if cache hit from memory-cache
|
||||||
if (cached) {
|
if (cached) {
|
||||||
let elapsed = new Date() - req.apicacheTimer;
|
let elapsed = new Date() - req.apicacheTimer;
|
||||||
log.debug("apicache", `sending cached (memory-cache) version of ${key} ${logDuration(elapsed)}`);
|
debug("sending cached (memory-cache) version of", key, logDuration(elapsed));
|
||||||
|
|
||||||
perf.hit(key);
|
perf.hit(key);
|
||||||
return sendCachedResponse(req, res, cached, middlewareToggle, next, duration);
|
return sendCachedResponse(req, res, cached, middlewareToggle, next, duration);
|
||||||
|
@ -800,7 +817,7 @@ function ApiCache() {
|
||||||
redis.hgetall(key, function (err, obj) {
|
redis.hgetall(key, function (err, obj) {
|
||||||
if (!err && obj && obj.response) {
|
if (!err && obj && obj.response) {
|
||||||
let elapsed = new Date() - req.apicacheTimer;
|
let elapsed = new Date() - req.apicacheTimer;
|
||||||
log.debug("apicache", "sending cached (redis) version of "+ key+" "+ logDuration(elapsed));
|
debug("sending cached (redis) version of", key, logDuration(elapsed));
|
||||||
|
|
||||||
perf.hit(key);
|
perf.hit(key);
|
||||||
return sendCachedResponse(
|
return sendCachedResponse(
|
||||||
|
@ -856,7 +873,7 @@ function ApiCache() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (globalOptions.trackPerformance) {
|
if (globalOptions.trackPerformance) {
|
||||||
log.debug("apicache", "WARNING: using trackPerformance flag can cause high memory usage!");
|
debug("WARNING: using trackPerformance flag can cause high memory usage!");
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -63,7 +63,7 @@ if (process.platform === "win32") {
|
||||||
* @returns {Promise<boolean>} The executable is allowed?
|
* @returns {Promise<boolean>} The executable is allowed?
|
||||||
*/
|
*/
|
||||||
async function isAllowedChromeExecutable(executablePath) {
|
async function isAllowedChromeExecutable(executablePath) {
|
||||||
log.info("Chromium", config.args);
|
console.log(config.args);
|
||||||
if (config.args["allow-all-chrome-exec"] || process.env.UPTIME_KUMA_ALLOW_ALL_CHROME_EXEC === "1") {
|
if (config.args["allow-all-chrome-exec"] || process.env.UPTIME_KUMA_ALLOW_ALL_CHROME_EXEC === "1") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -102,8 +102,7 @@ async function getBrowser() {
|
||||||
*/
|
*/
|
||||||
async function getRemoteBrowser(remoteBrowserID, userId) {
|
async function getRemoteBrowser(remoteBrowserID, userId) {
|
||||||
let remoteBrowser = await RemoteBrowser.get(remoteBrowserID, userId);
|
let remoteBrowser = await RemoteBrowser.get(remoteBrowserID, userId);
|
||||||
log.debug("Chromium", `Using remote browser: ${remoteBrowser.name} (${remoteBrowser.id})`);
|
log.debug("MONITOR", `Using remote browser: ${remoteBrowser.name} (${remoteBrowser.id})`);
|
||||||
browser = chromium.connect(remoteBrowser.url);
|
|
||||||
browser = await chromium.connect(remoteBrowser.url);
|
browser = await chromium.connect(remoteBrowser.url);
|
||||||
return browser;
|
return browser;
|
||||||
}
|
}
|
||||||
|
|
35
server/notification-providers/46elks.js
Normal file
35
server/notification-providers/46elks.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
const NotificationProvider = require("./notification-provider");
|
||||||
|
const axios = require("axios");
|
||||||
|
|
||||||
|
class Elks extends NotificationProvider {
|
||||||
|
name = "Elks";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||||
|
const okMsg = "Sent Successfully.";
|
||||||
|
const url = "https://api.46elks.com/a1/sms";
|
||||||
|
|
||||||
|
try {
|
||||||
|
let data = new URLSearchParams();
|
||||||
|
data.append("from", notification.elksFromNumber);
|
||||||
|
data.append("to", notification.elksToNumber );
|
||||||
|
data.append("message", msg);
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
headers: {
|
||||||
|
"Authorization": "Basic " + Buffer.from(`${notification.elksUsername}:${notification.elksAuthToken}`).toString("base64")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await axios.post(url, data, config);
|
||||||
|
|
||||||
|
return okMsg;
|
||||||
|
} catch (error) {
|
||||||
|
this.throwGeneralAxiosError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Elks;
|
|
@ -1,7 +1,7 @@
|
||||||
const NotificationProvider = require("./notification-provider");
|
const NotificationProvider = require("./notification-provider");
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
|
const { setting } = require("../util-server");
|
||||||
const { getMonitorRelativeURL, UP, DOWN } = require("../../src/util");
|
const { getMonitorRelativeURL, UP, DOWN } = require("../../src/util");
|
||||||
const { Settings } = require("../settings");
|
|
||||||
|
|
||||||
class AlertNow extends NotificationProvider {
|
class AlertNow extends NotificationProvider {
|
||||||
name = "AlertNow";
|
name = "AlertNow";
|
||||||
|
@ -29,7 +29,7 @@ class AlertNow extends NotificationProvider {
|
||||||
|
|
||||||
textMsg += ` - ${msg}`;
|
textMsg += ` - ${msg}`;
|
||||||
|
|
||||||
const baseURL = await Settings.get("primaryBaseURL");
|
const baseURL = await setting("primaryBaseURL");
|
||||||
if (baseURL && monitorJSON) {
|
if (baseURL && monitorJSON) {
|
||||||
textMsg += ` >> ${baseURL + getMonitorRelativeURL(monitorJSON.id)}`;
|
textMsg += ` >> ${baseURL + getMonitorRelativeURL(monitorJSON.id)}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,7 +87,6 @@ class DingDing extends NotificationProvider {
|
||||||
* @returns {string} Status
|
* @returns {string} Status
|
||||||
*/
|
*/
|
||||||
statusToString(status) {
|
statusToString(status) {
|
||||||
// TODO: Move to notification-provider.js to avoid repetition in classes
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case DOWN:
|
case DOWN:
|
||||||
return "DOWN";
|
return "DOWN";
|
||||||
|
|
|
@ -48,7 +48,7 @@ class Discord extends NotificationProvider {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
||||||
value: this.extractAdress(monitorJSON),
|
value: this.extractAddress(monitorJSON),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `Time (${heartbeatJSON["timezone"]})`,
|
name: `Time (${heartbeatJSON["timezone"]})`,
|
||||||
|
@ -85,7 +85,7 @@ class Discord extends NotificationProvider {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
||||||
value: this.extractAdress(monitorJSON),
|
value: this.extractAddress(monitorJSON),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `Time (${heartbeatJSON["timezone"]})`,
|
name: `Time (${heartbeatJSON["timezone"]})`,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const NotificationProvider = require("./notification-provider");
|
const NotificationProvider = require("./notification-provider");
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
|
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
|
||||||
const { Settings } = require("../settings");
|
const { setting } = require("../util-server");
|
||||||
const successMessage = "Sent Successfully.";
|
const successMessage = "Sent Successfully.";
|
||||||
|
|
||||||
class FlashDuty extends NotificationProvider {
|
class FlashDuty extends NotificationProvider {
|
||||||
|
@ -84,7 +84,7 @@ class FlashDuty extends NotificationProvider {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseURL = await Settings.get("primaryBaseURL");
|
const baseURL = await setting("primaryBaseURL");
|
||||||
if (baseURL && monitorInfo) {
|
if (baseURL && monitorInfo) {
|
||||||
options.client = "Uptime Kuma";
|
options.client = "Uptime Kuma";
|
||||||
options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id);
|
options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const NotificationProvider = require("./notification-provider");
|
const NotificationProvider = require("./notification-provider");
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
|
const { setting } = require("../util-server");
|
||||||
const { getMonitorRelativeURL, UP } = require("../../src/util");
|
const { getMonitorRelativeURL, UP } = require("../../src/util");
|
||||||
const { Settings } = require("../settings");
|
|
||||||
|
|
||||||
class GoogleChat extends NotificationProvider {
|
class GoogleChat extends NotificationProvider {
|
||||||
name = "GoogleChat";
|
name = "GoogleChat";
|
||||||
|
@ -45,7 +45,7 @@ class GoogleChat extends NotificationProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// add button for monitor link if available
|
// add button for monitor link if available
|
||||||
const baseURL = await Settings.get("primaryBaseURL");
|
const baseURL = await setting("primaryBaseURL");
|
||||||
if (baseURL) {
|
if (baseURL) {
|
||||||
const urlPath = monitorJSON ? getMonitorRelativeURL(monitorJSON.id) : "/";
|
const urlPath = monitorJSON ? getMonitorRelativeURL(monitorJSON.id) : "/";
|
||||||
sectionWidgets.push({
|
sectionWidgets.push({
|
||||||
|
|
|
@ -24,7 +24,7 @@ class NotificationProvider {
|
||||||
* @param {?object} monitorJSON Monitor details (For Up/Down only)
|
* @param {?object} monitorJSON Monitor details (For Up/Down only)
|
||||||
* @returns {string} The extracted address based on the monitor type.
|
* @returns {string} The extracted address based on the monitor type.
|
||||||
*/
|
*/
|
||||||
extractAdress(monitorJSON) {
|
extractAddress(monitorJSON) {
|
||||||
if (!monitorJSON) {
|
if (!monitorJSON) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const NotificationProvider = require("./notification-provider");
|
const NotificationProvider = require("./notification-provider");
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
|
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
|
||||||
const { Settings } = require("../settings");
|
const { setting } = require("../util-server");
|
||||||
let successMessage = "Sent Successfully.";
|
let successMessage = "Sent Successfully.";
|
||||||
|
|
||||||
class PagerDuty extends NotificationProvider {
|
class PagerDuty extends NotificationProvider {
|
||||||
|
@ -95,7 +95,7 @@ class PagerDuty extends NotificationProvider {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseURL = await Settings.get("primaryBaseURL");
|
const baseURL = await setting("primaryBaseURL");
|
||||||
if (baseURL && monitorInfo) {
|
if (baseURL && monitorInfo) {
|
||||||
options.client = "Uptime Kuma";
|
options.client = "Uptime Kuma";
|
||||||
options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id);
|
options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const NotificationProvider = require("./notification-provider");
|
const NotificationProvider = require("./notification-provider");
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
|
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
|
||||||
const { Settings } = require("../settings");
|
const { setting } = require("../util-server");
|
||||||
let successMessage = "Sent Successfully.";
|
let successMessage = "Sent Successfully.";
|
||||||
|
|
||||||
class PagerTree extends NotificationProvider {
|
class PagerTree extends NotificationProvider {
|
||||||
|
@ -74,7 +74,7 @@ class PagerTree extends NotificationProvider {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseURL = await Settings.get("primaryBaseURL");
|
const baseURL = await setting("primaryBaseURL");
|
||||||
if (baseURL && monitorJSON) {
|
if (baseURL && monitorJSON) {
|
||||||
options.client = "Uptime Kuma";
|
options.client = "Uptime Kuma";
|
||||||
options.client_url = baseURL + getMonitorRelativeURL(monitorJSON.id);
|
options.client_url = baseURL + getMonitorRelativeURL(monitorJSON.id);
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
const NotificationProvider = require("./notification-provider");
|
const NotificationProvider = require("./notification-provider");
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
const Slack = require("./slack");
|
const Slack = require("./slack");
|
||||||
|
const { setting } = require("../util-server");
|
||||||
const { getMonitorRelativeURL, DOWN } = require("../../src/util");
|
const { getMonitorRelativeURL, DOWN } = require("../../src/util");
|
||||||
const { Settings } = require("../settings");
|
|
||||||
|
|
||||||
class RocketChat extends NotificationProvider {
|
class RocketChat extends NotificationProvider {
|
||||||
name = "rocket.chat";
|
name = "rocket.chat";
|
||||||
|
@ -49,7 +49,7 @@ class RocketChat extends NotificationProvider {
|
||||||
await Slack.deprecateURL(notification.rocketbutton);
|
await Slack.deprecateURL(notification.rocketbutton);
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseURL = await Settings.get("primaryBaseURL");
|
const baseURL = await setting("primaryBaseURL");
|
||||||
|
|
||||||
if (baseURL) {
|
if (baseURL) {
|
||||||
data.attachments[0].title_link = baseURL + getMonitorRelativeURL(monitorJSON.id);
|
data.attachments[0].title_link = baseURL + getMonitorRelativeURL(monitorJSON.id);
|
||||||
|
|
|
@ -32,7 +32,7 @@ class SevenIO extends NotificationProvider {
|
||||||
return okMsg;
|
return okMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
let address = this.extractAdress(monitorJSON);
|
let address = this.extractAddress(monitorJSON);
|
||||||
if (address !== "") {
|
if (address !== "") {
|
||||||
address = `(${address}) `;
|
address = `(${address}) `;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ class SIGNL4 extends NotificationProvider {
|
||||||
msg,
|
msg,
|
||||||
// Source system
|
// Source system
|
||||||
"X-S4-SourceSystem": "UptimeKuma",
|
"X-S4-SourceSystem": "UptimeKuma",
|
||||||
monitorUrl: this.extractAdress(monitorJSON),
|
monitorUrl: this.extractAddress(monitorJSON),
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
const NotificationProvider = require("./notification-provider");
|
const NotificationProvider = require("./notification-provider");
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
|
const { setSettings, setting } = require("../util-server");
|
||||||
const { getMonitorRelativeURL, UP } = require("../../src/util");
|
const { getMonitorRelativeURL, UP } = require("../../src/util");
|
||||||
const { Settings } = require("../settings");
|
|
||||||
const { log } = require("../../src/util");
|
|
||||||
|
|
||||||
class Slack extends NotificationProvider {
|
class Slack extends NotificationProvider {
|
||||||
name = "slack";
|
name = "slack";
|
||||||
|
@ -15,13 +14,15 @@ class Slack extends NotificationProvider {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
static async deprecateURL(url) {
|
static async deprecateURL(url) {
|
||||||
let currentPrimaryBaseURL = await Settings.get("primaryBaseURL");
|
let currentPrimaryBaseURL = await setting("primaryBaseURL");
|
||||||
|
|
||||||
if (!currentPrimaryBaseURL) {
|
if (!currentPrimaryBaseURL) {
|
||||||
log.error("notification", "Move the url to be the primary base URL");
|
console.log("Move the url to be the primary base URL");
|
||||||
await Settings.set("primaryBaseURL", url, "general");
|
await setSettings("general", {
|
||||||
|
primaryBaseURL: url,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
log.debug("notification", "Already there, no need to move the primary base URL");
|
console.log("Already there, no need to move the primary base URL");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +48,7 @@ class Slack extends NotificationProvider {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const address = this.extractAdress(monitorJSON);
|
const address = this.extractAddress(monitorJSON);
|
||||||
if (address) {
|
if (address) {
|
||||||
actions.push({
|
actions.push({
|
||||||
"type": "button",
|
"type": "button",
|
||||||
|
@ -135,21 +136,26 @@ class Slack extends NotificationProvider {
|
||||||
return okMsg;
|
return okMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseURL = await Settings.get("primaryBaseURL");
|
const baseURL = await setting("primaryBaseURL");
|
||||||
|
|
||||||
const title = "Uptime Kuma Alert";
|
const title = "Uptime Kuma Alert";
|
||||||
let data = {
|
let data = {
|
||||||
"text": `${title}\n${msg}`,
|
|
||||||
"channel": notification.slackchannel,
|
"channel": notification.slackchannel,
|
||||||
"username": notification.slackusername,
|
"username": notification.slackusername,
|
||||||
"icon_emoji": notification.slackiconemo,
|
"icon_emoji": notification.slackiconemo,
|
||||||
"attachments": [
|
"attachments": [],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (notification.slackrichmessage) {
|
||||||
|
data.attachments.push(
|
||||||
{
|
{
|
||||||
"color": (heartbeatJSON["status"] === UP) ? "#2eb886" : "#e01e5a",
|
"color": (heartbeatJSON["status"] === UP) ? "#2eb886" : "#e01e5a",
|
||||||
"blocks": Slack.buildBlocks(baseURL, monitorJSON, heartbeatJSON, title, msg),
|
"blocks": Slack.buildBlocks(baseURL, monitorJSON, heartbeatJSON, title, msg),
|
||||||
}
|
}
|
||||||
]
|
);
|
||||||
};
|
} else {
|
||||||
|
data.text = `${title}\n${msg}`;
|
||||||
|
}
|
||||||
|
|
||||||
if (notification.slackbutton) {
|
if (notification.slackbutton) {
|
||||||
await Slack.deprecateURL(notification.slackbutton);
|
await Slack.deprecateURL(notification.slackbutton);
|
||||||
|
|
|
@ -93,7 +93,7 @@ class SMTP extends NotificationProvider {
|
||||||
|
|
||||||
if (monitorJSON !== null) {
|
if (monitorJSON !== null) {
|
||||||
monitorName = monitorJSON["name"];
|
monitorName = monitorJSON["name"];
|
||||||
monitorHostnameOrURL = this.extractAdress(monitorJSON);
|
monitorHostnameOrURL = this.extractAddress(monitorJSON);
|
||||||
}
|
}
|
||||||
|
|
||||||
let serviceStatus = "⚠️ Test";
|
let serviceStatus = "⚠️ Test";
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const NotificationProvider = require("./notification-provider");
|
const NotificationProvider = require("./notification-provider");
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
|
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
|
||||||
const { Settings } = require("../settings");
|
const { setting } = require("../util-server");
|
||||||
let successMessage = "Sent Successfully.";
|
let successMessage = "Sent Successfully.";
|
||||||
|
|
||||||
class Splunk extends NotificationProvider {
|
class Splunk extends NotificationProvider {
|
||||||
|
@ -95,7 +95,7 @@ class Splunk extends NotificationProvider {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseURL = await Settings.get("primaryBaseURL");
|
const baseURL = await setting("primaryBaseURL");
|
||||||
if (baseURL && monitorInfo) {
|
if (baseURL && monitorInfo) {
|
||||||
options.client = "Uptime Kuma";
|
options.client = "Uptime Kuma";
|
||||||
options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id);
|
options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id);
|
||||||
|
|
|
@ -34,7 +34,7 @@ class Squadcast extends NotificationProvider {
|
||||||
data.status = "resolve";
|
data.status = "resolve";
|
||||||
}
|
}
|
||||||
|
|
||||||
data.tags["AlertAddress"] = this.extractAdress(monitorJSON);
|
data.tags["AlertAddress"] = this.extractAddress(monitorJSON);
|
||||||
|
|
||||||
monitorJSON["tags"].forEach(tag => {
|
monitorJSON["tags"].forEach(tag => {
|
||||||
data.tags[tag["name"]] = {
|
data.tags[tag["name"]] = {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const NotificationProvider = require("./notification-provider");
|
const NotificationProvider = require("./notification-provider");
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
|
const { setting } = require("../util-server");
|
||||||
const { getMonitorRelativeURL } = require("../../src/util");
|
const { getMonitorRelativeURL } = require("../../src/util");
|
||||||
const { Settings } = require("../settings");
|
|
||||||
|
|
||||||
class Stackfield extends NotificationProvider {
|
class Stackfield extends NotificationProvider {
|
||||||
name = "stackfield";
|
name = "stackfield";
|
||||||
|
@ -23,7 +23,7 @@ class Stackfield extends NotificationProvider {
|
||||||
|
|
||||||
textMsg += `\n${msg}`;
|
textMsg += `\n${msg}`;
|
||||||
|
|
||||||
const baseURL = await Settings.get("primaryBaseURL");
|
const baseURL = await setting("primaryBaseURL");
|
||||||
if (baseURL) {
|
if (baseURL) {
|
||||||
textMsg += `\n${baseURL + getMonitorRelativeURL(monitorJSON.id)}`;
|
textMsg += `\n${baseURL + getMonitorRelativeURL(monitorJSON.id)}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -225,7 +225,7 @@ class Teams extends NotificationProvider {
|
||||||
const payload = this._notificationPayloadFactory({
|
const payload = this._notificationPayloadFactory({
|
||||||
heartbeatJSON: heartbeatJSON,
|
heartbeatJSON: heartbeatJSON,
|
||||||
monitorName: monitorJSON.name,
|
monitorName: monitorJSON.name,
|
||||||
monitorUrl: this.extractAdress(monitorJSON),
|
monitorUrl: this.extractAddress(monitorJSON),
|
||||||
dashboardUrl: dashboardUrl,
|
dashboardUrl: dashboardUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -10,11 +10,22 @@ class TechulusPush extends NotificationProvider {
|
||||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||||
const okMsg = "Sent Successfully.";
|
const okMsg = "Sent Successfully.";
|
||||||
|
|
||||||
try {
|
let data = {
|
||||||
await axios.post(`https://push.techulus.com/api/v1/notify/${notification.pushAPIKey}`, {
|
"title": notification?.pushTitle?.length ? notification.pushTitle : "Uptime-Kuma",
|
||||||
"title": "Uptime-Kuma",
|
|
||||||
"body": msg,
|
"body": msg,
|
||||||
});
|
"timeSensitive": notification.pushTimeSensitive ?? true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (notification.pushChannel) {
|
||||||
|
data.channel = notification.pushChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notification.pushSound) {
|
||||||
|
data.sound = notification.pushSound;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.post(`https://push.techulus.com/api/v1/notify/${notification.pushAPIKey}`, data);
|
||||||
return okMsg;
|
return okMsg;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.throwGeneralAxiosError(error);
|
this.throwGeneralAxiosError(error);
|
||||||
|
|
|
@ -32,20 +32,17 @@ class WeCom extends NotificationProvider {
|
||||||
* @returns {object} Message
|
* @returns {object} Message
|
||||||
*/
|
*/
|
||||||
composeMessage(heartbeatJSON, msg) {
|
composeMessage(heartbeatJSON, msg) {
|
||||||
let title;
|
let title = "UptimeKuma Message";
|
||||||
if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] === UP) {
|
if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] === UP) {
|
||||||
title = "UptimeKuma Monitor Up";
|
title = "UptimeKuma Monitor Up";
|
||||||
}
|
}
|
||||||
if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] === DOWN) {
|
if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] === DOWN) {
|
||||||
title = "UptimeKuma Monitor Down";
|
title = "UptimeKuma Monitor Down";
|
||||||
}
|
}
|
||||||
if (msg != null) {
|
|
||||||
title = "UptimeKuma Message";
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
msgtype: "text",
|
msgtype: "text",
|
||||||
text: {
|
text: {
|
||||||
content: title + msg
|
content: title + "\n" + msg
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,7 @@ class ZohoCliq extends NotificationProvider {
|
||||||
const payload = this._notificationPayloadFactory({
|
const payload = this._notificationPayloadFactory({
|
||||||
monitorMessage: heartbeatJSON.msg,
|
monitorMessage: heartbeatJSON.msg,
|
||||||
monitorName: monitorJSON.name,
|
monitorName: monitorJSON.name,
|
||||||
monitorUrl: this.extractAdress(monitorJSON),
|
monitorUrl: this.extractAddress(monitorJSON),
|
||||||
status: heartbeatJSON.status
|
status: heartbeatJSON.status
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ const CallMeBot = require("./notification-providers/call-me-bot");
|
||||||
const SMSC = require("./notification-providers/smsc");
|
const SMSC = require("./notification-providers/smsc");
|
||||||
const DingDing = require("./notification-providers/dingding");
|
const DingDing = require("./notification-providers/dingding");
|
||||||
const Discord = require("./notification-providers/discord");
|
const Discord = require("./notification-providers/discord");
|
||||||
|
const Elks = require("./notification-providers/46elks");
|
||||||
const Feishu = require("./notification-providers/feishu");
|
const Feishu = require("./notification-providers/feishu");
|
||||||
const FreeMobile = require("./notification-providers/freemobile");
|
const FreeMobile = require("./notification-providers/freemobile");
|
||||||
const GoogleChat = require("./notification-providers/google-chat");
|
const GoogleChat = require("./notification-providers/google-chat");
|
||||||
|
@ -95,6 +96,7 @@ class Notification {
|
||||||
new SMSC(),
|
new SMSC(),
|
||||||
new DingDing(),
|
new DingDing(),
|
||||||
new Discord(),
|
new Discord(),
|
||||||
|
new Elks(),
|
||||||
new Feishu(),
|
new Feishu(),
|
||||||
new FreeMobile(),
|
new FreeMobile(),
|
||||||
new GoogleChat(),
|
new GoogleChat(),
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
const { R } = require("redbean-node");
|
|
||||||
const PrometheusClient = require("prom-client");
|
const PrometheusClient = require("prom-client");
|
||||||
const { log } = require("../src/util");
|
const { log } = require("../src/util");
|
||||||
|
|
||||||
|
@ -10,102 +9,36 @@ const commonLabels = [
|
||||||
"monitor_port",
|
"monitor_port",
|
||||||
];
|
];
|
||||||
|
|
||||||
class Prometheus {
|
const monitorCertDaysRemaining = new PrometheusClient.Gauge({
|
||||||
|
|
||||||
/**
|
|
||||||
* Metric: monitor_cert_days_remaining
|
|
||||||
* @type {PrometheusClient.Gauge<string> | null}
|
|
||||||
*/
|
|
||||||
static monitorCertDaysRemaining = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Metric: monitor_cert_is_valid
|
|
||||||
* @type {PrometheusClient.Gauge<string> | null}
|
|
||||||
*/
|
|
||||||
static monitorCertIsValid = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Metric: monitor_response_time
|
|
||||||
* @type {PrometheusClient.Gauge<string> | null}
|
|
||||||
*/
|
|
||||||
static monitorResponseTime = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Metric: monitor_status
|
|
||||||
* @type {PrometheusClient.Gauge<string> | null}
|
|
||||||
*/
|
|
||||||
static monitorStatus = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All registered metric labels.
|
|
||||||
* @type {string[] | null}
|
|
||||||
*/
|
|
||||||
static monitorLabelNames = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Monitor labels/values combination.
|
|
||||||
* @type {{}}
|
|
||||||
*/
|
|
||||||
monitorLabelValues;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize metrics and get all label names the first time called.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
static async initMetrics() {
|
|
||||||
if (!this.monitorLabelNames) {
|
|
||||||
let labelNames = await R.getCol("SELECT name FROM tag");
|
|
||||||
this.monitorLabelNames = [ ...commonLabels, ...labelNames ];
|
|
||||||
}
|
|
||||||
if (!this.monitorCertDaysRemaining) {
|
|
||||||
this.monitorCertDaysRemaining = new PrometheusClient.Gauge({
|
|
||||||
name: "monitor_cert_days_remaining",
|
name: "monitor_cert_days_remaining",
|
||||||
help: "The number of days remaining until the certificate expires",
|
help: "The number of days remaining until the certificate expires",
|
||||||
labelNames: this.monitorLabelNames
|
labelNames: commonLabels
|
||||||
});
|
});
|
||||||
}
|
|
||||||
if (!this.monitorCertIsValid) {
|
const monitorCertIsValid = new PrometheusClient.Gauge({
|
||||||
this.monitorCertIsValid = new PrometheusClient.Gauge({
|
|
||||||
name: "monitor_cert_is_valid",
|
name: "monitor_cert_is_valid",
|
||||||
help: "Is the certificate still valid? (1 = Yes, 0 = No)",
|
help: "Is the certificate still valid? (1 = Yes, 0= No)",
|
||||||
labelNames: this.monitorLabelNames
|
labelNames: commonLabels
|
||||||
});
|
});
|
||||||
}
|
const monitorResponseTime = new PrometheusClient.Gauge({
|
||||||
if (!this.monitorResponseTime) {
|
|
||||||
this.monitorResponseTime = new PrometheusClient.Gauge({
|
|
||||||
name: "monitor_response_time",
|
name: "monitor_response_time",
|
||||||
help: "Monitor Response Time (ms)",
|
help: "Monitor Response Time (ms)",
|
||||||
labelNames: this.monitorLabelNames
|
labelNames: commonLabels
|
||||||
});
|
});
|
||||||
}
|
|
||||||
if (!this.monitorStatus) {
|
const monitorStatus = new PrometheusClient.Gauge({
|
||||||
this.monitorStatus = new PrometheusClient.Gauge({
|
|
||||||
name: "monitor_status",
|
name: "monitor_status",
|
||||||
help: "Monitor Status (1 = UP, 0 = DOWN, 2 = PENDING, 3 = MAINTENANCE)",
|
help: "Monitor Status (1 = UP, 0= DOWN, 2= PENDING, 3= MAINTENANCE)",
|
||||||
labelNames: this.monitorLabelNames
|
labelNames: commonLabels
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
class Prometheus {
|
||||||
|
monitorLabelValues = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper to create a `Prometheus` instance and ensure metrics are initialized.
|
* @param {object} monitor Monitor object to monitor
|
||||||
* @param {Monitor} monitor Monitor object to monitor
|
|
||||||
* @returns {Promise<Prometheus>} `Prometheus` instance
|
|
||||||
*/
|
*/
|
||||||
static async createAndInitMetrics(monitor) {
|
constructor(monitor) {
|
||||||
await Prometheus.initMetrics();
|
|
||||||
let tags = await monitor.getTags();
|
|
||||||
return new Prometheus(monitor, tags);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a prometheus metric instance.
|
|
||||||
*
|
|
||||||
* Note: Make sure to call `Prometheus.initMetrics()` once prior creating Prometheus instances.
|
|
||||||
* @param {Monitor} monitor Monitor object to monitor
|
|
||||||
* @param {Promise<LooseObject<any>[]>} tags Tags of the monitor
|
|
||||||
*/
|
|
||||||
constructor(monitor, tags) {
|
|
||||||
this.monitorLabelValues = {
|
this.monitorLabelValues = {
|
||||||
monitor_name: monitor.name,
|
monitor_name: monitor.name,
|
||||||
monitor_type: monitor.type,
|
monitor_type: monitor.type,
|
||||||
|
@ -113,12 +46,6 @@ class Prometheus {
|
||||||
monitor_hostname: monitor.hostname,
|
monitor_hostname: monitor.hostname,
|
||||||
monitor_port: monitor.port
|
monitor_port: monitor.port
|
||||||
};
|
};
|
||||||
Object.values(tags)
|
|
||||||
// only label names that were known at first metric creation.
|
|
||||||
.filter(tag => Prometheus.monitorLabelNames.includes(tag.name))
|
|
||||||
.forEach(tag => {
|
|
||||||
this.monitorLabelValues[tag.name] = tag.value;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -128,6 +55,7 @@ class Prometheus {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
update(heartbeat, tlsInfo) {
|
update(heartbeat, tlsInfo) {
|
||||||
|
|
||||||
if (typeof tlsInfo !== "undefined") {
|
if (typeof tlsInfo !== "undefined") {
|
||||||
try {
|
try {
|
||||||
let isValid;
|
let isValid;
|
||||||
|
@ -136,7 +64,7 @@ class Prometheus {
|
||||||
} else {
|
} else {
|
||||||
isValid = 0;
|
isValid = 0;
|
||||||
}
|
}
|
||||||
Prometheus.monitorCertIsValid.set(this.monitorLabelValues, isValid);
|
monitorCertIsValid.set(this.monitorLabelValues, isValid);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("prometheus", "Caught error");
|
log.error("prometheus", "Caught error");
|
||||||
log.error("prometheus", e);
|
log.error("prometheus", e);
|
||||||
|
@ -144,7 +72,7 @@ class Prometheus {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (tlsInfo.certInfo != null) {
|
if (tlsInfo.certInfo != null) {
|
||||||
Prometheus.monitorCertDaysRemaining.set(this.monitorLabelValues, tlsInfo.certInfo.daysRemaining);
|
monitorCertDaysRemaining.set(this.monitorLabelValues, tlsInfo.certInfo.daysRemaining);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("prometheus", "Caught error");
|
log.error("prometheus", "Caught error");
|
||||||
|
@ -154,7 +82,7 @@ class Prometheus {
|
||||||
|
|
||||||
if (heartbeat) {
|
if (heartbeat) {
|
||||||
try {
|
try {
|
||||||
Prometheus.monitorStatus.set(this.monitorLabelValues, heartbeat.status);
|
monitorStatus.set(this.monitorLabelValues, heartbeat.status);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("prometheus", "Caught error");
|
log.error("prometheus", "Caught error");
|
||||||
log.error("prometheus", e);
|
log.error("prometheus", e);
|
||||||
|
@ -162,10 +90,10 @@ class Prometheus {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (typeof heartbeat.ping === "number") {
|
if (typeof heartbeat.ping === "number") {
|
||||||
Prometheus.monitorResponseTime.set(this.monitorLabelValues, heartbeat.ping);
|
monitorResponseTime.set(this.monitorLabelValues, heartbeat.ping);
|
||||||
} else {
|
} else {
|
||||||
// Is it good?
|
// Is it good?
|
||||||
Prometheus.monitorResponseTime.set(this.monitorLabelValues, -1);
|
monitorResponseTime.set(this.monitorLabelValues, -1);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("prometheus", "Caught error");
|
log.error("prometheus", "Caught error");
|
||||||
|
@ -180,10 +108,10 @@ class Prometheus {
|
||||||
*/
|
*/
|
||||||
remove() {
|
remove() {
|
||||||
try {
|
try {
|
||||||
Prometheus.monitorCertDaysRemaining?.remove(this.monitorLabelValues);
|
monitorCertDaysRemaining.remove(this.monitorLabelValues);
|
||||||
Prometheus.monitorCertIsValid?.remove(this.monitorLabelValues);
|
monitorCertIsValid.remove(this.monitorLabelValues);
|
||||||
Prometheus.monitorResponseTime?.remove(this.monitorLabelValues);
|
monitorResponseTime.remove(this.monitorLabelValues);
|
||||||
Prometheus.monitorStatus?.remove(this.monitorLabelValues);
|
monitorStatus.remove(this.monitorLabelValues);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ const { R } = require("redbean-node");
|
||||||
const HttpProxyAgent = require("http-proxy-agent");
|
const HttpProxyAgent = require("http-proxy-agent");
|
||||||
const HttpsProxyAgent = require("https-proxy-agent");
|
const HttpsProxyAgent = require("https-proxy-agent");
|
||||||
const SocksProxyAgent = require("socks-proxy-agent");
|
const SocksProxyAgent = require("socks-proxy-agent");
|
||||||
const { log } = require("../src/util");
|
const { debug } = require("../src/util");
|
||||||
const { UptimeKumaServer } = require("./uptime-kuma-server");
|
const { UptimeKumaServer } = require("./uptime-kuma-server");
|
||||||
const { CookieJar } = require("tough-cookie");
|
const { CookieJar } = require("tough-cookie");
|
||||||
const { createCookieAgent } = require("http-cookie-agent/http");
|
const { createCookieAgent } = require("http-cookie-agent/http");
|
||||||
|
@ -110,9 +110,9 @@ class Proxy {
|
||||||
proxyOptions.auth = `${proxy.username}:${proxy.password}`;
|
proxyOptions.auth = `${proxy.username}:${proxy.password}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("update-proxy", `Proxy Options: ${JSON.stringify(proxyOptions)}`);
|
debug(`Proxy Options: ${JSON.stringify(proxyOptions)}`);
|
||||||
log.debug("update-proxy", `HTTP Agent Options: ${JSON.stringify(httpAgentOptions)}`);
|
debug(`HTTP Agent Options: ${JSON.stringify(httpAgentOptions)}`);
|
||||||
log.debug("update-proxy", `HTTPS Agent Options: ${JSON.stringify(httpsAgentOptions)}`);
|
debug(`HTTPS Agent Options: ${JSON.stringify(httpsAgentOptions)}`);
|
||||||
|
|
||||||
switch (proxy.protocol) {
|
switch (proxy.protocol) {
|
||||||
case "http":
|
case "http":
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
let express = require("express");
|
let express = require("express");
|
||||||
const {
|
const {
|
||||||
|
setting,
|
||||||
allowDevAllOrigin,
|
allowDevAllOrigin,
|
||||||
allowAllOrigin,
|
allowAllOrigin,
|
||||||
percentageToColor,
|
percentageToColor,
|
||||||
|
@ -17,7 +18,6 @@ const { makeBadge } = require("badge-maker");
|
||||||
const { Prometheus } = require("../prometheus");
|
const { Prometheus } = require("../prometheus");
|
||||||
const Database = require("../database");
|
const Database = require("../database");
|
||||||
const { UptimeCalculator } = require("../uptime-calculator");
|
const { UptimeCalculator } = require("../uptime-calculator");
|
||||||
const { Settings } = require("../settings");
|
|
||||||
|
|
||||||
let router = express.Router();
|
let router = express.Router();
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ router.get("/api/entry-page", async (request, response) => {
|
||||||
|
|
||||||
let result = { };
|
let result = { };
|
||||||
let hostname = request.hostname;
|
let hostname = request.hostname;
|
||||||
if ((await Settings.get("trustProxy")) && request.headers["x-forwarded-host"]) {
|
if ((await setting("trustProxy")) && request.headers["x-forwarded-host"]) {
|
||||||
hostname = request.headers["x-forwarded-host"];
|
hostname = request.headers["x-forwarded-host"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -90,7 +90,8 @@ const Monitor = require("./model/monitor");
|
||||||
const User = require("./model/user");
|
const User = require("./model/user");
|
||||||
|
|
||||||
log.debug("server", "Importing Settings");
|
log.debug("server", "Importing Settings");
|
||||||
const { initJWTSecret, checkLogin, doubleCheckPassword, shake256, SHAKE256_LENGTH, allowDevAllOrigin } = require("./util-server");
|
const { getSettings, setSettings, setting, initJWTSecret, checkLogin, doubleCheckPassword, shake256, SHAKE256_LENGTH, allowDevAllOrigin,
|
||||||
|
} = require("./util-server");
|
||||||
|
|
||||||
log.debug("server", "Importing Notification");
|
log.debug("server", "Importing Notification");
|
||||||
const { Notification } = require("./notification");
|
const { Notification } = require("./notification");
|
||||||
|
@ -200,7 +201,7 @@ let needSetup = false;
|
||||||
// Entry Page
|
// Entry Page
|
||||||
app.get("/", async (request, response) => {
|
app.get("/", async (request, response) => {
|
||||||
let hostname = request.hostname;
|
let hostname = request.hostname;
|
||||||
if (await Settings.get("trustProxy")) {
|
if (await setting("trustProxy")) {
|
||||||
const proxy = request.headers["x-forwarded-host"];
|
const proxy = request.headers["x-forwarded-host"];
|
||||||
if (proxy) {
|
if (proxy) {
|
||||||
hostname = proxy;
|
hostname = proxy;
|
||||||
|
@ -280,7 +281,7 @@ let needSetup = false;
|
||||||
// Robots.txt
|
// Robots.txt
|
||||||
app.get("/robots.txt", async (_request, response) => {
|
app.get("/robots.txt", async (_request, response) => {
|
||||||
let txt = "User-agent: *\nDisallow:";
|
let txt = "User-agent: *\nDisallow:";
|
||||||
if (!await Settings.get("searchEngineIndex")) {
|
if (!await setting("searchEngineIndex")) {
|
||||||
txt += " /";
|
txt += " /";
|
||||||
}
|
}
|
||||||
response.setHeader("Content-Type", "text/plain");
|
response.setHeader("Content-Type", "text/plain");
|
||||||
|
@ -726,7 +727,7 @@ let needSetup = false;
|
||||||
|
|
||||||
await updateMonitorNotification(bean.id, notificationIDList);
|
await updateMonitorNotification(bean.id, notificationIDList);
|
||||||
|
|
||||||
await server.sendMonitorList(socket);
|
await server.sendUpdateMonitorIntoList(socket, bean.id);
|
||||||
|
|
||||||
if (monitor.active !== false) {
|
if (monitor.active !== false) {
|
||||||
await startMonitor(socket.userID, bean.id);
|
await startMonitor(socket.userID, bean.id);
|
||||||
|
@ -879,11 +880,11 @@ let needSetup = false;
|
||||||
|
|
||||||
await updateMonitorNotification(bean.id, monitor.notificationIDList);
|
await updateMonitorNotification(bean.id, monitor.notificationIDList);
|
||||||
|
|
||||||
if (await bean.isActive()) {
|
if (await Monitor.isActive(bean.id, bean.active)) {
|
||||||
await restartMonitor(socket.userID, bean.id);
|
await restartMonitor(socket.userID, bean.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
await server.sendMonitorList(socket);
|
await server.sendUpdateMonitorIntoList(socket, bean.id);
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
|
@ -923,14 +924,17 @@ let needSetup = false;
|
||||||
|
|
||||||
log.info("monitor", `Get Monitor: ${monitorID} User ID: ${socket.userID}`);
|
log.info("monitor", `Get Monitor: ${monitorID} User ID: ${socket.userID}`);
|
||||||
|
|
||||||
let bean = await R.findOne("monitor", " id = ? AND user_id = ? ", [
|
let monitor = await R.findOne("monitor", " id = ? AND user_id = ? ", [
|
||||||
monitorID,
|
monitorID,
|
||||||
socket.userID,
|
socket.userID,
|
||||||
]);
|
]);
|
||||||
|
const monitorData = [{ id: monitor.id,
|
||||||
|
active: monitor.active
|
||||||
|
}];
|
||||||
|
const preloadData = await Monitor.preparePreloadData(monitorData);
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
monitor: await bean.toJSON(),
|
monitor: monitor.toJSON(preloadData),
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -981,7 +985,7 @@ let needSetup = false;
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
await startMonitor(socket.userID, monitorID);
|
await startMonitor(socket.userID, monitorID);
|
||||||
await server.sendMonitorList(socket);
|
await server.sendUpdateMonitorIntoList(socket, monitorID);
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
|
@ -1001,7 +1005,7 @@ let needSetup = false;
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
await pauseMonitor(socket.userID, monitorID);
|
await pauseMonitor(socket.userID, monitorID);
|
||||||
await server.sendMonitorList(socket);
|
await server.sendUpdateMonitorIntoList(socket, monitorID);
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
|
@ -1047,8 +1051,7 @@ let needSetup = false;
|
||||||
msg: "successDeleted",
|
msg: "successDeleted",
|
||||||
msgi18n: true,
|
msgi18n: true,
|
||||||
});
|
});
|
||||||
|
await server.sendDeleteMonitorFromList(socket, monitorID);
|
||||||
await server.sendMonitorList(socket);
|
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callback({
|
callback({
|
||||||
|
@ -1324,7 +1327,7 @@ let needSetup = false;
|
||||||
socket.on("getSettings", async (callback) => {
|
socket.on("getSettings", async (callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
const data = await Settings.getSettings("general");
|
const data = await getSettings("general");
|
||||||
|
|
||||||
if (!data.serverTimezone) {
|
if (!data.serverTimezone) {
|
||||||
data.serverTimezone = await server.getTimezone();
|
data.serverTimezone = await server.getTimezone();
|
||||||
|
@ -1352,7 +1355,7 @@ let needSetup = false;
|
||||||
// Disabled Auth + Want to Enable Auth => No Check
|
// Disabled Auth + Want to Enable Auth => No Check
|
||||||
// Enabled Auth + Want to Disable Auth => Check!!
|
// Enabled Auth + Want to Disable Auth => Check!!
|
||||||
// Enabled Auth + Want to Enable Auth => No Check
|
// Enabled Auth + Want to Enable Auth => No Check
|
||||||
const currentDisabledAuth = await Settings.get("disableAuth");
|
const currentDisabledAuth = await setting("disableAuth");
|
||||||
if (!currentDisabledAuth && data.disableAuth) {
|
if (!currentDisabledAuth && data.disableAuth) {
|
||||||
await doubleCheckPassword(socket, currentPassword);
|
await doubleCheckPassword(socket, currentPassword);
|
||||||
}
|
}
|
||||||
|
@ -1366,7 +1369,7 @@ let needSetup = false;
|
||||||
const previousChromeExecutable = await Settings.get("chromeExecutable");
|
const previousChromeExecutable = await Settings.get("chromeExecutable");
|
||||||
const previousNSCDStatus = await Settings.get("nscd");
|
const previousNSCDStatus = await Settings.get("nscd");
|
||||||
|
|
||||||
await Settings.setSettings("general", data);
|
await setSettings("general", data);
|
||||||
server.entryPage = data.entryPage;
|
server.entryPage = data.entryPage;
|
||||||
|
|
||||||
// Also need to apply timezone globally
|
// Also need to apply timezone globally
|
||||||
|
@ -1462,7 +1465,7 @@ let needSetup = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("server", e);
|
console.error(e);
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
ok: false,
|
ok: false,
|
||||||
|
@ -1575,7 +1578,7 @@ let needSetup = false;
|
||||||
// ***************************
|
// ***************************
|
||||||
|
|
||||||
log.debug("auth", "check auto login");
|
log.debug("auth", "check auto login");
|
||||||
if (await Settings.get("disableAuth")) {
|
if (await setting("disableAuth")) {
|
||||||
log.info("auth", "Disabled Auth: auto login to admin");
|
log.info("auth", "Disabled Auth: auto login to admin");
|
||||||
await afterLogin(socket, await R.findOne("user"));
|
await afterLogin(socket, await R.findOne("user"));
|
||||||
socket.emit("autoLogin");
|
socket.emit("autoLogin");
|
||||||
|
@ -1678,13 +1681,13 @@ async function afterLogin(socket, user) {
|
||||||
|
|
||||||
await StatusPage.sendStatusPageList(io, socket);
|
await StatusPage.sendStatusPageList(io, socket);
|
||||||
|
|
||||||
|
const monitorPromises = [];
|
||||||
for (let monitorID in monitorList) {
|
for (let monitorID in monitorList) {
|
||||||
await sendHeartbeatList(socket, monitorID);
|
monitorPromises.push(sendHeartbeatList(socket, monitorID));
|
||||||
|
monitorPromises.push(Monitor.sendStats(io, monitorID, user.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let monitorID in monitorList) {
|
await Promise.all(monitorPromises);
|
||||||
await Monitor.sendStats(io, monitorID, user.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set server timezone from client browser if not set
|
// Set server timezone from client browser if not set
|
||||||
// It should be run once only
|
// It should be run once only
|
||||||
|
|
|
@ -60,7 +60,7 @@ module.exports.apiKeySocketHandler = (socket) => {
|
||||||
ok: true,
|
ok: true,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("apikeys", e);
|
console.error(e);
|
||||||
callback({
|
callback({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: e.message,
|
msg: e.message,
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
const { checkLogin, doubleCheckPassword } = require("../util-server");
|
const { checkLogin, setSetting, setting, doubleCheckPassword } = require("../util-server");
|
||||||
const { CloudflaredTunnel } = require("node-cloudflared-tunnel");
|
const { CloudflaredTunnel } = require("node-cloudflared-tunnel");
|
||||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||||
const { log } = require("../../src/util");
|
const { log } = require("../../src/util");
|
||||||
const { Settings } = require("../settings");
|
|
||||||
const io = UptimeKumaServer.getInstance().io;
|
const io = UptimeKumaServer.getInstance().io;
|
||||||
|
|
||||||
const prefix = "cloudflared_";
|
const prefix = "cloudflared_";
|
||||||
|
@ -41,7 +40,7 @@ module.exports.cloudflaredSocketHandler = (socket) => {
|
||||||
socket.join("cloudflared");
|
socket.join("cloudflared");
|
||||||
io.to(socket.userID).emit(prefix + "installed", cloudflared.checkInstalled());
|
io.to(socket.userID).emit(prefix + "installed", cloudflared.checkInstalled());
|
||||||
io.to(socket.userID).emit(prefix + "running", cloudflared.running);
|
io.to(socket.userID).emit(prefix + "running", cloudflared.running);
|
||||||
io.to(socket.userID).emit(prefix + "token", await Settings.get("cloudflaredTunnelToken"));
|
io.to(socket.userID).emit(prefix + "token", await setting("cloudflaredTunnelToken"));
|
||||||
} catch (error) { }
|
} catch (error) { }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -56,7 +55,7 @@ module.exports.cloudflaredSocketHandler = (socket) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
if (token && typeof token === "string") {
|
if (token && typeof token === "string") {
|
||||||
await Settings.set("cloudflaredTunnelToken", token);
|
await setSetting("cloudflaredTunnelToken", token);
|
||||||
cloudflared.token = token;
|
cloudflared.token = token;
|
||||||
} else {
|
} else {
|
||||||
cloudflared.token = null;
|
cloudflared.token = null;
|
||||||
|
@ -68,7 +67,7 @@ module.exports.cloudflaredSocketHandler = (socket) => {
|
||||||
socket.on(prefix + "stop", async (currentPassword, callback) => {
|
socket.on(prefix + "stop", async (currentPassword, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
const disabledAuth = await Settings.get("disableAuth");
|
const disabledAuth = await setting("disableAuth");
|
||||||
if (!disabledAuth) {
|
if (!disabledAuth) {
|
||||||
await doubleCheckPassword(socket, currentPassword);
|
await doubleCheckPassword(socket, currentPassword);
|
||||||
}
|
}
|
||||||
|
@ -84,7 +83,7 @@ module.exports.cloudflaredSocketHandler = (socket) => {
|
||||||
socket.on(prefix + "removeToken", async () => {
|
socket.on(prefix + "removeToken", async () => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
await Settings.set("cloudflaredTunnelToken", "");
|
await setSetting("cloudflaredTunnelToken", "");
|
||||||
} catch (error) { }
|
} catch (error) { }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -97,15 +96,15 @@ module.exports.cloudflaredSocketHandler = (socket) => {
|
||||||
*/
|
*/
|
||||||
module.exports.autoStart = async (token) => {
|
module.exports.autoStart = async (token) => {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
token = await Settings.get("cloudflaredTunnelToken");
|
token = await setting("cloudflaredTunnelToken");
|
||||||
} else {
|
} else {
|
||||||
// Override the current token via args or env var
|
// Override the current token via args or env var
|
||||||
await Settings.set("cloudflaredTunnelToken", token);
|
await setSetting("cloudflaredTunnelToken", token);
|
||||||
log.info("cloudflare", "Use cloudflared token from args or env var");
|
console.log("Use cloudflared token from args or env var");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
log.info("cloudflare", "Start cloudflared");
|
console.log("Start cloudflared");
|
||||||
cloudflared.token = token;
|
cloudflared.token = token;
|
||||||
cloudflared.start();
|
cloudflared.start();
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("maintenance", e);
|
console.error(e);
|
||||||
callback({
|
callback({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: e.message,
|
msg: e.message,
|
||||||
|
@ -177,7 +177,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
|
||||||
ok: true,
|
ok: true,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("maintenance", e);
|
console.error(e);
|
||||||
callback({
|
callback({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: e.message,
|
msg: e.message,
|
||||||
|
@ -201,7 +201,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("maintenance", e);
|
console.error(e);
|
||||||
callback({
|
callback({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: e.message,
|
msg: e.message,
|
||||||
|
@ -225,7 +225,7 @@ module.exports.maintenanceSocketHandler = (socket) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("maintenance", e);
|
console.error(e);
|
||||||
callback({
|
callback({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: e.message,
|
msg: e.message,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { checkLogin } = require("../util-server");
|
const { checkLogin, setSetting } = require("../util-server");
|
||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
const { log } = require("../../src/util");
|
const { log } = require("../../src/util");
|
||||||
const ImageDataURI = require("../image-data-uri");
|
const ImageDataURI = require("../image-data-uri");
|
||||||
|
@ -7,7 +7,6 @@ const Database = require("../database");
|
||||||
const apicache = require("../modules/apicache");
|
const apicache = require("../modules/apicache");
|
||||||
const StatusPage = require("../model/status_page");
|
const StatusPage = require("../model/status_page");
|
||||||
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
const { UptimeKumaServer } = require("../uptime-kuma-server");
|
||||||
const { Settings } = require("../settings");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Socket handlers for status page
|
* Socket handlers for status page
|
||||||
|
@ -234,7 +233,7 @@ module.exports.statusPageSocketHandler = (socket) => {
|
||||||
// Also change entry page to new slug if it is the default one, and slug is changed.
|
// Also change entry page to new slug if it is the default one, and slug is changed.
|
||||||
if (server.entryPage === "statusPage-" + slug && statusPage.slug !== slug) {
|
if (server.entryPage === "statusPage-" + slug && statusPage.slug !== slug) {
|
||||||
server.entryPage = "statusPage-" + statusPage.slug;
|
server.entryPage = "statusPage-" + statusPage.slug;
|
||||||
await Settings.set("entryPage", server.entryPage, "general");
|
await setSetting("entryPage", server.entryPage, "general");
|
||||||
}
|
}
|
||||||
|
|
||||||
apicache.clear();
|
apicache.clear();
|
||||||
|
@ -292,7 +291,7 @@ module.exports.statusPageSocketHandler = (socket) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error("socket", error);
|
console.error(error);
|
||||||
callback({
|
callback({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: error.message,
|
msg: error.message,
|
||||||
|
@ -314,7 +313,7 @@ module.exports.statusPageSocketHandler = (socket) => {
|
||||||
// Reset entry page if it is the default one.
|
// Reset entry page if it is the default one.
|
||||||
if (server.entryPage === "statusPage-" + slug) {
|
if (server.entryPage === "statusPage-" + slug) {
|
||||||
server.entryPage = "dashboard";
|
server.entryPage = "dashboard";
|
||||||
await Settings.set("entryPage", server.entryPage, "general");
|
await setSetting("entryPage", server.entryPage, "general");
|
||||||
}
|
}
|
||||||
|
|
||||||
// No need to delete records from `status_page_cname`, because it has cascade foreign key.
|
// No need to delete records from `status_page_cname`, because it has cascade foreign key.
|
||||||
|
|
|
@ -205,24 +205,56 @@ class UptimeKumaServer {
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update Monitor into list
|
||||||
|
* @param {Socket} socket Socket to send list on
|
||||||
|
* @param {number} monitorID update or deleted monitor id
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async sendUpdateMonitorIntoList(socket, monitorID) {
|
||||||
|
let list = await this.getMonitorJSONList(socket.userID, monitorID);
|
||||||
|
this.io.to(socket.userID).emit("updateMonitorIntoList", list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete Monitor from list
|
||||||
|
* @param {Socket} socket Socket to send list on
|
||||||
|
* @param {number} monitorID update or deleted monitor id
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async sendDeleteMonitorFromList(socket, monitorID) {
|
||||||
|
this.io.to(socket.userID).emit("deleteMonitorFromList", monitorID);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a list of monitors for the given user.
|
* Get a list of monitors for the given user.
|
||||||
* @param {string} userID - The ID of the user to get monitors for.
|
* @param {string} userID - The ID of the user to get monitors for.
|
||||||
|
* @param {number} monitorID - The ID of monitor for.
|
||||||
* @returns {Promise<object>} A promise that resolves to an object with monitor IDs as keys and monitor objects as values.
|
* @returns {Promise<object>} A promise that resolves to an object with monitor IDs as keys and monitor objects as values.
|
||||||
*
|
*
|
||||||
* Generated by Trelent
|
* Generated by Trelent
|
||||||
*/
|
*/
|
||||||
async getMonitorJSONList(userID) {
|
async getMonitorJSONList(userID, monitorID = null) {
|
||||||
let result = {};
|
|
||||||
|
|
||||||
let monitorList = await R.find("monitor", " user_id = ? ORDER BY weight DESC, name", [
|
let query = " user_id = ? ";
|
||||||
userID,
|
let queryParams = [ userID ];
|
||||||
]);
|
|
||||||
|
|
||||||
for (let monitor of monitorList) {
|
if (monitorID) {
|
||||||
result[monitor.id] = await monitor.toJSON();
|
query += "AND id = ? ";
|
||||||
|
queryParams.push(monitorID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let monitorList = await R.find("monitor", query + "ORDER BY weight DESC, name", queryParams);
|
||||||
|
|
||||||
|
const monitorData = monitorList.map(monitor => ({
|
||||||
|
id: monitor.id,
|
||||||
|
active: monitor.active,
|
||||||
|
name: monitor.name,
|
||||||
|
}));
|
||||||
|
const preloadData = await Monitor.preparePreloadData(monitorData);
|
||||||
|
|
||||||
|
const result = {};
|
||||||
|
monitorList.forEach(monitor => result[monitor.id] = monitor.toJSON(preloadData));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -520,3 +552,4 @@ const { DnsMonitorType } = require("./monitor-types/dns");
|
||||||
const { MqttMonitorType } = require("./monitor-types/mqtt");
|
const { MqttMonitorType } = require("./monitor-types/mqtt");
|
||||||
const { SNMPMonitorType } = require("./monitor-types/snmp");
|
const { SNMPMonitorType } = require("./monitor-types/snmp");
|
||||||
const { MongodbMonitorType } = require("./monitor-types/mongodb");
|
const { MongodbMonitorType } = require("./monitor-types/mongodb");
|
||||||
|
const Monitor = require("./model/monitor");
|
||||||
|
|
|
@ -12,6 +12,7 @@ const { Client } = require("pg");
|
||||||
const postgresConParse = require("pg-connection-string").parse;
|
const postgresConParse = require("pg-connection-string").parse;
|
||||||
const mysql = require("mysql2");
|
const mysql = require("mysql2");
|
||||||
const { NtlmClient } = require("./modules/axios-ntlm/lib/ntlmClient.js");
|
const { NtlmClient } = require("./modules/axios-ntlm/lib/ntlmClient.js");
|
||||||
|
const { Settings } = require("./settings");
|
||||||
const grpc = require("@grpc/grpc-js");
|
const grpc = require("@grpc/grpc-js");
|
||||||
const protojs = require("protobufjs");
|
const protojs = require("protobufjs");
|
||||||
const radiusClient = require("node-radius-client");
|
const radiusClient = require("node-radius-client");
|
||||||
|
@ -520,6 +521,46 @@ exports.redisPingAsync = function (dsn, rejectUnauthorized) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve value of setting based on key
|
||||||
|
* @param {string} key Key of setting to retrieve
|
||||||
|
* @returns {Promise<any>} Value
|
||||||
|
* @deprecated Use await Settings.get(key)
|
||||||
|
*/
|
||||||
|
exports.setting = async function (key) {
|
||||||
|
return await Settings.get(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the specified setting to specified value
|
||||||
|
* @param {string} key Key of setting to set
|
||||||
|
* @param {any} value Value to set to
|
||||||
|
* @param {?string} type Type of setting
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
exports.setSetting = async function (key, value, type = null) {
|
||||||
|
await Settings.set(key, value, type);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get settings based on type
|
||||||
|
* @param {string} type The type of setting
|
||||||
|
* @returns {Promise<Bean>} Settings of requested type
|
||||||
|
*/
|
||||||
|
exports.getSettings = async function (type) {
|
||||||
|
return await Settings.getSettings(type);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set settings based on type
|
||||||
|
* @param {string} type Type of settings to set
|
||||||
|
* @param {object} data Values of settings
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
exports.setSettings = async function (type, data) {
|
||||||
|
await Settings.setSettings(type, data);
|
||||||
|
};
|
||||||
|
|
||||||
// ssl-checker by @dyaa
|
// ssl-checker by @dyaa
|
||||||
//https://github.com/dyaa/ssl-checker/blob/master/src/index.ts
|
//https://github.com/dyaa/ssl-checker/blob/master/src/index.ts
|
||||||
|
|
||||||
|
|
|
@ -118,6 +118,7 @@ export default {
|
||||||
"clicksendsms": "ClickSend SMS",
|
"clicksendsms": "ClickSend SMS",
|
||||||
"CallMeBot": "CallMeBot (WhatsApp, Telegram Call, Facebook Messanger)",
|
"CallMeBot": "CallMeBot (WhatsApp, Telegram Call, Facebook Messanger)",
|
||||||
"discord": "Discord",
|
"discord": "Discord",
|
||||||
|
"Elks": "46elks",
|
||||||
"GoogleChat": "Google Chat (Google Workspace)",
|
"GoogleChat": "Google Chat (Google Workspace)",
|
||||||
"gorush": "Gorush",
|
"gorush": "Gorush",
|
||||||
"gotify": "Gotify",
|
"gotify": "Gotify",
|
||||||
|
|
48
src/components/notifications/46elks.vue
Normal file
48
src/components/notifications/46elks.vue
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<template>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="ElksUsername" class="form-label">{{ $t("Username") }}</label>
|
||||||
|
<input id="ElksUsername" v-model="$parent.notification.elksUsername" type="text" class="form-control" required>
|
||||||
|
<label for="ElksPassword" class="form-label">{{ $t("Password") }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-text">
|
||||||
|
<HiddenInput id="ElksPassword" v-model="$parent.notification.elksAuthToken" :required="true" autocomplete="new-password"></HiddenInput>
|
||||||
|
<i18n-t tag="p" keypath="Can be found on:">
|
||||||
|
<a href="https://46elks.com/account" target="_blank">https://46elks.com/account</a>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="Elks-from-number" class="form-label">{{ $t("From") }}</label>
|
||||||
|
<input id="Elks-from-number" v-model="$parent.notification.elksFromNumber" type="text" class="form-control" required>
|
||||||
|
<div class="form-text">
|
||||||
|
{{ $t("Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.") }}
|
||||||
|
<i18n-t tag="p" keypath="More info on:">
|
||||||
|
<a href="https://46elks.se/kb/text-sender-id" target="_blank">https://46elks.se/kb/text-sender-id</a>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="Elks-to-number" class="form-label">{{ $t("To Number") }}</label>
|
||||||
|
<input id="Elks-to-number" v-model="$parent.notification.elksToNumber" type="text" class="form-control" required>
|
||||||
|
<div class="form-text">
|
||||||
|
{{ $t("The phone number of the recipient in E.164 format.") }}
|
||||||
|
<i18n-t tag="p" keypath="More info on:">
|
||||||
|
<a href="https://46elks.se/kb/e164" target="_blank">https://46elks.se/kb/e164</a>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;">
|
||||||
|
<a href="https://46elks.com/docs/send-sms" target="_blank">https://46elks.com/docs/send-sms</a>
|
||||||
|
</i18n-t>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import HiddenInput from "../HiddenInput.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
HiddenInput,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -9,6 +9,12 @@
|
||||||
<label for="slack-channel" class="form-label">{{ $t("Channel Name") }}</label>
|
<label for="slack-channel" class="form-label">{{ $t("Channel Name") }}</label>
|
||||||
<input id="slack-channel-name" v-model="$parent.notification.slackchannel" type="text" class="form-control">
|
<input id="slack-channel-name" v-model="$parent.notification.slackchannel" type="text" class="form-control">
|
||||||
|
|
||||||
|
<label class="form-label">{{ $t("Message format") }}</label>
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input id="slack-text-message" v-model="$parent.notification.slackrichmessage" type="checkbox" class="form-check-input">
|
||||||
|
<label for="slack-text-message" class="form-label">{{ $t("Send rich messages") }}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
<span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}
|
<span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}
|
||||||
<i18n-t tag="p" keypath="aboutWebhooks" style="margin-top: 8px;">
|
<i18n-t tag="p" keypath="aboutWebhooks" style="margin-top: 8px;">
|
||||||
|
|
|
@ -4,6 +4,53 @@
|
||||||
<HiddenInput id="push-api-key" v-model="$parent.notification.pushAPIKey" :required="true" autocomplete="new-password"></HiddenInput>
|
<HiddenInput id="push-api-key" v-model="$parent.notification.pushAPIKey" :required="true" autocomplete="new-password"></HiddenInput>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="push-api-title" class="form-label">{{ $t("Title") }}</label>
|
||||||
|
<input id="push-api-title" v-model="$parent.notification.pushTitle" type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="push-api-channel" class="form-label">{{ $t("Notification Channel") }}</label>
|
||||||
|
<input id="push-api-channel" v-model="$parent.notification.pushChannel" type="text" class="form-control" patttern="[A-Za-z0-9-]+">
|
||||||
|
<div class="form-text">
|
||||||
|
{{ $t("Alphanumerical string and hyphens only") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="push-api-sound" class="form-label">{{ $t("Sound") }}</label>
|
||||||
|
<select id="push-api-sound" v-model="$parent.notification.pushSound" class="form-select">
|
||||||
|
<option value="default">{{ $t("Default") }}</option>
|
||||||
|
<option value="arcade">{{ $t("Arcade") }}</option>
|
||||||
|
<option value="correct">{{ $t("Correct") }}</option>
|
||||||
|
<option value="fail">{{ $t("Fail") }}</option>
|
||||||
|
<option value="harp">{{ $t("Harp") }}</option>
|
||||||
|
<option value="reveal">{{ $t("Reveal") }}</option>
|
||||||
|
<option value="bubble">{{ $t("Bubble") }}</option>
|
||||||
|
<option value="doorbell">{{ $t("Doorbell") }}</option>
|
||||||
|
<option value="flute">{{ $t("Flute") }}</option>
|
||||||
|
<option value="money">{{ $t("Money") }}</option>
|
||||||
|
<option value="scifi">{{ $t("Scifi") }}</option>
|
||||||
|
<option value="clear">{{ $t("Clear") }}</option>
|
||||||
|
<option value="elevator">{{ $t("Elevator") }}</option>
|
||||||
|
<option value="guitar">{{ $t("Guitar") }}</option>
|
||||||
|
<option value="pop">{{ $t("Pop") }}</option>
|
||||||
|
</select>
|
||||||
|
<div class="form-text">
|
||||||
|
{{ $t("Custom sound to override default notification sound") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input v-model="$parent.notification.pushTimeSensitive" class="form-check-input" type="checkbox">
|
||||||
|
<label class="form-check-label">{{ $t("Time Sensitive (iOS Only)") }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-text">
|
||||||
|
{{ $t("Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;">
|
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;">
|
||||||
<a href="https://docs.push.techulus.com" target="_blank">https://docs.push.techulus.com</a>
|
<a href="https://docs.push.techulus.com" target="_blank">https://docs.push.techulus.com</a>
|
||||||
</i18n-t>
|
</i18n-t>
|
||||||
|
@ -16,5 +63,19 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
HiddenInput,
|
HiddenInput,
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
if (typeof this.$parent.notification.pushTitle === "undefined") {
|
||||||
|
this.$parent.notification.pushTitle = "Uptime-Kuma";
|
||||||
|
}
|
||||||
|
if (typeof this.$parent.notification.pushChannel === "undefined") {
|
||||||
|
this.$parent.notification.pushChannel = "uptime-kuma";
|
||||||
|
}
|
||||||
|
if (typeof this.$parent.notification.pushSound === "undefined") {
|
||||||
|
this.$parent.notification.pushSound = "default";
|
||||||
|
}
|
||||||
|
if (typeof this.$parent.notification.pushTimeSensitive === "undefined") {
|
||||||
|
this.$parent.notification.pushTimeSensitive = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -9,6 +9,7 @@ import CallMeBot from "./CallMeBot.vue";
|
||||||
import SMSC from "./SMSC.vue";
|
import SMSC from "./SMSC.vue";
|
||||||
import DingDing from "./DingDing.vue";
|
import DingDing from "./DingDing.vue";
|
||||||
import Discord from "./Discord.vue";
|
import Discord from "./Discord.vue";
|
||||||
|
import Elks from "./46elks.vue";
|
||||||
import Feishu from "./Feishu.vue";
|
import Feishu from "./Feishu.vue";
|
||||||
import FreeMobile from "./FreeMobile.vue";
|
import FreeMobile from "./FreeMobile.vue";
|
||||||
import GoogleChat from "./GoogleChat.vue";
|
import GoogleChat from "./GoogleChat.vue";
|
||||||
|
@ -82,6 +83,7 @@ const NotificationFormList = {
|
||||||
"smsc": SMSC,
|
"smsc": SMSC,
|
||||||
"DingDing": DingDing,
|
"DingDing": DingDing,
|
||||||
"discord": Discord,
|
"discord": Discord,
|
||||||
|
"Elks": Elks,
|
||||||
"Feishu": Feishu,
|
"Feishu": Feishu,
|
||||||
"FreeMobile": FreeMobile,
|
"FreeMobile": FreeMobile,
|
||||||
"GoogleChat": GoogleChat,
|
"GoogleChat": GoogleChat,
|
||||||
|
|
|
@ -1050,6 +1050,5 @@
|
||||||
"less than": "по-малко от",
|
"less than": "по-малко от",
|
||||||
"greater than": "по-голямо от",
|
"greater than": "по-голямо от",
|
||||||
"greater than or equal to": "по-голямо или равно на",
|
"greater than or equal to": "по-голямо или равно на",
|
||||||
"record": "запис",
|
"record": "запис"
|
||||||
"shrinkDatabaseDescriptionSqlite": "Стартира {vacuum} за база данни тип SQLite. Функцията {auto_vacuum} вече е активиран, но това не дефрагментира базата данни, нито препакетира отделните страници на базата данни по начина, по който го прави командата {vacuum}."
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,6 +82,7 @@
|
||||||
"resendEveryXTimes": "Resend every {0} times",
|
"resendEveryXTimes": "Resend every {0} times",
|
||||||
"resendDisabled": "Resend disabled",
|
"resendDisabled": "Resend disabled",
|
||||||
"retriesDescription": "Maximum retries before the service is marked as down and a notification is sent",
|
"retriesDescription": "Maximum retries before the service is marked as down and a notification is sent",
|
||||||
|
"ignoredTLSError": "TLS/SSL errors have been ignored",
|
||||||
"ignoreTLSError": "Ignore TLS/SSL errors for HTTPS websites",
|
"ignoreTLSError": "Ignore TLS/SSL errors for HTTPS websites",
|
||||||
"ignoreTLSErrorGeneral": "Ignore TLS/SSL error for connection",
|
"ignoreTLSErrorGeneral": "Ignore TLS/SSL error for connection",
|
||||||
"upsideDownModeDescription": "Flip the status upside down. If the service is reachable, it is DOWN.",
|
"upsideDownModeDescription": "Flip the status upside down. If the service is reachable, it is DOWN.",
|
||||||
|
@ -96,6 +97,8 @@
|
||||||
"pushOthers": "Others",
|
"pushOthers": "Others",
|
||||||
"programmingLanguages": "Programming Languages",
|
"programmingLanguages": "Programming Languages",
|
||||||
"Save": "Save",
|
"Save": "Save",
|
||||||
|
"Debug": "Debug",
|
||||||
|
"Copy": "Copy",
|
||||||
"Notifications": "Notifications",
|
"Notifications": "Notifications",
|
||||||
"Not available, please setup.": "Not available, please set up.",
|
"Not available, please setup.": "Not available, please set up.",
|
||||||
"Setup Notification": "Set Up Notification",
|
"Setup Notification": "Set Up Notification",
|
||||||
|
@ -248,6 +251,14 @@
|
||||||
"PushUrl": "Push URL",
|
"PushUrl": "Push URL",
|
||||||
"HeadersInvalidFormat": "The request headers are not valid JSON: ",
|
"HeadersInvalidFormat": "The request headers are not valid JSON: ",
|
||||||
"BodyInvalidFormat": "The request body is not valid JSON: ",
|
"BodyInvalidFormat": "The request body is not valid JSON: ",
|
||||||
|
"CopyToClipboardError": "Couldn't copy to clipbard: {error}",
|
||||||
|
"CopyToClipboardSuccess": "Copied!",
|
||||||
|
"CurlDebugInfo": "To debug the monitor, you can either paste this into your own machines terminal or into the machines terminal which uptime kuma is running on and see what you are requesting.{newiline}Please be aware of networking differences like {firewalls}, {dns_resolvers} or {docker_networks}.",
|
||||||
|
"firewalls": "firewalls",
|
||||||
|
"dns resolvers": "dns resolvers",
|
||||||
|
"docker networks": "docker networks",
|
||||||
|
"CurlDebugInfoOAuth2CCUnsupported": "Full oauth client credential flow is not supported in {curl}.{newline}Please get a bearer token and pass it via the {oauth2_bearer} option.",
|
||||||
|
"CurlDebugInfoProxiesUnsupported": "Proxy support in the above {curl} command is currently not implemented.",
|
||||||
"Monitor History": "Monitor History",
|
"Monitor History": "Monitor History",
|
||||||
"clearDataOlderThan": "Keep monitor history data for {0} days.",
|
"clearDataOlderThan": "Keep monitor history data for {0} days.",
|
||||||
"PasswordsDoNotMatch": "Passwords do not match.",
|
"PasswordsDoNotMatch": "Passwords do not match.",
|
||||||
|
@ -884,6 +895,8 @@
|
||||||
"cacheBusterParamDescription": "Randomly generated parameter to skip caches.",
|
"cacheBusterParamDescription": "Randomly generated parameter to skip caches.",
|
||||||
"gamedigGuessPort": "Gamedig: Guess Port",
|
"gamedigGuessPort": "Gamedig: Guess Port",
|
||||||
"gamedigGuessPortDescription": "The port used by Valve Server Query Protocol may be different from the client port. Try this if the monitor cannot connect to your server.",
|
"gamedigGuessPortDescription": "The port used by Valve Server Query Protocol may be different from the client port. Try this if the monitor cannot connect to your server.",
|
||||||
|
"Message format": "Message format",
|
||||||
|
"Send rich messages": "Send rich messages",
|
||||||
"Bitrix24 Webhook URL": "Bitrix24 Webhook URL",
|
"Bitrix24 Webhook URL": "Bitrix24 Webhook URL",
|
||||||
"wayToGetBitrix24Webhook": "You can create a webhook by following the steps at {0}",
|
"wayToGetBitrix24Webhook": "You can create a webhook by following the steps at {0}",
|
||||||
"bitrix24SupportUserID": "Enter your user ID in Bitrix24. You can find out the ID from the link by going to the user's profile.",
|
"bitrix24SupportUserID": "Enter your user ID in Bitrix24. You can find out the ID from the link by going to the user's profile.",
|
||||||
|
@ -1014,5 +1027,29 @@
|
||||||
"greater than": "greater than",
|
"greater than": "greater than",
|
||||||
"less than or equal to": "less than or equal to",
|
"less than or equal to": "less than or equal to",
|
||||||
"greater than or equal to": "greater than or equal to",
|
"greater than or equal to": "greater than or equal to",
|
||||||
"record": "record"
|
"record": "record",
|
||||||
|
"Notification Channel": "Notification Channel",
|
||||||
|
"Sound": "Sound",
|
||||||
|
"Alphanumerical string and hyphens only": "Alphanumerical string and hyphens only",
|
||||||
|
"Arcade": "Arcade",
|
||||||
|
"Correct": "Correct",
|
||||||
|
"Fail":"Fail",
|
||||||
|
"Harp":"Harp",
|
||||||
|
"Reveal":"Reveal",
|
||||||
|
"Bubble":"Bubble",
|
||||||
|
"Doorbell":"Doorbell",
|
||||||
|
"Flute":"Flute",
|
||||||
|
"Money":"Money",
|
||||||
|
"Scifi":"Scifi",
|
||||||
|
"Clear":"Clear",
|
||||||
|
"Elevator":"Elevator",
|
||||||
|
"Guitar":"Guitar",
|
||||||
|
"Pop":"Pop",
|
||||||
|
"Custom sound to override default notification sound": "Custom sound to override default notification sound",
|
||||||
|
"Time Sensitive (iOS Only)": "Time Sensitive (iOS Only)",
|
||||||
|
"Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.": "Time sensitive notifications will be delivered immediately, even if the device is in do not disturb mode.",
|
||||||
|
"From":"From",
|
||||||
|
"Can be found on:": "Can be found on: {0}",
|
||||||
|
"The phone number of the recipient in E.164 format.": "The phone number of the recipient in E.164 format.",
|
||||||
|
"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies.":"Either a text sender ID or a phone number in E.164 format if you want to be able to receive replies."
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,19 +141,23 @@ export default {
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("monitorList", (data) => {
|
socket.on("monitorList", (data) => {
|
||||||
// Add Helper function
|
this.assignMonitorUrlParser(data);
|
||||||
Object.entries(data).forEach(([ monitorID, monitor ]) => {
|
|
||||||
monitor.getUrl = () => {
|
|
||||||
try {
|
|
||||||
return new URL(monitor.url);
|
|
||||||
} catch (_) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
this.monitorList = data;
|
this.monitorList = data;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on("updateMonitorIntoList", (data) => {
|
||||||
|
this.assignMonitorUrlParser(data);
|
||||||
|
Object.entries(data).forEach(([ monitorID, updatedMonitor ]) => {
|
||||||
|
this.monitorList[monitorID] = updatedMonitor;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("deleteMonitorFromList", (monitorID) => {
|
||||||
|
if (this.monitorList[monitorID]) {
|
||||||
|
delete this.monitorList[monitorID];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
socket.on("monitorTypeList", (data) => {
|
socket.on("monitorTypeList", (data) => {
|
||||||
this.monitorTypeList = data;
|
this.monitorTypeList = data;
|
||||||
});
|
});
|
||||||
|
@ -289,6 +293,23 @@ export default {
|
||||||
location.reload();
|
location.reload();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* parse all urls from list.
|
||||||
|
* @param {object} data Monitor data to modify
|
||||||
|
* @returns {object} list
|
||||||
|
*/
|
||||||
|
assignMonitorUrlParser(data) {
|
||||||
|
Object.entries(data).forEach(([ monitorID, monitor ]) => {
|
||||||
|
monitor.getUrl = () => {
|
||||||
|
try {
|
||||||
|
return new URL(monitor.url);
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The storage currently in use
|
* The storage currently in use
|
||||||
|
|
|
@ -565,8 +565,8 @@
|
||||||
|
|
||||||
<h2 v-if="monitor.type !== 'push'" class="mt-5 mb-2">{{ $t("Advanced") }}</h2>
|
<h2 v-if="monitor.type !== 'push'" class="mt-5 mb-2">{{ $t("Advanced") }}</h2>
|
||||||
|
|
||||||
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' " class="my-3 form-check">
|
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' " class="my-3 form-check" :title="monitor.ignoreTls ? $t('ignoredTLSError') : ''">
|
||||||
<input id="expiry-notification" v-model="monitor.expiryNotification" class="form-check-input" type="checkbox">
|
<input id="expiry-notification" v-model="monitor.expiryNotification" class="form-check-input" type="checkbox" :disabled="monitor.ignoreTls">
|
||||||
<label class="form-check-label" for="expiry-notification">
|
<label class="form-check-label" for="expiry-notification">
|
||||||
{{ $t("Certificate Expiry Notification") }}
|
{{ $t("Certificate Expiry Notification") }}
|
||||||
</label>
|
</label>
|
||||||
|
@ -982,13 +982,23 @@
|
||||||
<div class="fixed-bottom-bar p-3">
|
<div class="fixed-bottom-bar p-3">
|
||||||
<button
|
<button
|
||||||
id="monitor-submit-btn"
|
id="monitor-submit-btn"
|
||||||
class="btn btn-primary"
|
class="btn btn-primary me-2"
|
||||||
type="submit"
|
type="submit"
|
||||||
:disabled="processing"
|
:disabled="processing"
|
||||||
data-testid="save-button"
|
data-testid="save-button"
|
||||||
>
|
>
|
||||||
{{ $t("Save") }}
|
{{ $t("Save") }}
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="monitor.type === 'http'"
|
||||||
|
id="monitor-debug-btn"
|
||||||
|
class="btn btn-outline-primary"
|
||||||
|
type="button"
|
||||||
|
:disabled="processing"
|
||||||
|
@click.stop="modal.show()"
|
||||||
|
>
|
||||||
|
{{ $t("Debug") }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1000,9 +1010,58 @@
|
||||||
<RemoteBrowserDialog ref="remoteBrowserDialog" />
|
<RemoteBrowserDialog ref="remoteBrowserDialog" />
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
<div ref="modal" class="modal fade" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-body">
|
||||||
|
<textarea id="curl-debug" v-model="curlCommand" class="form-control mb-3" readonly wrap="off"></textarea>
|
||||||
|
<button id="debug-copy-btn" class="btn btn-outline-primary position-absolute top-0 end-0 mt-3 me-3 border-0" type="button" @click.stop="copyToClipboard">
|
||||||
|
<font-awesome-icon icon="copy" />
|
||||||
|
</button>
|
||||||
|
<i18n-t keypath="CurlDebugInfo" tag="p" class="form-text">
|
||||||
|
<template #newiline>
|
||||||
|
<br>
|
||||||
|
</template>
|
||||||
|
<template #firewalls>
|
||||||
|
<a href="https://xkcd.com/2259/" target="_blank">{{ $t('firewalls') }}</a>
|
||||||
|
</template>
|
||||||
|
<template #dns_resolvers>
|
||||||
|
<a href="https://www.reddit.com/r/sysadmin/comments/rxho93/thank_you_for_the_running_its_always_dns_joke_its/" target="_blank">{{ $t('dns resolvers') }}</a>
|
||||||
|
</template>
|
||||||
|
<template #docker_networks>
|
||||||
|
<a href="https://youtu.be/bKFMS5C4CG0" target="_blank">{{ $t('docker networks') }}</a>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
<div v-if="monitor.authMethod === 'oauth2-cc'" class="alert alert-warning d-flex align-items-center gap-2" role="alert">
|
||||||
|
<div role="img" aria-label="Warning:">⚠️</div>
|
||||||
|
<i18n-t keypath="CurlDebugInfoOAuth2CCUnsupported" tag="div">
|
||||||
|
<template #curl>
|
||||||
|
<code>curl</code>
|
||||||
|
</template>
|
||||||
|
<template #newline>
|
||||||
|
<br>
|
||||||
|
</template>
|
||||||
|
<template #oauth2_bearer>
|
||||||
|
<code>--oauth2-bearer TOKEN</code>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
<div v-if="monitor.proxyId" class="alert alert-warning d-flex align-items-center gap-2" role="alert">
|
||||||
|
<div role="img" aria-label="Warning:">⚠️</div>
|
||||||
|
<i18n-t keypath="CurlDebugInfoProxiesUnsupported" tag="div">
|
||||||
|
<template #curl>
|
||||||
|
<code>curl</code>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { Modal } from "bootstrap";
|
||||||
import VueMultiselect from "vue-multiselect";
|
import VueMultiselect from "vue-multiselect";
|
||||||
import { useToast } from "vue-toastification";
|
import { useToast } from "vue-toastification";
|
||||||
import ActionSelect from "../components/ActionSelect.vue";
|
import ActionSelect from "../components/ActionSelect.vue";
|
||||||
|
@ -1017,8 +1076,10 @@ import { genSecret, isDev, MAX_INTERVAL_SECOND, MIN_INTERVAL_SECOND, sleep } fro
|
||||||
import { hostNameRegexPattern } from "../util-frontend";
|
import { hostNameRegexPattern } from "../util-frontend";
|
||||||
import HiddenInput from "../components/HiddenInput.vue";
|
import HiddenInput from "../components/HiddenInput.vue";
|
||||||
import EditMonitorConditions from "../components/EditMonitorConditions.vue";
|
import EditMonitorConditions from "../components/EditMonitorConditions.vue";
|
||||||
|
import { version } from "../../package.json";
|
||||||
|
const userAgent = `'Uptime-Kuma/${version}'`;
|
||||||
|
|
||||||
const toast = useToast;
|
const toast = useToast();
|
||||||
|
|
||||||
const pushTokenLength = 32;
|
const pushTokenLength = 32;
|
||||||
|
|
||||||
|
@ -1081,6 +1142,7 @@ export default {
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
modal: null,
|
||||||
minInterval: MIN_INTERVAL_SECOND,
|
minInterval: MIN_INTERVAL_SECOND,
|
||||||
maxInterval: MAX_INTERVAL_SECOND,
|
maxInterval: MAX_INTERVAL_SECOND,
|
||||||
processing: false,
|
processing: false,
|
||||||
|
@ -1108,6 +1170,53 @@ export default {
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
|
||||||
|
curlCommand() {
|
||||||
|
const command = [ "curl", "--verbose", "--head", "--request", this.monitor.method, "\\\n", "--user-agent", userAgent, "\\\n" ];
|
||||||
|
if (this.monitor.ignoreTls) {
|
||||||
|
command.push("--insecure", "\\\n");
|
||||||
|
}
|
||||||
|
if (this.monitor.headers) {
|
||||||
|
try {
|
||||||
|
// trying to parse the supplied data as json to trim whitespace
|
||||||
|
for (const [ key, value ] of Object.entries(JSON.parse(this.monitor.headers))) {
|
||||||
|
command.push("--header", `'${key}: ${value}'`, "\\\n");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
command.push("--header", `'${this.monitor.headers}'`, "\\\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.monitor.authMethod === "basic") {
|
||||||
|
command.push("--user", `${this.monitor.basic_auth_user}:${this.monitor.basic_auth_pass}`, "--basic", "\\\n");
|
||||||
|
} else if (this.monitor.authmethod === "mtls") {
|
||||||
|
command.push("--cacert", `'${this.monitor.tlsCa}'`, "\\\n", "--key", `'${this.monitor.tlsKey}'`, "\\\n", "--cert", `'${this.monitor.tlsCert}'`, "\\\n");
|
||||||
|
} else if (this.monitor.authMethod === "ntlm") {
|
||||||
|
command.push("--user", `'${this.monitor.authDomain ? `${this.monitor.authDomain}/` : ""}${this.monitor.basic_auth_user}:${this.monitor.basic_auth_pass}'`, "--ntlm", "\\\n");
|
||||||
|
}
|
||||||
|
if (this.monitor.body && this.monitor.httpBodyEncoding === "json") {
|
||||||
|
let json = "";
|
||||||
|
try {
|
||||||
|
// trying to parse the supplied data as json to trim whitespace
|
||||||
|
json = JSON.stringify(JSON.parse(this.monitor.body));
|
||||||
|
} catch (e) {
|
||||||
|
json = this.monitor.body;
|
||||||
|
}
|
||||||
|
command.push("--header", "'Content-Type: application/json'", "\\\n", "--data", `'${json}'`, "\\\n");
|
||||||
|
} else if (this.monitor.body && this.monitor.httpBodyEncoding === "xml") {
|
||||||
|
command.push("--headers", "'Content-Type: application/xml'", "\\\n", "--data", `'${this.monitor.body}'`, "\\\n");
|
||||||
|
}
|
||||||
|
if (this.monitor.maxredirects) {
|
||||||
|
command.push("--location", "--max-redirs", this.monitor.maxredirects, "\\\n");
|
||||||
|
}
|
||||||
|
if (this.monitor.timeout) {
|
||||||
|
command.push("--max-time", this.monitor.timeout, "\\\n");
|
||||||
|
}
|
||||||
|
if (this.monitor.maxretries) {
|
||||||
|
command.push("--retry", this.monitor.maxretries, "\\\n");
|
||||||
|
}
|
||||||
|
command.push("--url", this.monitor.url);
|
||||||
|
return command.join(" ");
|
||||||
|
},
|
||||||
|
|
||||||
ipRegex() {
|
ipRegex() {
|
||||||
|
|
||||||
// Allow to test with simple dns server with port (127.0.0.1:5300)
|
// Allow to test with simple dns server with port (127.0.0.1:5300)
|
||||||
|
@ -1456,8 +1565,15 @@ message HealthCheckResponse {
|
||||||
}
|
}
|
||||||
this.monitor.game = newGameObject.keys[0];
|
this.monitor.game = newGameObject.keys[0];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"monitor.ignoreTls"(newVal) {
|
||||||
|
if (newVal) {
|
||||||
|
this.monitor.expiryNotification = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.modal = new Modal(this.$refs.modal);
|
||||||
this.init();
|
this.init();
|
||||||
|
|
||||||
let acceptedStatusCodeOptions = [
|
let acceptedStatusCodeOptions = [
|
||||||
|
@ -1498,6 +1614,14 @@ message HealthCheckResponse {
|
||||||
this.kafkaSaslMechanismOptions = kafkaSaslMechanismOptions;
|
this.kafkaSaslMechanismOptions = kafkaSaslMechanismOptions;
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async copyToClipboard() {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(this.curlCommand);
|
||||||
|
toast.success(this.$t("CopyToClipboardSuccess"));
|
||||||
|
} catch (err) {
|
||||||
|
toast.error(this.$t("CopyToClipboardError", { error: err.message }));
|
||||||
|
}
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Initialize the edit monitor form
|
* Initialize the edit monitor form
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
|
@ -1689,7 +1813,6 @@ message HealthCheckResponse {
|
||||||
await this.startParentGroupMonitor();
|
await this.startParentGroupMonitor();
|
||||||
}
|
}
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
this.$root.getMonitorList();
|
|
||||||
this.$router.push("/dashboard/" + res.monitorID);
|
this.$router.push("/dashboard/" + res.monitorID);
|
||||||
} else {
|
} else {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
|
@ -1786,4 +1909,9 @@ message HealthCheckResponse {
|
||||||
textarea {
|
textarea {
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#curl-debug {
|
||||||
|
font-family: monospace;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -68,15 +68,17 @@ const routes = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
],
|
||||||
path: "/clone/:id",
|
|
||||||
component: EditMonitor,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/add",
|
path: "/add",
|
||||||
component: EditMonitor,
|
component: EditMonitor,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "/clone/:id",
|
||||||
|
component: EditMonitor,
|
||||||
},
|
},
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/list",
|
path: "/list",
|
||||||
|
|
71
src/util.js
71
src/util.js
|
@ -8,34 +8,17 @@
|
||||||
// Backend uses the compiled file util.js
|
// Backend uses the compiled file util.js
|
||||||
// Frontend uses util.ts
|
// Frontend uses util.ts
|
||||||
*/
|
*/
|
||||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
||||||
if (k2 === undefined) k2 = k;
|
|
||||||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
||||||
}) : (function(o, m, k, k2) {
|
|
||||||
if (k2 === undefined) k2 = k;
|
|
||||||
o[k2] = m[k];
|
|
||||||
}));
|
|
||||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
||||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
||||||
}) : function(o, v) {
|
|
||||||
o["default"] = v;
|
|
||||||
});
|
|
||||||
var __importStar = (this && this.__importStar) || function (mod) {
|
|
||||||
if (mod && mod.__esModule) return mod;
|
|
||||||
var result = {};
|
|
||||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
||||||
__setModuleDefault(result, mod);
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
};
|
};
|
||||||
var _a;
|
var _a;
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.sleep = exports.flipStatus = exports.badgeConstants = exports.CONSOLE_STYLE_BgGray = exports.CONSOLE_STYLE_BgWhite = exports.CONSOLE_STYLE_BgCyan = exports.CONSOLE_STYLE_BgMagenta = exports.CONSOLE_STYLE_BgBlue = exports.CONSOLE_STYLE_BgYellow = exports.CONSOLE_STYLE_BgGreen = exports.CONSOLE_STYLE_BgRed = exports.CONSOLE_STYLE_BgBlack = exports.CONSOLE_STYLE_FgPink = exports.CONSOLE_STYLE_FgBrown = exports.CONSOLE_STYLE_FgViolet = exports.CONSOLE_STYLE_FgLightBlue = exports.CONSOLE_STYLE_FgLightGreen = exports.CONSOLE_STYLE_FgOrange = exports.CONSOLE_STYLE_FgGray = exports.CONSOLE_STYLE_FgWhite = exports.CONSOLE_STYLE_FgCyan = exports.CONSOLE_STYLE_FgMagenta = exports.CONSOLE_STYLE_FgBlue = exports.CONSOLE_STYLE_FgYellow = exports.CONSOLE_STYLE_FgGreen = exports.CONSOLE_STYLE_FgRed = exports.CONSOLE_STYLE_FgBlack = exports.CONSOLE_STYLE_Hidden = exports.CONSOLE_STYLE_Reverse = exports.CONSOLE_STYLE_Blink = exports.CONSOLE_STYLE_Underscore = exports.CONSOLE_STYLE_Dim = exports.CONSOLE_STYLE_Bright = exports.CONSOLE_STYLE_Reset = exports.MIN_INTERVAL_SECOND = exports.MAX_INTERVAL_SECOND = exports.SQL_DATETIME_FORMAT_WITHOUT_SECOND = exports.SQL_DATETIME_FORMAT = exports.SQL_DATE_FORMAT = exports.STATUS_PAGE_MAINTENANCE = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.MAINTENANCE = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isNode = exports.isDev = void 0;
|
exports.sleep = exports.flipStatus = exports.badgeConstants = exports.CONSOLE_STYLE_BgGray = exports.CONSOLE_STYLE_BgWhite = exports.CONSOLE_STYLE_BgCyan = exports.CONSOLE_STYLE_BgMagenta = exports.CONSOLE_STYLE_BgBlue = exports.CONSOLE_STYLE_BgYellow = exports.CONSOLE_STYLE_BgGreen = exports.CONSOLE_STYLE_BgRed = exports.CONSOLE_STYLE_BgBlack = exports.CONSOLE_STYLE_FgPink = exports.CONSOLE_STYLE_FgBrown = exports.CONSOLE_STYLE_FgViolet = exports.CONSOLE_STYLE_FgLightBlue = exports.CONSOLE_STYLE_FgLightGreen = exports.CONSOLE_STYLE_FgOrange = exports.CONSOLE_STYLE_FgGray = exports.CONSOLE_STYLE_FgWhite = exports.CONSOLE_STYLE_FgCyan = exports.CONSOLE_STYLE_FgMagenta = exports.CONSOLE_STYLE_FgBlue = exports.CONSOLE_STYLE_FgYellow = exports.CONSOLE_STYLE_FgGreen = exports.CONSOLE_STYLE_FgRed = exports.CONSOLE_STYLE_FgBlack = exports.CONSOLE_STYLE_Hidden = exports.CONSOLE_STYLE_Reverse = exports.CONSOLE_STYLE_Blink = exports.CONSOLE_STYLE_Underscore = exports.CONSOLE_STYLE_Dim = exports.CONSOLE_STYLE_Bright = exports.CONSOLE_STYLE_Reset = exports.MIN_INTERVAL_SECOND = exports.MAX_INTERVAL_SECOND = exports.SQL_DATETIME_FORMAT_WITHOUT_SECOND = exports.SQL_DATETIME_FORMAT = exports.SQL_DATE_FORMAT = exports.STATUS_PAGE_MAINTENANCE = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.MAINTENANCE = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isNode = exports.isDev = void 0;
|
||||||
exports.evaluateJsonQuery = exports.intHash = exports.localToUTC = exports.utcToLocal = exports.utcToISODateTime = exports.isoToUTCDateTime = exports.parseTimeFromTimeObject = exports.parseTimeObject = exports.getMaintenanceRelativeURL = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.ucfirst = void 0;
|
exports.evaluateJsonQuery = exports.intHash = exports.localToUTC = exports.utcToLocal = exports.utcToISODateTime = exports.isoToUTCDateTime = exports.parseTimeFromTimeObject = exports.parseTimeObject = exports.getMaintenanceRelativeURL = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.debug = exports.ucfirst = void 0;
|
||||||
|
exports.intHash = exports.localToUTC = exports.utcToLocal = exports.utcToISODateTime = exports.isoToUTCDateTime = exports.parseTimeFromTimeObject = exports.parseTimeObject = exports.getMaintenanceRelativeURL = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.debug = exports.ucfirst = void 0;
|
||||||
const dayjs_1 = __importDefault(require("dayjs"));
|
const dayjs_1 = __importDefault(require("dayjs"));
|
||||||
const jsonata = __importStar(require("jsonata"));
|
const dayjs = require("dayjs");
|
||||||
|
const jsonata = require("jsonata");
|
||||||
exports.isDev = process.env.NODE_ENV === "development";
|
exports.isDev = process.env.NODE_ENV === "development";
|
||||||
exports.isNode = typeof process !== "undefined" && ((_a = process === null || process === void 0 ? void 0 : process.versions) === null || _a === void 0 ? void 0 : _a.node);
|
exports.isNode = typeof process !== "undefined" && ((_a = process === null || process === void 0 ? void 0 : process.versions) === null || _a === void 0 ? void 0 : _a.node);
|
||||||
exports.appName = "Uptime Kuma";
|
exports.appName = "Uptime Kuma";
|
||||||
|
@ -83,6 +66,7 @@ exports.CONSOLE_STYLE_BgMagenta = "\x1b[45m";
|
||||||
exports.CONSOLE_STYLE_BgCyan = "\x1b[46m";
|
exports.CONSOLE_STYLE_BgCyan = "\x1b[46m";
|
||||||
exports.CONSOLE_STYLE_BgWhite = "\x1b[47m";
|
exports.CONSOLE_STYLE_BgWhite = "\x1b[47m";
|
||||||
exports.CONSOLE_STYLE_BgGray = "\x1b[100m";
|
exports.CONSOLE_STYLE_BgGray = "\x1b[100m";
|
||||||
|
|
||||||
const consoleModuleColors = [
|
const consoleModuleColors = [
|
||||||
exports.CONSOLE_STYLE_FgCyan,
|
exports.CONSOLE_STYLE_FgCyan,
|
||||||
exports.CONSOLE_STYLE_FgGreen,
|
exports.CONSOLE_STYLE_FgGreen,
|
||||||
|
@ -141,6 +125,10 @@ function ucfirst(str) {
|
||||||
return firstLetter.toUpperCase() + str.substr(1);
|
return firstLetter.toUpperCase() + str.substr(1);
|
||||||
}
|
}
|
||||||
exports.ucfirst = ucfirst;
|
exports.ucfirst = ucfirst;
|
||||||
|
function debug(msg) {
|
||||||
|
exports.log.log("", msg, "debug");
|
||||||
|
}
|
||||||
|
exports.debug = debug;
|
||||||
class Logger {
|
class Logger {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.hideLog = {
|
this.hideLog = {
|
||||||
|
@ -168,6 +156,8 @@ class Logger {
|
||||||
if (this.hideLog[level] && this.hideLog[level].includes(module.toLowerCase())) {
|
if (this.hideLog[level] && this.hideLog[level].includes(module.toLowerCase())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
module = module.toUpperCase();
|
||||||
|
level = level.toUpperCase();
|
||||||
let now;
|
let now;
|
||||||
if (dayjs_1.default.tz) {
|
if (dayjs_1.default.tz) {
|
||||||
now = dayjs_1.default.tz(new Date()).format();
|
now = dayjs_1.default.tz(new Date()).format();
|
||||||
|
@ -177,20 +167,10 @@ class Logger {
|
||||||
}
|
}
|
||||||
const levelColor = consoleLevelColors[level];
|
const levelColor = consoleLevelColors[level];
|
||||||
const moduleColor = consoleModuleColors[intHash(module, consoleModuleColors.length)];
|
const moduleColor = consoleModuleColors[intHash(module, consoleModuleColors.length)];
|
||||||
let timePart = now;
|
let timePart;
|
||||||
let modulePart = module;
|
let modulePart;
|
||||||
let levelPart = level;
|
let levelPart;
|
||||||
let msgPart = msg;
|
let msgPart;
|
||||||
if (process.env.UPTIME_KUMA_LOG_FORMAT === "json") {
|
|
||||||
console.log(JSON.stringify({
|
|
||||||
time: timePart,
|
|
||||||
module: modulePart,
|
|
||||||
level: levelPart,
|
|
||||||
msg: typeof msg === "string" ? msg : JSON.stringify(msg),
|
|
||||||
}));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
module = module.toUpperCase();
|
|
||||||
if (exports.isNode) {
|
if (exports.isNode) {
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case "DEBUG":
|
case "DEBUG":
|
||||||
|
@ -207,17 +187,28 @@ class Logger {
|
||||||
if (typeof msg === "string") {
|
if (typeof msg === "string") {
|
||||||
msgPart = exports.CONSOLE_STYLE_FgRed + msg + exports.CONSOLE_STYLE_Reset;
|
msgPart = exports.CONSOLE_STYLE_FgRed + msg + exports.CONSOLE_STYLE_Reset;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
msgPart = msg;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case "DEBUG":
|
case "DEBUG":
|
||||||
if (typeof msg === "string") {
|
if (typeof msg === "string") {
|
||||||
msgPart = exports.CONSOLE_STYLE_FgGray + msg + exports.CONSOLE_STYLE_Reset;
|
msgPart = exports.CONSOLE_STYLE_FgGray + msg + exports.CONSOLE_STYLE_Reset;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
msgPart = msg;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
msgPart = msg;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
timePart = now;
|
||||||
modulePart = `[${module}]`;
|
modulePart = `[${module}]`;
|
||||||
levelPart = `${level}:`;
|
levelPart = `${level}:`;
|
||||||
|
msgPart = msg;
|
||||||
}
|
}
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case "ERROR":
|
case "ERROR":
|
||||||
|
@ -240,23 +231,23 @@ class Logger {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
info(module, msg) {
|
info(module, msg) {
|
||||||
this.log(module, msg, "INFO");
|
this.log(module, msg, "info");
|
||||||
}
|
}
|
||||||
warn(module, msg) {
|
warn(module, msg) {
|
||||||
this.log(module, msg, "WARN");
|
this.log(module, msg, "warn");
|
||||||
}
|
}
|
||||||
error(module, msg) {
|
error(module, msg) {
|
||||||
this.log(module, msg, "ERROR");
|
this.log(module, msg, "error");
|
||||||
}
|
}
|
||||||
debug(module, msg) {
|
debug(module, msg) {
|
||||||
this.log(module, msg, "DEBUG");
|
this.log(module, msg, "debug");
|
||||||
}
|
}
|
||||||
exception(module, exception, msg) {
|
exception(module, exception, msg) {
|
||||||
let finalMessage = exception;
|
let finalMessage = exception;
|
||||||
if (msg) {
|
if (msg) {
|
||||||
finalMessage = `${msg}: ${exception}`;
|
finalMessage = `${msg}: ${exception}`;
|
||||||
}
|
}
|
||||||
this.log(module, finalMessage, "ERROR");
|
this.log(module, finalMessage, "error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.log = new Logger();
|
exports.log = new Logger();
|
||||||
|
|
73
src/util.ts
73
src/util.ts
|
@ -120,7 +120,11 @@ export const badgeConstants = {
|
||||||
defaultCertExpireDownDays: "7"
|
defaultCertExpireDownDays: "7"
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Flip the status of s */
|
/**
|
||||||
|
* Flip the status of s between UP and DOWN if this is possible
|
||||||
|
* @param s {number} status
|
||||||
|
* @returns {number} flipped status
|
||||||
|
*/
|
||||||
export function flipStatus(s: number) {
|
export function flipStatus(s: number) {
|
||||||
if (s === UP) {
|
if (s === UP) {
|
||||||
return DOWN;
|
return DOWN;
|
||||||
|
@ -156,6 +160,15 @@ export function ucfirst(str: string) {
|
||||||
return firstLetter.toUpperCase() + str.substr(1);
|
return firstLetter.toUpperCase() + str.substr(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use log.debug (https://github.com/louislam/uptime-kuma/pull/910)
|
||||||
|
* @param msg Message to write
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
export function debug(msg: unknown) {
|
||||||
|
log.log("", msg, "debug");
|
||||||
|
}
|
||||||
|
|
||||||
class Logger {
|
class Logger {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -197,13 +210,12 @@ class Logger {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write a message to the log
|
* Write a message to the log
|
||||||
* @private
|
|
||||||
* @param module The module the log comes from
|
* @param module The module the log comes from
|
||||||
* @param msg Message to write
|
* @param msg Message to write
|
||||||
* @param level {"INFO"|"WARN"|"ERROR"|"DEBUG"} Log level
|
* @param level Log level. One of INFO, WARN, ERROR, DEBUG or can be customized.
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
log(module: string, msg: unknown, level: "INFO"|"WARN"|"ERROR"|"DEBUG"): void {
|
log(module: string, msg: any, level: string) {
|
||||||
if (level === "DEBUG" && !isDev) {
|
if (level === "DEBUG" && !isDev) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -212,6 +224,9 @@ class Logger {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module = module.toUpperCase();
|
||||||
|
level = level.toUpperCase();
|
||||||
|
|
||||||
let now;
|
let now;
|
||||||
if (dayjs.tz) {
|
if (dayjs.tz) {
|
||||||
now = dayjs.tz(new Date()).format();
|
now = dayjs.tz(new Date()).format();
|
||||||
|
@ -222,23 +237,10 @@ class Logger {
|
||||||
const levelColor = consoleLevelColors[level];
|
const levelColor = consoleLevelColors[level];
|
||||||
const moduleColor = consoleModuleColors[intHash(module, consoleModuleColors.length)];
|
const moduleColor = consoleModuleColors[intHash(module, consoleModuleColors.length)];
|
||||||
|
|
||||||
let timePart: string = now;
|
let timePart: string;
|
||||||
let modulePart: string = module;
|
let modulePart: string;
|
||||||
let levelPart: string = level;
|
let levelPart: string;
|
||||||
let msgPart: unknown = msg;
|
let msgPart: string;
|
||||||
|
|
||||||
if (process.env.UPTIME_KUMA_LOG_FORMAT === "json") {
|
|
||||||
console.log(JSON.stringify({
|
|
||||||
time: timePart,
|
|
||||||
module: modulePart,
|
|
||||||
level: levelPart,
|
|
||||||
msg: typeof msg === "string" ? msg : JSON.stringify(msg),
|
|
||||||
}));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Console rendering:
|
|
||||||
module = module.toUpperCase();
|
|
||||||
|
|
||||||
if (isNode) {
|
if (isNode) {
|
||||||
// Add console colors
|
// Add console colors
|
||||||
|
@ -259,18 +261,27 @@ class Logger {
|
||||||
case "ERROR":
|
case "ERROR":
|
||||||
if (typeof msg === "string") {
|
if (typeof msg === "string") {
|
||||||
msgPart = CONSOLE_STYLE_FgRed + msg + CONSOLE_STYLE_Reset;
|
msgPart = CONSOLE_STYLE_FgRed + msg + CONSOLE_STYLE_Reset;
|
||||||
|
} else {
|
||||||
|
msgPart = msg;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "DEBUG":
|
case "DEBUG":
|
||||||
if (typeof msg === "string") {
|
if (typeof msg === "string") {
|
||||||
msgPart = CONSOLE_STYLE_FgGray + msg + CONSOLE_STYLE_Reset;
|
msgPart = CONSOLE_STYLE_FgGray + msg + CONSOLE_STYLE_Reset;
|
||||||
|
} else {
|
||||||
|
msgPart = msg;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
msgPart = msg;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No console colors
|
// No console colors
|
||||||
|
timePart = now;
|
||||||
modulePart = `[${module}]`;
|
modulePart = `[${module}]`;
|
||||||
levelPart = `${level}:`;
|
levelPart = `${level}:`;
|
||||||
|
msgPart = msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write to console
|
// Write to console
|
||||||
|
@ -301,8 +312,8 @@ class Logger {
|
||||||
* @param msg Message to write
|
* @param msg Message to write
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
info(module: string, msg: string): void {
|
info(module: string, msg: unknown) {
|
||||||
this.log(module, msg, "INFO");
|
this.log(module, msg, "info");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -311,8 +322,8 @@ class Logger {
|
||||||
* @param msg Message to write
|
* @param msg Message to write
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
warn(module: string, msg: string): void {
|
warn(module: string, msg: unknown) {
|
||||||
this.log(module, msg, "WARN");
|
this.log(module, msg, "warn");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -321,8 +332,8 @@ class Logger {
|
||||||
* @param msg Message to write
|
* @param msg Message to write
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
error(module: string, msg: string): void {
|
error(module: string, msg: unknown) {
|
||||||
this.log(module, msg, "ERROR");
|
this.log(module, msg, "error");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -331,8 +342,8 @@ class Logger {
|
||||||
* @param msg Message to write
|
* @param msg Message to write
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
debug(module: string, msg: string): void {
|
debug(module: string, msg: unknown) {
|
||||||
this.log(module, msg, "DEBUG");
|
this.log(module, msg, "debug");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -349,7 +360,7 @@ class Logger {
|
||||||
finalMessage = `${msg}: ${exception}`;
|
finalMessage = `${msg}: ${exception}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.log(module, finalMessage, "ERROR");
|
this.log(module, finalMessage, "error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -393,7 +404,7 @@ export class TimeLogger {
|
||||||
* @param name Name of monitor
|
* @param name Name of monitor
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
print(name: string): void {
|
print(name: string) {
|
||||||
if (isDev && process.env.TIMELOGGER === "1") {
|
if (isDev && process.env.TIMELOGGER === "1") {
|
||||||
console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms");
|
console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const rmSync = require("../extra/fs-rmSync.js");
|
|
||||||
|
|
||||||
const path = "./data/test";
|
const path = "./data/test";
|
||||||
|
|
||||||
if (fs.existsSync(path)) {
|
if (fs.existsSync(path)) {
|
||||||
rmSync(path, {
|
fs.rmSync(path, {
|
||||||
recursive: true,
|
recursive: true,
|
||||||
|
force: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue