mirror of
				https://github.com/louislam/uptime-kuma.git
				synced 2025-10-31 03:19:20 +08:00 
			
		
		
		
	Merge branch 'master' into something
# Conflicts: # server/notification.js # src/components/NotificationDialog.vue
This commit is contained in:
		| @@ -3,3 +3,11 @@ | ||||
| /node_modules | ||||
| /data/kuma.db | ||||
| /.do | ||||
| **/.dockerignore | ||||
| **/.git | ||||
| **/.gitignore | ||||
| **/docker-compose* | ||||
| **/Dockerfile* | ||||
| LICENSE | ||||
| README.md | ||||
| .editorconfig | ||||
|   | ||||
							
								
								
									
										10
									
								
								.github/ISSUE_TEMPLATE/--please-go-to--discussion--tab-if-you-want-to-ask-or-share-something.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								.github/ISSUE_TEMPLATE/--please-go-to--discussion--tab-if-you-want-to-ask-or-share-something.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| --- | ||||
| name: ⚠ Please go to "Discussions" Tab if you want to ask or share something | ||||
| about: BUG REPORT ONLY HERE | ||||
| title: '' | ||||
| labels: '' | ||||
| assignees: '' | ||||
|  | ||||
| --- | ||||
|  | ||||
| BUG REPORT ONLY HERE | ||||
							
								
								
									
										34
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| --- | ||||
| name: Bug report | ||||
| about: Create a report to help us improve | ||||
| title: '' | ||||
| labels: bug | ||||
| assignees: '' | ||||
|  | ||||
| --- | ||||
|  | ||||
| **Describe the bug** | ||||
| A clear and concise description of what the bug is. | ||||
|  | ||||
| **To Reproduce** | ||||
| Steps to reproduce the behavior: | ||||
| 1. Go to '...' | ||||
| 2. Click on '....' | ||||
| 3. Scroll down to '....' | ||||
| 4. See error | ||||
|  | ||||
| **Expected behavior** | ||||
| A clear and concise description of what you expected to happen. | ||||
|  | ||||
| **Screenshots** | ||||
| If applicable, add screenshots to help explain your problem. | ||||
|  | ||||
| **Desktop (please complete the following information):** | ||||
|  - Uptime Kuma Version: | ||||
|  - Using Docker?: Yes/No | ||||
|  - OS:  | ||||
|  - Browser: | ||||
|  | ||||
|  | ||||
| **Additional context** | ||||
| Add any other context about the problem here. | ||||
							
								
								
									
										19
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								README.md
									
									
									
									
									
								
							| @@ -27,7 +27,7 @@ It is a self-hosted monitoring tool like "Uptime Robot". | ||||
| docker volume create uptime-kuma | ||||
|  | ||||
| # Start the container | ||||
| docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name uptime-kuma louislam/uptime-kuma | ||||
| docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name uptime-kuma louislam/uptime-kuma:1 | ||||
| ``` | ||||
|  | ||||
| Browse to http://localhost:3001 after started. | ||||
| @@ -35,7 +35,7 @@ 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 | ||||
| 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 | ||||
| @@ -80,12 +80,17 @@ PS: For every new release, it takes some time to build the docker image, please | ||||
|  | ||||
| ```bash | ||||
| git fetch --all | ||||
| git checkout 1.0.5 --force | ||||
| git checkout 1.0.6 --force | ||||
| npm install | ||||
| npm run build | ||||
| pm2 restart uptime-kuma | ||||
| ``` | ||||
|  | ||||
| # What's Next? | ||||
|  | ||||
| I will mark requests/issues to the next milestone.  | ||||
| https://github.com/louislam/uptime-kuma/milestones | ||||
|  | ||||
| # More Screenshots | ||||
|  | ||||
| Settings Page: | ||||
| @@ -109,3 +114,11 @@ Telegram Notification Sample: | ||||
|  | ||||
| If you love this project, please consider giving me a ⭐. | ||||
|  | ||||
|  | ||||
| # Contribute | ||||
|  | ||||
| If you want to report a bug or request a new feature. Free feel to open a new issue. | ||||
|  | ||||
| If you want to modify Uptime Kuma, this guideline maybe useful for you: https://github.com/louislam/uptime-kuma/wiki/%5BDev%5D-Setup-Development-Environment | ||||
|  | ||||
| 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. | ||||
|   | ||||
							
								
								
									
										37
									
								
								db/patch1.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								db/patch1.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| -- You should not modify if this have pushed to Github, unless it does serious wrong with the db. | ||||
| -- Change Monitor.created_date from "TIMESTAMP" to "DATETIME" | ||||
| -- SQL Generated by Intellij Idea | ||||
| PRAGMA foreign_keys=off; | ||||
|  | ||||
| BEGIN TRANSACTION; | ||||
|  | ||||
| create table monitor_dg_tmp | ||||
| ( | ||||
|     id INTEGER not null | ||||
|         primary key autoincrement, | ||||
|     name VARCHAR(150), | ||||
|     active BOOLEAN default 1 not null, | ||||
|     user_id INTEGER | ||||
|         references user | ||||
|                    on update cascade on delete set null, | ||||
|     interval INTEGER default 20 not null, | ||||
|     url TEXT, | ||||
|     type VARCHAR(20), | ||||
|     weight INTEGER default 2000, | ||||
|     hostname VARCHAR(255), | ||||
|     port INTEGER, | ||||
|     created_date DATETIME, | ||||
|     keyword VARCHAR(255) | ||||
| ); | ||||
|  | ||||
| insert into monitor_dg_tmp(id, name, active, user_id, interval, url, type, weight, hostname, port, created_date, keyword) select id, name, active, user_id, interval, url, type, weight, hostname, port, created_date, keyword from monitor; | ||||
|  | ||||
| drop table monitor; | ||||
|  | ||||
| alter table monitor_dg_tmp rename to monitor; | ||||
|  | ||||
| create index user_id on monitor (user_id); | ||||
|  | ||||
| COMMIT; | ||||
|  | ||||
| PRAGMA foreign_keys=on; | ||||
| @@ -17,11 +17,14 @@ RUN apk add --no-cache --virtual .build-deps make g++ python3 python3-dev && \ | ||||
| # Compilation Fail 3 => Google Search "alpine opensslv.h" => Add openssl-dev | ||||
| # Compilation Fail 4 => Google Search "alpine opensslv.h" again => Change to libressl-dev musl-dev | ||||
| # Compilation Fail 5 => Google Search "ERROR: libressl3.3-libtls-3.3.3-r0: trying to overwrite usr/lib/libtls.so.20 owned by libretls-3.3.3-r0." again => Change back to openssl-dev with musl-dev | ||||
| # Runtime Error => ModuleNotFoundError: No module named 'six' => pip3 install six | ||||
| # Runtime Error 2 => ModuleNotFoundError: No module named 'six' => apk add py3-six | ||||
| ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1 | ||||
| RUN apk add --no-cache python3 | ||||
| RUN apk add --no-cache --virtual .build-deps libffi-dev musl-dev openssl-dev cargo py3-pip python3-dev && \ | ||||
| RUN apk add --no-cache python3 py3-pip py3-six cargo | ||||
| RUN apk add --no-cache --virtual .build-deps libffi-dev musl-dev openssl-dev python3-dev && \ | ||||
|             pip3 install apprise && \ | ||||
|             apk del .build-deps | ||||
| RUN apprise --version | ||||
|  | ||||
| # New things add here | ||||
|  | ||||
| @@ -31,7 +34,7 @@ RUN npm run build | ||||
|  | ||||
| EXPOSE 3001 | ||||
| VOLUME ["/app/data"] | ||||
| HEALTHCHECK --interval=5s --timeout=3s --start-period=30s CMD node extra/healthcheck.js | ||||
| HEALTHCHECK --interval=60s --timeout=30s --start-period=300s CMD node extra/healthcheck.js | ||||
| CMD ["npm", "run", "start-server"] | ||||
|  | ||||
| FROM release AS nightly | ||||
|   | ||||
| @@ -34,6 +34,7 @@ if (newVersion) { | ||||
|     fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n") | ||||
|  | ||||
|     // Process README.md | ||||
|     if (fs.existsSync("README.md")) { | ||||
|         fs.writeFileSync("README.md", fs.readFileSync("README.md", 'utf8').replaceAll(oldVersion, newVersion)) | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										226
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										226
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "uptime-kuma", | ||||
|   "version": "1.0.4", | ||||
|   "version": "1.0.6", | ||||
|   "lockfileVersion": 1, | ||||
|   "requires": true, | ||||
|   "dependencies": { | ||||
| @@ -40,37 +40,43 @@ | ||||
|       "integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg==" | ||||
|     }, | ||||
|     "@types/cookie": { | ||||
|       "version": "0.4.0", | ||||
|       "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.0.tgz", | ||||
|       "integrity": "sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg==" | ||||
|       "version": "0.4.1", | ||||
|       "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", | ||||
|       "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" | ||||
|     }, | ||||
|     "@types/cors": { | ||||
|       "version": "2.8.10", | ||||
|       "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz", | ||||
|       "integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ==" | ||||
|       "version": "2.8.12", | ||||
|       "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", | ||||
|       "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" | ||||
|     }, | ||||
|     "@types/estree": { | ||||
|       "version": "0.0.48", | ||||
|       "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.48.tgz", | ||||
|       "integrity": "sha512-LfZwXoGUDo0C3me81HXgkBg5CTQYb6xzEl+fNmbO4JdRiSKQ8A0GD1OBBvKAIsbCUgoyAty7m99GqqMQe784ew==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "@types/node": { | ||||
|       "version": "15.12.4", | ||||
|       "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.4.tgz", | ||||
|       "integrity": "sha512-zrNj1+yqYF4WskCMOHwN+w9iuD12+dGm0rQ35HLl9/Ouuq52cEtd0CH9qMgrdNmi5ejC1/V7vKEXYubB+65DkA==" | ||||
|       "version": "16.3.3", | ||||
|       "resolved": "https://registry.npmjs.org/@types/node/-/node-16.3.3.tgz", | ||||
|       "integrity": "sha512-8h7k1YgQKxKXWckzFCMfsIwn0Y61UK6tlD6y2lOb3hTOIMlK3t9/QwHOhc81TwU+RMf0As5fj7NPjroERCnejQ==" | ||||
|     }, | ||||
|     "@vitejs/plugin-legacy": { | ||||
|       "version": "1.4.3", | ||||
|       "resolved": "https://registry.npmjs.org/@vitejs/plugin-legacy/-/plugin-legacy-1.4.3.tgz", | ||||
|       "integrity": "sha512-lxZUJaMWYMQuqvZM1wPzDP6KABQgA/drVL5fnaygEPcz9adc2OHhfFNN/SvvHQ1V0rP8gybIc7uA+iI1gAdkVQ==", | ||||
|       "version": "1.4.4", | ||||
|       "resolved": "https://registry.npmjs.org/@vitejs/plugin-legacy/-/plugin-legacy-1.4.4.tgz", | ||||
|       "integrity": "sha512-pVYeQUDPG5InWwrTu7acy187WWjGonJnL/GMqMLmeKCFiwkZ6UcsoUjojiKmCUI0nAJTrrKH5lhjTqkccY9Iow==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "@babel/standalone": "^7.14.7", | ||||
|         "core-js": "^3.15.1", | ||||
|         "core-js": "^3.15.2", | ||||
|         "magic-string": "^0.25.7", | ||||
|         "regenerator-runtime": "^0.13.7", | ||||
|         "systemjs": "^6.10.1" | ||||
|         "systemjs": "^6.10.2" | ||||
|       } | ||||
|     }, | ||||
|     "@vitejs/plugin-vue": { | ||||
|       "version": "1.2.3", | ||||
|       "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.2.3.tgz", | ||||
|       "integrity": "sha512-LlnLpObkGKZ+b7dcpL4T24l13nPSHLjo+6Oc7MbZiKz5PMAUzADfNJ3EKfYIQ0l0969nxf2jp/9vsfnuJ7h6fw==", | ||||
|       "version": "1.2.5", | ||||
|       "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.2.5.tgz", | ||||
|       "integrity": "sha512-GIR31mdXTEfvElmBUaRhDc5v7lfdkEdawWQqJRiaRL/5qKsH+xusukglkvJz5y7+c6dEpxgmvcATv2BbB7+fzQ==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "@vue/compiler-core": { | ||||
| @@ -95,17 +101,18 @@ | ||||
|       } | ||||
|     }, | ||||
|     "@vue/compiler-sfc": { | ||||
|       "version": "3.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.1.1.tgz", | ||||
|       "integrity": "sha512-lSgMsZaYHF+bAgryq5aUqpvyfhu52GJI2/4LoiJCE5uaxc6FCZfxfgqgw/d9ltiZghv+HiISFtmQVAVvlsk+/w==", | ||||
|       "version": "3.1.5", | ||||
|       "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.1.5.tgz", | ||||
|       "integrity": "sha512-mtMY6xMvZeSRx9MTa1+NgJWndrkzVTdJ1pQAmAKQuxyb5LsHVvrgP7kcQFvxPHVpLVTORbTJWHaiqoKrJvi1iA==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "@babel/parser": "^7.13.9", | ||||
|         "@babel/types": "^7.13.0", | ||||
|         "@vue/compiler-core": "3.1.1", | ||||
|         "@vue/compiler-dom": "3.1.1", | ||||
|         "@vue/compiler-ssr": "3.1.1", | ||||
|         "@vue/shared": "3.1.1", | ||||
|         "@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", | ||||
|         "consolidate": "^0.16.0", | ||||
|         "estree-walker": "^2.0.1", | ||||
|         "hash-sum": "^2.0.0", | ||||
| @@ -116,16 +123,78 @@ | ||||
|         "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.1", | ||||
|       "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.1.1.tgz", | ||||
|       "integrity": "sha512-7H6krZtVt3h/YzfNp7eYK41hMDz8ZskiBy+Wby+EDRINX6BD9JQ5C8zyy2xAa7T6Iz2VrQzsaJ/Bb52lTPSS5A==", | ||||
|       "version": "3.1.5", | ||||
|       "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.1.5.tgz", | ||||
|       "integrity": "sha512-CU5N7Di/a4lyJ18LGJxJYZS2a8PlLdWpWHX9p/XcsjT2TngMpj3QvHVRkuik2u8QrIDZ8OpYmTyj1WDNsOV+Dg==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "@vue/compiler-dom": "3.1.1", | ||||
|         "@vue/shared": "3.1.1" | ||||
|         "@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/devtools-api": { | ||||
| @@ -323,6 +392,11 @@ | ||||
|         "follow-redirects": "^1.10.0" | ||||
|       } | ||||
|     }, | ||||
|     "babel-plugin-add-module-exports": { | ||||
|       "version": "0.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-0.2.1.tgz", | ||||
|       "integrity": "sha1-mumh9KjcZ/DN7E9K7aHkOl/2XiU=" | ||||
|     }, | ||||
|     "backo2": { | ||||
|       "version": "1.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", | ||||
| @@ -531,9 +605,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "bootstrap": { | ||||
|       "version": "5.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.0.1.tgz", | ||||
|       "integrity": "sha512-Fl79+wsLOZKoiU345KeEaWD0ik8WKRI5zm0YSPj2oF1Qr+BO7z0fco6GbUtqjoG1h4VI89PeKJnMsMMVQdKKTw==" | ||||
|       "version": "5.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.0.2.tgz", | ||||
|       "integrity": "sha512-1Ge963tyEQWJJ+8qtXFU6wgmAVj9gweEjibUdbmcCEYsn38tVwRk8107rk2vzt6cfQcRr3SlZ8aQBqaD8aqf+Q==" | ||||
|     }, | ||||
|     "brace-expansion": { | ||||
|       "version": "1.1.11", | ||||
| @@ -655,6 +729,11 @@ | ||||
|         "delayed-stream": "~1.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "command-exists": { | ||||
|       "version": "1.2.9", | ||||
|       "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", | ||||
|       "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==" | ||||
|     }, | ||||
|     "commander": { | ||||
|       "version": "6.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", | ||||
| @@ -753,9 +832,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "dayjs": { | ||||
|       "version": "1.10.5", | ||||
|       "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.5.tgz", | ||||
|       "integrity": "sha512-BUFis41ikLz+65iH6LHQCDm4YPMj5r1YFLdupPIyM4SGcXMmtiLQ7U37i+hGS8urIuqe7I/ou3IS1jVc4nbN4g==" | ||||
|       "version": "1.10.6", | ||||
|       "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.6.tgz", | ||||
|       "integrity": "sha512-AztC/IOW4L1Q41A86phW5Thhcrco3xuAA+YX/BLpLWWjRcTj5TOt/QImBLmCKlrF7u7k47arTnOyL6GnbG8Hvw==" | ||||
|     }, | ||||
|     "debug": { | ||||
|       "version": "4.3.1", | ||||
| @@ -898,9 +977,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "engine.io-client": { | ||||
|       "version": "5.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-5.1.1.tgz", | ||||
|       "integrity": "sha512-jPFpw2HLL0lhZ2KY0BpZhIJdleQcUO9W1xkIpo0h3d6s+5D6+EV/xgQw9qWOmymszv2WXef/6KUUehyxEKomlQ==", | ||||
|       "version": "5.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-5.1.2.tgz", | ||||
|       "integrity": "sha512-blRrgXIE0A/eurWXRzvfCLG7uUFJqfTGFsyJzXSK71srMMGJ2VraBLg8Mdw28uUxSpVicepBN9X7asqpD1mZcQ==", | ||||
|       "requires": { | ||||
|         "base64-arraybuffer": "0.1.4", | ||||
|         "component-emitter": "~1.3.0", | ||||
| @@ -922,9 +1001,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "esbuild": { | ||||
|       "version": "0.12.9", | ||||
|       "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.9.tgz", | ||||
|       "integrity": "sha512-MWRhAbMOJ9RJygCrt778rz/qNYgA4ZVj6aXnNPxFjs7PmIpb0fuB9Gmg5uWrr6n++XKwwm/RmSz6RR5JL2Ocsw==", | ||||
|       "version": "0.12.15", | ||||
|       "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.15.tgz", | ||||
|       "integrity": "sha512-72V4JNd2+48eOVCXx49xoSWHgC3/cCy96e7mbXKY+WOWghN00cCmlGnwVLRhRHorvv0dgCyuMYBZlM2xDM5OQw==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "escape-html": { | ||||
| @@ -2019,6 +2098,11 @@ | ||||
|       "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", | ||||
|       "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" | ||||
|     }, | ||||
|     "merge": { | ||||
|       "version": "2.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/merge/-/merge-2.1.1.tgz", | ||||
|       "integrity": "sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w==" | ||||
|     }, | ||||
|     "merge-descriptors": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", | ||||
| @@ -2420,9 +2504,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "nodemailer": { | ||||
|       "version": "6.6.2", | ||||
|       "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.6.2.tgz", | ||||
|       "integrity": "sha512-YSzu7TLbI+bsjCis/TZlAXBoM4y93HhlIgo0P5oiA2ua9Z4k+E2Fod//ybIzdJxOlXGRcHIh/WaeCBehvxZb/Q==" | ||||
|       "version": "6.6.3", | ||||
|       "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.6.3.tgz", | ||||
|       "integrity": "sha512-faZFufgTMrphYoDjvyVpbpJcYzwyFnbAMmQtj1lVBYAUSm3SOy2fIdd9+Mr4UxPosBa0JRw9bJoIwQn+nswiew==" | ||||
|     }, | ||||
|     "nopt": { | ||||
|       "version": "3.0.6", | ||||
| @@ -2975,9 +3059,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "rollup": { | ||||
|       "version": "2.52.2", | ||||
|       "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.2.tgz", | ||||
|       "integrity": "sha512-4RlFC3k2BIHlUsJ9mGd8OO+9Lm2eDF5P7+6DNQOp5sx+7N/1tFM01kELfbxlMX3MxT6owvLB1ln4S3QvvQlbUA==", | ||||
|       "version": "2.53.2", | ||||
|       "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.53.2.tgz", | ||||
|       "integrity": "sha512-1CtEYuS5CRCzFZ7SNW5528SlDlk4VDXIRGwbm/2POQxA/G4+7/crIqJwkmnj8Q/74hGx4oVlNvh4E1CJQ5hZ6w==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "fsevents": "~2.3.2" | ||||
| @@ -3002,9 +3086,9 @@ | ||||
|       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" | ||||
|     }, | ||||
|     "sass": { | ||||
|       "version": "1.35.1", | ||||
|       "resolved": "https://registry.npmjs.org/sass/-/sass-1.35.1.tgz", | ||||
|       "integrity": "sha512-oCisuQJstxMcacOPmxLNiLlj4cUyN2+8xJnG7VanRoh2GOLr9RqkvI4AxA4a6LHVg/rsu+PmxXeGhrdSF9jCiQ==", | ||||
|       "version": "1.35.2", | ||||
|       "resolved": "https://registry.npmjs.org/sass/-/sass-1.35.2.tgz", | ||||
|       "integrity": "sha512-jhO5KAR+AMxCEwIH3v+4zbB2WB0z67V1X0jbapfVwQQdjHZUGUyukpnoM6+iCMfsIUC016w9OPKQ5jrNOS9uXw==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "chokidar": ">=3.0.0 <4.0.0" | ||||
| @@ -3225,19 +3309,19 @@ | ||||
|       } | ||||
|     }, | ||||
|     "socket.io": { | ||||
|       "version": "4.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.1.2.tgz", | ||||
|       "integrity": "sha512-xK0SD1C7hFrh9+bYoYCdVt+ncixkSLKtNLCax5aEy1o3r5PaO5yQhVb97exIe67cE7lAK+EpyMytXWTWmyZY8w==", | ||||
|       "version": "4.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.1.3.tgz", | ||||
|       "integrity": "sha512-tLkaY13RcO4nIRh1K2hT5iuotfTaIQw7cVIe0FUykN3SuQi0cm7ALxuyT5/CtDswOMWUzMGTibxYNx/gU7In+Q==", | ||||
|       "requires": { | ||||
|         "@types/cookie": "^0.4.0", | ||||
|         "@types/cors": "^2.8.8", | ||||
|         "@types/cors": "^2.8.10", | ||||
|         "@types/node": ">=10.0.0", | ||||
|         "accepts": "~1.3.4", | ||||
|         "base64id": "~2.0.0", | ||||
|         "debug": "~4.3.1", | ||||
|         "engine.io": "~5.1.0", | ||||
|         "socket.io-adapter": "~2.3.0", | ||||
|         "socket.io-parser": "~4.0.3" | ||||
|         "engine.io": "~5.1.1", | ||||
|         "socket.io-adapter": "~2.3.1", | ||||
|         "socket.io-parser": "~4.0.4" | ||||
|       } | ||||
|     }, | ||||
|     "socket.io-adapter": { | ||||
| @@ -3246,15 +3330,15 @@ | ||||
|       "integrity": "sha512-8cVkRxI8Nt2wadkY6u60Y4rpW3ejA1rxgcK2JuyIhmF+RMNpTy1QRtkHIDUOf3B4HlQwakMsWbKftMv/71VMmw==" | ||||
|     }, | ||||
|     "socket.io-client": { | ||||
|       "version": "4.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.1.2.tgz", | ||||
|       "integrity": "sha512-RDpWJP4DQT1XeexmeDyDkm0vrFc0+bUsHDKiVGaNISJvJonhQQOMqV9Vwfg0ZpPJ27LCdan7iqTI92FRSOkFWQ==", | ||||
|       "version": "4.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.1.3.tgz", | ||||
|       "integrity": "sha512-hISFn6PDpgDifVUiNklLHVPTMv1LAk8poHArfIUdXa+gKgbr0MZbAlquDFqCqsF30yBqa+jg42wgos2FK50BHA==", | ||||
|       "requires": { | ||||
|         "@types/component-emitter": "^1.2.10", | ||||
|         "backo2": "~1.0.2", | ||||
|         "component-emitter": "~1.3.0", | ||||
|         "debug": "~4.3.1", | ||||
|         "engine.io-client": "~5.1.1", | ||||
|         "engine.io-client": "~5.1.2", | ||||
|         "parseuri": "0.0.6", | ||||
|         "socket.io-parser": "~4.0.4" | ||||
|       } | ||||
| @@ -3624,6 +3708,16 @@ | ||||
|       "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", | ||||
|       "optional": true | ||||
|     }, | ||||
|     "v-pagination-3": { | ||||
|       "version": "0.1.6", | ||||
|       "resolved": "https://registry.npmjs.org/v-pagination-3/-/v-pagination-3-0.1.6.tgz", | ||||
|       "integrity": "sha512-82J8HnEIYtZijn6F3xhyP/ildI5K7Rv4Yu74VNfQWQsiPWTKntgVvZgBH8UPh/lFEjgWxty/M4N+YHvS+YbGzg==", | ||||
|       "requires": { | ||||
|         "babel-plugin-add-module-exports": "^0.2.1", | ||||
|         "merge": "^2.1.1", | ||||
|         "vue": ">=3.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "v8flags": { | ||||
|       "version": "3.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", | ||||
| @@ -3649,14 +3743,14 @@ | ||||
|       } | ||||
|     }, | ||||
|     "vite": { | ||||
|       "version": "2.3.8", | ||||
|       "resolved": "https://registry.npmjs.org/vite/-/vite-2.3.8.tgz", | ||||
|       "integrity": "sha512-QiEx+iqNnJntSgSF2fWRQvRey9pORIrtNJzNyBJXwc+BdzWs83FQolX84cTBo393cfhObrtWa6180dAa4NLDiQ==", | ||||
|       "version": "2.4.2", | ||||
|       "resolved": "https://registry.npmjs.org/vite/-/vite-2.4.2.tgz", | ||||
|       "integrity": "sha512-2MifxD2I9fjyDmmEzbULOo3kOUoqX90A58cT6mECxoVQlMYFuijZsPQBuA14mqSwvV3ydUsqnq+BRWXyO9Qa+w==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "esbuild": "^0.12.8", | ||||
|         "fsevents": "~2.3.2", | ||||
|         "postcss": "^8.3.4", | ||||
|         "postcss": "^8.3.5", | ||||
|         "resolve": "^1.20.0", | ||||
|         "rollup": "^2.38.5" | ||||
|       } | ||||
|   | ||||
							
								
								
									
										38
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,15 +1,21 @@ | ||||
| { | ||||
|     "name": "uptime-kuma", | ||||
|     "version": "1.0.5", | ||||
|     "version": "1.0.6", | ||||
|     "license": "MIT", | ||||
|     "repository": { | ||||
|         "type": "git", | ||||
|         "url": "https://github.com/louislam/uptime-kuma.git" | ||||
|     }, | ||||
|     "scripts": { | ||||
|         "dev": "vite --host", | ||||
|         "start-server": "node server/server.js", | ||||
|         "update": "", | ||||
|         "build": "vite build", | ||||
|         "vite-preview-dist": "vite preview --host", | ||||
|         "build-docker": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.0.5 --target release . --push", | ||||
|         "build-docker-nightly": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:nightly --target nightly . --push", | ||||
|         "setup": "git checkout 1.0.5 && npm install && npm run build", | ||||
|         "build-docker": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.0.6 --target release . --push", | ||||
|         "build-docker-nightly": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push", | ||||
|         "build-docker-nightly-amd64": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push", | ||||
|         "setup": "git checkout 1.0.6 && npm install && npm run build", | ||||
|         "version-global-replace": "node extra/version-global-replace.js", | ||||
|         "mark-as-nightly": "node extra/mark-as-nightly.js" | ||||
|     }, | ||||
| @@ -18,30 +24,32 @@ | ||||
|         "args-parser": "^1.3.0", | ||||
|         "axios": "^0.21.1", | ||||
|         "bcrypt": "^5.0.1", | ||||
|         "bootstrap": "^5.0.0", | ||||
|         "dayjs": "^1.10.4", | ||||
|         "bootstrap": "^5.0.2", | ||||
|         "command-exists": "^1.2.9", | ||||
|         "dayjs": "^1.10.6", | ||||
|         "express": "^4.17.1", | ||||
|         "form-data": "^4.0.0", | ||||
|         "http-graceful-shutdown": "^3.1.2", | ||||
|         "jsonwebtoken": "^8.5.1", | ||||
|         "nodemailer": "^6.6.2", | ||||
|         "nodemailer": "^6.6.3", | ||||
|         "password-hash": "^1.2.2", | ||||
|         "redbean-node": "0.0.20", | ||||
|         "socket.io": "^4.0.2", | ||||
|         "socket.io-client": "^4.1.2", | ||||
|         "sqlite3": "^5.0.0", | ||||
|         "socket.io": "^4.1.3", | ||||
|         "socket.io-client": "^4.1.3", | ||||
|         "sqlite3": "^5.0.2", | ||||
|         "tcp-ping": "^0.1.1", | ||||
|         "v-pagination-3": "^0.1.6", | ||||
|         "vue": "^3.0.5", | ||||
|         "vue-confirm-dialog": "^1.0.2", | ||||
|         "vue-router": "^4.0.10", | ||||
|         "vue-toastification": "^2.0.0-rc.1" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "@vitejs/plugin-legacy": "^1.4.3", | ||||
|         "@vitejs/plugin-vue": "^1.2.3", | ||||
|         "@vue/compiler-sfc": "^3.0.5", | ||||
|         "@vitejs/plugin-legacy": "^1.4.4", | ||||
|         "@vitejs/plugin-vue": "^1.2.5", | ||||
|         "@vue/compiler-sfc": "^3.1.5", | ||||
|         "core-js": "^3.15.2", | ||||
|         "sass": "^1.35.1", | ||||
|         "vite": "^2.3.7" | ||||
|         "sass": "^1.35.2", | ||||
|         "vite": "^2.4.2" | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										119
									
								
								server/database.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								server/database.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,119 @@ | ||||
| const fs = require("fs"); | ||||
| const {sleep} = require("./util"); | ||||
| const {R} = require("redbean-node"); | ||||
| const {setSetting, setting} = require("./util-server"); | ||||
|  | ||||
|  | ||||
| class Database { | ||||
|  | ||||
|     static templatePath = "./db/kuma.db" | ||||
|     static path =  './data/kuma.db'; | ||||
|     static latestVersion = 1; | ||||
|     static noReject = true; | ||||
|  | ||||
|     static async patch() { | ||||
|         let version = parseInt(await setting("database_version")); | ||||
|  | ||||
|         if (! version) { | ||||
|             version = 0; | ||||
|         } | ||||
|  | ||||
|         console.info("Your database version: " + version); | ||||
|         console.info("Latest database version: " + this.latestVersion); | ||||
|  | ||||
|         if (version === this.latestVersion) { | ||||
|             console.info("Database no need to patch"); | ||||
|         } else { | ||||
|             console.info("Database patch is needed") | ||||
|  | ||||
|             console.info("Backup the db") | ||||
|             const backupPath = "./data/kuma.db.bak" + version; | ||||
|             fs.copyFileSync(Database.path, backupPath); | ||||
|  | ||||
|             // Try catch anything here, if gone wrong, restore the backup | ||||
|             try { | ||||
|                 for (let i = version + 1; i <= this.latestVersion; i++) { | ||||
|                     const sqlFile = `./db/patch${i}.sql`; | ||||
|                     console.info(`Patching ${sqlFile}`); | ||||
|                     await Database.importSQLFile(sqlFile); | ||||
|                     console.info(`Patched ${sqlFile}`); | ||||
|                     await setSetting("database_version", i); | ||||
|                 } | ||||
|                 console.log("Database Patched Successfully"); | ||||
|             } catch (ex) { | ||||
|                 await Database.close(); | ||||
|                 console.error("Patch db failed!!! Restoring the backup") | ||||
|                 fs.copyFileSync(backupPath, Database.path); | ||||
|                 console.error(ex) | ||||
|  | ||||
|                 console.error("Start Uptime-Kuma failed due to patch db failed") | ||||
|                 console.error("Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues") | ||||
|                 process.exit(1); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Sadly, multi sql statements is not supported by many sqlite libraries, I have to implement it myself | ||||
|      * @param filename | ||||
|      * @returns {Promise<void>} | ||||
|      */ | ||||
|     static async importSQLFile(filename) { | ||||
|  | ||||
|         await R.getCell("SELECT 1"); | ||||
|  | ||||
|         let text = fs.readFileSync(filename).toString(); | ||||
|  | ||||
|         // Remove all comments (--) | ||||
|         let lines = text.split("\n"); | ||||
|         lines = lines.filter((line) => { | ||||
|             return ! line.startsWith("--") | ||||
|         }); | ||||
|  | ||||
|         // Split statements by semicolon | ||||
|         // Filter out empty line | ||||
|         text = lines.join("\n") | ||||
|  | ||||
|         let statements = text.split(";") | ||||
|             .map((statement) => { | ||||
|                 return statement.trim(); | ||||
|             }) | ||||
|             .filter((statement) => { | ||||
|                 return statement !== ""; | ||||
|             }) | ||||
|  | ||||
|         for (let statement of statements) { | ||||
|             await R.exec(statement); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Special handle, because tarn.js throw a promise reject that cannot be caught | ||||
|      * @returns {Promise<void>} | ||||
|      */ | ||||
|     static async close() { | ||||
|         const listener = (reason, p) => { | ||||
|             Database.noReject = false; | ||||
|         }; | ||||
|         process.addListener('unhandledRejection', listener); | ||||
|  | ||||
|         console.log("Closing DB") | ||||
|  | ||||
|         while (true) { | ||||
|             Database.noReject = true; | ||||
|             await R.close() | ||||
|             await sleep(2000) | ||||
|  | ||||
|             if (Database.noReject) { | ||||
|                 break; | ||||
|             } else { | ||||
|                 console.log("Waiting to close the db") | ||||
|             } | ||||
|         } | ||||
|         console.log("SQLite closed") | ||||
|  | ||||
|         process.removeListener('unhandledRejection', listener); | ||||
|     } | ||||
| } | ||||
|  | ||||
| module.exports = Database; | ||||
| @@ -48,8 +48,6 @@ class Monitor extends BeanModel { | ||||
|         let previousBeat = null; | ||||
|  | ||||
|         const beat = async () => { | ||||
|             console.log(`Monitor ${this.id}: Heartbeat`) | ||||
|  | ||||
|             if (! previousBeat) { | ||||
|                 previousBeat = await R.findOne("heartbeat", " monitor_id = ? ORDER BY time DESC", [ | ||||
|                     this.id | ||||
| @@ -145,6 +143,12 @@ class Monitor extends BeanModel { | ||||
|                 bean.important = false; | ||||
|             } | ||||
|  | ||||
|             if (bean.status === 1) { | ||||
|                 console.info(`Monitor #${this.id} '${this.name}': Successful Response: ${bean.ping} ms | Interval: ${this.interval} seconds | Type: ${this.type}`) | ||||
|             } else { | ||||
|                 console.warn(`Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Type: ${this.type}`) | ||||
|             } | ||||
|  | ||||
|             io.to(this.user_id).emit("heartbeat", bean.toJSON()); | ||||
|  | ||||
|             await R.store(bean) | ||||
|   | ||||
| @@ -2,9 +2,22 @@ const axios = require("axios"); | ||||
| const {R} = require("redbean-node"); | ||||
| const FormData = require('form-data'); | ||||
| const nodemailer = require("nodemailer"); | ||||
| const child_process = require("child_process"); | ||||
|  | ||||
| class Notification { | ||||
|  | ||||
|     /** | ||||
|      * | ||||
|      * @param notification | ||||
|      * @param msg | ||||
|      * @param monitorJSON | ||||
|      * @param heartbeatJSON | ||||
|      * @returns {Promise<string>} Successful msg | ||||
|      * Throw Error with fail msg | ||||
|      */ | ||||
|     static async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | ||||
|         let okMsg = "Sent Successfully. "; | ||||
|  | ||||
|         if (notification.type === "telegram") { | ||||
|             try { | ||||
|                 await axios.get(`https://api.telegram.org/bot${notification.telegramBotToken}/sendMessage`, { | ||||
| @@ -13,15 +26,16 @@ class Notification { | ||||
|                         text: msg, | ||||
|                     } | ||||
|                 }) | ||||
|                 return true; | ||||
|                 return okMsg; | ||||
|  | ||||
|             } catch (error) { | ||||
|                 console.error(error) | ||||
|                 return false; | ||||
|                 let msg = (error.response.data.description) ? error.response.data.description : "Error without description" | ||||
|                 throw new Error(msg) | ||||
|             } | ||||
|  | ||||
|         } else if (notification.type === "gotify") { | ||||
|             try { | ||||
|                 if (notification.gotifyserverurl.endsWith("/")) { | ||||
|                 if (notification.gotifyserverurl && notification.gotifyserverurl.endsWith("/")) { | ||||
|                     notification.gotifyserverurl = notification.gotifyserverurl.slice(0, -1); | ||||
|                 } | ||||
|                 await axios.post(`${notification.gotifyserverurl}/message?token=${notification.gotifyapplicationToken}`, { | ||||
| @@ -29,15 +43,15 @@ class Notification { | ||||
|                     "priority": notification.gotifyPriority || 8, | ||||
|                     "title": "Uptime-Kuma" | ||||
|                 }) | ||||
|                 return true; | ||||
|  | ||||
|                 return okMsg; | ||||
|  | ||||
|             } catch (error) { | ||||
|                 console.error(error) | ||||
|                 return false; | ||||
|                 throwGeneralAxiosError(error) | ||||
|             } | ||||
|  | ||||
|         } else if (notification.type === "webhook") { | ||||
|             try { | ||||
|  | ||||
|                 let data = { | ||||
|                     heartbeat: heartbeatJSON, | ||||
|                     monitor: monitorJSON, | ||||
| @@ -58,11 +72,11 @@ class Notification { | ||||
|                     finalData = data; | ||||
|                 } | ||||
|  | ||||
|                 await axios.post(notification.webhookURL, finalData, config) | ||||
|                 return true; | ||||
|                 let res = await axios.post(notification.webhookURL, finalData, config) | ||||
|                 return okMsg; | ||||
|  | ||||
|             } catch (error) { | ||||
|                 console.error(error) | ||||
|                 return false; | ||||
|                 throwGeneralAxiosError(error) | ||||
|             } | ||||
|  | ||||
|         } else if (notification.type === "smtp") { | ||||
| @@ -77,7 +91,7 @@ class Notification { | ||||
|                   content: msg | ||||
|                 } | ||||
|                 let res = await axios.post(notification.discordWebhookUrl, data) | ||||
|                 return true; | ||||
|                 return okMsg; | ||||
|               } | ||||
|               // If heartbeatJSON is not null, we go into the normal alerting loop. | ||||
|               if(heartbeatJSON['status'] == 0) { | ||||
| @@ -102,12 +116,10 @@ class Notification { | ||||
|                   ] | ||||
|                 }] | ||||
|               } | ||||
|  | ||||
|               await axios.post(notification.discordWebhookUrl, data) | ||||
|               return true; | ||||
|               let res = await axios.post(notification.discordWebhookUrl, data) | ||||
|               return okMsg; | ||||
|             } catch(error) { | ||||
|               console.error(error) | ||||
|               return false; | ||||
|               throwGeneralAxiosError(error) | ||||
|             } | ||||
|  | ||||
|         } else if (notification.type === "signal") { | ||||
| @@ -119,19 +131,18 @@ class Notification { | ||||
|             }; | ||||
|             let config = {}; | ||||
|  | ||||
|             await axios.post(notification.signalURL, data, config) | ||||
|             return true; | ||||
|             let res = await axios.post(notification.signalURL, data, config) | ||||
|             return okMsg; | ||||
|         } catch (error) { | ||||
|             console.error(error) | ||||
|             return false; | ||||
|               throwGeneralAxiosError(error) | ||||
|         } | ||||
|  | ||||
|         } else if (notification.type === "slack") { | ||||
|             try { | ||||
|                 if (heartbeatJSON == null) { | ||||
|                     let data = {'text': "Uptime Kuma Slack testing successful.", 'channel': notification.slackchannel, 'username': notification.slackusername, 'icon_emoji': notification.slackiconemo} | ||||
|                     await axios.post(notification.slackwebhookURL, data) | ||||
|                     return true; | ||||
|                     let res = await axios.post(notification.slackwebhookURL, data) | ||||
|                     return okMsg; | ||||
|                 } | ||||
|  | ||||
|                 const time = heartbeatJSON["time"]; | ||||
| @@ -175,11 +186,10 @@ class Notification { | ||||
|                             } | ||||
|                         ] | ||||
|                     } | ||||
|                 await axios.post(notification.slackwebhookURL, data) | ||||
|                 return true; | ||||
|                 let res = await axios.post(notification.slackwebhookURL, data) | ||||
|                 return okMsg; | ||||
|             } catch (error) { | ||||
|                 console.error(error) | ||||
|                 return false; | ||||
|                 throwGeneralAxiosError(error) | ||||
|             } | ||||
|  | ||||
|         } else if (notification.type === "pushover") { | ||||
| @@ -190,7 +200,7 @@ class Notification { | ||||
|                     'user': notification.pushoveruserkey, 'token': notification.pushoverapptoken, 'sound':notification.pushoversounds, | ||||
|                     'priority': notification.pushoverpriority, 'title':notification.pushovertitle, 'retry': "30", 'expire':"3600", 'html': 1} | ||||
|                     let res = await axios.post(pushoverlink, data) | ||||
|                     return true; | ||||
|                     return okMsg; | ||||
|                 } | ||||
|  | ||||
|                 let data = { | ||||
| @@ -205,12 +215,15 @@ class Notification { | ||||
|                     "html": 1 | ||||
|                     } | ||||
|                 let res = await axios.post(pushoverlink, data) | ||||
|                 return true; | ||||
|                 return okMsg; | ||||
|             } catch (error) { | ||||
|                 console.log(error) | ||||
|                 return false; | ||||
|                 throwGeneralAxiosError(error) | ||||
|             } | ||||
|  | ||||
|         } else if (notification.type === "apprise") { | ||||
|  | ||||
|             return Notification.apprise(notification, msg) | ||||
|  | ||||
|         } else { | ||||
|             throw new Error("Notification type is not supported") | ||||
|         } | ||||
| @@ -272,20 +285,47 @@ class Notification { | ||||
|             text: msg, | ||||
|         }); | ||||
|  | ||||
|         return true; | ||||
|         return "Sent Successfully."; | ||||
|     } | ||||
|  | ||||
|     static async discord(notification, msg) { | ||||
|         const client = new Discord.Client(); | ||||
|         await client.login(notification.discordToken) | ||||
|     static async apprise(notification, msg) { | ||||
|         let s = child_process.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL]) | ||||
|  | ||||
|         const channel = await client.channels.fetch(notification.discordChannelID); | ||||
|         await channel.send(msg); | ||||
|  | ||||
|         client.destroy() | ||||
|         let output = (s.stdout) ? s.stdout.toString() : 'ERROR: maybe apprise not found'; | ||||
|  | ||||
|         return true; | ||||
|         if (output) { | ||||
|  | ||||
|             if (! output.includes("ERROR")) { | ||||
|                 return "Sent Successfully"; | ||||
|             } else { | ||||
|                 throw new Error(output) | ||||
|             } | ||||
|         } else { | ||||
|             return "" | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     static checkApprise() { | ||||
|         let commandExistsSync = require('command-exists').sync; | ||||
|         let exists = commandExistsSync('apprise'); | ||||
|         return exists; | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| function throwGeneralAxiosError(error) { | ||||
|     let msg = "Error: " + error + " "; | ||||
|  | ||||
|     if (error.response && error.response.data) { | ||||
|         if (typeof error.response.data === "string") { | ||||
|             msg += error.response.data; | ||||
|         } else { | ||||
|             msg += JSON.stringify(error.response.data) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     throw new Error(msg) | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|   | ||||
| @@ -12,6 +12,7 @@ const fs = require("fs"); | ||||
| const {getSettings} = require("./util-server"); | ||||
| const {Notification} = require("./notification") | ||||
| const gracefulShutdown = require('http-graceful-shutdown'); | ||||
| const Database = require("./database"); | ||||
| const {sleep} = require("./util"); | ||||
| const args = require('args-parser')(process.argv); | ||||
|  | ||||
| @@ -27,27 +28,48 @@ const server = http.createServer(app); | ||||
| const io = new Server(server); | ||||
| app.use(express.json()) | ||||
|  | ||||
| /** | ||||
|  * Total WebSocket client connected to server currently, no actual use | ||||
|  * @type {number} | ||||
|  */ | ||||
| let totalClient = 0; | ||||
|  | ||||
| /** | ||||
|  * Use for decode the auth object | ||||
|  * @type {null} | ||||
|  */ | ||||
| let jwtSecret = null; | ||||
|  | ||||
| /** | ||||
|  * Main monitor list | ||||
|  * @type {{}} | ||||
|  */ | ||||
| let monitorList = {}; | ||||
|  | ||||
| /** | ||||
|  * Show Setup Page | ||||
|  * @type {boolean} | ||||
|  */ | ||||
| let needSetup = false; | ||||
|  | ||||
| (async () => { | ||||
|     await initDatabase(); | ||||
|  | ||||
|     console.log("Adding route") | ||||
|     app.use('/', express.static("dist")); | ||||
|  | ||||
|     app.get('*', function(request, response, next) { | ||||
|         response.sendFile(process.cwd() + '/dist/index.html'); | ||||
|     }); | ||||
|  | ||||
|  | ||||
|     console.log("Adding socket handler") | ||||
|     io.on('connection', async (socket) => { | ||||
|  | ||||
|         socket.emit("info", { | ||||
|             version, | ||||
|         }) | ||||
|  | ||||
|         console.log('a user connected'); | ||||
|         totalClient++; | ||||
|  | ||||
|         if (needSetup) { | ||||
| @@ -56,7 +78,6 @@ let needSetup = false; | ||||
|         } | ||||
|  | ||||
|         socket.on('disconnect', () => { | ||||
|             console.log('user disconnected'); | ||||
|             totalClient--; | ||||
|         }); | ||||
|  | ||||
| @@ -433,25 +454,36 @@ let needSetup = false; | ||||
|             try { | ||||
|                 checkLogin(socket) | ||||
|  | ||||
|                 await Notification.send(notification, notification.name + " Testing") | ||||
|                 let msg = await Notification.send(notification, notification.name + " Testing") | ||||
|  | ||||
|                 callback({ | ||||
|                     ok: true, | ||||
|                     msg: "Sent Successfully" | ||||
|                     msg | ||||
|                 }); | ||||
|  | ||||
|             } catch (e) { | ||||
|                 console.error(e) | ||||
|  | ||||
|                 callback({ | ||||
|                     ok: false, | ||||
|                     msg: e.message | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         socket.on("checkApprise", async (callback) => { | ||||
|             try { | ||||
|                 checkLogin(socket) | ||||
|                 callback(Notification.checkApprise()); | ||||
|             } catch (e) { | ||||
|                 callback(false); | ||||
|             } | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     console.log("Init") | ||||
|     server.listen(port, hostname, () => { | ||||
|         console.log(`Listening on ${hostname}:${port}`); | ||||
|  | ||||
|         startMonitors(); | ||||
|     }); | ||||
|  | ||||
| @@ -539,18 +571,21 @@ function checkLogin(socket) { | ||||
| } | ||||
|  | ||||
| async function initDatabase() { | ||||
|     const path = './data/kuma.db'; | ||||
|  | ||||
|     if (! fs.existsSync(path)) { | ||||
|     if (! fs.existsSync(Database.path)) { | ||||
|         console.log("Copying Database") | ||||
|         fs.copyFileSync("./db/kuma.db", path); | ||||
|         fs.copyFileSync(Database.templatePath, Database.path); | ||||
|     } | ||||
|  | ||||
|     console.log("Connecting to Database") | ||||
|  | ||||
|     R.setup('sqlite', { | ||||
|         filename: path | ||||
|         filename: Database.path | ||||
|     }); | ||||
|     console.log("Connected") | ||||
|  | ||||
|     // Patch the database | ||||
|     await Database.patch() | ||||
|  | ||||
|     // Auto map the model to a bean object | ||||
|     R.freeze(true) | ||||
|     await R.autoloadModels("./server/model"); | ||||
|  | ||||
| @@ -565,10 +600,12 @@ async function initDatabase() { | ||||
|  | ||||
|         jwtSecretBean.value = passwordHash.generate(dayjs() + "") | ||||
|         await R.store(jwtSecretBean) | ||||
|         console.log("Stored JWT secret into database") | ||||
|     } else { | ||||
|         console.log("Load JWT secret from database.") | ||||
|     } | ||||
|  | ||||
|     // If there is no record in user table, it is a new Uptime Kuma instance, need to setup | ||||
|     if ((await R.count("user")) === 0) { | ||||
|         console.log("No user, need setup") | ||||
|         needSetup = true; | ||||
| @@ -687,11 +724,6 @@ const startGracefulShutdown = async () => { | ||||
|  | ||||
| } | ||||
|  | ||||
| let noReject = true; | ||||
| process.on('unhandledRejection', (reason, p) => { | ||||
|     noReject = false; | ||||
| }); | ||||
|  | ||||
| async function shutdownFunction(signal) { | ||||
|     console.log('Called signal: ' + signal); | ||||
|  | ||||
| @@ -700,24 +732,8 @@ async function shutdownFunction(signal) { | ||||
|         let monitor = monitorList[id] | ||||
|         monitor.stop() | ||||
|     } | ||||
|     await sleep(2000) | ||||
|  | ||||
|     console.log("Closing DB") | ||||
|  | ||||
|     // Special handle, because tarn.js throw a promise reject that cannot be caught | ||||
|     while (true) { | ||||
|         noReject = true; | ||||
|         await R.close() | ||||
|         await sleep(2000) | ||||
|  | ||||
|         if (noReject) { | ||||
|             break; | ||||
|         } else { | ||||
|             console.log("Waiting...") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     console.log("OK") | ||||
|     await sleep(2000); | ||||
|     await Database.close(); | ||||
| } | ||||
|  | ||||
| function finalFunction() { | ||||
|   | ||||
| @@ -45,6 +45,18 @@ exports.setting = async function (key) { | ||||
|     ]) | ||||
| } | ||||
|  | ||||
| exports.setSetting = async function (key, value) { | ||||
|     let bean = await R.findOne("setting", " `key` = ? ", [ | ||||
|         key | ||||
|     ]) | ||||
|     if (! bean) { | ||||
|         bean = R.dispense("setting") | ||||
|         bean.key = key; | ||||
|     } | ||||
|     bean.value = value; | ||||
|     await R.store(bean) | ||||
| } | ||||
|  | ||||
| exports.getSettings = async function (type) { | ||||
|     let list = await R.getAll("SELECT * FROM setting WHERE `type` = ? ", [ | ||||
|         type | ||||
|   | ||||
| @@ -15,14 +15,14 @@ | ||||
|                     <label for="floatingPassword">Password</label> | ||||
|                 </div> | ||||
|  | ||||
|                 <div class="form-check mb-3 mt-3" > | ||||
|                     <label> | ||||
|                 <div class="form-check mb-3 mt-3 d-flex justify-content-center pe-4"> | ||||
|                     <div class="form-check"> | ||||
|                         <input type="checkbox" value="remember-me" class="form-check-input" id="remember" v-model="$root.remember"> | ||||
|  | ||||
|                         <label class="form-check-label" for="remember"> | ||||
|                             Remember me | ||||
|                         </label> | ||||
|                     </label> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <button class="w-100 btn btn-primary" type="submit" :disabled="processing">Login</button> | ||||
|  | ||||
|   | ||||
| @@ -21,6 +21,7 @@ | ||||
|                                 <option value="gotify">Gotify</option> | ||||
|                                 <option value="slack">Slack</option> | ||||
|                                 <option value="pushover">Pushover</option> | ||||
|                                 <option value="apprise">Apprise (Support 50+ Notification services)</option> | ||||
|                             </select> | ||||
|                         </div> | ||||
|  | ||||
| @@ -54,7 +55,7 @@ | ||||
|                                     <p style="margin-top: 8px;"> | ||||
|  | ||||
|                                         <template v-if="notification.telegramBotToken"> | ||||
|                                                 <a :href="telegramGetUpdatesURL" target="_blank">{{ telegramGetUpdatesURL }}</a> | ||||
|                                             <a :href="telegramGetUpdatesURL" target="_blank" style="word-break: break-word;">{{ telegramGetUpdatesURL }}</a> | ||||
|                                         </template> | ||||
|  | ||||
|                                         <template v-else> | ||||
| @@ -269,6 +270,29 @@ | ||||
|                             </div> | ||||
|                         </template> | ||||
|  | ||||
|                         <template v-if="notification.type === 'apprise'"> | ||||
|  | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="gotify-application-token" class="form-label">Apprise URL</label> | ||||
|                                 <input type="text" class="form-control" id="gotify-application-token" required v-model="notification.appriseURL"> | ||||
|                                 <div class="form-text"> | ||||
|                                     <p>Example: twilio://AccountSid:AuthToken@FromPhoneNo</p> | ||||
|                                     <p> | ||||
|                                         Read more: <a href="https://github.com/caronc/apprise/wiki#notification-services" target="_blank">https://github.com/caronc/apprise/wiki#notification-services</a> | ||||
|                                     </p> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|  | ||||
|                             <div class="mb-3"> | ||||
|                                 <p> | ||||
|                                     Status: | ||||
|                                     <span class="text-primary" v-if="appriseInstalled">Apprise is installed</span> | ||||
|                                     <span class="text-danger" v-else>Apprise is not installed. <a href="https://github.com/caronc/apprise">Read more</a></span> | ||||
|                                 </p> | ||||
|                             </div> | ||||
|  | ||||
|                         </template> | ||||
|  | ||||
|                     </div> | ||||
|                     <div class="modal-footer"> | ||||
|                         <button type="button" class="btn btn-danger" @click="deleteConfirm" :disabled="processing" v-if="id">Delete</button> | ||||
| @@ -307,17 +331,15 @@ export default { | ||||
|                 type: null, | ||||
|                 gotifyPriority: 8 | ||||
|             }, | ||||
|             appriseInstalled: false, | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.modal = new Modal(this.$refs.modal) | ||||
|  | ||||
|         // TODO: for edit | ||||
|         this.$root.getSocket().emit("getSettings", "notification", (data) => { | ||||
|           //  this.notification = data | ||||
|         this.$root.getSocket().emit("checkApprise", (installed) => { | ||||
|             this.appriseInstalled = installed; | ||||
|         }) | ||||
|  | ||||
|  | ||||
|     }, | ||||
|     methods: { | ||||
|  | ||||
|   | ||||
| @@ -35,7 +35,8 @@ export default { | ||||
|         window.addEventListener('resize', this.onResize); | ||||
|  | ||||
|         let wsHost; | ||||
|         if (localStorage.dev === "dev") { | ||||
|         const env = process.env.NODE_ENV || "production"; | ||||
|         if (env === "development" || localStorage.dev === "dev") { | ||||
|             wsHost = ":3001" | ||||
|         } else { | ||||
|             wsHost = "" | ||||
| @@ -45,6 +46,10 @@ export default { | ||||
|             transports: ['websocket'] | ||||
|         }); | ||||
|  | ||||
|         socket.on("connect_error", (err) => { | ||||
|             console.error(`Failed to connect to the backend. Socket.io connect_error: ${err.message}`); | ||||
|         }); | ||||
|  | ||||
|         socket.on('info', (info) => { | ||||
|             this.info = info; | ||||
|         }); | ||||
|   | ||||
| @@ -47,7 +47,7 @@ | ||||
|                 </tr> | ||||
|                 </thead> | ||||
|                 <tbody> | ||||
|                 <tr v-for="beat in importantHeartBeatList"> | ||||
|                 <tr v-for="(beat, index) in displayedRecords" :key="index"> | ||||
|                     <td>{{ beat.name }}</td> | ||||
|                     <td><Status :status="beat.status" /></td> | ||||
|                     <td><Datetime :value="beat.time" /></td> | ||||
| @@ -59,6 +59,13 @@ | ||||
|                 </tr> | ||||
|                 </tbody> | ||||
|             </table> | ||||
|  | ||||
|             <div class="d-flex justify-content-center kuma_pagination"> | ||||
|                 <pagination | ||||
|                     v-model="page" | ||||
|                     :records=importantHeartBeatList.length | ||||
|                     :per-page="perPage" /> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
|  | ||||
| @@ -68,8 +75,21 @@ | ||||
| <script> | ||||
| import Status from "../components/Status.vue"; | ||||
| import Datetime from "../components/Datetime.vue"; | ||||
| import Pagination from "v-pagination-3"; | ||||
|  | ||||
| export default { | ||||
|     components: {Datetime, Status}, | ||||
|     components: { | ||||
|         Datetime, | ||||
|         Status, | ||||
|         Pagination, | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
|             page: 1, | ||||
|             perPage: 25, | ||||
|             heartBeatList: [], | ||||
|         } | ||||
|     }, | ||||
|     computed: { | ||||
|         stats() { | ||||
|             let result = { | ||||
| @@ -127,8 +147,16 @@ export default { | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             this.heartBeatList = result; | ||||
|  | ||||
|             return result; | ||||
|         } | ||||
|         }, | ||||
|  | ||||
|         displayedRecords() { | ||||
|             const startIndex = this.perPage * (this.page - 1); | ||||
|             const endIndex = startIndex + this.perPage; | ||||
|             return this.heartBeatList.slice(startIndex, endIndex); | ||||
|         }, | ||||
|     } | ||||
| } | ||||
| </script> | ||||
|   | ||||
| @@ -64,7 +64,7 @@ | ||||
|                 </tr> | ||||
|             </thead> | ||||
|             <tbody> | ||||
|                 <tr v-for="beat in importantHeartBeatList"> | ||||
|                 <tr v-for="(beat, index) in displayedRecords" :key="index"> | ||||
|                     <td><Status :status="beat.status" /></td> | ||||
|                     <td><Datetime :value="beat.time" /></td> | ||||
|                     <td>{{ beat.msg }}</td> | ||||
| @@ -75,6 +75,13 @@ | ||||
|                 </tr> | ||||
|             </tbody> | ||||
|         </table> | ||||
|  | ||||
|         <div class="d-flex justify-content-center kuma_pagination"> | ||||
|             <pagination | ||||
|                 v-model="page" | ||||
|                 :records=importantHeartBeatList.length | ||||
|                 :per-page="perPage" /> | ||||
|         </div> | ||||
|     </div> | ||||
|  | ||||
|     <Confirm ref="confirmPause" @yes="pauseMonitor"> | ||||
| @@ -95,6 +102,7 @@ import Status from "../components/Status.vue"; | ||||
| import Datetime from "../components/Datetime.vue"; | ||||
| import CountUp from "../components/CountUp.vue"; | ||||
| import Uptime from "../components/Uptime.vue"; | ||||
| import Pagination from "v-pagination-3"; | ||||
|  | ||||
| export default { | ||||
|     components: { | ||||
| @@ -104,13 +112,16 @@ export default { | ||||
|         HeartbeatBar, | ||||
|         Confirm, | ||||
|         Status, | ||||
|         Pagination, | ||||
|     }, | ||||
|     mounted() { | ||||
|  | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
|  | ||||
|             page: 1, | ||||
|             perPage: 25, | ||||
|             heartBeatList: [], | ||||
|         } | ||||
|     }, | ||||
|     computed: { | ||||
| @@ -154,6 +165,7 @@ export default { | ||||
|  | ||||
|         importantHeartBeatList() { | ||||
|             if (this.$root.importantHeartbeatList[this.monitor.id]) { | ||||
|                 this.heartBeatList = this.$root.importantHeartbeatList[this.monitor.id]; | ||||
|                 return this.$root.importantHeartbeatList[this.monitor.id] | ||||
|             } else { | ||||
|                 return []; | ||||
| @@ -166,8 +178,13 @@ export default { | ||||
|             } else { | ||||
|                 return { } | ||||
|             } | ||||
|         } | ||||
|         }, | ||||
|  | ||||
|         displayedRecords() { | ||||
|             const startIndex = this.perPage * (this.page - 1); | ||||
|             const endIndex = startIndex + this.perPage; | ||||
|             return this.heartBeatList.slice(startIndex, endIndex); | ||||
|         }, | ||||
|     }, | ||||
|     methods: { | ||||
|         testNotification() { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user