diff --git a/db/patch-add-docker-columns.sql b/db/patch-add-docker-columns.sql index 564756678..4cea448d7 100644 --- a/db/patch-add-docker-columns.sql +++ b/db/patch-add-docker-columns.sql @@ -1,13 +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_daemon VARCHAR(255); + ADD docker_host INTEGER REFERENCES docker_host(id); ALTER TABLE monitor ADD docker_container VARCHAR(255); -ALTER TABLE monitor - ADD docker_type VARCHAR(255); - COMMIT; diff --git a/server/client.js b/server/client.js index f6f133d19..614038427 100644 --- a/server/client.js +++ b/server/client.js @@ -122,10 +122,30 @@ async function sendInfo(socket) { }); } +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.export()); + } + + io.to(socket.userID).emit("dockerHostList", result); + + timeLogger.print("Send Docker Host List"); + + return list; +} + module.exports = { sendNotificationList, sendImportantHeartbeatList, sendHeartbeatList, sendProxyList, sendInfo, + sendDockerHostList }; diff --git a/server/docker.js b/server/docker.js new file mode 100644 index 000000000..a13236aa9 --- /dev/null +++ b/server/docker.js @@ -0,0 +1,67 @@ +const axios = require("axios"); +const { R } = require("redbean-node"); +const version = require("../package.json").version; +const https = require("https"); + +class DockerHost { + 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.docker_daemon; + bean.docker_type = dockerHost.docker_type; + bean.name = dockerHost.name; + + await R.store(bean); + + return bean; + } + + 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"); + } + + await R.trash(bean); + } + + static async getAmountContainer(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.docker_type === "socket") { + options.socketPath = dockerHost.docker_daemon; + } else if (dockerHost.docker_type === "tcp") { + options.baseURL = dockerHost.docker_daemon; + } + + let res = await axios.request(options); + return res.data.length; + } +} + +module.exports = { + DockerHost, +} \ No newline at end of file diff --git a/server/model/docker_host.js b/server/model/docker_host.js new file mode 100644 index 000000000..26f3035a5 --- /dev/null +++ b/server/model/docker_host.js @@ -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, + daemon: this._dockerDaemon, + type: this._dockerType, + name: this._name, + } + } +} + +module.exports = DockerHost; \ No newline at end of file diff --git a/server/model/monitor.js b/server/model/monitor.js index eff167c6c..373796e96 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -89,8 +89,7 @@ class Monitor extends BeanModel { dns_last_result: this.dns_last_result, pushToken: this.pushToken, docker_container: this.docker_container, - docker_daemon: this.docker_daemon, - docker_type: this.docker_type, + docker_host: this.docker_host, proxyId: this.proxy_id, notificationIDList, tags: tags, @@ -471,6 +470,8 @@ class Monitor extends BeanModel { } else if (this.type === "docker") { log.debug(`[${this.name}] Prepare Options for Axios`); + const docker_host = await R.load("docker_host", this.docker_host); + const options = { url: `/containers/${this.docker_container}/json`, headers: { @@ -483,10 +484,10 @@ class Monitor extends BeanModel { }), }; - if (this.docker_type === "socket") { - options.socketPath = this.docker_daemon; - } else if (this.docker_type === "tcp") { - options.baseURL = this.docker_daemon; + if (docker_host._dockerType === "socket") { + options.socketPath = docker_host._dockerDaemon; + } else if (docker_host._dockerType === "tcp") { + options.baseURL = docker_host._dockerDaemon; } log.debug(`[${this.name}] Axios Request`); diff --git a/server/server.js b/server/server.js index 71a9a7b5d..1e5661482 100644 --- a/server/server.js +++ b/server/server.js @@ -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()); @@ -665,8 +666,7 @@ let needSetup = false; bean.dns_resolve_server = monitor.dns_resolve_server; bean.pushToken = monitor.pushToken; bean.docker_container = monitor.docker_container; - bean.docker_daemon = monitor.docker_daemon; - bean.docker_type = monitor.docker_type; + bean.docker_host = monitor.docker_host; bean.proxyId = Number.isInteger(monitor.proxyId) ? monitor.proxyId : null; bean.mqttUsername = monitor.mqttUsername; bean.mqttPassword = monitor.mqttPassword; @@ -1425,6 +1425,7 @@ let needSetup = false; cloudflaredSocketHandler(socket); databaseSocketHandler(socket); proxySocketHandler(socket); + dockerSocketHandler(socket); log.debug("server", "added all socket handlers"); @@ -1525,6 +1526,7 @@ async function afterLogin(socket, user) { let monitorList = await server.sendMonitorList(socket); sendNotificationList(socket); sendProxyList(socket); + sendDockerHostList(socket); await sleep(500); diff --git a/server/socket-handlers/docker-socket-handler.js b/server/socket-handlers/docker-socket-handler.js new file mode 100644 index 000000000..eddcd7b84 --- /dev/null +++ b/server/socket-handlers/docker-socket-handler.js @@ -0,0 +1,67 @@ +const { sendDockerHostList } = require("../client"); +const { checkLogin } = require("../util-server"); +const { DockerHost } = require("../docker"); + +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.getAmountContainer(dockerHost); + + callback({ + ok: true, + msg: "Amount of containers: " + amount, + }); + + } catch (e) { + console.error(e); + + callback({ + ok: false, + msg: e.message, + }) + } + }) +} \ No newline at end of file diff --git a/src/components/DockerHostDialog.vue b/src/components/DockerHostDialog.vue new file mode 100644 index 000000000..e52c4ecf3 --- /dev/null +++ b/src/components/DockerHostDialog.vue @@ -0,0 +1,160 @@ + + + + + diff --git a/src/mixins/socket.js b/src/mixins/socket.js index c54b573f3..f6de82c2b 100644 --- a/src/mixins/socket.js +++ b/src/mixins/socket.js @@ -39,6 +39,7 @@ export default { uptimeList: { }, tlsInfoList: {}, notificationList: [], + dockerHostList: [], statusPageListLoaded: false, statusPageList: [], proxyList: [], @@ -141,6 +142,10 @@ export default { }); }); + socket.on("dockerHostList", (data) => { + this.dockerHostList = data; + }) + socket.on("heartbeat", (data) => { if (! (data.monitorID in this.heartbeatList)) { this.heartbeatList[data.monitorID] = []; diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index 600324590..5ff318bf8 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -148,25 +148,25 @@ - +
- - -
+

{{ $t("Docker Host") }}

+

+ {{ $t("Not available, please setup.") }} +

- - -
- - +
+ + + {{ $t("Edit") }} +
+ +
@@ -446,6 +446,7 @@ + @@ -456,6 +457,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"; @@ -467,6 +469,7 @@ export default { ProxyDialog, CopyableInput, NotificationDialog, + DockerHostDialog, TagsManager, VueMultiselect, }, @@ -625,8 +628,7 @@ export default { dns_resolve_type: "A", dns_resolve_server: "1.1.1.1", docker_container: "", - docker_daemon: "/var/run/docker.sock", - docker_type: "socket", + docker_host: null, proxyId: null, mqttUsername: "", mqttPassword: "", @@ -740,6 +742,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; + } }, };