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 | /node_modules | ||||||
| /data/kuma.db | /data/kuma.db | ||||||
| /.do | /.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 | docker volume create uptime-kuma | ||||||
|  |  | ||||||
| # Start the container | # 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. | Browse to http://localhost:3001 after started. | ||||||
| @@ -35,7 +35,7 @@ Browse to http://localhost:3001 after started. | |||||||
| Change Port and Volume | Change Port and Volume | ||||||
|  |  | ||||||
| ```bash | ```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 | ### Without Docker | ||||||
| @@ -80,12 +80,17 @@ PS: For every new release, it takes some time to build the docker image, please | |||||||
|  |  | ||||||
| ```bash | ```bash | ||||||
| git fetch --all | git fetch --all | ||||||
| git checkout 1.0.5 --force | git checkout 1.0.6 --force | ||||||
| npm install | npm install | ||||||
| npm run build | npm run build | ||||||
| pm2 restart uptime-kuma | 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 | # More Screenshots | ||||||
|  |  | ||||||
| Settings Page: | Settings Page: | ||||||
| @@ -109,3 +114,11 @@ Telegram Notification Sample: | |||||||
|  |  | ||||||
| If you love this project, please consider giving me a ⭐. | 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 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 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 | # 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 | ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1 | ||||||
| RUN apk add --no-cache python3 | 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 cargo py3-pip python3-dev && \ | RUN apk add --no-cache --virtual .build-deps libffi-dev musl-dev openssl-dev python3-dev && \ | ||||||
|             pip3 install apprise && \ |             pip3 install apprise && \ | ||||||
|             apk del .build-deps |             apk del .build-deps | ||||||
|  | RUN apprise --version | ||||||
|  |  | ||||||
| # New things add here | # New things add here | ||||||
|  |  | ||||||
| @@ -31,7 +34,7 @@ RUN npm run build | |||||||
|  |  | ||||||
| EXPOSE 3001 | EXPOSE 3001 | ||||||
| VOLUME ["/app/data"] | 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"] | CMD ["npm", "run", "start-server"] | ||||||
|  |  | ||||||
| FROM release AS nightly | FROM release AS nightly | ||||||
|   | |||||||
| @@ -34,6 +34,7 @@ if (newVersion) { | |||||||
|     fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n") |     fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n") | ||||||
|  |  | ||||||
|     // Process README.md |     // Process README.md | ||||||
|    fs.writeFileSync("README.md", fs.readFileSync("README.md", 'utf8').replaceAll(oldVersion, newVersion)) |     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", |   "name": "uptime-kuma", | ||||||
|   "version": "1.0.4", |   "version": "1.0.6", | ||||||
|   "lockfileVersion": 1, |   "lockfileVersion": 1, | ||||||
|   "requires": true, |   "requires": true, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
| @@ -40,37 +40,43 @@ | |||||||
|       "integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg==" |       "integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg==" | ||||||
|     }, |     }, | ||||||
|     "@types/cookie": { |     "@types/cookie": { | ||||||
|       "version": "0.4.0", |       "version": "0.4.1", | ||||||
|       "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.0.tgz", |       "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", | ||||||
|       "integrity": "sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg==" |       "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" | ||||||
|     }, |     }, | ||||||
|     "@types/cors": { |     "@types/cors": { | ||||||
|       "version": "2.8.10", |       "version": "2.8.12", | ||||||
|       "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz", |       "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", | ||||||
|       "integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ==" |       "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": { |     "@types/node": { | ||||||
|       "version": "15.12.4", |       "version": "16.3.3", | ||||||
|       "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.4.tgz", |       "resolved": "https://registry.npmjs.org/@types/node/-/node-16.3.3.tgz", | ||||||
|       "integrity": "sha512-zrNj1+yqYF4WskCMOHwN+w9iuD12+dGm0rQ35HLl9/Ouuq52cEtd0CH9qMgrdNmi5ejC1/V7vKEXYubB+65DkA==" |       "integrity": "sha512-8h7k1YgQKxKXWckzFCMfsIwn0Y61UK6tlD6y2lOb3hTOIMlK3t9/QwHOhc81TwU+RMf0As5fj7NPjroERCnejQ==" | ||||||
|     }, |     }, | ||||||
|     "@vitejs/plugin-legacy": { |     "@vitejs/plugin-legacy": { | ||||||
|       "version": "1.4.3", |       "version": "1.4.4", | ||||||
|       "resolved": "https://registry.npmjs.org/@vitejs/plugin-legacy/-/plugin-legacy-1.4.3.tgz", |       "resolved": "https://registry.npmjs.org/@vitejs/plugin-legacy/-/plugin-legacy-1.4.4.tgz", | ||||||
|       "integrity": "sha512-lxZUJaMWYMQuqvZM1wPzDP6KABQgA/drVL5fnaygEPcz9adc2OHhfFNN/SvvHQ1V0rP8gybIc7uA+iI1gAdkVQ==", |       "integrity": "sha512-pVYeQUDPG5InWwrTu7acy187WWjGonJnL/GMqMLmeKCFiwkZ6UcsoUjojiKmCUI0nAJTrrKH5lhjTqkccY9Iow==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "requires": { |       "requires": { | ||||||
|         "@babel/standalone": "^7.14.7", |         "@babel/standalone": "^7.14.7", | ||||||
|         "core-js": "^3.15.1", |         "core-js": "^3.15.2", | ||||||
|         "magic-string": "^0.25.7", |         "magic-string": "^0.25.7", | ||||||
|         "regenerator-runtime": "^0.13.7", |         "regenerator-runtime": "^0.13.7", | ||||||
|         "systemjs": "^6.10.1" |         "systemjs": "^6.10.2" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "@vitejs/plugin-vue": { |     "@vitejs/plugin-vue": { | ||||||
|       "version": "1.2.3", |       "version": "1.2.5", | ||||||
|       "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.2.3.tgz", |       "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.2.5.tgz", | ||||||
|       "integrity": "sha512-LlnLpObkGKZ+b7dcpL4T24l13nPSHLjo+6Oc7MbZiKz5PMAUzADfNJ3EKfYIQ0l0969nxf2jp/9vsfnuJ7h6fw==", |       "integrity": "sha512-GIR31mdXTEfvElmBUaRhDc5v7lfdkEdawWQqJRiaRL/5qKsH+xusukglkvJz5y7+c6dEpxgmvcATv2BbB7+fzQ==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "@vue/compiler-core": { |     "@vue/compiler-core": { | ||||||
| @@ -95,17 +101,18 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "@vue/compiler-sfc": { |     "@vue/compiler-sfc": { | ||||||
|       "version": "3.1.1", |       "version": "3.1.5", | ||||||
|       "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.1.1.tgz", |       "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.1.5.tgz", | ||||||
|       "integrity": "sha512-lSgMsZaYHF+bAgryq5aUqpvyfhu52GJI2/4LoiJCE5uaxc6FCZfxfgqgw/d9ltiZghv+HiISFtmQVAVvlsk+/w==", |       "integrity": "sha512-mtMY6xMvZeSRx9MTa1+NgJWndrkzVTdJ1pQAmAKQuxyb5LsHVvrgP7kcQFvxPHVpLVTORbTJWHaiqoKrJvi1iA==", | ||||||
|       "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", | ||||||
|         "@vue/compiler-core": "3.1.1", |         "@types/estree": "^0.0.48", | ||||||
|         "@vue/compiler-dom": "3.1.1", |         "@vue/compiler-core": "3.1.5", | ||||||
|         "@vue/compiler-ssr": "3.1.1", |         "@vue/compiler-dom": "3.1.5", | ||||||
|         "@vue/shared": "3.1.1", |         "@vue/compiler-ssr": "3.1.5", | ||||||
|  |         "@vue/shared": "3.1.5", | ||||||
|         "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", | ||||||
| @@ -116,16 +123,78 @@ | |||||||
|         "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.1", |       "version": "3.1.5", | ||||||
|       "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.1.1.tgz", |       "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.1.5.tgz", | ||||||
|       "integrity": "sha512-7H6krZtVt3h/YzfNp7eYK41hMDz8ZskiBy+Wby+EDRINX6BD9JQ5C8zyy2xAa7T6Iz2VrQzsaJ/Bb52lTPSS5A==", |       "integrity": "sha512-CU5N7Di/a4lyJ18LGJxJYZS2a8PlLdWpWHX9p/XcsjT2TngMpj3QvHVRkuik2u8QrIDZ8OpYmTyj1WDNsOV+Dg==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "requires": { |       "requires": { | ||||||
|         "@vue/compiler-dom": "3.1.1", |         "@vue/compiler-dom": "3.1.5", | ||||||
|         "@vue/shared": "3.1.1" |         "@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": { |     "@vue/devtools-api": { | ||||||
| @@ -323,6 +392,11 @@ | |||||||
|         "follow-redirects": "^1.10.0" |         "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": { |     "backo2": { | ||||||
|       "version": "1.0.2", |       "version": "1.0.2", | ||||||
|       "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", |       "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", | ||||||
| @@ -531,9 +605,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "bootstrap": { |     "bootstrap": { | ||||||
|       "version": "5.0.1", |       "version": "5.0.2", | ||||||
|       "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.0.1.tgz", |       "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.0.2.tgz", | ||||||
|       "integrity": "sha512-Fl79+wsLOZKoiU345KeEaWD0ik8WKRI5zm0YSPj2oF1Qr+BO7z0fco6GbUtqjoG1h4VI89PeKJnMsMMVQdKKTw==" |       "integrity": "sha512-1Ge963tyEQWJJ+8qtXFU6wgmAVj9gweEjibUdbmcCEYsn38tVwRk8107rk2vzt6cfQcRr3SlZ8aQBqaD8aqf+Q==" | ||||||
|     }, |     }, | ||||||
|     "brace-expansion": { |     "brace-expansion": { | ||||||
|       "version": "1.1.11", |       "version": "1.1.11", | ||||||
| @@ -655,6 +729,11 @@ | |||||||
|         "delayed-stream": "~1.0.0" |         "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": { |     "commander": { | ||||||
|       "version": "6.2.1", |       "version": "6.2.1", | ||||||
|       "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", |       "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", | ||||||
| @@ -753,9 +832,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "dayjs": { |     "dayjs": { | ||||||
|       "version": "1.10.5", |       "version": "1.10.6", | ||||||
|       "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.5.tgz", |       "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.6.tgz", | ||||||
|       "integrity": "sha512-BUFis41ikLz+65iH6LHQCDm4YPMj5r1YFLdupPIyM4SGcXMmtiLQ7U37i+hGS8urIuqe7I/ou3IS1jVc4nbN4g==" |       "integrity": "sha512-AztC/IOW4L1Q41A86phW5Thhcrco3xuAA+YX/BLpLWWjRcTj5TOt/QImBLmCKlrF7u7k47arTnOyL6GnbG8Hvw==" | ||||||
|     }, |     }, | ||||||
|     "debug": { |     "debug": { | ||||||
|       "version": "4.3.1", |       "version": "4.3.1", | ||||||
| @@ -898,9 +977,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "engine.io-client": { |     "engine.io-client": { | ||||||
|       "version": "5.1.1", |       "version": "5.1.2", | ||||||
|       "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-5.1.1.tgz", |       "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-5.1.2.tgz", | ||||||
|       "integrity": "sha512-jPFpw2HLL0lhZ2KY0BpZhIJdleQcUO9W1xkIpo0h3d6s+5D6+EV/xgQw9qWOmymszv2WXef/6KUUehyxEKomlQ==", |       "integrity": "sha512-blRrgXIE0A/eurWXRzvfCLG7uUFJqfTGFsyJzXSK71srMMGJ2VraBLg8Mdw28uUxSpVicepBN9X7asqpD1mZcQ==", | ||||||
|       "requires": { |       "requires": { | ||||||
|         "base64-arraybuffer": "0.1.4", |         "base64-arraybuffer": "0.1.4", | ||||||
|         "component-emitter": "~1.3.0", |         "component-emitter": "~1.3.0", | ||||||
| @@ -922,9 +1001,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "esbuild": { |     "esbuild": { | ||||||
|       "version": "0.12.9", |       "version": "0.12.15", | ||||||
|       "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.9.tgz", |       "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.15.tgz", | ||||||
|       "integrity": "sha512-MWRhAbMOJ9RJygCrt778rz/qNYgA4ZVj6aXnNPxFjs7PmIpb0fuB9Gmg5uWrr6n++XKwwm/RmSz6RR5JL2Ocsw==", |       "integrity": "sha512-72V4JNd2+48eOVCXx49xoSWHgC3/cCy96e7mbXKY+WOWghN00cCmlGnwVLRhRHorvv0dgCyuMYBZlM2xDM5OQw==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "escape-html": { |     "escape-html": { | ||||||
| @@ -2019,6 +2098,11 @@ | |||||||
|       "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", |       "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", | ||||||
|       "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" |       "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": { |     "merge-descriptors": { | ||||||
|       "version": "1.0.1", |       "version": "1.0.1", | ||||||
|       "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", |       "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", | ||||||
| @@ -2420,9 +2504,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "nodemailer": { |     "nodemailer": { | ||||||
|       "version": "6.6.2", |       "version": "6.6.3", | ||||||
|       "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.6.2.tgz", |       "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.6.3.tgz", | ||||||
|       "integrity": "sha512-YSzu7TLbI+bsjCis/TZlAXBoM4y93HhlIgo0P5oiA2ua9Z4k+E2Fod//ybIzdJxOlXGRcHIh/WaeCBehvxZb/Q==" |       "integrity": "sha512-faZFufgTMrphYoDjvyVpbpJcYzwyFnbAMmQtj1lVBYAUSm3SOy2fIdd9+Mr4UxPosBa0JRw9bJoIwQn+nswiew==" | ||||||
|     }, |     }, | ||||||
|     "nopt": { |     "nopt": { | ||||||
|       "version": "3.0.6", |       "version": "3.0.6", | ||||||
| @@ -2975,9 +3059,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "rollup": { |     "rollup": { | ||||||
|       "version": "2.52.2", |       "version": "2.53.2", | ||||||
|       "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.2.tgz", |       "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.53.2.tgz", | ||||||
|       "integrity": "sha512-4RlFC3k2BIHlUsJ9mGd8OO+9Lm2eDF5P7+6DNQOp5sx+7N/1tFM01kELfbxlMX3MxT6owvLB1ln4S3QvvQlbUA==", |       "integrity": "sha512-1CtEYuS5CRCzFZ7SNW5528SlDlk4VDXIRGwbm/2POQxA/G4+7/crIqJwkmnj8Q/74hGx4oVlNvh4E1CJQ5hZ6w==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "requires": { |       "requires": { | ||||||
|         "fsevents": "~2.3.2" |         "fsevents": "~2.3.2" | ||||||
| @@ -3002,9 +3086,9 @@ | |||||||
|       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" |       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" | ||||||
|     }, |     }, | ||||||
|     "sass": { |     "sass": { | ||||||
|       "version": "1.35.1", |       "version": "1.35.2", | ||||||
|       "resolved": "https://registry.npmjs.org/sass/-/sass-1.35.1.tgz", |       "resolved": "https://registry.npmjs.org/sass/-/sass-1.35.2.tgz", | ||||||
|       "integrity": "sha512-oCisuQJstxMcacOPmxLNiLlj4cUyN2+8xJnG7VanRoh2GOLr9RqkvI4AxA4a6LHVg/rsu+PmxXeGhrdSF9jCiQ==", |       "integrity": "sha512-jhO5KAR+AMxCEwIH3v+4zbB2WB0z67V1X0jbapfVwQQdjHZUGUyukpnoM6+iCMfsIUC016w9OPKQ5jrNOS9uXw==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "requires": { |       "requires": { | ||||||
|         "chokidar": ">=3.0.0 <4.0.0" |         "chokidar": ">=3.0.0 <4.0.0" | ||||||
| @@ -3225,19 +3309,19 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "socket.io": { |     "socket.io": { | ||||||
|       "version": "4.1.2", |       "version": "4.1.3", | ||||||
|       "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.1.2.tgz", |       "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.1.3.tgz", | ||||||
|       "integrity": "sha512-xK0SD1C7hFrh9+bYoYCdVt+ncixkSLKtNLCax5aEy1o3r5PaO5yQhVb97exIe67cE7lAK+EpyMytXWTWmyZY8w==", |       "integrity": "sha512-tLkaY13RcO4nIRh1K2hT5iuotfTaIQw7cVIe0FUykN3SuQi0cm7ALxuyT5/CtDswOMWUzMGTibxYNx/gU7In+Q==", | ||||||
|       "requires": { |       "requires": { | ||||||
|         "@types/cookie": "^0.4.0", |         "@types/cookie": "^0.4.0", | ||||||
|         "@types/cors": "^2.8.8", |         "@types/cors": "^2.8.10", | ||||||
|         "@types/node": ">=10.0.0", |         "@types/node": ">=10.0.0", | ||||||
|         "accepts": "~1.3.4", |         "accepts": "~1.3.4", | ||||||
|         "base64id": "~2.0.0", |         "base64id": "~2.0.0", | ||||||
|         "debug": "~4.3.1", |         "debug": "~4.3.1", | ||||||
|         "engine.io": "~5.1.0", |         "engine.io": "~5.1.1", | ||||||
|         "socket.io-adapter": "~2.3.0", |         "socket.io-adapter": "~2.3.1", | ||||||
|         "socket.io-parser": "~4.0.3" |         "socket.io-parser": "~4.0.4" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "socket.io-adapter": { |     "socket.io-adapter": { | ||||||
| @@ -3246,15 +3330,15 @@ | |||||||
|       "integrity": "sha512-8cVkRxI8Nt2wadkY6u60Y4rpW3ejA1rxgcK2JuyIhmF+RMNpTy1QRtkHIDUOf3B4HlQwakMsWbKftMv/71VMmw==" |       "integrity": "sha512-8cVkRxI8Nt2wadkY6u60Y4rpW3ejA1rxgcK2JuyIhmF+RMNpTy1QRtkHIDUOf3B4HlQwakMsWbKftMv/71VMmw==" | ||||||
|     }, |     }, | ||||||
|     "socket.io-client": { |     "socket.io-client": { | ||||||
|       "version": "4.1.2", |       "version": "4.1.3", | ||||||
|       "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.1.2.tgz", |       "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.1.3.tgz", | ||||||
|       "integrity": "sha512-RDpWJP4DQT1XeexmeDyDkm0vrFc0+bUsHDKiVGaNISJvJonhQQOMqV9Vwfg0ZpPJ27LCdan7iqTI92FRSOkFWQ==", |       "integrity": "sha512-hISFn6PDpgDifVUiNklLHVPTMv1LAk8poHArfIUdXa+gKgbr0MZbAlquDFqCqsF30yBqa+jg42wgos2FK50BHA==", | ||||||
|       "requires": { |       "requires": { | ||||||
|         "@types/component-emitter": "^1.2.10", |         "@types/component-emitter": "^1.2.10", | ||||||
|         "backo2": "~1.0.2", |         "backo2": "~1.0.2", | ||||||
|         "component-emitter": "~1.3.0", |         "component-emitter": "~1.3.0", | ||||||
|         "debug": "~4.3.1", |         "debug": "~4.3.1", | ||||||
|         "engine.io-client": "~5.1.1", |         "engine.io-client": "~5.1.2", | ||||||
|         "parseuri": "0.0.6", |         "parseuri": "0.0.6", | ||||||
|         "socket.io-parser": "~4.0.4" |         "socket.io-parser": "~4.0.4" | ||||||
|       } |       } | ||||||
| @@ -3624,6 +3708,16 @@ | |||||||
|       "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", |       "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", | ||||||
|       "optional": true |       "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": { |     "v8flags": { | ||||||
|       "version": "3.2.0", |       "version": "3.2.0", | ||||||
|       "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", |       "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", | ||||||
| @@ -3649,14 +3743,14 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "vite": { |     "vite": { | ||||||
|       "version": "2.3.8", |       "version": "2.4.2", | ||||||
|       "resolved": "https://registry.npmjs.org/vite/-/vite-2.3.8.tgz", |       "resolved": "https://registry.npmjs.org/vite/-/vite-2.4.2.tgz", | ||||||
|       "integrity": "sha512-QiEx+iqNnJntSgSF2fWRQvRey9pORIrtNJzNyBJXwc+BdzWs83FQolX84cTBo393cfhObrtWa6180dAa4NLDiQ==", |       "integrity": "sha512-2MifxD2I9fjyDmmEzbULOo3kOUoqX90A58cT6mECxoVQlMYFuijZsPQBuA14mqSwvV3ydUsqnq+BRWXyO9Qa+w==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "requires": { |       "requires": { | ||||||
|         "esbuild": "^0.12.8", |         "esbuild": "^0.12.8", | ||||||
|         "fsevents": "~2.3.2", |         "fsevents": "~2.3.2", | ||||||
|         "postcss": "^8.3.4", |         "postcss": "^8.3.5", | ||||||
|         "resolve": "^1.20.0", |         "resolve": "^1.20.0", | ||||||
|         "rollup": "^2.38.5" |         "rollup": "^2.38.5" | ||||||
|       } |       } | ||||||
|   | |||||||
							
								
								
									
										38
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,15 +1,21 @@ | |||||||
| { | { | ||||||
|     "name": "uptime-kuma", |     "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": { |     "scripts": { | ||||||
|         "dev": "vite --host", |         "dev": "vite --host", | ||||||
|         "start-server": "node server/server.js", |         "start-server": "node server/server.js", | ||||||
|         "update": "", |         "update": "", | ||||||
|         "build": "vite build", |         "build": "vite build", | ||||||
|         "vite-preview-dist": "vite preview --host", |         "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": "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 -t louislam/uptime-kuma:nightly --target nightly . --push", |         "build-docker-nightly": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push", | ||||||
|         "setup": "git checkout 1.0.5 && npm install && npm run build", |         "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", |         "version-global-replace": "node extra/version-global-replace.js", | ||||||
|         "mark-as-nightly": "node extra/mark-as-nightly.js" |         "mark-as-nightly": "node extra/mark-as-nightly.js" | ||||||
|     }, |     }, | ||||||
| @@ -18,30 +24,32 @@ | |||||||
|         "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.0.0", |         "bootstrap": "^5.0.2", | ||||||
|         "dayjs": "^1.10.4", |         "command-exists": "^1.2.9", | ||||||
|  |         "dayjs": "^1.10.6", | ||||||
|         "express": "^4.17.1", |         "express": "^4.17.1", | ||||||
|         "form-data": "^4.0.0", |         "form-data": "^4.0.0", | ||||||
|         "http-graceful-shutdown": "^3.1.2", |         "http-graceful-shutdown": "^3.1.2", | ||||||
|         "jsonwebtoken": "^8.5.1", |         "jsonwebtoken": "^8.5.1", | ||||||
|         "nodemailer": "^6.6.2", |         "nodemailer": "^6.6.3", | ||||||
|         "password-hash": "^1.2.2", |         "password-hash": "^1.2.2", | ||||||
|         "redbean-node": "0.0.20", |         "redbean-node": "0.0.20", | ||||||
|         "socket.io": "^4.0.2", |         "socket.io": "^4.1.3", | ||||||
|         "socket.io-client": "^4.1.2", |         "socket.io-client": "^4.1.3", | ||||||
|         "sqlite3": "^5.0.0", |         "sqlite3": "^5.0.2", | ||||||
|         "tcp-ping": "^0.1.1", |         "tcp-ping": "^0.1.1", | ||||||
|  |         "v-pagination-3": "^0.1.6", | ||||||
|         "vue": "^3.0.5", |         "vue": "^3.0.5", | ||||||
|         "vue-confirm-dialog": "^1.0.2", |         "vue-confirm-dialog": "^1.0.2", | ||||||
|         "vue-router": "^4.0.10", |         "vue-router": "^4.0.10", | ||||||
|         "vue-toastification": "^2.0.0-rc.1" |         "vue-toastification": "^2.0.0-rc.1" | ||||||
|     }, |     }, | ||||||
|     "devDependencies": { |     "devDependencies": { | ||||||
|         "@vitejs/plugin-legacy": "^1.4.3", |         "@vitejs/plugin-legacy": "^1.4.4", | ||||||
|         "@vitejs/plugin-vue": "^1.2.3", |         "@vitejs/plugin-vue": "^1.2.5", | ||||||
|         "@vue/compiler-sfc": "^3.0.5", |         "@vue/compiler-sfc": "^3.1.5", | ||||||
|         "core-js": "^3.15.2", |         "core-js": "^3.15.2", | ||||||
|         "sass": "^1.35.1", |         "sass": "^1.35.2", | ||||||
|         "vite": "^2.3.7" |         "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; |         let previousBeat = null; | ||||||
|  |  | ||||||
|         const beat = async () => { |         const beat = async () => { | ||||||
|             console.log(`Monitor ${this.id}: Heartbeat`) |  | ||||||
|  |  | ||||||
|             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 | ||||||
| @@ -145,6 +143,12 @@ class Monitor extends BeanModel { | |||||||
|                 bean.important = false; |                 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()); |             io.to(this.user_id).emit("heartbeat", bean.toJSON()); | ||||||
|  |  | ||||||
|             await R.store(bean) |             await R.store(bean) | ||||||
|   | |||||||
| @@ -2,9 +2,22 @@ const axios = require("axios"); | |||||||
| const {R} = require("redbean-node"); | const {R} = require("redbean-node"); | ||||||
| const FormData = require('form-data'); | const FormData = require('form-data'); | ||||||
| const nodemailer = require("nodemailer"); | const nodemailer = require("nodemailer"); | ||||||
|  | const child_process = require("child_process"); | ||||||
|  |  | ||||||
| class Notification { | 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) { |     static async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | ||||||
|  |         let okMsg = "Sent Successfully. "; | ||||||
|  |  | ||||||
|         if (notification.type === "telegram") { |         if (notification.type === "telegram") { | ||||||
|             try { |             try { | ||||||
|                 await axios.get(`https://api.telegram.org/bot${notification.telegramBotToken}/sendMessage`, { |                 await axios.get(`https://api.telegram.org/bot${notification.telegramBotToken}/sendMessage`, { | ||||||
| @@ -13,15 +26,16 @@ class Notification { | |||||||
|                         text: msg, |                         text: msg, | ||||||
|                     } |                     } | ||||||
|                 }) |                 }) | ||||||
|                 return true; |                 return okMsg; | ||||||
|  |  | ||||||
|             } catch (error) { |             } catch (error) { | ||||||
|                 console.error(error) |                 let msg = (error.response.data.description) ? error.response.data.description : "Error without description" | ||||||
|                 return false; |                 throw new Error(msg) | ||||||
|             } |             } | ||||||
|  |  | ||||||
|         } else if (notification.type === "gotify") { |         } else if (notification.type === "gotify") { | ||||||
|             try { |             try { | ||||||
|                 if (notification.gotifyserverurl.endsWith("/")) { |                 if (notification.gotifyserverurl && notification.gotifyserverurl.endsWith("/")) { | ||||||
|                     notification.gotifyserverurl = notification.gotifyserverurl.slice(0, -1); |                     notification.gotifyserverurl = notification.gotifyserverurl.slice(0, -1); | ||||||
|                 } |                 } | ||||||
|                 await axios.post(`${notification.gotifyserverurl}/message?token=${notification.gotifyapplicationToken}`, { |                 await axios.post(`${notification.gotifyserverurl}/message?token=${notification.gotifyapplicationToken}`, { | ||||||
| @@ -29,15 +43,15 @@ class Notification { | |||||||
|                     "priority": notification.gotifyPriority || 8, |                     "priority": notification.gotifyPriority || 8, | ||||||
|                     "title": "Uptime-Kuma" |                     "title": "Uptime-Kuma" | ||||||
|                 }) |                 }) | ||||||
|                 return true; |  | ||||||
|  |                 return okMsg; | ||||||
|  |  | ||||||
|             } catch (error) { |             } catch (error) { | ||||||
|                 console.error(error) |                 throwGeneralAxiosError(error) | ||||||
|                 return false; |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|         } else if (notification.type === "webhook") { |         } else if (notification.type === "webhook") { | ||||||
|             try { |             try { | ||||||
|  |  | ||||||
|                 let data = { |                 let data = { | ||||||
|                     heartbeat: heartbeatJSON, |                     heartbeat: heartbeatJSON, | ||||||
|                     monitor: monitorJSON, |                     monitor: monitorJSON, | ||||||
| @@ -58,11 +72,11 @@ class Notification { | |||||||
|                     finalData = data; |                     finalData = data; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 await axios.post(notification.webhookURL, finalData, config) |                 let res = await axios.post(notification.webhookURL, finalData, config) | ||||||
|                 return true; |                 return okMsg; | ||||||
|  |  | ||||||
|             } catch (error) { |             } catch (error) { | ||||||
|                 console.error(error) |                 throwGeneralAxiosError(error) | ||||||
|                 return false; |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|         } else if (notification.type === "smtp") { |         } else if (notification.type === "smtp") { | ||||||
| @@ -77,7 +91,7 @@ class Notification { | |||||||
|                   content: msg |                   content: msg | ||||||
|                 } |                 } | ||||||
|                 let res = await axios.post(notification.discordWebhookUrl, data) |                 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 is not null, we go into the normal alerting loop. | ||||||
|               if(heartbeatJSON['status'] == 0) { |               if(heartbeatJSON['status'] == 0) { | ||||||
| @@ -102,12 +116,10 @@ class Notification { | |||||||
|                   ] |                   ] | ||||||
|                 }] |                 }] | ||||||
|               } |               } | ||||||
|  |               let res = await axios.post(notification.discordWebhookUrl, data) | ||||||
|               await axios.post(notification.discordWebhookUrl, data) |               return okMsg; | ||||||
|               return true; |  | ||||||
|             } catch(error) { |             } catch(error) { | ||||||
|               console.error(error) |               throwGeneralAxiosError(error) | ||||||
|               return false; |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|         } else if (notification.type === "signal") { |         } else if (notification.type === "signal") { | ||||||
| @@ -119,19 +131,18 @@ class Notification { | |||||||
|             }; |             }; | ||||||
|             let config = {}; |             let config = {}; | ||||||
|  |  | ||||||
|             await axios.post(notification.signalURL, data, config) |             let res = await axios.post(notification.signalURL, data, config) | ||||||
|             return true; |             return okMsg; | ||||||
|         } catch (error) { |         } catch (error) { | ||||||
|             console.error(error) |               throwGeneralAxiosError(error) | ||||||
|             return false; |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         } else if (notification.type === "slack") { |         } else if (notification.type === "slack") { | ||||||
|             try { |             try { | ||||||
|                 if (heartbeatJSON == null) { |                 if (heartbeatJSON == null) { | ||||||
|                     let data = {'text': "Uptime Kuma Slack testing successful.", 'channel': notification.slackchannel, 'username': notification.slackusername, 'icon_emoji': notification.slackiconemo} |                     let data = {'text': "Uptime Kuma Slack testing successful.", 'channel': notification.slackchannel, 'username': notification.slackusername, 'icon_emoji': notification.slackiconemo} | ||||||
|                     await axios.post(notification.slackwebhookURL, data) |                     let res = await axios.post(notification.slackwebhookURL, data) | ||||||
|                     return true; |                     return okMsg; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 const time = heartbeatJSON["time"]; |                 const time = heartbeatJSON["time"]; | ||||||
| @@ -175,22 +186,21 @@ class Notification { | |||||||
|                             } |                             } | ||||||
|                         ] |                         ] | ||||||
|                     } |                     } | ||||||
|                 await axios.post(notification.slackwebhookURL, data) |                 let res = await axios.post(notification.slackwebhookURL, data) | ||||||
|                 return true; |                 return okMsg; | ||||||
|             } catch (error) { |             } catch (error) { | ||||||
|                 console.error(error) |                 throwGeneralAxiosError(error) | ||||||
|                 return false; |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|         } else if (notification.type === "pushover") { |         } else if (notification.type === "pushover") { | ||||||
|                     var pushoverlink = 'https://api.pushover.net/1/messages.json' |                     var pushoverlink = 'https://api.pushover.net/1/messages.json' | ||||||
|             try { |             try { | ||||||
|                 if (heartbeatJSON == null) { |                 if (heartbeatJSON == null) { | ||||||
|                     let data = {'message': "<b>Uptime Kuma Pushover testing successful.</b>",  |                     let data = {'message': "<b>Uptime Kuma Pushover testing successful.</b>", | ||||||
|                     'user': notification.pushoveruserkey, 'token': notification.pushoverapptoken, 'sound':notification.pushoversounds, |                     'user': notification.pushoveruserkey, 'token': notification.pushoverapptoken, 'sound':notification.pushoversounds, | ||||||
|                     'priority': notification.pushoverpriority, 'title':notification.pushovertitle, 'retry': "30", 'expire':"3600", 'html': 1} |                     'priority': notification.pushoverpriority, 'title':notification.pushovertitle, 'retry': "30", 'expire':"3600", 'html': 1} | ||||||
|                     let res = await axios.post(pushoverlink, data) |                     let res = await axios.post(pushoverlink, data) | ||||||
|                     return true; |                     return okMsg; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 let data = { |                 let data = { | ||||||
| @@ -205,12 +215,15 @@ class Notification { | |||||||
|                     "html": 1 |                     "html": 1 | ||||||
|                     } |                     } | ||||||
|                 let res = await axios.post(pushoverlink, data) |                 let res = await axios.post(pushoverlink, data) | ||||||
|                 return true; |                 return okMsg; | ||||||
|             } catch (error) { |             } catch (error) { | ||||||
|                 console.log(error) |                 throwGeneralAxiosError(error) | ||||||
|                 return false; |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |         } else if (notification.type === "apprise") { | ||||||
|  |  | ||||||
|  |             return Notification.apprise(notification, msg) | ||||||
|  |  | ||||||
|         } else { |         } else { | ||||||
|             throw new Error("Notification type is not supported") |             throw new Error("Notification type is not supported") | ||||||
|         } |         } | ||||||
| @@ -272,20 +285,47 @@ class Notification { | |||||||
|             text: msg, |             text: msg, | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         return true; |         return "Sent Successfully."; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     static async discord(notification, msg) { |     static async apprise(notification, msg) { | ||||||
|         const client = new Discord.Client(); |         let s = child_process.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL]) | ||||||
|         await client.login(notification.discordToken) |  | ||||||
|  |  | ||||||
|         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 = { | module.exports = { | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ const fs = require("fs"); | |||||||
| const {getSettings} = require("./util-server"); | const {getSettings} = require("./util-server"); | ||||||
| const {Notification} = require("./notification") | const {Notification} = require("./notification") | ||||||
| const gracefulShutdown = require('http-graceful-shutdown'); | const gracefulShutdown = require('http-graceful-shutdown'); | ||||||
|  | const Database = require("./database"); | ||||||
| const {sleep} = require("./util"); | const {sleep} = require("./util"); | ||||||
| const args = require('args-parser')(process.argv); | const args = require('args-parser')(process.argv); | ||||||
|  |  | ||||||
| @@ -27,27 +28,48 @@ const server = http.createServer(app); | |||||||
| const io = new Server(server); | const io = new Server(server); | ||||||
| app.use(express.json()) | app.use(express.json()) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Total WebSocket client connected to server currently, no actual use | ||||||
|  |  * @type {number} | ||||||
|  |  */ | ||||||
| let totalClient = 0; | let totalClient = 0; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Use for decode the auth object | ||||||
|  |  * @type {null} | ||||||
|  |  */ | ||||||
| let jwtSecret = null; | let jwtSecret = null; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Main monitor list | ||||||
|  |  * @type {{}} | ||||||
|  |  */ | ||||||
| let monitorList = {}; | let monitorList = {}; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Show Setup Page | ||||||
|  |  * @type {boolean} | ||||||
|  |  */ | ||||||
| let needSetup = false; | let needSetup = false; | ||||||
|  |  | ||||||
| (async () => { | (async () => { | ||||||
|     await initDatabase(); |     await initDatabase(); | ||||||
|  |  | ||||||
|  |     console.log("Adding route") | ||||||
|     app.use('/', express.static("dist")); |     app.use('/', express.static("dist")); | ||||||
|  |  | ||||||
|     app.get('*', function(request, response, next) { |     app.get('*', function(request, response, next) { | ||||||
|         response.sendFile(process.cwd() + '/dist/index.html'); |         response.sendFile(process.cwd() + '/dist/index.html'); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     console.log("Adding socket handler") | ||||||
|     io.on('connection', async (socket) => { |     io.on('connection', async (socket) => { | ||||||
|  |  | ||||||
|         socket.emit("info", { |         socket.emit("info", { | ||||||
|             version, |             version, | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|         console.log('a user connected'); |  | ||||||
|         totalClient++; |         totalClient++; | ||||||
|  |  | ||||||
|         if (needSetup) { |         if (needSetup) { | ||||||
| @@ -56,7 +78,6 @@ let needSetup = false; | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         socket.on('disconnect', () => { |         socket.on('disconnect', () => { | ||||||
|             console.log('user disconnected'); |  | ||||||
|             totalClient--; |             totalClient--; | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
| @@ -433,25 +454,36 @@ let needSetup = false; | |||||||
|             try { |             try { | ||||||
|                 checkLogin(socket) |                 checkLogin(socket) | ||||||
|  |  | ||||||
|                 await Notification.send(notification, notification.name + " Testing") |                 let msg = await Notification.send(notification, notification.name + " Testing") | ||||||
|  |  | ||||||
|                 callback({ |                 callback({ | ||||||
|                     ok: true, |                     ok: true, | ||||||
|                     msg: "Sent Successfully" |                     msg | ||||||
|                 }); |                 }); | ||||||
|  |  | ||||||
|             } catch (e) { |             } catch (e) { | ||||||
|  |                 console.error(e) | ||||||
|  |  | ||||||
|                 callback({ |                 callback({ | ||||||
|                     ok: false, |                     ok: false, | ||||||
|                     msg: e.message |                     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, () => { |     server.listen(port, hostname, () => { | ||||||
|         console.log(`Listening on ${hostname}:${port}`); |         console.log(`Listening on ${hostname}:${port}`); | ||||||
|  |  | ||||||
|         startMonitors(); |         startMonitors(); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
| @@ -539,18 +571,21 @@ function checkLogin(socket) { | |||||||
| } | } | ||||||
|  |  | ||||||
| async function initDatabase() { | async function initDatabase() { | ||||||
|     const path = './data/kuma.db'; |     if (! fs.existsSync(Database.path)) { | ||||||
|  |  | ||||||
|     if (! fs.existsSync(path)) { |  | ||||||
|         console.log("Copying Database") |         console.log("Copying Database") | ||||||
|         fs.copyFileSync("./db/kuma.db", path); |         fs.copyFileSync(Database.templatePath, Database.path); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     console.log("Connecting to Database") |     console.log("Connecting to Database") | ||||||
|  |  | ||||||
|     R.setup('sqlite', { |     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) |     R.freeze(true) | ||||||
|     await R.autoloadModels("./server/model"); |     await R.autoloadModels("./server/model"); | ||||||
|  |  | ||||||
| @@ -565,10 +600,12 @@ async function initDatabase() { | |||||||
|  |  | ||||||
|         jwtSecretBean.value = passwordHash.generate(dayjs() + "") |         jwtSecretBean.value = passwordHash.generate(dayjs() + "") | ||||||
|         await R.store(jwtSecretBean) |         await R.store(jwtSecretBean) | ||||||
|  |         console.log("Stored JWT secret into database") | ||||||
|     } else { |     } else { | ||||||
|         console.log("Load JWT secret from database.") |         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) { |     if ((await R.count("user")) === 0) { | ||||||
|         console.log("No user, need setup") |         console.log("No user, need setup") | ||||||
|         needSetup = true; |         needSetup = true; | ||||||
| @@ -687,11 +724,6 @@ const startGracefulShutdown = async () => { | |||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| let noReject = true; |  | ||||||
| process.on('unhandledRejection', (reason, p) => { |  | ||||||
|     noReject = false; |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| async function shutdownFunction(signal) { | async function shutdownFunction(signal) { | ||||||
|     console.log('Called signal: ' + signal); |     console.log('Called signal: ' + signal); | ||||||
|  |  | ||||||
| @@ -700,24 +732,8 @@ async function shutdownFunction(signal) { | |||||||
|         let monitor = monitorList[id] |         let monitor = monitorList[id] | ||||||
|         monitor.stop() |         monitor.stop() | ||||||
|     } |     } | ||||||
|     await sleep(2000) |     await sleep(2000); | ||||||
|  |     await Database.close(); | ||||||
|     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") |  | ||||||
| } | } | ||||||
|  |  | ||||||
| function finalFunction() { | 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) { | exports.getSettings = async function (type) { | ||||||
|     let list = await R.getAll("SELECT * FROM setting WHERE `type` = ? ", [ |     let list = await R.getAll("SELECT * FROM setting WHERE `type` = ? ", [ | ||||||
|         type |         type | ||||||
|   | |||||||
| @@ -15,14 +15,14 @@ | |||||||
|                     <label for="floatingPassword">Password</label> |                     <label for="floatingPassword">Password</label> | ||||||
|                 </div> |                 </div> | ||||||
|  |  | ||||||
|                 <div class="form-check mb-3 mt-3" > |                 <div class="form-check mb-3 mt-3 d-flex justify-content-center pe-4"> | ||||||
|                     <label> |                     <div class="form-check"> | ||||||
|                         <input type="checkbox" value="remember-me" class="form-check-input" id="remember" v-model="$root.remember"> |                         <input type="checkbox" value="remember-me" class="form-check-input" id="remember" v-model="$root.remember"> | ||||||
|  |  | ||||||
|                         <label class="form-check-label" for="remember"> |                         <label class="form-check-label" for="remember"> | ||||||
|                             Remember me |                             Remember me | ||||||
|                         </label> |                         </label> | ||||||
|                     </label> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|                 <button class="w-100 btn btn-primary" type="submit" :disabled="processing">Login</button> |                 <button class="w-100 btn btn-primary" type="submit" :disabled="processing">Login</button> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,60 +10,61 @@ | |||||||
|                     </div> |                     </div> | ||||||
|                     <div class="modal-body"> |                     <div class="modal-body"> | ||||||
|  |  | ||||||
|  |                         <div class="mb-3"> | ||||||
|  |                             <label for="type" class="form-label">Notification Type</label> | ||||||
|  |                             <select class="form-select"  id="type" v-model="notification.type"> | ||||||
|  |                                 <option value="telegram">Telegram</option> | ||||||
|  |                                 <option value="webhook">Webhook</option> | ||||||
|  |                                 <option value="smtp">Email (SMTP)</option> | ||||||
|  |                                 <option value="discord">Discord</option> | ||||||
|  |                                 <option value="signal">Signal</option> | ||||||
|  |                                 <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> | ||||||
|  |  | ||||||
|  |                         <div class="mb-3"> | ||||||
|  |                             <label for="name" class="form-label">Friendly Name</label> | ||||||
|  |                             <input type="text" class="form-control" id="name" required v-model="notification.name"> | ||||||
|  |                         </div> | ||||||
|  |  | ||||||
|  |                         <template v-if="notification.type === 'telegram'"> | ||||||
|                             <div class="mb-3"> |                             <div class="mb-3"> | ||||||
|                                 <label for="type" class="form-label">Notification Type</label> |                                 <label for="telegram-bot-token" class="form-label">Bot Token</label> | ||||||
|                                 <select class="form-select" id="type" v-model="notification.type"> |                                 <input type="text" class="form-control" id="telegram-bot-token" required v-model="notification.telegramBotToken"> | ||||||
|                                     <option value="telegram">Telegram</option> |                                 <div class="form-text">You can get a token from <a href="https://t.me/BotFather" target="_blank">https://t.me/BotFather</a>.</div> | ||||||
|                                     <option value="webhook">Webhook</option> |  | ||||||
|                                     <option value="smtp">Email (SMTP)</option> |  | ||||||
|                                     <option value="discord">Discord</option> |  | ||||||
|                                     <option value="signal">Signal</option> |  | ||||||
|                                     <option value="gotify">Gotify</option> |  | ||||||
|                                     <option value="slack">Slack</option> |  | ||||||
|                                     <option value="pushover">Pushover</option> |  | ||||||
|                                 </select> |  | ||||||
|                             </div> |                             </div> | ||||||
|  |  | ||||||
|                             <div class="mb-3"> |                             <div class="mb-3"> | ||||||
|                                 <label for="name" class="form-label">Friendly Name</label> |                                 <label for="telegram-chat-id" class="form-label">Chat ID</label> | ||||||
|                                 <input type="text" class="form-control" id="name" required v-model="notification.name"> |  | ||||||
|  |                                 <div class="input-group mb-3"> | ||||||
|  |                                     <input type="text" class="form-control" id="telegram-chat-id" required v-model="notification.telegramChatID"> | ||||||
|  |                                     <button class="btn btn-outline-secondary" type="button" @click="autoGetTelegramChatID" v-if="notification.telegramBotToken">Auto Get</button> | ||||||
|  |                                 </div> | ||||||
|  |  | ||||||
|  |                                 <div class="form-text"> | ||||||
|  |                                     Support Direct Chat / Group / Channel's Chat ID | ||||||
|  |  | ||||||
|  |                                     <p style="margin-top: 8px;"> | ||||||
|  |                                         You can get your chat id by sending message to the bot and go to this url to view the chat_id: | ||||||
|  |                                     </p> | ||||||
|  |  | ||||||
|  |                                     <p style="margin-top: 8px;"> | ||||||
|  |  | ||||||
|  |                                         <template v-if="notification.telegramBotToken"> | ||||||
|  |                                             <a :href="telegramGetUpdatesURL" target="_blank" style="word-break: break-word;">{{ telegramGetUpdatesURL }}</a> | ||||||
|  |                                         </template> | ||||||
|  |  | ||||||
|  |                                         <template v-else> | ||||||
|  |                                             {{ telegramGetUpdatesURL }} | ||||||
|  |                                         </template> | ||||||
|  |                                     </p> | ||||||
|  |                                 </div> | ||||||
|                             </div> |                             </div> | ||||||
|  |                         </template> | ||||||
|                             <template v-if="notification.type === 'telegram'"> |  | ||||||
|                                 <div class="mb-3"> |  | ||||||
|                                     <label for="telegram-bot-token" class="form-label">Bot Token</label> |  | ||||||
|                                     <input type="text" class="form-control" id="telegram-bot-token" required v-model="notification.telegramBotToken"> |  | ||||||
|                                     <div class="form-text">You can get a token from <a href="https://t.me/BotFather" target="_blank">https://t.me/BotFather</a>.</div> |  | ||||||
|                                 </div> |  | ||||||
|  |  | ||||||
|                                 <div class="mb-3"> |  | ||||||
|                                     <label for="telegram-chat-id" class="form-label">Chat ID</label> |  | ||||||
|  |  | ||||||
|                                     <div class="input-group mb-3"> |  | ||||||
|                                         <input type="text" class="form-control" id="telegram-chat-id" required v-model="notification.telegramChatID"> |  | ||||||
|                                         <button class="btn btn-outline-secondary" type="button" @click="autoGetTelegramChatID" v-if="notification.telegramBotToken">Auto Get</button> |  | ||||||
|                                     </div> |  | ||||||
|  |  | ||||||
|                                     <div class="form-text"> |  | ||||||
|                                         Support Direct Chat / Group / Channel's Chat ID |  | ||||||
|  |  | ||||||
|                                         <p style="margin-top: 8px;"> |  | ||||||
|                                             You can get your chat id by sending message to the bot and go to this url to view the chat_id: |  | ||||||
|                                         </p> |  | ||||||
|  |  | ||||||
|                                         <p style="margin-top: 8px;"> |  | ||||||
|  |  | ||||||
|                                             <template v-if="notification.telegramBotToken"> |  | ||||||
|                                                 <a :href="telegramGetUpdatesURL" target="_blank">{{ telegramGetUpdatesURL }}</a> |  | ||||||
|                                             </template> |  | ||||||
|  |  | ||||||
|                                             <template v-else> |  | ||||||
|                                                 {{ telegramGetUpdatesURL }} |  | ||||||
|                                             </template> |  | ||||||
|                                         </p> |  | ||||||
|                                     </div> |  | ||||||
|                                 </div> |  | ||||||
|                             </template> |  | ||||||
|  |  | ||||||
|                         <template v-if="notification.type === 'webhook'"> |                         <template v-if="notification.type === 'webhook'"> | ||||||
|                             <div class="mb-3"> |                             <div class="mb-3"> | ||||||
| @@ -219,7 +220,7 @@ | |||||||
|                                 </div> |                                 </div> | ||||||
|                             </div> |                             </div> | ||||||
|                         </template> |                         </template> | ||||||
|                          |  | ||||||
|                         <template v-if="notification.type === 'pushover'"> |                         <template v-if="notification.type === 'pushover'"> | ||||||
|                             <div class="mb-3"> |                             <div class="mb-3"> | ||||||
|                                 <label for="pushover-app-token" class="form-label">Application Token<span style="color:red;"><sup>*</sup></span></label> |                                 <label for="pushover-app-token" class="form-label">Application Token<span style="color:red;"><sup>*</sup></span></label> | ||||||
| @@ -269,6 +270,29 @@ | |||||||
|                             </div> |                             </div> | ||||||
|                         </template> |                         </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> | ||||||
|                     <div class="modal-footer"> |                     <div class="modal-footer"> | ||||||
|                         <button type="button" class="btn btn-danger" @click="deleteConfirm" :disabled="processing" v-if="id">Delete</button> |                         <button type="button" class="btn btn-danger" @click="deleteConfirm" :disabled="processing" v-if="id">Delete</button> | ||||||
| @@ -307,17 +331,15 @@ export default { | |||||||
|                 type: null, |                 type: null, | ||||||
|                 gotifyPriority: 8 |                 gotifyPriority: 8 | ||||||
|             }, |             }, | ||||||
|  |             appriseInstalled: false, | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|     mounted() { |     mounted() { | ||||||
|         this.modal = new Modal(this.$refs.modal) |         this.modal = new Modal(this.$refs.modal) | ||||||
|  |  | ||||||
|         // TODO: for edit |         this.$root.getSocket().emit("checkApprise", (installed) => { | ||||||
|         this.$root.getSocket().emit("getSettings", "notification", (data) => { |             this.appriseInstalled = installed; | ||||||
|           //  this.notification = data |  | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|  |  | ||||||
|     }, |     }, | ||||||
|     methods: { |     methods: { | ||||||
|  |  | ||||||
|   | |||||||
| @@ -35,7 +35,8 @@ export default { | |||||||
|         window.addEventListener('resize', this.onResize); |         window.addEventListener('resize', this.onResize); | ||||||
|  |  | ||||||
|         let wsHost; |         let wsHost; | ||||||
|         if (localStorage.dev === "dev") { |         const env = process.env.NODE_ENV || "production"; | ||||||
|  |         if (env === "development" || localStorage.dev === "dev") { | ||||||
|             wsHost = ":3001" |             wsHost = ":3001" | ||||||
|         } else { |         } else { | ||||||
|             wsHost = "" |             wsHost = "" | ||||||
| @@ -45,6 +46,10 @@ export default { | |||||||
|             transports: ['websocket'] |             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) => { |         socket.on('info', (info) => { | ||||||
|             this.info = info; |             this.info = info; | ||||||
|         }); |         }); | ||||||
| @@ -158,7 +163,7 @@ export default { | |||||||
|         }, |         }, | ||||||
|  |  | ||||||
|         getSocket() { |         getSocket() { | ||||||
|           return socket; |             return socket; | ||||||
|         }, |         }, | ||||||
|  |  | ||||||
|         toastRes(res) { |         toastRes(res) { | ||||||
|   | |||||||
| @@ -47,7 +47,7 @@ | |||||||
|                 </tr> |                 </tr> | ||||||
|                 </thead> |                 </thead> | ||||||
|                 <tbody> |                 <tbody> | ||||||
|                 <tr v-for="beat in importantHeartBeatList"> |                 <tr v-for="(beat, index) in displayedRecords" :key="index"> | ||||||
|                     <td>{{ beat.name }}</td> |                     <td>{{ beat.name }}</td> | ||||||
|                     <td><Status :status="beat.status" /></td> |                     <td><Status :status="beat.status" /></td> | ||||||
|                     <td><Datetime :value="beat.time" /></td> |                     <td><Datetime :value="beat.time" /></td> | ||||||
| @@ -59,6 +59,13 @@ | |||||||
|                 </tr> |                 </tr> | ||||||
|                 </tbody> |                 </tbody> | ||||||
|             </table> |             </table> | ||||||
|  |  | ||||||
|  |             <div class="d-flex justify-content-center kuma_pagination"> | ||||||
|  |                 <pagination | ||||||
|  |                     v-model="page" | ||||||
|  |                     :records=importantHeartBeatList.length | ||||||
|  |                     :per-page="perPage" /> | ||||||
|  |             </div> | ||||||
|         </div> |         </div> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
| @@ -68,8 +75,21 @@ | |||||||
| <script> | <script> | ||||||
| import Status from "../components/Status.vue"; | import Status from "../components/Status.vue"; | ||||||
| import Datetime from "../components/Datetime.vue"; | import Datetime from "../components/Datetime.vue"; | ||||||
|  | import Pagination from "v-pagination-3"; | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     components: {Datetime, Status}, |     components: { | ||||||
|  |         Datetime, | ||||||
|  |         Status, | ||||||
|  |         Pagination, | ||||||
|  |     }, | ||||||
|  |     data() { | ||||||
|  |         return { | ||||||
|  |             page: 1, | ||||||
|  |             perPage: 25, | ||||||
|  |             heartBeatList: [], | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|     computed: { |     computed: { | ||||||
|         stats() { |         stats() { | ||||||
|             let result = { |             let result = { | ||||||
| @@ -127,8 +147,16 @@ export default { | |||||||
|                 } |                 } | ||||||
|             }); |             }); | ||||||
|  |  | ||||||
|  |             this.heartBeatList = result; | ||||||
|  |  | ||||||
|             return result; |             return result; | ||||||
|         } |         }, | ||||||
|  |  | ||||||
|  |         displayedRecords() { | ||||||
|  |             const startIndex = this.perPage * (this.page - 1); | ||||||
|  |             const endIndex = startIndex + this.perPage; | ||||||
|  |             return this.heartBeatList.slice(startIndex, endIndex); | ||||||
|  |         }, | ||||||
|     } |     } | ||||||
| } | } | ||||||
| </script> | </script> | ||||||
|   | |||||||
| @@ -64,7 +64,7 @@ | |||||||
|                 </tr> |                 </tr> | ||||||
|             </thead> |             </thead> | ||||||
|             <tbody> |             <tbody> | ||||||
|                 <tr v-for="beat in importantHeartBeatList"> |                 <tr v-for="(beat, index) in displayedRecords" :key="index"> | ||||||
|                     <td><Status :status="beat.status" /></td> |                     <td><Status :status="beat.status" /></td> | ||||||
|                     <td><Datetime :value="beat.time" /></td> |                     <td><Datetime :value="beat.time" /></td> | ||||||
|                     <td>{{ beat.msg }}</td> |                     <td>{{ beat.msg }}</td> | ||||||
| @@ -75,6 +75,13 @@ | |||||||
|                 </tr> |                 </tr> | ||||||
|             </tbody> |             </tbody> | ||||||
|         </table> |         </table> | ||||||
|  |  | ||||||
|  |         <div class="d-flex justify-content-center kuma_pagination"> | ||||||
|  |             <pagination | ||||||
|  |                 v-model="page" | ||||||
|  |                 :records=importantHeartBeatList.length | ||||||
|  |                 :per-page="perPage" /> | ||||||
|  |         </div> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|     <Confirm ref="confirmPause" @yes="pauseMonitor"> |     <Confirm ref="confirmPause" @yes="pauseMonitor"> | ||||||
| @@ -95,6 +102,7 @@ import Status from "../components/Status.vue"; | |||||||
| import Datetime from "../components/Datetime.vue"; | 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"; | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     components: { |     components: { | ||||||
| @@ -104,13 +112,16 @@ export default { | |||||||
|         HeartbeatBar, |         HeartbeatBar, | ||||||
|         Confirm, |         Confirm, | ||||||
|         Status, |         Status, | ||||||
|  |         Pagination, | ||||||
|     }, |     }, | ||||||
|     mounted() { |     mounted() { | ||||||
|  |  | ||||||
|     }, |     }, | ||||||
|     data() { |     data() { | ||||||
|         return { |         return { | ||||||
|  |             page: 1, | ||||||
|  |             perPage: 25, | ||||||
|  |             heartBeatList: [], | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|     computed: { |     computed: { | ||||||
| @@ -154,6 +165,7 @@ export default { | |||||||
|  |  | ||||||
|         importantHeartBeatList() { |         importantHeartBeatList() { | ||||||
|             if (this.$root.importantHeartbeatList[this.monitor.id]) { |             if (this.$root.importantHeartbeatList[this.monitor.id]) { | ||||||
|  |                 this.heartBeatList = this.$root.importantHeartbeatList[this.monitor.id]; | ||||||
|                 return this.$root.importantHeartbeatList[this.monitor.id] |                 return this.$root.importantHeartbeatList[this.monitor.id] | ||||||
|             } else { |             } else { | ||||||
|                 return []; |                 return []; | ||||||
| @@ -166,8 +178,13 @@ export default { | |||||||
|             } else { |             } else { | ||||||
|                 return { } |                 return { } | ||||||
|             } |             } | ||||||
|         } |         }, | ||||||
|  |  | ||||||
|  |         displayedRecords() { | ||||||
|  |             const startIndex = this.perPage * (this.page - 1); | ||||||
|  |             const endIndex = startIndex + this.perPage; | ||||||
|  |             return this.heartBeatList.slice(startIndex, endIndex); | ||||||
|  |         }, | ||||||
|     }, |     }, | ||||||
|     methods: { |     methods: { | ||||||
|         testNotification() { |         testNotification() { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user