form.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. const Mn = require('backbone.marionette');
  2. const App = require('../../main');
  3. const ProxyHostModel = require('../../../models/proxy-host');
  4. const ProxyLocationModel = require('../../../models/proxy-host-location');
  5. const template = require('./form.ejs');
  6. const certListItemTemplate = require('../certificates-list-item.ejs');
  7. const accessListItemTemplate = require('./access-list-item.ejs');
  8. const CustomLocation = require('./location');
  9. const Helpers = require('../../../lib/helpers');
  10. require('jquery-serializejson');
  11. require('selectize');
  12. module.exports = Mn.View.extend({
  13. template: template,
  14. className: 'modal-dialog',
  15. locationsCollection: new ProxyLocationModel.Collection(),
  16. ui: {
  17. form: 'form',
  18. domain_names: 'input[name="domain_names"]',
  19. forward_host: 'input[name="forward_host"]',
  20. buttons: '.modal-footer button',
  21. cancel: 'button.cancel',
  22. save: 'button.save',
  23. add_location_btn: 'button.add_location',
  24. locations_container:'.locations_container',
  25. certificate_select: 'select[name="certificate_id"]',
  26. access_list_select: 'select[name="access_list_id"]',
  27. ssl_forced: 'input[name="ssl_forced"]',
  28. hsts_enabled: 'input[name="hsts_enabled"]',
  29. hsts_subdomains: 'input[name="hsts_subdomains"]',
  30. http2_support: 'input[name="http2_support"]',
  31. forward_scheme: 'select[name="forward_scheme"]',
  32. letsencrypt: '.letsencrypt'
  33. },
  34. regions: {
  35. locations_regions: '@ui.locations_container'
  36. },
  37. events: {
  38. 'change @ui.certificate_select': function () {
  39. let id = this.ui.certificate_select.val();
  40. if (id === 'new') {
  41. this.ui.letsencrypt.show().find('input').prop('disabled', false);
  42. } else {
  43. this.ui.letsencrypt.hide().find('input').prop('disabled', true);
  44. }
  45. let enabled = id === 'new' || parseInt(id, 10) > 0;
  46. let inputs = this.ui.ssl_forced.add(this.ui.http2_support);
  47. inputs
  48. .prop('disabled', !enabled)
  49. .parents('.form-group')
  50. .css('opacity', enabled ? 1 : 0.5);
  51. if (!enabled) {
  52. inputs.prop('checked', false);
  53. }
  54. inputs.trigger('change');
  55. },
  56. 'change @ui.ssl_forced': function () {
  57. let checked = this.ui.ssl_forced.prop('checked');
  58. this.ui.hsts_enabled
  59. .prop('disabled', !checked)
  60. .parents('.form-group')
  61. .css('opacity', checked ? 1 : 0.5);
  62. if (!checked) {
  63. this.ui.hsts_enabled.prop('checked', false);
  64. }
  65. this.ui.hsts_enabled.trigger('change');
  66. },
  67. 'change @ui.hsts_enabled': function () {
  68. let checked = this.ui.hsts_enabled.prop('checked');
  69. this.ui.hsts_subdomains
  70. .prop('disabled', !checked)
  71. .parents('.form-group')
  72. .css('opacity', checked ? 1 : 0.5);
  73. if (!checked) {
  74. this.ui.hsts_subdomains.prop('checked', false);
  75. }
  76. },
  77. 'click @ui.add_location_btn': function (e) {
  78. e.preventDefault();
  79. const model = new ProxyLocationModel.Model();
  80. this.locationsCollection.add(model);
  81. },
  82. 'click @ui.save': function (e) {
  83. e.preventDefault();
  84. if (!this.ui.form[0].checkValidity()) {
  85. $('<input type="submit">').hide().appendTo(this.ui.form).click().remove();
  86. return;
  87. }
  88. let view = this;
  89. let data = this.ui.form.serializeJSON();
  90. // Add locations
  91. data.locations = [];
  92. this.locationsCollection.models.forEach((location) => {
  93. data.locations.push(location.toJSON());
  94. });
  95. // Serialize collects path from custom locations
  96. // This field must be removed from root object
  97. delete data.path;
  98. // Manipulate
  99. data.forward_port = parseInt(data.forward_port, 10);
  100. data.block_exploits = !!data.block_exploits;
  101. data.caching_enabled = !!data.caching_enabled;
  102. data.allow_websocket_upgrade = !!data.allow_websocket_upgrade;
  103. data.http2_support = !!data.http2_support;
  104. data.hsts_enabled = !!data.hsts_enabled;
  105. data.hsts_subdomains = !!data.hsts_subdomains;
  106. data.ssl_forced = !!data.ssl_forced;
  107. if (typeof data.domain_names === 'string' && data.domain_names) {
  108. data.domain_names = data.domain_names.split(',');
  109. }
  110. // Check for any domain names containing wildcards, which are not allowed with letsencrypt
  111. if (data.certificate_id === 'new') {
  112. let domain_err = false;
  113. data.domain_names.map(function (name) {
  114. if (name.match(/\*/im)) {
  115. domain_err = true;
  116. }
  117. });
  118. if (domain_err) {
  119. alert('Cannot request Let\'s Encrypt Certificate for wildcard domains');
  120. return;
  121. }
  122. data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1';
  123. } else {
  124. data.certificate_id = parseInt(data.certificate_id, 10);
  125. }
  126. let method = App.Api.Nginx.ProxyHosts.create;
  127. let is_new = true;
  128. if (this.model.get('id')) {
  129. // edit
  130. is_new = false;
  131. method = App.Api.Nginx.ProxyHosts.update;
  132. data.id = this.model.get('id');
  133. }
  134. this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
  135. method(data)
  136. .then(result => {
  137. view.model.set(result);
  138. App.UI.closeModal(function () {
  139. if (is_new) {
  140. App.Controller.showNginxProxy();
  141. }
  142. });
  143. })
  144. .catch(err => {
  145. alert(err.message);
  146. this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
  147. });
  148. }
  149. },
  150. templateContext: {
  151. getLetsencryptEmail: function () {
  152. return App.Cache.User.get('email');
  153. }
  154. },
  155. onRender: function () {
  156. let view = this;
  157. this.ui.ssl_forced.trigger('change');
  158. this.ui.hsts_enabled.trigger('change');
  159. // Domain names
  160. this.ui.domain_names.selectize({
  161. delimiter: ',',
  162. persist: false,
  163. maxOptions: 15,
  164. create: function (input) {
  165. return {
  166. value: input,
  167. text: input
  168. };
  169. },
  170. createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/
  171. });
  172. // Access Lists
  173. this.ui.access_list_select.selectize({
  174. valueField: 'id',
  175. labelField: 'name',
  176. searchField: ['name'],
  177. create: false,
  178. preload: true,
  179. allowEmptyOption: true,
  180. render: {
  181. option: function (item) {
  182. item.i18n = App.i18n;
  183. item.formatDbDate = Helpers.formatDbDate;
  184. return accessListItemTemplate(item);
  185. }
  186. },
  187. load: function (query, callback) {
  188. App.Api.Nginx.AccessLists.getAll(['items', 'clients'])
  189. .then(rows => {
  190. callback(rows);
  191. })
  192. .catch(err => {
  193. console.error(err);
  194. callback();
  195. });
  196. },
  197. onLoad: function () {
  198. view.ui.access_list_select[0].selectize.setValue(view.model.get('access_list_id'));
  199. }
  200. });
  201. // Certificates
  202. this.ui.letsencrypt.hide();
  203. this.ui.certificate_select.selectize({
  204. valueField: 'id',
  205. labelField: 'nice_name',
  206. searchField: ['nice_name', 'domain_names'],
  207. create: false,
  208. preload: true,
  209. allowEmptyOption: true,
  210. render: {
  211. option: function (item) {
  212. item.i18n = App.i18n;
  213. item.formatDbDate = Helpers.formatDbDate;
  214. return certListItemTemplate(item);
  215. }
  216. },
  217. load: function (query, callback) {
  218. App.Api.Nginx.Certificates.getAll()
  219. .then(rows => {
  220. callback(rows);
  221. })
  222. .catch(err => {
  223. console.error(err);
  224. callback();
  225. });
  226. },
  227. onLoad: function () {
  228. view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id'));
  229. }
  230. });
  231. },
  232. initialize: function (options) {
  233. if (typeof options.model === 'undefined' || !options.model) {
  234. this.model = new ProxyHostModel.Model();
  235. }
  236. this.locationsCollection = new ProxyLocationModel.Collection();
  237. // Custom locations
  238. this.showChildView('locations_regions', new CustomLocation.LocationCollectionView({
  239. collection: this.locationsCollection
  240. }));
  241. // Check wether there are any location defined
  242. if (options.model && Array.isArray(options.model.attributes.locations)) {
  243. options.model.attributes.locations.forEach((location) => {
  244. let m = new ProxyLocationModel.Model(location);
  245. this.locationsCollection.add(m);
  246. });
  247. }
  248. }
  249. });