mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-08-08 10:19:12 +08:00
Merge pull request #1183 from c0derMo/master
Adding option to monitor other docker containers
This commit is contained in:
@@ -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,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
Reference in New Issue
Block a user