diff --git a/src/backend/migrations/20190215115310_customlocations.js b/src/backend/migrations/20190215115310_customlocations.js new file mode 100644 index 00000000..395e2bc5 --- /dev/null +++ b/src/backend/migrations/20190215115310_customlocations.js @@ -0,0 +1,37 @@ +'use strict'; + +const migrate_name = 'custom_locations'; +const logger = require('../logger').migrate; + +/** + * Migrate + * Extends proxy_host table with locations field + * + * @see http://knexjs.org/#Schema + * + * @param {Object} knex + * @param {Promise} Promise + * @returns {Promise} + */ +exports.up = function (knex/*, Promise*/) { + logger.info('[' + migrate_name + '] Migrating Up...'); + + return knex.schema.table('proxy_host', function (proxy_host) { + proxy_host.text('locations'); + }) + .then(() => { + logger.info('[' + migrate_name + '] proxy_host Table altered'); + }) +}; + +/** + * Undo Migrate + * + * @param {Object} knex + * @param {Promise} Promise + * @returns {Promise} + */ +exports.down = function (knex, Promise) { + logger.warn('[' + migrate_name + '] You can\'t migrate down this one.'); + return Promise.resolve(true); +}; diff --git a/src/backend/models/proxy_host.js b/src/backend/models/proxy_host.js index a1217236..cc7fb370 100644 --- a/src/backend/models/proxy_host.js +++ b/src/backend/models/proxy_host.js @@ -26,6 +26,9 @@ class ProxyHost extends Model { this.meta = {}; } + // Serialize custom locations + this.locations = JSON.stringify(this.locations); + this.domain_names.sort(); } @@ -47,7 +50,7 @@ class ProxyHost extends Model { } static get jsonAttributes () { - return ['domain_names', 'meta']; + return ['domain_names', 'meta', 'locations']; } static get relationMappings () { diff --git a/src/backend/schema/endpoints/proxy-hosts.json b/src/backend/schema/endpoints/proxy-hosts.json index 9e033da2..460bb39f 100644 --- a/src/backend/schema/endpoints/proxy-hosts.json +++ b/src/backend/schema/endpoints/proxy-hosts.json @@ -63,6 +63,41 @@ }, "meta": { "type": "object" + }, + "locations": { + "type": "array", + "minItems": 0, + "items": { + "type": "object", + "required": [ + "forward_scheme", + "forward_host", + "forward_port", + "path" + ], + "additionalProperties": false, + "properties": { + "id": { + "type": ["integer", "null"] + }, + "path": { + "type": "string", + "minLength": 1 + }, + "forward_scheme": { + "$ref": "#/definitions/forward_scheme" + }, + "forward_host": { + "$ref": "#/definitions/forward_host" + }, + "forward_port": { + "$ref": "#/definitions/forward_port" + }, + "advanced_config": { + "type": "string" + } + } + } } }, "properties": { @@ -116,6 +151,9 @@ }, "meta": { "$ref": "#/definitions/meta" + }, + "locations": { + "$ref": "#/definitions/locations" } }, "links": [ @@ -197,6 +235,9 @@ }, "meta": { "$ref": "#/definitions/meta" + }, + "locations": { + "$ref": "#/definitions/locations" } } }, @@ -261,6 +302,9 @@ }, "meta": { "$ref": "#/definitions/meta" + }, + "locations": { + "$ref": "#/definitions/locations" } } }, diff --git a/src/frontend/js/app/nginx/proxy/form.ejs b/src/frontend/js/app/nginx/proxy/form.ejs index 1cc13b5e..8f2d358b 100644 --- a/src/frontend/js/app/nginx/proxy/form.ejs +++ b/src/frontend/js/app/nginx/proxy/form.ejs @@ -7,10 +7,22 @@
+ + +
+
+
+ +
+
+
+
+
diff --git a/src/frontend/js/app/nginx/proxy/form.js b/src/frontend/js/app/nginx/proxy/form.js index 19cf2791..0af08b89 100644 --- a/src/frontend/js/app/nginx/proxy/form.js +++ b/src/frontend/js/app/nginx/proxy/form.js @@ -3,18 +3,71 @@ const Mn = require('backbone.marionette'); const App = require('../../main'); const ProxyHostModel = require('../../../models/proxy-host'); +const ProxyLocationModel = require('../../../models/proxy-host-location'); const template = require('./form.ejs'); const certListItemTemplate = require('../certificates-list-item.ejs'); +const locationItemTemplate = require('./location-item.ejs'); const accessListItemTemplate = require('./access-list-item.ejs'); const Helpers = require('../../../lib/helpers'); + require('jquery-serializejson'); require('selectize'); +const LocationView = Mn.View.extend({ + template: locationItemTemplate, + className: 'location_block', + + ui: { + toggle: 'input[type="checkbox"]', + config: '.config', + delete: '.location-delete' + }, + + events: { + 'change @ui.toggle': function(el) { + if (el.target.checked) { + this.ui.config.show(); + } else { + this.ui.config.hide(); + } + }, + + 'change .model': function (e) { + console.log(e); + const map = {}; + map[e.target.name] = e.target.value; + this.model.set(map); + }, + + 'click @ui.delete': function () { + this.model.destroy(); + } + }, + + onRender: function() { + $(this.ui.config).hide(); + }, + + templateContext: function() { + return { + i18n: App.i18n, + advanced_config: '' + } + } +}); + +const LocationCollectionView = Mn.CollectionView.extend({ + className: 'locations_container', + childView: LocationView +}); + module.exports = Mn.View.extend({ template: template, className: 'modal-dialog', + locationsCollection: new ProxyLocationModel.Collection(), + ui: { form: 'form', domain_names: 'input[name="domain_names"]', @@ -22,6 +75,8 @@ module.exports = Mn.View.extend({ buttons: '.modal-footer button', cancel: 'button.cancel', save: 'button.save', + add_location_btn: 'button.add_location', + locations_container:'.locations_container', certificate_select: 'select[name="certificate_id"]', access_list_select: 'select[name="access_list_id"]', ssl_forced: 'input[name="ssl_forced"]', @@ -30,6 +85,10 @@ module.exports = Mn.View.extend({ letsencrypt: '.letsencrypt' }, + regions: { + locations_regions: '@ui.locations_container' + }, + events: { 'change @ui.certificate_select': function () { let id = this.ui.certificate_select.val(); @@ -46,6 +105,13 @@ module.exports = Mn.View.extend({ .css('opacity', enabled ? 1 : 0.5); }, + 'click @ui.add_location_btn': function (e) { + e.preventDefault(); + + const model = new ProxyLocationModel.Model(); + this.locationsCollection.add(model); + }, + 'click @ui.save': function (e) { e.preventDefault(); @@ -57,6 +123,17 @@ module.exports = Mn.View.extend({ let view = this; let data = this.ui.form.serializeJSON(); + console.log('FORM', data); + // Add locations + data.locations = []; + this.locationsCollection.models.forEach((location) => { + data.locations.push(location.toJSON()); + }); + + // Serialize collects path from custom locations + // This field must be removed from root object + delete data.path; + // Manipulate data.forward_port = parseInt(data.forward_port, 10); data.block_exploits = !!data.block_exploits; @@ -211,5 +288,18 @@ module.exports = Mn.View.extend({ if (typeof options.model === 'undefined' || !options.model) { this.model = new ProxyHostModel.Model(); } + + // Custom locations + this.showChildView('locations_regions', new LocationCollectionView({ + collection: this.locationsCollection + })); + + // Check wether there are any location defined + if (Array.isArray(options.model.attributes.locations)) { + options.model.attributes.locations.forEach((location) => { + let m = new ProxyLocationModel.Model(location); + this.locationsCollection.add(m); + }); + } } }); diff --git a/src/frontend/js/app/nginx/proxy/location-item.ejs b/src/frontend/js/app/nginx/proxy/location-item.ejs new file mode 100644 index 00000000..0e854c96 --- /dev/null +++ b/src/frontend/js/app/nginx/proxy/location-item.ejs @@ -0,0 +1,63 @@ +
+
+
+
+
+ +
+
+
+ + location + + +
+
+
+
+ +
+
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+ +
+
+
+ + + delete + +
+
\ No newline at end of file diff --git a/src/frontend/js/models/proxy-host-location.js b/src/frontend/js/models/proxy-host-location.js new file mode 100644 index 00000000..08459138 --- /dev/null +++ b/src/frontend/js/models/proxy-host-location.js @@ -0,0 +1,37 @@ +'use strict'; + +const Backbone = require('backbone'); + +const model = Backbone.Model.extend({ + idAttribute: 'id', + + defaults: function() { + return { + opened: false, + path: '', + advanced_config: '', + forward_scheme: 'http', + forward_host: '', + forward_port: '80' + } + }, + + toJSON() { + const r = Object.assign({}, this.attributes); + delete r.opened; + return r; + }, + + toggleVisibility: function () { + this.save({ + opened: !this.get('opened') + }); + } +}) + +module.exports = { + Model: model, + Collection: Backbone.Collection.extend({ + model + }) +} \ No newline at end of file