mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-08-09 17:29:08 +08:00
Feature: remote browser support (#3904)
* [empty commit] pull request for remote browser support * Remote browser: Added UI screens and DB tables. * Remote browser working * Fixing tests * Fix tests * Fix tests * fix tests * Test browser * revert init_db.js * Changed drop down to ActionSelect * Fix translations * added remote browsers toggle * revert changes package-lock * Fix bad english * Set default remote browser * Remote browsers Requested changes * fixed description.
This commit is contained in:
@@ -185,6 +185,30 @@ async function sendDockerHostList(socket) {
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send list of docker hosts to client
|
||||
* @param {Socket} socket Socket.io socket instance
|
||||
* @returns {Promise<Bean[]>} List of docker hosts
|
||||
*/
|
||||
async function sendRemoteBrowserList(socket) {
|
||||
const timeLogger = new TimeLogger();
|
||||
|
||||
let result = [];
|
||||
let list = await R.find("remote_browser", " user_id = ? ", [
|
||||
socket.userID,
|
||||
]);
|
||||
|
||||
for (let bean of list) {
|
||||
result.push(bean.toJSON());
|
||||
}
|
||||
|
||||
io.to(socket.userID).emit("remoteBrowserList", result);
|
||||
|
||||
timeLogger.print("Send Remote Browser List");
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sendNotificationList,
|
||||
sendImportantHeartbeatList,
|
||||
@@ -192,5 +216,6 @@ module.exports = {
|
||||
sendProxyList,
|
||||
sendAPIKeyList,
|
||||
sendInfo,
|
||||
sendDockerHostList
|
||||
sendDockerHostList,
|
||||
sendRemoteBrowserList,
|
||||
};
|
||||
|
@@ -154,6 +154,7 @@ class Monitor extends BeanModel {
|
||||
kafkaProducerAllowAutoTopicCreation: this.getKafkaProducerAllowAutoTopicCreation(),
|
||||
kafkaProducerMessage: this.kafkaProducerMessage,
|
||||
screenshot,
|
||||
remote_browser: this.remote_browser,
|
||||
};
|
||||
|
||||
if (includeSensitiveData) {
|
||||
|
17
server/model/remote_browser.js
Normal file
17
server/model/remote_browser.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||
|
||||
class RemoteBrowser extends BeanModel {
|
||||
/**
|
||||
* Returns an object that ready to parse to JSON
|
||||
* @returns {object} Object ready to parse
|
||||
*/
|
||||
toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
url: this.url,
|
||||
name: this.name,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RemoteBrowser;
|
@@ -8,6 +8,7 @@ const path = require("path");
|
||||
const Database = require("../database");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const config = require("../config");
|
||||
const { RemoteBrowser } = require("../remote-browser");
|
||||
|
||||
let browser = null;
|
||||
|
||||
@@ -85,6 +86,19 @@ async function getBrowser() {
|
||||
return browser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current instance of the browser. If there isn't one, create it
|
||||
* @param {integer} remoteBrowserID Path to executable
|
||||
* @param {integer} userId User ID
|
||||
* @returns {Promise<Browser>} The browser
|
||||
*/
|
||||
async function getRemoteBrowser(remoteBrowserID, userId) {
|
||||
let remoteBrowser = await RemoteBrowser.get(remoteBrowserID, userId);
|
||||
log.debug("MONITOR", `Using remote browser: ${remoteBrowser.name} (${remoteBrowser.id})`);
|
||||
browser = chromium.connect(remoteBrowser.url);
|
||||
return browser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the chrome executable path
|
||||
* @param {string} executablePath Path to chrome executable
|
||||
@@ -191,11 +205,21 @@ async function testChrome(executablePath) {
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// test remote browser
|
||||
/**
|
||||
* TODO: connect remote browser? https://playwright.dev/docs/api/class-browsertype#browser-type-connect
|
||||
*
|
||||
* @param {string} remoteBrowserURL Remote Browser URL
|
||||
* @returns {Promise<boolean>} Returns if connection worked
|
||||
*/
|
||||
async function testRemoteBrowser(remoteBrowserURL) {
|
||||
try {
|
||||
const browser = await chromium.connect(remoteBrowserURL);
|
||||
browser.version();
|
||||
await browser.close();
|
||||
return true;
|
||||
} catch (e) {
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
class RealBrowserMonitorType extends MonitorType {
|
||||
|
||||
name = "real-browser";
|
||||
@@ -204,7 +228,7 @@ class RealBrowserMonitorType extends MonitorType {
|
||||
* @inheritdoc
|
||||
*/
|
||||
async check(monitor, heartbeat, server) {
|
||||
const browser = await getBrowser();
|
||||
const browser = monitor.remote_browser ? await getRemoteBrowser(monitor.remote_browser, monitor.user_id) : await getBrowser();
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
|
||||
@@ -237,4 +261,5 @@ module.exports = {
|
||||
RealBrowserMonitorType,
|
||||
testChrome,
|
||||
resetChrome,
|
||||
testRemoteBrowser,
|
||||
};
|
||||
|
84
server/remote-browser.js
Normal file
84
server/remote-browser.js
Normal file
@@ -0,0 +1,84 @@
|
||||
const { R } = require("redbean-node");
|
||||
const { testRemoteBrowser } = require("./monitor-types/real-browser-monitor-type.js");
|
||||
class RemoteBrowser {
|
||||
|
||||
/**
|
||||
* Gets remote browser from ID
|
||||
* @param {number} remoteBrowserID ID of the remote browser
|
||||
* @param {number} userID ID of the user who created the remote browser
|
||||
* @returns {Promise<Bean>} Remote Browser
|
||||
*/
|
||||
static async get(remoteBrowserID, userID) {
|
||||
let bean = await R.findOne("remote_browser", " id = ? AND user_id = ? ", [ remoteBrowserID, userID ]);
|
||||
|
||||
if (!bean) {
|
||||
throw new Error("Remote browser not found");
|
||||
}
|
||||
|
||||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a Remote Browser
|
||||
* @param {object} remoteBrowser Remote Browser to save
|
||||
* @param {?number} remoteBrowserID ID of the Remote Browser to update
|
||||
* @param {number} userID ID of the user who adds the Remote Browser
|
||||
* @returns {Promise<Bean>} Updated Remote Browser
|
||||
*/
|
||||
static async save(remoteBrowser, remoteBrowserID, userID) {
|
||||
let bean;
|
||||
|
||||
if (remoteBrowserID) {
|
||||
bean = await R.findOne("remote_browser", " id = ? AND user_id = ? ", [ remoteBrowserID, userID ]);
|
||||
|
||||
if (!bean) {
|
||||
throw new Error("Remote browser not found");
|
||||
}
|
||||
|
||||
} else {
|
||||
bean = R.dispense("remote_browser");
|
||||
}
|
||||
|
||||
bean.user_id = userID;
|
||||
bean.name = remoteBrowser.name;
|
||||
bean.url = remoteBrowser.url;
|
||||
|
||||
await R.store(bean);
|
||||
|
||||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a Remote Browser
|
||||
* @param {number} remoteBrowserID ID of the Remote Browser to delete
|
||||
* @param {number} userID ID of the user who created the Remote Browser
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async delete(remoteBrowserID, userID) {
|
||||
let bean = await R.findOne("remote_browser", " id = ? AND user_id = ? ", [ remoteBrowserID, userID ]);
|
||||
|
||||
if (!bean) {
|
||||
throw new Error("Remote Browser not found");
|
||||
}
|
||||
|
||||
// Delete removed remote browser from monitors if exists
|
||||
await R.exec("UPDATE monitor SET remote_browser = null WHERE remote_browser = ?", [ remoteBrowserID ]);
|
||||
|
||||
await R.trash(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the connection to Remote Browser
|
||||
* @param {object} remoteBrowser Docker host to check for
|
||||
* @returns {boolean} Returns if connection worked
|
||||
*/
|
||||
static async test(remoteBrowser) {
|
||||
const testResult = await testRemoteBrowser(remoteBrowser.id, remoteBrowser.user_id);
|
||||
return testResult;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
RemoteBrowser,
|
||||
};
|
@@ -131,9 +131,10 @@ const testMode = !!args["test"] || false;
|
||||
const e2eTestMode = !!args["e2e"] || false;
|
||||
|
||||
// Must be after io instantiation
|
||||
const { sendNotificationList, sendHeartbeatList, sendInfo, sendProxyList, sendDockerHostList, sendAPIKeyList } = require("./client");
|
||||
const { sendNotificationList, sendHeartbeatList, sendInfo, sendProxyList, sendDockerHostList, sendAPIKeyList, sendRemoteBrowserList } = require("./client");
|
||||
const { statusPageSocketHandler } = require("./socket-handlers/status-page-socket-handler");
|
||||
const databaseSocketHandler = require("./socket-handlers/database-socket-handler");
|
||||
const { remoteBrowserSocketHandler } = require("./socket-handlers/remote-browser-socket-handler");
|
||||
const TwoFA = require("./2fa");
|
||||
const StatusPage = require("./model/status_page");
|
||||
const { cloudflaredSocketHandler, autoStart: cloudflaredAutoStart, stop: cloudflaredStop } = require("./socket-handlers/cloudflared-socket-handler");
|
||||
@@ -827,6 +828,7 @@ let needSetup = false;
|
||||
bean.kafkaProducerAllowAutoTopicCreation =
|
||||
monitor.kafkaProducerAllowAutoTopicCreation;
|
||||
bean.gamedigGivenPortOnly = monitor.gamedigGivenPortOnly;
|
||||
bean.remote_browser = monitor.remote_browser;
|
||||
|
||||
bean.validate();
|
||||
|
||||
@@ -1508,6 +1510,7 @@ let needSetup = false;
|
||||
dockerSocketHandler(socket);
|
||||
maintenanceSocketHandler(socket);
|
||||
apiKeySocketHandler(socket);
|
||||
remoteBrowserSocketHandler(socket);
|
||||
generalSocketHandler(socket, server);
|
||||
|
||||
log.debug("server", "added all socket handlers");
|
||||
@@ -1616,6 +1619,7 @@ async function afterLogin(socket, user) {
|
||||
sendProxyList(socket);
|
||||
sendDockerHostList(socket);
|
||||
sendAPIKeyList(socket);
|
||||
sendRemoteBrowserList(socket);
|
||||
|
||||
await sleep(500);
|
||||
|
||||
|
82
server/socket-handlers/remote-browser-socket-handler.js
Normal file
82
server/socket-handlers/remote-browser-socket-handler.js
Normal file
@@ -0,0 +1,82 @@
|
||||
const { sendRemoteBrowserList } = require("../client");
|
||||
const { checkLogin } = require("../util-server");
|
||||
const { RemoteBrowser } = require("../remote-browser");
|
||||
|
||||
const { log } = require("../../src/util");
|
||||
const { testRemoteBrowser } = require("../monitor-types/real-browser-monitor-type");
|
||||
|
||||
/**
|
||||
* Handlers for docker hosts
|
||||
* @param {Socket} socket Socket.io instance
|
||||
* @returns {void}
|
||||
*/
|
||||
module.exports.remoteBrowserSocketHandler = (socket) => {
|
||||
socket.on("addRemoteBrowser", async (remoteBrowser, remoteBrowserID, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
let remoteBrowserBean = await RemoteBrowser.save(remoteBrowser, remoteBrowserID, socket.userID);
|
||||
await sendRemoteBrowserList(socket);
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Saved.",
|
||||
msgi18n: true,
|
||||
id: remoteBrowserBean.id,
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("deleteRemoteBrowser", async (dockerHostID, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
await RemoteBrowser.delete(dockerHostID, socket.userID);
|
||||
await sendRemoteBrowserList(socket);
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "successDeleted",
|
||||
msgi18n: true,
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("testRemoteBrowser", async (remoteBrowser, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
let check = await testRemoteBrowser(remoteBrowser.url);
|
||||
log.info("remoteBrowser", "Tested remote browser: " + check);
|
||||
let msg;
|
||||
|
||||
if (check) {
|
||||
msg = "Connected Successfully.";
|
||||
}
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
msg,
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
log.error("remoteBrowser", e);
|
||||
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
Reference in New Issue
Block a user