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 proxiespull/840/head
parent
2cc7a990ff
commit
78d76512ba
@ -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;
|
@ -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;
|
@ -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,
|
||||||
|
};
|
@ -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>
|
@ -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>
|
Loading…
Reference in new issue