mirror of
				https://github.com/louislam/uptime-kuma.git
				synced 2025-11-04 13:46:13 +08:00 
			
		
		
		
	Merge pull request #1183 from c0derMo/master
Adding option to monitor other docker containers
This commit is contained in:
		@@ -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;
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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;
 | 
			
		||||
@@ -88,6 +88,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,
 | 
			
		||||
@@ -468,6 +471,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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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());
 | 
			
		||||
 | 
			
		||||
@@ -680,6 +681,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;
 | 
			
		||||
@@ -1438,6 +1441,7 @@ let needSetup = false;
 | 
			
		||||
        cloudflaredSocketHandler(socket);
 | 
			
		||||
        databaseSocketHandler(socket);
 | 
			
		||||
        proxySocketHandler(socket);
 | 
			
		||||
        dockerSocketHandler(socket);
 | 
			
		||||
 | 
			
		||||
        log.debug("server", "added all socket handlers");
 | 
			
		||||
 | 
			
		||||
@@ -1538,6 +1542,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,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										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>
 | 
			
		||||
							
								
								
									
										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>
 | 
			
		||||
@@ -487,7 +487,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 +531,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",
 | 
			
		||||
 
 | 
			
		||||
@@ -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">
 | 
			
		||||
@@ -141,6 +144,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'">
 | 
			
		||||
@@ -424,6 +455,7 @@
 | 
			
		||||
            </form>
 | 
			
		||||
 | 
			
		||||
            <NotificationDialog ref="notificationDialog" @added="addedNotification" />
 | 
			
		||||
            <DockerHostDialog ref="dockerHostDialog" @added="addedDockerHost" />
 | 
			
		||||
            <ProxyDialog ref="proxyDialog" @added="addedProxy" />
 | 
			
		||||
        </div>
 | 
			
		||||
    </transition>
 | 
			
		||||
@@ -434,6 +466,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 +478,7 @@ export default {
 | 
			
		||||
        ProxyDialog,
 | 
			
		||||
        CopyableInput,
 | 
			
		||||
        NotificationDialog,
 | 
			
		||||
        DockerHostDialog,
 | 
			
		||||
        TagsManager,
 | 
			
		||||
        VueMultiselect,
 | 
			
		||||
    },
 | 
			
		||||
@@ -602,6 +636,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 +765,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,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user