Browse Source
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 proxiesrebasesoftware/feature/request-with-http-proxy
Uğur Erkan
3 years ago
12 changed files with 597 additions and 7 deletions
@ -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> |
Loading…
Reference in new issue