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
				 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