Merge pull request #1461 from louislam/proxy-improvement

Proxy Improvements
pull/1415/head
Louis Lam 3 years ago committed by GitHub
commit 4fb2c69dd1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,7 +1,5 @@
console.log("== Uptime Kuma Reset Password Tool =="); console.log("== Uptime Kuma Reset Password Tool ==");
console.log("Loading the database");
const Database = require("../server/database"); const Database = require("../server/database");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const readline = require("readline"); const readline = require("readline");
@ -13,8 +11,9 @@ const rl = readline.createInterface({
}); });
const main = async () => { const main = async () => {
console.log("Connecting the database");
Database.init(args); Database.init(args);
await Database.connect(); await Database.connect(false, false, true);
try { try {
// No need to actually reset the password for testing, just make sure no connection problem. It is ok for now. // No need to actually reset the password for testing, just make sure no connection problem. It is ok for now.

@ -83,7 +83,7 @@ class Database {
console.log(`Data Dir: ${Database.dataDir}`); console.log(`Data Dir: ${Database.dataDir}`);
} }
static async connect(testMode = false) { static async connect(testMode = false, autoloadModels = true, noLog = false) {
const acquireConnectionTimeout = 120 * 1000; const acquireConnectionTimeout = 120 * 1000;
const Dialect = require("knex/lib/dialects/sqlite3/index.js"); const Dialect = require("knex/lib/dialects/sqlite3/index.js");
@ -113,7 +113,10 @@ class Database {
// Auto map the model to a bean object // Auto map the model to a bean object
R.freeze(true); R.freeze(true);
await R.autoloadModels("./server/model");
if (autoloadModels) {
await R.autoloadModels("./server/model");
}
await R.exec("PRAGMA foreign_keys = ON"); await R.exec("PRAGMA foreign_keys = ON");
if (testMode) { if (testMode) {
@ -126,10 +129,12 @@ class Database {
await R.exec("PRAGMA cache_size = -12000"); await R.exec("PRAGMA cache_size = -12000");
await R.exec("PRAGMA auto_vacuum = FULL"); await R.exec("PRAGMA auto_vacuum = FULL");
console.log("SQLite config:"); if (!noLog) {
console.log(await R.getAll("PRAGMA journal_mode")); console.log("SQLite config:");
console.log(await R.getAll("PRAGMA cache_size")); console.log(await R.getAll("PRAGMA journal_mode"));
console.log("SQLite Version: " + await R.getCell("SELECT sqlite_version()")); console.log(await R.getAll("PRAGMA cache_size"));
console.log("SQLite Version: " + await R.getCell("SELECT sqlite_version()"));
}
} }
static async patch() { static async patch() {

@ -3,6 +3,7 @@ const HttpProxyAgent = require("http-proxy-agent");
const HttpsProxyAgent = require("https-proxy-agent"); const HttpsProxyAgent = require("https-proxy-agent");
const SocksProxyAgent = require("socks-proxy-agent"); const SocksProxyAgent = require("socks-proxy-agent");
const { debug } = require("../src/util"); const { debug } = require("../src/util");
const server = require("./server");
class Proxy { class Proxy {
@ -144,6 +145,22 @@ class Proxy {
httpsAgent httpsAgent
}; };
} }
/**
* Reload proxy settings for current monitors
* @returns {Promise<void>}
*/
static async reloadProxy() {
let updatedList = await R.getAssoc("SELECT id, proxy_id FROM monitor");
for (let monitorID in server.monitorList) {
let monitor = server.monitorList[monitorID];
if (updatedList[monitorID]) {
monitor.proxy_id = updatedList[monitorID].proxy_id;
}
}
}
} }
/** /**

@ -48,6 +48,27 @@ debug("Importing 2FA Modules");
const notp = require("notp"); const notp = require("notp");
const base32 = require("thirty-two"); const base32 = require("thirty-two");
/**
* `module.exports` (alias: `server`) should be inside this class, in order to avoid circular dependency issue.
* @type {UptimeKumaServer}
*/
class UptimeKumaServer {
/**
* Main monitor list
* @type {{}}
*/
monitorList = {};
entryPage = "dashboard";
async sendMonitorList(socket) {
let list = await getMonitorJSONList(socket.userID);
io.to(socket.userID).emit("monitorList", list);
return list;
}
}
const server = module.exports = new UptimeKumaServer();
console.log("Importing this project modules"); console.log("Importing this project modules");
debug("Importing Monitor"); debug("Importing Monitor");
const Monitor = require("./model/monitor"); const Monitor = require("./model/monitor");
@ -115,20 +136,20 @@ if (config.demoMode) {
console.log("Creating express and socket.io instance"); console.log("Creating express and socket.io instance");
const app = express(); const app = express();
let server; let httpServer;
if (sslKey && sslCert) { if (sslKey && sslCert) {
console.log("Server Type: HTTPS"); console.log("Server Type: HTTPS");
server = https.createServer({ httpServer = https.createServer({
key: fs.readFileSync(sslKey), key: fs.readFileSync(sslKey),
cert: fs.readFileSync(sslCert) cert: fs.readFileSync(sslCert)
}, app); }, app);
} else { } else {
console.log("Server Type: HTTP"); console.log("Server Type: HTTP");
server = http.createServer(app); httpServer = http.createServer(app);
} }
const io = new Server(server); const io = new Server(httpServer);
module.exports.io = io; module.exports.io = io;
// Must be after io instantiation // Must be after io instantiation
@ -138,6 +159,7 @@ const databaseSocketHandler = require("./socket-handlers/database-socket-handler
const TwoFA = require("./2fa"); const TwoFA = require("./2fa");
const StatusPage = require("./model/status_page"); const StatusPage = require("./model/status_page");
const { cloudflaredSocketHandler, autoStart: cloudflaredAutoStart, stop: cloudflaredStop } = require("./socket-handlers/cloudflared-socket-handler"); const { cloudflaredSocketHandler, autoStart: cloudflaredAutoStart, stop: cloudflaredStop } = require("./socket-handlers/cloudflared-socket-handler");
const { proxySocketHandler } = require("./socket-handlers/proxy-socket-handler");
app.use(express.json()); app.use(express.json());
@ -162,12 +184,6 @@ let totalClient = 0;
*/ */
let jwtSecret = null; let jwtSecret = null;
/**
* Main monitor list
* @type {{}}
*/
let monitorList = {};
/** /**
* Show Setup Page * Show Setup Page
* @type {boolean} * @type {boolean}
@ -190,8 +206,6 @@ try {
} }
} }
exports.entryPage = "dashboard";
(async () => { (async () => {
Database.init(args); Database.init(args);
await initDatabase(testMode); await initDatabase(testMode);
@ -600,7 +614,7 @@ exports.entryPage = "dashboard";
await updateMonitorNotification(bean.id, notificationIDList); await updateMonitorNotification(bean.id, notificationIDList);
await sendMonitorList(socket); await server.sendMonitorList(socket);
await startMonitor(socket.userID, bean.id); await startMonitor(socket.userID, bean.id);
callback({ callback({
@ -629,7 +643,7 @@ exports.entryPage = "dashboard";
} }
// Reset Prometheus labels // Reset Prometheus labels
monitorList[monitor.id]?.prometheus()?.remove(); server.monitorList[monitor.id]?.prometheus()?.remove();
bean.name = monitor.name; bean.name = monitor.name;
bean.type = monitor.type; bean.type = monitor.type;
@ -663,7 +677,7 @@ exports.entryPage = "dashboard";
await restartMonitor(socket.userID, bean.id); await restartMonitor(socket.userID, bean.id);
} }
await sendMonitorList(socket); await server.sendMonitorList(socket);
callback({ callback({
ok: true, ok: true,
@ -683,7 +697,7 @@ exports.entryPage = "dashboard";
socket.on("getMonitorList", async (callback) => { socket.on("getMonitorList", async (callback) => {
try { try {
checkLogin(socket); checkLogin(socket);
await sendMonitorList(socket); await server.sendMonitorList(socket);
callback({ callback({
ok: true, ok: true,
}); });
@ -757,7 +771,7 @@ exports.entryPage = "dashboard";
try { try {
checkLogin(socket); checkLogin(socket);
await startMonitor(socket.userID, monitorID); await startMonitor(socket.userID, monitorID);
await sendMonitorList(socket); await server.sendMonitorList(socket);
callback({ callback({
ok: true, ok: true,
@ -776,7 +790,7 @@ exports.entryPage = "dashboard";
try { try {
checkLogin(socket); checkLogin(socket);
await pauseMonitor(socket.userID, monitorID); await pauseMonitor(socket.userID, monitorID);
await sendMonitorList(socket); await server.sendMonitorList(socket);
callback({ callback({
ok: true, ok: true,
@ -797,9 +811,9 @@ exports.entryPage = "dashboard";
console.log(`Delete Monitor: ${monitorID} User ID: ${socket.userID}`); console.log(`Delete Monitor: ${monitorID} User ID: ${socket.userID}`);
if (monitorID in monitorList) { if (monitorID in server.monitorList) {
monitorList[monitorID].stop(); server.monitorList[monitorID].stop();
delete monitorList[monitorID]; delete server.monitorList[monitorID];
} }
await R.exec("DELETE FROM monitor WHERE id = ? AND user_id = ? ", [ await R.exec("DELETE FROM monitor WHERE id = ? AND user_id = ? ", [
@ -812,7 +826,7 @@ exports.entryPage = "dashboard";
msg: "Deleted Successfully.", msg: "Deleted Successfully.",
}); });
await sendMonitorList(socket); await server.sendMonitorList(socket);
// Clear heartbeat list on client // Clear heartbeat list on client
await sendImportantHeartbeatList(socket, monitorID, true, true); await sendImportantHeartbeatList(socket, monitorID, true, true);
@ -1112,52 +1126,6 @@ exports.entryPage = "dashboard";
} }
}); });
socket.on("addProxy", async (proxy, proxyID, callback) => {
try {
checkLogin(socket);
const proxyBean = await Proxy.save(proxy, proxyID, socket.userID);
await sendProxyList(socket);
if (proxy.applyExisting) {
await restartMonitors(socket.userID);
}
callback({
ok: true,
msg: "Saved",
id: proxyBean.id,
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});
socket.on("deleteProxy", async (proxyID, callback) => {
try {
checkLogin(socket);
await Proxy.delete(proxyID, socket.userID);
await sendProxyList(socket);
await restartMonitors(socket.userID);
callback({
ok: true,
msg: "Deleted",
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});
socket.on("checkApprise", async (callback) => { socket.on("checkApprise", async (callback) => {
try { try {
checkLogin(socket); checkLogin(socket);
@ -1184,8 +1152,8 @@ exports.entryPage = "dashboard";
// If the import option is "overwrite" it'll clear most of the tables, except "settings" and "user" // If the import option is "overwrite" it'll clear most of the tables, except "settings" and "user"
if (importHandle == "overwrite") { if (importHandle == "overwrite") {
// Stops every monitor first, so it doesn't execute any heartbeat while importing // Stops every monitor first, so it doesn't execute any heartbeat while importing
for (let id in monitorList) { for (let id in server.monitorList) {
let monitor = monitorList[id]; let monitor = server.monitorList[id];
await monitor.stop(); await monitor.stop();
} }
await R.exec("DELETE FROM heartbeat"); await R.exec("DELETE FROM heartbeat");
@ -1348,7 +1316,7 @@ exports.entryPage = "dashboard";
} }
await sendNotificationList(socket); await sendNotificationList(socket);
await sendMonitorList(socket); await server.sendMonitorList(socket);
} }
callback({ callback({
@ -1438,6 +1406,7 @@ exports.entryPage = "dashboard";
statusPageSocketHandler(socket); statusPageSocketHandler(socket);
cloudflaredSocketHandler(socket); cloudflaredSocketHandler(socket);
databaseSocketHandler(socket); databaseSocketHandler(socket);
proxySocketHandler(socket);
debug("added all socket handlers"); debug("added all socket handlers");
@ -1458,12 +1427,12 @@ exports.entryPage = "dashboard";
console.log("Init the server"); console.log("Init the server");
server.once("error", async (err) => { httpServer.once("error", async (err) => {
console.error("Cannot listen: " + err.message); console.error("Cannot listen: " + err.message);
await shutdownFunction(); await shutdownFunction();
}); });
server.listen(port, hostname, () => { httpServer.listen(port, hostname, () => {
if (hostname) { if (hostname) {
console.log(`Listening on ${hostname}:${port}`); console.log(`Listening on ${hostname}:${port}`);
} else { } else {
@ -1510,17 +1479,11 @@ async function checkOwner(userID, monitorID) {
} }
} }
async function sendMonitorList(socket) {
let list = await getMonitorJSONList(socket.userID);
io.to(socket.userID).emit("monitorList", list);
return list;
}
async function afterLogin(socket, user) { async function afterLogin(socket, user) {
socket.userID = user.id; socket.userID = user.id;
socket.join(user.id); socket.join(user.id);
let monitorList = await sendMonitorList(socket); let monitorList = await server.sendMonitorList(socket);
sendNotificationList(socket); sendNotificationList(socket);
sendProxyList(socket); sendProxyList(socket);
@ -1603,11 +1566,11 @@ async function startMonitor(userID, monitorID) {
monitorID, monitorID,
]); ]);
if (monitor.id in monitorList) { if (monitor.id in server.monitorList) {
monitorList[monitor.id].stop(); server.monitorList[monitor.id].stop();
} }
monitorList[monitor.id] = monitor; server.monitorList[monitor.id] = monitor;
monitor.start(io); monitor.start(io);
} }
@ -1615,19 +1578,6 @@ async function restartMonitor(userID, monitorID) {
return await startMonitor(userID, monitorID); return await startMonitor(userID, monitorID);
} }
async function restartMonitors(userID) {
// Fetch all active monitors for user
const monitors = await R.getAll("SELECT id FROM monitor WHERE active = 1 AND user_id = ?", [userID]);
for (const monitor of monitors) {
// Start updated monitor
await startMonitor(userID, monitor.id);
// Give some delays, so all monitors won't make request at the same moment when just start the server.
await sleep(getRandomInt(300, 1000));
}
}
async function pauseMonitor(userID, monitorID) { async function pauseMonitor(userID, monitorID) {
await checkOwner(userID, monitorID); await checkOwner(userID, monitorID);
@ -1638,8 +1588,8 @@ async function pauseMonitor(userID, monitorID) {
userID, userID,
]); ]);
if (monitorID in monitorList) { if (monitorID in server.monitorList) {
monitorList[monitorID].stop(); server.monitorList[monitorID].stop();
} }
} }
@ -1650,7 +1600,7 @@ async function startMonitors() {
let list = await R.find("monitor", " active = 1 "); let list = await R.find("monitor", " active = 1 ");
for (let monitor of list) { for (let monitor of list) {
monitorList[monitor.id] = monitor; server.monitorList[monitor.id] = monitor;
} }
for (let monitor of list) { for (let monitor of list) {
@ -1665,8 +1615,8 @@ async function shutdownFunction(signal) {
console.log("Called signal: " + signal); console.log("Called signal: " + signal);
console.log("Stopping all monitors"); console.log("Stopping all monitors");
for (let id in monitorList) { for (let id in server.monitorList) {
let monitor = monitorList[id]; let monitor = server.monitorList[id];
monitor.stop(); monitor.stop();
} }
await sleep(2000); await sleep(2000);
@ -1680,7 +1630,7 @@ function finalFunction() {
console.log("Graceful shutdown successful!"); console.log("Graceful shutdown successful!");
} }
gracefulShutdown(server, { gracefulShutdown(httpServer, {
signals: "SIGINT SIGTERM", signals: "SIGINT SIGTERM",
timeout: 30000, // timeout: 30 secs timeout: 30000, // timeout: 30 secs
development: false, // not in dev mode development: false, // not in dev mode

@ -0,0 +1,53 @@
const { checkLogin } = require("../util-server");
const { Proxy } = require("../proxy");
const { sendProxyList } = require("../client");
const server = require("../server");
module.exports.proxySocketHandler = (socket) => {
socket.on("addProxy", async (proxy, proxyID, callback) => {
try {
checkLogin(socket);
const proxyBean = await Proxy.save(proxy, proxyID, socket.userID);
await sendProxyList(socket);
if (proxy.applyExisting) {
await Proxy.reloadProxy();
await server.sendMonitorList(socket);
}
callback({
ok: true,
msg: "Saved",
id: proxyBean.id,
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});
socket.on("deleteProxy", async (proxyID, callback) => {
try {
checkLogin(socket);
await Proxy.delete(proxyID, socket.userID);
await sendProxyList(socket);
await Proxy.reloadProxy();
callback({
ok: true,
msg: "Deleted",
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});
};

@ -232,31 +232,33 @@
</button> </button>
<!-- Proxies --> <!-- Proxies -->
<h2 class="mt-5 mb-2">{{ $t("Proxies") }}</h2> <div v-if="monitor.type === 'http' || monitor.type === 'keyword'">
<p v-if="$root.proxyList.length === 0"> <h2 class="mt-5 mb-2">{{ $t("Proxy") }}</h2>
{{ $t("Not available, please setup.") }} <p v-if="$root.proxyList.length === 0">
</p> {{ $t("Not available, please setup.") }}
</p>
<div v-if="$root.proxyList.length > 0" class="form-check my-3">
<input id="proxy-disable" v-model="monitor.proxyId" :value="null" name="proxy" class="form-check-input" type="radio">
<label class="form-check-label" for="proxy-disable">{{ $t("No Proxy") }}</label>
</div>
<div v-if="$root.proxyList.length > 0" class="form-check form-switch my-3"> <div v-for="proxy in $root.proxyList" :key="proxy.id" class="form-check my-3">
<input id="proxy-disable" v-model="monitor.proxyId" :value="null" name="proxy" class="form-check-input" type="radio"> <input :id="`proxy-${proxy.id}`" v-model="monitor.proxyId" :value="proxy.id" name="proxy" class="form-check-input" type="radio">
<label class="form-check-label" for="proxy-disable">{{ $t("No Proxy") }}</label>
</div>
<div v-for="proxy in $root.proxyList" :key="proxy.id" class="form-check form-switch my-3"> <label class="form-check-label" :for="`proxy-${proxy.id}`">
<input :id="`proxy-${proxy.id}`" v-model="monitor.proxyId" :value="proxy.id" name="proxy" class="form-check-input" type="radio"> {{ proxy.host }}:{{ proxy.port }} ({{ proxy.protocol }})
<a href="#" @click="$refs.proxyDialog.show(proxy.id)">{{ $t("Edit") }}</a>
</label>
<label class="form-check-label" :for="`proxy-${proxy.id}`"> <span v-if="proxy.default === true" class="badge bg-primary ms-2">{{ $t("default") }}</span>
{{ proxy.host }}:{{ proxy.port }} ({{ proxy.protocol }}) </div>
<a href="#" @click="$refs.proxyDialog.show(proxy.id)">{{ $t("Edit") }}</a>
</label>
<span v-if="proxy.default === true" class="badge bg-primary ms-2">{{ $t("default") }}</span> <button class="btn btn-primary me-2" type="button" @click="$refs.proxyDialog.show()">
{{ $t("Setup Proxy") }}
</button>
</div> </div>
<button class="btn btn-primary me-2" type="button" @click="$refs.proxyDialog.show()">
{{ $t("Setup Proxy") }}
</button>
<!-- HTTP Options --> <!-- HTTP Options -->
<template v-if="monitor.type === 'http' || monitor.type === 'keyword' "> <template v-if="monitor.type === 'http' || monitor.type === 'keyword' ">
<h2 class="mt-5 mb-2">{{ $t("HTTP Options") }}</h2> <h2 class="mt-5 mb-2">{{ $t("HTTP Options") }}</h2>

Loading…
Cancel
Save