mirror of
				https://github.com/louislam/uptime-kuma.git
				synced 2025-10-31 19:39:20 +08:00 
			
		
		
		
	Merge branch 'louislam:master' into bulgarian
This commit is contained in:
		README.md
db
docker
package-lock.jsonpackage.jsonserver
client.jsdatabase.jsdocker.js
model
notification-providers
notification.jsserver.jssocket-handlers
util-server.jssrc
| @@ -23,7 +23,7 @@ VPS is sponsored by Uptime Kuma sponsors on [Open Collective](https://opencollec | |||||||
|  |  | ||||||
| ## ⭐ Features | ## ⭐ Features | ||||||
|  |  | ||||||
| * Monitoring uptime for HTTP(s) / TCP / HTTP(s) Keyword / Ping / DNS Record / Push / Steam Game Server. | * Monitoring uptime for HTTP(s) / TCP / HTTP(s) Keyword / Ping / DNS Record / Push / Steam Game Server / Docker Containers. | ||||||
| * Fancy, Reactive, Fast UI/UX. | * Fancy, Reactive, Fast UI/UX. | ||||||
| * Notifications via Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP), and [90+ notification services, click here for the full list](https://github.com/louislam/uptime-kuma/tree/master/src/components/notifications). | * Notifications via Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP), and [90+ notification services, click here for the full list](https://github.com/louislam/uptime-kuma/tree/master/src/components/notifications). | ||||||
| * 20 second intervals. | * 20 second intervals. | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								db/patch-add-docker-columns.sql
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										18
									
								
								db/patch-add-docker-columns.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | -- You should not modify if this have pushed to Github, unless it does serious wrong with the db. | ||||||
|  | BEGIN TRANSACTION; | ||||||
|  |  | ||||||
|  | CREATE TABLE docker_host ( | ||||||
|  | 	id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, | ||||||
|  | 	user_id INT NOT NULL, | ||||||
|  | 	docker_daemon VARCHAR(255), | ||||||
|  | 	docker_type VARCHAR(255), | ||||||
|  | 	name VARCHAR(255) | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | ALTER TABLE monitor | ||||||
|  | 	ADD docker_host INTEGER REFERENCES docker_host(id); | ||||||
|  |  | ||||||
|  | ALTER TABLE monitor | ||||||
|  | 	ADD docker_container VARCHAR(255); | ||||||
|  |  | ||||||
|  | COMMIT; | ||||||
							
								
								
									
										18
									
								
								db/patch-add-radius-monitor.sql
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										18
									
								
								db/patch-add-radius-monitor.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | BEGIN TRANSACTION; | ||||||
|  |  | ||||||
|  | ALTER TABLE monitor | ||||||
|  |     ADD radius_username VARCHAR(255); | ||||||
|  |  | ||||||
|  | ALTER TABLE monitor | ||||||
|  |     ADD radius_password VARCHAR(255); | ||||||
|  |  | ||||||
|  | ALTER TABLE monitor | ||||||
|  |     ADD radius_calling_station_id VARCHAR(50); | ||||||
|  |  | ||||||
|  | ALTER TABLE monitor | ||||||
|  |     ADD radius_called_station_id VARCHAR(50); | ||||||
|  |  | ||||||
|  | ALTER TABLE monitor | ||||||
|  |     ADD radius_secret VARCHAR(255); | ||||||
|  |  | ||||||
|  | COMMIT | ||||||
							
								
								
									
										10
									
								
								db/patch-monitor-add-resend-interval.sql
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										10
									
								
								db/patch-monitor-add-resend-interval.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | -- You should not modify if this have pushed to Github, unless it does serious wrong with the db. | ||||||
|  | BEGIN TRANSACTION; | ||||||
|  |  | ||||||
|  | ALTER TABLE monitor | ||||||
|  |     ADD resend_interval INTEGER default 0 not null; | ||||||
|  |  | ||||||
|  | ALTER TABLE heartbeat | ||||||
|  |     ADD down_count INTEGER default 0 not null; | ||||||
|  |  | ||||||
|  | COMMIT; | ||||||
| @@ -4,5 +4,5 @@ WORKDIR /app | |||||||
|  |  | ||||||
| # Install apprise, iputils for non-root ping, setpriv | # Install apprise, iputils for non-root ping, setpriv | ||||||
| RUN apk add --no-cache iputils setpriv dumb-init python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib && \ | RUN apk add --no-cache iputils setpriv dumb-init python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib && \ | ||||||
|     pip3 --no-cache-dir install apprise==0.9.9 && \ |     pip3 --no-cache-dir install apprise==1.0.0 && \ | ||||||
|     rm -rf /root/.cache |     rm -rf /root/.cache | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ WORKDIR /app | |||||||
| RUN apt update && \ | RUN apt update && \ | ||||||
|     apt --yes --no-install-recommends install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \ |     apt --yes --no-install-recommends install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \ | ||||||
|         sqlite3 iputils-ping util-linux dumb-init && \ |         sqlite3 iputils-ping util-linux dumb-init && \ | ||||||
|     pip3 --no-cache-dir install apprise==0.9.9 && \ |     pip3 --no-cache-dir install apprise==1.0.0 && \ | ||||||
|     rm -rf /var/lib/apt/lists/* && \ |     rm -rf /var/lib/apt/lists/* && \ | ||||||
|     apt --yes autoremove |     apt --yes autoremove | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										114
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										114
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -39,6 +39,7 @@ | |||||||
|                 "mqtt": "^4.2.8", |                 "mqtt": "^4.2.8", | ||||||
|                 "mssql": "^8.1.0", |                 "mssql": "^8.1.0", | ||||||
|                 "node-cloudflared-tunnel": "~1.0.9", |                 "node-cloudflared-tunnel": "~1.0.9", | ||||||
|  |                 "node-radius-client": "^1.0.0", | ||||||
|                 "nodemailer": "~6.6.5", |                 "nodemailer": "~6.6.5", | ||||||
|                 "notp": "~2.0.3", |                 "notp": "~2.0.3", | ||||||
|                 "password-hash": "~1.2.2", |                 "password-hash": "~1.2.2", | ||||||
| @@ -8215,6 +8216,12 @@ | |||||||
|                 "readable-stream": "^3.6.0" |                 "readable-stream": "^3.6.0" | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|  |         "node_modules/hoek": { | ||||||
|  |             "version": "6.1.3", | ||||||
|  |             "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz", | ||||||
|  |             "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==", | ||||||
|  |             "deprecated": "This module has moved and is now available at @hapi/hoek. Please update your dependencies as this version is no longer maintained an may contain bugs and security issues." | ||||||
|  |         }, | ||||||
|         "node_modules/homedir-polyfill": { |         "node_modules/homedir-polyfill": { | ||||||
|             "version": "1.0.3", |             "version": "1.0.3", | ||||||
|             "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", |             "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", | ||||||
| @@ -8915,6 +8922,17 @@ | |||||||
|             "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", |             "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", | ||||||
|             "devOptional": true |             "devOptional": true | ||||||
|         }, |         }, | ||||||
|  |         "node_modules/isemail": { | ||||||
|  |             "version": "3.2.0", | ||||||
|  |             "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", | ||||||
|  |             "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", | ||||||
|  |             "dependencies": { | ||||||
|  |                 "punycode": "2.x.x" | ||||||
|  |             }, | ||||||
|  |             "engines": { | ||||||
|  |                 "node": ">=4.0.0" | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|         "node_modules/isexe": { |         "node_modules/isexe": { | ||||||
|             "version": "2.0.0", |             "version": "2.0.0", | ||||||
|             "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", |             "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", | ||||||
| @@ -12151,6 +12169,32 @@ | |||||||
|             "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", |             "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", | ||||||
|             "dev": true |             "dev": true | ||||||
|         }, |         }, | ||||||
|  |         "node_modules/node-radius-client": { | ||||||
|  |             "version": "1.0.0", | ||||||
|  |             "resolved": "https://registry.npmjs.org/node-radius-client/-/node-radius-client-1.0.0.tgz", | ||||||
|  |             "integrity": "sha512-FkR9cMV5hNoX+kKDUTzuagvEixlLiaEJQ1/ywOdhahsihKrGDhVZmnCvmrCStA589MT3yuC/J2eKc6z68IGdBw==", | ||||||
|  |             "dependencies": { | ||||||
|  |                 "joi": "^14.3.1", | ||||||
|  |                 "node-radius-utils": "^1.2.0", | ||||||
|  |                 "radius": "^1.1.4" | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         "node_modules/node-radius-client/node_modules/joi": { | ||||||
|  |             "version": "14.3.1", | ||||||
|  |             "resolved": "https://registry.npmjs.org/joi/-/joi-14.3.1.tgz", | ||||||
|  |             "integrity": "sha512-LQDdM+pkOrpAn4Lp+neNIFV3axv1Vna3j38bisbQhETPMANYRbFJFUyOZcOClYvM/hppMhGWuKSFEK9vjrB+bQ==", | ||||||
|  |             "deprecated": "This module has moved and is now available at @hapi/joi. Please update your dependencies as this version is no longer maintained an may contain bugs and security issues.", | ||||||
|  |             "dependencies": { | ||||||
|  |                 "hoek": "6.x.x", | ||||||
|  |                 "isemail": "3.x.x", | ||||||
|  |                 "topo": "3.x.x" | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         "node_modules/node-radius-utils": { | ||||||
|  |             "version": "1.2.0", | ||||||
|  |             "resolved": "https://registry.npmjs.org/node-radius-utils/-/node-radius-utils-1.2.0.tgz", | ||||||
|  |             "integrity": "sha512-i3Sf6khnenl0aXumo0whAlfPWTaBqHxEnVBBxpu3dZ7q69NkPPv71rvPjlDZ5wkeKCTNNUTECljerS5kcYQxRw==" | ||||||
|  |         }, | ||||||
|         "node_modules/node-releases": { |         "node_modules/node-releases": { | ||||||
|             "version": "2.0.5", |             "version": "2.0.5", | ||||||
|             "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz", |             "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz", | ||||||
| @@ -13429,6 +13473,14 @@ | |||||||
|                 "node": ">=8" |                 "node": ">=8" | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|  |         "node_modules/radius": { | ||||||
|  |             "version": "1.1.4", | ||||||
|  |             "resolved": "https://registry.npmjs.org/radius/-/radius-1.1.4.tgz", | ||||||
|  |             "integrity": "sha512-UWuzdF6xf3NpsXFZZmUEkxtEalDXj8hdmMXgbGzn7vOk6zXNsiIY2I6SJ1euHt7PTQuMoz2qDEJB+AfJDJgQYw==", | ||||||
|  |             "engines": { | ||||||
|  |                 "node": ">=0.8.0" | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|         "node_modules/range-parser": { |         "node_modules/range-parser": { | ||||||
|             "version": "1.2.1", |             "version": "1.2.1", | ||||||
|             "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", |             "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", | ||||||
| @@ -15261,6 +15313,15 @@ | |||||||
|                 "node": ">=0.6" |                 "node": ">=0.6" | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|  |         "node_modules/topo": { | ||||||
|  |             "version": "3.0.3", | ||||||
|  |             "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz", | ||||||
|  |             "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==", | ||||||
|  |             "deprecated": "This module has moved and is now available at @hapi/topo. Please update your dependencies as this version is no longer maintained an may contain bugs and security issues.", | ||||||
|  |             "dependencies": { | ||||||
|  |                 "hoek": "6.x.x" | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|         "node_modules/toposort": { |         "node_modules/toposort": { | ||||||
|             "version": "2.0.2", |             "version": "2.0.2", | ||||||
|             "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", |             "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", | ||||||
| @@ -22641,6 +22702,11 @@ | |||||||
|                 "readable-stream": "^3.6.0" |                 "readable-stream": "^3.6.0" | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|  |         "hoek": { | ||||||
|  |             "version": "6.1.3", | ||||||
|  |             "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz", | ||||||
|  |             "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==" | ||||||
|  |         }, | ||||||
|         "homedir-polyfill": { |         "homedir-polyfill": { | ||||||
|             "version": "1.0.3", |             "version": "1.0.3", | ||||||
|             "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", |             "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", | ||||||
| @@ -23123,6 +23189,14 @@ | |||||||
|             "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", |             "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", | ||||||
|             "devOptional": true |             "devOptional": true | ||||||
|         }, |         }, | ||||||
|  |         "isemail": { | ||||||
|  |             "version": "3.2.0", | ||||||
|  |             "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", | ||||||
|  |             "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", | ||||||
|  |             "requires": { | ||||||
|  |                 "punycode": "2.x.x" | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|         "isexe": { |         "isexe": { | ||||||
|             "version": "2.0.0", |             "version": "2.0.0", | ||||||
|             "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", |             "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", | ||||||
| @@ -25618,6 +25692,33 @@ | |||||||
|             "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", |             "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", | ||||||
|             "dev": true |             "dev": true | ||||||
|         }, |         }, | ||||||
|  |         "node-radius-client": { | ||||||
|  |             "version": "1.0.0", | ||||||
|  |             "resolved": "https://registry.npmjs.org/node-radius-client/-/node-radius-client-1.0.0.tgz", | ||||||
|  |             "integrity": "sha512-FkR9cMV5hNoX+kKDUTzuagvEixlLiaEJQ1/ywOdhahsihKrGDhVZmnCvmrCStA589MT3yuC/J2eKc6z68IGdBw==", | ||||||
|  |             "requires": { | ||||||
|  |                 "joi": "^14.3.1", | ||||||
|  |                 "node-radius-utils": "^1.2.0", | ||||||
|  |                 "radius": "^1.1.4" | ||||||
|  |             }, | ||||||
|  |             "dependencies": { | ||||||
|  |                 "joi": { | ||||||
|  |                     "version": "14.3.1", | ||||||
|  |                     "resolved": "https://registry.npmjs.org/joi/-/joi-14.3.1.tgz", | ||||||
|  |                     "integrity": "sha512-LQDdM+pkOrpAn4Lp+neNIFV3axv1Vna3j38bisbQhETPMANYRbFJFUyOZcOClYvM/hppMhGWuKSFEK9vjrB+bQ==", | ||||||
|  |                     "requires": { | ||||||
|  |                         "hoek": "6.x.x", | ||||||
|  |                         "isemail": "3.x.x", | ||||||
|  |                         "topo": "3.x.x" | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         "node-radius-utils": { | ||||||
|  |             "version": "1.2.0", | ||||||
|  |             "resolved": "https://registry.npmjs.org/node-radius-utils/-/node-radius-utils-1.2.0.tgz", | ||||||
|  |             "integrity": "sha512-i3Sf6khnenl0aXumo0whAlfPWTaBqHxEnVBBxpu3dZ7q69NkPPv71rvPjlDZ5wkeKCTNNUTECljerS5kcYQxRw==" | ||||||
|  |         }, | ||||||
|         "node-releases": { |         "node-releases": { | ||||||
|             "version": "2.0.5", |             "version": "2.0.5", | ||||||
|             "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz", |             "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz", | ||||||
| @@ -26532,6 +26633,11 @@ | |||||||
|             "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", |             "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", | ||||||
|             "dev": true |             "dev": true | ||||||
|         }, |         }, | ||||||
|  |         "radius": { | ||||||
|  |             "version": "1.1.4", | ||||||
|  |             "resolved": "https://registry.npmjs.org/radius/-/radius-1.1.4.tgz", | ||||||
|  |             "integrity": "sha512-UWuzdF6xf3NpsXFZZmUEkxtEalDXj8hdmMXgbGzn7vOk6zXNsiIY2I6SJ1euHt7PTQuMoz2qDEJB+AfJDJgQYw==" | ||||||
|  |         }, | ||||||
|         "range-parser": { |         "range-parser": { | ||||||
|             "version": "1.2.1", |             "version": "1.2.1", | ||||||
|             "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", |             "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", | ||||||
| @@ -27967,6 +28073,14 @@ | |||||||
|             "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", |             "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", | ||||||
|             "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" |             "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" | ||||||
|         }, |         }, | ||||||
|  |         "topo": { | ||||||
|  |             "version": "3.0.3", | ||||||
|  |             "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz", | ||||||
|  |             "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==", | ||||||
|  |             "requires": { | ||||||
|  |                 "hoek": "6.x.x" | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|         "toposort": { |         "toposort": { | ||||||
|             "version": "2.0.2", |             "version": "2.0.2", | ||||||
|             "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", |             "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|     "name": "uptime-kuma", |     "name": "uptime-kuma", | ||||||
|     "version": "1.17.1", |     "version": "1.18.0-beta.0", | ||||||
|     "license": "MIT", |     "license": "MIT", | ||||||
|     "repository": { |     "repository": { | ||||||
|         "type": "git", |         "type": "git", | ||||||
| @@ -91,6 +91,7 @@ | |||||||
|         "mqtt": "^4.2.8", |         "mqtt": "^4.2.8", | ||||||
|         "mssql": "^8.1.0", |         "mssql": "^8.1.0", | ||||||
|         "node-cloudflared-tunnel": "~1.0.9", |         "node-cloudflared-tunnel": "~1.0.9", | ||||||
|  |         "node-radius-client": "^1.0.0", | ||||||
|         "nodemailer": "~6.6.5", |         "nodemailer": "~6.6.5", | ||||||
|         "notp": "~2.0.3", |         "notp": "~2.0.3", | ||||||
|         "password-hash": "~1.2.2", |         "password-hash": "~1.2.2", | ||||||
|   | |||||||
| @@ -125,10 +125,35 @@ async function sendInfo(socket) { | |||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Send list of docker hosts to client | ||||||
|  |  * @param {Socket} socket Socket.io socket instance | ||||||
|  |  * @returns {Promise<Bean[]>} | ||||||
|  |  */ | ||||||
|  | async function sendDockerHostList(socket) { | ||||||
|  |     const timeLogger = new TimeLogger(); | ||||||
|  |  | ||||||
|  |     let result = []; | ||||||
|  |     let list = await R.find("docker_host", " user_id = ? ", [ | ||||||
|  |         socket.userID, | ||||||
|  |     ]); | ||||||
|  |  | ||||||
|  |     for (let bean of list) { | ||||||
|  |         result.push(bean.toJSON()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     io.to(socket.userID).emit("dockerHostList", result); | ||||||
|  |  | ||||||
|  |     timeLogger.print("Send Docker Host List"); | ||||||
|  |  | ||||||
|  |     return list; | ||||||
|  | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     sendNotificationList, |     sendNotificationList, | ||||||
|     sendImportantHeartbeatList, |     sendImportantHeartbeatList, | ||||||
|     sendHeartbeatList, |     sendHeartbeatList, | ||||||
|     sendProxyList, |     sendProxyList, | ||||||
|     sendInfo, |     sendInfo, | ||||||
|  |     sendDockerHostList | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -53,6 +53,7 @@ class Database { | |||||||
|         "patch-2fa-invalidate-used-token.sql": true, |         "patch-2fa-invalidate-used-token.sql": true, | ||||||
|         "patch-notification_sent_history.sql": true, |         "patch-notification_sent_history.sql": true, | ||||||
|         "patch-monitor-basic-auth.sql": true, |         "patch-monitor-basic-auth.sql": true, | ||||||
|  |         "patch-add-docker-columns.sql": true, | ||||||
|         "patch-status-page.sql": true, |         "patch-status-page.sql": true, | ||||||
|         "patch-proxy.sql": true, |         "patch-proxy.sql": true, | ||||||
|         "patch-monitor-expiry-notification.sql": true, |         "patch-monitor-expiry-notification.sql": true, | ||||||
| @@ -61,6 +62,8 @@ class Database { | |||||||
|         "patch-add-clickable-status-page-link.sql": true, |         "patch-add-clickable-status-page-link.sql": true, | ||||||
|         "patch-add-sqlserver-monitor.sql": true, |         "patch-add-sqlserver-monitor.sql": true, | ||||||
|         "patch-add-other-auth.sql": { parents: [ "patch-monitor-basic-auth.sql" ] }, |         "patch-add-other-auth.sql": { parents: [ "patch-monitor-basic-auth.sql" ] }, | ||||||
|  |         "patch-add-radius-monitor.sql": true, | ||||||
|  |         "patch-monitor-add-resend-interval.sql": true, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -147,6 +150,9 @@ class Database { | |||||||
|         await R.exec("PRAGMA cache_size = -12000"); |         await R.exec("PRAGMA cache_size = -12000"); | ||||||
|         await R.exec("PRAGMA auto_vacuum = FULL"); |         await R.exec("PRAGMA auto_vacuum = FULL"); | ||||||
|  |  | ||||||
|  |         // Avoid error "SQLITE_BUSY: database is locked" by allowing SQLITE to wait up to 5 seconds to do a write | ||||||
|  |         await R.exec("PRAGMA busy_timeout = 5000"); | ||||||
|  |  | ||||||
|         // This ensures that an operating system crash or power failure will not corrupt the database. |         // This ensures that an operating system crash or power failure will not corrupt the database. | ||||||
|         // FULL synchronous is very safe, but it is also slower. |         // FULL synchronous is very safe, but it is also slower. | ||||||
|         // Read more: https://sqlite.org/pragma.html#pragma_synchronous |         // Read more: https://sqlite.org/pragma.html#pragma_synchronous | ||||||
|   | |||||||
							
								
								
									
										106
									
								
								server/docker.js
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										106
									
								
								server/docker.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,106 @@ | |||||||
|  | const axios = require("axios"); | ||||||
|  | const { R } = require("redbean-node"); | ||||||
|  | const version = require("../package.json").version; | ||||||
|  | const https = require("https"); | ||||||
|  |  | ||||||
|  | class DockerHost { | ||||||
|  |     /** | ||||||
|  |      * Save a docker host | ||||||
|  |      * @param {Object} dockerHost Docker host to save | ||||||
|  |      * @param {?number} dockerHostID ID of the docker host to update | ||||||
|  |      * @param {number} userID ID of the user who adds the docker host | ||||||
|  |      * @returns {Promise<Bean>} | ||||||
|  |      */ | ||||||
|  |     static async save(dockerHost, dockerHostID, userID) { | ||||||
|  |         let bean; | ||||||
|  |  | ||||||
|  |         if (dockerHostID) { | ||||||
|  |             bean = await R.findOne("docker_host", " id = ? AND user_id = ? ", [ dockerHostID, userID ]); | ||||||
|  |  | ||||||
|  |             if (!bean) { | ||||||
|  |                 throw new Error("docker host not found"); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |         } else { | ||||||
|  |             bean = R.dispense("docker_host"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         bean.user_id = userID; | ||||||
|  |         bean.docker_daemon = dockerHost.dockerDaemon; | ||||||
|  |         bean.docker_type = dockerHost.dockerType; | ||||||
|  |         bean.name = dockerHost.name; | ||||||
|  |  | ||||||
|  |         await R.store(bean); | ||||||
|  |  | ||||||
|  |         return bean; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Delete a Docker host | ||||||
|  |      * @param {number} dockerHostID ID of the Docker host to delete | ||||||
|  |      * @param {number} userID ID of the user who created the Docker host | ||||||
|  |      * @returns {Promise<void>} | ||||||
|  |      */ | ||||||
|  |     static async delete(dockerHostID, userID) { | ||||||
|  |         let bean = await R.findOne("docker_host", " id = ? AND user_id = ? ", [ dockerHostID, userID ]); | ||||||
|  |  | ||||||
|  |         if (!bean) { | ||||||
|  |             throw new Error("docker host not found"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Delete removed proxy from monitors if exists | ||||||
|  |         await R.exec("UPDATE monitor SET docker_host = null WHERE docker_host = ?", [ dockerHostID ]); | ||||||
|  |  | ||||||
|  |         await R.trash(bean); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Fetches the amount of containers on the Docker host | ||||||
|  |      * @param {Object} dockerHost Docker host to check for | ||||||
|  |      * @returns {number} Total amount of containers on the host | ||||||
|  |      */ | ||||||
|  |     static async testDockerHost(dockerHost) { | ||||||
|  |         const options = { | ||||||
|  |             url: "/containers/json?all=true", | ||||||
|  |             headers: { | ||||||
|  |                 "Accept": "*/*", | ||||||
|  |                 "User-Agent": "Uptime-Kuma/" + version | ||||||
|  |             }, | ||||||
|  |             httpsAgent: new https.Agent({ | ||||||
|  |                 maxCachedSessions: 0,      // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940) | ||||||
|  |                 rejectUnauthorized: false, | ||||||
|  |             }), | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         if (dockerHost.dockerType === "socket") { | ||||||
|  |             options.socketPath = dockerHost.dockerDaemon; | ||||||
|  |         } else if (dockerHost.dockerType === "tcp") { | ||||||
|  |             options.baseURL = dockerHost.dockerDaemon; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         let res = await axios.request(options); | ||||||
|  |  | ||||||
|  |         if (Array.isArray(res.data)) { | ||||||
|  |  | ||||||
|  |             if (res.data.length > 1) { | ||||||
|  |  | ||||||
|  |                 if ("ImageID" in res.data[0]) { | ||||||
|  |                     return res.data.length; | ||||||
|  |                 } else { | ||||||
|  |                     throw new Error("Invalid Docker response, is it Docker really a daemon?"); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |             } else { | ||||||
|  |                 return res.data.length; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |         } else { | ||||||
|  |             throw new Error("Invalid Docker response, is it Docker really a daemon?"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = { | ||||||
|  |     DockerHost, | ||||||
|  | }; | ||||||
							
								
								
									
										19
									
								
								server/model/docker_host.js
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										19
									
								
								server/model/docker_host.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | const { BeanModel } = require("redbean-node/dist/bean-model"); | ||||||
|  |  | ||||||
|  | class DockerHost extends BeanModel { | ||||||
|  |     /** | ||||||
|  |      * Returns an object that ready to parse to JSON | ||||||
|  |      * @returns {Object} | ||||||
|  |      */ | ||||||
|  |     toJSON() { | ||||||
|  |         return { | ||||||
|  |             id: this.id, | ||||||
|  |             userID: this.user_id, | ||||||
|  |             dockerDaemon: this.docker_daemon, | ||||||
|  |             dockerType: this.docker_type, | ||||||
|  |             name: this.name, | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = DockerHost; | ||||||
| @@ -7,7 +7,7 @@ dayjs.extend(timezone); | |||||||
| const axios = require("axios"); | const axios = require("axios"); | ||||||
| const { Prometheus } = require("../prometheus"); | const { Prometheus } = require("../prometheus"); | ||||||
| const { log, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util"); | const { log, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util"); | ||||||
| const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mqttAsync, setSetting, httpNtlm } = require("../util-server"); | const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mqttAsync, setSetting, httpNtlm, radius } = require("../util-server"); | ||||||
| const { R } = require("redbean-node"); | const { R } = require("redbean-node"); | ||||||
| const { BeanModel } = require("redbean-node/dist/bean-model"); | const { BeanModel } = require("redbean-node/dist/bean-model"); | ||||||
| const { Notification } = require("../notification"); | const { Notification } = require("../notification"); | ||||||
| @@ -79,6 +79,7 @@ class Monitor extends BeanModel { | |||||||
|             type: this.type, |             type: this.type, | ||||||
|             interval: this.interval, |             interval: this.interval, | ||||||
|             retryInterval: this.retryInterval, |             retryInterval: this.retryInterval, | ||||||
|  |             resendInterval: this.resendInterval, | ||||||
|             keyword: this.keyword, |             keyword: this.keyword, | ||||||
|             expiryNotification: this.isEnabledExpiryNotification(), |             expiryNotification: this.isEnabledExpiryNotification(), | ||||||
|             ignoreTls: this.getIgnoreTls(), |             ignoreTls: this.getIgnoreTls(), | ||||||
| @@ -88,6 +89,9 @@ class Monitor extends BeanModel { | |||||||
|             dns_resolve_type: this.dns_resolve_type, |             dns_resolve_type: this.dns_resolve_type, | ||||||
|             dns_resolve_server: this.dns_resolve_server, |             dns_resolve_server: this.dns_resolve_server, | ||||||
|             dns_last_result: this.dns_last_result, |             dns_last_result: this.dns_last_result, | ||||||
|  |             pushToken: this.pushToken, | ||||||
|  |             docker_container: this.docker_container, | ||||||
|  |             docker_host: this.docker_host, | ||||||
|             proxyId: this.proxy_id, |             proxyId: this.proxy_id, | ||||||
|             notificationIDList, |             notificationIDList, | ||||||
|             tags: tags, |             tags: tags, | ||||||
| @@ -100,6 +104,11 @@ class Monitor extends BeanModel { | |||||||
|             authMethod: this.authMethod, |             authMethod: this.authMethod, | ||||||
|             authWorkstation: this.authWorkstation, |             authWorkstation: this.authWorkstation, | ||||||
|             authDomain: this.authDomain, |             authDomain: this.authDomain, | ||||||
|  |             radiusUsername: this.radiusUsername, | ||||||
|  |             radiusPassword: this.radiusPassword, | ||||||
|  |             radiusCalledStationId: this.radiusCalledStationId, | ||||||
|  |             radiusCallingStationId: this.radiusCallingStationId, | ||||||
|  |             radiusSecret: this.radiusSecret, | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         if (includeSensitiveData) { |         if (includeSensitiveData) { | ||||||
| @@ -206,6 +215,7 @@ class Monitor extends BeanModel { | |||||||
|             bean.monitor_id = this.id; |             bean.monitor_id = this.id; | ||||||
|             bean.time = R.isoDateTimeMillis(dayjs.utc()); |             bean.time = R.isoDateTimeMillis(dayjs.utc()); | ||||||
|             bean.status = DOWN; |             bean.status = DOWN; | ||||||
|  |             bean.downCount = previousBeat?.downCount || 0; | ||||||
|  |  | ||||||
|             if (this.isUpsideDown()) { |             if (this.isUpsideDown()) { | ||||||
|                 bean.status = flipStatus(bean.status); |                 bean.status = flipStatus(bean.status); | ||||||
| @@ -468,6 +478,35 @@ class Monitor extends BeanModel { | |||||||
|                     } else { |                     } else { | ||||||
|                         throw new Error("Server not found on Steam"); |                         throw new Error("Server not found on Steam"); | ||||||
|                     } |                     } | ||||||
|  |                 } else if (this.type === "docker") { | ||||||
|  |                     log.debug(`[${this.name}] Prepare Options for Axios`); | ||||||
|  |  | ||||||
|  |                     const dockerHost = await R.load("docker_host", this.docker_host); | ||||||
|  |  | ||||||
|  |                     const options = { | ||||||
|  |                         url: `/containers/${this.docker_container}/json`, | ||||||
|  |                         headers: { | ||||||
|  |                             "Accept": "*/*", | ||||||
|  |                             "User-Agent": "Uptime-Kuma/" + version, | ||||||
|  |                         }, | ||||||
|  |                         httpsAgent: new https.Agent({ | ||||||
|  |                             maxCachedSessions: 0,      // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940) | ||||||
|  |                             rejectUnauthorized: ! this.getIgnoreTls(), | ||||||
|  |                         }), | ||||||
|  |                     }; | ||||||
|  |  | ||||||
|  |                     if (dockerHost._dockerType === "socket") { | ||||||
|  |                         options.socketPath = dockerHost._dockerDaemon; | ||||||
|  |                     } else if (dockerHost._dockerType === "tcp") { | ||||||
|  |                         options.baseURL = dockerHost._dockerDaemon; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     log.debug(`[${this.name}] Axios Request`); | ||||||
|  |                     let res = await axios.request(options); | ||||||
|  |                     if (res.data.State.Running) { | ||||||
|  |                         bean.status = UP; | ||||||
|  |                         bean.msg = ""; | ||||||
|  |                     } | ||||||
|                 } else if (this.type === "mqtt") { |                 } else if (this.type === "mqtt") { | ||||||
|                     bean.msg = await mqttAsync(this.hostname, this.mqttTopic, this.mqttSuccessMessage, { |                     bean.msg = await mqttAsync(this.hostname, this.mqttTopic, this.mqttSuccessMessage, { | ||||||
|                         port: this.port, |                         port: this.port, | ||||||
| @@ -492,6 +531,30 @@ class Monitor extends BeanModel { | |||||||
|                     bean.msg = ""; |                     bean.msg = ""; | ||||||
|                     bean.status = UP; |                     bean.status = UP; | ||||||
|                     bean.ping = dayjs().valueOf() - startTime; |                     bean.ping = dayjs().valueOf() - startTime; | ||||||
|  |                 } else if (this.type === "radius") { | ||||||
|  |                     let startTime = dayjs().valueOf(); | ||||||
|  |                     try { | ||||||
|  |                         const resp = await radius( | ||||||
|  |                             this.hostname, | ||||||
|  |                             this.radiusUsername, | ||||||
|  |                             this.radiusPassword, | ||||||
|  |                             this.radiusCalledStationId, | ||||||
|  |                             this.radiusCallingStationId, | ||||||
|  |                             this.radiusSecret | ||||||
|  |                         ); | ||||||
|  |                         if (resp.code) { | ||||||
|  |                             bean.msg = resp.code; | ||||||
|  |                         } | ||||||
|  |                         bean.status = UP; | ||||||
|  |                     } catch (error) { | ||||||
|  |                         bean.status = DOWN; | ||||||
|  |                         if (error.response?.code) { | ||||||
|  |                             bean.msg = error.response.code; | ||||||
|  |                         } else { | ||||||
|  |                             bean.msg = error.message; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     bean.ping = dayjs().valueOf() - startTime; | ||||||
|                 } else { |                 } else { | ||||||
|                     bean.msg = "Unknown Monitor Type"; |                     bean.msg = "Unknown Monitor Type"; | ||||||
|                     bean.status = PENDING; |                     bean.status = PENDING; | ||||||
| @@ -533,12 +596,27 @@ class Monitor extends BeanModel { | |||||||
|                 log.debug("monitor", `[${this.name}] sendNotification`); |                 log.debug("monitor", `[${this.name}] sendNotification`); | ||||||
|                 await Monitor.sendNotification(isFirstBeat, this, bean); |                 await Monitor.sendNotification(isFirstBeat, this, bean); | ||||||
|  |  | ||||||
|  |                 // Reset down count | ||||||
|  |                 bean.downCount = 0; | ||||||
|  |  | ||||||
|                 // Clear Status Page Cache |                 // Clear Status Page Cache | ||||||
|                 log.debug("monitor", `[${this.name}] apicache clear`); |                 log.debug("monitor", `[${this.name}] apicache clear`); | ||||||
|                 apicache.clear(); |                 apicache.clear(); | ||||||
|  |  | ||||||
|             } else { |             } else { | ||||||
|                 bean.important = false; |                 bean.important = false; | ||||||
|  |  | ||||||
|  |                 if (bean.status === DOWN && this.resendInterval > 0) { | ||||||
|  |                     ++bean.downCount; | ||||||
|  |                     if (bean.downCount >= this.resendInterval) { | ||||||
|  |                         // Send notification again, because we are still DOWN | ||||||
|  |                         log.debug("monitor", `[${this.name}] sendNotification again: Down Count: ${bean.downCount} | Resend Interval: ${this.resendInterval}`); | ||||||
|  |                         await Monitor.sendNotification(isFirstBeat, this, bean); | ||||||
|  |  | ||||||
|  |                         // Reset down count | ||||||
|  |                         bean.downCount = 0; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (bean.status === UP) { |             if (bean.status === UP) { | ||||||
| @@ -549,7 +627,7 @@ class Monitor extends BeanModel { | |||||||
|                 } |                 } | ||||||
|                 log.warn("monitor", `Monitor #${this.id} '${this.name}': Pending: ${bean.msg} | Max retries: ${this.maxretries} | Retry: ${retries} | Retry Interval: ${beatInterval} seconds | Type: ${this.type}`); |                 log.warn("monitor", `Monitor #${this.id} '${this.name}': Pending: ${bean.msg} | Max retries: ${this.maxretries} | Retry: ${retries} | Retry Interval: ${beatInterval} seconds | Type: ${this.type}`); | ||||||
|             } else { |             } else { | ||||||
|                 log.warn("monitor", `Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Interval: ${beatInterval} seconds | Type: ${this.type}`); |                 log.warn("monitor", `Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Interval: ${beatInterval} seconds | Type: ${this.type} | Down Count: ${bean.downCount} | Resend Interval: ${this.resendInterval}`); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             log.debug("monitor", `[${this.name}] Send to socket`); |             log.debug("monitor", `[${this.name}] Send to socket`); | ||||||
|   | |||||||
| @@ -12,9 +12,7 @@ const { default: axios } = require("axios"); | |||||||
|  |  | ||||||
| // bark is an APN bridge that sends notifications to Apple devices. | // bark is an APN bridge that sends notifications to Apple devices. | ||||||
|  |  | ||||||
| const barkNotificationGroup = "UptimeKuma"; |  | ||||||
| const barkNotificationAvatar = "https://github.com/louislam/uptime-kuma/raw/master/public/icon.png"; | const barkNotificationAvatar = "https://github.com/louislam/uptime-kuma/raw/master/public/icon.png"; | ||||||
| const barkNotificationSound = "telegraph"; |  | ||||||
| const successMessage = "Successes!"; | const successMessage = "Successes!"; | ||||||
|  |  | ||||||
| class Bark extends NotificationProvider { | class Bark extends NotificationProvider { | ||||||
| @@ -50,13 +48,23 @@ class Bark extends NotificationProvider { | |||||||
|      * @param {string} postUrl URL to append parameters to |      * @param {string} postUrl URL to append parameters to | ||||||
|      * @returns {string} |      * @returns {string} | ||||||
|      */ |      */ | ||||||
|     appendAdditionalParameters(postUrl) { |     appendAdditionalParameters(notification, postUrl) { | ||||||
|         // grouping all our notifications |  | ||||||
|         postUrl += "?group=" + barkNotificationGroup; |  | ||||||
|         // set icon to uptime kuma icon, 11kb should be fine |         // set icon to uptime kuma icon, 11kb should be fine | ||||||
|         postUrl += "&icon=" + barkNotificationAvatar; |         postUrl += "&icon=" + barkNotificationAvatar; | ||||||
|  |         // grouping all our notifications | ||||||
|  |         if (notification.barkGroup != null) { | ||||||
|  |             postUrl += "&group=" + notification.barkGroup; | ||||||
|  |         } else { | ||||||
|  |             // default name | ||||||
|  |             postUrl += "&group=" + "UptimeKuma"; | ||||||
|  |         } | ||||||
|         // picked a sound, this should follow system's mute status when arrival |         // picked a sound, this should follow system's mute status when arrival | ||||||
|         postUrl += "&sound=" + barkNotificationSound; |         if (notification.barkSound != null) { | ||||||
|  |             postUrl += "&sound=" + notification.barkSound; | ||||||
|  |         } else { | ||||||
|  |             // default sound | ||||||
|  |             postUrl += "&sound=" + "telegraph"; | ||||||
|  |         } | ||||||
|         return postUrl; |         return postUrl; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										38
									
								
								server/notification-providers/home-assistant.js
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										38
									
								
								server/notification-providers/home-assistant.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | const NotificationProvider = require("./notification-provider"); | ||||||
|  | const axios = require("axios"); | ||||||
|  |  | ||||||
|  | const defaultNotificationService = "notify"; | ||||||
|  |  | ||||||
|  | class HomeAssistant extends NotificationProvider { | ||||||
|  |     name = "HomeAssistant"; | ||||||
|  |  | ||||||
|  |     async send(notification, message, monitor = null, heartbeat = null) { | ||||||
|  |         const notificationService = notification?.notificationService || defaultNotificationService; | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             await axios.post( | ||||||
|  |                 `${notification.homeAssistantUrl}/api/services/notify/${notificationService}`, | ||||||
|  |                 { | ||||||
|  |                     title: "Uptime Kuma", | ||||||
|  |                     message, | ||||||
|  |                     ...(notificationService !== "persistent_notification" && { data: { | ||||||
|  |                         name: monitor?.name, | ||||||
|  |                         status: heartbeat?.status, | ||||||
|  |                     } }), | ||||||
|  |                 }, | ||||||
|  |                 { | ||||||
|  |                     headers: { | ||||||
|  |                         Authorization: `Bearer ${notification.longLivedAccessToken}`, | ||||||
|  |                         "Content-Type": "application/json", | ||||||
|  |                     }, | ||||||
|  |                 } | ||||||
|  |             ); | ||||||
|  |  | ||||||
|  |             return "Sent Successfully."; | ||||||
|  |         } catch (error) { | ||||||
|  |             this.throwGeneralAxiosError(error); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = HomeAssistant; | ||||||
| @@ -12,6 +12,7 @@ const Feishu = require("./notification-providers/feishu"); | |||||||
| const GoogleChat = require("./notification-providers/google-chat"); | const GoogleChat = require("./notification-providers/google-chat"); | ||||||
| const Gorush = require("./notification-providers/gorush"); | const Gorush = require("./notification-providers/gorush"); | ||||||
| const Gotify = require("./notification-providers/gotify"); | const Gotify = require("./notification-providers/gotify"); | ||||||
|  | const HomeAssistant = require("./notification-providers/home-assistant"); | ||||||
| const Line = require("./notification-providers/line"); | const Line = require("./notification-providers/line"); | ||||||
| const LineNotify = require("./notification-providers/linenotify"); | const LineNotify = require("./notification-providers/linenotify"); | ||||||
| const LunaSea = require("./notification-providers/lunasea"); | const LunaSea = require("./notification-providers/lunasea"); | ||||||
| @@ -61,6 +62,7 @@ class Notification { | |||||||
|             new GoogleChat(), |             new GoogleChat(), | ||||||
|             new Gorush(), |             new Gorush(), | ||||||
|             new Gotify(), |             new Gotify(), | ||||||
|  |             new HomeAssistant(), | ||||||
|             new Line(), |             new Line(), | ||||||
|             new LineNotify(), |             new LineNotify(), | ||||||
|             new LunaSea(), |             new LunaSea(), | ||||||
|   | |||||||
| @@ -118,13 +118,14 @@ if (config.demoMode) { | |||||||
| } | } | ||||||
|  |  | ||||||
| // Must be after io instantiation | // Must be after io instantiation | ||||||
| const { sendNotificationList, sendHeartbeatList, sendImportantHeartbeatList, sendInfo, sendProxyList } = require("./client"); | const { sendNotificationList, sendHeartbeatList, sendImportantHeartbeatList, sendInfo, sendProxyList, sendDockerHostList } = require("./client"); | ||||||
| const { statusPageSocketHandler } = require("./socket-handlers/status-page-socket-handler"); | const { statusPageSocketHandler } = require("./socket-handlers/status-page-socket-handler"); | ||||||
| const databaseSocketHandler = require("./socket-handlers/database-socket-handler"); | const databaseSocketHandler = require("./socket-handlers/database-socket-handler"); | ||||||
| const TwoFA = require("./2fa"); | const TwoFA = require("./2fa"); | ||||||
| const StatusPage = require("./model/status_page"); | const StatusPage = require("./model/status_page"); | ||||||
| const { cloudflaredSocketHandler, autoStart: cloudflaredAutoStart, stop: cloudflaredStop } = require("./socket-handlers/cloudflared-socket-handler"); | const { cloudflaredSocketHandler, autoStart: cloudflaredAutoStart, stop: cloudflaredStop } = require("./socket-handlers/cloudflared-socket-handler"); | ||||||
| const { proxySocketHandler } = require("./socket-handlers/proxy-socket-handler"); | const { proxySocketHandler } = require("./socket-handlers/proxy-socket-handler"); | ||||||
|  | const { dockerSocketHandler } = require("./socket-handlers/docker-socket-handler"); | ||||||
|  |  | ||||||
| app.use(express.json()); | app.use(express.json()); | ||||||
|  |  | ||||||
| @@ -668,6 +669,7 @@ let needSetup = false; | |||||||
|                 bean.basic_auth_pass = monitor.basic_auth_pass; |                 bean.basic_auth_pass = monitor.basic_auth_pass; | ||||||
|                 bean.interval = monitor.interval; |                 bean.interval = monitor.interval; | ||||||
|                 bean.retryInterval = monitor.retryInterval; |                 bean.retryInterval = monitor.retryInterval; | ||||||
|  |                 bean.resendInterval = monitor.resendInterval; | ||||||
|                 bean.hostname = monitor.hostname; |                 bean.hostname = monitor.hostname; | ||||||
|                 bean.maxretries = monitor.maxretries; |                 bean.maxretries = monitor.maxretries; | ||||||
|                 bean.port = parseInt(monitor.port); |                 bean.port = parseInt(monitor.port); | ||||||
| @@ -680,6 +682,8 @@ let needSetup = false; | |||||||
|                 bean.dns_resolve_type = monitor.dns_resolve_type; |                 bean.dns_resolve_type = monitor.dns_resolve_type; | ||||||
|                 bean.dns_resolve_server = monitor.dns_resolve_server; |                 bean.dns_resolve_server = monitor.dns_resolve_server; | ||||||
|                 bean.pushToken = monitor.pushToken; |                 bean.pushToken = monitor.pushToken; | ||||||
|  |                 bean.docker_container = monitor.docker_container; | ||||||
|  |                 bean.docker_host = monitor.docker_host; | ||||||
|                 bean.proxyId = Number.isInteger(monitor.proxyId) ? monitor.proxyId : null; |                 bean.proxyId = Number.isInteger(monitor.proxyId) ? monitor.proxyId : null; | ||||||
|                 bean.mqttUsername = monitor.mqttUsername; |                 bean.mqttUsername = monitor.mqttUsername; | ||||||
|                 bean.mqttPassword = monitor.mqttPassword; |                 bean.mqttPassword = monitor.mqttPassword; | ||||||
| @@ -690,6 +694,11 @@ let needSetup = false; | |||||||
|                 bean.authMethod = monitor.authMethod; |                 bean.authMethod = monitor.authMethod; | ||||||
|                 bean.authWorkstation = monitor.authWorkstation; |                 bean.authWorkstation = monitor.authWorkstation; | ||||||
|                 bean.authDomain = monitor.authDomain; |                 bean.authDomain = monitor.authDomain; | ||||||
|  |                 bean.radiusUsername = monitor.radiusUsername; | ||||||
|  |                 bean.radiusPassword = monitor.radiusPassword; | ||||||
|  |                 bean.radiusCalledStationId = monitor.radiusCalledStationId; | ||||||
|  |                 bean.radiusCallingStationId = monitor.radiusCallingStationId; | ||||||
|  |                 bean.radiusSecret = monitor.radiusSecret; | ||||||
|  |  | ||||||
|                 await R.store(bean); |                 await R.store(bean); | ||||||
|  |  | ||||||
| @@ -1270,6 +1279,7 @@ let needSetup = false; | |||||||
|                                 authDomain: monitorListData[i].authDomain, |                                 authDomain: monitorListData[i].authDomain, | ||||||
|                                 interval: monitorListData[i].interval, |                                 interval: monitorListData[i].interval, | ||||||
|                                 retryInterval: retryInterval, |                                 retryInterval: retryInterval, | ||||||
|  |                                 resendInterval: monitorListData[i].resendInterval || 0, | ||||||
|                                 hostname: monitorListData[i].hostname, |                                 hostname: monitorListData[i].hostname, | ||||||
|                                 maxretries: monitorListData[i].maxretries, |                                 maxretries: monitorListData[i].maxretries, | ||||||
|                                 port: monitorListData[i].port, |                                 port: monitorListData[i].port, | ||||||
| @@ -1438,6 +1448,7 @@ let needSetup = false; | |||||||
|         cloudflaredSocketHandler(socket); |         cloudflaredSocketHandler(socket); | ||||||
|         databaseSocketHandler(socket); |         databaseSocketHandler(socket); | ||||||
|         proxySocketHandler(socket); |         proxySocketHandler(socket); | ||||||
|  |         dockerSocketHandler(socket); | ||||||
|  |  | ||||||
|         log.debug("server", "added all socket handlers"); |         log.debug("server", "added all socket handlers"); | ||||||
|  |  | ||||||
| @@ -1538,6 +1549,7 @@ async function afterLogin(socket, user) { | |||||||
|     let monitorList = await server.sendMonitorList(socket); |     let monitorList = await server.sendMonitorList(socket); | ||||||
|     sendNotificationList(socket); |     sendNotificationList(socket); | ||||||
|     sendProxyList(socket); |     sendProxyList(socket); | ||||||
|  |     sendDockerHostList(socket); | ||||||
|  |  | ||||||
|     await sleep(500); |     await sleep(500); | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										79
									
								
								server/socket-handlers/docker-socket-handler.js
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										79
									
								
								server/socket-handlers/docker-socket-handler.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | |||||||
|  | const { sendDockerHostList } = require("../client"); | ||||||
|  | const { checkLogin } = require("../util-server"); | ||||||
|  | const { DockerHost } = require("../docker"); | ||||||
|  | const { log } = require("../../src/util"); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Handlers for docker hosts | ||||||
|  |  * @param {Socket} socket Socket.io instance | ||||||
|  |  */ | ||||||
|  | module.exports.dockerSocketHandler = (socket) => { | ||||||
|  |     socket.on("addDockerHost", async (dockerHost, dockerHostID, callback) => { | ||||||
|  |         try { | ||||||
|  |             checkLogin(socket); | ||||||
|  |  | ||||||
|  |             let dockerHostBean = await DockerHost.save(dockerHost, dockerHostID, socket.userID); | ||||||
|  |             await sendDockerHostList(socket); | ||||||
|  |  | ||||||
|  |             callback({ | ||||||
|  |                 ok: true, | ||||||
|  |                 msg: "Saved", | ||||||
|  |                 id: dockerHostBean.id, | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |         } catch (e) { | ||||||
|  |             callback({ | ||||||
|  |                 ok: false, | ||||||
|  |                 msg: e.message, | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     socket.on("deleteDockerHost", async (dockerHostID, callback) => { | ||||||
|  |         try { | ||||||
|  |             checkLogin(socket); | ||||||
|  |  | ||||||
|  |             await DockerHost.delete(dockerHostID, socket.userID); | ||||||
|  |             await sendDockerHostList(socket); | ||||||
|  |  | ||||||
|  |             callback({ | ||||||
|  |                 ok: true, | ||||||
|  |                 msg: "Deleted", | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |         } catch (e) { | ||||||
|  |             callback({ | ||||||
|  |                 ok: false, | ||||||
|  |                 msg: e.message, | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     socket.on("testDockerHost", async (dockerHost, callback) => { | ||||||
|  |         try { | ||||||
|  |             checkLogin(socket); | ||||||
|  |  | ||||||
|  |             let amount = await DockerHost.testDockerHost(dockerHost); | ||||||
|  |             let msg; | ||||||
|  |  | ||||||
|  |             if (amount > 1) { | ||||||
|  |                 msg = "Connected Successfully. Amount of containers: " + amount; | ||||||
|  |             } else { | ||||||
|  |                 msg = "Connected Successfully, but there are no containers?"; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             callback({ | ||||||
|  |                 ok: true, | ||||||
|  |                 msg, | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |         } catch (e) { | ||||||
|  |             log.error("docker", e); | ||||||
|  |  | ||||||
|  |             callback({ | ||||||
|  |                 ok: false, | ||||||
|  |                 msg: e.message, | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  | }; | ||||||
| @@ -15,6 +15,12 @@ const { Client } = require("pg"); | |||||||
| const postgresConParse = require("pg-connection-string").parse; | const postgresConParse = require("pg-connection-string").parse; | ||||||
| const { NtlmClient } = require("axios-ntlm"); | const { NtlmClient } = require("axios-ntlm"); | ||||||
| const { Settings } = require("./settings"); | const { Settings } = require("./settings"); | ||||||
|  | const radiusClient = require("node-radius-client"); | ||||||
|  | const { | ||||||
|  |     dictionaries: { | ||||||
|  |         rfc2865: { file, attributes }, | ||||||
|  |     }, | ||||||
|  | } = require("node-radius-utils"); | ||||||
|  |  | ||||||
| // From ping-lite | // From ping-lite | ||||||
| exports.WIN = /^win/.test(process.platform); | exports.WIN = /^win/.test(process.platform); | ||||||
| @@ -285,6 +291,30 @@ exports.postgresQuery = function (connectionString, query) { | |||||||
|     }); |     }); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | exports.radius = function ( | ||||||
|  |     hostname, | ||||||
|  |     username, | ||||||
|  |     password, | ||||||
|  |     calledStationId, | ||||||
|  |     callingStationId, | ||||||
|  |     secret, | ||||||
|  | ) { | ||||||
|  |     const client = new radiusClient({ | ||||||
|  |         host: hostname, | ||||||
|  |         dictionaries: [ file ], | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     return client.accessRequest({ | ||||||
|  |         secret: secret, | ||||||
|  |         attributes: [ | ||||||
|  |             [ attributes.USER_NAME, username ], | ||||||
|  |             [ attributes.USER_PASSWORD, password ], | ||||||
|  |             [ attributes.CALLING_STATION_ID, callingStationId ], | ||||||
|  |             [ attributes.CALLED_STATION_ID, calledStationId ], | ||||||
|  |         ], | ||||||
|  |     }); | ||||||
|  | }; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Retrieve value of setting based on key |  * Retrieve value of setting based on key | ||||||
|  * @param {string} key Key of setting to retrieve |  * @param {string} key Key of setting to retrieve | ||||||
|   | |||||||
							
								
								
									
										177
									
								
								src/components/DockerHostDialog.vue
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										177
									
								
								src/components/DockerHostDialog.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,177 @@ | |||||||
|  | <template> | ||||||
|  |     <form @submit.prevent="submit"> | ||||||
|  |         <div ref="modal" class="modal fade" tabindex="-1" data-bs-backdrop="static"> | ||||||
|  |             <div class="modal-dialog"> | ||||||
|  |                 <div class="modal-content"> | ||||||
|  |                     <div class="modal-header"> | ||||||
|  |                         <h5 id="exampleModalLabel" class="modal-title"> | ||||||
|  |                             {{ $t("Setup Docker Host") }} | ||||||
|  |                         </h5> | ||||||
|  |                         <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" /> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="modal-body"> | ||||||
|  |                         <div class="mb-3"> | ||||||
|  |                             <label for="docker-name" class="form-label">{{ $t("Friendly Name") }}</label> | ||||||
|  |                             <input id="docker-name" v-model="dockerHost.name" type="text" class="form-control" required> | ||||||
|  |                         </div> | ||||||
|  |  | ||||||
|  |                         <div class="mb-3"> | ||||||
|  |                             <label for="docker-type" class="form-label">{{ $t("Connection Type") }}</label> | ||||||
|  |                             <select id="docker-type" v-model="dockerHost.dockerType" class="form-select"> | ||||||
|  |                                 <option v-for="type in connectionTypes" :key="type" :value="type">{{ $t(type) }}</option> | ||||||
|  |                             </select> | ||||||
|  |                         </div> | ||||||
|  |  | ||||||
|  |                         <div class="mb-3"> | ||||||
|  |                             <label for="docker-daemon" class="form-label">{{ $t("Docker Daemon") }}</label> | ||||||
|  |                             <input id="docker-daemon" v-model="dockerHost.dockerDaemon" type="text" class="form-control" required> | ||||||
|  |  | ||||||
|  |                             <div class="form-text"> | ||||||
|  |                                 {{ $t("Examples") }}: | ||||||
|  |                                 <ul> | ||||||
|  |                                     <li>/var/run/docker.sock</li> | ||||||
|  |                                     <li>tcp://localhost:2375</li> | ||||||
|  |                                 </ul> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                     </div> | ||||||
|  |  | ||||||
|  |                     <div class="modal-footer"> | ||||||
|  |                         <button v-if="id" type="button" class="btn btn-danger" :disabled="processing" @click="deleteConfirm"> | ||||||
|  |                             {{ $t("Delete") }} | ||||||
|  |                         </button> | ||||||
|  |                         <button type="button" class="btn btn-warning" :disabled="processing" @click="test"> | ||||||
|  |                             {{ $t("Test") }} | ||||||
|  |                         </button> | ||||||
|  |                         <button type="submit" class="btn btn-primary" :disabled="processing"> | ||||||
|  |                             <div v-if="processing" class="spinner-border spinner-border-sm me-1"></div> | ||||||
|  |                             {{ $t("Save") }} | ||||||
|  |                         </button> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     </form> | ||||||
|  |  | ||||||
|  |     <Confirm ref="confirmDelete" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="deleteDockerHost"> | ||||||
|  |         {{ $t("deleteDockerHostMsg") }} | ||||||
|  |     </Confirm> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts"> | ||||||
|  | import { Modal } from "bootstrap"; | ||||||
|  | import Confirm from "./Confirm.vue"; | ||||||
|  | import { useToast } from "vue-toastification"; | ||||||
|  | const toast = useToast(); | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |     components: { | ||||||
|  |         Confirm, | ||||||
|  |     }, | ||||||
|  |     props: {}, | ||||||
|  |     emits: [ "added" ], | ||||||
|  |     data() { | ||||||
|  |         return { | ||||||
|  |             model: null, | ||||||
|  |             processing: false, | ||||||
|  |             id: null, | ||||||
|  |             connectionTypes: [ "socket", "tcp" ], | ||||||
|  |             dockerHost: { | ||||||
|  |                 name: "", | ||||||
|  |                 dockerDaemon: "", | ||||||
|  |                 dockerType: "", | ||||||
|  |                 // Do not set default value here, please scroll to show() | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     mounted() { | ||||||
|  |         this.modal = new Modal(this.$refs.modal); | ||||||
|  |     }, | ||||||
|  |     methods: { | ||||||
|  |  | ||||||
|  |         deleteConfirm() { | ||||||
|  |             this.modal.hide(); | ||||||
|  |             this.$refs.confirmDelete.show(); | ||||||
|  |         }, | ||||||
|  |  | ||||||
|  |         show(dockerHostID) { | ||||||
|  |             if (dockerHostID) { | ||||||
|  |                 let found = false; | ||||||
|  |  | ||||||
|  |                 this.id = dockerHostID; | ||||||
|  |  | ||||||
|  |                 for (let n of this.$root.dockerHostList) { | ||||||
|  |                     if (n.id === dockerHostID) { | ||||||
|  |                         this.dockerHost = n; | ||||||
|  |                         found = true; | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (!found) { | ||||||
|  |                     toast.error("Docker Host not found!"); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |             } else { | ||||||
|  |                 this.id = null; | ||||||
|  |                 this.dockerHost = { | ||||||
|  |                     name: "", | ||||||
|  |                     dockerType: "socket", | ||||||
|  |                     dockerDaemon: "/var/run/docker.sock", | ||||||
|  |                 }; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             this.modal.show(); | ||||||
|  |         }, | ||||||
|  |  | ||||||
|  |         submit() { | ||||||
|  |             this.processing = true; | ||||||
|  |             this.$root.getSocket().emit("addDockerHost", this.dockerHost, this.id, (res) => { | ||||||
|  |                 this.$root.toastRes(res); | ||||||
|  |                 this.processing = false; | ||||||
|  |  | ||||||
|  |                 if (res.ok) { | ||||||
|  |                     this.modal.hide(); | ||||||
|  |  | ||||||
|  |                     // Emit added event, doesn't emit edit. | ||||||
|  |                     if (! this.id) { | ||||||
|  |                         this.$emit("added", res.id); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         }, | ||||||
|  |  | ||||||
|  |         test() { | ||||||
|  |             this.processing = true; | ||||||
|  |             this.$root.getSocket().emit("testDockerHost", this.dockerHost, (res) => { | ||||||
|  |                 this.$root.toastRes(res); | ||||||
|  |                 this.processing = false; | ||||||
|  |             }); | ||||||
|  |         }, | ||||||
|  |  | ||||||
|  |         deleteDockerHost() { | ||||||
|  |             this.processing = true; | ||||||
|  |             this.$root.getSocket().emit("deleteDockerHost", this.id, (res) => { | ||||||
|  |                 this.$root.toastRes(res); | ||||||
|  |                 this.processing = false; | ||||||
|  |  | ||||||
|  |                 if (res.ok) { | ||||||
|  |                     this.modal.hide(); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         }, | ||||||
|  |     }, | ||||||
|  | }; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | @import "../assets/vars.scss"; | ||||||
|  |  | ||||||
|  | .dark { | ||||||
|  |     .modal-dialog .form-text, .modal-dialog p { | ||||||
|  |         color: $dark-font-color; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | </style> | ||||||
| @@ -2,9 +2,6 @@ | |||||||
|     <div class="mb-3"> |     <div class="mb-3"> | ||||||
|         <label for="Bark Endpoint" class="form-label">{{ $t("Bark Endpoint") }}<span style="color: red;"><sup>*</sup></span></label> |         <label for="Bark Endpoint" class="form-label">{{ $t("Bark Endpoint") }}<span style="color: red;"><sup>*</sup></span></label> | ||||||
|         <input id="Bark Endpoint" v-model="$parent.notification.barkEndpoint" type="text" class="form-control" required> |         <input id="Bark Endpoint" v-model="$parent.notification.barkEndpoint" type="text" class="form-control" required> | ||||||
|         <div class="form-text"> |  | ||||||
|             <p><span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}</p> |  | ||||||
|         </div> |  | ||||||
|         <i18n-t tag="div" keypath="wayToGetTeamsURL" class="form-text"> |         <i18n-t tag="div" keypath="wayToGetTeamsURL" class="form-text"> | ||||||
|             <a |             <a | ||||||
|                 href="https://github.com/Finb/Bark" |                 href="https://github.com/Finb/Bark" | ||||||
| @@ -12,4 +9,45 @@ | |||||||
|             >{{ $t("here") }}</a> |             >{{ $t("here") }}</a> | ||||||
|         </i18n-t> |         </i18n-t> | ||||||
|     </div> |     </div> | ||||||
|  |     <div class="mb-3"> | ||||||
|  |         <label for="Bark Group" class="form-label">{{ $t("Bark Group") }}</label> | ||||||
|  |         <input id="Bark Group" v-model="$parent.notification.barkGroup" type="text" class="form-control" required> | ||||||
|  |     </div> | ||||||
|  |     <div class="mb-3"> | ||||||
|  |         <label for="Bark Sound" class="form-label">{{ $t("Bark Sound") }}</label> | ||||||
|  |         <select id="Bark Sound" v-model="$parent.notification.barkSound" class="form-select" required> | ||||||
|  |             <option value="alarm">alarm</option> | ||||||
|  |             <option value="anticipate">anticipate</option> | ||||||
|  |             <option value="bell">bell</option> | ||||||
|  |             <option value="birdsong">birdsong</option> | ||||||
|  |             <option value="bloom">bloom</option> | ||||||
|  |             <option value="calypso">calypso</option> | ||||||
|  |             <option value="chime">chime</option> | ||||||
|  |             <option value="choo">choo</option> | ||||||
|  |             <option value="descent">descent</option> | ||||||
|  |             <option value="electronic">electronic</option> | ||||||
|  |             <option value="fanfare">fanfare</option> | ||||||
|  |             <option value="glass">glass</option> | ||||||
|  |             <option value="gotosleep">gotosleep</option> | ||||||
|  |             <option value="healthnotification">healthnotification</option> | ||||||
|  |             <option value="horn">horn</option> | ||||||
|  |             <option value="ladder">ladder</option> | ||||||
|  |             <option value="mailsent">mailsent</option> | ||||||
|  |             <option value="minuet">minuet</option> | ||||||
|  |             <option value="multiwayinvitation">multiwayinvitation</option> | ||||||
|  |             <option value="newmail">newmail</option> | ||||||
|  |             <option value="newsflash">newsflash</option> | ||||||
|  |             <option value="noir">noir</option> | ||||||
|  |             <option value="paymentsuccess">paymentsuccess</option> | ||||||
|  |             <option value="shake">shake</option> | ||||||
|  |             <option value="sherwoodforest">sherwoodforest</option> | ||||||
|  |             <option value="silence">silence</option> | ||||||
|  |             <option value="spell">spell</option> | ||||||
|  |             <option value="suspense">suspense</option> | ||||||
|  |             <option value="telegraph">telegraph</option> | ||||||
|  |             <option value="tiptoes">tiptoes</option> | ||||||
|  |             <option value="typewriters">typewriters</option> | ||||||
|  |             <option value="update">update</option> | ||||||
|  |         </select> | ||||||
|  |     </div> | ||||||
| </template> | </template> | ||||||
|   | |||||||
							
								
								
									
										40
									
								
								src/components/notifications/HomeAssistant.vue
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										40
									
								
								src/components/notifications/HomeAssistant.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | <template> | ||||||
|  |     <div class="mb-3"> | ||||||
|  |         <label for="homeAssistantUrl" class="form-label">{{ $t("Home Assistant URL") }}<span style="color: red;"><sup>*</sup></span></label> | ||||||
|  |         <input id="homeAssistantUrl" v-model="$parent.notification.homeAssistantUrl" type="url" class="form-control" required> | ||||||
|  |     </div> | ||||||
|  |  | ||||||
|  |     <div class="mb-3"> | ||||||
|  |         <label for="longLivedAccessToken" class="form-label">{{ $t("Long-Lived Access Token") }}<span style="color: red;"><sup>*</sup></span></label> | ||||||
|  |         <input id="longLivedAccessToken" v-model="$parent.notification.longLivedAccessToken" type="text" class="form-control" required> | ||||||
|  |  | ||||||
|  |         <div class="form-text"> | ||||||
|  |             <p>{{ $t("Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ") }}</p> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  |  | ||||||
|  |     <div class="mb-3"> | ||||||
|  |         <label for="notificationService" class="form-label">{{ $t("Notification Service") }}</label> | ||||||
|  |         <input id="notificationService" v-model="$parent.notification.notificationService" type="text" :placeholder="$t('default: notify all devices')" class="form-control"> | ||||||
|  |  | ||||||
|  |         <div class="form-text"> | ||||||
|  |             <p>{{ $t("A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.") }}</p> | ||||||
|  |             <p>{{ $t("Automations can optionally be triggered in Home Assistant:") }}</p> | ||||||
|  |             <p> | ||||||
|  |                 {{ $t("Trigger type:") }} <code>Event</code><br /> | ||||||
|  |                 {{ $t("Event type:") }} <code>call_service</code><br /> | ||||||
|  |                 {{ $t("Event data:") }} | ||||||
|  |             </p> | ||||||
|  |             <pre>domain: notify | ||||||
|  | service: mobile_app_my_phone # change to your device name | ||||||
|  | service_data: | ||||||
|  |   title: Uptime Kuma | ||||||
|  |   data: | ||||||
|  |     status: 0 # 0=down 1=up | ||||||
|  |     # name: Optional Uptime Kuma Monitor Name to filter by</pre> | ||||||
|  |             <p> | ||||||
|  |                 {{ $t("Then choose an action, for example switch the scene to where an RGB light is red.") }} | ||||||
|  |             </p> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  | </template> | ||||||
| @@ -10,6 +10,7 @@ import Feishu from "./Feishu.vue"; | |||||||
| import GoogleChat from "./GoogleChat.vue"; | import GoogleChat from "./GoogleChat.vue"; | ||||||
| import Gorush from "./Gorush.vue"; | import Gorush from "./Gorush.vue"; | ||||||
| import Gotify from "./Gotify.vue"; | import Gotify from "./Gotify.vue"; | ||||||
|  | import HomeAssistant from "./HomeAssistant.vue"; | ||||||
| import Line from "./Line.vue"; | import Line from "./Line.vue"; | ||||||
| import LineNotify from "./LineNotify.vue"; | import LineNotify from "./LineNotify.vue"; | ||||||
| import LunaSea from "./LunaSea.vue"; | import LunaSea from "./LunaSea.vue"; | ||||||
| @@ -54,6 +55,7 @@ const NotificationFormList = { | |||||||
|     "GoogleChat": GoogleChat, |     "GoogleChat": GoogleChat, | ||||||
|     "gorush": Gorush, |     "gorush": Gorush, | ||||||
|     "gotify": Gotify, |     "gotify": Gotify, | ||||||
|  |     "HomeAssistant": HomeAssistant, | ||||||
|     "line": Line, |     "line": Line, | ||||||
|     "LineNotify": LineNotify, |     "LineNotify": LineNotify, | ||||||
|     "lunasea": LunaSea, |     "lunasea": LunaSea, | ||||||
|   | |||||||
							
								
								
									
										48
									
								
								src/components/settings/Docker.vue
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										48
									
								
								src/components/settings/Docker.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | <template> | ||||||
|  |     <div> | ||||||
|  |         <div class="dockerHost-list my-4"> | ||||||
|  |             <p v-if="$root.dockerHostList.length === 0"> | ||||||
|  |                 {{ $t("Not available, please setup.") }} | ||||||
|  |             </p> | ||||||
|  |  | ||||||
|  |             <ul class="list-group mb-3" style="border-radius: 1rem;"> | ||||||
|  |                 <li v-for="(dockerHost, index) in $root.dockerHostList" :key="index" class="list-group-item"> | ||||||
|  |                     {{ dockerHost.name }}<br> | ||||||
|  |                     <a href="#" @click="$refs.dockerHostDialog.show(dockerHost.id)">{{ $t("Edit") }}</a> | ||||||
|  |                 </li> | ||||||
|  |             </ul> | ||||||
|  |  | ||||||
|  |             <button class="btn btn-primary me-2" type="button" @click="$refs.dockerHostDialog.show()"> | ||||||
|  |                 {{ $t("Setup Docker Host") }} | ||||||
|  |             </button> | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|  |         <DockerHostDialog ref="dockerHostDialog" /> | ||||||
|  |     </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script> | ||||||
|  | import DockerHostDialog from "../../components/DockerHostDialog.vue"; | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |     components: { | ||||||
|  |         DockerHostDialog, | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     data() { | ||||||
|  |         return {}; | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     computed: { | ||||||
|  |         settings() { | ||||||
|  |             return this.$parent.$parent.$parent.settings; | ||||||
|  |         }, | ||||||
|  |         saveSettings() { | ||||||
|  |             return this.$parent.$parent.$parent.saveSettings; | ||||||
|  |         }, | ||||||
|  |         settingsLoaded() { | ||||||
|  |             return this.$parent.$parent.$parent.settingsLoaded; | ||||||
|  |         }, | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | </script> | ||||||
| @@ -165,7 +165,10 @@ export default { | |||||||
|     Pink: "Pink", |     Pink: "Pink", | ||||||
|     "Search...": "Suchen...", |     "Search...": "Suchen...", | ||||||
|     "Heartbeat Retry Interval": "Überprüfungsintervall", |     "Heartbeat Retry Interval": "Überprüfungsintervall", | ||||||
|  |     "Resend Notification if Down X times consequently": "Benachrichtigung erneut senden, wenn Inaktiv X mal hintereinander", | ||||||
|     retryCheckEverySecond: "Alle {0} Sekunden neu versuchen", |     retryCheckEverySecond: "Alle {0} Sekunden neu versuchen", | ||||||
|  |     resendEveryXTimes: "Erneut versenden alle {0} mal", | ||||||
|  |     resendDisabled: "Erneut versenden deaktiviert", | ||||||
|     "Import Backup": "Backup importieren", |     "Import Backup": "Backup importieren", | ||||||
|     "Export Backup": "Backup exportieren", |     "Export Backup": "Backup exportieren", | ||||||
|     "Avg. Ping": "Durchschn. Ping", |     "Avg. Ping": "Durchschn. Ping", | ||||||
|   | |||||||
| @@ -2,6 +2,8 @@ export default { | |||||||
|     languageName: "English", |     languageName: "English", | ||||||
|     checkEverySecond: "Check every {0} seconds", |     checkEverySecond: "Check every {0} seconds", | ||||||
|     retryCheckEverySecond: "Retry every {0} seconds", |     retryCheckEverySecond: "Retry every {0} seconds", | ||||||
|  |     resendEveryXTimes: "Resend every {0} times", | ||||||
|  |     resendDisabled: "Resend disabled", | ||||||
|     retriesDescription: "Maximum retries before the service is marked as down and a notification is sent", |     retriesDescription: "Maximum retries before the service is marked as down and a notification is sent", | ||||||
|     ignoreTLSError: "Ignore TLS/SSL error for HTTPS websites", |     ignoreTLSError: "Ignore TLS/SSL error for HTTPS websites", | ||||||
|     upsideDownModeDescription: "Flip the status upside down. If the service is reachable, it is DOWN.", |     upsideDownModeDescription: "Flip the status upside down. If the service is reachable, it is DOWN.", | ||||||
| @@ -72,6 +74,7 @@ export default { | |||||||
|     "Heartbeat Interval": "Heartbeat Interval", |     "Heartbeat Interval": "Heartbeat Interval", | ||||||
|     Retries: "Retries", |     Retries: "Retries", | ||||||
|     "Heartbeat Retry Interval": "Heartbeat Retry Interval", |     "Heartbeat Retry Interval": "Heartbeat Retry Interval", | ||||||
|  |     "Resend Notification if Down X times consequently": "Resend Notification if Down X times consequently", | ||||||
|     Advanced: "Advanced", |     Advanced: "Advanced", | ||||||
|     "Upside Down Mode": "Upside Down Mode", |     "Upside Down Mode": "Upside Down Mode", | ||||||
|     "Max. Redirects": "Max. Redirects", |     "Max. Redirects": "Max. Redirects", | ||||||
| @@ -408,6 +411,8 @@ export default { | |||||||
|     SignName: "SignName", |     SignName: "SignName", | ||||||
|     "Sms template must contain parameters: ": "Sms template must contain parameters: ", |     "Sms template must contain parameters: ": "Sms template must contain parameters: ", | ||||||
|     "Bark Endpoint": "Bark Endpoint", |     "Bark Endpoint": "Bark Endpoint", | ||||||
|  |     "Bark Group": "Bark Group", | ||||||
|  |     "Bark Sound": "Bark Sound", | ||||||
|     WebHookUrl: "WebHookUrl", |     WebHookUrl: "WebHookUrl", | ||||||
|     SecretKey: "SecretKey", |     SecretKey: "SecretKey", | ||||||
|     "For safety, must use secret key": "For safety, must use secret key", |     "For safety, must use secret key": "For safety, must use secret key", | ||||||
| @@ -467,6 +472,7 @@ export default { | |||||||
|     "Domain Name Expiry Notification": "Domain Name Expiry Notification", |     "Domain Name Expiry Notification": "Domain Name Expiry Notification", | ||||||
|     Proxy: "Proxy", |     Proxy: "Proxy", | ||||||
|     "Date Created": "Date Created", |     "Date Created": "Date Created", | ||||||
|  |     HomeAssistant: "Home Assistant", | ||||||
|     onebotHttpAddress: "OneBot HTTP Address", |     onebotHttpAddress: "OneBot HTTP Address", | ||||||
|     onebotMessageType: "OneBot Message Type", |     onebotMessageType: "OneBot Message Type", | ||||||
|     onebotGroupMessage: "Group", |     onebotGroupMessage: "Group", | ||||||
| @@ -479,6 +485,12 @@ export default { | |||||||
|     "Domain Names": "Domain Names", |     "Domain Names": "Domain Names", | ||||||
|     signedInDisp: "Signed in as {0}", |     signedInDisp: "Signed in as {0}", | ||||||
|     signedInDispDisabled: "Auth Disabled.", |     signedInDispDisabled: "Auth Disabled.", | ||||||
|  |     RadiusSecret: "Radius Secret", | ||||||
|  |     RadiusSecretDescription: "Shared Secret between client and server", | ||||||
|  |     RadiusCalledStationId: "Called Station Id", | ||||||
|  |     RadiusCalledStationIdDescription: "Identifier of the called device", | ||||||
|  |     RadiusCallingStationId: "Calling Station Id", | ||||||
|  |     RadiusCallingStationIdDescription: "Identifier of the calling device", | ||||||
|     "Certificate Expiry Notification": "Certificate Expiry Notification", |     "Certificate Expiry Notification": "Certificate Expiry Notification", | ||||||
|     "API Username": "API Username", |     "API Username": "API Username", | ||||||
|     "API Key": "API Key", |     "API Key": "API Key", | ||||||
| @@ -487,7 +499,7 @@ export default { | |||||||
|     "Leave blank to use a shared sender number.": "Leave blank to use a shared sender number.", |     "Leave blank to use a shared sender number.": "Leave blank to use a shared sender number.", | ||||||
|     "Octopush API Version": "Octopush API Version", |     "Octopush API Version": "Octopush API Version", | ||||||
|     "Legacy Octopush-DM": "Legacy Octopush-DM", |     "Legacy Octopush-DM": "Legacy Octopush-DM", | ||||||
|     "endpoint": "endpoint", |     endpoint: "endpoint", | ||||||
|     octopushAPIKey: "\"API key\" from HTTP API credentials in control panel", |     octopushAPIKey: "\"API key\" from HTTP API credentials in control panel", | ||||||
|     octopushLogin: "\"Login\" from HTTP API credentials in control panel", |     octopushLogin: "\"Login\" from HTTP API credentials in control panel", | ||||||
|     promosmsLogin: "API Login Name", |     promosmsLogin: "API Login Name", | ||||||
| @@ -531,9 +543,19 @@ export default { | |||||||
|     "Coming Soon": "Coming Soon", |     "Coming Soon": "Coming Soon", | ||||||
|     wayToGetClickSendSMSToken: "You can get API Username and API Key from {0} .", |     wayToGetClickSendSMSToken: "You can get API Username and API Key from {0} .", | ||||||
|     "Connection String": "Connection String", |     "Connection String": "Connection String", | ||||||
|     "Query": "Query", |     Query: "Query", | ||||||
|     settingsCertificateExpiry: "TLS Certificate Expiry", |     settingsCertificateExpiry: "TLS Certificate Expiry", | ||||||
|     certificationExpiryDescription: "HTTPS Monitors trigger notification when TLS certificate expires in:", |     certificationExpiryDescription: "HTTPS Monitors trigger notification when TLS certificate expires in:", | ||||||
|  |     "Setup Docker Host": "Setup Docker Host", | ||||||
|  |     "Connection Type": "Connection Type", | ||||||
|  |     "Docker Daemon": "Docker Daemon", | ||||||
|  |     deleteDockerHostMsg: "Are you sure want to delete this docker host for all monitors?", | ||||||
|  |     socket: "Socket", | ||||||
|  |     tcp: "TCP / HTTP", | ||||||
|  |     "Docker Container": "Docker Container", | ||||||
|  |     "Container Name / ID": "Container Name / ID", | ||||||
|  |     "Docker Host": "Docker Host", | ||||||
|  |     "Docker Hosts": "Docker Hosts", | ||||||
|     "ntfy Topic": "ntfy Topic", |     "ntfy Topic": "ntfy Topic", | ||||||
|     "Domain": "Domain", |     "Domain": "Domain", | ||||||
|     "Workstation": "Workstation", |     "Workstation": "Workstation", | ||||||
|   | |||||||
| @@ -404,6 +404,8 @@ export default { | |||||||
|     TemplateCode: "TemplateCode", |     TemplateCode: "TemplateCode", | ||||||
|     SignName: "SignName", |     SignName: "SignName", | ||||||
|     "Bark Endpoint": "Bark 接入点", |     "Bark Endpoint": "Bark 接入点", | ||||||
|  |     "Bark Group": "Bark 群组", | ||||||
|  |     "Bark Sound": "Bark 铃声", | ||||||
|     "Device Token": "Apple Device Token", |     "Device Token": "Apple Device Token", | ||||||
|     Platform: "平台", |     Platform: "平台", | ||||||
|     iOS: "iOS", |     iOS: "iOS", | ||||||
|   | |||||||
| @@ -408,6 +408,8 @@ export default { | |||||||
|     SignName: "SignName", |     SignName: "SignName", | ||||||
|     "Sms template must contain parameters: ": "Sms 範本必須包含參數:", |     "Sms template must contain parameters: ": "Sms 範本必須包含參數:", | ||||||
|     "Bark Endpoint": "Bark 端點", |     "Bark Endpoint": "Bark 端點", | ||||||
|  |     "Bark Group": "Bark 群組", | ||||||
|  |     "Bark Sound": "Bark 鈴聲", | ||||||
|     WebHookUrl: "WebHookUrl", |     WebHookUrl: "WebHookUrl", | ||||||
|     SecretKey: "SecretKey", |     SecretKey: "SecretKey", | ||||||
|     "For safety, must use secret key": "為了安全起見,必須使用秘密金鑰", |     "For safety, must use secret key": "為了安全起見,必須使用秘密金鑰", | ||||||
|   | |||||||
| @@ -39,6 +39,7 @@ export default { | |||||||
|             uptimeList: { }, |             uptimeList: { }, | ||||||
|             tlsInfoList: {}, |             tlsInfoList: {}, | ||||||
|             notificationList: [], |             notificationList: [], | ||||||
|  |             dockerHostList: [], | ||||||
|             statusPageListLoaded: false, |             statusPageListLoaded: false, | ||||||
|             statusPageList: [], |             statusPageList: [], | ||||||
|             proxyList: [], |             proxyList: [], | ||||||
| @@ -147,6 +148,10 @@ export default { | |||||||
|                 }); |                 }); | ||||||
|             }); |             }); | ||||||
|  |  | ||||||
|  |             socket.on("dockerHostList", (data) => { | ||||||
|  |                 this.dockerHostList = data; | ||||||
|  |             }); | ||||||
|  |  | ||||||
|             socket.on("heartbeat", (data) => { |             socket.on("heartbeat", (data) => { | ||||||
|                 if (! (data.monitorID in this.heartbeatList)) { |                 if (! (data.monitorID in this.heartbeatList)) { | ||||||
|                     this.heartbeatList[data.monitorID] = []; |                     this.heartbeatList[data.monitorID] = []; | ||||||
|   | |||||||
| @@ -27,6 +27,9 @@ | |||||||
|                                         <option value="dns"> |                                         <option value="dns"> | ||||||
|                                             DNS |                                             DNS | ||||||
|                                         </option> |                                         </option> | ||||||
|  |                                         <option value="docker"> | ||||||
|  |                                             {{ $t("Docker Container") }} | ||||||
|  |                                         </option> | ||||||
|                                     </optgroup> |                                     </optgroup> | ||||||
|  |  | ||||||
|                                     <optgroup label="Passive Monitor Type"> |                                     <optgroup label="Passive Monitor Type"> | ||||||
| @@ -48,6 +51,9 @@ | |||||||
|                                         <option value="postgres"> |                                         <option value="postgres"> | ||||||
|                                             PostgreSQL |                                             PostgreSQL | ||||||
|                                         </option> |                                         </option> | ||||||
|  |                                         <option value="radius"> | ||||||
|  |                                             Radius | ||||||
|  |                                         </option> | ||||||
|                                     </optgroup> |                                     </optgroup> | ||||||
|                                 </select> |                                 </select> | ||||||
|                             </div> |                             </div> | ||||||
| @@ -84,8 +90,8 @@ | |||||||
|                             </div> |                             </div> | ||||||
|  |  | ||||||
|                             <!-- Hostname --> |                             <!-- Hostname --> | ||||||
|                             <!-- TCP Port / Ping / DNS / Steam / MQTT only --> |                             <!-- TCP Port / Ping / DNS / Steam / MQTT / Radius only --> | ||||||
|                             <div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' || monitor.type === 'steam' || monitor.type === 'mqtt'" class="my-3"> |                             <div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' || monitor.type === 'steam' || monitor.type === 'mqtt' || monitor.type === 'radius'" class="my-3"> | ||||||
|                                 <label for="hostname" class="form-label">{{ $t("Hostname") }}</label> |                                 <label for="hostname" class="form-label">{{ $t("Hostname") }}</label> | ||||||
|                                 <input id="hostname" v-model="monitor.hostname" type="text" class="form-control" :pattern="`${ipRegexPattern}|${hostnameRegexPattern}`" required> |                                 <input id="hostname" v-model="monitor.hostname" type="text" class="form-control" :pattern="`${ipRegexPattern}|${hostnameRegexPattern}`" required> | ||||||
|                             </div> |                             </div> | ||||||
| @@ -141,6 +147,34 @@ | |||||||
|                                 </div> |                                 </div> | ||||||
|                             </template> |                             </template> | ||||||
|  |  | ||||||
|  |                             <!-- Docker Container Name / ID --> | ||||||
|  |                             <!-- For Docker Type --> | ||||||
|  |                             <div v-if="monitor.type === 'docker'" class="my-3"> | ||||||
|  |                                 <label for="docker_container" class="form-label">{{ $t("Container Name / ID") }}</label> | ||||||
|  |                                 <input id="docker_container" v-model="monitor.docker_container" type="text" class="form-control" required> | ||||||
|  |                             </div> | ||||||
|  |  | ||||||
|  |                             <!-- Docker Host --> | ||||||
|  |                             <!-- For Docker Type --> | ||||||
|  |                             <div v-if="monitor.type === 'docker'" class="my-3"> | ||||||
|  |                                 <h2 class="mb-2">{{ $t("Docker Host") }}</h2> | ||||||
|  |                                 <p v-if="$root.dockerHostList.length === 0"> | ||||||
|  |                                     {{ $t("Not available, please setup.") }} | ||||||
|  |                                 </p> | ||||||
|  |  | ||||||
|  |                                 <div v-else class="mb-3"> | ||||||
|  |                                     <label for="docker-host" class="form-label">{{ $t("Docker Host") }}</label> | ||||||
|  |                                     <select id="docket-host" v-model="monitor.docker_host" class="form-select"> | ||||||
|  |                                         <option v-for="host in $root.dockerHostList" :key="host.id" :value="host.id">{{ host.name }}</option> | ||||||
|  |                                     </select> | ||||||
|  |                                     <a href="#" @click="$refs.dockerHostDialog.show(monitor.docker_host)">{{ $t("Edit") }}</a> | ||||||
|  |                                 </div> | ||||||
|  |  | ||||||
|  |                                 <button class="btn btn-primary me-2" type="button" @click="$refs.dockerHostDialog.show()"> | ||||||
|  |                                     {{ $t("Setup Docker Host") }} | ||||||
|  |                                 </button> | ||||||
|  |                             </div> | ||||||
|  |  | ||||||
|                             <!-- MQTT --> |                             <!-- MQTT --> | ||||||
|                             <!-- For MQTT Type --> |                             <!-- For MQTT Type --> | ||||||
|                             <template v-if="monitor.type === 'mqtt'"> |                             <template v-if="monitor.type === 'mqtt'"> | ||||||
| @@ -171,6 +205,36 @@ | |||||||
|                                 </div> |                                 </div> | ||||||
|                             </template> |                             </template> | ||||||
|  |  | ||||||
|  |                             <template v-if="monitor.type === 'radius'"> | ||||||
|  |                                 <div class="my-3"> | ||||||
|  |                                     <label for="radius_username" class="form-label">Radius {{ $t("Username") }}</label> | ||||||
|  |                                     <input id="radius_username" v-model="monitor.radiusUsername" type="text" class="form-control" required /> | ||||||
|  |                                 </div> | ||||||
|  |  | ||||||
|  |                                 <div class="my-3"> | ||||||
|  |                                     <label for="radius_password" class="form-label">Radius {{ $t("Password") }}</label> | ||||||
|  |                                     <input id="radius_password" v-model="monitor.radiusPassword" type="password" class="form-control" required /> | ||||||
|  |                                 </div> | ||||||
|  |  | ||||||
|  |                                 <div class="my-3"> | ||||||
|  |                                     <label for="radius_secret" class="form-label">{{ $t("RadiusSecret") }}</label> | ||||||
|  |                                     <input id="radius_secret" v-model="monitor.radiusSecret" type="password" class="form-control" required /> | ||||||
|  |                                     <div class="form-text"> {{ $t( "RadiusSecretDescription") }} </div> | ||||||
|  |                                 </div> | ||||||
|  |  | ||||||
|  |                                 <div class="my-3"> | ||||||
|  |                                     <label for="radius_called_station_id" class="form-label">{{ $t("RadiusCalledStationId") }}</label> | ||||||
|  |                                     <input id="radius_called_station_id" v-model="monitor.radiusCalledStationId" type="text" class="form-control" required /> | ||||||
|  |                                     <div class="form-text"> {{ $t( "RadiusCalledStationIdDescription") }} </div> | ||||||
|  |                                 </div> | ||||||
|  |  | ||||||
|  |                                 <div class="my-3"> | ||||||
|  |                                     <label for="radius_calling_station_id" class="form-label">{{ $t("RadiusCallingStationId") }}</label> | ||||||
|  |                                     <input id="radius_calling_station_id" v-model="monitor.radiusCallingStationId" type="text" class="form-control" required /> | ||||||
|  |                                     <div class="form-text"> {{ $t( "RadiusCallingStationIdDescription") }} </div> | ||||||
|  |                                 </div> | ||||||
|  |                             </template> | ||||||
|  |  | ||||||
|                             <!-- SQL Server and PostgreSQL --> |                             <!-- SQL Server and PostgreSQL --> | ||||||
|                             <template v-if="monitor.type === 'sqlserver' || monitor.type === 'postgres'"> |                             <template v-if="monitor.type === 'sqlserver' || monitor.type === 'postgres'"> | ||||||
|                                 <div class="my-3"> |                                 <div class="my-3"> | ||||||
| @@ -211,6 +275,15 @@ | |||||||
|                                 <input id="retry-interval" v-model="monitor.retryInterval" type="number" class="form-control" required min="20" step="1"> |                                 <input id="retry-interval" v-model="monitor.retryInterval" type="number" class="form-control" required min="20" step="1"> | ||||||
|                             </div> |                             </div> | ||||||
|  |  | ||||||
|  |                             <div class="my-3"> | ||||||
|  |                                 <label for="resend-interval" class="form-label"> | ||||||
|  |                                     {{ $t("Resend Notification if Down X times consequently") }} | ||||||
|  |                                     <span v-if="monitor.resendInterval > 0">({{ $t("resendEveryXTimes", [ monitor.resendInterval ]) }})</span> | ||||||
|  |                                     <span v-else>({{ $t("resendDisabled") }})</span> | ||||||
|  |                                 </label> | ||||||
|  |                                 <input id="resend-interval" v-model="monitor.resendInterval" type="number" class="form-control" required min="0" step="1"> | ||||||
|  |                             </div> | ||||||
|  |  | ||||||
|                             <h2 v-if="monitor.type !== 'push'" class="mt-5 mb-2">{{ $t("Advanced") }}</h2> |                             <h2 v-if="monitor.type !== 'push'" class="mt-5 mb-2">{{ $t("Advanced") }}</h2> | ||||||
|  |  | ||||||
|                             <div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3 form-check"> |                             <div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3 form-check"> | ||||||
| @@ -424,6 +497,7 @@ | |||||||
|             </form> |             </form> | ||||||
|  |  | ||||||
|             <NotificationDialog ref="notificationDialog" @added="addedNotification" /> |             <NotificationDialog ref="notificationDialog" @added="addedNotification" /> | ||||||
|  |             <DockerHostDialog ref="dockerHostDialog" @added="addedDockerHost" /> | ||||||
|             <ProxyDialog ref="proxyDialog" @added="addedProxy" /> |             <ProxyDialog ref="proxyDialog" @added="addedProxy" /> | ||||||
|         </div> |         </div> | ||||||
|     </transition> |     </transition> | ||||||
| @@ -434,6 +508,7 @@ import VueMultiselect from "vue-multiselect"; | |||||||
| import { useToast } from "vue-toastification"; | import { useToast } from "vue-toastification"; | ||||||
| import CopyableInput from "../components/CopyableInput.vue"; | import CopyableInput from "../components/CopyableInput.vue"; | ||||||
| import NotificationDialog from "../components/NotificationDialog.vue"; | import NotificationDialog from "../components/NotificationDialog.vue"; | ||||||
|  | import DockerHostDialog from "../components/DockerHostDialog.vue"; | ||||||
| import ProxyDialog from "../components/ProxyDialog.vue"; | import ProxyDialog from "../components/ProxyDialog.vue"; | ||||||
| import TagsManager from "../components/TagsManager.vue"; | import TagsManager from "../components/TagsManager.vue"; | ||||||
| import { genSecret, isDev } from "../util.ts"; | import { genSecret, isDev } from "../util.ts"; | ||||||
| @@ -445,6 +520,7 @@ export default { | |||||||
|         ProxyDialog, |         ProxyDialog, | ||||||
|         CopyableInput, |         CopyableInput, | ||||||
|         NotificationDialog, |         NotificationDialog, | ||||||
|  |         DockerHostDialog, | ||||||
|         TagsManager, |         TagsManager, | ||||||
|         VueMultiselect, |         VueMultiselect, | ||||||
|     }, |     }, | ||||||
| @@ -593,6 +669,7 @@ export default { | |||||||
|                     method: "GET", |                     method: "GET", | ||||||
|                     interval: 60, |                     interval: 60, | ||||||
|                     retryInterval: this.interval, |                     retryInterval: this.interval, | ||||||
|  |                     resendInterval: 0, | ||||||
|                     maxretries: 0, |                     maxretries: 0, | ||||||
|                     notificationIDList: {}, |                     notificationIDList: {}, | ||||||
|                     ignoreTls: false, |                     ignoreTls: false, | ||||||
| @@ -602,6 +679,8 @@ export default { | |||||||
|                     accepted_statuscodes: [ "200-299" ], |                     accepted_statuscodes: [ "200-299" ], | ||||||
|                     dns_resolve_type: "A", |                     dns_resolve_type: "A", | ||||||
|                     dns_resolve_server: "1.1.1.1", |                     dns_resolve_server: "1.1.1.1", | ||||||
|  |                     docker_container: "", | ||||||
|  |                     docker_host: null, | ||||||
|                     proxyId: null, |                     proxyId: null, | ||||||
|                     mqttUsername: "", |                     mqttUsername: "", | ||||||
|                     mqttPassword: "", |                     mqttPassword: "", | ||||||
| @@ -729,6 +808,12 @@ export default { | |||||||
|         addedProxy(id) { |         addedProxy(id) { | ||||||
|             this.monitor.proxyId = id; |             this.monitor.proxyId = id; | ||||||
|         }, |         }, | ||||||
|  |  | ||||||
|  |         // Added a Docker Host Event | ||||||
|  |         // Enable it if the Docker Host is added in EditMonitor.vue | ||||||
|  |         addedDockerHost(id) { | ||||||
|  |             this.monitor.docker_host = id; | ||||||
|  |         } | ||||||
|     }, |     }, | ||||||
| }; | }; | ||||||
| </script> | </script> | ||||||
|   | |||||||
| @@ -89,6 +89,9 @@ export default { | |||||||
|                 "monitor-history": { |                 "monitor-history": { | ||||||
|                     title: this.$t("Monitor History"), |                     title: this.$t("Monitor History"), | ||||||
|                 }, |                 }, | ||||||
|  |                 "docker-hosts": { | ||||||
|  |                     title: this.$t("Docker Hosts"), | ||||||
|  |                 }, | ||||||
|                 security: { |                 security: { | ||||||
|                     title: this.$t("Security"), |                     title: this.$t("Security"), | ||||||
|                 }, |                 }, | ||||||
|   | |||||||
| @@ -25,6 +25,7 @@ const Security = () => import("./components/settings/Security.vue"); | |||||||
| import Proxies from "./components/settings/Proxies.vue"; | import Proxies from "./components/settings/Proxies.vue"; | ||||||
| import Backup from "./components/settings/Backup.vue"; | import Backup from "./components/settings/Backup.vue"; | ||||||
| import About from "./components/settings/About.vue"; | import About from "./components/settings/About.vue"; | ||||||
|  | import DockerHosts from "./components/settings/Docker.vue"; | ||||||
|  |  | ||||||
| const routes = [ | const routes = [ | ||||||
|     { |     { | ||||||
| @@ -95,6 +96,10 @@ const routes = [ | |||||||
|                                 path: "monitor-history", |                                 path: "monitor-history", | ||||||
|                                 component: MonitorHistory, |                                 component: MonitorHistory, | ||||||
|                             }, |                             }, | ||||||
|  |                             { | ||||||
|  |                                 path: "docker-hosts", | ||||||
|  |                                 component: DockerHosts, | ||||||
|  |                             }, | ||||||
|                             { |                             { | ||||||
|                                 path: "security", |                                 path: "security", | ||||||
|                                 component: Security, |                                 component: Security, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user