mirror of
				https://github.com/louislam/uptime-kuma.git
				synced 2025-10-31 19:39:20 +08:00 
			
		
		
		
	Merge branch 'louislam:master' into master
This commit is contained in:
		
							
								
								
									
										48
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								README.md
									
									
									
									
									
								
							| @@ -10,16 +10,16 @@ It is a self-hosted monitoring tool like "Uptime Robot". | ||||
|  | ||||
| <img src="https://louislam.net/uptimekuma/1.jpg" width="512" alt="" /> | ||||
|  | ||||
| ## Features | ||||
| ## ⭐ Features | ||||
|  | ||||
| * Monitoring uptime for HTTP(s) / TCP / Ping. | ||||
| * Fancy, Reactive, Fast UI/UX. | ||||
| * Notifications via Webhook, Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP) and more by Apprise. | ||||
| * 20 seconds interval. | ||||
|  | ||||
| ## How to Use | ||||
| ## 🔧 How to Install | ||||
|  | ||||
| ### Docker | ||||
| ### 🐳 Docker | ||||
|  | ||||
| ```bash | ||||
| # Create a volume | ||||
| @@ -31,18 +31,13 @@ docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name upti | ||||
|  | ||||
| Browse to http://localhost:3001 after started. | ||||
|  | ||||
| Change Port and Volume | ||||
|  | ||||
| ```bash | ||||
| docker run -d --restart=always -p <YOUR_PORT>:3001 -v <YOUR_DIR OR VOLUME>:/app/data --name uptime-kuma louislam/uptime-kuma:1 | ||||
| ``` | ||||
| If you want to change port and volume, or need to browse via a reserve proxy, please read: https://github.com/louislam/uptime-kuma/wiki/Installation. | ||||
|  | ||||
| ### Without Docker (x86/x64 only) | ||||
| ### 💪🏻 Without Docker (Recommanded for x86/x64 only) | ||||
|  | ||||
| Required Tools: Node.js >= 14, git and pm2. | ||||
|  | ||||
| (**Not recommanded for ARM CPU users.** Since there is no prebuilt for node-sqlite3, it is hard to get it running) | ||||
|  | ||||
| ```bash | ||||
| git clone https://github.com/louislam/uptime-kuma.git | ||||
| cd uptime-kuma | ||||
| @@ -56,33 +51,15 @@ npm run start-server | ||||
| # Install PM2 if you don't have: npm install pm2 -g | ||||
| pm2 start npm --name uptime-kuma -- run start-server | ||||
|  | ||||
| # Listen to different port or hostname | ||||
| pm2 start npm --name uptime-kuma -- run start-server -- --port=80 --hostname=0.0.0.0 | ||||
|  | ||||
| ``` | ||||
|  | ||||
| More useful commands if you have installed. | ||||
|  | ||||
| ```bash | ||||
| pm2 start uptime-kuma | ||||
| pm2 restart uptime-kuma | ||||
| pm2 stop uptime-kuma | ||||
| ``` | ||||
|  | ||||
| Browse to http://localhost:3001 after started. | ||||
|  | ||||
| ### (Optional) One more step for Reverse Proxy | ||||
| If you want to change port and hostname, or need to browse via a reserve proxy, please read: https://github.com/louislam/uptime-kuma/wiki/Installation. | ||||
|  | ||||
| This is optional for someone who want to do reverse proxy. | ||||
| ## 🆙 How to Update | ||||
|  | ||||
| Unlikely other web apps, Uptime Kuma is based on WebSocket. You need two more headers **"Upgrade"** and **"Connection"** in order to reverse proxy WebSocket. | ||||
|  | ||||
| Please read wiki for more info: | ||||
| https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy | ||||
|  | ||||
| ## How to Update | ||||
|  | ||||
| ### Docker | ||||
| ### 🆙🐳 Docker | ||||
|  | ||||
| Re-pull the latest docker image and create another container with the same volume. | ||||
|  | ||||
| @@ -97,9 +74,10 @@ docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name upti | ||||
|  | ||||
| PS: For every new release, it takes some time to build the docker image, please be patient if it is not available yet. | ||||
|  | ||||
| ### Without Docker | ||||
| ### 🆙 💪🏻 Without Docker | ||||
|  | ||||
| ```bash | ||||
| cd <uptime-kuma-directory> | ||||
| git fetch --all | ||||
| git checkout 1.1.0 --force | ||||
| npm install | ||||
| @@ -107,12 +85,12 @@ npm run build | ||||
| pm2 restart uptime-kuma | ||||
| ``` | ||||
|  | ||||
| ## What's Next? | ||||
| ## 🆕 What's Next? | ||||
|  | ||||
| I will mark requests/issues to the next milestone. | ||||
| https://github.com/louislam/uptime-kuma/milestones | ||||
|  | ||||
| ## More Screenshots | ||||
| ## 🖼 More Screenshots | ||||
|  | ||||
| Dark Mode: | ||||
|  | ||||
| @@ -144,3 +122,5 @@ If you want to report a bug or request a new feature. Free feel to open a new is | ||||
| If you want to modify Uptime Kuma, this guideline maybe useful for you: https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md | ||||
|  | ||||
| English proofreading is needed too, because my grammar is not that great sadly. Feel free to correct my grammar in this Readme, source code or wiki. | ||||
|  | ||||
| 🐻 | ||||
|   | ||||
							
								
								
									
										11
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								index.html
									
									
									
									
									
								
							| @@ -1,13 +1,12 @@ | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
|     <meta charset="UTF-8"/> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"/> | ||||
|     <meta charset="UTF-8" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||||
|     <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"> | ||||
|     <link rel="icon" type="image/svg+xml" href="/icon.svg"/> | ||||
|     <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5"> | ||||
|     <meta name="theme-color" content="#5cdd8b"/> | ||||
|     <meta name="description" content="Uptime Kuma monitoring tool"/> | ||||
|     <link rel="icon" type="image/svg+xml" href="/icon.svg" /> | ||||
|     <meta name="theme-color" id="theme-color" content="" /> | ||||
|     <meta name="description" content="Uptime Kuma monitoring tool" /> | ||||
|     <title>Uptime Kuma</title> | ||||
| </head> | ||||
| <body> | ||||
|   | ||||
							
								
								
									
										288
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										288
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -756,9 +756,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "@types/bootstrap": { | ||||
|       "version": "5.0.17", | ||||
|       "resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.0.17.tgz", | ||||
|       "integrity": "sha512-uQQQ3p+zw10VjZLvtCuKWI6QgVCYEnK/yHnno3gyEhikfQdiZexS2XPxjWRboGmX135o470GkmCta9eAgQMVLQ==", | ||||
|       "version": "5.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.1.1.tgz", | ||||
|       "integrity": "sha512-W/fEBlqwaJFh+3sCz/H88LPsLt/zLsEECFlrAOkrRPjWuo/ETl8u0JefIerCdc8+WukowQS1f60eIJOwkCBwhg==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "@popperjs/core": "^2.9.2", | ||||
| @@ -960,45 +960,45 @@ | ||||
|       } | ||||
|     }, | ||||
|     "@vitejs/plugin-vue": { | ||||
|       "version": "1.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.3.0.tgz", | ||||
|       "integrity": "sha512-wJvuJdTBjvucUX0vK4fuy60t+A9bJSZxc59vp1Y+8kiOd0NU5kFt4lay72gMWPeR+lSUjrTmGUq8Uzb99Jbw3A==", | ||||
|       "version": "1.4.0", | ||||
|       "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.4.0.tgz", | ||||
|       "integrity": "sha512-RkqfJHz9wdLKBp5Yi+kQL8BAljdrvPoccQm2PTZc/UcL4EjD11xsv2PPCduYx2oV1a/bpSKA3sD5sxOHFhz+LA==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "@vue/compiler-core": { | ||||
|       "version": "3.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.1.tgz", | ||||
|       "integrity": "sha512-UEJf2ZGww5wGVdrWIXIZo04KdJFGPmI2bHRUsBZ3AdyCAqJ5ykRXKOBn1OR1hvA2YzimudOEyHM+DpbBv91Kww==", | ||||
|       "version": "3.2.2", | ||||
|       "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.2.tgz", | ||||
|       "integrity": "sha512-QhCI0ZU5nAR0LMcLgzW3v75374tIrHGp8XG5CzJS7Nsy+iuignbE4MZ2XJfh5TGIrtpuzfWA4eTIfukZf/cRdg==", | ||||
|       "requires": { | ||||
|         "@babel/parser": "^7.12.0", | ||||
|         "@babel/types": "^7.12.0", | ||||
|         "@vue/shared": "3.2.1", | ||||
|         "@vue/shared": "3.2.2", | ||||
|         "estree-walker": "^2.0.1", | ||||
|         "source-map": "^0.6.1" | ||||
|       } | ||||
|     }, | ||||
|     "@vue/compiler-dom": { | ||||
|       "version": "3.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.1.tgz", | ||||
|       "integrity": "sha512-tXg8tkPb3j54zNfWqoao9T1JI41yWPz8TROzmif/QNNA46eq8/SRuRsBd36i47GWaz7mh+yg3vOJ87/YBjcMyQ==", | ||||
|       "version": "3.2.2", | ||||
|       "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.2.tgz", | ||||
|       "integrity": "sha512-ggcc+NV/ENIE0Uc3TxVE/sKrhYVpLepMAAmEiQ047332mbKOvUkowz4TTFZ+YkgOIuBOPP0XpCxmCMg7p874mA==", | ||||
|       "requires": { | ||||
|         "@vue/compiler-core": "3.2.1", | ||||
|         "@vue/shared": "3.2.1" | ||||
|         "@vue/compiler-core": "3.2.2", | ||||
|         "@vue/shared": "3.2.2" | ||||
|       } | ||||
|     }, | ||||
|     "@vue/compiler-sfc": { | ||||
|       "version": "3.1.5", | ||||
|       "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.1.5.tgz", | ||||
|       "integrity": "sha512-mtMY6xMvZeSRx9MTa1+NgJWndrkzVTdJ1pQAmAKQuxyb5LsHVvrgP7kcQFvxPHVpLVTORbTJWHaiqoKrJvi1iA==", | ||||
|       "version": "3.2.2", | ||||
|       "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.2.tgz", | ||||
|       "integrity": "sha512-hrtqpQ5L6IPn5v7yVRo7uvLcQxv0z1+KBjZBWMBOcrXz4t+PKUxU/SWd6Tl9T8FDmYlunzKUh6lcx+2CLo6f5A==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "@babel/parser": "^7.13.9", | ||||
|         "@babel/types": "^7.13.0", | ||||
|         "@types/estree": "^0.0.48", | ||||
|         "@vue/compiler-core": "3.1.5", | ||||
|         "@vue/compiler-dom": "3.1.5", | ||||
|         "@vue/compiler-ssr": "3.1.5", | ||||
|         "@vue/shared": "3.1.5", | ||||
|         "@vue/compiler-core": "3.2.2", | ||||
|         "@vue/compiler-dom": "3.2.2", | ||||
|         "@vue/compiler-ssr": "3.2.2", | ||||
|         "@vue/shared": "3.2.2", | ||||
|         "consolidate": "^0.16.0", | ||||
|         "estree-walker": "^2.0.1", | ||||
|         "hash-sum": "^2.0.0", | ||||
| @@ -1009,116 +1009,54 @@ | ||||
|         "postcss-modules": "^4.0.0", | ||||
|         "postcss-selector-parser": "^6.0.4", | ||||
|         "source-map": "^0.6.1" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "@vue/compiler-core": { | ||||
|           "version": "3.1.5", | ||||
|           "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.1.5.tgz", | ||||
|           "integrity": "sha512-TXBhFinoBaXKDykJzY26UEuQU1K07FOp/0Ie+OXySqqk0bS0ZO7Xvl7UmiTUPYcLrWbxWBR7Bs/y55AI0MNc2Q==", | ||||
|           "dev": true, | ||||
|           "requires": { | ||||
|             "@babel/parser": "^7.12.0", | ||||
|             "@babel/types": "^7.12.0", | ||||
|             "@vue/shared": "3.1.5", | ||||
|             "estree-walker": "^2.0.1", | ||||
|             "source-map": "^0.6.1" | ||||
|           } | ||||
|         }, | ||||
|         "@vue/compiler-dom": { | ||||
|           "version": "3.1.5", | ||||
|           "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.1.5.tgz", | ||||
|           "integrity": "sha512-ZsL3jqJ52OjGU/YiT/9XiuZAmWClKInZM2aFJh9gnsAPqOrj2JIELMbkIFpVKR/CrVO/f2VxfPiiQdQTr65jcQ==", | ||||
|           "dev": true, | ||||
|           "requires": { | ||||
|             "@vue/compiler-core": "3.1.5", | ||||
|             "@vue/shared": "3.1.5" | ||||
|           } | ||||
|         }, | ||||
|         "@vue/shared": { | ||||
|           "version": "3.1.5", | ||||
|           "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz", | ||||
|           "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==", | ||||
|           "dev": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "@vue/compiler-ssr": { | ||||
|       "version": "3.1.5", | ||||
|       "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.1.5.tgz", | ||||
|       "integrity": "sha512-CU5N7Di/a4lyJ18LGJxJYZS2a8PlLdWpWHX9p/XcsjT2TngMpj3QvHVRkuik2u8QrIDZ8OpYmTyj1WDNsOV+Dg==", | ||||
|       "version": "3.2.2", | ||||
|       "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.2.tgz", | ||||
|       "integrity": "sha512-rVl1agMFhdEN3Go0bCriXo+3cysxKIuRP0yh1Wd8ysRrKfAmokyDhUA8PrGSq2Ymj/LdZTh+4OKfj3p2+C+hlA==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "@vue/compiler-dom": "3.1.5", | ||||
|         "@vue/shared": "3.1.5" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "@vue/compiler-core": { | ||||
|           "version": "3.1.5", | ||||
|           "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.1.5.tgz", | ||||
|           "integrity": "sha512-TXBhFinoBaXKDykJzY26UEuQU1K07FOp/0Ie+OXySqqk0bS0ZO7Xvl7UmiTUPYcLrWbxWBR7Bs/y55AI0MNc2Q==", | ||||
|           "dev": true, | ||||
|           "requires": { | ||||
|             "@babel/parser": "^7.12.0", | ||||
|             "@babel/types": "^7.12.0", | ||||
|             "@vue/shared": "3.1.5", | ||||
|             "estree-walker": "^2.0.1", | ||||
|             "source-map": "^0.6.1" | ||||
|           } | ||||
|         }, | ||||
|         "@vue/compiler-dom": { | ||||
|           "version": "3.1.5", | ||||
|           "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.1.5.tgz", | ||||
|           "integrity": "sha512-ZsL3jqJ52OjGU/YiT/9XiuZAmWClKInZM2aFJh9gnsAPqOrj2JIELMbkIFpVKR/CrVO/f2VxfPiiQdQTr65jcQ==", | ||||
|           "dev": true, | ||||
|           "requires": { | ||||
|             "@vue/compiler-core": "3.1.5", | ||||
|             "@vue/shared": "3.1.5" | ||||
|           } | ||||
|         }, | ||||
|         "@vue/shared": { | ||||
|           "version": "3.1.5", | ||||
|           "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz", | ||||
|           "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==", | ||||
|           "dev": true | ||||
|         } | ||||
|         "@vue/compiler-dom": "3.2.2", | ||||
|         "@vue/shared": "3.2.2" | ||||
|       } | ||||
|     }, | ||||
|     "@vue/devtools-api": { | ||||
|       "version": "6.0.0-beta.14", | ||||
|       "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.0.0-beta.14.tgz", | ||||
|       "integrity": "sha512-44fPrrN1cqcs6bFkT0C+yxTM6PZXLbR+ESh1U1j8UD22yO04gXvxH62HApMjLbS3WqJO/iCNC+CYT+evPQh2EQ==" | ||||
|       "version": "6.0.0-beta.15", | ||||
|       "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.0.0-beta.15.tgz", | ||||
|       "integrity": "sha512-quBx4Jjpexo6KDiNUGFr/zF/2A4srKM9S9v2uHgMXSU//hjgq1eGzqkIFql8T9gfX5ZaVOUzYBP3jIdIR3PKIA==" | ||||
|     }, | ||||
|     "@vue/reactivity": { | ||||
|       "version": "3.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.1.tgz", | ||||
|       "integrity": "sha512-4Lja2KmyiKvuraDed6dXK2A6+r/7x7xGDA7vVR2Aqc8hQVu0+FWeVX+IBfiVOSpbZXFlHLNmCBFkbuWLQSlgxg==", | ||||
|       "version": "3.2.2", | ||||
|       "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.2.tgz", | ||||
|       "integrity": "sha512-IHjhtmrhK6dzacj/EnLQDWOaA3HuzzVk6w84qgV8EpS4uWGIJXiRalMRg6XvGW2ykJvIl3pLsF0aBFlTMRiLOA==", | ||||
|       "requires": { | ||||
|         "@vue/shared": "3.2.1" | ||||
|         "@vue/shared": "3.2.2" | ||||
|       } | ||||
|     }, | ||||
|     "@vue/runtime-core": { | ||||
|       "version": "3.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.1.tgz", | ||||
|       "integrity": "sha512-IsgelRM/5hYeRhz5+ECi66XvYDdjG2t4lARjHvCXw5s9Q4N6uIbjLMwtLzAWRxYf3/y258BrD+ehxAi943ScJg==", | ||||
|       "version": "3.2.2", | ||||
|       "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.2.tgz", | ||||
|       "integrity": "sha512-/aUk1+GO/VPX0oVxhbzSWE1zrf3/wGCsO1ALNisVokYftKqfqLDjbJHE6mrI2hx3MiuwbHrWjJClkGUVTIOPEQ==", | ||||
|       "requires": { | ||||
|         "@vue/reactivity": "3.2.1", | ||||
|         "@vue/shared": "3.2.1" | ||||
|         "@vue/reactivity": "3.2.2", | ||||
|         "@vue/shared": "3.2.2" | ||||
|       } | ||||
|     }, | ||||
|     "@vue/runtime-dom": { | ||||
|       "version": "3.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.1.tgz", | ||||
|       "integrity": "sha512-bUAHUSe49A5wYdHQ8wsLU1CMPXaG2fRuv2661mx/6Q9+20QxglT3ss8ZeL6AVRu16JNJMcdvTTsNpbnMbVc/lQ==", | ||||
|       "version": "3.2.2", | ||||
|       "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.2.tgz", | ||||
|       "integrity": "sha512-1Le/NpCfawCOfePfJezvWUF+oCVLU8N+IHN4oFDOxRe6/PgHNJ+yT+YdxFifBfI+TIAoXI/9PsnqzmJZV+xsmw==", | ||||
|       "requires": { | ||||
|         "@vue/runtime-core": "3.2.1", | ||||
|         "@vue/shared": "3.2.1", | ||||
|         "@vue/runtime-core": "3.2.2", | ||||
|         "@vue/shared": "3.2.2", | ||||
|         "csstype": "^2.6.8" | ||||
|       } | ||||
|     }, | ||||
|     "@vue/shared": { | ||||
|       "version": "3.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.1.tgz", | ||||
|       "integrity": "sha512-INN92dVBNgd0TW9BqfQQKx/HWGCHhUUbAV5EZ5FgSCiEdwuZsJbGt1mdnaD9IxGhpiyOjP2ClxGG8SFp7ELcWg==" | ||||
|       "version": "3.2.2", | ||||
|       "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.2.tgz", | ||||
|       "integrity": "sha512-dvYb318tk9uOzHtSaT3WII/HscQSIRzoCZ5GyxEb3JlkEXASpAUAQwKnvSe2CudnF8XHFRTB7VITWSnWNLZUtA==" | ||||
|     }, | ||||
|     "abbrev": { | ||||
|       "version": "1.1.1", | ||||
| @@ -1779,6 +1717,16 @@ | ||||
|       "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "chart.js": { | ||||
|       "version": "3.5.0", | ||||
|       "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.5.0.tgz", | ||||
|       "integrity": "sha512-J1a4EAb1Gi/KbhwDRmoovHTRuqT8qdF0kZ4XgwxpGethJHUdDrkqyPYwke0a+BuvSeUxPf8Cos6AX2AB8H8GLA==" | ||||
|     }, | ||||
|     "chartjs-adapter-dayjs": { | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/chartjs-adapter-dayjs/-/chartjs-adapter-dayjs-1.0.0.tgz", | ||||
|       "integrity": "sha512-EnbVqTJGFKLpg1TROLdCEufrzbmIa2oeLGx8O2Wdjw2EoMudoOo9+YFu+6CM0Z0hQ/v3yq/e/Y6efQMu22n8Jg==" | ||||
|     }, | ||||
|     "chokidar": { | ||||
|       "version": "3.5.2", | ||||
|       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", | ||||
| @@ -1945,9 +1893,9 @@ | ||||
|       "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" | ||||
|     }, | ||||
|     "core-js": { | ||||
|       "version": "3.16.0", | ||||
|       "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.16.0.tgz", | ||||
|       "integrity": "sha512-5+5VxRFmSf97nM8Jr2wzOwLqRo6zphH2aX+7KsAUONObyzakDNq2G/bgbhinxB4PoV9L3aXQYhiDKyIKWd2c8g==", | ||||
|       "version": "3.16.1", | ||||
|       "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.16.1.tgz", | ||||
|       "integrity": "sha512-AAkP8i35EbefU+JddyWi12AWE9f2N/qr/pwnDtWz4nyUIBGMJPX99ANFFRSw6FefM374lDujdtLDyhN2A/btHw==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "core-util-is": { | ||||
| @@ -2441,9 +2389,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "eslint-plugin-vue": { | ||||
|       "version": "7.15.1", | ||||
|       "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.15.1.tgz", | ||||
|       "integrity": "sha512-4/r+n/i+ovyeW2gVRRH92kpy4lkpFbyPR4BMxGBTLtGnwqOKKzjSo6EMSaT0RhWPvEjK9uifcY8e7z5n8BIEgw==", | ||||
|       "version": "7.16.0", | ||||
|       "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.16.0.tgz", | ||||
|       "integrity": "sha512-0E2dVvVC7I2Xm1HXyx+ZwPj9CNX4NJjs4K4r+GVsHWyt5Pew3JLD4fI7A91b2jeL0TXE7LlszrwLSTJU9eqehw==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "eslint-utils": "^2.1.0", | ||||
| @@ -4789,9 +4737,9 @@ | ||||
|       "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" | ||||
|     }, | ||||
|     "postcss": { | ||||
|       "version": "8.3.5", | ||||
|       "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.5.tgz", | ||||
|       "integrity": "sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA==", | ||||
|       "version": "8.3.6", | ||||
|       "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.6.tgz", | ||||
|       "integrity": "sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "colorette": "^1.2.2", | ||||
| @@ -4874,9 +4822,9 @@ | ||||
|       "dev": true | ||||
|     }, | ||||
|     "postcss-modules": { | ||||
|       "version": "4.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-4.1.3.tgz", | ||||
|       "integrity": "sha512-dBT39hrXe4OAVYJe/2ZuIZ9BzYhOe7t+IhedYeQ2OxKwDpAGlkEN/fR0fGnrbx4BvgbMReRX4hCubYK9cE/pJQ==", | ||||
|       "version": "4.2.2", | ||||
|       "resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-4.2.2.tgz", | ||||
|       "integrity": "sha512-/H08MGEmaalv/OU8j6bUKi/kZr2kqGF6huAW8m9UAgOLWtpFdhA14+gPBoymtqyv+D4MLsmqaF2zvIegdCxJXg==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "generic-names": "^2.0.1", | ||||
| @@ -5148,9 +5096,9 @@ | ||||
|       "dev": true | ||||
|     }, | ||||
|     "prom-client": { | ||||
|       "version": "13.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-13.1.0.tgz", | ||||
|       "integrity": "sha512-jT9VccZCWrJWXdyEtQddCDszYsiuWj5T0ekrPszi/WEegj3IZy6Mm09iOOVM86A4IKMWq8hZkT2dD9MaSe+sng==", | ||||
|       "version": "13.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-13.2.0.tgz", | ||||
|       "integrity": "sha512-wGr5mlNNdRNzEhRYXgboUU2LxHWIojxscJKmtG3R8f4/KiWqyYgXTLHs0+Ted7tG3zFT7pgHJbtomzZ1L0ARaQ==", | ||||
|       "requires": { | ||||
|         "tdigest": "^0.1.1" | ||||
|       } | ||||
| @@ -6805,13 +6753,82 @@ | ||||
|       } | ||||
|     }, | ||||
|     "vue": { | ||||
|       "version": "3.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.1.tgz", | ||||
|       "integrity": "sha512-0jhXluF5mzTAK5bXw/8yq4McvsI8HwEWI4cnQwJeN8NYGRbwh9wwuE4FNv1Kej9pxBB5ajTNsWr0M6DPs5EJZg==", | ||||
|       "version": "3.2.2", | ||||
|       "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.2.tgz", | ||||
|       "integrity": "sha512-D/LuzAV30CgNJYGyNheE/VUs5N4toL2IgmS6c9qeOxvyh0xyn4exyRqizpXIrsvfx34zG9x5gCI2tdRHCGvF9w==", | ||||
|       "requires": { | ||||
|         "@vue/compiler-dom": "3.2.1", | ||||
|         "@vue/runtime-dom": "3.2.1", | ||||
|         "@vue/shared": "3.2.1" | ||||
|         "@vue/compiler-dom": "3.2.2", | ||||
|         "@vue/runtime-dom": "3.2.2", | ||||
|         "@vue/shared": "3.2.2" | ||||
|       } | ||||
|     }, | ||||
|     "vue-chart-3": { | ||||
|       "version": "0.5.7", | ||||
|       "resolved": "https://registry.npmjs.org/vue-chart-3/-/vue-chart-3-0.5.7.tgz", | ||||
|       "integrity": "sha512-BccfPv2rodY6IOppYHvMluVmIJE1CHfp5uW2DXrHrm1kIzaafLwpQ5SwdrxuCevn/QhKoi7azzcxwRcoWbX9hg==", | ||||
|       "requires": { | ||||
|         "@vue/runtime-core": "^3.2.1", | ||||
|         "@vue/runtime-dom": "^3.2.1", | ||||
|         "csstype": "^3.0.8", | ||||
|         "lodash": "^4.17.21", | ||||
|         "nanoid": "^3.1.23", | ||||
|         "vue-demi": "^0.10.1" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "@vue/reactivity": { | ||||
|           "version": "3.2.1", | ||||
|           "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.1.tgz", | ||||
|           "integrity": "sha512-4Lja2KmyiKvuraDed6dXK2A6+r/7x7xGDA7vVR2Aqc8hQVu0+FWeVX+IBfiVOSpbZXFlHLNmCBFkbuWLQSlgxg==", | ||||
|           "requires": { | ||||
|             "@vue/shared": "3.2.1" | ||||
|           } | ||||
|         }, | ||||
|         "@vue/runtime-core": { | ||||
|           "version": "3.2.1", | ||||
|           "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.1.tgz", | ||||
|           "integrity": "sha512-IsgelRM/5hYeRhz5+ECi66XvYDdjG2t4lARjHvCXw5s9Q4N6uIbjLMwtLzAWRxYf3/y258BrD+ehxAi943ScJg==", | ||||
|           "requires": { | ||||
|             "@vue/reactivity": "3.2.1", | ||||
|             "@vue/shared": "3.2.1" | ||||
|           } | ||||
|         }, | ||||
|         "@vue/runtime-dom": { | ||||
|           "version": "3.2.1", | ||||
|           "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.1.tgz", | ||||
|           "integrity": "sha512-bUAHUSe49A5wYdHQ8wsLU1CMPXaG2fRuv2661mx/6Q9+20QxglT3ss8ZeL6AVRu16JNJMcdvTTsNpbnMbVc/lQ==", | ||||
|           "requires": { | ||||
|             "@vue/runtime-core": "3.2.1", | ||||
|             "@vue/shared": "3.2.1", | ||||
|             "csstype": "^2.6.8" | ||||
|           }, | ||||
|           "dependencies": { | ||||
|             "csstype": { | ||||
|               "version": "2.6.17", | ||||
|               "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.17.tgz", | ||||
|               "integrity": "sha512-u1wmTI1jJGzCJzWndZo8mk4wnPTZd1eOIYTYvuEyOQGfmDl3TrabCCfKnOC86FZwW/9djqTl933UF/cS425i9A==" | ||||
|             } | ||||
|           } | ||||
|         }, | ||||
|         "@vue/shared": { | ||||
|           "version": "3.2.1", | ||||
|           "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.1.tgz", | ||||
|           "integrity": "sha512-INN92dVBNgd0TW9BqfQQKx/HWGCHhUUbAV5EZ5FgSCiEdwuZsJbGt1mdnaD9IxGhpiyOjP2ClxGG8SFp7ELcWg==" | ||||
|         }, | ||||
|         "csstype": { | ||||
|           "version": "3.0.8", | ||||
|           "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz", | ||||
|           "integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==" | ||||
|         }, | ||||
|         "lodash": { | ||||
|           "version": "4.17.21", | ||||
|           "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", | ||||
|           "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" | ||||
|         }, | ||||
|         "nanoid": { | ||||
|           "version": "3.1.23", | ||||
|           "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", | ||||
|           "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "vue-confirm-dialog": { | ||||
| @@ -6819,6 +6836,11 @@ | ||||
|       "resolved": "https://registry.npmjs.org/vue-confirm-dialog/-/vue-confirm-dialog-1.0.2.tgz", | ||||
|       "integrity": "sha512-gTo1bMDWOLd/6ihmWv8VlPxhc9QaKoE5YqlsKydUOfrrQ3Q3taljF6yI+1TMtAtJLrvZ8DYrePhgBhY1VCJzbQ==" | ||||
|     }, | ||||
|     "vue-demi": { | ||||
|       "version": "0.10.1", | ||||
|       "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.10.1.tgz", | ||||
|       "integrity": "sha512-L6Oi+BvmMv6YXvqv5rJNCFHEKSVu7llpWWJczqmAQYOdmPPw5PNYoz1KKS//Fxhi+4QP64dsPjtmvnYGo1jemA==" | ||||
|     }, | ||||
|     "vue-eslint-parser": { | ||||
|       "version": "7.10.0", | ||||
|       "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.10.0.tgz", | ||||
| @@ -6865,9 +6887,9 @@ | ||||
|       "integrity": "sha512-Xp9fGJECns45v+v8jXbCIsAkCybYkEg0lNwr7Z6HDUSMyx2TEIK2giipPE+qXiShEc1Ipn+ZtttH2iq9hwXP4Q==" | ||||
|     }, | ||||
|     "vue-router": { | ||||
|       "version": "4.0.10", | ||||
|       "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.10.tgz", | ||||
|       "integrity": "sha512-YbPf6QnZpyyWfnk7CUt2Bme+vo7TLfg1nGZNkvYqKYh4vLaFw6Gn8bPGdmt5m4qrGnKoXLqc4htAsd3dIukICA==", | ||||
|       "version": "4.0.11", | ||||
|       "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.11.tgz", | ||||
|       "integrity": "sha512-sha6I8fx9HWtvTrFZfxZkiQQBpqSeT+UCwauYjkdOQYRvwsGwimlQQE2ayqUwuuXGzquFpCPoXzYKWlzL4OuXg==", | ||||
|       "requires": { | ||||
|         "@vue/devtools-api": "^6.0.0-beta.14" | ||||
|       } | ||||
|   | ||||
							
								
								
									
										21
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								package.json
									
									
									
									
									
								
							| @@ -31,11 +31,14 @@ | ||||
|         "@fortawesome/free-regular-svg-icons": "^5.15.4", | ||||
|         "@fortawesome/free-solid-svg-icons": "^5.15.4", | ||||
|         "@fortawesome/vue-fontawesome": "^3.0.0-4", | ||||
|         "@louislam/sqlite3": "^5.0.3", | ||||
|         "@popperjs/core": "^2.9.3", | ||||
|         "args-parser": "^1.3.0", | ||||
|         "axios": "^0.21.1", | ||||
|         "bcrypt": "^5.0.1", | ||||
|         "bootstrap": "^5.1.0", | ||||
|         "chart.js": "^3.5.0", | ||||
|         "chartjs-adapter-dayjs": "^1.0.0", | ||||
|         "command-exists": "^1.2.9", | ||||
|         "dayjs": "^1.10.6", | ||||
|         "express": "^4.17.1", | ||||
| @@ -45,29 +48,29 @@ | ||||
|         "jsonwebtoken": "^8.5.1", | ||||
|         "nodemailer": "^6.6.3", | ||||
|         "password-hash": "^1.2.2", | ||||
|         "prom-client": "^13.1.0", | ||||
|         "prom-client": "^13.2.0", | ||||
|         "prometheus-api-metrics": "^3.2.0", | ||||
|         "redbean-node": "0.0.21", | ||||
|         "socket.io": "^4.1.3", | ||||
|         "socket.io-client": "^4.1.3", | ||||
|         "@louislam/sqlite3": "^5.0.3", | ||||
|         "tcp-ping": "^0.1.1", | ||||
|         "v-pagination-3": "^0.1.6", | ||||
|         "vue": "^3.2.1", | ||||
|         "vue": "^3.2.2", | ||||
|         "vue-chart-3": "^0.5.7", | ||||
|         "vue-confirm-dialog": "^1.0.2", | ||||
|         "vue-multiselect": "^3.0.0-alpha.2", | ||||
|         "vue-router": "^4.0.10", | ||||
|         "vue-router": "^4.0.11", | ||||
|         "vue-toastification": "^2.0.0-rc.1" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "@babel/eslint-parser": "^7.15.0", | ||||
|         "@types/bootstrap": "^5.0.17", | ||||
|         "@types/bootstrap": "^5.1.1", | ||||
|         "@vitejs/plugin-legacy": "^1.5.1", | ||||
|         "@vitejs/plugin-vue": "^1.3.0", | ||||
|         "@vue/compiler-sfc": "^3.1.5", | ||||
|         "core-js": "^3.16.0", | ||||
|         "@vitejs/plugin-vue": "^1.4.0", | ||||
|         "@vue/compiler-sfc": "^3.2.2", | ||||
|         "core-js": "^3.16.1", | ||||
|         "eslint": "^7.32.0", | ||||
|         "eslint-plugin-vue": "^7.15.1", | ||||
|         "eslint-plugin-vue": "^7.16.0", | ||||
|         "sass": "^1.37.5", | ||||
|         "stylelint": "^13.13.1", | ||||
|         "stylelint-config-recommended": "^5.0.0", | ||||
|   | ||||
| @@ -11,7 +11,7 @@ const { tcping, ping, checkCertificate, checkStatusCode } = require("../util-ser | ||||
| const { R } = require("redbean-node"); | ||||
| const { BeanModel } = require("redbean-node/dist/bean-model"); | ||||
| const { Notification } = require("../notification") | ||||
| const version = require("../package.json").version; | ||||
| const version = require("../../package.json").version; | ||||
|  | ||||
| /** | ||||
|  * status: | ||||
| @@ -80,6 +80,10 @@ class Monitor extends BeanModel { | ||||
|  | ||||
|         const beat = async () => { | ||||
|  | ||||
|             // Expose here for prometheus update | ||||
|             // undefined if not https | ||||
|             let tlsInfo = undefined; | ||||
|  | ||||
|             if (! previousBeat) { | ||||
|                 previousBeat = await R.findOne("heartbeat", " monitor_id = ? ORDER BY time DESC", [ | ||||
|                     this.id, | ||||
| @@ -133,7 +137,7 @@ class Monitor extends BeanModel { | ||||
|                     let certInfoStartTime = dayjs().valueOf(); | ||||
|                     if (this.getUrl()?.protocol === "https:") { | ||||
|                         try { | ||||
|                             await this.updateTlsInfo(checkCertificate(res)); | ||||
|                             tlsInfo = await this.updateTlsInfo(checkCertificate(res)); | ||||
|                         } catch (e) { | ||||
|                             if (e.message !== "No TLS certificate in response") { | ||||
|                                 console.error(e.message) | ||||
| @@ -255,7 +259,7 @@ class Monitor extends BeanModel { | ||||
|                 console.warn(`Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Type: ${this.type}`) | ||||
|             } | ||||
|  | ||||
|             prometheus.update(bean) | ||||
|             prometheus.update(bean, tlsInfo) | ||||
|  | ||||
|             io.to(this.user_id).emit("heartbeat", bean.toJSON()); | ||||
|  | ||||
| @@ -290,7 +294,7 @@ class Monitor extends BeanModel { | ||||
|     /** | ||||
|      * Store TLS info to database | ||||
|      * @param checkCertificateResult | ||||
|      * @returns {Promise<void>} | ||||
|      * @returns {Promise<object>} | ||||
|      */ | ||||
|     async updateTlsInfo(checkCertificateResult) { | ||||
|         let tls_info_bean = await R.findOne("monitor_tls_info", "monitor_id = ?", [ | ||||
| @@ -302,6 +306,8 @@ class Monitor extends BeanModel { | ||||
|         } | ||||
|         tls_info_bean.info_json = JSON.stringify(checkCertificateResult); | ||||
|         await R.store(tls_info_bean); | ||||
|  | ||||
|         return checkCertificateResult; | ||||
|     } | ||||
|  | ||||
|     static async sendStats(io, monitorID, userID) { | ||||
|   | ||||
| @@ -193,6 +193,34 @@ class Notification { | ||||
|                 console.log(error) | ||||
|                 return false; | ||||
|             } | ||||
|         } else if (notification.type === "octopush") { | ||||
|             try { | ||||
|                 let config = { | ||||
|                     headers: { | ||||
|                         'api-key': notification.octopushAPIKey, | ||||
|                         'api-login': notification.octopushLogin, | ||||
|                         'cache-control': 'no-cache' | ||||
|                     } | ||||
|                 }; | ||||
|                 let data = { | ||||
|                     "recipients": [ | ||||
|                         { | ||||
|                             "phone_number": notification.octopushPhoneNumber | ||||
|                         } | ||||
|                     ], | ||||
|                     //octopush not supporting non ascii char | ||||
|                     "text": msg.replace(/[^\x00-\x7F]/g, ""), | ||||
|                     "type": notification.octopushSMSType, | ||||
|                     "purpose": "alert", | ||||
|                     "sender": notification.octopushSenderName | ||||
|                 }; | ||||
|  | ||||
|                 await axios.post(`https://api.octopush.com/v1/public/sms-campaign/send`, data, config) | ||||
|                 return true; | ||||
|             } catch (error) { | ||||
|                 console.log(error) | ||||
|                 return false; | ||||
|             } | ||||
|         } else if (notification.type === "slack") { | ||||
|             try { | ||||
|                 if (heartbeatJSON == null) { | ||||
| @@ -326,6 +354,41 @@ class Notification { | ||||
|                 throwGeneralAxiosError(error) | ||||
|             } | ||||
|  | ||||
|         } else if (notification.type === "pushbullet") { | ||||
|             try { | ||||
|                 let pushbulletUrl = `https://api.pushbullet.com/v2/pushes`; | ||||
|                 let config = { | ||||
|                     headers: { | ||||
|                         'Access-Token': notification.pushbulletAccessToken, | ||||
|                         'Content-Type': 'application/json' | ||||
|                     } | ||||
|                 }; | ||||
|                 if (heartbeatJSON == null) { | ||||
|                     let testdata = { | ||||
|                         "type": "note",  | ||||
|                         "title": "Uptime Kuma Alert", | ||||
|                         "body": "Testing Successful.", | ||||
|                     } | ||||
|                     await axios.post(pushbulletUrl, testdata, config) | ||||
|                 } else if (heartbeatJSON["status"] == 0) { | ||||
|                     let downdata = { | ||||
|                         "type": "note",  | ||||
|                         "title": "UptimeKuma Alert:" + monitorJSON["name"], | ||||
|                         "body": "[🔴 Down]" + heartbeatJSON["msg"] + "\nTime (UTC):" + heartbeatJSON["time"], | ||||
|                     } | ||||
|                     await axios.post(pushbulletUrl, downdata, config) | ||||
|                 } else if (heartbeatJSON["status"] == 1) { | ||||
|                     let updata = { | ||||
|                         "type": "note",  | ||||
|                         "title": "UptimeKuma Alert:" + monitorJSON["name"], | ||||
|                         "body": "[✅ Up]" + heartbeatJSON["msg"] + "\nTime (UTC):" + heartbeatJSON["time"], | ||||
|                     } | ||||
|                     await axios.post(pushbulletUrl, updata, config) | ||||
|                 } | ||||
|                 return okMsg; | ||||
|             } catch (error) { | ||||
|                 throwGeneralAxiosError(error) | ||||
|             } | ||||
|         } else { | ||||
|             throw new Error("Notification type is not supported") | ||||
|         } | ||||
|   | ||||
| @@ -1,22 +1,33 @@ | ||||
| const PrometheusClient = require('prom-client'); | ||||
| const PrometheusClient = require("prom-client"); | ||||
|  | ||||
| const commonLabels = [ | ||||
|     'monitor_name', | ||||
|     'monitor_type', | ||||
|     'monitor_url', | ||||
|     'monitor_hostname', | ||||
|     'monitor_port', | ||||
|     "monitor_name", | ||||
|     "monitor_type", | ||||
|     "monitor_url", | ||||
|     "monitor_hostname", | ||||
|     "monitor_port", | ||||
| ] | ||||
|  | ||||
| const monitor_cert_days_remaining = new PrometheusClient.Gauge({ | ||||
|     name: "monitor_cert_days_remaining", | ||||
|     help: "The number of days remaining until the certificate expires", | ||||
|     labelNames: commonLabels | ||||
| }); | ||||
|  | ||||
| const monitor_cert_is_valid = new PrometheusClient.Gauge({ | ||||
|     name: "monitor_cert_is_valid", | ||||
|     help: "Is the certificate still valid? (1 = Yes, 0= No)", | ||||
|     labelNames: commonLabels | ||||
| }); | ||||
| const monitor_response_time = new PrometheusClient.Gauge({ | ||||
|     name: 'monitor_response_time', | ||||
|     help: 'Monitor Response Time (ms)', | ||||
|     name: "monitor_response_time", | ||||
|     help: "Monitor Response Time (ms)", | ||||
|     labelNames: commonLabels | ||||
| }); | ||||
|  | ||||
| const monitor_status = new PrometheusClient.Gauge({ | ||||
|     name: 'monitor_status', | ||||
|     help: 'Monitor Status (1 = UP, 0= DOWN)', | ||||
|     name: "monitor_status", | ||||
|     help: "Monitor Status (1 = UP, 0= DOWN)", | ||||
|     labelNames: commonLabels | ||||
| }); | ||||
|  | ||||
| @@ -33,7 +44,27 @@ class Prometheus { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     update(heartbeat) { | ||||
|     update(heartbeat, tlsInfo) { | ||||
|         if (typeof tlsInfo !== "undefined") { | ||||
|             try { | ||||
|                 let is_valid = 0 | ||||
|                 if (tlsInfo.valid == true) { | ||||
|                     is_valid = 1 | ||||
|                 } else { | ||||
|                     is_valid = 0 | ||||
|                 } | ||||
|                 monitor_cert_is_valid.set(this.monitorLabelValues, is_valid) | ||||
|             } catch (e) { | ||||
|                 console.error(e) | ||||
|             } | ||||
|  | ||||
|             try { | ||||
|                 monitor_cert_days_remaining.set(this.monitorLabelValues, tlsInfo.daysRemaining) | ||||
|             } catch (e) { | ||||
|                 console.error(e) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         try { | ||||
|             monitor_status.set(this.monitorLabelValues, heartbeat.status) | ||||
|         } catch (e) { | ||||
| @@ -41,7 +72,7 @@ class Prometheus { | ||||
|         } | ||||
|  | ||||
|         try { | ||||
|             if (typeof heartbeat.ping === 'number') { | ||||
|             if (typeof heartbeat.ping === "number") { | ||||
|                 monitor_response_time.set(this.monitorLabelValues, heartbeat.ping) | ||||
|             } else { | ||||
|                 // Is it good? | ||||
|   | ||||
| @@ -22,8 +22,10 @@ | ||||
|                                 <option value="slack">Slack</option> | ||||
|                                 <option value="pushover">Pushover</option> | ||||
|                                 <option value="pushy">Pushy</option> | ||||
|                                 <option value="octopush">Octopush</option> | ||||
|                                 <option value="lunasea">LunaSea</option> | ||||
|                                 <option value="apprise">Apprise (Support 50+ Notification services)</option> | ||||
|                                 <option value="pushbullet">Pushbullet</option> | ||||
|                             </select> | ||||
|                         </div> | ||||
|  | ||||
| @@ -252,6 +254,37 @@ | ||||
|                             </p> | ||||
|                         </template> | ||||
|  | ||||
|                         <template v-if="notification.type === 'octopush'"> | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="octopush-key" class="form-label">API KEY</label> | ||||
|                                 <input id="octopush-key" v-model="notification.octopushAPIKey" type="text" class="form-control" required> | ||||
|                                 <label for="octopush-login" class="form-label">API LOGIN</label> | ||||
|                                 <input id="octopush-login" v-model="notification.octopushLogin" type="text" class="form-control" required> | ||||
|                             </div> | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="octopush-type-sms" class="form-label">SMS Type</label> | ||||
|                                 <select id="octopush-type-sms" v-model="notification.octopushSMSType" class="form-select"> | ||||
|                                     <option value="sms_premium">Premium (Fast - recommended for alerting)</option> | ||||
|                                     <option value="sms_low_cost">Low Cost (Slow, sometimes blocked by operator)</option> | ||||
|                                 </select> | ||||
|                                 <div class="form-text"> | ||||
|                                     Check octopush prices <a href="https://octopush.com/tarifs-sms-international/" target="_blank">https://octopush.com/tarifs-sms-international/</a>. | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="octopush-phone-number" class="form-label">Phone number (intl format, eg : +33612345678) </label> | ||||
|                                 <input id="octopush-phone-number" v-model="notification.octopushPhoneNumber" type="text" class="form-control" required> | ||||
|                             </div> | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="octopush-sender-name" class="form-label">SMS Sender Name : 3-11 alphanumeric characters and space (a-zA-Z0-9)</label> | ||||
|                                 <input id="octopush-sender-name" v-model="notification.octopushSenderName" type="text" minlength="3" maxlength="11" class="form-control"> | ||||
|                             </div> | ||||
|  | ||||
|                             <p style="margin-top: 8px;"> | ||||
|                                 More info on: <a href="https://octopush.com/api-sms-documentation/envoi-de-sms/" target="_blank">https://octopush.com/api-sms-documentation/envoi-de-sms/</a> | ||||
|                             </p> | ||||
|                         </template> | ||||
|  | ||||
|                         <template v-if="notification.type === 'pushover'"> | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="pushover-user" class="form-label">User Key<span style="color:red;"><sup>*</sup></span></label> | ||||
| @@ -339,6 +372,17 @@ | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </template> | ||||
|  | ||||
|                         <template v-if="notification.type === 'pushbullet'"> | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="pushbullet-access-token" class="form-label">Access Token</label> | ||||
|                                 <input id="pushbullet-access-token" v-model="notification.pushbulletAccessToken" type="text" class="form-control" required> | ||||
|                             </div> | ||||
|  | ||||
|                             <p style="margin-top: 8px;"> | ||||
|                                 More info on: <a href="https://docs.pushbullet.com" target="_blank">https://docs.pushbullet.com</a> | ||||
|                             </p> | ||||
|                         </template> | ||||
|                     </div> | ||||
|                     <div class="modal-footer"> | ||||
|                         <button v-if="id" type="button" class="btn btn-danger" :disabled="processing" @click="deleteConfirm"> | ||||
|   | ||||
							
								
								
									
										152
									
								
								src/components/PingChart.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								src/components/PingChart.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,152 @@ | ||||
| <template> | ||||
|     <LineChart :chart-data="chartData" :height="100" :options="chartOptions" /> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { BarController, BarElement, Chart, Filler, LinearScale, LineController, LineElement, PointElement, TimeScale, Tooltip } from "chart.js"; | ||||
| import dayjs from "dayjs"; | ||||
| import utc from "dayjs/plugin/utc"; | ||||
| import timezone from "dayjs/plugin/timezone"; | ||||
| import "chartjs-adapter-dayjs"; | ||||
| import { LineChart } from "vue-chart-3"; | ||||
| dayjs.extend(utc); | ||||
| dayjs.extend(timezone); | ||||
|  | ||||
| Chart.register(LineController, BarController, LineElement, PointElement, TimeScale, BarElement, LinearScale, Tooltip, Filler); | ||||
|  | ||||
| export default { | ||||
|     components: { LineChart }, | ||||
|     props: { | ||||
|         monitorId: { | ||||
|             type: Number, | ||||
|             required: true, | ||||
|         }, | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
|             chartPeriodHrs: 6, | ||||
|         }; | ||||
|     }, | ||||
|     computed: { | ||||
|         chartOptions() { | ||||
|             return { | ||||
|                 responsive: true, | ||||
|                 layout: { | ||||
|                     padding: { | ||||
|                         left: 10, | ||||
|                         right: 30, | ||||
|                         top: 30, | ||||
|                         bottom: 10, | ||||
|                     }, | ||||
|                 }, | ||||
|  | ||||
|                 elements: { | ||||
|                     point: { | ||||
|                         radius: 0, | ||||
|                     }, | ||||
|                     bar: { | ||||
|                         barThickness: "flex", | ||||
|                     } | ||||
|                 }, | ||||
|                 scales: { | ||||
|                     x: { | ||||
|                         type: "time", | ||||
|                         time: { | ||||
|                             unit: "minute", | ||||
|                         }, | ||||
|                         ticks: { | ||||
|                             maxRotation: 0, | ||||
|                             autoSkipPadding: 10, | ||||
|                         }, | ||||
|                         grid: { | ||||
|                             color: this.$root.theme === "light" ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.1)", | ||||
|                         }, | ||||
|                     }, | ||||
|                     y: { | ||||
|                         title: { | ||||
|                             display: true, | ||||
|                             text: "Response Time (ms)", | ||||
|                         }, | ||||
|                         offset: false, | ||||
|                         grid: { | ||||
|                             color: this.$root.theme === "light" ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.1)", | ||||
|                         }, | ||||
|                     }, | ||||
|                     y1: { | ||||
|                         display: false, | ||||
|                         position: "right", | ||||
|                         grid: { | ||||
|                             drawOnChartArea: false, | ||||
|                         }, | ||||
|                         min: 0, | ||||
|                         max: 1, | ||||
|                         offset: false, | ||||
|                     }, | ||||
|                 }, | ||||
|                 bounds: "ticks", | ||||
|                 plugins: { | ||||
|                     tooltip: { | ||||
|                         mode: "nearest", | ||||
|                         intersect: false, | ||||
|                         padding: 10, | ||||
|                         filter: function (tooltipItem) { | ||||
|                             return tooltipItem.datasetIndex === 0; | ||||
|                         }, | ||||
|                         callbacks: { | ||||
|                             label: (context) => { | ||||
|                                 return ` ${new Intl.NumberFormat().format(context.parsed.y)} ms` | ||||
|                             }, | ||||
|                         } | ||||
|                     }, | ||||
|                     legend: { | ||||
|                         display: false, | ||||
|                     }, | ||||
|                 }, | ||||
|             } | ||||
|         }, | ||||
|         chartData() { | ||||
|             let ping_data = []; | ||||
|             let down_data = []; | ||||
|             if (this.monitorId in this.$root.heartbeatList) { | ||||
|                 ping_data = this.$root.heartbeatList[this.monitorId] | ||||
|                     .filter( | ||||
|                         (beat) => dayjs.utc(beat.time).tz(this.$root.timezone).isAfter(dayjs().subtract(this.chartPeriodHrs, "hours"))) | ||||
|                     .map((beat) => { | ||||
|                         return { | ||||
|                             x: dayjs.utc(beat.time).tz(this.$root.timezone).format("YYYY-MM-DD HH:mm:ss"), | ||||
|                             y: beat.ping, | ||||
|                         }; | ||||
|                     }); | ||||
|                 down_data = this.$root.heartbeatList[this.monitorId] | ||||
|                     .filter( | ||||
|                         (beat) => dayjs.utc(beat.time).tz(this.$root.timezone).isAfter(dayjs().subtract(this.chartPeriodHrs, "hours"))) | ||||
|                     .map((beat) => { | ||||
|                         return { | ||||
|                             x: dayjs.utc(beat.time).tz(this.$root.timezone).format("YYYY-MM-DD HH:mm:ss"), | ||||
|                             y: beat.status === 0 ? 1 : 0, | ||||
|                         }; | ||||
|                     }); | ||||
|             } | ||||
|             return { | ||||
|                 datasets: [ | ||||
|                     { | ||||
|                         data: ping_data, | ||||
|                         fill: "origin", | ||||
|                         tension: 0.2, | ||||
|                         borderColor: "#5CDD8B", | ||||
|                         backgroundColor: "#5CDD8B38", | ||||
|                         yAxisID: "y", | ||||
|                     }, | ||||
|                     { | ||||
|                         type: "bar", | ||||
|                         data: down_data, | ||||
|                         borderColor: "#00000000", | ||||
|                         backgroundColor: "#DC354568", | ||||
|                         yAxisID: "y1", | ||||
|                     }, | ||||
|                 ], | ||||
|             }; | ||||
|         }, | ||||
|     }, | ||||
| }; | ||||
| </script> | ||||
| @@ -2,7 +2,7 @@ export default { | ||||
|  | ||||
|     data() { | ||||
|         return { | ||||
|             system: (window.matchMedia("(prefers-color-scheme: dark)")) ? "dark" : "light", | ||||
|             system: (window.matchMedia("(prefers-color-scheme: dark)").matches) ? "dark" : "light", | ||||
|             userTheme: localStorage.theme, | ||||
|         }; | ||||
|     }, | ||||
| @@ -14,6 +14,7 @@ export default { | ||||
|         } | ||||
|  | ||||
|         document.body.classList.add(this.theme); | ||||
|         this.updateThemeColorMeta(); | ||||
|     }, | ||||
|  | ||||
|     computed: { | ||||
| @@ -33,6 +34,17 @@ export default { | ||||
|         theme(to, from) { | ||||
|             document.body.classList.remove(from); | ||||
|             document.body.classList.add(this.theme); | ||||
|             this.updateThemeColorMeta(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     methods: { | ||||
|         updateThemeColorMeta() { | ||||
|             if (this.theme === "dark") { | ||||
|                 document.querySelector("#theme-color").setAttribute("content", "#161B22"); | ||||
|             } else { | ||||
|                 document.querySelector("#theme-color").setAttribute("content", "#5cdd8b"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -42,7 +42,11 @@ | ||||
|             <div class="col"> | ||||
|                 <h4>{{ pingTitle }}</h4> | ||||
|                 <p>(Current)</p> | ||||
|                 <span class="num"><CountUp :value="ping" /></span> | ||||
|                 <span class="num"> | ||||
|                     <a href="#" @click.prevent="showPingChartBox = !showPingChartBox"> | ||||
|                         <CountUp :value="ping" /> | ||||
|                     </a> | ||||
|                 </span> | ||||
|             </div> | ||||
|             <div class="col"> | ||||
|                 <h4>Avg. {{ pingTitle }}</h4> | ||||
| @@ -70,6 +74,14 @@ | ||||
|         </div> | ||||
|     </div> | ||||
|  | ||||
|     <div v-if="showPingChartBox" class="shadow-box big-padding text-center"> | ||||
|         <div class="row"> | ||||
|             <div class="col"> | ||||
|                 <PingChart :monitor-id="monitor.id" /> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
|  | ||||
|     <div v-if="showCertInfoBox" class="shadow-box big-padding text-center"> | ||||
|         <div class="row"> | ||||
|             <div class="col"> | ||||
| @@ -155,6 +167,7 @@ | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { defineAsyncComponent } from "vue"; | ||||
| import { useToast } from "vue-toastification" | ||||
| const toast = useToast() | ||||
| import Confirm from "../components/Confirm.vue"; | ||||
| @@ -164,6 +177,7 @@ import Datetime from "../components/Datetime.vue"; | ||||
| import CountUp from "../components/CountUp.vue"; | ||||
| import Uptime from "../components/Uptime.vue"; | ||||
| import Pagination from "v-pagination-3"; | ||||
| const PingChart = defineAsyncComponent(() => import("../components/PingChart.vue")); | ||||
|  | ||||
| export default { | ||||
|     components: { | ||||
| @@ -174,6 +188,7 @@ export default { | ||||
|         Confirm, | ||||
|         Status, | ||||
|         Pagination, | ||||
|         PingChart, | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
| @@ -181,6 +196,7 @@ export default { | ||||
|             perPage: 25, | ||||
|             heartBeatList: [], | ||||
|             toggleCertInfoBox: false, | ||||
|             showPingChartBox: true, | ||||
|         } | ||||
|     }, | ||||
|     computed: { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user