mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-11-24 07:14:04 +00:00
Merge branch 'louislam:master' into bulgarian
This commit is contained in:
commit
8bc3651a7d
32 changed files with 1016 additions and 20 deletions
|
@ -23,7 +23,7 @@ VPS is sponsored by Uptime Kuma sponsors on [Open Collective](https://opencollec
|
|||
|
||||
## ⭐ 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.
|
||||
* 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.
|
||||
|
|
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
|
||||
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
|
||||
|
|
|
@ -11,7 +11,7 @@ WORKDIR /app
|
|||
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 \
|
||||
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/* && \
|
||||
apt --yes autoremove
|
||||
|
||||
|
|
114
package-lock.json
generated
114
package-lock.json
generated
|
@ -39,6 +39,7 @@
|
|||
"mqtt": "^4.2.8",
|
||||
"mssql": "^8.1.0",
|
||||
"node-cloudflared-tunnel": "~1.0.9",
|
||||
"node-radius-client": "^1.0.0",
|
||||
"nodemailer": "~6.6.5",
|
||||
"notp": "~2.0.3",
|
||||
"password-hash": "~1.2.2",
|
||||
|
@ -8215,6 +8216,12 @@
|
|||
"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": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
|
||||
|
@ -8915,6 +8922,17 @@
|
|||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
||||
"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": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
|
@ -12151,6 +12169,32 @@
|
|||
"integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
|
||||
"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": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz",
|
||||
|
@ -13429,6 +13473,14 @@
|
|||
"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": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
|
@ -15261,6 +15313,15 @@
|
|||
"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": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
|
||||
|
@ -22641,6 +22702,11 @@
|
|||
"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": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
|
||||
|
@ -23123,6 +23189,14 @@
|
|||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
||||
"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": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
|
@ -25618,6 +25692,33 @@
|
|||
"integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
|
||||
"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": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz",
|
||||
|
@ -26532,6 +26633,11 @@
|
|||
"integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==",
|
||||
"dev": true
|
||||
},
|
||||
"radius": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/radius/-/radius-1.1.4.tgz",
|
||||
"integrity": "sha512-UWuzdF6xf3NpsXFZZmUEkxtEalDXj8hdmMXgbGzn7vOk6zXNsiIY2I6SJ1euHt7PTQuMoz2qDEJB+AfJDJgQYw=="
|
||||
},
|
||||
"range-parser": {
|
||||
"version": "1.2.1",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "uptime-kuma",
|
||||
"version": "1.17.1",
|
||||
"version": "1.18.0-beta.0",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -91,6 +91,7 @@
|
|||
"mqtt": "^4.2.8",
|
||||
"mssql": "^8.1.0",
|
||||
"node-cloudflared-tunnel": "~1.0.9",
|
||||
"node-radius-client": "^1.0.0",
|
||||
"nodemailer": "~6.6.5",
|
||||
"notp": "~2.0.3",
|
||||
"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 = {
|
||||
sendNotificationList,
|
||||
sendImportantHeartbeatList,
|
||||
sendHeartbeatList,
|
||||
sendProxyList,
|
||||
sendInfo,
|
||||
sendDockerHostList
|
||||
};
|
||||
|
|
|
@ -53,6 +53,7 @@ class Database {
|
|||
"patch-2fa-invalidate-used-token.sql": true,
|
||||
"patch-notification_sent_history.sql": true,
|
||||
"patch-monitor-basic-auth.sql": true,
|
||||
"patch-add-docker-columns.sql": true,
|
||||
"patch-status-page.sql": true,
|
||||
"patch-proxy.sql": true,
|
||||
"patch-monitor-expiry-notification.sql": true,
|
||||
|
@ -61,6 +62,8 @@ class Database {
|
|||
"patch-add-clickable-status-page-link.sql": true,
|
||||
"patch-add-sqlserver-monitor.sql": true,
|
||||
"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 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.
|
||||
// FULL synchronous is very safe, but it is also slower.
|
||||
// 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 { Prometheus } = require("../prometheus");
|
||||
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 { BeanModel } = require("redbean-node/dist/bean-model");
|
||||
const { Notification } = require("../notification");
|
||||
|
@ -79,6 +79,7 @@ class Monitor extends BeanModel {
|
|||
type: this.type,
|
||||
interval: this.interval,
|
||||
retryInterval: this.retryInterval,
|
||||
resendInterval: this.resendInterval,
|
||||
keyword: this.keyword,
|
||||
expiryNotification: this.isEnabledExpiryNotification(),
|
||||
ignoreTls: this.getIgnoreTls(),
|
||||
|
@ -88,6 +89,9 @@ class Monitor extends BeanModel {
|
|||
dns_resolve_type: this.dns_resolve_type,
|
||||
dns_resolve_server: this.dns_resolve_server,
|
||||
dns_last_result: this.dns_last_result,
|
||||
pushToken: this.pushToken,
|
||||
docker_container: this.docker_container,
|
||||
docker_host: this.docker_host,
|
||||
proxyId: this.proxy_id,
|
||||
notificationIDList,
|
||||
tags: tags,
|
||||
|
@ -100,6 +104,11 @@ class Monitor extends BeanModel {
|
|||
authMethod: this.authMethod,
|
||||
authWorkstation: this.authWorkstation,
|
||||
authDomain: this.authDomain,
|
||||
radiusUsername: this.radiusUsername,
|
||||
radiusPassword: this.radiusPassword,
|
||||
radiusCalledStationId: this.radiusCalledStationId,
|
||||
radiusCallingStationId: this.radiusCallingStationId,
|
||||
radiusSecret: this.radiusSecret,
|
||||
};
|
||||
|
||||
if (includeSensitiveData) {
|
||||
|
@ -206,6 +215,7 @@ class Monitor extends BeanModel {
|
|||
bean.monitor_id = this.id;
|
||||
bean.time = R.isoDateTimeMillis(dayjs.utc());
|
||||
bean.status = DOWN;
|
||||
bean.downCount = previousBeat?.downCount || 0;
|
||||
|
||||
if (this.isUpsideDown()) {
|
||||
bean.status = flipStatus(bean.status);
|
||||
|
@ -468,6 +478,35 @@ class Monitor extends BeanModel {
|
|||
} else {
|
||||
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") {
|
||||
bean.msg = await mqttAsync(this.hostname, this.mqttTopic, this.mqttSuccessMessage, {
|
||||
port: this.port,
|
||||
|
@ -492,6 +531,30 @@ class Monitor extends BeanModel {
|
|||
bean.msg = "";
|
||||
bean.status = UP;
|
||||
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 {
|
||||
bean.msg = "Unknown Monitor Type";
|
||||
bean.status = PENDING;
|
||||
|
@ -533,12 +596,27 @@ class Monitor extends BeanModel {
|
|||
log.debug("monitor", `[${this.name}] sendNotification`);
|
||||
await Monitor.sendNotification(isFirstBeat, this, bean);
|
||||
|
||||
// Reset down count
|
||||
bean.downCount = 0;
|
||||
|
||||
// Clear Status Page Cache
|
||||
log.debug("monitor", `[${this.name}] apicache clear`);
|
||||
apicache.clear();
|
||||
|
||||
} else {
|
||||
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) {
|
||||
|
@ -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}`);
|
||||
} 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`);
|
||||
|
|
|
@ -12,9 +12,7 @@ const { default: axios } = require("axios");
|
|||
|
||||
// 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 barkNotificationSound = "telegraph";
|
||||
const successMessage = "Successes!";
|
||||
|
||||
class Bark extends NotificationProvider {
|
||||
|
@ -50,13 +48,23 @@ class Bark extends NotificationProvider {
|
|||
* @param {string} postUrl URL to append parameters to
|
||||
* @returns {string}
|
||||
*/
|
||||
appendAdditionalParameters(postUrl) {
|
||||
// grouping all our notifications
|
||||
postUrl += "?group=" + barkNotificationGroup;
|
||||
appendAdditionalParameters(notification, postUrl) {
|
||||
// set icon to uptime kuma icon, 11kb should be fine
|
||||
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
|
||||
postUrl += "&sound=" + barkNotificationSound;
|
||||
if (notification.barkSound != null) {
|
||||
postUrl += "&sound=" + notification.barkSound;
|
||||
} else {
|
||||
// default sound
|
||||
postUrl += "&sound=" + "telegraph";
|
||||
}
|
||||
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 Gorush = require("./notification-providers/gorush");
|
||||
const Gotify = require("./notification-providers/gotify");
|
||||
const HomeAssistant = require("./notification-providers/home-assistant");
|
||||
const Line = require("./notification-providers/line");
|
||||
const LineNotify = require("./notification-providers/linenotify");
|
||||
const LunaSea = require("./notification-providers/lunasea");
|
||||
|
@ -61,6 +62,7 @@ class Notification {
|
|||
new GoogleChat(),
|
||||
new Gorush(),
|
||||
new Gotify(),
|
||||
new HomeAssistant(),
|
||||
new Line(),
|
||||
new LineNotify(),
|
||||
new LunaSea(),
|
||||
|
|
|
@ -118,13 +118,14 @@ if (config.demoMode) {
|
|||
}
|
||||
|
||||
// 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 databaseSocketHandler = require("./socket-handlers/database-socket-handler");
|
||||
const TwoFA = require("./2fa");
|
||||
const StatusPage = require("./model/status_page");
|
||||
const { cloudflaredSocketHandler, autoStart: cloudflaredAutoStart, stop: cloudflaredStop } = require("./socket-handlers/cloudflared-socket-handler");
|
||||
const { proxySocketHandler } = require("./socket-handlers/proxy-socket-handler");
|
||||
const { dockerSocketHandler } = require("./socket-handlers/docker-socket-handler");
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
|
@ -668,6 +669,7 @@ let needSetup = false;
|
|||
bean.basic_auth_pass = monitor.basic_auth_pass;
|
||||
bean.interval = monitor.interval;
|
||||
bean.retryInterval = monitor.retryInterval;
|
||||
bean.resendInterval = monitor.resendInterval;
|
||||
bean.hostname = monitor.hostname;
|
||||
bean.maxretries = monitor.maxretries;
|
||||
bean.port = parseInt(monitor.port);
|
||||
|
@ -680,6 +682,8 @@ let needSetup = false;
|
|||
bean.dns_resolve_type = monitor.dns_resolve_type;
|
||||
bean.dns_resolve_server = monitor.dns_resolve_server;
|
||||
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.mqttUsername = monitor.mqttUsername;
|
||||
bean.mqttPassword = monitor.mqttPassword;
|
||||
|
@ -690,6 +694,11 @@ let needSetup = false;
|
|||
bean.authMethod = monitor.authMethod;
|
||||
bean.authWorkstation = monitor.authWorkstation;
|
||||
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);
|
||||
|
||||
|
@ -1270,6 +1279,7 @@ let needSetup = false;
|
|||
authDomain: monitorListData[i].authDomain,
|
||||
interval: monitorListData[i].interval,
|
||||
retryInterval: retryInterval,
|
||||
resendInterval: monitorListData[i].resendInterval || 0,
|
||||
hostname: monitorListData[i].hostname,
|
||||
maxretries: monitorListData[i].maxretries,
|
||||
port: monitorListData[i].port,
|
||||
|
@ -1438,6 +1448,7 @@ let needSetup = false;
|
|||
cloudflaredSocketHandler(socket);
|
||||
databaseSocketHandler(socket);
|
||||
proxySocketHandler(socket);
|
||||
dockerSocketHandler(socket);
|
||||
|
||||
log.debug("server", "added all socket handlers");
|
||||
|
||||
|
@ -1538,6 +1549,7 @@ async function afterLogin(socket, user) {
|
|||
let monitorList = await server.sendMonitorList(socket);
|
||||
sendNotificationList(socket);
|
||||
sendProxyList(socket);
|
||||
sendDockerHostList(socket);
|
||||
|
||||
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 { NtlmClient } = require("axios-ntlm");
|
||||
const { Settings } = require("./settings");
|
||||
const radiusClient = require("node-radius-client");
|
||||
const {
|
||||
dictionaries: {
|
||||
rfc2865: { file, attributes },
|
||||
},
|
||||
} = require("node-radius-utils");
|
||||
|
||||
// From ping-lite
|
||||
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
|
||||
* @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">
|
||||
<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>
|
||||
<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">
|
||||
<a
|
||||
href="https://github.com/Finb/Bark"
|
||||
|
@ -12,4 +9,45 @@
|
|||
>{{ $t("here") }}</a>
|
||||
</i18n-t>
|
||||
</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>
|
||||
|
|
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 Gorush from "./Gorush.vue";
|
||||
import Gotify from "./Gotify.vue";
|
||||
import HomeAssistant from "./HomeAssistant.vue";
|
||||
import Line from "./Line.vue";
|
||||
import LineNotify from "./LineNotify.vue";
|
||||
import LunaSea from "./LunaSea.vue";
|
||||
|
@ -54,6 +55,7 @@ const NotificationFormList = {
|
|||
"GoogleChat": GoogleChat,
|
||||
"gorush": Gorush,
|
||||
"gotify": Gotify,
|
||||
"HomeAssistant": HomeAssistant,
|
||||
"line": Line,
|
||||
"LineNotify": LineNotify,
|
||||
"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",
|
||||
"Search...": "Suchen...",
|
||||
"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",
|
||||
resendEveryXTimes: "Erneut versenden alle {0} mal",
|
||||
resendDisabled: "Erneut versenden deaktiviert",
|
||||
"Import Backup": "Backup importieren",
|
||||
"Export Backup": "Backup exportieren",
|
||||
"Avg. Ping": "Durchschn. Ping",
|
||||
|
|
|
@ -2,6 +2,8 @@ export default {
|
|||
languageName: "English",
|
||||
checkEverySecond: "Check 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",
|
||||
ignoreTLSError: "Ignore TLS/SSL error for HTTPS websites",
|
||||
upsideDownModeDescription: "Flip the status upside down. If the service is reachable, it is DOWN.",
|
||||
|
@ -72,6 +74,7 @@ export default {
|
|||
"Heartbeat Interval": "Heartbeat Interval",
|
||||
Retries: "Retries",
|
||||
"Heartbeat Retry Interval": "Heartbeat Retry Interval",
|
||||
"Resend Notification if Down X times consequently": "Resend Notification if Down X times consequently",
|
||||
Advanced: "Advanced",
|
||||
"Upside Down Mode": "Upside Down Mode",
|
||||
"Max. Redirects": "Max. Redirects",
|
||||
|
@ -408,6 +411,8 @@ export default {
|
|||
SignName: "SignName",
|
||||
"Sms template must contain parameters: ": "Sms template must contain parameters: ",
|
||||
"Bark Endpoint": "Bark Endpoint",
|
||||
"Bark Group": "Bark Group",
|
||||
"Bark Sound": "Bark Sound",
|
||||
WebHookUrl: "WebHookUrl",
|
||||
SecretKey: "SecretKey",
|
||||
"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",
|
||||
Proxy: "Proxy",
|
||||
"Date Created": "Date Created",
|
||||
HomeAssistant: "Home Assistant",
|
||||
onebotHttpAddress: "OneBot HTTP Address",
|
||||
onebotMessageType: "OneBot Message Type",
|
||||
onebotGroupMessage: "Group",
|
||||
|
@ -479,6 +485,12 @@ export default {
|
|||
"Domain Names": "Domain Names",
|
||||
signedInDisp: "Signed in as {0}",
|
||||
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",
|
||||
"API Username": "API Username",
|
||||
"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.",
|
||||
"Octopush API Version": "Octopush API Version",
|
||||
"Legacy Octopush-DM": "Legacy Octopush-DM",
|
||||
"endpoint": "endpoint",
|
||||
endpoint: "endpoint",
|
||||
octopushAPIKey: "\"API key\" from HTTP API credentials in control panel",
|
||||
octopushLogin: "\"Login\" from HTTP API credentials in control panel",
|
||||
promosmsLogin: "API Login Name",
|
||||
|
@ -531,9 +543,19 @@ export default {
|
|||
"Coming Soon": "Coming Soon",
|
||||
wayToGetClickSendSMSToken: "You can get API Username and API Key from {0} .",
|
||||
"Connection String": "Connection String",
|
||||
"Query": "Query",
|
||||
Query: "Query",
|
||||
settingsCertificateExpiry: "TLS Certificate Expiry",
|
||||
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",
|
||||
"Domain": "Domain",
|
||||
"Workstation": "Workstation",
|
||||
|
|
|
@ -404,6 +404,8 @@ export default {
|
|||
TemplateCode: "TemplateCode",
|
||||
SignName: "SignName",
|
||||
"Bark Endpoint": "Bark 接入点",
|
||||
"Bark Group": "Bark 群组",
|
||||
"Bark Sound": "Bark 铃声",
|
||||
"Device Token": "Apple Device Token",
|
||||
Platform: "平台",
|
||||
iOS: "iOS",
|
||||
|
|
|
@ -408,6 +408,8 @@ export default {
|
|||
SignName: "SignName",
|
||||
"Sms template must contain parameters: ": "Sms 範本必須包含參數:",
|
||||
"Bark Endpoint": "Bark 端點",
|
||||
"Bark Group": "Bark 群組",
|
||||
"Bark Sound": "Bark 鈴聲",
|
||||
WebHookUrl: "WebHookUrl",
|
||||
SecretKey: "SecretKey",
|
||||
"For safety, must use secret key": "為了安全起見,必須使用秘密金鑰",
|
||||
|
|
|
@ -39,6 +39,7 @@ export default {
|
|||
uptimeList: { },
|
||||
tlsInfoList: {},
|
||||
notificationList: [],
|
||||
dockerHostList: [],
|
||||
statusPageListLoaded: false,
|
||||
statusPageList: [],
|
||||
proxyList: [],
|
||||
|
@ -147,6 +148,10 @@ export default {
|
|||
});
|
||||
});
|
||||
|
||||
socket.on("dockerHostList", (data) => {
|
||||
this.dockerHostList = data;
|
||||
});
|
||||
|
||||
socket.on("heartbeat", (data) => {
|
||||
if (! (data.monitorID in this.heartbeatList)) {
|
||||
this.heartbeatList[data.monitorID] = [];
|
||||
|
|
|
@ -27,6 +27,9 @@
|
|||
<option value="dns">
|
||||
DNS
|
||||
</option>
|
||||
<option value="docker">
|
||||
{{ $t("Docker Container") }}
|
||||
</option>
|
||||
</optgroup>
|
||||
|
||||
<optgroup label="Passive Monitor Type">
|
||||
|
@ -48,6 +51,9 @@
|
|||
<option value="postgres">
|
||||
PostgreSQL
|
||||
</option>
|
||||
<option value="radius">
|
||||
Radius
|
||||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
|
@ -84,8 +90,8 @@
|
|||
</div>
|
||||
|
||||
<!-- Hostname -->
|
||||
<!-- TCP Port / Ping / DNS / Steam / MQTT only -->
|
||||
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' || monitor.type === 'steam' || monitor.type === 'mqtt'" class="my-3">
|
||||
<!-- 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' || monitor.type === 'radius'" class="my-3">
|
||||
<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>
|
||||
</div>
|
||||
|
@ -141,6 +147,34 @@
|
|||
</div>
|
||||
</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 -->
|
||||
<!-- For MQTT Type -->
|
||||
<template v-if="monitor.type === 'mqtt'">
|
||||
|
@ -171,6 +205,36 @@
|
|||
</div>
|
||||
</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 -->
|
||||
<template v-if="monitor.type === 'sqlserver' || monitor.type === 'postgres'">
|
||||
<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">
|
||||
</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>
|
||||
|
||||
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3 form-check">
|
||||
|
@ -424,6 +497,7 @@
|
|||
</form>
|
||||
|
||||
<NotificationDialog ref="notificationDialog" @added="addedNotification" />
|
||||
<DockerHostDialog ref="dockerHostDialog" @added="addedDockerHost" />
|
||||
<ProxyDialog ref="proxyDialog" @added="addedProxy" />
|
||||
</div>
|
||||
</transition>
|
||||
|
@ -434,6 +508,7 @@ import VueMultiselect from "vue-multiselect";
|
|||
import { useToast } from "vue-toastification";
|
||||
import CopyableInput from "../components/CopyableInput.vue";
|
||||
import NotificationDialog from "../components/NotificationDialog.vue";
|
||||
import DockerHostDialog from "../components/DockerHostDialog.vue";
|
||||
import ProxyDialog from "../components/ProxyDialog.vue";
|
||||
import TagsManager from "../components/TagsManager.vue";
|
||||
import { genSecret, isDev } from "../util.ts";
|
||||
|
@ -445,6 +520,7 @@ export default {
|
|||
ProxyDialog,
|
||||
CopyableInput,
|
||||
NotificationDialog,
|
||||
DockerHostDialog,
|
||||
TagsManager,
|
||||
VueMultiselect,
|
||||
},
|
||||
|
@ -593,6 +669,7 @@ export default {
|
|||
method: "GET",
|
||||
interval: 60,
|
||||
retryInterval: this.interval,
|
||||
resendInterval: 0,
|
||||
maxretries: 0,
|
||||
notificationIDList: {},
|
||||
ignoreTls: false,
|
||||
|
@ -602,6 +679,8 @@ export default {
|
|||
accepted_statuscodes: [ "200-299" ],
|
||||
dns_resolve_type: "A",
|
||||
dns_resolve_server: "1.1.1.1",
|
||||
docker_container: "",
|
||||
docker_host: null,
|
||||
proxyId: null,
|
||||
mqttUsername: "",
|
||||
mqttPassword: "",
|
||||
|
@ -729,6 +808,12 @@ export default {
|
|||
addedProxy(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>
|
||||
|
|
|
@ -89,6 +89,9 @@ export default {
|
|||
"monitor-history": {
|
||||
title: this.$t("Monitor History"),
|
||||
},
|
||||
"docker-hosts": {
|
||||
title: this.$t("Docker Hosts"),
|
||||
},
|
||||
security: {
|
||||
title: this.$t("Security"),
|
||||
},
|
||||
|
|
|
@ -25,6 +25,7 @@ const Security = () => import("./components/settings/Security.vue");
|
|||
import Proxies from "./components/settings/Proxies.vue";
|
||||
import Backup from "./components/settings/Backup.vue";
|
||||
import About from "./components/settings/About.vue";
|
||||
import DockerHosts from "./components/settings/Docker.vue";
|
||||
|
||||
const routes = [
|
||||
{
|
||||
|
@ -95,6 +96,10 @@ const routes = [
|
|||
path: "monitor-history",
|
||||
component: MonitorHistory,
|
||||
},
|
||||
{
|
||||
path: "docker-hosts",
|
||||
component: DockerHosts,
|
||||
},
|
||||
{
|
||||
path: "security",
|
||||
component: Security,
|
||||
|
|
Loading…
Reference in a new issue