Merge branch 'master' into fix-1448-discord-service-url
This commit is contained in:
		
						commit
						288ed1e3ca
					
				
					 94 changed files with 1230 additions and 406 deletions
				
			
		|  | @ -1,4 +1,9 @@ | |||
| module.exports = { | ||||
|     ignorePatterns: [ | ||||
|         "test/*", | ||||
|         "server/modules/apicache/*", | ||||
|         "src/util.js" | ||||
|     ], | ||||
|     root: true, | ||||
|     env: { | ||||
|         browser: true, | ||||
|  | @ -34,7 +39,7 @@ module.exports = { | |||
|             }, | ||||
|         ], | ||||
|         quotes: ["warn", "double"], | ||||
|         semi: "warn", | ||||
|         semi: "error", | ||||
|         "vue/html-indent": ["warn", 4], // default: 2
 | ||||
|         "vue/max-attributes-per-line": "off", | ||||
|         "vue/singleline-html-element-content-newline": "off", | ||||
|  |  | |||
							
								
								
									
										1
									
								
								.github/workflows/auto-test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/auto-test.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -20,6 +20,7 @@ jobs: | |||
|         # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ | ||||
| 
 | ||||
|     steps: | ||||
|     - run: git config --global core.autocrlf false  # Mainly for Windows | ||||
|     - uses: actions/checkout@v2 | ||||
| 
 | ||||
|     - name: Use Node.js ${{ matrix.node-version }} | ||||
|  |  | |||
|  | @ -1,9 +1,13 @@ | |||
| { | ||||
|     "extends": "stylelint-config-standard", | ||||
|     "customSyntax": "postcss-html", | ||||
|     "rules": { | ||||
|         "indentation": 4, | ||||
|         "no-descending-specificity": null, | ||||
|         "selector-list-comma-newline-after": null, | ||||
|         "declaration-empty-line-before": null | ||||
|         "declaration-empty-line-before": null, | ||||
|         "alpha-value-notation": "number", | ||||
|         "color-function-notation": "legacy", | ||||
|         "shorthand-property-no-redundant-values": null | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -44,6 +44,8 @@ My long story here: https://www.reddit.com/r/UptimeKuma/comments/t1t6or/comment/ | |||
| 
 | ||||
| ### Recommended Pull Request Guideline | ||||
| 
 | ||||
| Before deep into coding, disscussion first is preferred. Creating an empty pull request for disscussion would be recommended. | ||||
| 
 | ||||
| 1. Fork the project | ||||
| 1. Clone your fork repo to local | ||||
| 1. Create a new branch | ||||
|  | @ -53,6 +55,7 @@ My long story here: https://www.reddit.com/r/UptimeKuma/comments/t1t6or/comment/ | |||
| 1. Create a pull request: https://github.com/louislam/uptime-kuma/compare | ||||
| 1. Write a proper description | ||||
| 1. Click "Change to draft" | ||||
| 1. Discussion | ||||
| 
 | ||||
| #### ❌ Won't Merge | ||||
| 
 | ||||
|  |  | |||
|  | @ -11,3 +11,4 @@ services: | |||
|       - ./uptime-kuma:/app/data | ||||
|     ports: | ||||
|       - 3001:3001 | ||||
|     restart: always | ||||
|  |  | |||
|  | @ -3,4 +3,4 @@ module.exports = { | |||
|         name: "uptime-kuma", | ||||
|         script: "./server/server.js", | ||||
|     }] | ||||
| } | ||||
| }; | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| const pkg = require("../../package.json"); | ||||
| const fs = require("fs"); | ||||
| const child_process = require("child_process"); | ||||
| const childProcess = require("child_process"); | ||||
| const util = require("../../src/util"); | ||||
| 
 | ||||
| util.polyfill(); | ||||
|  | @ -32,7 +32,7 @@ if (! exists) { | |||
| function commit(version) { | ||||
|     let msg = "Update to " + version; | ||||
| 
 | ||||
|     let res = child_process.spawnSync("git", ["commit", "-m", msg, "-a"]); | ||||
|     let res = childProcess.spawnSync("git", ["commit", "-m", msg, "-a"]); | ||||
|     let stdout = res.stdout.toString().trim(); | ||||
|     console.log(stdout); | ||||
| 
 | ||||
|  | @ -40,15 +40,15 @@ function commit(version) { | |||
|         throw new Error("commit error"); | ||||
|     } | ||||
| 
 | ||||
|     res = child_process.spawnSync("git", ["push", "origin", "master"]); | ||||
|     res = childProcess.spawnSync("git", ["push", "origin", "master"]); | ||||
|     console.log(res.stdout.toString().trim()); | ||||
| } | ||||
| 
 | ||||
| function tag(version) { | ||||
|     let res = child_process.spawnSync("git", ["tag", version]); | ||||
|     let res = childProcess.spawnSync("git", ["tag", version]); | ||||
|     console.log(res.stdout.toString().trim()); | ||||
| 
 | ||||
|     res = child_process.spawnSync("git", ["push", "origin", version]); | ||||
|     res = childProcess.spawnSync("git", ["push", "origin", version]); | ||||
|     console.log(res.stdout.toString().trim()); | ||||
| } | ||||
| 
 | ||||
|  | @ -57,7 +57,7 @@ function tagExists(version) { | |||
|         throw new Error("invalid version"); | ||||
|     } | ||||
| 
 | ||||
|     let res = child_process.spawnSync("git", ["tag", "-l", version]); | ||||
|     let res = childProcess.spawnSync("git", ["tag", "-l", version]); | ||||
| 
 | ||||
|     return res.stdout.toString().trim() === version; | ||||
| } | ||||
|  |  | |||
|  | @ -12,6 +12,12 @@ const filename = "dist.tar.gz"; | |||
| const url = `https://github.com/louislam/uptime-kuma/releases/download/${version}/${filename}`; | ||||
| download(url); | ||||
| 
 | ||||
| /** | ||||
|  * Downloads the latest version of the dist from a GitHub release. | ||||
|  * @param {string} url The URL to download from. | ||||
|  * | ||||
|  * Generated by Trelent | ||||
|  */ | ||||
| function download(url) { | ||||
|     console.log(url); | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,21 +4,21 @@ const util = require("../src/util"); | |||
| 
 | ||||
| util.polyfill(); | ||||
| 
 | ||||
| const oldVersion = pkg.version | ||||
| const newVersion = oldVersion + "-nightly" | ||||
| const oldVersion = pkg.version; | ||||
| const newVersion = oldVersion + "-nightly"; | ||||
| 
 | ||||
| console.log("Old Version: " + oldVersion) | ||||
| console.log("New Version: " + newVersion) | ||||
| console.log("Old Version: " + oldVersion); | ||||
| console.log("New Version: " + newVersion); | ||||
| 
 | ||||
| if (newVersion) { | ||||
|     // Process package.json
 | ||||
|     pkg.version = newVersion | ||||
|     pkg.scripts.setup = pkg.scripts.setup.replaceAll(oldVersion, newVersion) | ||||
|     pkg.scripts["build-docker"] = pkg.scripts["build-docker"].replaceAll(oldVersion, newVersion) | ||||
|     fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n") | ||||
|     pkg.version = newVersion; | ||||
|     pkg.scripts.setup = pkg.scripts.setup.replaceAll(oldVersion, newVersion); | ||||
|     pkg.scripts["build-docker"] = pkg.scripts["build-docker"].replaceAll(oldVersion, newVersion); | ||||
|     fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n"); | ||||
| 
 | ||||
|     // Process README.md
 | ||||
|     if (fs.existsSync("README.md")) { | ||||
|         fs.writeFileSync("README.md", fs.readFileSync("README.md", "utf8").replaceAll(oldVersion, newVersion)) | ||||
|         fs.writeFileSync("README.md", fs.readFileSync("README.md", "utf8").replaceAll(oldVersion, newVersion)); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ server.on("request", (request, send, rinfo) => { | |||
|                     ttl: 300, | ||||
|                     address: "1.2.3.4" | ||||
|                 }); | ||||
|             } if (question.type === Packet.TYPE.AAAA) { | ||||
|             } else if (question.type === Packet.TYPE.AAAA) { | ||||
|                 response.answers.push({ | ||||
|                     name: question.name, | ||||
|                     type: question.type, | ||||
|  |  | |||
|  | @ -33,6 +33,12 @@ if (! exists) { | |||
|     console.log("version exists"); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Updates the version number in package.json and commits it to git. | ||||
|  * @param {string} version - The new version number | ||||
|  * | ||||
|  * Generated by Trelent | ||||
|  */ | ||||
| function commit(version) { | ||||
|     let msg = "Update to " + version; | ||||
| 
 | ||||
|  | @ -50,6 +56,12 @@ function tag(version) { | |||
|     console.log(res.stdout.toString().trim()); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Checks if a given version is already tagged in the git repository. | ||||
|  * @param {string} version - The version to check for. | ||||
|  * | ||||
|  * Generated by Trelent | ||||
|  */ | ||||
| function tagExists(version) { | ||||
|     if (! version) { | ||||
|         throw new Error("invalid version"); | ||||
|  |  | |||
							
								
								
									
										188
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										188
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							|  | @ -1,12 +1,12 @@ | |||
| { | ||||
|     "name": "uptime-kuma", | ||||
|     "version": "1.14.0-beta.0", | ||||
|     "version": "1.14.0", | ||||
|     "lockfileVersion": 2, | ||||
|     "requires": true, | ||||
|     "packages": { | ||||
|         "": { | ||||
|             "name": "uptime-kuma", | ||||
|             "version": "1.14.0-beta.0", | ||||
|             "version": "1.14.0", | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@fortawesome/fontawesome-svg-core": "~1.2.36", | ||||
|  | @ -85,6 +85,7 @@ | |||
|                 "jest": "~27.2.5", | ||||
|                 "jest-puppeteer": "~6.0.3", | ||||
|                 "npm-check-updates": "^12.5.5", | ||||
|                 "postcss-html": "^1.3.1", | ||||
|                 "puppeteer": "~13.1.3", | ||||
|                 "sass": "~1.42.1", | ||||
|                 "stylelint": "~14.2.0", | ||||
|  | @ -5612,6 +5613,41 @@ | |||
|                 "node": ">=6.0.0" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/dom-serializer": { | ||||
|             "version": "1.4.1", | ||||
|             "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", | ||||
|             "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", | ||||
|             "dev": true, | ||||
|             "dependencies": { | ||||
|                 "domelementtype": "^2.0.1", | ||||
|                 "domhandler": "^4.2.0", | ||||
|                 "entities": "^2.0.0" | ||||
|             }, | ||||
|             "funding": { | ||||
|                 "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/dom-serializer/node_modules/entities": { | ||||
|             "version": "2.2.0", | ||||
|             "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", | ||||
|             "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", | ||||
|             "dev": true, | ||||
|             "funding": { | ||||
|                 "url": "https://github.com/fb55/entities?sponsor=1" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/domelementtype": { | ||||
|             "version": "2.3.0", | ||||
|             "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", | ||||
|             "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", | ||||
|             "dev": true, | ||||
|             "funding": [ | ||||
|                 { | ||||
|                     "type": "github", | ||||
|                     "url": "https://github.com/sponsors/fb55" | ||||
|                 } | ||||
|             ] | ||||
|         }, | ||||
|         "node_modules/domexception": { | ||||
|             "version": "2.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", | ||||
|  | @ -5633,6 +5669,35 @@ | |||
|                 "node": ">=8" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/domhandler": { | ||||
|             "version": "4.3.1", | ||||
|             "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", | ||||
|             "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", | ||||
|             "dev": true, | ||||
|             "dependencies": { | ||||
|                 "domelementtype": "^2.2.0" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": ">= 4" | ||||
|             }, | ||||
|             "funding": { | ||||
|                 "url": "https://github.com/fb55/domhandler?sponsor=1" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/domutils": { | ||||
|             "version": "2.8.0", | ||||
|             "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", | ||||
|             "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", | ||||
|             "dev": true, | ||||
|             "dependencies": { | ||||
|                 "dom-serializer": "^1.0.1", | ||||
|                 "domelementtype": "^2.2.0", | ||||
|                 "domhandler": "^4.2.0" | ||||
|             }, | ||||
|             "funding": { | ||||
|                 "url": "https://github.com/fb55/domutils?sponsor=1" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/dot-prop": { | ||||
|             "version": "5.3.0", | ||||
|             "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", | ||||
|  | @ -5817,6 +5882,18 @@ | |||
|                 "node": ">=8.6" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/entities": { | ||||
|             "version": "3.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", | ||||
|             "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", | ||||
|             "dev": true, | ||||
|             "engines": { | ||||
|                 "node": ">=0.12" | ||||
|             }, | ||||
|             "funding": { | ||||
|                 "url": "https://github.com/fb55/entities?sponsor=1" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/env-paths": { | ||||
|             "version": "2.2.1", | ||||
|             "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", | ||||
|  | @ -7557,6 +7634,25 @@ | |||
|                 "node": ">=8" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/htmlparser2": { | ||||
|             "version": "7.2.0", | ||||
|             "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", | ||||
|             "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", | ||||
|             "dev": true, | ||||
|             "funding": [ | ||||
|                 "https://github.com/fb55/htmlparser2?sponsor=1", | ||||
|                 { | ||||
|                     "type": "github", | ||||
|                     "url": "https://github.com/sponsors/fb55" | ||||
|                 } | ||||
|             ], | ||||
|             "dependencies": { | ||||
|                 "domelementtype": "^2.0.1", | ||||
|                 "domhandler": "^4.2.2", | ||||
|                 "domutils": "^2.8.0", | ||||
|                 "entities": "^3.0.1" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/http-cache-semantics": { | ||||
|             "version": "4.1.0", | ||||
|             "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", | ||||
|  | @ -12505,6 +12601,20 @@ | |||
|                 "node": "^10 || ^12 || >=14" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/postcss-html": { | ||||
|             "version": "1.3.1", | ||||
|             "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-1.3.1.tgz", | ||||
|             "integrity": "sha512-SJ7iRw+IngyZv3Z9lChlZU30a9y9MZjZZcoUJmx0T/nKE9S+hetJ8fAv/MRu4bPnGDsXhVlaFs5+umpK3yaaQQ==", | ||||
|             "dev": true, | ||||
|             "dependencies": { | ||||
|                 "htmlparser2": "^7.1.2", | ||||
|                 "postcss": "^8.4.0", | ||||
|                 "postcss-safe-parser": "^6.0.0" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^12 || >=14" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/postcss-media-query-parser": { | ||||
|             "version": "0.2.3", | ||||
|             "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", | ||||
|  | @ -20003,6 +20113,31 @@ | |||
|                 "esutils": "^2.0.2" | ||||
|             } | ||||
|         }, | ||||
|         "dom-serializer": { | ||||
|             "version": "1.4.1", | ||||
|             "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", | ||||
|             "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", | ||||
|             "dev": true, | ||||
|             "requires": { | ||||
|                 "domelementtype": "^2.0.1", | ||||
|                 "domhandler": "^4.2.0", | ||||
|                 "entities": "^2.0.0" | ||||
|             }, | ||||
|             "dependencies": { | ||||
|                 "entities": { | ||||
|                     "version": "2.2.0", | ||||
|                     "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", | ||||
|                     "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", | ||||
|                     "dev": true | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "domelementtype": { | ||||
|             "version": "2.3.0", | ||||
|             "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", | ||||
|             "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", | ||||
|             "dev": true | ||||
|         }, | ||||
|         "domexception": { | ||||
|             "version": "2.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", | ||||
|  | @ -20020,6 +20155,26 @@ | |||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "domhandler": { | ||||
|             "version": "4.3.1", | ||||
|             "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", | ||||
|             "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", | ||||
|             "dev": true, | ||||
|             "requires": { | ||||
|                 "domelementtype": "^2.2.0" | ||||
|             } | ||||
|         }, | ||||
|         "domutils": { | ||||
|             "version": "2.8.0", | ||||
|             "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", | ||||
|             "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", | ||||
|             "dev": true, | ||||
|             "requires": { | ||||
|                 "dom-serializer": "^1.0.1", | ||||
|                 "domelementtype": "^2.2.0", | ||||
|                 "domhandler": "^4.2.0" | ||||
|             } | ||||
|         }, | ||||
|         "dot-prop": { | ||||
|             "version": "5.3.0", | ||||
|             "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", | ||||
|  | @ -20157,6 +20312,12 @@ | |||
|                 "ansi-colors": "^4.1.1" | ||||
|             } | ||||
|         }, | ||||
|         "entities": { | ||||
|             "version": "3.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", | ||||
|             "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", | ||||
|             "dev": true | ||||
|         }, | ||||
|         "env-paths": { | ||||
|             "version": "2.2.1", | ||||
|             "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", | ||||
|  | @ -21437,6 +21598,18 @@ | |||
|             "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==", | ||||
|             "dev": true | ||||
|         }, | ||||
|         "htmlparser2": { | ||||
|             "version": "7.2.0", | ||||
|             "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", | ||||
|             "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", | ||||
|             "dev": true, | ||||
|             "requires": { | ||||
|                 "domelementtype": "^2.0.1", | ||||
|                 "domhandler": "^4.2.2", | ||||
|                 "domutils": "^2.8.0", | ||||
|                 "entities": "^3.0.1" | ||||
|             } | ||||
|         }, | ||||
|         "http-cache-semantics": { | ||||
|             "version": "4.1.0", | ||||
|             "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", | ||||
|  | @ -25173,6 +25346,17 @@ | |||
|                 "source-map-js": "^1.0.2" | ||||
|             } | ||||
|         }, | ||||
|         "postcss-html": { | ||||
|             "version": "1.3.1", | ||||
|             "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-1.3.1.tgz", | ||||
|             "integrity": "sha512-SJ7iRw+IngyZv3Z9lChlZU30a9y9MZjZZcoUJmx0T/nKE9S+hetJ8fAv/MRu4bPnGDsXhVlaFs5+umpK3yaaQQ==", | ||||
|             "dev": true, | ||||
|             "requires": { | ||||
|                 "htmlparser2": "^7.1.2", | ||||
|                 "postcss": "^8.4.0", | ||||
|                 "postcss-safe-parser": "^6.0.0" | ||||
|             } | ||||
|         }, | ||||
|         "postcss-media-query-parser": { | ||||
|             "version": "0.2.3", | ||||
|             "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|     "name": "uptime-kuma", | ||||
|     "version": "1.14.0-beta.2", | ||||
|     "version": "1.14.0", | ||||
|     "license": "MIT", | ||||
|     "repository": { | ||||
|         "type": "git", | ||||
|  | @ -20,7 +20,7 @@ | |||
|         "start-server": "node server/server.js", | ||||
|         "start-server-dev": "cross-env NODE_ENV=development node server/server.js", | ||||
|         "build": "vite build --config ./config/vite.config.js", | ||||
|         "test": "node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/ --test", | ||||
|         "test": "npm run lint && node test/prepare-test-server.js && node server/server.js --port=3002 --data-dir=./data/test/ --test", | ||||
|         "test-with-build": "npm run build && npm test", | ||||
|         "jest": "node test/prepare-jest.js && npm run jest-frontend && npm run jest-backend", | ||||
|         "jest-frontend": "cross-env TEST_FRONTEND=1 jest --config=./config/jest-frontend.config.js", | ||||
|  | @ -36,7 +36,7 @@ | |||
|         "build-docker-nightly-alpine": "docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly-alpine --target nightly . --push", | ||||
|         "build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain", | ||||
|         "upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain", | ||||
|         "setup": "git checkout 1.13.2 && npm ci --production && npm run download-dist", | ||||
|         "setup": "git checkout 1.14.0 && npm ci --production && npm run download-dist", | ||||
|         "download-dist": "node extra/download-dist.js", | ||||
|         "mark-as-nightly": "node extra/mark-as-nightly.js", | ||||
|         "reset-password": "node extra/reset-password.js", | ||||
|  | @ -132,6 +132,7 @@ | |||
|         "jest": "~27.2.5", | ||||
|         "jest-puppeteer": "~6.0.3", | ||||
|         "npm-check-updates": "^12.5.5", | ||||
|         "postcss-html": "^1.3.1", | ||||
|         "puppeteer": "~13.1.3", | ||||
|         "sass": "~1.42.1", | ||||
|         "stylelint": "~14.2.0", | ||||
|  |  | |||
|  | @ -2,7 +2,6 @@ const basicAuth = require("express-basic-auth"); | |||
| const passwordHash = require("./password-hash"); | ||||
| const { R } = require("redbean-node"); | ||||
| const { setting } = require("./util-server"); | ||||
| const { debug } = require("../src/util"); | ||||
| const { loginRateLimiter } = require("./rate-limiter"); | ||||
| 
 | ||||
| /** | ||||
|  | @ -34,6 +33,13 @@ exports.login = async function (username, password) { | |||
|     return null; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * A function that checks if a user is logged in. | ||||
|  * @param {string} username The username of the user to check for. | ||||
|  * @param {function} callback The callback to call when done, with an error and result parameter. | ||||
|  * | ||||
|  * Generated by Trelent | ||||
|  */ | ||||
| function myAuthorizer(username, password, callback) { | ||||
|     // Login Rate Limit
 | ||||
|     loginRateLimiter.pass(null, 0).then((pass) => { | ||||
|  |  | |||
|  | @ -7,6 +7,12 @@ const { io } = require("./server"); | |||
| const { setting } = require("./util-server"); | ||||
| const checkVersion = require("./check-version"); | ||||
| 
 | ||||
| /** | ||||
|  * Send a list of notifications to the user. | ||||
|  * @param {Socket} socket The socket object that is connected to the client. | ||||
|  * | ||||
|  * Generated by Trelent | ||||
|  */ | ||||
| async function sendNotificationList(socket) { | ||||
|     const timeLogger = new TimeLogger(); | ||||
| 
 | ||||
|  | @ -100,6 +106,12 @@ async function sendProxyList(socket) { | |||
|     return list; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Emits the version information to the client. | ||||
|  * @param {Socket} socket The socket object that is connected to the client. | ||||
|  * | ||||
|  * Generated by Trelent | ||||
|  */ | ||||
| async function sendInfo(socket) { | ||||
|     socket.emit("info", { | ||||
|         version: checkVersion.version, | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| const fs = require("fs"); | ||||
| const { R } = require("redbean-node"); | ||||
| const { setSetting, setting } = require("./util-server"); | ||||
| const { debug, sleep } = require("../src/util"); | ||||
| const { log, sleep } = require("../src/util"); | ||||
| const dayjs = require("dayjs"); | ||||
| const knex = require("knex"); | ||||
| 
 | ||||
|  | @ -80,7 +80,7 @@ class Database { | |||
|             fs.mkdirSync(Database.uploadDir, { recursive: true }); | ||||
|         } | ||||
| 
 | ||||
|         console.log(`Data Dir: ${Database.dataDir}`); | ||||
|         log.info("db", `Data Dir: ${Database.dataDir}`); | ||||
|     } | ||||
| 
 | ||||
|     static async connect(testMode = false, autoloadModels = true, noLog = false) { | ||||
|  | @ -135,10 +135,10 @@ class Database { | |||
|         await R.exec("PRAGMA synchronous = FULL"); | ||||
| 
 | ||||
|         if (!noLog) { | ||||
|             console.log("SQLite config:"); | ||||
|             console.log(await R.getAll("PRAGMA journal_mode")); | ||||
|             console.log(await R.getAll("PRAGMA cache_size")); | ||||
|             console.log("SQLite Version: " + await R.getCell("SELECT sqlite_version()")); | ||||
|             log.info("db", "SQLite config:"); | ||||
|             log.info("db", await R.getAll("PRAGMA journal_mode")); | ||||
|             log.info("db", await R.getAll("PRAGMA cache_size")); | ||||
|             log.info("db", "SQLite Version: " + await R.getCell("SELECT sqlite_version()")); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -149,15 +149,15 @@ class Database { | |||
|             version = 0; | ||||
|         } | ||||
| 
 | ||||
|         console.info("Your database version: " + version); | ||||
|         console.info("Latest database version: " + this.latestVersion); | ||||
|         log.info("db", "Your database version: " + version); | ||||
|         log.info("db", "Latest database version: " + this.latestVersion); | ||||
| 
 | ||||
|         if (version === this.latestVersion) { | ||||
|             console.info("Database patch not needed"); | ||||
|             log.info("db", "Database patch not needed"); | ||||
|         } else if (version > this.latestVersion) { | ||||
|             console.info("Warning: Database version is newer than expected"); | ||||
|             log.info("db", "Warning: Database version is newer than expected"); | ||||
|         } else { | ||||
|             console.info("Database patch is needed"); | ||||
|             log.info("db", "Database patch is needed"); | ||||
| 
 | ||||
|             this.backup(version); | ||||
| 
 | ||||
|  | @ -165,17 +165,17 @@ class Database { | |||
|             try { | ||||
|                 for (let i = version + 1; i <= this.latestVersion; i++) { | ||||
|                     const sqlFile = `./db/patch${i}.sql`; | ||||
|                     console.info(`Patching ${sqlFile}`); | ||||
|                     log.info("db", `Patching ${sqlFile}`); | ||||
|                     await Database.importSQLFile(sqlFile); | ||||
|                     console.info(`Patched ${sqlFile}`); | ||||
|                     log.info("db", `Patched ${sqlFile}`); | ||||
|                     await setSetting("database_version", i); | ||||
|                 } | ||||
|             } catch (ex) { | ||||
|                 await Database.close(); | ||||
| 
 | ||||
|                 console.error(ex); | ||||
|                 console.error("Start Uptime-Kuma failed due to issue patching the database"); | ||||
|                 console.error("Please submit a bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues"); | ||||
|                 log.error("db", ex); | ||||
|                 log.error("db", "Start Uptime-Kuma failed due to issue patching the database"); | ||||
|                 log.error("db", "Please submit a bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues"); | ||||
| 
 | ||||
|                 this.restore(); | ||||
|                 process.exit(1); | ||||
|  | @ -191,15 +191,15 @@ class Database { | |||
|      * @returns {Promise<void>} | ||||
|      */ | ||||
|     static async patch2() { | ||||
|         console.log("Database Patch 2.0 Process"); | ||||
|         log.info("db", "Database Patch 2.0 Process"); | ||||
|         let databasePatchedFiles = await setting("databasePatchedFiles"); | ||||
| 
 | ||||
|         if (! databasePatchedFiles) { | ||||
|             databasePatchedFiles = {}; | ||||
|         } | ||||
| 
 | ||||
|         debug("Patched files:"); | ||||
|         debug(databasePatchedFiles); | ||||
|         log.debug("db", "Patched files:"); | ||||
|         log.debug("db", databasePatchedFiles); | ||||
| 
 | ||||
|         try { | ||||
|             for (let sqlFilename in this.patchList) { | ||||
|  | @ -207,15 +207,15 @@ class Database { | |||
|             } | ||||
| 
 | ||||
|             if (this.patched) { | ||||
|                 console.log("Database Patched Successfully"); | ||||
|                 log.info("db", "Database Patched Successfully"); | ||||
|             } | ||||
| 
 | ||||
|         } catch (ex) { | ||||
|             await Database.close(); | ||||
| 
 | ||||
|             console.error(ex); | ||||
|             console.error("Start Uptime-Kuma failed due to issue patching the database"); | ||||
|             console.error("Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues"); | ||||
|             log.error("db", ex); | ||||
|             log.error("db", "Start Uptime-Kuma failed due to issue patching the database"); | ||||
|             log.error("db", "Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues"); | ||||
| 
 | ||||
|             this.restore(); | ||||
| 
 | ||||
|  | @ -302,16 +302,16 @@ class Database { | |||
|         let value = this.patchList[sqlFilename]; | ||||
| 
 | ||||
|         if (! value) { | ||||
|             console.log(sqlFilename + " skip"); | ||||
|             log.info("db", sqlFilename + " skip"); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // Check if patched
 | ||||
|         if (! databasePatchedFiles[sqlFilename]) { | ||||
|             console.log(sqlFilename + " is not patched"); | ||||
|             log.info("db", sqlFilename + " is not patched"); | ||||
| 
 | ||||
|             if (value.parents) { | ||||
|                 console.log(sqlFilename + " need parents"); | ||||
|                 log.info("db", sqlFilename + " need parents"); | ||||
|                 for (let parentSQLFilename of value.parents) { | ||||
|                     await this.patch2Recursion(parentSQLFilename, databasePatchedFiles); | ||||
|                 } | ||||
|  | @ -319,14 +319,14 @@ class Database { | |||
| 
 | ||||
|             this.backup(dayjs().format("YYYYMMDDHHmmss")); | ||||
| 
 | ||||
|             console.log(sqlFilename + " is patching"); | ||||
|             log.info("db", sqlFilename + " is patching"); | ||||
|             this.patched = true; | ||||
|             await this.importSQLFile("./db/" + sqlFilename); | ||||
|             databasePatchedFiles[sqlFilename] = true; | ||||
|             console.log(sqlFilename + " was patched successfully"); | ||||
|             log.info("db", sqlFilename + " was patched successfully"); | ||||
| 
 | ||||
|         } else { | ||||
|             debug(sqlFilename + " is already patched, skip"); | ||||
|             log.debug("db", sqlFilename + " is already patched, skip"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -378,7 +378,7 @@ class Database { | |||
|         }; | ||||
|         process.addListener("unhandledRejection", listener); | ||||
| 
 | ||||
|         console.log("Closing the database"); | ||||
|         log.info("db", "Closing the database"); | ||||
| 
 | ||||
|         while (true) { | ||||
|             Database.noReject = true; | ||||
|  | @ -388,10 +388,10 @@ class Database { | |||
|             if (Database.noReject) { | ||||
|                 break; | ||||
|             } else { | ||||
|                 console.log("Waiting to close the database"); | ||||
|                 log.info("db", "Waiting to close the database"); | ||||
|             } | ||||
|         } | ||||
|         console.log("SQLite closed"); | ||||
|         log.info("db", "SQLite closed"); | ||||
| 
 | ||||
|         process.removeListener("unhandledRejection", listener); | ||||
|     } | ||||
|  | @ -403,7 +403,7 @@ class Database { | |||
|      */ | ||||
|     static backup(version) { | ||||
|         if (! this.backupPath) { | ||||
|             console.info("Backing up the database"); | ||||
|             log.info("db", "Backing up the database"); | ||||
|             this.backupPath = this.dataDir + "kuma.db.bak" + version; | ||||
|             fs.copyFileSync(Database.path, this.backupPath); | ||||
| 
 | ||||
|  | @ -426,7 +426,7 @@ class Database { | |||
|      */ | ||||
|     static restore() { | ||||
|         if (this.backupPath) { | ||||
|             console.error("Patching the database failed!!! Restoring the backup"); | ||||
|             log.error("db", "Patching the database failed!!! Restoring the backup"); | ||||
| 
 | ||||
|             const shmPath = Database.path + "-shm"; | ||||
|             const walPath = Database.path + "-wal"; | ||||
|  | @ -445,7 +445,7 @@ class Database { | |||
|                     fs.unlinkSync(walPath); | ||||
|                 } | ||||
|             } catch (e) { | ||||
|                 console.log("Restore failed; you may need to restore the backup manually"); | ||||
|                 log.error("db", "Restore failed; you may need to restore the backup manually"); | ||||
|                 process.exit(1); | ||||
|             } | ||||
| 
 | ||||
|  | @ -461,14 +461,14 @@ class Database { | |||
|             } | ||||
| 
 | ||||
|         } else { | ||||
|             console.log("Nothing to restore"); | ||||
|             log.info("db", "Nothing to restore"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     static getSize() { | ||||
|         debug("Database.getSize()"); | ||||
|         log.debug("db", "Database.getSize()"); | ||||
|         let stats = fs.statSync(Database.path); | ||||
|         debug(stats); | ||||
|         log.debug("db", stats); | ||||
|         return stats.size; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,12 +3,19 @@ | |||
|     Modified with 0 dependencies | ||||
|  */ | ||||
| let fs = require("fs"); | ||||
| const { log } = require("../src/util"); | ||||
| 
 | ||||
| let ImageDataURI = (() => { | ||||
| 
 | ||||
|     /** | ||||
|      * @param {string} dataURI - A string that is a valid Data URI. | ||||
|      * @returns {?Object} An object with properties "imageType" and "dataBase64". The former is the image type, e.g., "png", and the latter is a base64 encoded string of the image's binary data. If it fails to parse, returns null instead of an object. | ||||
|      * | ||||
|      * Generated by Trelent | ||||
|      */ | ||||
|     function decode(dataURI) { | ||||
|         if (!/data:image\//.test(dataURI)) { | ||||
|             console.log("ImageDataURI :: Error :: It seems that it is not an Image Data URI. Couldn't match \"data:image/\""); | ||||
|             log.error("image-data-uri", "It seems that it is not an Image Data URI. Couldn't match \"data:image/\""); | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|  | @ -20,9 +27,16 @@ let ImageDataURI = (() => { | |||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param {Buffer} data - The image data to be encoded. | ||||
|      * @param {String} mediaType - The type of the image, e.g., "image/png". | ||||
|      * @returns {String|null} A string representing the base64-encoded version of the given Buffer object or null if an error occurred. | ||||
|      * | ||||
|      * Generated by Trelent | ||||
|      */ | ||||
|     function encode(data, mediaType) { | ||||
|         if (!data || !mediaType) { | ||||
|             console.log("ImageDataURI :: Error :: Missing some of the required params: data, mediaType "); | ||||
|             log.error("image-data-uri", "Missing some of the required params: data, mediaType"); | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|  | @ -33,6 +47,13 @@ let ImageDataURI = (() => { | |||
|         return dataImgBase64; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Converts a data URI to a file path. | ||||
|      * @param {string} dataURI The Data URI of the image. | ||||
|      * @param {string} [filePath] The path where the image will be saved, defaults to "./". | ||||
|      * | ||||
|      * Generated by Trelent | ||||
|      */ | ||||
|     function outputFile(dataURI, filePath) { | ||||
|         filePath = filePath || "./"; | ||||
|         return new Promise((resolve, reject) => { | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| const path = require("path"); | ||||
| const Bree = require("bree"); | ||||
| const { SHARE_ENV } = require("worker_threads"); | ||||
| const { log } = require("../src/util"); | ||||
| let bree; | ||||
| const jobs = [ | ||||
|     { | ||||
|  | @ -18,7 +19,7 @@ const initBackgroundJobs = function (args) { | |||
|             workerData: args, | ||||
|         }, | ||||
|         workerMessageHandler: (message) => { | ||||
|             console.log("[Background Job]:", message); | ||||
|             log.info("jobs", message); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										0
									
								
								server/logger.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								server/logger.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -6,7 +6,7 @@ dayjs.extend(utc); | |||
| dayjs.extend(timezone); | ||||
| const axios = require("axios"); | ||||
| const { Prometheus } = require("../prometheus"); | ||||
| const { debug, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util"); | ||||
| const { log, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util"); | ||||
| const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, errorLog } = require("../util-server"); | ||||
| const { R } = require("redbean-node"); | ||||
| const { BeanModel } = require("redbean-node/dist/bean-model"); | ||||
|  | @ -193,7 +193,7 @@ class Monitor extends BeanModel { | |||
|                         rejectUnauthorized: !this.getIgnoreTls(), | ||||
|                     }; | ||||
| 
 | ||||
|                     debug(`[${this.name}] Prepare Options for axios`); | ||||
|                     log.debug("monitor", `[${this.name}] Prepare Options for axios`); | ||||
| 
 | ||||
|                     const options = { | ||||
|                         url: this.url, | ||||
|  | @ -230,8 +230,8 @@ class Monitor extends BeanModel { | |||
|                         options.httpsAgent = new https.Agent(httpsAgentOptions); | ||||
|                     } | ||||
| 
 | ||||
|                     debug(`[${this.name}] Axios Options: ${JSON.stringify(options)}`); | ||||
|                     debug(`[${this.name}] Axios Request`); | ||||
|                     log.debug("monitor", `[${this.name}] Axios Options: ${JSON.stringify(options)}`); | ||||
|                     log.debug("monitor", `[${this.name}] Axios Request`); | ||||
| 
 | ||||
|                     let res = await axios.request(options); | ||||
|                     bean.msg = `${res.status} - ${res.statusText}`; | ||||
|  | @ -240,29 +240,30 @@ class Monitor extends BeanModel { | |||
|                     // Check certificate if https is used
 | ||||
|                     let certInfoStartTime = dayjs().valueOf(); | ||||
|                     if (this.getUrl()?.protocol === "https:") { | ||||
|                         debug(`[${this.name}] Check cert`); | ||||
|                         log.debug("monitor", `[${this.name}] Check cert`); | ||||
|                         try { | ||||
|                             let tlsInfoObject = checkCertificate(res); | ||||
|                             tlsInfo = await this.updateTlsInfo(tlsInfoObject); | ||||
| 
 | ||||
|                             if (!this.getIgnoreTls() && this.isEnabledExpiryNotification()) { | ||||
|                                 debug(`[${this.name}] call sendCertNotification`); | ||||
|                                 log.debug("monitor", `[${this.name}] call sendCertNotification`); | ||||
|                                 await this.sendCertNotification(tlsInfoObject); | ||||
|                             } | ||||
| 
 | ||||
|                         } catch (e) { | ||||
|                             if (e.message !== "No TLS certificate in response") { | ||||
|                                 console.error(e.message); | ||||
|                                 log.error("monitor", "Caught error"); | ||||
|                                 log.error("monitor", e.message); | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     if (process.env.TIMELOGGER === "1") { | ||||
|                         debug("Cert Info Query Time: " + (dayjs().valueOf() - certInfoStartTime) + "ms"); | ||||
|                         log.debug("monitor", "Cert Info Query Time: " + (dayjs().valueOf() - certInfoStartTime) + "ms"); | ||||
|                     } | ||||
| 
 | ||||
|                     if (process.env.UPTIME_KUMA_LOG_RESPONSE_BODY_MONITOR_ID == this.id) { | ||||
|                         console.log(res.data); | ||||
|                         log.info("monitor", res.data); | ||||
|                     } | ||||
| 
 | ||||
|                     if (this.type === "http") { | ||||
|  | @ -342,7 +343,7 @@ class Monitor extends BeanModel { | |||
|                         time | ||||
|                     ]); | ||||
| 
 | ||||
|                     debug("heartbeatCount" + heartbeatCount + " " + time); | ||||
|                     log.debug("monitor", "heartbeatCount" + heartbeatCount + " " + time); | ||||
| 
 | ||||
|                     if (heartbeatCount <= 0) { | ||||
|                         // Fix #922, since previous heartbeat could be inserted by api, it should get from database
 | ||||
|  | @ -426,7 +427,7 @@ class Monitor extends BeanModel { | |||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             debug(`[${this.name}] Check isImportant`); | ||||
|             log.debug("monitor", `[${this.name}] Check isImportant`); | ||||
|             let isImportant = Monitor.isImportantBeat(isFirstBeat, previousBeat?.status, bean.status); | ||||
| 
 | ||||
|             // Mark as important if status changed, ignore pending pings,
 | ||||
|  | @ -434,11 +435,11 @@ class Monitor extends BeanModel { | |||
|             if (isImportant) { | ||||
|                 bean.important = true; | ||||
| 
 | ||||
|                 debug(`[${this.name}] sendNotification`); | ||||
|                 log.debug("monitor", `[${this.name}] sendNotification`); | ||||
|                 await Monitor.sendNotification(isFirstBeat, this, bean); | ||||
| 
 | ||||
|                 // Clear Status Page Cache
 | ||||
|                 debug(`[${this.name}] apicache clear`); | ||||
|                 log.debug("monitor", `[${this.name}] apicache clear`); | ||||
|                 apicache.clear(); | ||||
| 
 | ||||
|             } else { | ||||
|  | @ -446,33 +447,33 @@ class Monitor extends BeanModel { | |||
|             } | ||||
| 
 | ||||
|             if (bean.status === UP) { | ||||
|                 console.info(`Monitor #${this.id} '${this.name}': Successful Response: ${bean.ping} ms | Interval: ${beatInterval} seconds | Type: ${this.type}`); | ||||
|                 log.info("monitor", `Monitor #${this.id} '${this.name}': Successful Response: ${bean.ping} ms | Interval: ${beatInterval} seconds | Type: ${this.type}`); | ||||
|             } else if (bean.status === PENDING) { | ||||
|                 if (this.retryInterval > 0) { | ||||
|                     beatInterval = this.retryInterval; | ||||
|                 } | ||||
|                 console.warn(`Monitor #${this.id} '${this.name}': Pending: ${bean.msg} | Max retries: ${this.maxretries} | Retry: ${retries} | Retry Interval: ${beatInterval} seconds | Type: ${this.type}`); | ||||
|                 log.warn("monitor", `Monitor #${this.id} '${this.name}': Pending: ${bean.msg} | Max retries: ${this.maxretries} | Retry: ${retries} | Retry Interval: ${beatInterval} seconds | Type: ${this.type}`); | ||||
|             } else { | ||||
|                 console.warn(`Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Interval: ${beatInterval} seconds | Type: ${this.type}`); | ||||
|                 log.warn("monitor", `Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Interval: ${beatInterval} seconds | Type: ${this.type}`); | ||||
|             } | ||||
| 
 | ||||
|             debug(`[${this.name}] Send to socket`); | ||||
|             log.debug("monitor", `[${this.name}] Send to socket`); | ||||
|             io.to(this.user_id).emit("heartbeat", bean.toJSON()); | ||||
|             Monitor.sendStats(io, this.id, this.user_id); | ||||
| 
 | ||||
|             debug(`[${this.name}] Store`); | ||||
|             log.debug("monitor", `[${this.name}] Store`); | ||||
|             await R.store(bean); | ||||
| 
 | ||||
|             debug(`[${this.name}] prometheus.update`); | ||||
|             log.debug("monitor", `[${this.name}] prometheus.update`); | ||||
|             prometheus.update(bean, tlsInfo); | ||||
| 
 | ||||
|             previousBeat = bean; | ||||
| 
 | ||||
|             if (! this.isStop) { | ||||
|                 debug(`[${this.name}] SetTimeout for next check.`); | ||||
|                 log.debug("monitor", `[${this.name}] SetTimeout for next check.`); | ||||
|                 this.heartbeatInterval = setTimeout(safeBeat, beatInterval * 1000); | ||||
|             } else { | ||||
|                 console.log(`[${this.name}] isStop = true, no next check.`); | ||||
|                 log.info("monitor", `[${this.name}] isStop = true, no next check.`); | ||||
|             } | ||||
| 
 | ||||
|         }; | ||||
|  | @ -483,10 +484,10 @@ class Monitor extends BeanModel { | |||
|             } catch (e) { | ||||
|                 console.trace(e); | ||||
|                 errorLog(e, false); | ||||
|                 console.error("Please report to https://github.com/louislam/uptime-kuma/issues"); | ||||
|                 log.error("monitor", "Please report to https://github.com/louislam/uptime-kuma/issues"); | ||||
| 
 | ||||
|                 if (! this.isStop) { | ||||
|                     console.log("Try to restart the monitor"); | ||||
|                     log.info("monitor", "Try to restart the monitor"); | ||||
|                     this.heartbeatInterval = setTimeout(safeBeat, this.interval * 1000); | ||||
|                 } | ||||
|             } | ||||
|  | @ -533,41 +534,41 @@ class Monitor extends BeanModel { | |||
|      * @returns {Promise<object>} | ||||
|      */ | ||||
|     async updateTlsInfo(checkCertificateResult) { | ||||
|         let tls_info_bean = await R.findOne("monitor_tls_info", "monitor_id = ?", [ | ||||
|         let tlsInfoBean = await R.findOne("monitor_tls_info", "monitor_id = ?", [ | ||||
|             this.id, | ||||
|         ]); | ||||
| 
 | ||||
|         if (tls_info_bean == null) { | ||||
|             tls_info_bean = R.dispense("monitor_tls_info"); | ||||
|             tls_info_bean.monitor_id = this.id; | ||||
|         if (tlsInfoBean == null) { | ||||
|             tlsInfoBean = R.dispense("monitor_tls_info"); | ||||
|             tlsInfoBean.monitor_id = this.id; | ||||
|         } else { | ||||
| 
 | ||||
|             // Clear sent history if the cert changed.
 | ||||
|             try { | ||||
|                 let oldCertInfo = JSON.parse(tls_info_bean.info_json); | ||||
|                 let oldCertInfo = JSON.parse(tlsInfoBean.info_json); | ||||
| 
 | ||||
|                 let isValidObjects = oldCertInfo && oldCertInfo.certInfo && checkCertificateResult && checkCertificateResult.certInfo; | ||||
| 
 | ||||
|                 if (isValidObjects) { | ||||
|                     if (oldCertInfo.certInfo.fingerprint256 !== checkCertificateResult.certInfo.fingerprint256) { | ||||
|                         debug("Resetting sent_history"); | ||||
|                         log.debug("monitor", "Resetting sent_history"); | ||||
|                         await R.exec("DELETE FROM notification_sent_history WHERE type = 'certificate' AND monitor_id = ?", [ | ||||
|                             this.id | ||||
|                         ]); | ||||
|                     } else { | ||||
|                         debug("No need to reset sent_history"); | ||||
|                         debug(oldCertInfo.certInfo.fingerprint256); | ||||
|                         debug(checkCertificateResult.certInfo.fingerprint256); | ||||
|                         log.debug("monitor", "No need to reset sent_history"); | ||||
|                         log.debug("monitor", oldCertInfo.certInfo.fingerprint256); | ||||
|                         log.debug("monitor", checkCertificateResult.certInfo.fingerprint256); | ||||
|                     } | ||||
|                 } else { | ||||
|                     debug("Not valid object"); | ||||
|                     log.debug("monitor", "Not valid object"); | ||||
|                 } | ||||
|             } catch (e) { } | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         tls_info_bean.info_json = JSON.stringify(checkCertificateResult); | ||||
|         await R.store(tls_info_bean); | ||||
|         tlsInfoBean.info_json = JSON.stringify(checkCertificateResult); | ||||
|         await R.store(tlsInfoBean); | ||||
| 
 | ||||
|         return checkCertificateResult; | ||||
|     } | ||||
|  | @ -581,7 +582,7 @@ class Monitor extends BeanModel { | |||
|             await Monitor.sendUptime(24 * 30, io, monitorID, userID); | ||||
|             await Monitor.sendCertInfo(io, monitorID, userID); | ||||
|         } else { | ||||
|             debug("No clients in the room, no need to send stats"); | ||||
|             log.debug("monitor", "No clients in the room, no need to send stats"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -728,8 +729,8 @@ class Monitor extends BeanModel { | |||
|                 try { | ||||
|                     await Notification.send(JSON.parse(notification.config), msg, await monitor.toJSON(), bean.toJSON()); | ||||
|                 } catch (e) { | ||||
|                     console.error("Cannot send notification to " + notification.name); | ||||
|                     console.log(e); | ||||
|                     log.error("monitor", "Cannot send notification to " + notification.name); | ||||
|                     log.error("monitor", e); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | @ -746,7 +747,7 @@ class Monitor extends BeanModel { | |||
|         if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) { | ||||
|             const notificationList = await Monitor.getNotificationList(this); | ||||
| 
 | ||||
|             debug("call sendCertNotificationByTargetDays"); | ||||
|             log.debug("monitor", "call sendCertNotificationByTargetDays"); | ||||
|             await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 21, notificationList); | ||||
|             await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 14, notificationList); | ||||
|             await this.sendCertNotificationByTargetDays(tlsInfoObject.certInfo.daysRemaining, 7, notificationList); | ||||
|  | @ -756,7 +757,7 @@ class Monitor extends BeanModel { | |||
|     async sendCertNotificationByTargetDays(daysRemaining, targetDays, notificationList) { | ||||
| 
 | ||||
|         if (daysRemaining > targetDays) { | ||||
|             debug(`No need to send cert notification. ${daysRemaining} > ${targetDays}`); | ||||
|             log.debug("monitor", `No need to send cert notification. ${daysRemaining} > ${targetDays}`); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|  | @ -770,21 +771,21 @@ class Monitor extends BeanModel { | |||
| 
 | ||||
|             // Sent already, no need to send again
 | ||||
|             if (row) { | ||||
|                 debug("Sent already, no need to send again"); | ||||
|                 log.debug("monitor", "Sent already, no need to send again"); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             let sent = false; | ||||
|             debug("Send certificate notification"); | ||||
|             log.debug("monitor", "Send certificate notification"); | ||||
| 
 | ||||
|             for (let notification of notificationList) { | ||||
|                 try { | ||||
|                     debug("Sending to " + notification.name); | ||||
|                     log.debug("monitor", "Sending to " + notification.name); | ||||
|                     await Notification.send(JSON.parse(notification.config), `[${this.name}][${this.url}] Certificate will be expired in ${daysRemaining} days`); | ||||
|                     sent = true; | ||||
|                 } catch (e) { | ||||
|                     console.error("Cannot send cert notification to " + notification.name); | ||||
|                     console.error(e); | ||||
|                     log.error("monitor", "Cannot send cert notification to " + notification.name); | ||||
|                     log.error("monitor", e); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|  | @ -796,7 +797,7 @@ class Monitor extends BeanModel { | |||
|                 ]); | ||||
|             } | ||||
|         } else { | ||||
|             debug("No notification, no need to send cert notification"); | ||||
|             log.debug("monitor", "No notification, no need to send cert notification"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -68,6 +68,15 @@ function ApiCache() { | |||
|     instances.push(this); | ||||
|     this.id = instances.length; | ||||
| 
 | ||||
|     /** | ||||
|      * Logs a message to the console if the `DEBUG` environment variable is set. | ||||
|      * @param {string} a - The first argument to log. | ||||
|      * @param {string} b - The second argument to log. | ||||
|      * @param {string} c - The third argument to log. | ||||
|      * @param {string} d - The fourth argument to log, and so on... (optional) | ||||
|      * | ||||
|      * Generated by Trelent | ||||
|      */ | ||||
|     function debug(a, b, c, d) { | ||||
|         let arr = ["\x1b[36m[apicache]\x1b[0m", a, b, c, d].filter(function (arg) { | ||||
|             return arg !== undefined; | ||||
|  | @ -77,6 +86,13 @@ function ApiCache() { | |||
|         return (globalOptions.debug || debugEnv) && console.log.apply(null, arr); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns true if the given request and response should be logged. | ||||
|      * @param {Object} request The HTTP request object. | ||||
|      * @param {Object} response The HTTP response object. | ||||
|      * | ||||
|      * Generated by Trelent | ||||
|      */ | ||||
|     function shouldCacheResponse(request, response, toggle) { | ||||
|         let opt = globalOptions; | ||||
|         let codes = opt.statusCodes; | ||||
|  | @ -99,6 +115,12 @@ function ApiCache() { | |||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Adds a key to the index. | ||||
|      * @param {string} key The key to add. | ||||
|      * | ||||
|      * Generated by Trelent | ||||
|      */ | ||||
|     function addIndexEntries(key, req) { | ||||
|         let groupName = req.apicacheGroup; | ||||
| 
 | ||||
|  | @ -111,6 +133,13 @@ function ApiCache() { | |||
|         index.all.unshift(key); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns a new object containing only the whitelisted headers. | ||||
|      * @param {Object} headers The original object of header names and values. | ||||
|      * @param {Array.<string>} globalOptions.headerWhitelist An array of strings representing the whitelisted header names to keep in the output object. | ||||
|      * | ||||
|      * Generated by Trelent | ||||
|      */ | ||||
|     function filterBlacklistedHeaders(headers) { | ||||
|         return Object.keys(headers) | ||||
|             .filter(function (key) { | ||||
|  | @ -122,6 +151,12 @@ function ApiCache() { | |||
|             }, {}); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param {Object} headers The response headers to filter. | ||||
|      * @returns {Object} A new object containing only the whitelisted response headers. | ||||
|      * | ||||
|      * Generated by Trelent | ||||
|      */ | ||||
|     function createCacheObject(status, headers, data, encoding) { | ||||
|         return { | ||||
|             status: status, | ||||
|  | @ -132,6 +167,14 @@ function ApiCache() { | |||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sets a cache value for the given key. | ||||
|      * @param {string} key The cache key to set. | ||||
|      * @param {*} value The cache value to set. | ||||
|      * @param {number} duration How long in milliseconds the cached response should be valid for (defaults to 1 hour). | ||||
|      * | ||||
|      * Generated by Trelent | ||||
|      */ | ||||
|     function cacheResponse(key, value, duration) { | ||||
|         let redis = globalOptions.redisClient; | ||||
|         let expireCallback = globalOptions.events.expire; | ||||
|  | @ -154,6 +197,12 @@ function ApiCache() { | |||
|         }, Math.min(duration, 2147483647)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Appends content to the response. | ||||
|      * @param {string|Buffer} content The content to append. | ||||
|      * | ||||
|      * Generated by Trelent | ||||
|      */ | ||||
|     function accumulateContent(res, content) { | ||||
|         if (content) { | ||||
|             if (typeof content == "string") { | ||||
|  | @ -179,6 +228,13 @@ function ApiCache() { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Monkeypatches the response object to add cache control headers and create a cache object. | ||||
|      * @param {Object} req - The request object. | ||||
|      * @param {Object} res - The response object. | ||||
|      * | ||||
|      * Generated by Trelent | ||||
|      */ | ||||
|     function makeResponseCacheable(req, res, next, key, duration, strDuration, toggle) { | ||||
|     // monkeypatch res.end to create cache object
 | ||||
|         res._apicache = { | ||||
|  | @ -245,6 +301,13 @@ function ApiCache() { | |||
|         next(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param {Request} request | ||||
|      * @param {Response} response | ||||
|      * @returns {boolean|undefined} true if the request should be cached, false otherwise. If undefined, defaults to true. | ||||
|      * | ||||
|      * Generated by Trelent | ||||
|      */ | ||||
|     function sendCachedResponse(request, response, cacheObject, toggle, next, duration) { | ||||
|         if (toggle && !toggle(request, response)) { | ||||
|             return next(); | ||||
|  | @ -365,6 +428,13 @@ function ApiCache() { | |||
|         return this.getIndex(); | ||||
|     }; | ||||
| 
 | ||||
|     /** | ||||
|      * Converts a duration string to an integer number of milliseconds. | ||||
|      * @param {string} duration - The string to convert. | ||||
|      * @returns {number} The converted value in milliseconds, or the defaultDuration if it can't be parsed. | ||||
|      * | ||||
|      * Generated by Trelent | ||||
|      */ | ||||
|     function parseDuration(duration, defaultDuration) { | ||||
|         if (typeof duration === "number") { | ||||
|             return duration; | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ class Apprise extends NotificationProvider { | |||
|     name = "apprise"; | ||||
| 
 | ||||
|     async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | ||||
|         let s = child_process.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL]) | ||||
|         let s = child_process.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL]); | ||||
| 
 | ||||
|         let output = (s.stdout) ? s.stdout.toString() : "ERROR: maybe apprise not found"; | ||||
| 
 | ||||
|  | @ -16,7 +16,7 @@ class Apprise extends NotificationProvider { | |||
|                 return "Sent Successfully"; | ||||
|             } | ||||
| 
 | ||||
|             throw new Error(output) | ||||
|             throw new Error(output); | ||||
|         } else { | ||||
|             return "No output from apprise"; | ||||
|         } | ||||
|  |  | |||
|  | @ -21,8 +21,7 @@ class Bark extends NotificationProvider { | |||
|     name = "Bark"; | ||||
| 
 | ||||
|     async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | ||||
|         try { | ||||
|             var barkEndpoint = notification.barkEndpoint; | ||||
|         let barkEndpoint = notification.barkEndpoint; | ||||
| 
 | ||||
|         // check if the endpoint has a "/" suffix, if so, delete it first
 | ||||
|         if (barkEndpoint.endsWith("/")) { | ||||
|  | @ -43,10 +42,6 @@ class Bark extends NotificationProvider { | |||
|             let title = "UptimeKuma Message"; | ||||
|             return await this.postNotification(title, msg, barkEndpoint); | ||||
|         } | ||||
| 
 | ||||
|         } catch (error) { | ||||
|             throw error; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // add additional parameter for better on device styles (iOS 15 optimized)
 | ||||
|  |  | |||
|  | @ -12,7 +12,7 @@ class ClickSendSMS extends NotificationProvider { | |||
|             let config = { | ||||
|                 headers: { | ||||
|                     "Content-Type": "application/json", | ||||
|                     "Authorization": "Basic " + Buffer.from(notification.clicksendsmsLogin + ":" + notification.clicksendsmsPassword).toString('base64'), | ||||
|                     "Authorization": "Basic " + Buffer.from(notification.clicksendsmsLogin + ":" + notification.clicksendsmsPassword).toString("base64"), | ||||
|                     "Accept": "text/json", | ||||
|                 } | ||||
|             }; | ||||
|  |  | |||
|  | @ -17,8 +17,8 @@ class Discord extends NotificationProvider { | |||
|                 let discordtestdata = { | ||||
|                     username: discordDisplayName, | ||||
|                     content: msg, | ||||
|                 } | ||||
|                 await axios.post(notification.discordWebhookUrl, discordtestdata) | ||||
|                 }; | ||||
|                 await axios.post(notification.discordWebhookUrl, discordtestdata); | ||||
|                 return okMsg; | ||||
|             } | ||||
| 
 | ||||
|  | @ -66,13 +66,13 @@ class Discord extends NotificationProvider { | |||
|                             }, | ||||
|                         ], | ||||
|                     }], | ||||
|                 } | ||||
|                 }; | ||||
| 
 | ||||
|                 if (notification.discordPrefixMessage) { | ||||
|                     discorddowndata.content = notification.discordPrefixMessage; | ||||
|                 } | ||||
| 
 | ||||
|                 await axios.post(notification.discordWebhookUrl, discorddowndata) | ||||
|                 await axios.post(notification.discordWebhookUrl, discorddowndata); | ||||
|                 return okMsg; | ||||
| 
 | ||||
|             } else if (heartbeatJSON["status"] == UP) { | ||||
|  | @ -101,17 +101,17 @@ class Discord extends NotificationProvider { | |||
|                             }, | ||||
|                         ], | ||||
|                     }], | ||||
|                 } | ||||
|                 }; | ||||
| 
 | ||||
|                 if (notification.discordPrefixMessage) { | ||||
|                     discordupdata.content = notification.discordPrefixMessage; | ||||
|                 } | ||||
| 
 | ||||
|                 await axios.post(notification.discordWebhookUrl, discordupdata) | ||||
|                 await axios.post(notification.discordWebhookUrl, discordupdata); | ||||
|                 return okMsg; | ||||
|             } | ||||
|         } catch (error) { | ||||
|             this.throwGeneralAxiosError(error) | ||||
|             this.throwGeneralAxiosError(error); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -13,11 +13,11 @@ class GoogleChat extends NotificationProvider { | |||
|         try { | ||||
|             // Google Chat message formatting: https://developers.google.com/chat/api/guides/message-formats/basic
 | ||||
| 
 | ||||
|             let textMsg = '' | ||||
|             let textMsg = ""; | ||||
|             if (heartbeatJSON && heartbeatJSON.status === UP) { | ||||
|                 textMsg = `✅ Application is back online\n`; | ||||
|                 textMsg = "✅ Application is back online\n"; | ||||
|             } else if (heartbeatJSON && heartbeatJSON.status === DOWN) { | ||||
|                 textMsg = `🔴 Application went down\n`; | ||||
|                 textMsg = "🔴 Application went down\n"; | ||||
|             } | ||||
| 
 | ||||
|             if (monitorJSON && monitorJSON.name) { | ||||
|  |  | |||
|  | @ -15,7 +15,7 @@ class Gotify extends NotificationProvider { | |||
|                 "message": msg, | ||||
|                 "priority": notification.gotifyPriority || 8, | ||||
|                 "title": "Uptime-Kuma", | ||||
|             }) | ||||
|             }); | ||||
| 
 | ||||
|             return okMsg; | ||||
| 
 | ||||
|  |  | |||
|  | @ -25,8 +25,8 @@ class Line extends NotificationProvider { | |||
|                             "text": "Test Successful!" | ||||
|                         } | ||||
|                     ] | ||||
|                 } | ||||
|                 await axios.post(lineAPIUrl, testMessage, config) | ||||
|                 }; | ||||
|                 await axios.post(lineAPIUrl, testMessage, config); | ||||
|             } else if (heartbeatJSON["status"] == DOWN) { | ||||
|                 let downMessage = { | ||||
|                     "to": notification.lineUserID, | ||||
|  | @ -36,8 +36,8 @@ class Line extends NotificationProvider { | |||
|                             "text": "UptimeKuma Alert: [🔴 Down]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"] | ||||
|                         } | ||||
|                     ] | ||||
|                 } | ||||
|                 await axios.post(lineAPIUrl, downMessage, config) | ||||
|                 }; | ||||
|                 await axios.post(lineAPIUrl, downMessage, config); | ||||
|             } else if (heartbeatJSON["status"] == UP) { | ||||
|                 let upMessage = { | ||||
|                     "to": notification.lineUserID, | ||||
|  | @ -47,12 +47,12 @@ class Line extends NotificationProvider { | |||
|                             "text": "UptimeKuma Alert: [✅ Up]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"] | ||||
|                         } | ||||
|                     ] | ||||
|                 } | ||||
|                 await axios.post(lineAPIUrl, upMessage, config) | ||||
|                 }; | ||||
|                 await axios.post(lineAPIUrl, upMessage, config); | ||||
|             } | ||||
|             return okMsg; | ||||
|         } catch (error) { | ||||
|             this.throwGeneralAxiosError(error) | ||||
|             this.throwGeneralAxiosError(error); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -8,15 +8,15 @@ class LunaSea extends NotificationProvider { | |||
| 
 | ||||
|     async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | ||||
|         let okMsg = "Sent Successfully."; | ||||
|         let lunaseadevice = "https://notify.lunasea.app/v1/custom/device/" + notification.lunaseaDevice | ||||
|         let lunaseadevice = "https://notify.lunasea.app/v1/custom/device/" + notification.lunaseaDevice; | ||||
| 
 | ||||
|         try { | ||||
|             if (heartbeatJSON == null) { | ||||
|                 let testdata = { | ||||
|                     "title": "Uptime Kuma Alert", | ||||
|                     "body": "Testing Successful.", | ||||
|                 } | ||||
|                 await axios.post(lunaseadevice, testdata) | ||||
|                 }; | ||||
|                 await axios.post(lunaseadevice, testdata); | ||||
|                 return okMsg; | ||||
|             } | ||||
| 
 | ||||
|  | @ -24,8 +24,8 @@ class LunaSea extends NotificationProvider { | |||
|                 let downdata = { | ||||
|                     "title": "UptimeKuma Alert: " + monitorJSON["name"], | ||||
|                     "body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], | ||||
|                 } | ||||
|                 await axios.post(lunaseadevice, downdata) | ||||
|                 }; | ||||
|                 await axios.post(lunaseadevice, downdata); | ||||
|                 return okMsg; | ||||
|             } | ||||
| 
 | ||||
|  | @ -33,13 +33,13 @@ class LunaSea extends NotificationProvider { | |||
|                 let updata = { | ||||
|                     "title": "UptimeKuma Alert: " + monitorJSON["name"], | ||||
|                     "body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], | ||||
|                 } | ||||
|                 await axios.post(lunaseadevice, updata) | ||||
|                 }; | ||||
|                 await axios.post(lunaseadevice, updata); | ||||
|                 return okMsg; | ||||
|             } | ||||
| 
 | ||||
|         } catch (error) { | ||||
|             this.throwGeneralAxiosError(error) | ||||
|             this.throwGeneralAxiosError(error); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| const NotificationProvider = require("./notification-provider"); | ||||
| const axios = require("axios"); | ||||
| const Crypto = require("crypto"); | ||||
| const { debug } = require("../../src/util"); | ||||
| const { log } = require("../../src/util"); | ||||
| 
 | ||||
| class Matrix extends NotificationProvider { | ||||
|     name = "matrix"; | ||||
|  | @ -17,11 +17,11 @@ class Matrix extends NotificationProvider { | |||
|                 .slice(0, size) | ||||
|         ); | ||||
| 
 | ||||
|         debug("Random String: " + randomString); | ||||
|         log.debug("notification", "Random String: " + randomString); | ||||
| 
 | ||||
|         const roomId = encodeURIComponent(notification.internalRoomId); | ||||
| 
 | ||||
|         debug("Matrix Room ID: " + roomId); | ||||
|         log.debug("notification", "Matrix Room ID: " + roomId); | ||||
| 
 | ||||
|         try { | ||||
|             let config = { | ||||
|  |  | |||
|  | @ -25,11 +25,11 @@ class NotificationProvider { | |||
|             if (typeof error.response.data === "string") { | ||||
|                 msg += error.response.data; | ||||
|             } else { | ||||
|                 msg += JSON.stringify(error.response.data) | ||||
|                 msg += JSON.stringify(error.response.data); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         throw new Error(msg) | ||||
|         throw new Error(msg); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -30,7 +30,7 @@ class Octopush extends NotificationProvider { | |||
|                     "purpose": "alert", | ||||
|                     "sender": notification.octopushSenderName | ||||
|                 }; | ||||
|                 await axios.post("https://api.octopush.com/v1/public/sms-campaign/send", data, config) | ||||
|                 await axios.post("https://api.octopush.com/v1/public/sms-campaign/send", data, config); | ||||
|             } else if (notification.octopushVersion == 1) { | ||||
|                 let data = { | ||||
|                     "user_login": notification.octopushDMLogin, | ||||
|  | @ -49,7 +49,7 @@ class Octopush extends NotificationProvider { | |||
|                     }, | ||||
|                     params: data | ||||
|                 }; | ||||
|                 await axios.post("https://www.octopush-dm.com/api/sms/json", {}, config) | ||||
|                 await axios.post("https://www.octopush-dm.com/api/sms/json", {}, config); | ||||
|             } else { | ||||
|                 throw new Error("Unknown Octopush version!"); | ||||
|             } | ||||
|  |  | |||
							
								
								
									
										45
									
								
								server/notification-providers/onebot.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								server/notification-providers/onebot.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,45 @@ | |||
| const NotificationProvider = require("./notification-provider"); | ||||
| const axios = require("axios"); | ||||
| 
 | ||||
| class OneBot extends NotificationProvider { | ||||
| 
 | ||||
|     name = "OneBot"; | ||||
| 
 | ||||
|     async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | ||||
|         let okMsg = "Sent Successfully."; | ||||
|         try { | ||||
|             let httpAddr = notification.httpAddr; | ||||
|             if (!httpAddr.startsWith("http")) { | ||||
|                 httpAddr = "http://" + httpAddr; | ||||
|             } | ||||
|             if (!httpAddr.endsWith("/")) { | ||||
|                 httpAddr += "/"; | ||||
|             } | ||||
|             let onebotAPIUrl = httpAddr + "send_msg"; | ||||
|             let config = { | ||||
|                 headers: { | ||||
|                     "Content-Type": "application/json", | ||||
|                     "Authorization": "Bearer " + notification.accessToken, | ||||
|                 } | ||||
|             }; | ||||
|             let pushText = "UptimeKuma Alert: " + msg; | ||||
|             let data = { | ||||
|                 "auto_escape": true, | ||||
|                 "message": pushText, | ||||
|             }; | ||||
|             if (notification.msgType == "group") { | ||||
|                 data["message_type"] = "group"; | ||||
|                 data["group_id"] = notification.recieverId; | ||||
|             } else { | ||||
|                 data["message_type"] = "private"; | ||||
|                 data["user_id"] = notification.recieverId; | ||||
|             } | ||||
|             await axios.post(onebotAPIUrl, data, config); | ||||
|             return okMsg; | ||||
|         } catch (error) { | ||||
|             this.throwGeneralAxiosError(error); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| module.exports = OneBot; | ||||
|  | @ -12,7 +12,7 @@ class PromoSMS extends NotificationProvider { | |||
|             let config = { | ||||
|                 headers: { | ||||
|                     "Content-Type": "application/json", | ||||
|                     "Authorization": "Basic " + Buffer.from(notification.promosmsLogin + ":" + notification.promosmsPassword).toString('base64'), | ||||
|                     "Authorization": "Basic " + Buffer.from(notification.promosmsLogin + ":" + notification.promosmsPassword).toString("base64"), | ||||
|                     "Accept": "text/json", | ||||
|                 } | ||||
|             }; | ||||
|  |  | |||
|  | @ -23,26 +23,26 @@ class Pushbullet extends NotificationProvider { | |||
|                     "type": "note", | ||||
|                     "title": "Uptime Kuma Alert", | ||||
|                     "body": "Testing Successful.", | ||||
|                 } | ||||
|                 await axios.post(pushbulletUrl, testdata, config) | ||||
|                 }; | ||||
|                 await axios.post(pushbulletUrl, testdata, config); | ||||
|             } else if (heartbeatJSON["status"] == DOWN) { | ||||
|                 let downdata = { | ||||
|                     "type": "note", | ||||
|                     "title": "UptimeKuma Alert: " + monitorJSON["name"], | ||||
|                     "body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], | ||||
|                 } | ||||
|                 await axios.post(pushbulletUrl, downdata, config) | ||||
|                 }; | ||||
|                 await axios.post(pushbulletUrl, downdata, config); | ||||
|             } else if (heartbeatJSON["status"] == UP) { | ||||
|                 let updata = { | ||||
|                     "type": "note", | ||||
|                     "title": "UptimeKuma Alert: " + monitorJSON["name"], | ||||
|                     "body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], | ||||
|                 } | ||||
|                 await axios.post(pushbulletUrl, updata, config) | ||||
|                 }; | ||||
|                 await axios.post(pushbulletUrl, updata, config); | ||||
|             } | ||||
|             return okMsg; | ||||
|         } catch (error) { | ||||
|             this.throwGeneralAxiosError(error) | ||||
|             this.throwGeneralAxiosError(error); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										52
									
								
								server/notification-providers/pushdeer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								server/notification-providers/pushdeer.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,52 @@ | |||
| const NotificationProvider = require("./notification-provider"); | ||||
| const axios = require("axios"); | ||||
| const { DOWN, UP } = require("../../src/util"); | ||||
| 
 | ||||
| class PushDeer extends NotificationProvider { | ||||
| 
 | ||||
|     name = "PushDeer"; | ||||
| 
 | ||||
|     async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | ||||
|         let okMsg = "Sent Successfully."; | ||||
|         let pushdeerlink = "https://api2.pushdeer.com/message/push"; | ||||
| 
 | ||||
|         let valid = msg != null && monitorJSON != null && heartbeatJSON != null; | ||||
| 
 | ||||
|         let title; | ||||
|         if (valid && heartbeatJSON.status == UP) { | ||||
|             title = "## Uptime Kuma: " + monitorJSON.name + " up"; | ||||
|         } else if (valid && heartbeatJSON.status == DOWN) { | ||||
|             title = "## Uptime Kuma: " + monitorJSON.name + " down"; | ||||
|         } else { | ||||
|             title = "## Uptime Kuma Message"; | ||||
|         } | ||||
| 
 | ||||
|         let data = { | ||||
|             "pushkey": notification.pushdeerKey, | ||||
|             "text": title, | ||||
|             "desp": msg.replace(/\n/g, "\n\n"), | ||||
|             "type": "markdown", | ||||
|         }; | ||||
| 
 | ||||
|         try { | ||||
|             let res = await axios.post(pushdeerlink, data); | ||||
| 
 | ||||
|             if ("error" in res.data) { | ||||
|                 let error = res.data.error; | ||||
|                 this.throwGeneralAxiosError(error); | ||||
|             } | ||||
|             if (res.data.content.result.length === 0) { | ||||
|                 let error = "Invalid PushDeer key"; | ||||
|                 this.throwGeneralAxiosError(error); | ||||
|             } else if (JSON.parse(res.data.content.result[0]).success != "ok") { | ||||
|                 let error = "Unknown error"; | ||||
|                 this.throwGeneralAxiosError(error); | ||||
|             } | ||||
|             return okMsg; | ||||
|         } catch (error) { | ||||
|             this.throwGeneralAxiosError(error); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| module.exports = PushDeer; | ||||
|  | @ -19,10 +19,10 @@ class Pushy extends NotificationProvider { | |||
|                     "badge": 1, | ||||
|                     "sound": "ping.aiff" | ||||
|                 } | ||||
|             }) | ||||
|             }); | ||||
|             return okMsg; | ||||
|         } catch (error) { | ||||
|             this.throwGeneralAxiosError(error) | ||||
|             this.throwGeneralAxiosError(error); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ const NotificationProvider = require("./notification-provider"); | |||
| const axios = require("axios"); | ||||
| const Slack = require("./slack"); | ||||
| const { setting } = require("../util-server"); | ||||
| const { getMonitorRelativeURL, UP, DOWN } = require("../../src/util"); | ||||
| const { getMonitorRelativeURL, DOWN } = require("../../src/util"); | ||||
| 
 | ||||
| class RocketChat extends NotificationProvider { | ||||
| 
 | ||||
|  |  | |||
|  | @ -16,10 +16,10 @@ class Signal extends NotificationProvider { | |||
|             }; | ||||
|             let config = {}; | ||||
| 
 | ||||
|             await axios.post(notification.signalURL, data, config) | ||||
|             await axios.post(notification.signalURL, data, config); | ||||
|             return okMsg; | ||||
|         } catch (error) { | ||||
|             this.throwGeneralAxiosError(error) | ||||
|             this.throwGeneralAxiosError(error); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -12,10 +12,10 @@ class TechulusPush extends NotificationProvider { | |||
|             await axios.post(`https://push.techulus.com/api/v1/notify/${notification.pushAPIKey}`, { | ||||
|                 "title": "Uptime-Kuma", | ||||
|                 "body": msg, | ||||
|             }) | ||||
|             }); | ||||
|             return okMsg; | ||||
|         } catch (error) { | ||||
|             this.throwGeneralAxiosError(error) | ||||
|             this.throwGeneralAxiosError(error); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -14,12 +14,12 @@ class Telegram extends NotificationProvider { | |||
|                     chat_id: notification.telegramChatID, | ||||
|                     text: msg, | ||||
|                 }, | ||||
|             }) | ||||
|             }); | ||||
|             return okMsg; | ||||
| 
 | ||||
|         } catch (error) { | ||||
|             let msg = (error.response.data.description) ? error.response.data.description : "Error without description" | ||||
|             throw new Error(msg) | ||||
|             let msg = (error.response.data.description) ? error.response.data.description : "Error without description"; | ||||
|             throw new Error(msg); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -24,17 +24,17 @@ class Webhook extends NotificationProvider { | |||
| 
 | ||||
|                 config = { | ||||
|                     headers: finalData.getHeaders(), | ||||
|                 } | ||||
|                 }; | ||||
| 
 | ||||
|             } else { | ||||
|                 finalData = data; | ||||
|             } | ||||
| 
 | ||||
|             await axios.post(notification.webhookURL, finalData, config) | ||||
|             await axios.post(notification.webhookURL, finalData, config); | ||||
|             return okMsg; | ||||
| 
 | ||||
|         } catch (error) { | ||||
|             this.throwGeneralAxiosError(error) | ||||
|             this.throwGeneralAxiosError(error); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ class WeCom extends NotificationProvider { | |||
| 
 | ||||
|     composeMessage(heartbeatJSON, msg) { | ||||
|         let title; | ||||
|         if (msg != null && heartbeatJSON != null && heartbeatJSON['status'] == UP) { | ||||
|         if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] == UP) { | ||||
|             title = "UptimeKuma Monitor Up"; | ||||
|         } | ||||
|         if (msg != null && heartbeatJSON != null && heartbeatJSON["status"] == DOWN) { | ||||
|  |  | |||
|  | @ -24,19 +24,22 @@ const Feishu = require("./notification-providers/feishu"); | |||
| const AliyunSms = require("./notification-providers/aliyun-sms"); | ||||
| const DingDing = require("./notification-providers/dingding"); | ||||
| const Bark = require("./notification-providers/bark"); | ||||
| const { log } = require("../src/util"); | ||||
| const SerwerSMS = require("./notification-providers/serwersms"); | ||||
| const Stackfield = require("./notification-providers/stackfield"); | ||||
| const WeCom = require("./notification-providers/wecom"); | ||||
| const GoogleChat = require("./notification-providers/google-chat"); | ||||
| const Gorush = require("./notification-providers/gorush"); | ||||
| const Alerta = require("./notification-providers/alerta"); | ||||
| const OneBot = require("./notification-providers/onebot"); | ||||
| const PushDeer = require("./notification-providers/pushdeer"); | ||||
| 
 | ||||
| class Notification { | ||||
| 
 | ||||
|     providerList = {}; | ||||
| 
 | ||||
|     static init() { | ||||
|         console.log("Prepare Notification Providers"); | ||||
|         log.info("notification", "Prepare Notification Providers"); | ||||
| 
 | ||||
|         this.providerList = {}; | ||||
| 
 | ||||
|  | @ -72,6 +75,8 @@ class Notification { | |||
|             new GoogleChat(), | ||||
|             new Gorush(), | ||||
|             new Alerta(), | ||||
|             new OneBot(), | ||||
|             new PushDeer(), | ||||
|         ]; | ||||
| 
 | ||||
|         for (let item of list) { | ||||
|  | @ -104,27 +109,27 @@ class Notification { | |||
|     } | ||||
| 
 | ||||
|     static async save(notification, notificationID, userID) { | ||||
|         let bean | ||||
|         let bean; | ||||
| 
 | ||||
|         if (notificationID) { | ||||
|             bean = await R.findOne("notification", " id = ? AND user_id = ? ", [ | ||||
|                 notificationID, | ||||
|                 userID, | ||||
|             ]) | ||||
|             ]); | ||||
| 
 | ||||
|             if (! bean) { | ||||
|                 throw new Error("notification not found") | ||||
|                 throw new Error("notification not found"); | ||||
|             } | ||||
| 
 | ||||
|         } else { | ||||
|             bean = R.dispense("notification") | ||||
|             bean = R.dispense("notification"); | ||||
|         } | ||||
| 
 | ||||
|         bean.name = notification.name; | ||||
|         bean.user_id = userID; | ||||
|         bean.config = JSON.stringify(notification); | ||||
|         bean.is_default = notification.isDefault || false; | ||||
|         await R.store(bean) | ||||
|         await R.store(bean); | ||||
| 
 | ||||
|         if (notification.applyExisting) { | ||||
|             await applyNotificationEveryMonitor(bean.id, userID); | ||||
|  | @ -137,13 +142,13 @@ class Notification { | |||
|         let bean = await R.findOne("notification", " id = ? AND user_id = ? ", [ | ||||
|             notificationID, | ||||
|             userID, | ||||
|         ]) | ||||
|         ]); | ||||
| 
 | ||||
|         if (! bean) { | ||||
|             throw new Error("notification not found") | ||||
|             throw new Error("notification not found"); | ||||
|         } | ||||
| 
 | ||||
|         await R.trash(bean) | ||||
|         await R.trash(bean); | ||||
|     } | ||||
| 
 | ||||
|     static checkApprise() { | ||||
|  | @ -154,6 +159,13 @@ class Notification { | |||
| 
 | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Adds a new monitor to the database. | ||||
|  * @param {number} userID The ID of the user that owns this monitor. | ||||
|  * @param {string} name The name of this monitor. | ||||
|  * | ||||
|  * Generated by Trelent | ||||
|  */ | ||||
| async function applyNotificationEveryMonitor(notificationID, userID) { | ||||
|     let monitors = await R.getAll("SELECT id FROM monitor WHERE user_id = ?", [ | ||||
|         userID | ||||
|  | @ -163,17 +175,17 @@ async function applyNotificationEveryMonitor(notificationID, userID) { | |||
|         let checkNotification = await R.findOne("monitor_notification", " monitor_id = ? AND notification_id = ? ", [ | ||||
|             monitors[i].id, | ||||
|             notificationID, | ||||
|         ]) | ||||
|         ]); | ||||
| 
 | ||||
|         if (! checkNotification) { | ||||
|             let relation = R.dispense("monitor_notification"); | ||||
|             relation.monitor_id = monitors[i].id; | ||||
|             relation.notification_id = notificationID; | ||||
|             await R.store(relation) | ||||
|             await R.store(relation); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| module.exports = { | ||||
|     Notification, | ||||
| } | ||||
| }; | ||||
|  |  | |||
|  | @ -4,20 +4,20 @@ const saltRounds = 10; | |||
| 
 | ||||
| exports.generate = function (password) { | ||||
|     return bcrypt.hashSync(password, saltRounds); | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| exports.verify = function (password, hash) { | ||||
|     if (isSHA1(hash)) { | ||||
|         return passwordHashOld.verify(password, hash) | ||||
|         return passwordHashOld.verify(password, hash); | ||||
|     } | ||||
| 
 | ||||
|     return bcrypt.compareSync(password, hash); | ||||
| } | ||||
| }; | ||||
| 
 | ||||
| function isSHA1(hash) { | ||||
|     return (typeof hash === "string" && hash.startsWith("sha1")) | ||||
|     return (typeof hash === "string" && hash.startsWith("sha1")); | ||||
| } | ||||
| 
 | ||||
| exports.needRehash = function (hash) { | ||||
|     return isSHA1(hash); | ||||
| } | ||||
| }; | ||||
|  |  | |||
|  | @ -8,6 +8,13 @@ const util = require("./util-server"); | |||
| 
 | ||||
| module.exports = Ping; | ||||
| 
 | ||||
| /** | ||||
|  * @param {string} host - The host to ping | ||||
|  * @param {object} [options] - Options for the ping command | ||||
|  * @param {array|string} [options.args] - Arguments to pass to the ping command | ||||
|  * | ||||
|  * Generated by Trelent | ||||
|  */ | ||||
| function Ping(host, options) { | ||||
|     if (!host) { | ||||
|         throw new Error("You must specify a host to ping!"); | ||||
|  | @ -125,6 +132,11 @@ Ping.prototype.send = function (callback) { | |||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     /** | ||||
|      * @param {Function} callback | ||||
|      * | ||||
|      * Generated by Trelent | ||||
|      */ | ||||
|     function onEnd() { | ||||
|         let stdout = this.stdout._stdout; | ||||
|         let stderr = this.stderr._stderr; | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| const PrometheusClient = require("prom-client"); | ||||
| const { log } = require("../src/util"); | ||||
| 
 | ||||
| const commonLabels = [ | ||||
|     "monitor_name", | ||||
|  | @ -48,15 +49,16 @@ class Prometheus { | |||
| 
 | ||||
|         if (typeof tlsInfo !== "undefined") { | ||||
|             try { | ||||
|                 let is_valid = 0; | ||||
|                 let isValid = 0; | ||||
|                 if (tlsInfo.valid == true) { | ||||
|                     is_valid = 1; | ||||
|                     isValid = 1; | ||||
|                 } else { | ||||
|                     is_valid = 0; | ||||
|                     isValid = 0; | ||||
|                 } | ||||
|                 monitor_cert_is_valid.set(this.monitorLabelValues, is_valid); | ||||
|                 monitor_cert_is_valid.set(this.monitorLabelValues, isValid); | ||||
|             } catch (e) { | ||||
|                 console.error(e); | ||||
|                 log.error("prometheus", "Caught error"); | ||||
|                 log.error("prometheus", e); | ||||
|             } | ||||
| 
 | ||||
|             try { | ||||
|  | @ -64,14 +66,16 @@ class Prometheus { | |||
|                     monitor_cert_days_remaining.set(this.monitorLabelValues, tlsInfo.certInfo.daysRemaining); | ||||
|                 } | ||||
|             } catch (e) { | ||||
|                 console.error(e); | ||||
|                 log.error("prometheus", "Caught error"); | ||||
|                 log.error("prometheus", e); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             monitor_status.set(this.monitorLabelValues, heartbeat.status); | ||||
|         } catch (e) { | ||||
|             console.error(e); | ||||
|             log.error("prometheus", "Caught error"); | ||||
|             log.error("prometheus", e); | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|  | @ -82,7 +86,8 @@ class Prometheus { | |||
|                 monitor_response_time.set(this.monitorLabelValues, -1); | ||||
|             } | ||||
|         } catch (e) { | ||||
|             console.error(e); | ||||
|             log.error("prometheus", "Caught error"); | ||||
|             log.error("prometheus", e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| const { RateLimiter } = require("limiter"); | ||||
| const { debug } = require("../src/util"); | ||||
| const { log } = require("../src/util"); | ||||
| 
 | ||||
| class KumaRateLimiter { | ||||
|     constructor(config) { | ||||
|  | @ -9,7 +9,7 @@ class KumaRateLimiter { | |||
| 
 | ||||
|     async pass(callback, num = 1) { | ||||
|         const remainingRequests = await this.removeTokens(num); | ||||
|         debug("Rate Limit (remainingRequests):" + remainingRequests); | ||||
|         log.info("rate-limit", "remaining requests: " + remainingRequests); | ||||
|         if (remainingRequests < 0) { | ||||
|             if (callback) { | ||||
|                 callback({ | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ const server = require("../server"); | |||
| const apicache = require("../modules/apicache"); | ||||
| const Monitor = require("../model/monitor"); | ||||
| const dayjs = require("dayjs"); | ||||
| const { UP, flipStatus, debug } = require("../../src/util"); | ||||
| const { UP, flipStatus, log } = require("../../src/util"); | ||||
| const StatusPage = require("../model/status_page"); | ||||
| let router = express.Router(); | ||||
| 
 | ||||
|  | @ -62,8 +62,8 @@ router.get("/api/push/:pushToken", async (request, response) => { | |||
|             duration = dayjs(bean.time).diff(dayjs(previousHeartbeat.time), "second"); | ||||
|         } | ||||
| 
 | ||||
|         debug("PreviousStatus: " + previousStatus); | ||||
|         debug("Current Status: " + status); | ||||
|         log.debug("router", "PreviousStatus: " + previousStatus); | ||||
|         log.debug("router", "Current Status: " + status); | ||||
| 
 | ||||
|         bean.important = Monitor.isImportantBeat(isFirstBeat, previousStatus, status); | ||||
|         bean.monitor_id = monitor.id; | ||||
|  | @ -124,7 +124,7 @@ router.get("/api/status-page/:slug", cache("5 minutes"), async (request, respons | |||
|         // Public Group List
 | ||||
|         const publicGroupList = []; | ||||
|         const showTags = !!statusPage.show_tags; | ||||
|         debug("Show Tags???" + showTags); | ||||
| 
 | ||||
|         const list = await R.find("group", " public = 1 AND status_page_id = ? ORDER BY weight ", [ | ||||
|             statusPage.id | ||||
|         ]); | ||||
|  |  | |||
							
								
								
									
										240
									
								
								server/server.js
									
									
									
									
									
								
							
							
						
						
									
										240
									
								
								server/server.js
									
									
									
									
									
								
							|  | @ -11,40 +11,42 @@ if (nodeVersion < requiredVersion) { | |||
| } | ||||
| 
 | ||||
| const args = require("args-parser")(process.argv); | ||||
| const { sleep, debug, getRandomInt, genSecret } = require("../src/util"); | ||||
| const { sleep, log, getRandomInt, genSecret, debug } = require("../src/util"); | ||||
| const config = require("./config"); | ||||
| 
 | ||||
| debug(args); | ||||
| log.info("server", "Welcome to Uptime Kuma"); | ||||
| log.debug("server", "Arguments"); | ||||
| log.debug("server", args); | ||||
| 
 | ||||
| if (! process.env.NODE_ENV) { | ||||
|     process.env.NODE_ENV = "production"; | ||||
| } | ||||
| 
 | ||||
| console.log("Node Env: " + process.env.NODE_ENV); | ||||
| log.info("server", "Node Env: " + process.env.NODE_ENV); | ||||
| 
 | ||||
| console.log("Importing Node libraries"); | ||||
| log.info("server", "Importing Node libraries"); | ||||
| const fs = require("fs"); | ||||
| const http = require("http"); | ||||
| const https = require("https"); | ||||
| 
 | ||||
| console.log("Importing 3rd-party libraries"); | ||||
| debug("Importing express"); | ||||
| log.info("server", "Importing 3rd-party libraries"); | ||||
| log.debug("server", "Importing express"); | ||||
| const express = require("express"); | ||||
| debug("Importing socket.io"); | ||||
| log.debug("server", "Importing socket.io"); | ||||
| const { Server } = require("socket.io"); | ||||
| debug("Importing redbean-node"); | ||||
| log.debug("server", "Importing redbean-node"); | ||||
| const { R } = require("redbean-node"); | ||||
| debug("Importing jsonwebtoken"); | ||||
| log.debug("server", "Importing jsonwebtoken"); | ||||
| const jwt = require("jsonwebtoken"); | ||||
| debug("Importing http-graceful-shutdown"); | ||||
| log.debug("server", "Importing http-graceful-shutdown"); | ||||
| const gracefulShutdown = require("http-graceful-shutdown"); | ||||
| debug("Importing prometheus-api-metrics"); | ||||
| log.debug("server", "Importing prometheus-api-metrics"); | ||||
| const prometheusAPIMetrics = require("prometheus-api-metrics"); | ||||
| debug("Importing compare-versions"); | ||||
| log.debug("server", "Importing compare-versions"); | ||||
| const compareVersions = require("compare-versions"); | ||||
| const { passwordStrength } = require("check-password-strength"); | ||||
| 
 | ||||
| debug("Importing 2FA Modules"); | ||||
| log.debug("server", "Importing 2FA Modules"); | ||||
| const notp = require("notp"); | ||||
| const base32 = require("thirty-two"); | ||||
| 
 | ||||
|  | @ -69,23 +71,23 @@ class UptimeKumaServer { | |||
| 
 | ||||
| const server = module.exports = new UptimeKumaServer(); | ||||
| 
 | ||||
| console.log("Importing this project modules"); | ||||
| debug("Importing Monitor"); | ||||
| log.info("server", "Importing this project modules"); | ||||
| log.debug("server", "Importing Monitor"); | ||||
| const Monitor = require("./model/monitor"); | ||||
| debug("Importing Settings"); | ||||
| log.debug("server", "Importing Settings"); | ||||
| const { getSettings, setSettings, setting, initJWTSecret, checkLogin, startUnitTest, FBSD, errorLog, doubleCheckPassword } = require("./util-server"); | ||||
| 
 | ||||
| debug("Importing Notification"); | ||||
| log.debug("server", "Importing Notification"); | ||||
| const { Notification } = require("./notification"); | ||||
| Notification.init(); | ||||
| 
 | ||||
| debug("Importing Proxy"); | ||||
| log.debug("server", "Importing Proxy"); | ||||
| const { Proxy } = require("./proxy"); | ||||
| 
 | ||||
| debug("Importing Database"); | ||||
| log.debug("server", "Importing Database"); | ||||
| const Database = require("./database"); | ||||
| 
 | ||||
| debug("Importing Background Jobs"); | ||||
| log.debug("server", "Importing Background Jobs"); | ||||
| const { initBackgroundJobs, stopBackgroundJobs } = require("./jobs"); | ||||
| const { loginRateLimiter, twoFaRateLimiter } = require("./rate-limiter"); | ||||
| 
 | ||||
|  | @ -94,27 +96,26 @@ const { login } = require("./auth"); | |||
| const passwordHash = require("./password-hash"); | ||||
| 
 | ||||
| const checkVersion = require("./check-version"); | ||||
| console.info("Version: " + checkVersion.version); | ||||
| log.info("server", "Version: " + checkVersion.version); | ||||
| 
 | ||||
| // If host is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available and the unspecified IPv4 address (0.0.0.0) otherwise.
 | ||||
| // Dual-stack support for (::)
 | ||||
| let hostname = process.env.UPTIME_KUMA_HOST || args.host; | ||||
| 
 | ||||
| // Also read HOST if not FreeBSD, as HOST is a system environment variable in FreeBSD
 | ||||
| if (!hostname && !FBSD) { | ||||
|     hostname = process.env.HOST; | ||||
| } | ||||
| let hostEnv = FBSD ? null : process.env.HOST; | ||||
| let hostname = args.host || process.env.UPTIME_KUMA_HOST || hostEnv; | ||||
| 
 | ||||
| if (hostname) { | ||||
|     console.log("Custom hostname: " + hostname); | ||||
|     log.info("server", "Custom hostname: " + hostname); | ||||
| } | ||||
| 
 | ||||
| const port = parseInt(process.env.UPTIME_KUMA_PORT || process.env.PORT || args.port || 3001); | ||||
| const port = [args.port, process.env.UPTIME_KUMA_PORT, process.env.PORT, 3001] | ||||
|     .map(portValue => parseInt(portValue)) | ||||
|     .find(portValue => !isNaN(portValue)); | ||||
| 
 | ||||
| // SSL
 | ||||
| const sslKey = process.env.UPTIME_KUMA_SSL_KEY || process.env.SSL_KEY || args["ssl-key"] || undefined; | ||||
| const sslCert = process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || args["ssl-cert"] || undefined; | ||||
| const disableFrameSameOrigin = !!process.env.UPTIME_KUMA_DISABLE_FRAME_SAMEORIGIN || args["disable-frame-sameorigin"] || false; | ||||
| const sslKey = args["ssl-key"] || process.env.UPTIME_KUMA_SSL_KEY || process.env.SSL_KEY || undefined; | ||||
| const sslCert = args["ssl-cert"] || process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || undefined; | ||||
| const disableFrameSameOrigin = args["disable-frame-sameorigin"] || !!process.env.UPTIME_KUMA_DISABLE_FRAME_SAMEORIGIN || false; | ||||
| const cloudflaredToken = args["cloudflared-token"] || process.env.UPTIME_KUMA_CLOUDFLARED_TOKEN || undefined; | ||||
| 
 | ||||
| // 2FA / notp verification defaults
 | ||||
|  | @ -130,22 +131,22 @@ const twofa_verification_opts = { | |||
| const testMode = !!args["test"] || false; | ||||
| 
 | ||||
| if (config.demoMode) { | ||||
|     console.log("==== Demo Mode ===="); | ||||
|     log.info("server", "==== Demo Mode ===="); | ||||
| } | ||||
| 
 | ||||
| console.log("Creating express and socket.io instance"); | ||||
| log.info("server", "Creating express and socket.io instance"); | ||||
| const app = express(); | ||||
| 
 | ||||
| let httpServer; | ||||
| 
 | ||||
| if (sslKey && sslCert) { | ||||
|     console.log("Server Type: HTTPS"); | ||||
|     log.info("server", "Server Type: HTTPS"); | ||||
|     httpServer = https.createServer({ | ||||
|         key: fs.readFileSync(sslKey), | ||||
|         cert: fs.readFileSync(sslCert) | ||||
|     }, app); | ||||
| } else { | ||||
|     console.log("Server Type: HTTP"); | ||||
|     log.info("server", "Server Type: HTTP"); | ||||
|     httpServer = http.createServer(app); | ||||
| } | ||||
| 
 | ||||
|  | @ -201,7 +202,7 @@ try { | |||
| } catch (e) { | ||||
|     // "dist/index.html" is not necessary for development
 | ||||
|     if (process.env.NODE_ENV !== "development") { | ||||
|         console.error("Error: Cannot find 'dist/index.html', did you install correctly?"); | ||||
|         log.error("server", "Error: Cannot find 'dist/index.html', did you install correctly?"); | ||||
|         process.exit(1); | ||||
|     } | ||||
| } | ||||
|  | @ -213,7 +214,7 @@ try { | |||
|     exports.entryPage = await setting("entryPage"); | ||||
|     await StatusPage.loadDomainMappingList(); | ||||
| 
 | ||||
|     console.log("Adding route"); | ||||
|     log.info("server", "Adding route"); | ||||
| 
 | ||||
|     // ***************************
 | ||||
|     // Normal Router here
 | ||||
|  | @ -271,7 +272,7 @@ try { | |||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     console.log("Adding socket handler"); | ||||
|     log.info("server", "Adding socket handler"); | ||||
|     io.on("connection", async (socket) => { | ||||
| 
 | ||||
|         sendInfo(socket); | ||||
|  | @ -279,7 +280,7 @@ try { | |||
|         totalClient++; | ||||
| 
 | ||||
|         if (needSetup) { | ||||
|             console.log("Redirect to setup page"); | ||||
|             log.info("server", "Redirect to setup page"); | ||||
|             socket.emit("setup"); | ||||
|         } | ||||
| 
 | ||||
|  | @ -292,33 +293,40 @@ try { | |||
|         // ***************************
 | ||||
| 
 | ||||
|         socket.on("loginByToken", async (token, callback) => { | ||||
|             log.info("auth", `Login by token. IP=${getClientIp(socket)}`); | ||||
| 
 | ||||
|             try { | ||||
|                 let decoded = jwt.verify(token, jwtSecret); | ||||
| 
 | ||||
|                 console.log("Username from JWT: " + decoded.username); | ||||
|                 log.info("auth", "Username from JWT: " + decoded.username); | ||||
| 
 | ||||
|                 let user = await R.findOne("user", " username = ? AND active = 1 ", [ | ||||
|                     decoded.username, | ||||
|                 ]); | ||||
| 
 | ||||
|                 if (user) { | ||||
|                     debug("afterLogin"); | ||||
| 
 | ||||
|                     log.debug("auth", "afterLogin"); | ||||
|                     afterLogin(socket, user); | ||||
|                     log.debug("auth", "afterLogin ok"); | ||||
| 
 | ||||
|                     debug("afterLogin ok"); | ||||
|                     log.info("auth", `Successfully logged in user ${decoded.username}. IP=${getClientIp(socket)}`); | ||||
| 
 | ||||
|                     callback({ | ||||
|                         ok: true, | ||||
|                     }); | ||||
|                 } else { | ||||
| 
 | ||||
|                     log.info("auth", `Inactive or deleted user ${decoded.username}. IP=${getClientIp(socket)}`); | ||||
| 
 | ||||
|                     callback({ | ||||
|                         ok: false, | ||||
|                         msg: "The user is inactive or deleted.", | ||||
|                     }); | ||||
|                 } | ||||
|             } catch (error) { | ||||
| 
 | ||||
|                 log.error("auth", `Invalid token. IP=${getClientIp(socket)}`); | ||||
| 
 | ||||
|                 callback({ | ||||
|                     ok: false, | ||||
|                     msg: "Invalid token.", | ||||
|  | @ -328,7 +336,7 @@ try { | |||
|         }); | ||||
| 
 | ||||
|         socket.on("login", async (data, callback) => { | ||||
|             console.log("Login"); | ||||
|             log.info("auth", `Login by username + password. IP=${getClientIp(socket)}`); | ||||
| 
 | ||||
|             // Checking
 | ||||
|             if (typeof callback !== "function") { | ||||
|  | @ -341,6 +349,7 @@ try { | |||
| 
 | ||||
|             // Login Rate Limit
 | ||||
|             if (! await loginRateLimiter.pass(callback)) { | ||||
|                 log.info("auth", `Too many failed requests for user ${data.username}. IP=${getClientIp(socket)}`); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|  | @ -349,6 +358,9 @@ try { | |||
|             if (user) { | ||||
|                 if (user.twofa_status == 0) { | ||||
|                     afterLogin(socket, user); | ||||
| 
 | ||||
|                     log.info("auth", `Successfully logged in user ${data.username}. IP=${getClientIp(socket)}`); | ||||
| 
 | ||||
|                     callback({ | ||||
|                         ok: true, | ||||
|                         token: jwt.sign({ | ||||
|  | @ -358,6 +370,9 @@ try { | |||
|                 } | ||||
| 
 | ||||
|                 if (user.twofa_status == 1 && !data.token) { | ||||
| 
 | ||||
|                     log.info("auth", `2FA token required for user ${data.username}. IP=${getClientIp(socket)}`); | ||||
| 
 | ||||
|                     callback({ | ||||
|                         tokenRequired: true, | ||||
|                     }); | ||||
|  | @ -374,6 +389,8 @@ try { | |||
|                             socket.userID, | ||||
|                         ]); | ||||
| 
 | ||||
|                         log.info("auth", `Successfully logged in user ${data.username}. IP=${getClientIp(socket)}`); | ||||
| 
 | ||||
|                         callback({ | ||||
|                             ok: true, | ||||
|                             token: jwt.sign({ | ||||
|  | @ -381,6 +398,9 @@ try { | |||
|                             }, jwtSecret), | ||||
|                         }); | ||||
|                     } else { | ||||
| 
 | ||||
|                         log.warn("auth", `Invalid token provided for user ${data.username}. IP=${getClientIp(socket)}`); | ||||
| 
 | ||||
|                         callback({ | ||||
|                             ok: false, | ||||
|                             msg: "Invalid Token!", | ||||
|  | @ -388,6 +408,9 @@ try { | |||
|                     } | ||||
|                 } | ||||
|             } else { | ||||
| 
 | ||||
|                 log.warn("auth", `Incorrect username or password for user ${data.username}. IP=${getClientIp(socket)}`); | ||||
| 
 | ||||
|                 callback({ | ||||
|                     ok: false, | ||||
|                     msg: "Incorrect username or password.", | ||||
|  | @ -470,11 +493,16 @@ try { | |||
|                     socket.userID, | ||||
|                 ]); | ||||
| 
 | ||||
|                 log.info("auth", `Saved 2FA token. IP=${getClientIp(socket)}`); | ||||
| 
 | ||||
|                 callback({ | ||||
|                     ok: true, | ||||
|                     msg: "2FA Enabled.", | ||||
|                 }); | ||||
|             } catch (error) { | ||||
| 
 | ||||
|                 log.error("auth", `Error changing 2FA token. IP=${getClientIp(socket)}`); | ||||
| 
 | ||||
|                 callback({ | ||||
|                     ok: false, | ||||
|                     msg: error.message, | ||||
|  | @ -492,11 +520,16 @@ try { | |||
|                 await doubleCheckPassword(socket, currentPassword); | ||||
|                 await TwoFA.disable2FA(socket.userID); | ||||
| 
 | ||||
|                 log.info("auth", `Disabled 2FA token. IP=${getClientIp(socket)}`); | ||||
| 
 | ||||
|                 callback({ | ||||
|                     ok: true, | ||||
|                     msg: "2FA Disabled.", | ||||
|                 }); | ||||
|             } catch (error) { | ||||
| 
 | ||||
|                 log.error("auth", `Error disabling 2FA token. IP=${getClientIp(socket)}`); | ||||
| 
 | ||||
|                 callback({ | ||||
|                     ok: false, | ||||
|                     msg: error.message, | ||||
|  | @ -623,6 +656,8 @@ try { | |||
|                 await server.sendMonitorList(socket); | ||||
|                 await startMonitor(socket.userID, bean.id); | ||||
| 
 | ||||
|                 log.info("monitor", `Added Monitor: ${monitor.id} User ID: ${socket.userID}`); | ||||
| 
 | ||||
|                 callback({ | ||||
|                     ok: true, | ||||
|                     msg: "Added Successfully.", | ||||
|  | @ -630,6 +665,9 @@ try { | |||
|                 }); | ||||
| 
 | ||||
|             } catch (e) { | ||||
| 
 | ||||
|                 log.error("monitor", `Error adding Monitor: ${monitor.id} User ID: ${socket.userID}`); | ||||
| 
 | ||||
|                 callback({ | ||||
|                     ok: false, | ||||
|                     msg: e.message, | ||||
|  | @ -692,7 +730,7 @@ try { | |||
|                 }); | ||||
| 
 | ||||
|             } catch (e) { | ||||
|                 console.error(e); | ||||
|                 log.error("monitor", e); | ||||
|                 callback({ | ||||
|                     ok: false, | ||||
|                     msg: e.message, | ||||
|  | @ -708,7 +746,7 @@ try { | |||
|                     ok: true, | ||||
|                 }); | ||||
|             } catch (e) { | ||||
|                 console.error(e); | ||||
|                 log.error("monitor", e); | ||||
|                 callback({ | ||||
|                     ok: false, | ||||
|                     msg: e.message, | ||||
|  | @ -720,7 +758,7 @@ try { | |||
|             try { | ||||
|                 checkLogin(socket); | ||||
| 
 | ||||
|                 console.log(`Get Monitor: ${monitorID} User ID: ${socket.userID}`); | ||||
|                 log.info("monitor", `Get Monitor: ${monitorID} User ID: ${socket.userID}`); | ||||
| 
 | ||||
|                 let bean = await R.findOne("monitor", " id = ? AND user_id = ? ", [ | ||||
|                     monitorID, | ||||
|  | @ -744,7 +782,7 @@ try { | |||
|             try { | ||||
|                 checkLogin(socket); | ||||
| 
 | ||||
|                 console.log(`Get Monitor Beats: ${monitorID} User ID: ${socket.userID}`); | ||||
|                 log.info("monitor", `Get Monitor Beats: ${monitorID} User ID: ${socket.userID}`); | ||||
| 
 | ||||
|                 if (period == null) { | ||||
|                     throw new Error("Invalid period."); | ||||
|  | @ -815,7 +853,7 @@ try { | |||
|             try { | ||||
|                 checkLogin(socket); | ||||
| 
 | ||||
|                 console.log(`Delete Monitor: ${monitorID} User ID: ${socket.userID}`); | ||||
|                 log.info("manage", `Delete Monitor: ${monitorID} User ID: ${socket.userID}`); | ||||
| 
 | ||||
|                 if (monitorID in server.monitorList) { | ||||
|                     server.monitorList[monitorID].stop(); | ||||
|  | @ -1147,7 +1185,7 @@ try { | |||
| 
 | ||||
|                 let backupData = JSON.parse(uploadedJSON); | ||||
| 
 | ||||
|                 console.log(`Importing Backup, User ID: ${socket.userID}, Version: ${backupData.version}`); | ||||
|                 log.info("manage", `Importing Backup, User ID: ${socket.userID}, Version: ${backupData.version}`); | ||||
| 
 | ||||
|                 let notificationListData = backupData.notificationList; | ||||
|                 let proxyListData = backupData.proxyList; | ||||
|  | @ -1190,7 +1228,7 @@ try { | |||
|                 } | ||||
| 
 | ||||
|                 // Only starts importing if the backup file contains at least one proxy
 | ||||
|                 if (proxyListData.length >= 1) { | ||||
|                 if (proxyListData && proxyListData.length >= 1) { | ||||
|                     const proxies = await R.findAll("proxy"); | ||||
| 
 | ||||
|                     // Loop over proxy list and save proxies
 | ||||
|  | @ -1342,7 +1380,7 @@ try { | |||
|             try { | ||||
|                 checkLogin(socket); | ||||
| 
 | ||||
|                 console.log(`Clear Events Monitor: ${monitorID} User ID: ${socket.userID}`); | ||||
|                 log.info("manage", `Clear Events Monitor: ${monitorID} User ID: ${socket.userID}`); | ||||
| 
 | ||||
|                 await R.exec("UPDATE heartbeat SET msg = ?, important = ? WHERE monitor_id = ? ", [ | ||||
|                     "", | ||||
|  | @ -1368,7 +1406,7 @@ try { | |||
|             try { | ||||
|                 checkLogin(socket); | ||||
| 
 | ||||
|                 console.log(`Clear Heartbeats Monitor: ${monitorID} User ID: ${socket.userID}`); | ||||
|                 log.info("manage", `Clear Heartbeats Monitor: ${monitorID} User ID: ${socket.userID}`); | ||||
| 
 | ||||
|                 await R.exec("DELETE FROM heartbeat WHERE monitor_id = ?", [ | ||||
|                     monitorID | ||||
|  | @ -1392,7 +1430,7 @@ try { | |||
|             try { | ||||
|                 checkLogin(socket); | ||||
| 
 | ||||
|                 console.log(`Clear Statistics User ID: ${socket.userID}`); | ||||
|                 log.info("manage", `Clear Statistics User ID: ${socket.userID}`); | ||||
| 
 | ||||
|                 await R.exec("DELETE FROM heartbeat"); | ||||
| 
 | ||||
|  | @ -1414,24 +1452,24 @@ try { | |||
|         databaseSocketHandler(socket); | ||||
|         proxySocketHandler(socket); | ||||
| 
 | ||||
|         debug("added all socket handlers"); | ||||
|         log.debug("server", "added all socket handlers"); | ||||
| 
 | ||||
|         // ***************************
 | ||||
|         // Better do anything after added all socket handlers here
 | ||||
|         // ***************************
 | ||||
| 
 | ||||
|         debug("check auto login"); | ||||
|         log.debug("auth", "check auto login"); | ||||
|         if (await setting("disableAuth")) { | ||||
|             console.log("Disabled Auth: auto login to admin"); | ||||
|             log.info("auth", "Disabled Auth: auto login to admin"); | ||||
|             afterLogin(socket, await R.findOne("user")); | ||||
|             socket.emit("autoLogin"); | ||||
|         } else { | ||||
|             debug("need auth"); | ||||
|             log.debug("auth", "need auth"); | ||||
|         } | ||||
| 
 | ||||
|     }); | ||||
| 
 | ||||
|     console.log("Init the server"); | ||||
|     log.info("server", "Init the server"); | ||||
| 
 | ||||
|     httpServer.once("error", async (err) => { | ||||
|         console.error("Cannot listen: " + err.message); | ||||
|  | @ -1440,9 +1478,9 @@ try { | |||
| 
 | ||||
|     httpServer.listen(port, hostname, () => { | ||||
|         if (hostname) { | ||||
|             console.log(`Listening on ${hostname}:${port}`); | ||||
|             log.info("server", `Listening on ${hostname}:${port}`); | ||||
|         } else { | ||||
|             console.log(`Listening on ${port}`); | ||||
|             log.info("server", `Listening on ${port}`); | ||||
|         } | ||||
|         startMonitors(); | ||||
|         checkVersion.startInterval(); | ||||
|  | @ -1459,6 +1497,13 @@ try { | |||
| 
 | ||||
| })(); | ||||
| 
 | ||||
| /** | ||||
|  * Adds or removes notifications from a monitor. | ||||
|  * @param {number} monitorID The ID of the monitor to add/remove notifications from. | ||||
|  * @param {Array.<number>} notificationIDList An array of IDs for the notifications to add/remove. | ||||
|  * | ||||
|  * Generated by Trelent | ||||
|  */ | ||||
| async function updateMonitorNotification(monitorID, notificationIDList) { | ||||
|     await R.exec("DELETE FROM monitor_notification WHERE monitor_id = ? ", [ | ||||
|         monitorID, | ||||
|  | @ -1474,6 +1519,13 @@ async function updateMonitorNotification(monitorID, notificationIDList) { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * This function checks if the user owns a monitor with the given ID. | ||||
|  * @param {number} monitorID - The ID of the monitor to check ownership for. | ||||
|  * @param {number} userID - The ID of the user who is trying to access this data. | ||||
|  * | ||||
|  * Generated by Trelent | ||||
|  */ | ||||
| async function checkOwner(userID, monitorID) { | ||||
|     let row = await R.getRow("SELECT id FROM monitor WHERE id = ? AND user_id = ? ", [ | ||||
|         monitorID, | ||||
|  | @ -1485,6 +1537,10 @@ async function checkOwner(userID, monitorID) { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * This function is used to send the heartbeat list of a monitor. | ||||
|  * @param {Socket} socket - The socket object that will be used to send the data. | ||||
|  */ | ||||
| async function afterLogin(socket, user) { | ||||
|     socket.userID = user.id; | ||||
|     socket.join(user.id); | ||||
|  | @ -1510,6 +1566,13 @@ async function afterLogin(socket, user) { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Get a list of monitors for the given user. | ||||
|  * @param {string} userID - The ID of the user to get monitors for. | ||||
|  * @returns {Promise<Object>} A promise that resolves to an object with monitor IDs as keys and monitor objects as values. | ||||
|  * | ||||
|  * Generated by Trelent | ||||
|  */ | ||||
| async function getMonitorJSONList(userID) { | ||||
|     let result = {}; | ||||
| 
 | ||||
|  | @ -1524,15 +1587,20 @@ async function getMonitorJSONList(userID) { | |||
|     return result; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Connect to the database and patch it if necessary. | ||||
|  * | ||||
|  * Generated by Trelent | ||||
|  */ | ||||
| async function initDatabase(testMode = false) { | ||||
|     if (! fs.existsSync(Database.path)) { | ||||
|         console.log("Copying Database"); | ||||
|         log.info("server", "Copying Database"); | ||||
|         fs.copyFileSync(Database.templatePath, Database.path); | ||||
|     } | ||||
| 
 | ||||
|     console.log("Connecting to the Database"); | ||||
|     log.info("server", "Connecting to the Database"); | ||||
|     await Database.connect(testMode); | ||||
|     console.log("Connected"); | ||||
|     log.info("server", "Connected"); | ||||
| 
 | ||||
|     // Patch the database
 | ||||
|     await Database.patch(); | ||||
|  | @ -1542,26 +1610,33 @@ async function initDatabase(testMode = false) { | |||
|     ]); | ||||
| 
 | ||||
|     if (! jwtSecretBean) { | ||||
|         console.log("JWT secret is not found, generate one."); | ||||
|         log.info("server", "JWT secret is not found, generate one."); | ||||
|         jwtSecretBean = await initJWTSecret(); | ||||
|         console.log("Stored JWT secret into database"); | ||||
|         log.info("server", "Stored JWT secret into database"); | ||||
|     } else { | ||||
|         console.log("Load JWT secret from database."); | ||||
|         log.info("server", "Load JWT secret from database."); | ||||
|     } | ||||
| 
 | ||||
|     // If there is no record in user table, it is a new Uptime Kuma instance, need to setup
 | ||||
|     if ((await R.count("user")) === 0) { | ||||
|         console.log("No user, need setup"); | ||||
|         log.info("server", "No user, need setup"); | ||||
|         needSetup = true; | ||||
|     } | ||||
| 
 | ||||
|     jwtSecret = jwtSecretBean.value; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Resume a monitor. | ||||
|  * @param {string} userID - The ID of the user who owns the monitor. | ||||
|  * @param {string} monitorID - The ID of the monitor to resume. | ||||
|  * | ||||
|  * Generated by Trelent | ||||
|  */ | ||||
| async function startMonitor(userID, monitorID) { | ||||
|     await checkOwner(userID, monitorID); | ||||
| 
 | ||||
|     console.log(`Resume Monitor: ${monitorID} User ID: ${userID}`); | ||||
|     log.info("manage", `Resume Monitor: ${monitorID} User ID: ${userID}`); | ||||
| 
 | ||||
|     await R.exec("UPDATE monitor SET active = 1 WHERE id = ? AND user_id = ? ", [ | ||||
|         monitorID, | ||||
|  | @ -1584,10 +1659,17 @@ async function restartMonitor(userID, monitorID) { | |||
|     return await startMonitor(userID, monitorID); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Pause a monitor. | ||||
|  * @param {string} userID - The ID of the user who owns the monitor. | ||||
|  * @param {string} monitorID - The ID of the monitor to pause. | ||||
|  * | ||||
|  * Generated by Trelent | ||||
|  */ | ||||
| async function pauseMonitor(userID, monitorID) { | ||||
|     await checkOwner(userID, monitorID); | ||||
| 
 | ||||
|     console.log(`Pause Monitor: ${monitorID} User ID: ${userID}`); | ||||
|     log.info("manage", `Pause Monitor: ${monitorID} User ID: ${userID}`); | ||||
| 
 | ||||
|     await R.exec("UPDATE monitor SET active = 0 WHERE id = ? AND user_id = ? ", [ | ||||
|         monitorID, | ||||
|  | @ -1616,11 +1698,17 @@ async function startMonitors() { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Stops all monitors and closes the database connection. | ||||
|  * @param {string} signal The signal that triggered this function to be called. | ||||
|  * | ||||
|  * Generated by Trelent | ||||
|  */ | ||||
| async function shutdownFunction(signal) { | ||||
|     console.log("Shutdown requested"); | ||||
|     console.log("Called signal: " + signal); | ||||
|     log.info("server", "Shutdown requested"); | ||||
|     log.info("server", "Called signal: " + signal); | ||||
| 
 | ||||
|     console.log("Stopping all monitors"); | ||||
|     log.info("server", "Stopping all monitors"); | ||||
|     for (let id in server.monitorList) { | ||||
|         let monitor = server.monitorList[id]; | ||||
|         monitor.stop(); | ||||
|  | @ -1632,8 +1720,12 @@ async function shutdownFunction(signal) { | |||
|     await cloudflaredStop(); | ||||
| } | ||||
| 
 | ||||
| function getClientIp(socket) { | ||||
|     return socket.client.conn.remoteAddress.replace(/^.*:/, ""); | ||||
| } | ||||
| 
 | ||||
| function finalFunction() { | ||||
|     console.log("Graceful shutdown successful!"); | ||||
|     log.info("server", "Graceful shutdown successful!"); | ||||
| } | ||||
| 
 | ||||
| gracefulShutdown(httpServer, { | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| const { R } = require("redbean-node"); | ||||
| const { checkLogin, setSettings, setSetting } = require("../util-server"); | ||||
| const { checkLogin, setSetting } = require("../util-server"); | ||||
| const dayjs = require("dayjs"); | ||||
| const { debug } = require("../../src/util"); | ||||
| const { log } = require("../../src/util"); | ||||
| const ImageDataURI = require("../image-data-uri"); | ||||
| const Database = require("../database"); | ||||
| const apicache = require("../modules/apicache"); | ||||
|  | @ -202,8 +202,8 @@ module.exports.statusPageSocketHandler = (socket) => { | |||
|                 group.id = groupBean.id; | ||||
|             } | ||||
| 
 | ||||
|             // Delete groups that not in the list
 | ||||
|             debug("Delete groups that not in the list"); | ||||
|             // Delete groups that are not in the list
 | ||||
|             log.debug("socket", "Delete groups that are not in the list"); | ||||
|             const slots = groupIDList.map(() => "?").join(","); | ||||
| 
 | ||||
|             const data = [ | ||||
|  | @ -226,7 +226,7 @@ module.exports.statusPageSocketHandler = (socket) => { | |||
|             }); | ||||
| 
 | ||||
|         } catch (error) { | ||||
|             console.error(error); | ||||
|             log.error("socket", error); | ||||
| 
 | ||||
|             callback({ | ||||
|                 ok: false, | ||||
|  |  | |||
|  | @ -1,10 +1,10 @@ | |||
| const tcpp = require("tcp-ping"); | ||||
| const Ping = require("./ping-lite"); | ||||
| const { R } = require("redbean-node"); | ||||
| const { debug, genSecret } = require("../src/util"); | ||||
| const { log, genSecret } = require("../src/util"); | ||||
| const passwordHash = require("./password-hash"); | ||||
| const { Resolver } = require("dns"); | ||||
| const child_process = require("child_process"); | ||||
| const childProcess = require("child_process"); | ||||
| const iconv = require("iconv-lite"); | ||||
| const chardet = require("chardet"); | ||||
| const fs = require("fs"); | ||||
|  | @ -119,7 +119,7 @@ exports.setting = async function (key) { | |||
| 
 | ||||
|     try { | ||||
|         const v = JSON.parse(value); | ||||
|         debug(`Get Setting: ${key}: ${v}`); | ||||
|         log.debug("util", `Get Setting: ${key}: ${v}`); | ||||
|         return v; | ||||
|     } catch (e) { | ||||
|         return value; | ||||
|  | @ -206,7 +206,7 @@ const parseCertificateInfo = function (info) { | |||
|     const existingList = {}; | ||||
| 
 | ||||
|     while (link) { | ||||
|         debug(`[${i}] ${link.fingerprint}`); | ||||
|         log.debug("util", `[${i}] ${link.fingerprint}`); | ||||
| 
 | ||||
|         if (!link.valid_from || !link.valid_to) { | ||||
|             break; | ||||
|  | @ -221,7 +221,7 @@ const parseCertificateInfo = function (info) { | |||
|         if (link.issuerCertificate == null) { | ||||
|             break; | ||||
|         } else if (link.issuerCertificate.fingerprint in existingList) { | ||||
|             debug(`[Last] ${link.issuerCertificate.fingerprint}`); | ||||
|             log.debug("util", `[Last] ${link.issuerCertificate.fingerprint}`); | ||||
|             link.issuerCertificate = null; | ||||
|             break; | ||||
|         } else { | ||||
|  | @ -242,7 +242,7 @@ exports.checkCertificate = function (res) { | |||
|     const info = res.request.res.socket.getPeerCertificate(true); | ||||
|     const valid = res.request.res.socket.authorized || false; | ||||
| 
 | ||||
|     debug("Parsing Certificate Info"); | ||||
|     log.debug("util", "Parsing Certificate Info"); | ||||
|     const parsedInfo = parseCertificateInfo(info); | ||||
| 
 | ||||
|     return { | ||||
|  | @ -345,7 +345,7 @@ exports.doubleCheckPassword = async (socket, currentPassword) => { | |||
| exports.startUnitTest = async () => { | ||||
|     console.log("Starting unit test..."); | ||||
|     const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm"; | ||||
|     const child = child_process.spawn(npm, ["run", "jest"]); | ||||
|     const child = childProcess.spawn(npm, ["run", "jest"]); | ||||
| 
 | ||||
|     child.stdout.on("data", (data) => { | ||||
|         console.log(data.toString()); | ||||
|  | @ -367,7 +367,6 @@ exports.startUnitTest = async () => { | |||
|  */ | ||||
| exports.convertToUTF8 = (body) => { | ||||
|     const guessEncoding = chardet.detect(body); | ||||
|     //debug("Guess Encoding: " + guessEncoding);
 | ||||
|     const str = iconv.decode(body, guessEncoding); | ||||
|     return str.toString(); | ||||
| }; | ||||
|  |  | |||
|  | @ -25,7 +25,7 @@ | |||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| import { Modal } from "bootstrap" | ||||
| import { Modal } from "bootstrap"; | ||||
| 
 | ||||
| export default { | ||||
|     props: { | ||||
|  | @ -46,15 +46,15 @@ export default { | |||
|         modal: null, | ||||
|     }), | ||||
|     mounted() { | ||||
|         this.modal = new Modal(this.$refs.modal) | ||||
|         this.modal = new Modal(this.$refs.modal); | ||||
|     }, | ||||
|     methods: { | ||||
|         show() { | ||||
|             this.modal.show() | ||||
|             this.modal.show(); | ||||
|         }, | ||||
|         yes() { | ||||
|             this.$emit("yes"); | ||||
|         }, | ||||
|     }, | ||||
| } | ||||
| }; | ||||
| </script> | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ | |||
| 
 | ||||
| <script lang="ts"> | ||||
| 
 | ||||
| import { sleep } from "../util.ts" | ||||
| import { sleep } from "../util.ts"; | ||||
| 
 | ||||
| export default { | ||||
| 
 | ||||
|  | @ -25,12 +25,12 @@ export default { | |||
|         return { | ||||
|             output: "", | ||||
|             frameDuration: 30, | ||||
|         } | ||||
|         }; | ||||
|     }, | ||||
| 
 | ||||
|     computed: { | ||||
|         isNum() { | ||||
|             return typeof this.value === "number" | ||||
|             return typeof this.value === "number"; | ||||
|         }, | ||||
|     }, | ||||
| 
 | ||||
|  | @ -45,7 +45,7 @@ export default { | |||
|             } else { | ||||
|                 for (let i = 1; i < frames; i++) { | ||||
|                     this.output += step; | ||||
|                     await sleep(15) | ||||
|                     await sleep(15); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|  | @ -59,5 +59,5 @@ export default { | |||
| 
 | ||||
|     methods: {}, | ||||
| 
 | ||||
| } | ||||
| }; | ||||
| </script> | ||||
|  |  | |||
|  | @ -4,12 +4,12 @@ | |||
| 
 | ||||
| <script> | ||||
| import dayjs from "dayjs"; | ||||
| import relativeTime from "dayjs/plugin/relativeTime" | ||||
| import utc from "dayjs/plugin/utc" | ||||
| import timezone from "dayjs/plugin/timezone" // dependent on utc plugin | ||||
| dayjs.extend(utc) | ||||
| dayjs.extend(timezone) | ||||
| dayjs.extend(relativeTime) | ||||
| import relativeTime from "dayjs/plugin/relativeTime"; | ||||
| import utc from "dayjs/plugin/utc"; | ||||
| import timezone from "dayjs/plugin/timezone"; // dependent on utc plugin | ||||
| dayjs.extend(utc); | ||||
| dayjs.extend(timezone); | ||||
| dayjs.extend(relativeTime); | ||||
| 
 | ||||
| export default { | ||||
|     props: { | ||||
|  | @ -29,5 +29,5 @@ export default { | |||
|             } | ||||
|         }, | ||||
|     }, | ||||
| } | ||||
| }; | ||||
| </script> | ||||
|  |  | |||
|  | @ -38,7 +38,7 @@ export default { | |||
|             beatMargin: 4, | ||||
|             move: false, | ||||
|             maxBeat: -1, | ||||
|         } | ||||
|         }; | ||||
|     }, | ||||
|     computed: { | ||||
| 
 | ||||
|  | @ -69,12 +69,12 @@ export default { | |||
|             if (start < 0) { | ||||
|                 // Add empty placeholder | ||||
|                 for (let i = start; i < 0; i++) { | ||||
|                     placeholders.push(0) | ||||
|                     placeholders.push(0); | ||||
|                 } | ||||
|                 start = 0; | ||||
|             } | ||||
| 
 | ||||
|             return placeholders.concat(this.beatList.slice(start)) | ||||
|             return placeholders.concat(this.beatList.slice(start)); | ||||
|         }, | ||||
| 
 | ||||
|         wrapStyle() { | ||||
|  | @ -84,7 +84,7 @@ export default { | |||
|             return { | ||||
|                 padding: `${topBottom}px ${leftRight}px`, | ||||
|                 width: "100%", | ||||
|             } | ||||
|             }; | ||||
|         }, | ||||
| 
 | ||||
|         barStyle() { | ||||
|  | @ -94,12 +94,12 @@ export default { | |||
|                 return { | ||||
|                     transition: "all ease-in-out 0.25s", | ||||
|                     transform: `translateX(${width}px)`, | ||||
|                 } | ||||
|                 }; | ||||
| 
 | ||||
|             } | ||||
|             return { | ||||
|                 transform: "translateX(0)", | ||||
|             } | ||||
|             }; | ||||
| 
 | ||||
|         }, | ||||
| 
 | ||||
|  | @ -109,7 +109,7 @@ export default { | |||
|                 height: this.beatHeight + "px", | ||||
|                 margin: this.beatMargin + "px", | ||||
|                 "--hover-scale": this.hoverScale, | ||||
|             } | ||||
|             }; | ||||
|         }, | ||||
| 
 | ||||
|     }, | ||||
|  | @ -120,7 +120,7 @@ export default { | |||
| 
 | ||||
|                 setTimeout(() => { | ||||
|                     this.move = false; | ||||
|                 }, 300) | ||||
|                 }, 300); | ||||
|             }, | ||||
|             deep: true, | ||||
|         }, | ||||
|  | @ -162,15 +162,15 @@ export default { | |||
|     methods: { | ||||
|         resize() { | ||||
|             if (this.$refs.wrap) { | ||||
|                 this.maxBeat = Math.floor(this.$refs.wrap.clientWidth / (this.beatWidth + this.beatMargin * 2)) | ||||
|                 this.maxBeat = Math.floor(this.$refs.wrap.clientWidth / (this.beatWidth + this.beatMargin * 2)); | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         getBeatTitle(beat) { | ||||
|             return `${this.$root.datetime(beat.time)}` + ((beat.msg) ? ` - ${beat.msg}` : ``); | ||||
|             return `${this.$root.datetime(beat.time)}` + ((beat.msg) ? ` - ${beat.msg}` : ""); | ||||
|         } | ||||
|     }, | ||||
| } | ||||
| }; | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
|  |  | |||
|  | @ -51,15 +51,15 @@ export default { | |||
|     data() { | ||||
|         return { | ||||
|             visibility: "password", | ||||
|         } | ||||
|         }; | ||||
|     }, | ||||
|     computed: { | ||||
|         model: { | ||||
|             get() { | ||||
|                 return this.modelValue | ||||
|                 return this.modelValue; | ||||
|             }, | ||||
|             set(value) { | ||||
|                 this.$emit("update:modelValue", value) | ||||
|                 this.$emit("update:modelValue", value); | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|  | @ -74,5 +74,5 @@ export default { | |||
|             this.visibility = "password"; | ||||
|         }, | ||||
|     } | ||||
| } | ||||
| }; | ||||
| </script> | ||||
|  |  | |||
|  | @ -21,7 +21,7 @@ | |||
| 
 | ||||
|             <router-link v-for="(item, index) in sortedMonitorList" :key="index" :to="monitorURL(item.id)" class="item" :class="{ 'disabled': ! item.active }"> | ||||
|                 <div class="row"> | ||||
|                     <div class="col-9 col-md-8 small-padding" :class="{ 'monitorItem': $root.userHeartbeatBar == 'bottom' || $root.userHeartbeatBar == 'none' }"> | ||||
|                     <div class="col-9 col-md-8 small-padding" :class="{ 'monitor-item': $root.userHeartbeatBar == 'bottom' || $root.userHeartbeatBar == 'none' }"> | ||||
|                         <div class="info"> | ||||
|                             <Uptime :monitor="item" type="24" :pill="true" /> | ||||
|                             {{ item.name }} | ||||
|  | @ -36,7 +36,7 @@ | |||
|                 </div> | ||||
| 
 | ||||
|                 <div v-if="$root.userHeartbeatBar == 'bottom'" class="row"> | ||||
|                     <div class="col-12"> | ||||
|                     <div class="col-12 bottom-style"> | ||||
|                         <HeartbeatBar size="small" :monitor-id="item.id" /> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | @ -198,14 +198,21 @@ export default { | |||
|     max-width: 15em; | ||||
| } | ||||
| 
 | ||||
| .monitorItem { | ||||
| .monitor-item { | ||||
|     width: 100%; | ||||
| } | ||||
| 
 | ||||
| .tags { | ||||
|     padding-left: 62px; | ||||
|     margin-top: 4px; | ||||
|     padding-left: 67px; | ||||
|     display: flex; | ||||
|     flex-wrap: wrap; | ||||
|     gap: 0; | ||||
| } | ||||
| 
 | ||||
| .bottom-style { | ||||
|     padding-left: 67px; | ||||
|     margin-top: 5px; | ||||
| } | ||||
| 
 | ||||
| </style> | ||||
|  |  | |||
|  | @ -69,7 +69,6 @@ | |||
| 
 | ||||
| <script lang="ts"> | ||||
| import { Modal } from "bootstrap"; | ||||
| import { ucfirst } from "../util.ts"; | ||||
| 
 | ||||
| import Confirm from "./Confirm.vue"; | ||||
| import NotificationFormList from "./notifications"; | ||||
|  |  | |||
|  | @ -24,7 +24,7 @@ import timezone from "dayjs/plugin/timezone"; | |||
| import "chartjs-adapter-dayjs"; | ||||
| import { LineChart } from "vue-chart-3"; | ||||
| import { useToast } from "vue-toastification"; | ||||
| import { UP, DOWN, PENDING } from "../util.ts"; | ||||
| import { DOWN } from "../util.ts"; | ||||
| 
 | ||||
| dayjs.extend(utc); | ||||
| dayjs.extend(timezone); | ||||
|  | @ -278,7 +278,7 @@ export default { | |||
| 
 | ||||
|         .dropdown-item { | ||||
|             border-radius: 0.3rem; | ||||
|             padding: 2px 16px 4px 16px; | ||||
|             padding: 2px 16px 4px; | ||||
| 
 | ||||
|             .dark & { | ||||
|                 background: $dark-bg; | ||||
|  | @ -286,6 +286,7 @@ export default { | |||
| 
 | ||||
|             .dark &:hover { | ||||
|                 background: $dark-font-color; | ||||
|                 color: $dark-font-color2; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -25,7 +25,7 @@ | |||
|                             <label for="proxy-host" class="form-label">{{ $t("Proxy Server") }}</label> | ||||
|                             <div class="d-flex"> | ||||
|                                 <input id="proxy-host" v-model="proxy.host" type="text" class="form-control" required :placeholder="$t('Server Address')"> | ||||
|                                 <input v-model="proxy.port" type="number" class="form-control ms-2" style="width: 100px" required min="1" max="65535" :placeholder="$t('Port')"> | ||||
|                                 <input v-model="proxy.port" type="number" class="form-control ms-2" style="width: 100px;" required min="1" max="65535" :placeholder="$t('Port')"> | ||||
|                             </div> | ||||
|                         </div> | ||||
| 
 | ||||
|  |  | |||
|  | @ -145,7 +145,7 @@ export default { | |||
| 
 | ||||
| .mobile { | ||||
|     .item { | ||||
|         padding: 13px 0 10px 0; | ||||
|         padding: 13px 0 10px; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -41,7 +41,7 @@ export default { | |||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| }; | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
|  |  | |||
|  | @ -49,7 +49,7 @@ export default { | |||
| <style lang="scss" scoped> | ||||
| @import "../assets/vars.scss"; | ||||
| 
 | ||||
| h5:after { | ||||
| h5::after { | ||||
|     content: ""; | ||||
|     display: block; | ||||
|     width: 50%; | ||||
|  |  | |||
|  | @ -46,7 +46,7 @@ | |||
|                                     <input v-model="token" type="text" maxlength="6" class="form-control"> | ||||
|                                     <button class="btn btn-outline-primary" type="button" @click="verifyToken()">{{ $t("Verify Token") }}</button> | ||||
|                                 </div> | ||||
|                                 <p v-show="tokenValid" class="mt-2" style="color: green">{{ $t("tokenValidSettingsMsg") }}</p> | ||||
|                                 <p v-show="tokenValid" class="mt-2" style="color: green;">{{ $t("tokenValidSettingsMsg") }}</p> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|  |  | |||
|  | @ -22,33 +22,33 @@ export default { | |||
|                 return Math.round(this.$root.uptimeList[key] * 10000) / 100 + "%"; | ||||
|             } | ||||
| 
 | ||||
|             return this.$t("notAvailableShort") | ||||
|             return this.$t("notAvailableShort"); | ||||
|         }, | ||||
| 
 | ||||
|         color() { | ||||
|             if (this.lastHeartBeat.status === 0) { | ||||
|                 return "danger" | ||||
|                 return "danger"; | ||||
|             } | ||||
| 
 | ||||
|             if (this.lastHeartBeat.status === 1) { | ||||
|                 return "primary" | ||||
|                 return "primary"; | ||||
|             } | ||||
| 
 | ||||
|             if (this.lastHeartBeat.status === 2) { | ||||
|                 return "warning" | ||||
|                 return "warning"; | ||||
|             } | ||||
| 
 | ||||
|             return "secondary" | ||||
|             return "secondary"; | ||||
|         }, | ||||
| 
 | ||||
|         lastHeartBeat() { | ||||
|             if (this.monitor.id in this.$root.lastHeartbeatList && this.$root.lastHeartbeatList[this.monitor.id]) { | ||||
|                 return this.$root.lastHeartbeatList[this.monitor.id] | ||||
|                 return this.$root.lastHeartbeatList[this.monitor.id]; | ||||
|             } | ||||
| 
 | ||||
|             return { | ||||
|                 status: -1, | ||||
|             } | ||||
|             }; | ||||
|         }, | ||||
| 
 | ||||
|         className() { | ||||
|  | @ -59,7 +59,7 @@ export default { | |||
|             return ""; | ||||
|         }, | ||||
|     }, | ||||
| } | ||||
| }; | ||||
| </script> | ||||
| 
 | ||||
| <style> | ||||
|  |  | |||
|  | @ -28,5 +28,5 @@ export default { | |||
|             this.$parent.notification.gotifyPriority = 8; | ||||
|         } | ||||
|     }, | ||||
| } | ||||
| }; | ||||
| </script> | ||||
|  |  | |||
							
								
								
									
										34
									
								
								src/components/notifications/OneBot.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/components/notifications/OneBot.vue
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,34 @@ | |||
| <template> | ||||
|     <div class="mb-3"> | ||||
|         <div class="mb-3"> | ||||
|             <label for="onebot-http-addr" class="form-label">{{ $t("onebotHttpAddress") }}<span style="color: red;"><sup>*</sup></span></label> | ||||
|             <input id="HttpUrl" v-model="$parent.notification.httpAddr" type="text" class="form-control" required> | ||||
|         </div> | ||||
|         <div class="mb-3"> | ||||
|             <label for="onebot-access-token" class="form-label">AccessToken<span style="color: red;"><sup>*</sup></span></label> | ||||
|             <input id="HttpUrl" v-model="$parent.notification.accessToken" type="text" class="form-control" required> | ||||
|             <div class="form-text"> | ||||
|                 <p>{{ $t("onebotSafetyTips") }}</p> | ||||
|             </div> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="mb-3"> | ||||
|             <label for="onebot-msg-type" class="form-label">{{ $t("onebotMessageType") }}</label> | ||||
|             <select id="onebot-msg-type" v-model="$parent.notification.msgType" class="form-select"> | ||||
|                 <option value="group">{{ $t("onebotGroupMessage") }}</option> | ||||
|                 <option value="private">{{ $t("onebotPrivateMessage") }}</option> | ||||
|             </select> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="mb-3"> | ||||
|             <label for="onebot-reciever-id" class="form-label">{{ $t("onebotUserOrGroupId") }}<span style="color: red;"><sup>*</sup></span></label> | ||||
|             <input id="secretKey" v-model="$parent.notification.recieverId" type="text" class="form-control" required> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="form-text"> | ||||
|             <i18n-t tag="p" keypath="Read more:"> | ||||
|                 <a href="https://github.com/botuniverse/onebot-11" target="_blank">https://github.com/botuniverse/onebot-11</a> | ||||
|             </i18n-t> | ||||
|         </div> | ||||
|     </div> | ||||
| </template> | ||||
							
								
								
									
										19
									
								
								src/components/notifications/PushDeer.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/components/notifications/PushDeer.vue
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| <template> | ||||
|     <div class="mb-3"> | ||||
|         <label for="pushdeer-key" class="form-label">{{ $t("PushDeer Key") }}</label> | ||||
|         <HiddenInput id="pushdeer-key" v-model="$parent.notification.pushdeerKey" :required="true" autocomplete="one-time-code" placeholder="PDUxxxx"></HiddenInput> | ||||
|     </div> | ||||
| 
 | ||||
|     <i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;"> | ||||
|         <a href="http://www.pushdeer.com/" rel="noopener noreferrer" target="_blank">http://www.pushdeer.com/</a> | ||||
|     </i18n-t> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| import HiddenInput from "../HiddenInput.vue"; | ||||
| export default { | ||||
|     components: { | ||||
|         HiddenInput, | ||||
|     }, | ||||
| }; | ||||
| </script> | ||||
|  | @ -63,5 +63,5 @@ export default { | |||
|     components: { | ||||
|         HiddenInput, | ||||
|     }, | ||||
| } | ||||
| }; | ||||
| </script> | ||||
|  |  | |||
|  | @ -24,11 +24,13 @@ import AliyunSMS from "./AliyunSms.vue"; | |||
| import DingDing from "./DingDing.vue"; | ||||
| import Bark from "./Bark.vue"; | ||||
| import SerwerSMS from "./SerwerSMS.vue"; | ||||
| import Stackfield from './Stackfield.vue'; | ||||
| import Stackfield from "./Stackfield.vue"; | ||||
| import WeCom from "./WeCom.vue"; | ||||
| import GoogleChat from "./GoogleChat.vue"; | ||||
| import Gorush from "./Gorush.vue"; | ||||
| import Alerta from "./Alerta.vue"; | ||||
| import OneBot from "./OneBot.vue"; | ||||
| import PushDeer from "./PushDeer.vue"; | ||||
| 
 | ||||
| /** | ||||
|  * Manage all notification form. | ||||
|  | @ -67,6 +69,8 @@ const NotificationFormList = { | |||
|     "GoogleChat": GoogleChat, | ||||
|     "gorush": Gorush, | ||||
|     "alerta": Alerta, | ||||
|     "OneBot": OneBot, | ||||
|     "PushDeer": PushDeer, | ||||
| }; | ||||
| 
 | ||||
| export default NotificationFormList; | ||||
|  |  | |||
|  | @ -44,6 +44,7 @@ export default { | |||
| .logo { | ||||
|     margin: 4em 1em; | ||||
| } | ||||
| 
 | ||||
| .update-link { | ||||
|     font-size: 0.9em; | ||||
| } | ||||
|  |  | |||
|  | @ -69,7 +69,7 @@ | |||
| 
 | ||||
|             <div class="mb-2"> | ||||
|                 <input | ||||
|                     id="importBackup" | ||||
|                     id="import-backend" | ||||
|                     type="file" | ||||
|                     class="form-control" | ||||
|                     accept="application/json" | ||||
|  | @ -94,7 +94,7 @@ | |||
|             <div | ||||
|                 v-if="importAlert" | ||||
|                 class="alert alert-danger mt-3" | ||||
|                 style="padding: 6px 16px" | ||||
|                 style="padding: 6px 16px;" | ||||
|             > | ||||
|                 {{ importAlert }} | ||||
|             </div> | ||||
|  | @ -159,7 +159,7 @@ export default { | |||
| 
 | ||||
|         importBackup() { | ||||
|             this.processing = true; | ||||
|             let uploadItem = document.getElementById("importBackup").files; | ||||
|             let uploadItem = document.getElementById("import-backend").files; | ||||
| 
 | ||||
|             if (uploadItem.length <= 0) { | ||||
|                 this.processing = false; | ||||
|  | @ -198,7 +198,7 @@ export default { | |||
| @import "../../assets/vars.scss"; | ||||
| 
 | ||||
| .dark { | ||||
|     #importBackup { | ||||
|     #import-backend { | ||||
|         &::file-selector-button { | ||||
|             color: $primary; | ||||
|             background-color: $dark-bg; | ||||
|  |  | |||
|  | @ -189,4 +189,3 @@ export default { | |||
| }; | ||||
| </script> | ||||
| 
 | ||||
| <style></style> | ||||
|  |  | |||
|  | @ -52,7 +52,7 @@ | |||
| 
 | ||||
| <script> | ||||
| import Confirm from "../../components/Confirm.vue"; | ||||
| import { debug } from "../../util.ts"; | ||||
| import { log } from "../../util.ts"; | ||||
| import { useToast } from "vue-toastification"; | ||||
| 
 | ||||
| const toast = useToast(); | ||||
|  | @ -91,13 +91,13 @@ export default { | |||
| 
 | ||||
|     methods: { | ||||
|         loadDatabaseSize() { | ||||
|             debug("load database size"); | ||||
|             log.debug("monitorhistory", "load database size"); | ||||
|             this.$root.getSocket().emit("getDatabaseSize", (res) => { | ||||
|                 if (res.ok) { | ||||
|                     this.databaseSize = res.size; | ||||
|                     debug("database size: " + res.size); | ||||
|                     log.debug("monitorhistory", "database size: " + res.size); | ||||
|                 } else { | ||||
|                     debug(res); | ||||
|                     log.debug("monitorhistory", res); | ||||
|                 } | ||||
|             }); | ||||
|         }, | ||||
|  | @ -108,7 +108,7 @@ export default { | |||
|                     this.loadDatabaseSize(); | ||||
|                     toast.success("Done"); | ||||
|                 } else { | ||||
|                     debug(res); | ||||
|                     log.debug("monitorhistory", res); | ||||
|                 } | ||||
|             }); | ||||
|         }, | ||||
|  | @ -129,5 +129,3 @@ export default { | |||
|     }, | ||||
| }; | ||||
| </script> | ||||
| 
 | ||||
| <style></style> | ||||
|  |  | |||
|  | @ -355,7 +355,7 @@ export default { | |||
| <style lang="scss" scoped> | ||||
| @import "../../assets/vars.scss"; | ||||
| 
 | ||||
| h5:after { | ||||
| h5::after { | ||||
|     content: ""; | ||||
|     display: block; | ||||
|     width: 50%; | ||||
|  |  | |||
|  | @ -343,7 +343,7 @@ export default { | |||
|     "No Monitors": "Няма монитори", | ||||
|     "Untitled Group": "Група без заглавие", | ||||
|     Services: "Услуги", | ||||
|     Discard: "Премахни", | ||||
|     Discard: "Отмени", | ||||
|     Cancel: "Отмени", | ||||
|     "Powered by": "Създадено чрез", | ||||
|     serwersms: "SerwerSMS.pl", | ||||
|  |  | |||
|  | @ -350,7 +350,7 @@ export default { | |||
|     serwersmsAPIPassword: "API Passwort", | ||||
|     serwersmsPhoneNumber: "Telefonnummer", | ||||
|     serwersmsSenderName: "Name des SMS-Absenders (über Kundenportal registriert)", | ||||
|     "stackfield": "Stackfield", | ||||
|     stackfield: "Stackfield", | ||||
|     clicksendsms: "ClickSend SMS", | ||||
|     apiCredentials: "API Zugangsdaten", | ||||
|     smtpDkimSettings: "DKIM Einstellungen", | ||||
|  | @ -362,4 +362,84 @@ export default { | |||
|     smtpDkimHashAlgo: "Hash-Algorithmus (Optional)", | ||||
|     smtpDkimheaderFieldNames: "Zu validierende Header-Schlüssel (optional)", | ||||
|     smtpDkimskipFields: "Zu ignorierende Header Schlüssel (optional)", | ||||
|     PushByTechulus: "Push by Techulus", | ||||
|     gorush: "Gorush", | ||||
|     alerta: "Alerta", | ||||
|     alertaApiEndpoint: "API Endpunkt", | ||||
|     alertaEnvironment: "Umgebung", | ||||
|     alertaApiKey: "API Schlüssel", | ||||
|     alertaAlertState: "Alarmstatus", | ||||
|     alertaRecoverState: "Wiederherstellungsstatus", | ||||
|     deleteStatusPageMsg: "Bist du sicher, dass du diese Status-Seite löschen willst?", | ||||
|     Proxies: "Proxies", | ||||
|     default: "Standard", | ||||
|     enabled: "Aktiviert", | ||||
|     setAsDefault: "Als Standard setzen", | ||||
|     deleteProxyMsg: "Bist du sicher, dass du diesen Proxy für alle Monitore löschen willst?", | ||||
|     proxyDescription: "Proxies müssen einem Monitor zugewiesen werden, um zu funktionieren.", | ||||
|     enableProxyDescription: "Dieser Proxy wird keinen Effekt auf Monitor-Anfragen haben, bis er aktiviert ist. Du kannst ihn temporär von allen Monitoren nach Aktivierungsstatus deaktivieren.", | ||||
|     setAsDefaultProxyDescription: "Dieser Proxy wird standardmäßig für alle neuen Monitore aktiviert sein. Du kannst den Proxy immernoch für jeden Monitor einzeln deaktivieren.", | ||||
|     "Certificate Chain": "Zertifikatskette", | ||||
|     Valid: "Gültig", | ||||
|     Invalid: "Ungültig", | ||||
|     AccessKeyId: "AccessKey ID", | ||||
|     SecretAccessKey: "AccessKey Secret", | ||||
|     PhoneNumbers: "Telefonnummern", | ||||
|     TemplateCode: "Vorlagencode", | ||||
|     SignName: "Signaturname", | ||||
|     "Sms template must contain parameters: ": "SMS Vorlage muss folgende Parameter enthalten: ", | ||||
|     "Bark Endpoint": "Bark Endpunkt", | ||||
|     WebHookUrl: "Webhook URL", | ||||
|     SecretKey: "Geheimer Schlüssel", | ||||
|     "For safety, must use secret key": "Zur Sicherheit muss ein geheimer Schlüssel verwendet werden", | ||||
|     "Device Token": "Gerätetoken", | ||||
|     Platform: "Platform", | ||||
|     iOS: "iOS", | ||||
|     Android: "Android", | ||||
|     Huawei: "Huawei", | ||||
|     High: "Hoch", | ||||
|     Retry: "Wiederholungen", | ||||
|     Topic: "Thema", | ||||
|     "WeCom Bot Key": "WeCom Bot Schlüssel", | ||||
|     "Setup Proxy": "Proxy einrichten", | ||||
|     "Proxy Protocol": "Proxy Protokoll", | ||||
|     "Proxy Server": "Proxy Server", | ||||
|     "Proxy server has authentication": "Proxy server hat Authentifizierung", | ||||
|     User: "Benutzer", | ||||
|     Installed: "Installiert", | ||||
|     "Not installed": "Nicht installiert", | ||||
|     Running: "Läuft", | ||||
|     "Not running": "Gestoppt", | ||||
|     "Remove Token": "Token entfernen", | ||||
|     Start: "Start", | ||||
|     Stop: "Stop", | ||||
|     "Uptime Kuma": "Uptime Kuma", | ||||
|     "Add New Status Page": "Neue Status-Seite hinzufügen", | ||||
|     Slug: "Slug", | ||||
|     "Accept characters:": "Akzeptierte Zeichen:", | ||||
|     startOrEndWithOnly: "Nur mit {0} anfangen und enden", | ||||
|     "No consecutive dashes": "Keine aufeinanderfolgenden Bindestriche", | ||||
|     Next: "Weiter", | ||||
|     "The slug is already taken. Please choose another slug.": "Der Slug ist bereits in Verwendung. Bitte wähle einen anderen.", | ||||
|     "No Proxy": "Kein Proxy", | ||||
|     "HTTP Basic Auth": "HTTP Basisauthentifizierung", | ||||
|     "New Status Page": "Neue Status-Seite", | ||||
|     "Page Not Found": "Seite nicht gefunden", | ||||
|     "Reverse Proxy": "Reverse Proxy", | ||||
|     Backup: "Sicherung", | ||||
|     About: "Über", | ||||
|     wayToGetCloudflaredURL: "(Lade cloudflared von {0} herunter)", | ||||
|     cloudflareWebsite: "Cloudflare Website", | ||||
|     "Message:": "Nachricht:", | ||||
|     "Don't know how to get the token? Please read the guide:": "Du weißt nicht, wie man den Token bekommt? Lies die Anleitung dazu:", | ||||
|     "The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.": "Die aktuelle Verbindung kann unterbrochen werden, wenn du aktuell über Cloudflare Tunnel verbunden bist. Bist du sicher, dass du es stoppen willst? Gib zur Bestätigung dein aktuelles Passwort ein.", | ||||
|     "Other Software": "Andere Software", | ||||
|     "For example: nginx, Apache and Traefik.": "Zum Beispiel: nginx, Apache und Traefik.", | ||||
|     "Please read": "Bitte lesen", | ||||
|     "Subject:": "Betreff:", | ||||
|     "Valid To:": "Gültig bis:", | ||||
|     "Days Remaining:": "Tage verbleibend:", | ||||
|     "Issuer:": "Aussteller:", | ||||
|     "Fingerprint:": "Fingerabdruck:", | ||||
|     "No status pages": "Keine Status-Seiten", | ||||
| }; | ||||
|  |  | |||
|  | @ -442,4 +442,14 @@ export default { | |||
|     "Issuer:": "Issuer:", | ||||
|     "Fingerprint:": "Fingerprint:", | ||||
|     "No status pages": "No status pages", | ||||
|     "Domain Name Expiry Notification": "Domain Name Expiry Notification", | ||||
|     "Proxy": "Proxy", | ||||
|     "Date Created": "Date Created", | ||||
|     onebotHttpAddress: "OneBot HTTP Address", | ||||
|     onebotMessageType: "OneBot Message Type", | ||||
|     onebotGroupMessage: "Group", | ||||
|     onebotPrivateMessage: "Private", | ||||
|     onebotUserOrGroupId: "Group/User ID", | ||||
|     onebotSafetyTips: "For safety, must set access token", | ||||
|     "PushDeer Key": "PushDeer Key", | ||||
| }; | ||||
|  |  | |||
|  | @ -374,8 +374,8 @@ export default { | |||
|     serwersmsSenderName: "SMS Имя Отправителя (регистрированный через пользовательский портал)", | ||||
|     stackfield: "Stackfield", | ||||
|     smtpDkimSettings: "DKIM Настройки", | ||||
|     smtpDkimDesc: "Please refer to the Nodemailer DKIM {0} for usage.", | ||||
|     documentation: "документация", | ||||
|     smtpDkimDesc: "Пожалуйста ознакомьтесь с {0} Nodemailer DKIM для использования.", | ||||
|     documentation: "документацией", | ||||
|     smtpDkimDomain: "Имя Домена", | ||||
|     smtpDkimKeySelector: "Ключ", | ||||
|     smtpDkimPrivateKey: "Приватный ключ", | ||||
|  | @ -389,4 +389,12 @@ export default { | |||
|     alertaApiKey: "Ключ API", | ||||
|     alertaAlertState: "Состояние алерта", | ||||
|     alertaRecoverState: "Состояние восстановления", | ||||
|     Proxies: "Прокси", | ||||
|     default: "По умолчанию", | ||||
|     enabled: "Включено", | ||||
|     setAsDefault: "Установлено по умолчанию", | ||||
|     deleteProxyMsg: "Вы действительно хотите удалить этот прокси для всех мониторов?", | ||||
|     proxyDescription: "Прокси должны быть привязаны к монитору, чтобы работать.", | ||||
|     enableProxyDescription: "Этот прокси не будет влиять на запросы монитора, пока не будет активирован. Вы можете контролировать временное отключение прокси для всех мониторов через статус активации.", | ||||
|     setAsDefaultProxyDescription: "Этот прокси будет по умолчанию включен для новых мониторов. Вы всё ещё можете отдельно отключать прокси в каждом мониторе.", | ||||
| }; | ||||
|  |  | |||
|  | @ -449,4 +449,13 @@ export default { | |||
|     "Issuer:": "颁发者:", | ||||
|     "Fingerprint:": "指纹:", | ||||
|     "No status pages": "无状态页", | ||||
|     "Domain Name Expiry Notification": "域名到期时通知", | ||||
|     "Proxy": "代理", | ||||
|     "Date Created": "创建于", | ||||
|     onebotHttpAddress: "OneBot HTTP 地址", | ||||
|     onebotMessageType: "OneBot 消息类型", | ||||
|     onebotGroupMessage: "群聊", | ||||
|     onebotPrivateMessage: "私聊", | ||||
|     onebotUserOrGroupId: "群组/用户ID", | ||||
|     onebotSafetyTips: "出于安全原因,请务必设置AccessToken", | ||||
| }; | ||||
|  |  | |||
|  | @ -3,5 +3,6 @@ | |||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| export default {} | ||||
| export default {}; | ||||
| </script> | ||||
| 
 | ||||
|  |  | |||
|  | @ -25,9 +25,9 @@ export default { | |||
|         MonitorList, | ||||
|     }, | ||||
|     data() { | ||||
|         return {} | ||||
|         return {}; | ||||
|     }, | ||||
| } | ||||
| }; | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
|  |  | |||
|  | @ -118,6 +118,7 @@ export default { | |||
|                 return 0; | ||||
|             }); | ||||
| 
 | ||||
|             // eslint-disable-next-line vue/no-side-effects-in-computed-properties | ||||
|             this.heartBeatList = result; | ||||
| 
 | ||||
|             return result; | ||||
|  |  | |||
|  | @ -11,6 +11,6 @@ export default { | |||
|     components: { | ||||
|         MonitorList, | ||||
|     }, | ||||
| } | ||||
| }; | ||||
| </script> | ||||
| 
 | ||||
|  |  | |||
|  | @ -92,7 +92,6 @@ export default { | |||
|         } | ||||
| 
 | ||||
|         .info { | ||||
| 
 | ||||
|             .title { | ||||
|                 font-weight: bold; | ||||
|                 font-size: 20px; | ||||
|  |  | |||
|  | @ -836,7 +836,7 @@ footer { | |||
| 
 | ||||
| .incident { | ||||
|     .content { | ||||
|         &[contenteditable=true] { | ||||
|         &[contenteditable="true"] { | ||||
|             min-height: 60px; | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -7,6 +7,12 @@ import { localeDirection, currentLocale } from "./i18n"; | |||
| dayjs.extend(utc); | ||||
| dayjs.extend(timezone); | ||||
| 
 | ||||
| /** | ||||
|  * Returns the offset from UTC in hours for the current locale. | ||||
|  * @returns {number} The offset from UTC in hours. | ||||
|  * | ||||
|  * Generated by Trelent | ||||
|  */ | ||||
| function getTimezoneOffset(timeZone) { | ||||
|     const now = new Date(); | ||||
|     const tzString = now.toLocaleString("en-US", { | ||||
|  | @ -18,6 +24,13 @@ function getTimezoneOffset(timeZone) { | |||
|     return -offset; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
| * Returns a list of timezones sorted by their offset from UTC. | ||||
| * @param {Array} timezones - An array of timezone objects. | ||||
| * @returns {Array} A list of the given timezones sorted by their offset from UTC. | ||||
| * | ||||
| * Generated by Trelent | ||||
| */ | ||||
| export function timezoneList() { | ||||
|     let result = []; | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										56
									
								
								src/util.js
									
									
									
									
									
								
							
							
						
						
									
										56
									
								
								src/util.js
									
									
									
									
									
								
							|  | @ -7,7 +7,7 @@ | |||
| // Backend uses the compiled file util.js
 | ||||
| // Frontend uses util.ts
 | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0; | ||||
| exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0; | ||||
| const _dayjs = require("dayjs"); | ||||
| const dayjs = _dayjs; | ||||
| exports.isDev = process.env.NODE_ENV === "development"; | ||||
|  | @ -44,12 +44,60 @@ function ucfirst(str) { | |||
|     return firstLetter.toUpperCase() + str.substr(1); | ||||
| } | ||||
| exports.ucfirst = ucfirst; | ||||
| /** | ||||
|  * @deprecated Use log.debug | ||||
|  * @since https://github.com/louislam/uptime-kuma/pull/910
 | ||||
|  * @param msg | ||||
|  */ | ||||
| function debug(msg) { | ||||
|     if (exports.isDev) { | ||||
|         console.log(msg); | ||||
|     } | ||||
|     exports.log.log("", msg, "debug"); | ||||
| } | ||||
| exports.debug = debug; | ||||
| class Logger { | ||||
|     log(module, msg, level) { | ||||
|         module = module.toUpperCase(); | ||||
|         level = level.toUpperCase(); | ||||
|         const now = new Date().toISOString(); | ||||
|         const formattedMessage = (typeof msg === "string") ? `${now} [${module}] ${level}: ${msg}` : msg; | ||||
|         if (level === "INFO") { | ||||
|             console.info(formattedMessage); | ||||
|         } | ||||
|         else if (level === "WARN") { | ||||
|             console.warn(formattedMessage); | ||||
|         } | ||||
|         else if (level === "ERROR") { | ||||
|             console.error(formattedMessage); | ||||
|         } | ||||
|         else if (level === "DEBUG") { | ||||
|             if (exports.isDev) { | ||||
|                 console.debug(formattedMessage); | ||||
|             } | ||||
|         } | ||||
|         else { | ||||
|             console.log(formattedMessage); | ||||
|         } | ||||
|     } | ||||
|     info(module, msg) { | ||||
|         this.log(module, msg, "info"); | ||||
|     } | ||||
|     warn(module, msg) { | ||||
|         this.log(module, msg, "warn"); | ||||
|     } | ||||
|     error(module, msg) { | ||||
|         this.log(module, msg, "error"); | ||||
|     } | ||||
|     debug(module, msg) { | ||||
|         this.log(module, msg, "debug"); | ||||
|     } | ||||
|     exception(module, exception, msg) { | ||||
|         let finalMessage = exception; | ||||
|         if (msg) { | ||||
|             finalMessage = `${msg}: ${exception}`; | ||||
|         } | ||||
|         this.log(module, finalMessage, "error"); | ||||
|     } | ||||
| } | ||||
| exports.log = new Logger(); | ||||
| function polyfill() { | ||||
|     /** | ||||
|      * String.prototype.replaceAll() polyfill | ||||
|  |  | |||
							
								
								
									
										56
									
								
								src/util.ts
									
									
									
									
									
								
							
							
						
						
									
										56
									
								
								src/util.ts
									
									
									
									
									
								
							|  | @ -49,12 +49,66 @@ export function ucfirst(str: string) { | |||
|     return firstLetter.toUpperCase() + str.substr(1); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * @deprecated Use log.debug | ||||
|  * @since https://github.com/louislam/uptime-kuma/pull/910
 | ||||
|  * @param msg | ||||
|  */ | ||||
| export function debug(msg: any) { | ||||
|     log.log("", msg, "debug"); | ||||
| } | ||||
| 
 | ||||
| class Logger { | ||||
|     log(module: string, msg: any, level: string) { | ||||
|         module = module.toUpperCase(); | ||||
|         level = level.toUpperCase(); | ||||
| 
 | ||||
|         const now = new Date().toISOString(); | ||||
|         const formattedMessage = (typeof msg === "string") ? `${now} [${module}] ${level}: ${msg}` : msg; | ||||
| 
 | ||||
|         if (level === "INFO") { | ||||
|             console.info(formattedMessage); | ||||
|         } else if (level === "WARN") { | ||||
|             console.warn(formattedMessage); | ||||
|         } else if (level === "ERROR") { | ||||
|             console.error(formattedMessage); | ||||
|         } else if (level === "DEBUG") { | ||||
|             if (isDev) { | ||||
|         console.log(msg); | ||||
|                 console.debug(formattedMessage); | ||||
|             } | ||||
|         } else { | ||||
|             console.log(formattedMessage); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     info(module: string, msg: any) { | ||||
|         this.log(module, msg, "info"); | ||||
|     } | ||||
| 
 | ||||
|     warn(module: string, msg: any) { | ||||
|         this.log(module, msg, "warn"); | ||||
|     } | ||||
| 
 | ||||
|    error(module: string, msg: any) { | ||||
|        this.log(module, msg, "error"); | ||||
|     } | ||||
| 
 | ||||
|    debug(module: string, msg: any) { | ||||
|        this.log(module, msg, "debug"); | ||||
|     } | ||||
| 
 | ||||
|     exception(module: string, exception: any, msg: any) { | ||||
|         let finalMessage = exception | ||||
| 
 | ||||
|         if (msg) { | ||||
|             finalMessage = `${msg}: ${exception}` | ||||
|         } | ||||
| 
 | ||||
|         this.log(module, finalMessage , "error"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export const log = new Logger(); | ||||
| 
 | ||||
| declare global { interface String { replaceAll(str: string, newStr: string): string; } } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| const { genSecret, sleep, DOWN } = require("../src/util"); | ||||
| const { genSecret, DOWN } = require("../src/util"); | ||||
| const utilServerRewire = require("../server/util-server"); | ||||
| const Discord = require("../server/notification-providers/discord"); | ||||
| const axios = require("axios"); | ||||
|  |  | |||
|  | @ -1,7 +1,6 @@ | |||
| // eslint-disable-next-line no-unused-vars
 | ||||
| const { Page, Browser } = require("puppeteer"); | ||||
| const { sleep } = require("../src/util"); | ||||
| const axios = require("axios"); | ||||
| 
 | ||||
| /** | ||||
|  * Set back the correct data type for page object | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue