mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-12-24 22:14:47 +02:00
Add http and https proxy feature
Added new proxy feature based on http and https proxy agents. Proxy feature works like notifications, there is many proxy could be related one proxy entry. Supported features - Proxies can activate and disable in bulk - Proxies auto enabled by default for new monitors - Proxies could be applied in bulk to current monitors - Both authenticated and anonymous proxies supported - Export and import support for proxies
This commit is contained in:
parent
2cc7a990ff
commit
78d76512ba
23
db/patch-proxy.sql
Normal file
23
db/patch-proxy.sql
Normal file
@ -0,0 +1,23 @@
|
||||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE proxy (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INT NOT NULL,
|
||||
protocol VARCHAR(10) NOT NULL,
|
||||
host VARCHAR(255) NOT NULL,
|
||||
port SMALLINT NOT NULL,
|
||||
auth BOOLEAN NOT NULL,
|
||||
username VARCHAR(255) NULL,
|
||||
password VARCHAR(255) NULL,
|
||||
active BOOLEAN NOT NULL DEFAULT 1,
|
||||
'default' BOOLEAN NOT NULL DEFAULT 0,
|
||||
created_date DATETIME DEFAULT (DATETIME('now')) NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE monitor ADD COLUMN proxy_id INTEGER REFERENCES proxy(id);
|
||||
|
||||
CREATE INDEX proxy_id ON monitor (proxy_id);
|
||||
CREATE INDEX proxy_user_id ON proxy (user_id);
|
||||
|
||||
COMMIT;
|
@ -83,6 +83,23 @@ async function sendImportantHeartbeatList(socket, monitorID, toUser = false, ove
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delivers proxy list
|
||||
*
|
||||
* @param socket
|
||||
* @return {Promise<Bean[]>}
|
||||
*/
|
||||
async function sendProxyList(socket) {
|
||||
const timeLogger = new TimeLogger();
|
||||
|
||||
const list = await R.find("proxy", " user_id = ? ", [socket.userID]);
|
||||
io.to(socket.userID).emit("proxyList", list.map(bean => bean.export()));
|
||||
|
||||
timeLogger.print("Send Proxy List");
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
async function sendInfo(socket) {
|
||||
socket.emit("info", {
|
||||
version: checkVersion.version,
|
||||
@ -95,6 +112,6 @@ module.exports = {
|
||||
sendNotificationList,
|
||||
sendImportantHeartbeatList,
|
||||
sendHeartbeatList,
|
||||
sendInfo
|
||||
sendProxyList,
|
||||
sendInfo,
|
||||
};
|
||||
|
||||
|
@ -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-proxy.sql": true,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,4 +1,6 @@
|
||||
const https = require("https");
|
||||
const HttpProxyAgent = require("http-proxy-agent");
|
||||
const HttpsProxyAgent = require("https-proxy-agent");
|
||||
const dayjs = require("dayjs");
|
||||
const utc = require("dayjs/plugin/utc");
|
||||
let timezone = require("dayjs/plugin/timezone");
|
||||
@ -77,6 +79,7 @@ class Monitor extends BeanModel {
|
||||
dns_resolve_server: this.dns_resolve_server,
|
||||
dns_last_result: this.dns_last_result,
|
||||
pushToken: this.pushToken,
|
||||
proxyId: this.proxy_id,
|
||||
notificationIDList,
|
||||
tags: tags,
|
||||
};
|
||||
@ -173,6 +176,11 @@ class Monitor extends BeanModel {
|
||||
};
|
||||
}
|
||||
|
||||
const httpsAgentOptions = {
|
||||
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
|
||||
rejectUnauthorized: !this.getIgnoreTls(),
|
||||
};
|
||||
|
||||
debug(`[${this.name}] Prepare Options for axios`);
|
||||
|
||||
const options = {
|
||||
@ -186,17 +194,51 @@ class Monitor extends BeanModel {
|
||||
...(this.headers ? JSON.parse(this.headers) : {}),
|
||||
...(basicAuthHeader),
|
||||
},
|
||||
httpsAgent: new https.Agent({
|
||||
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
|
||||
rejectUnauthorized: ! this.getIgnoreTls(),
|
||||
}),
|
||||
maxRedirects: this.maxredirects,
|
||||
validateStatus: (status) => {
|
||||
return checkStatusCode(status, this.getAcceptedStatuscodes());
|
||||
},
|
||||
};
|
||||
|
||||
if (this.proxy_id) {
|
||||
const proxy = await R.load("proxy", this.proxy_id);
|
||||
|
||||
if (proxy && proxy.active) {
|
||||
const httpProxyAgentOptions = {
|
||||
protocol: proxy.protocol,
|
||||
host: proxy.host,
|
||||
port: proxy.port,
|
||||
};
|
||||
const httpsProxyAgentOptions = {
|
||||
...httpsAgentOptions,
|
||||
protocol: proxy.protocol,
|
||||
hostname: proxy.host,
|
||||
port: proxy.port,
|
||||
};
|
||||
|
||||
if (proxy.auth) {
|
||||
httpProxyAgentOptions.auth = `${proxy.username}:${proxy.password}`;
|
||||
httpsProxyAgentOptions.auth = `${proxy.username}:${proxy.password}`;
|
||||
}
|
||||
|
||||
debug(`[${this.name}] HTTP options: ${JSON.stringify({
|
||||
"http": httpProxyAgentOptions,
|
||||
"https": httpsProxyAgentOptions,
|
||||
})}`);
|
||||
|
||||
options.proxy = false;
|
||||
options.httpAgent = new HttpProxyAgent(httpProxyAgentOptions);
|
||||
options.httpsAgent = new HttpsProxyAgent(httpsProxyAgentOptions);
|
||||
}
|
||||
}
|
||||
|
||||
if (!options.httpsAgent) {
|
||||
options.httpsAgent = new https.Agent(httpsAgentOptions);
|
||||
}
|
||||
|
||||
debug(`[${this.name}] Axios Options: ${JSON.stringify(options)}`);
|
||||
debug(`[${this.name}] Axios Request`);
|
||||
|
||||
let res = await axios.request(options);
|
||||
bean.msg = `${res.status} - ${res.statusText}`;
|
||||
bean.ping = dayjs().valueOf() - startTime;
|
||||
|
21
server/model/proxy.js
Normal file
21
server/model/proxy.js
Normal file
@ -0,0 +1,21 @@
|
||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||
|
||||
class Proxy extends BeanModel {
|
||||
toJSON() {
|
||||
return {
|
||||
id: this._id,
|
||||
userId: this._user_id,
|
||||
protocol: this._protocol,
|
||||
host: this._host,
|
||||
port: this._port,
|
||||
auth: !!this._auth,
|
||||
username: this._username,
|
||||
password: this._password,
|
||||
active: !!this._active,
|
||||
default: !!this._default,
|
||||
createdDate: this._created_date,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Proxy;
|
99
server/proxy.js
Normal file
99
server/proxy.js
Normal file
@ -0,0 +1,99 @@
|
||||
const { R } = require("redbean-node");
|
||||
|
||||
class Proxy {
|
||||
|
||||
/**
|
||||
* Saves and updates given proxy entity
|
||||
*
|
||||
* @param proxy
|
||||
* @param proxyID
|
||||
* @param userID
|
||||
* @return {Promise<Bean>}
|
||||
*/
|
||||
static async save(proxy, proxyID, userID) {
|
||||
let bean;
|
||||
|
||||
if (proxyID) {
|
||||
bean = await R.findOne("proxy", " id = ? AND user_id = ? ", [proxyID, userID]);
|
||||
|
||||
if (!bean) {
|
||||
throw new Error("proxy not found");
|
||||
}
|
||||
|
||||
} else {
|
||||
bean = R.dispense("proxy");
|
||||
}
|
||||
|
||||
// Make sure given proxy protocol is supported
|
||||
if (!["http", "https"].includes(proxy.protocol)) {
|
||||
throw new Error(`Unsupported proxy protocol "${proxy.protocol}. Supported protocols are http and https."`);
|
||||
}
|
||||
|
||||
// When proxy is default update deactivate old default proxy
|
||||
if (proxy.default) {
|
||||
await R.exec("UPDATE proxy SET `default` = 0 WHERE `default` = 1");
|
||||
}
|
||||
|
||||
bean.user_id = userID;
|
||||
bean.protocol = proxy.protocol;
|
||||
bean.host = proxy.host;
|
||||
bean.port = proxy.port;
|
||||
bean.auth = proxy.auth;
|
||||
bean.username = proxy.username;
|
||||
bean.password = proxy.password;
|
||||
bean.active = proxy.active || true;
|
||||
bean.default = proxy.default || false;
|
||||
|
||||
await R.store(bean);
|
||||
|
||||
if (proxy.applyExisting) {
|
||||
await applyProxyEveryMonitor(bean.id, userID);
|
||||
}
|
||||
|
||||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes proxy with given id and removes it from monitors
|
||||
*
|
||||
* @param proxyID
|
||||
* @param userID
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
static async delete(proxyID, userID) {
|
||||
const bean = await R.findOne("proxy", " id = ? AND user_id = ? ", [proxyID, userID]);
|
||||
|
||||
if (!bean) {
|
||||
throw new Error("proxy not found");
|
||||
}
|
||||
|
||||
// Delete removed proxy from monitors if exists
|
||||
await R.exec("UPDATE monitor SET proxy_id = null WHERE proxy_id = ?", [proxyID]);
|
||||
|
||||
// Delete proxy from list
|
||||
await R.trash(bean);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies given proxy id to monitors
|
||||
*
|
||||
* @param proxyID
|
||||
* @param userID
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async function applyProxyEveryMonitor(proxyID, userID) {
|
||||
// Find all monitors with id and proxy id
|
||||
const monitors = await R.getAll("SELECT id, proxy_id FROM monitor WHERE user_id = ?", [userID]);
|
||||
|
||||
// Update proxy id not match with given proxy id
|
||||
for (const monitor of monitors) {
|
||||
if (monitor.proxy_id !== proxyID) {
|
||||
await R.exec("UPDATE monitor SET proxy_id = ? WHERE id = ?", [proxyID, monitor.id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Proxy,
|
||||
};
|
@ -58,6 +58,9 @@ debug("Importing Notification");
|
||||
const { Notification } = require("./notification");
|
||||
Notification.init();
|
||||
|
||||
debug("Importing Proxy");
|
||||
const { Proxy } = require("./proxy");
|
||||
|
||||
debug("Importing Database");
|
||||
const Database = require("./database");
|
||||
|
||||
@ -128,7 +131,7 @@ const io = new Server(server);
|
||||
module.exports.io = io;
|
||||
|
||||
// Must be after io instantiation
|
||||
const { sendNotificationList, sendHeartbeatList, sendImportantHeartbeatList, sendInfo } = require("./client");
|
||||
const { sendNotificationList, sendHeartbeatList, sendImportantHeartbeatList, sendInfo, sendProxyList } = require("./client");
|
||||
const { statusPageSocketHandler } = require("./socket-handlers/status-page-socket-handler");
|
||||
const databaseSocketHandler = require("./socket-handlers/database-socket-handler");
|
||||
const TwoFA = require("./2fa");
|
||||
@ -599,6 +602,7 @@ exports.entryPage = "dashboard";
|
||||
bean.dns_resolve_type = monitor.dns_resolve_type;
|
||||
bean.dns_resolve_server = monitor.dns_resolve_server;
|
||||
bean.pushToken = monitor.pushToken;
|
||||
bean.proxyId = Number.isInteger(monitor.proxyId) ? monitor.proxyId : null;
|
||||
|
||||
await R.store(bean);
|
||||
|
||||
@ -1061,6 +1065,52 @@ 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) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
@ -1079,6 +1129,7 @@ exports.entryPage = "dashboard";
|
||||
console.log(`Importing Backup, User ID: ${socket.userID}, Version: ${backupData.version}`);
|
||||
|
||||
let notificationListData = backupData.notificationList;
|
||||
let proxyListData = backupData.proxyList;
|
||||
let monitorListData = backupData.monitorList;
|
||||
|
||||
let version17x = compareVersions.compare(backupData.version, "1.7.0", ">=");
|
||||
@ -1097,6 +1148,7 @@ exports.entryPage = "dashboard";
|
||||
await R.exec("DELETE FROM monitor_tag");
|
||||
await R.exec("DELETE FROM tag");
|
||||
await R.exec("DELETE FROM monitor");
|
||||
await R.exec("DELETE FROM proxy");
|
||||
}
|
||||
|
||||
// Only starts importing if the backup file contains at least one notification
|
||||
@ -1116,6 +1168,24 @@ exports.entryPage = "dashboard";
|
||||
}
|
||||
}
|
||||
|
||||
// Only starts importing if the backup file contains at least one proxy
|
||||
if (proxyListData.length >= 1) {
|
||||
const proxies = await R.findAll("proxy");
|
||||
|
||||
// Loop over proxy list and save proxies
|
||||
for (const proxy of proxyListData) {
|
||||
const exists = proxies.find(item => item.id === proxy.id);
|
||||
|
||||
// Do not process when proxy already exists in import handle is skip and keep
|
||||
if (["skip", "keep"].includes(importHandle) && !exists) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save proxy as new entry if exists update exists one
|
||||
await Proxy.save(proxy, exists ? proxy.id : undefined, proxy.userId);
|
||||
}
|
||||
}
|
||||
|
||||
// Only starts importing if the backup file contains at least one monitor
|
||||
if (monitorListData.length >= 1) {
|
||||
// Get every existing monitor name and puts them in one simple string
|
||||
@ -1165,6 +1235,7 @@ exports.entryPage = "dashboard";
|
||||
dns_resolve_type: monitorListData[i].dns_resolve_type,
|
||||
dns_resolve_server: monitorListData[i].dns_resolve_server,
|
||||
notificationIDList: {},
|
||||
proxy_id: monitorListData[i].proxy_id || null,
|
||||
};
|
||||
|
||||
if (monitorListData[i].pushToken) {
|
||||
@ -1400,6 +1471,7 @@ async function afterLogin(socket, user) {
|
||||
|
||||
let monitorList = await sendMonitorList(socket);
|
||||
sendNotificationList(socket);
|
||||
sendProxyList(socket);
|
||||
|
||||
await sleep(500);
|
||||
|
||||
@ -1490,6 +1562,19 @@ async function restartMonitor(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) {
|
||||
await checkOwner(userID, monitorID);
|
||||
|
||||
|
203
src/components/ProxyDialog.vue
Normal file
203
src/components/ProxyDialog.vue
Normal file
@ -0,0 +1,203 @@
|
||||
<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 Proxy") }}
|
||||
</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="proxy-protocol" class="form-label">{{ $t("Proxy Protocol") }}</label>
|
||||
<select id="proxy-protocol" v-model="proxy.protocol" class="form-select">
|
||||
<option value="https">HTTPS</option>
|
||||
<option value="http">HTTP</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="proxy-host" class="form-label">{{ $t("Proxy Server") }}</label>
|
||||
<div class="d-flex">
|
||||
<input id="proxy-host" v-model="proxy.host" type="text" class="form-control" required :placeholder="$t('Server Address')">
|
||||
<input v-model="proxy.port" type="number" class="form-control ms-2" style="width: 100px" required min="1" max="65535" :placeholder="$t('Port')">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input id="mark-auth" v-model="proxy.auth" class="form-check-input" type="checkbox">
|
||||
<label for="mark-auth" class="form-check-label">{{ $t("Proxy server has authentication") }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="proxy.auth" class="mb-3">
|
||||
<label for="proxy-username" class="form-label">{{ $t("User") }}</label>
|
||||
<input id="proxy-username" v-model="proxy.username" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div v-if="proxy.auth" class="mb-3">
|
||||
<label for="proxy-password" class="form-label">{{ $t("Password") }}</label>
|
||||
<input id="proxy-password" v-model="proxy.password" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 mt-4">
|
||||
<hr class="dropdown-divider mb-4">
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input id="mark-active" v-model="proxy.active" class="form-check-input" type="checkbox">
|
||||
<label for="mark-active" class="form-check-label">{{ $t("enabled") }}</label>
|
||||
</div>
|
||||
<div class="form-text">
|
||||
{{ $t("enableProxyDescription") }}
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input id="mark-default" v-model="proxy.default" class="form-check-input" type="checkbox">
|
||||
<label for="mark-default" class="form-check-label">{{ $t("setAsDefault") }}</label>
|
||||
</div>
|
||||
<div class="form-text">
|
||||
{{ $t("setAsDefaultProxyDescription") }}
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input id="apply-existing" v-model="proxy.applyExisting" class="form-check-input" type="checkbox">
|
||||
<label class="form-check-label" for="apply-existing">{{ $t("Apply on all existing monitors") }}</label>
|
||||
</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="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="deleteProxy">
|
||||
{{ $t("deleteProxyMsg") }}
|
||||
</Confirm>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Modal } from "bootstrap";
|
||||
|
||||
import Confirm from "./Confirm.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Confirm,
|
||||
},
|
||||
props: {},
|
||||
emits: ["added"],
|
||||
data() {
|
||||
return {
|
||||
model: null,
|
||||
processing: false,
|
||||
id: null,
|
||||
proxy: {
|
||||
protocol: null,
|
||||
host: null,
|
||||
port: null,
|
||||
auth: false,
|
||||
username: null,
|
||||
password: null,
|
||||
active: false,
|
||||
default: false,
|
||||
applyExisting: false,
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.modal = new Modal(this.$refs.modal);
|
||||
},
|
||||
|
||||
methods: {
|
||||
deleteConfirm() {
|
||||
this.modal.hide();
|
||||
this.$refs.confirmDelete.show();
|
||||
},
|
||||
|
||||
show(proxyID) {
|
||||
if (proxyID) {
|
||||
this.id = proxyID;
|
||||
|
||||
for (let proxy of this.$root.proxyList) {
|
||||
if (proxy.id === proxyID) {
|
||||
this.proxy = proxy;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.id = null;
|
||||
this.proxy = {
|
||||
protocol: "https",
|
||||
host: null,
|
||||
port: null,
|
||||
auth: false,
|
||||
username: null,
|
||||
password: null,
|
||||
active: true,
|
||||
default: false,
|
||||
applyExisting: false,
|
||||
};
|
||||
}
|
||||
|
||||
this.modal.show();
|
||||
},
|
||||
|
||||
submit() {
|
||||
this.processing = true;
|
||||
this.$root.getSocket().emit("addProxy", this.proxy, 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
deleteProxy() {
|
||||
this.processing = true;
|
||||
this.$root.getSocket().emit("deleteProxy", 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>
|
47
src/components/settings/Proxies.vue
Normal file
47
src/components/settings/Proxies.vue
Normal file
@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- Proxies -->
|
||||
<div class="proxy-list my-4">
|
||||
<p v-if="$root.proxyList.length === 0">
|
||||
{{ $t("Not available, please setup.") }}
|
||||
</p>
|
||||
<p v-else>
|
||||
{{ $t("proxyDescription") }}
|
||||
</p>
|
||||
|
||||
<ul class="list-group mb-3" style="border-radius: 1rem;">
|
||||
<li v-for="(proxy, index) in $root.proxyList" :key="index" class="list-group-item">
|
||||
{{ proxy.host }}<span v-if="proxy.default === true" class="badge bg-primary ms-2">{{ $t("Default") }}</span><br>
|
||||
<a href="#" @click="$refs.proxyDialog.show(proxy.id)">{{ $t("Edit") }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<button class="btn btn-primary me-2" type="button" @click="$refs.proxyDialog.show()">
|
||||
{{ $t("Setup Proxy") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ProxyDialog ref="proxyDialog" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ProxyDialog from "../../components/ProxyDialog.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ProxyDialog
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../assets/vars.scss";
|
||||
|
||||
.dark {
|
||||
.list-group-item {
|
||||
background-color: $dark-bg2;
|
||||
color: $dark-font-color;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -369,4 +369,12 @@ export default {
|
||||
alertaApiKey: 'API Key',
|
||||
alertaAlertState: 'Alert State',
|
||||
alertaRecoverState: 'Recover State',
|
||||
Proxies: "Proxies",
|
||||
default: "Default",
|
||||
enabled: "Enabled",
|
||||
setAsDefault: "Set As Default",
|
||||
deleteProxyMsg: "Are you sure want to delete this proxy for all monitors?",
|
||||
proxyDescription: "Proxies must be assigned to a monitor to function.",
|
||||
enableProxyDescription: "This proxy will not effect on monitor requests until it is activated. You can control temporarily disable the proxy from all monitors by activation status.",
|
||||
setAsDefaultProxyDescription: "This proxy will be enabled by default for new monitors. You can still disable the proxy separately for each monitor.",
|
||||
};
|
||||
|
@ -33,6 +33,7 @@ export default {
|
||||
uptimeList: { },
|
||||
tlsInfoList: {},
|
||||
notificationList: [],
|
||||
proxyList: [],
|
||||
connectionErrorMsg: "Cannot connect to the socket server. Reconnecting...",
|
||||
};
|
||||
},
|
||||
@ -103,6 +104,16 @@ export default {
|
||||
this.notificationList = data;
|
||||
});
|
||||
|
||||
socket.on("proxyList", (data) => {
|
||||
this.proxyList = data.map(item => {
|
||||
item.auth = !!item.auth;
|
||||
item.active = !!item.active;
|
||||
item.default = !!item.default;
|
||||
|
||||
return item;
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("heartbeat", (data) => {
|
||||
if (! (data.monitorID in this.heartbeatList)) {
|
||||
this.heartbeatList[data.monitorID] = [];
|
||||
|
@ -222,6 +222,32 @@
|
||||
{{ $t("Setup Notification") }}
|
||||
</button>
|
||||
|
||||
<!-- Proxies -->
|
||||
<h2 class="mt-5 mb-2">{{ $t("Proxies") }}</h2>
|
||||
<p v-if="$root.proxyList.length === 0">
|
||||
{{ $t("Not available, please setup.") }}
|
||||
</p>
|
||||
|
||||
<div v-if="$root.proxyList.length > 0" class="form-check form-switch 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-for="proxy in $root.proxyList" :key="proxy.id" class="form-check form-switch my-3">
|
||||
<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-${proxy.id}`">
|
||||
{{ proxy.host }}
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary me-2" type="button" @click="$refs.proxyDialog.show()">
|
||||
{{ $t("Setup Proxy") }}
|
||||
</button>
|
||||
|
||||
<!-- HTTP Options -->
|
||||
<template v-if="monitor.type === 'http' || monitor.type === 'keyword' ">
|
||||
<h2 class="mt-5 mb-2">{{ $t("HTTP Options") }}</h2>
|
||||
@ -285,12 +311,14 @@
|
||||
</form>
|
||||
|
||||
<NotificationDialog ref="notificationDialog" @added="addedNotification" />
|
||||
<ProxyDialog ref="proxyDialog" @added="addedProxy" />
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NotificationDialog from "../components/NotificationDialog.vue";
|
||||
import ProxyDialog from "../components/ProxyDialog.vue";
|
||||
import TagsManager from "../components/TagsManager.vue";
|
||||
import CopyableInput from "../components/CopyableInput.vue";
|
||||
|
||||
@ -302,6 +330,7 @@ const toast = useToast();
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ProxyDialog,
|
||||
CopyableInput,
|
||||
NotificationDialog,
|
||||
TagsManager,
|
||||
@ -368,6 +397,17 @@ export default {
|
||||
|
||||
},
|
||||
watch: {
|
||||
"$root.proxyList"() {
|
||||
if (this.isAdd) {
|
||||
if (this.$root.proxyList && !this.monitor.proxyId) {
|
||||
const proxy = this.$root.proxyList.find(proxy => proxy.default);
|
||||
|
||||
if (proxy) {
|
||||
this.monitor.proxyId = proxy.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"$route.fullPath"() {
|
||||
this.init();
|
||||
@ -439,8 +479,17 @@ export default {
|
||||
accepted_statuscodes: ["200-299"],
|
||||
dns_resolve_type: "A",
|
||||
dns_resolve_server: "1.1.1.1",
|
||||
proxyId: null,
|
||||
};
|
||||
|
||||
if (this.$root.proxyList && !this.monitor.proxyId) {
|
||||
const proxy = this.$root.proxyList.find(proxy => proxy.default);
|
||||
|
||||
if (proxy) {
|
||||
this.monitor.proxyId = proxy.id;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.$root.notificationList.length; i++) {
|
||||
if (this.$root.notificationList[i].isDefault == true) {
|
||||
this.monitor.notificationIDList[this.$root.notificationList[i].id] = true;
|
||||
@ -532,6 +581,12 @@ export default {
|
||||
addedNotification(id) {
|
||||
this.monitor.notificationIDList[id] = true;
|
||||
},
|
||||
|
||||
// Added a Proxy Event
|
||||
// Enable it if the proxy is added in EditMonitor.vue
|
||||
addedProxy(id) {
|
||||
this.monitor.proxyId = id;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -81,6 +81,9 @@ export default {
|
||||
security: {
|
||||
title: this.$t("Security"),
|
||||
},
|
||||
proxies: {
|
||||
title: this.$t("Proxies"),
|
||||
},
|
||||
backup: {
|
||||
title: this.$t("Backup"),
|
||||
},
|
||||
|
@ -16,6 +16,7 @@ import General from "./components/settings/General.vue";
|
||||
import Notifications from "./components/settings/Notifications.vue";
|
||||
import MonitorHistory from "./components/settings/MonitorHistory.vue";
|
||||
import Security from "./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";
|
||||
|
||||
@ -88,6 +89,10 @@ const routes = [
|
||||
path: "security",
|
||||
component: Security,
|
||||
},
|
||||
{
|
||||
path: "proxies",
|
||||
component: Proxies,
|
||||
},
|
||||
{
|
||||
path: "backup",
|
||||
component: Backup,
|
||||
|
Loading…
Reference in New Issue
Block a user