mirror of
				https://github.com/louislam/uptime-kuma.git
				synced 2025-11-01 03:49:24 +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="" /> | <img src="https://louislam.net/uptimekuma/1.jpg" width="512" alt="" /> | ||||||
|  |  | ||||||
| ## Features | ## ⭐ Features | ||||||
|  |  | ||||||
| * Monitoring uptime for HTTP(s) / TCP / Ping. | * Monitoring uptime for HTTP(s) / TCP / Ping. | ||||||
| * Fancy, Reactive, Fast UI/UX. | * Fancy, Reactive, Fast UI/UX. | ||||||
| * Notifications via Webhook, Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP) and more by Apprise. | * Notifications via Webhook, Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP) and more by Apprise. | ||||||
| * 20 seconds interval. | * 20 seconds interval. | ||||||
|  |  | ||||||
| ## How to Use | ## 🔧 How to Install | ||||||
|  |  | ||||||
| ### Docker | ### 🐳 Docker | ||||||
|  |  | ||||||
| ```bash | ```bash | ||||||
| # Create a volume | # 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. | Browse to http://localhost:3001 after started. | ||||||
|  |  | ||||||
| Change Port and Volume |  | ||||||
|  |  | ||||||
| ```bash | 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. | ||||||
| docker run -d --restart=always -p <YOUR_PORT>:3001 -v <YOUR_DIR OR VOLUME>:/app/data --name uptime-kuma louislam/uptime-kuma:1 |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ### Without Docker (x86/x64 only) | ### 💪🏻 Without Docker (Recommanded for x86/x64 only) | ||||||
|  |  | ||||||
| Required Tools: Node.js >= 14, git and pm2. | 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 | ```bash | ||||||
| git clone https://github.com/louislam/uptime-kuma.git | git clone https://github.com/louislam/uptime-kuma.git | ||||||
| cd uptime-kuma | cd uptime-kuma | ||||||
| @@ -56,33 +51,15 @@ npm run start-server | |||||||
| # Install PM2 if you don't have: npm install pm2 -g | # Install PM2 if you don't have: npm install pm2 -g | ||||||
| pm2 start npm --name uptime-kuma -- run start-server | 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. | 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. | ### 🆙🐳 Docker | ||||||
|  |  | ||||||
| Please read wiki for more info: |  | ||||||
| https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy |  | ||||||
|  |  | ||||||
| ## How to Update |  | ||||||
|  |  | ||||||
| ### Docker |  | ||||||
|  |  | ||||||
| Re-pull the latest docker image and create another container with the same volume. | 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. | 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 | ```bash | ||||||
|  | cd <uptime-kuma-directory> | ||||||
| git fetch --all | git fetch --all | ||||||
| git checkout 1.1.0 --force | git checkout 1.1.0 --force | ||||||
| npm install | npm install | ||||||
| @@ -107,12 +85,12 @@ npm run build | |||||||
| pm2 restart uptime-kuma | pm2 restart uptime-kuma | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ## What's Next? | ## 🆕 What's Next? | ||||||
|  |  | ||||||
| I will mark requests/issues to the next milestone. | I will mark requests/issues to the next milestone. | ||||||
| https://github.com/louislam/uptime-kuma/milestones | https://github.com/louislam/uptime-kuma/milestones | ||||||
|  |  | ||||||
| ## More Screenshots | ## 🖼 More Screenshots | ||||||
|  |  | ||||||
| Dark Mode: | 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 | 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. | 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> | <!DOCTYPE html> | ||||||
| <html lang="en"> | <html lang="en"> | ||||||
| <head> | <head> | ||||||
|     <meta charset="UTF-8"/> |     <meta charset="UTF-8" /> | ||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"/> |     <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="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"> | ||||||
|     <link rel="icon" type="image/svg+xml" href="/icon.svg"/> |     <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" id="theme-color" content="" /> | ||||||
|     <meta name="theme-color" content="#5cdd8b"/> |     <meta name="description" content="Uptime Kuma monitoring tool" /> | ||||||
|     <meta name="description" content="Uptime Kuma monitoring tool"/> |  | ||||||
|     <title>Uptime Kuma</title> |     <title>Uptime Kuma</title> | ||||||
| </head> | </head> | ||||||
| <body> | <body> | ||||||
|   | |||||||
							
								
								
									
										288
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										288
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -756,9 +756,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "@types/bootstrap": { |     "@types/bootstrap": { | ||||||
|       "version": "5.0.17", |       "version": "5.1.1", | ||||||
|       "resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.0.17.tgz", |       "resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.1.1.tgz", | ||||||
|       "integrity": "sha512-uQQQ3p+zw10VjZLvtCuKWI6QgVCYEnK/yHnno3gyEhikfQdiZexS2XPxjWRboGmX135o470GkmCta9eAgQMVLQ==", |       "integrity": "sha512-W/fEBlqwaJFh+3sCz/H88LPsLt/zLsEECFlrAOkrRPjWuo/ETl8u0JefIerCdc8+WukowQS1f60eIJOwkCBwhg==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "requires": { |       "requires": { | ||||||
|         "@popperjs/core": "^2.9.2", |         "@popperjs/core": "^2.9.2", | ||||||
| @@ -960,45 +960,45 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "@vitejs/plugin-vue": { |     "@vitejs/plugin-vue": { | ||||||
|       "version": "1.3.0", |       "version": "1.4.0", | ||||||
|       "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.3.0.tgz", |       "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.4.0.tgz", | ||||||
|       "integrity": "sha512-wJvuJdTBjvucUX0vK4fuy60t+A9bJSZxc59vp1Y+8kiOd0NU5kFt4lay72gMWPeR+lSUjrTmGUq8Uzb99Jbw3A==", |       "integrity": "sha512-RkqfJHz9wdLKBp5Yi+kQL8BAljdrvPoccQm2PTZc/UcL4EjD11xsv2PPCduYx2oV1a/bpSKA3sD5sxOHFhz+LA==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "@vue/compiler-core": { |     "@vue/compiler-core": { | ||||||
|       "version": "3.2.1", |       "version": "3.2.2", | ||||||
|       "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.1.tgz", |       "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.2.tgz", | ||||||
|       "integrity": "sha512-UEJf2ZGww5wGVdrWIXIZo04KdJFGPmI2bHRUsBZ3AdyCAqJ5ykRXKOBn1OR1hvA2YzimudOEyHM+DpbBv91Kww==", |       "integrity": "sha512-QhCI0ZU5nAR0LMcLgzW3v75374tIrHGp8XG5CzJS7Nsy+iuignbE4MZ2XJfh5TGIrtpuzfWA4eTIfukZf/cRdg==", | ||||||
|       "requires": { |       "requires": { | ||||||
|         "@babel/parser": "^7.12.0", |         "@babel/parser": "^7.12.0", | ||||||
|         "@babel/types": "^7.12.0", |         "@babel/types": "^7.12.0", | ||||||
|         "@vue/shared": "3.2.1", |         "@vue/shared": "3.2.2", | ||||||
|         "estree-walker": "^2.0.1", |         "estree-walker": "^2.0.1", | ||||||
|         "source-map": "^0.6.1" |         "source-map": "^0.6.1" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "@vue/compiler-dom": { |     "@vue/compiler-dom": { | ||||||
|       "version": "3.2.1", |       "version": "3.2.2", | ||||||
|       "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.1.tgz", |       "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.2.tgz", | ||||||
|       "integrity": "sha512-tXg8tkPb3j54zNfWqoao9T1JI41yWPz8TROzmif/QNNA46eq8/SRuRsBd36i47GWaz7mh+yg3vOJ87/YBjcMyQ==", |       "integrity": "sha512-ggcc+NV/ENIE0Uc3TxVE/sKrhYVpLepMAAmEiQ047332mbKOvUkowz4TTFZ+YkgOIuBOPP0XpCxmCMg7p874mA==", | ||||||
|       "requires": { |       "requires": { | ||||||
|         "@vue/compiler-core": "3.2.1", |         "@vue/compiler-core": "3.2.2", | ||||||
|         "@vue/shared": "3.2.1" |         "@vue/shared": "3.2.2" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "@vue/compiler-sfc": { |     "@vue/compiler-sfc": { | ||||||
|       "version": "3.1.5", |       "version": "3.2.2", | ||||||
|       "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.1.5.tgz", |       "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.2.tgz", | ||||||
|       "integrity": "sha512-mtMY6xMvZeSRx9MTa1+NgJWndrkzVTdJ1pQAmAKQuxyb5LsHVvrgP7kcQFvxPHVpLVTORbTJWHaiqoKrJvi1iA==", |       "integrity": "sha512-hrtqpQ5L6IPn5v7yVRo7uvLcQxv0z1+KBjZBWMBOcrXz4t+PKUxU/SWd6Tl9T8FDmYlunzKUh6lcx+2CLo6f5A==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "requires": { |       "requires": { | ||||||
|         "@babel/parser": "^7.13.9", |         "@babel/parser": "^7.13.9", | ||||||
|         "@babel/types": "^7.13.0", |         "@babel/types": "^7.13.0", | ||||||
|         "@types/estree": "^0.0.48", |         "@types/estree": "^0.0.48", | ||||||
|         "@vue/compiler-core": "3.1.5", |         "@vue/compiler-core": "3.2.2", | ||||||
|         "@vue/compiler-dom": "3.1.5", |         "@vue/compiler-dom": "3.2.2", | ||||||
|         "@vue/compiler-ssr": "3.1.5", |         "@vue/compiler-ssr": "3.2.2", | ||||||
|         "@vue/shared": "3.1.5", |         "@vue/shared": "3.2.2", | ||||||
|         "consolidate": "^0.16.0", |         "consolidate": "^0.16.0", | ||||||
|         "estree-walker": "^2.0.1", |         "estree-walker": "^2.0.1", | ||||||
|         "hash-sum": "^2.0.0", |         "hash-sum": "^2.0.0", | ||||||
| @@ -1009,116 +1009,54 @@ | |||||||
|         "postcss-modules": "^4.0.0", |         "postcss-modules": "^4.0.0", | ||||||
|         "postcss-selector-parser": "^6.0.4", |         "postcss-selector-parser": "^6.0.4", | ||||||
|         "source-map": "^0.6.1" |         "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": { |     "@vue/compiler-ssr": { | ||||||
|       "version": "3.1.5", |       "version": "3.2.2", | ||||||
|       "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.1.5.tgz", |       "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.2.tgz", | ||||||
|       "integrity": "sha512-CU5N7Di/a4lyJ18LGJxJYZS2a8PlLdWpWHX9p/XcsjT2TngMpj3QvHVRkuik2u8QrIDZ8OpYmTyj1WDNsOV+Dg==", |       "integrity": "sha512-rVl1agMFhdEN3Go0bCriXo+3cysxKIuRP0yh1Wd8ysRrKfAmokyDhUA8PrGSq2Ymj/LdZTh+4OKfj3p2+C+hlA==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "requires": { |       "requires": { | ||||||
|         "@vue/compiler-dom": "3.1.5", |         "@vue/compiler-dom": "3.2.2", | ||||||
|         "@vue/shared": "3.1.5" |         "@vue/shared": "3.2.2" | ||||||
|       }, |  | ||||||
|       "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/devtools-api": { |     "@vue/devtools-api": { | ||||||
|       "version": "6.0.0-beta.14", |       "version": "6.0.0-beta.15", | ||||||
|       "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.0.0-beta.14.tgz", |       "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.0.0-beta.15.tgz", | ||||||
|       "integrity": "sha512-44fPrrN1cqcs6bFkT0C+yxTM6PZXLbR+ESh1U1j8UD22yO04gXvxH62HApMjLbS3WqJO/iCNC+CYT+evPQh2EQ==" |       "integrity": "sha512-quBx4Jjpexo6KDiNUGFr/zF/2A4srKM9S9v2uHgMXSU//hjgq1eGzqkIFql8T9gfX5ZaVOUzYBP3jIdIR3PKIA==" | ||||||
|     }, |     }, | ||||||
|     "@vue/reactivity": { |     "@vue/reactivity": { | ||||||
|       "version": "3.2.1", |       "version": "3.2.2", | ||||||
|       "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.1.tgz", |       "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.2.tgz", | ||||||
|       "integrity": "sha512-4Lja2KmyiKvuraDed6dXK2A6+r/7x7xGDA7vVR2Aqc8hQVu0+FWeVX+IBfiVOSpbZXFlHLNmCBFkbuWLQSlgxg==", |       "integrity": "sha512-IHjhtmrhK6dzacj/EnLQDWOaA3HuzzVk6w84qgV8EpS4uWGIJXiRalMRg6XvGW2ykJvIl3pLsF0aBFlTMRiLOA==", | ||||||
|       "requires": { |       "requires": { | ||||||
|         "@vue/shared": "3.2.1" |         "@vue/shared": "3.2.2" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "@vue/runtime-core": { |     "@vue/runtime-core": { | ||||||
|       "version": "3.2.1", |       "version": "3.2.2", | ||||||
|       "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.1.tgz", |       "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.2.tgz", | ||||||
|       "integrity": "sha512-IsgelRM/5hYeRhz5+ECi66XvYDdjG2t4lARjHvCXw5s9Q4N6uIbjLMwtLzAWRxYf3/y258BrD+ehxAi943ScJg==", |       "integrity": "sha512-/aUk1+GO/VPX0oVxhbzSWE1zrf3/wGCsO1ALNisVokYftKqfqLDjbJHE6mrI2hx3MiuwbHrWjJClkGUVTIOPEQ==", | ||||||
|       "requires": { |       "requires": { | ||||||
|         "@vue/reactivity": "3.2.1", |         "@vue/reactivity": "3.2.2", | ||||||
|         "@vue/shared": "3.2.1" |         "@vue/shared": "3.2.2" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "@vue/runtime-dom": { |     "@vue/runtime-dom": { | ||||||
|       "version": "3.2.1", |       "version": "3.2.2", | ||||||
|       "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.1.tgz", |       "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.2.tgz", | ||||||
|       "integrity": "sha512-bUAHUSe49A5wYdHQ8wsLU1CMPXaG2fRuv2661mx/6Q9+20QxglT3ss8ZeL6AVRu16JNJMcdvTTsNpbnMbVc/lQ==", |       "integrity": "sha512-1Le/NpCfawCOfePfJezvWUF+oCVLU8N+IHN4oFDOxRe6/PgHNJ+yT+YdxFifBfI+TIAoXI/9PsnqzmJZV+xsmw==", | ||||||
|       "requires": { |       "requires": { | ||||||
|         "@vue/runtime-core": "3.2.1", |         "@vue/runtime-core": "3.2.2", | ||||||
|         "@vue/shared": "3.2.1", |         "@vue/shared": "3.2.2", | ||||||
|         "csstype": "^2.6.8" |         "csstype": "^2.6.8" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "@vue/shared": { |     "@vue/shared": { | ||||||
|       "version": "3.2.1", |       "version": "3.2.2", | ||||||
|       "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.1.tgz", |       "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.2.tgz", | ||||||
|       "integrity": "sha512-INN92dVBNgd0TW9BqfQQKx/HWGCHhUUbAV5EZ5FgSCiEdwuZsJbGt1mdnaD9IxGhpiyOjP2ClxGG8SFp7ELcWg==" |       "integrity": "sha512-dvYb318tk9uOzHtSaT3WII/HscQSIRzoCZ5GyxEb3JlkEXASpAUAQwKnvSe2CudnF8XHFRTB7VITWSnWNLZUtA==" | ||||||
|     }, |     }, | ||||||
|     "abbrev": { |     "abbrev": { | ||||||
|       "version": "1.1.1", |       "version": "1.1.1", | ||||||
| @@ -1779,6 +1717,16 @@ | |||||||
|       "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", |       "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", | ||||||
|       "dev": true |       "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": { |     "chokidar": { | ||||||
|       "version": "3.5.2", |       "version": "3.5.2", | ||||||
|       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", |       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", | ||||||
| @@ -1945,9 +1893,9 @@ | |||||||
|       "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" |       "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" | ||||||
|     }, |     }, | ||||||
|     "core-js": { |     "core-js": { | ||||||
|       "version": "3.16.0", |       "version": "3.16.1", | ||||||
|       "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.16.0.tgz", |       "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.16.1.tgz", | ||||||
|       "integrity": "sha512-5+5VxRFmSf97nM8Jr2wzOwLqRo6zphH2aX+7KsAUONObyzakDNq2G/bgbhinxB4PoV9L3aXQYhiDKyIKWd2c8g==", |       "integrity": "sha512-AAkP8i35EbefU+JddyWi12AWE9f2N/qr/pwnDtWz4nyUIBGMJPX99ANFFRSw6FefM374lDujdtLDyhN2A/btHw==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "core-util-is": { |     "core-util-is": { | ||||||
| @@ -2441,9 +2389,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "eslint-plugin-vue": { |     "eslint-plugin-vue": { | ||||||
|       "version": "7.15.1", |       "version": "7.16.0", | ||||||
|       "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.15.1.tgz", |       "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.16.0.tgz", | ||||||
|       "integrity": "sha512-4/r+n/i+ovyeW2gVRRH92kpy4lkpFbyPR4BMxGBTLtGnwqOKKzjSo6EMSaT0RhWPvEjK9uifcY8e7z5n8BIEgw==", |       "integrity": "sha512-0E2dVvVC7I2Xm1HXyx+ZwPj9CNX4NJjs4K4r+GVsHWyt5Pew3JLD4fI7A91b2jeL0TXE7LlszrwLSTJU9eqehw==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "requires": { |       "requires": { | ||||||
|         "eslint-utils": "^2.1.0", |         "eslint-utils": "^2.1.0", | ||||||
| @@ -4789,9 +4737,9 @@ | |||||||
|       "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" |       "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" | ||||||
|     }, |     }, | ||||||
|     "postcss": { |     "postcss": { | ||||||
|       "version": "8.3.5", |       "version": "8.3.6", | ||||||
|       "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.5.tgz", |       "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.6.tgz", | ||||||
|       "integrity": "sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA==", |       "integrity": "sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "requires": { |       "requires": { | ||||||
|         "colorette": "^1.2.2", |         "colorette": "^1.2.2", | ||||||
| @@ -4874,9 +4822,9 @@ | |||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "postcss-modules": { |     "postcss-modules": { | ||||||
|       "version": "4.1.3", |       "version": "4.2.2", | ||||||
|       "resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-4.1.3.tgz", |       "resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-4.2.2.tgz", | ||||||
|       "integrity": "sha512-dBT39hrXe4OAVYJe/2ZuIZ9BzYhOe7t+IhedYeQ2OxKwDpAGlkEN/fR0fGnrbx4BvgbMReRX4hCubYK9cE/pJQ==", |       "integrity": "sha512-/H08MGEmaalv/OU8j6bUKi/kZr2kqGF6huAW8m9UAgOLWtpFdhA14+gPBoymtqyv+D4MLsmqaF2zvIegdCxJXg==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "requires": { |       "requires": { | ||||||
|         "generic-names": "^2.0.1", |         "generic-names": "^2.0.1", | ||||||
| @@ -5148,9 +5096,9 @@ | |||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "prom-client": { |     "prom-client": { | ||||||
|       "version": "13.1.0", |       "version": "13.2.0", | ||||||
|       "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-13.1.0.tgz", |       "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-13.2.0.tgz", | ||||||
|       "integrity": "sha512-jT9VccZCWrJWXdyEtQddCDszYsiuWj5T0ekrPszi/WEegj3IZy6Mm09iOOVM86A4IKMWq8hZkT2dD9MaSe+sng==", |       "integrity": "sha512-wGr5mlNNdRNzEhRYXgboUU2LxHWIojxscJKmtG3R8f4/KiWqyYgXTLHs0+Ted7tG3zFT7pgHJbtomzZ1L0ARaQ==", | ||||||
|       "requires": { |       "requires": { | ||||||
|         "tdigest": "^0.1.1" |         "tdigest": "^0.1.1" | ||||||
|       } |       } | ||||||
| @@ -6805,13 +6753,82 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "vue": { |     "vue": { | ||||||
|       "version": "3.2.1", |       "version": "3.2.2", | ||||||
|       "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.1.tgz", |       "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.2.tgz", | ||||||
|       "integrity": "sha512-0jhXluF5mzTAK5bXw/8yq4McvsI8HwEWI4cnQwJeN8NYGRbwh9wwuE4FNv1Kej9pxBB5ajTNsWr0M6DPs5EJZg==", |       "integrity": "sha512-D/LuzAV30CgNJYGyNheE/VUs5N4toL2IgmS6c9qeOxvyh0xyn4exyRqizpXIrsvfx34zG9x5gCI2tdRHCGvF9w==", | ||||||
|       "requires": { |       "requires": { | ||||||
|         "@vue/compiler-dom": "3.2.1", |         "@vue/compiler-dom": "3.2.2", | ||||||
|         "@vue/runtime-dom": "3.2.1", |         "@vue/runtime-dom": "3.2.2", | ||||||
|         "@vue/shared": "3.2.1" |         "@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": { |     "vue-confirm-dialog": { | ||||||
| @@ -6819,6 +6836,11 @@ | |||||||
|       "resolved": "https://registry.npmjs.org/vue-confirm-dialog/-/vue-confirm-dialog-1.0.2.tgz", |       "resolved": "https://registry.npmjs.org/vue-confirm-dialog/-/vue-confirm-dialog-1.0.2.tgz", | ||||||
|       "integrity": "sha512-gTo1bMDWOLd/6ihmWv8VlPxhc9QaKoE5YqlsKydUOfrrQ3Q3taljF6yI+1TMtAtJLrvZ8DYrePhgBhY1VCJzbQ==" |       "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": { |     "vue-eslint-parser": { | ||||||
|       "version": "7.10.0", |       "version": "7.10.0", | ||||||
|       "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.10.0.tgz", |       "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==" |       "integrity": "sha512-Xp9fGJECns45v+v8jXbCIsAkCybYkEg0lNwr7Z6HDUSMyx2TEIK2giipPE+qXiShEc1Ipn+ZtttH2iq9hwXP4Q==" | ||||||
|     }, |     }, | ||||||
|     "vue-router": { |     "vue-router": { | ||||||
|       "version": "4.0.10", |       "version": "4.0.11", | ||||||
|       "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.10.tgz", |       "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.11.tgz", | ||||||
|       "integrity": "sha512-YbPf6QnZpyyWfnk7CUt2Bme+vo7TLfg1nGZNkvYqKYh4vLaFw6Gn8bPGdmt5m4qrGnKoXLqc4htAsd3dIukICA==", |       "integrity": "sha512-sha6I8fx9HWtvTrFZfxZkiQQBpqSeT+UCwauYjkdOQYRvwsGwimlQQE2ayqUwuuXGzquFpCPoXzYKWlzL4OuXg==", | ||||||
|       "requires": { |       "requires": { | ||||||
|         "@vue/devtools-api": "^6.0.0-beta.14" |         "@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-regular-svg-icons": "^5.15.4", | ||||||
|         "@fortawesome/free-solid-svg-icons": "^5.15.4", |         "@fortawesome/free-solid-svg-icons": "^5.15.4", | ||||||
|         "@fortawesome/vue-fontawesome": "^3.0.0-4", |         "@fortawesome/vue-fontawesome": "^3.0.0-4", | ||||||
|  |         "@louislam/sqlite3": "^5.0.3", | ||||||
|         "@popperjs/core": "^2.9.3", |         "@popperjs/core": "^2.9.3", | ||||||
|         "args-parser": "^1.3.0", |         "args-parser": "^1.3.0", | ||||||
|         "axios": "^0.21.1", |         "axios": "^0.21.1", | ||||||
|         "bcrypt": "^5.0.1", |         "bcrypt": "^5.0.1", | ||||||
|         "bootstrap": "^5.1.0", |         "bootstrap": "^5.1.0", | ||||||
|  |         "chart.js": "^3.5.0", | ||||||
|  |         "chartjs-adapter-dayjs": "^1.0.0", | ||||||
|         "command-exists": "^1.2.9", |         "command-exists": "^1.2.9", | ||||||
|         "dayjs": "^1.10.6", |         "dayjs": "^1.10.6", | ||||||
|         "express": "^4.17.1", |         "express": "^4.17.1", | ||||||
| @@ -45,29 +48,29 @@ | |||||||
|         "jsonwebtoken": "^8.5.1", |         "jsonwebtoken": "^8.5.1", | ||||||
|         "nodemailer": "^6.6.3", |         "nodemailer": "^6.6.3", | ||||||
|         "password-hash": "^1.2.2", |         "password-hash": "^1.2.2", | ||||||
|         "prom-client": "^13.1.0", |         "prom-client": "^13.2.0", | ||||||
|         "prometheus-api-metrics": "^3.2.0", |         "prometheus-api-metrics": "^3.2.0", | ||||||
|         "redbean-node": "0.0.21", |         "redbean-node": "0.0.21", | ||||||
|         "socket.io": "^4.1.3", |         "socket.io": "^4.1.3", | ||||||
|         "socket.io-client": "^4.1.3", |         "socket.io-client": "^4.1.3", | ||||||
|         "@louislam/sqlite3": "^5.0.3", |  | ||||||
|         "tcp-ping": "^0.1.1", |         "tcp-ping": "^0.1.1", | ||||||
|         "v-pagination-3": "^0.1.6", |         "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-confirm-dialog": "^1.0.2", | ||||||
|         "vue-multiselect": "^3.0.0-alpha.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" |         "vue-toastification": "^2.0.0-rc.1" | ||||||
|     }, |     }, | ||||||
|     "devDependencies": { |     "devDependencies": { | ||||||
|         "@babel/eslint-parser": "^7.15.0", |         "@babel/eslint-parser": "^7.15.0", | ||||||
|         "@types/bootstrap": "^5.0.17", |         "@types/bootstrap": "^5.1.1", | ||||||
|         "@vitejs/plugin-legacy": "^1.5.1", |         "@vitejs/plugin-legacy": "^1.5.1", | ||||||
|         "@vitejs/plugin-vue": "^1.3.0", |         "@vitejs/plugin-vue": "^1.4.0", | ||||||
|         "@vue/compiler-sfc": "^3.1.5", |         "@vue/compiler-sfc": "^3.2.2", | ||||||
|         "core-js": "^3.16.0", |         "core-js": "^3.16.1", | ||||||
|         "eslint": "^7.32.0", |         "eslint": "^7.32.0", | ||||||
|         "eslint-plugin-vue": "^7.15.1", |         "eslint-plugin-vue": "^7.16.0", | ||||||
|         "sass": "^1.37.5", |         "sass": "^1.37.5", | ||||||
|         "stylelint": "^13.13.1", |         "stylelint": "^13.13.1", | ||||||
|         "stylelint-config-recommended": "^5.0.0", |         "stylelint-config-recommended": "^5.0.0", | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ const { tcping, ping, checkCertificate, checkStatusCode } = require("../util-ser | |||||||
| const { R } = require("redbean-node"); | const { R } = require("redbean-node"); | ||||||
| const { BeanModel } = require("redbean-node/dist/bean-model"); | const { BeanModel } = require("redbean-node/dist/bean-model"); | ||||||
| const { Notification } = require("../notification") | const { Notification } = require("../notification") | ||||||
| const version = require("../package.json").version; | const version = require("../../package.json").version; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * status: |  * status: | ||||||
| @@ -80,6 +80,10 @@ class Monitor extends BeanModel { | |||||||
|  |  | ||||||
|         const beat = async () => { |         const beat = async () => { | ||||||
|  |  | ||||||
|  |             // Expose here for prometheus update | ||||||
|  |             // undefined if not https | ||||||
|  |             let tlsInfo = undefined; | ||||||
|  |  | ||||||
|             if (! previousBeat) { |             if (! previousBeat) { | ||||||
|                 previousBeat = await R.findOne("heartbeat", " monitor_id = ? ORDER BY time DESC", [ |                 previousBeat = await R.findOne("heartbeat", " monitor_id = ? ORDER BY time DESC", [ | ||||||
|                     this.id, |                     this.id, | ||||||
| @@ -133,7 +137,7 @@ class Monitor extends BeanModel { | |||||||
|                     let certInfoStartTime = dayjs().valueOf(); |                     let certInfoStartTime = dayjs().valueOf(); | ||||||
|                     if (this.getUrl()?.protocol === "https:") { |                     if (this.getUrl()?.protocol === "https:") { | ||||||
|                         try { |                         try { | ||||||
|                             await this.updateTlsInfo(checkCertificate(res)); |                             tlsInfo = await this.updateTlsInfo(checkCertificate(res)); | ||||||
|                         } catch (e) { |                         } catch (e) { | ||||||
|                             if (e.message !== "No TLS certificate in response") { |                             if (e.message !== "No TLS certificate in response") { | ||||||
|                                 console.error(e.message) |                                 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}`) |                 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()); |             io.to(this.user_id).emit("heartbeat", bean.toJSON()); | ||||||
|  |  | ||||||
| @@ -290,7 +294,7 @@ class Monitor extends BeanModel { | |||||||
|     /** |     /** | ||||||
|      * Store TLS info to database |      * Store TLS info to database | ||||||
|      * @param checkCertificateResult |      * @param checkCertificateResult | ||||||
|      * @returns {Promise<void>} |      * @returns {Promise<object>} | ||||||
|      */ |      */ | ||||||
|     async updateTlsInfo(checkCertificateResult) { |     async updateTlsInfo(checkCertificateResult) { | ||||||
|         let tls_info_bean = await R.findOne("monitor_tls_info", "monitor_id = ?", [ |         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); |         tls_info_bean.info_json = JSON.stringify(checkCertificateResult); | ||||||
|         await R.store(tls_info_bean); |         await R.store(tls_info_bean); | ||||||
|  |  | ||||||
|  |         return checkCertificateResult; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     static async sendStats(io, monitorID, userID) { |     static async sendStats(io, monitorID, userID) { | ||||||
|   | |||||||
| @@ -193,6 +193,34 @@ class Notification { | |||||||
|                 console.log(error) |                 console.log(error) | ||||||
|                 return false; |                 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") { |         } else if (notification.type === "slack") { | ||||||
|             try { |             try { | ||||||
|                 if (heartbeatJSON == null) { |                 if (heartbeatJSON == null) { | ||||||
| @@ -326,6 +354,41 @@ class Notification { | |||||||
|                 throwGeneralAxiosError(error) |                 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 { |         } else { | ||||||
|             throw new Error("Notification type is not supported") |             throw new Error("Notification type is not supported") | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -1,22 +1,33 @@ | |||||||
| const PrometheusClient = require('prom-client'); | const PrometheusClient = require("prom-client"); | ||||||
|  |  | ||||||
| const commonLabels = [ | const commonLabels = [ | ||||||
|     'monitor_name', |     "monitor_name", | ||||||
|     'monitor_type', |     "monitor_type", | ||||||
|     'monitor_url', |     "monitor_url", | ||||||
|     'monitor_hostname', |     "monitor_hostname", | ||||||
|     'monitor_port', |     "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({ | const monitor_response_time = new PrometheusClient.Gauge({ | ||||||
|     name: 'monitor_response_time', |     name: "monitor_response_time", | ||||||
|     help: 'Monitor Response Time (ms)', |     help: "Monitor Response Time (ms)", | ||||||
|     labelNames: commonLabels |     labelNames: commonLabels | ||||||
| }); | }); | ||||||
|  |  | ||||||
| const monitor_status = new PrometheusClient.Gauge({ | const monitor_status = new PrometheusClient.Gauge({ | ||||||
|     name: 'monitor_status', |     name: "monitor_status", | ||||||
|     help: 'Monitor Status (1 = UP, 0= DOWN)', |     help: "Monitor Status (1 = UP, 0= DOWN)", | ||||||
|     labelNames: commonLabels |     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 { |         try { | ||||||
|             monitor_status.set(this.monitorLabelValues, heartbeat.status) |             monitor_status.set(this.monitorLabelValues, heartbeat.status) | ||||||
|         } catch (e) { |         } catch (e) { | ||||||
| @@ -41,7 +72,7 @@ class Prometheus { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         try { |         try { | ||||||
|             if (typeof heartbeat.ping === 'number') { |             if (typeof heartbeat.ping === "number") { | ||||||
|                 monitor_response_time.set(this.monitorLabelValues, heartbeat.ping) |                 monitor_response_time.set(this.monitorLabelValues, heartbeat.ping) | ||||||
|             } else { |             } else { | ||||||
|                 // Is it good? |                 // Is it good? | ||||||
|   | |||||||
| @@ -22,8 +22,10 @@ | |||||||
|                                 <option value="slack">Slack</option> |                                 <option value="slack">Slack</option> | ||||||
|                                 <option value="pushover">Pushover</option> |                                 <option value="pushover">Pushover</option> | ||||||
|                                 <option value="pushy">Pushy</option> |                                 <option value="pushy">Pushy</option> | ||||||
|  |                                 <option value="octopush">Octopush</option> | ||||||
|                                 <option value="lunasea">LunaSea</option> |                                 <option value="lunasea">LunaSea</option> | ||||||
|                                 <option value="apprise">Apprise (Support 50+ Notification services)</option> |                                 <option value="apprise">Apprise (Support 50+ Notification services)</option> | ||||||
|  |                                 <option value="pushbullet">Pushbullet</option> | ||||||
|                             </select> |                             </select> | ||||||
|                         </div> |                         </div> | ||||||
|  |  | ||||||
| @@ -252,6 +254,37 @@ | |||||||
|                             </p> |                             </p> | ||||||
|                         </template> |                         </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'"> |                         <template v-if="notification.type === 'pushover'"> | ||||||
|                             <div class="mb-3"> |                             <div class="mb-3"> | ||||||
|                                 <label for="pushover-user" class="form-label">User Key<span style="color:red;"><sup>*</sup></span></label> |                                 <label for="pushover-user" class="form-label">User Key<span style="color:red;"><sup>*</sup></span></label> | ||||||
| @@ -339,6 +372,17 @@ | |||||||
|                                 </div> |                                 </div> | ||||||
|                             </div> |                             </div> | ||||||
|                         </template> |                         </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> | ||||||
|                     <div class="modal-footer"> |                     <div class="modal-footer"> | ||||||
|                         <button v-if="id" type="button" class="btn btn-danger" :disabled="processing" @click="deleteConfirm"> |                         <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() { |     data() { | ||||||
|         return { |         return { | ||||||
|             system: (window.matchMedia("(prefers-color-scheme: dark)")) ? "dark" : "light", |             system: (window.matchMedia("(prefers-color-scheme: dark)").matches) ? "dark" : "light", | ||||||
|             userTheme: localStorage.theme, |             userTheme: localStorage.theme, | ||||||
|         }; |         }; | ||||||
|     }, |     }, | ||||||
| @@ -14,6 +14,7 @@ export default { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         document.body.classList.add(this.theme); |         document.body.classList.add(this.theme); | ||||||
|  |         this.updateThemeColorMeta(); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     computed: { |     computed: { | ||||||
| @@ -33,6 +34,17 @@ export default { | |||||||
|         theme(to, from) { |         theme(to, from) { | ||||||
|             document.body.classList.remove(from); |             document.body.classList.remove(from); | ||||||
|             document.body.classList.add(this.theme); |             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"> |             <div class="col"> | ||||||
|                 <h4>{{ pingTitle }}</h4> |                 <h4>{{ pingTitle }}</h4> | ||||||
|                 <p>(Current)</p> |                 <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> | ||||||
|             <div class="col"> |             <div class="col"> | ||||||
|                 <h4>Avg. {{ pingTitle }}</h4> |                 <h4>Avg. {{ pingTitle }}</h4> | ||||||
| @@ -70,6 +74,14 @@ | |||||||
|         </div> |         </div> | ||||||
|     </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 v-if="showCertInfoBox" class="shadow-box big-padding text-center"> | ||||||
|         <div class="row"> |         <div class="row"> | ||||||
|             <div class="col"> |             <div class="col"> | ||||||
| @@ -155,6 +167,7 @@ | |||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
|  | import { defineAsyncComponent } from "vue"; | ||||||
| import { useToast } from "vue-toastification" | import { useToast } from "vue-toastification" | ||||||
| const toast = useToast() | const toast = useToast() | ||||||
| import Confirm from "../components/Confirm.vue"; | import Confirm from "../components/Confirm.vue"; | ||||||
| @@ -164,6 +177,7 @@ import Datetime from "../components/Datetime.vue"; | |||||||
| import CountUp from "../components/CountUp.vue"; | import CountUp from "../components/CountUp.vue"; | ||||||
| import Uptime from "../components/Uptime.vue"; | import Uptime from "../components/Uptime.vue"; | ||||||
| import Pagination from "v-pagination-3"; | import Pagination from "v-pagination-3"; | ||||||
|  | const PingChart = defineAsyncComponent(() => import("../components/PingChart.vue")); | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     components: { |     components: { | ||||||
| @@ -174,6 +188,7 @@ export default { | |||||||
|         Confirm, |         Confirm, | ||||||
|         Status, |         Status, | ||||||
|         Pagination, |         Pagination, | ||||||
|  |         PingChart, | ||||||
|     }, |     }, | ||||||
|     data() { |     data() { | ||||||
|         return { |         return { | ||||||
| @@ -181,6 +196,7 @@ export default { | |||||||
|             perPage: 25, |             perPage: 25, | ||||||
|             heartBeatList: [], |             heartBeatList: [], | ||||||
|             toggleCertInfoBox: false, |             toggleCertInfoBox: false, | ||||||
|  |             showPingChartBox: true, | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|     computed: { |     computed: { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user