mirror of
				https://github.com/NginxProxyManager/nginx-proxy-manager.git
				synced 2025-10-31 07:43:33 +00:00 
			
		
		
		
	Access lists shell, redirections work
This commit is contained in:
		
							
								
								
									
										183
									
								
								src/backend/internal/access-list.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								src/backend/internal/access-list.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,183 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const _               = require('lodash'); | ||||||
|  | const error           = require('../lib/error'); | ||||||
|  | const accessListModel = require('../models/access_list'); | ||||||
|  |  | ||||||
|  | function omissions () { | ||||||
|  |     return ['is_deleted']; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const internalAccessList = { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @param   {Access}  access | ||||||
|  |      * @param   {Object}  data | ||||||
|  |      * @returns {Promise} | ||||||
|  |      */ | ||||||
|  |     create: (access, data) => { | ||||||
|  |         return access.can('access_lists:create', data) | ||||||
|  |             .then(access_data => { | ||||||
|  |                 // TODO | ||||||
|  |                 return {}; | ||||||
|  |             }); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @param  {Access}  access | ||||||
|  |      * @param  {Object}  data | ||||||
|  |      * @param  {Integer} data.id | ||||||
|  |      * @param  {String}  [data.email] | ||||||
|  |      * @param  {String}  [data.name] | ||||||
|  |      * @return {Promise} | ||||||
|  |      */ | ||||||
|  |     update: (access, data) => { | ||||||
|  |         return access.can('access_lists:update', data.id) | ||||||
|  |             .then(access_data => { | ||||||
|  |                 // TODO | ||||||
|  |                 return {}; | ||||||
|  |             }); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @param  {Access}   access | ||||||
|  |      * @param  {Object}   data | ||||||
|  |      * @param  {Integer}  data.id | ||||||
|  |      * @param  {Array}    [data.expand] | ||||||
|  |      * @param  {Array}    [data.omit] | ||||||
|  |      * @return {Promise} | ||||||
|  |      */ | ||||||
|  |     get: (access, data) => { | ||||||
|  |         if (typeof data === 'undefined') { | ||||||
|  |             data = {}; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (typeof data.id === 'undefined' || !data.id) { | ||||||
|  |             data.id = access.token.get('attrs').id; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return access.can('access_lists:get', data.id) | ||||||
|  |             .then(access_data => { | ||||||
|  |                 let query = accessListModel | ||||||
|  |                     .query() | ||||||
|  |                     .where('is_deleted', 0) | ||||||
|  |                     .andWhere('id', data.id) | ||||||
|  |                     .allowEager('[owner]') | ||||||
|  |                     .first(); | ||||||
|  |  | ||||||
|  |                 if (access_data.permission_visibility !== 'all') { | ||||||
|  |                     query.andWhere('owner_user_id', access.token.get('attrs').id); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 // Custom omissions | ||||||
|  |                 if (typeof data.omit !== 'undefined' && data.omit !== null) { | ||||||
|  |                     query.omit(data.omit); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (typeof data.expand !== 'undefined' && data.expand !== null) { | ||||||
|  |                     query.eager('[' + data.expand.join(', ') + ']'); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 return query; | ||||||
|  |             }) | ||||||
|  |             .then(row => { | ||||||
|  |                 if (row) { | ||||||
|  |                     return _.omit(row, omissions()); | ||||||
|  |                 } else { | ||||||
|  |                     throw new error.ItemNotFoundError(data.id); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @param   {Access}  access | ||||||
|  |      * @param   {Object}  data | ||||||
|  |      * @param   {Integer} data.id | ||||||
|  |      * @param   {String}  [data.reason] | ||||||
|  |      * @returns {Promise} | ||||||
|  |      */ | ||||||
|  |     delete: (access, data) => { | ||||||
|  |         return access.can('access_lists:delete', data.id) | ||||||
|  |             .then(() => { | ||||||
|  |                 return internalAccessList.get(access, {id: data.id}); | ||||||
|  |             }) | ||||||
|  |             .then(row => { | ||||||
|  |                 if (!row) { | ||||||
|  |                     throw new error.ItemNotFoundError(data.id); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 return accessListModel | ||||||
|  |                     .query() | ||||||
|  |                     .where('id', row.id) | ||||||
|  |                     .patch({ | ||||||
|  |                         is_deleted: 1 | ||||||
|  |                     }); | ||||||
|  |             }) | ||||||
|  |             .then(() => { | ||||||
|  |                 return true; | ||||||
|  |             }); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * All Lists | ||||||
|  |      * | ||||||
|  |      * @param   {Access}  access | ||||||
|  |      * @param   {Array}   [expand] | ||||||
|  |      * @param   {String}  [search_query] | ||||||
|  |      * @returns {Promise} | ||||||
|  |      */ | ||||||
|  |     getAll: (access, expand, search_query) => { | ||||||
|  |         return access.can('access_lists:list') | ||||||
|  |             .then(access_data => { | ||||||
|  |                 let query = accessListModel | ||||||
|  |                     .query() | ||||||
|  |                     .where('is_deleted', 0) | ||||||
|  |                     .groupBy('id') | ||||||
|  |                     .omit(['is_deleted']) | ||||||
|  |                     .allowEager('[owner]') | ||||||
|  |                     .orderBy('name', 'ASC'); | ||||||
|  |  | ||||||
|  |                 if (access_data.permission_visibility !== 'all') { | ||||||
|  |                     query.andWhere('owner_user_id', access.token.get('attrs').id); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 // Query is used for searching | ||||||
|  |                 if (typeof search_query === 'string') { | ||||||
|  |                     query.where(function () { | ||||||
|  |                         this.where('name', 'like', '%' + search_query + '%'); | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (typeof expand !== 'undefined' && expand !== null) { | ||||||
|  |                     query.eager('[' + expand.join(', ') + ']'); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 return query; | ||||||
|  |             }); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Report use | ||||||
|  |      * | ||||||
|  |      * @param   {Integer} user_id | ||||||
|  |      * @param   {String}  visibility | ||||||
|  |      * @returns {Promise} | ||||||
|  |      */ | ||||||
|  |     getCount: (user_id, visibility) => { | ||||||
|  |         let query = accessListModel | ||||||
|  |             .query() | ||||||
|  |             .count('id as count') | ||||||
|  |             .where('is_deleted', 0); | ||||||
|  |  | ||||||
|  |         if (visibility !== 'all') { | ||||||
|  |             query.andWhere('owner_user_id', user_id); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return query.first() | ||||||
|  |             .then(row => { | ||||||
|  |                 return parseInt(row.count, 10); | ||||||
|  |             }); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | module.exports = internalAccessList; | ||||||
| @@ -125,7 +125,7 @@ const internalProxyHost = { | |||||||
|                     .query() |                     .query() | ||||||
|                     .where('is_deleted', 0) |                     .where('is_deleted', 0) | ||||||
|                     .andWhere('id', data.id) |                     .andWhere('id', data.id) | ||||||
|                     .allowEager('[permissions]') |                     .allowEager('[owner,access_list]') | ||||||
|                     .first(); |                     .first(); | ||||||
|  |  | ||||||
|                 if (access_data.permission_visibility !== 'all') { |                 if (access_data.permission_visibility !== 'all') { | ||||||
|   | |||||||
| @@ -3,12 +3,210 @@ | |||||||
| const _                    = require('lodash'); | const _                    = require('lodash'); | ||||||
| const error                = require('../lib/error'); | const error                = require('../lib/error'); | ||||||
| const redirectionHostModel = require('../models/redirection_host'); | const redirectionHostModel = require('../models/redirection_host'); | ||||||
|  | const internalHost         = require('./host'); | ||||||
|  |  | ||||||
| function omissions () { | function omissions () { | ||||||
|     return ['is_deleted']; |     return ['is_deleted']; | ||||||
| } | } | ||||||
|  |  | ||||||
| const internalProxyHost = { | const internalRedirectionHost = { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @param   {Access}  access | ||||||
|  |      * @param   {Object}  data | ||||||
|  |      * @returns {Promise} | ||||||
|  |      */ | ||||||
|  |     create: (access, data) => { | ||||||
|  |         return access.can('redirection_hosts:create', data) | ||||||
|  |             .then(access_data => { | ||||||
|  |                 // Get a list of the domain names and check each of them against existing records | ||||||
|  |                 let domain_name_check_promises = []; | ||||||
|  |  | ||||||
|  |                 data.domain_names.map(function (domain_name) { | ||||||
|  |                     domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name)); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |                 return Promise.all(domain_name_check_promises) | ||||||
|  |                     .then(check_results => { | ||||||
|  |                         check_results.map(function (result) { | ||||||
|  |                             if (result.is_taken) { | ||||||
|  |                                 throw new error.ValidationError(result.hostname + ' is already in use'); | ||||||
|  |                             } | ||||||
|  |                         }); | ||||||
|  |                     }); | ||||||
|  |             }) | ||||||
|  |             .then(() => { | ||||||
|  |                 // At this point the domains should have been checked | ||||||
|  |                 data.owner_user_id = access.token.get('attrs').id; | ||||||
|  |  | ||||||
|  |                 if (typeof data.meta === 'undefined') { | ||||||
|  |                     data.meta = {}; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 return redirectionHostModel | ||||||
|  |                     .query() | ||||||
|  |                     .omit(omissions()) | ||||||
|  |                     .insertAndFetch(data); | ||||||
|  |             }) | ||||||
|  |             .then(row => { | ||||||
|  |                 return _.omit(row, omissions()); | ||||||
|  |             }); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @param  {Access}  access | ||||||
|  |      * @param  {Object}  data | ||||||
|  |      * @param  {Integer} data.id | ||||||
|  |      * @param  {String}  [data.email] | ||||||
|  |      * @param  {String}  [data.name] | ||||||
|  |      * @return {Promise} | ||||||
|  |      */ | ||||||
|  |     update: (access, data) => { | ||||||
|  |         return access.can('redirection_hosts:update', data.id) | ||||||
|  |             .then(access_data => { | ||||||
|  |                 // Get a list of the domain names and check each of them against existing records | ||||||
|  |                 let domain_name_check_promises = []; | ||||||
|  |  | ||||||
|  |                 if (typeof data.domain_names !== 'undefined') { | ||||||
|  |                     data.domain_names.map(function (domain_name) { | ||||||
|  |                         domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'redirection', data.id)); | ||||||
|  |                     }); | ||||||
|  |  | ||||||
|  |                     return Promise.all(domain_name_check_promises) | ||||||
|  |                         .then(check_results => { | ||||||
|  |                             check_results.map(function (result) { | ||||||
|  |                                 if (result.is_taken) { | ||||||
|  |                                     throw new error.ValidationError(result.hostname + ' is already in use'); | ||||||
|  |                                 } | ||||||
|  |                             }); | ||||||
|  |                         }); | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|  |             .then(() => { | ||||||
|  |                 return internalRedirectionHost.get(access, {id: data.id}); | ||||||
|  |             }) | ||||||
|  |             .then(row => { | ||||||
|  |                 if (row.id !== data.id) { | ||||||
|  |                     // Sanity check that something crazy hasn't happened | ||||||
|  |                     throw new error.InternalValidationError('Redirection Host could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 return redirectionHostModel | ||||||
|  |                     .query() | ||||||
|  |                     .omit(omissions()) | ||||||
|  |                     .patchAndFetchById(row.id, data) | ||||||
|  |                     .then(saved_row => { | ||||||
|  |                         saved_row.meta = internalHost.cleanMeta(saved_row.meta); | ||||||
|  |                         return _.omit(saved_row, omissions()); | ||||||
|  |                     }); | ||||||
|  |             }); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @param  {Access}   access | ||||||
|  |      * @param  {Object}   data | ||||||
|  |      * @param  {Integer}  data.id | ||||||
|  |      * @param  {Array}    [data.expand] | ||||||
|  |      * @param  {Array}    [data.omit] | ||||||
|  |      * @return {Promise} | ||||||
|  |      */ | ||||||
|  |     get: (access, data) => { | ||||||
|  |         if (typeof data === 'undefined') { | ||||||
|  |             data = {}; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (typeof data.id === 'undefined' || !data.id) { | ||||||
|  |             data.id = access.token.get('attrs').id; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return access.can('redirection_hosts:get', data.id) | ||||||
|  |             .then(access_data => { | ||||||
|  |                 let query = redirectionHostModel | ||||||
|  |                     .query() | ||||||
|  |                     .where('is_deleted', 0) | ||||||
|  |                     .andWhere('id', data.id) | ||||||
|  |                     .allowEager('[owner]') | ||||||
|  |                     .first(); | ||||||
|  |  | ||||||
|  |                 if (access_data.permission_visibility !== 'all') { | ||||||
|  |                     query.andWhere('owner_user_id', access.token.get('attrs').id); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 // Custom omissions | ||||||
|  |                 if (typeof data.omit !== 'undefined' && data.omit !== null) { | ||||||
|  |                     query.omit(data.omit); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (typeof data.expand !== 'undefined' && data.expand !== null) { | ||||||
|  |                     query.eager('[' + data.expand.join(', ') + ']'); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 return query; | ||||||
|  |             }) | ||||||
|  |             .then(row => { | ||||||
|  |                 if (row) { | ||||||
|  |                     row.meta = internalHost.cleanMeta(row.meta); | ||||||
|  |                     return _.omit(row, omissions()); | ||||||
|  |                 } else { | ||||||
|  |                     throw new error.ItemNotFoundError(data.id); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @param {Access}  access | ||||||
|  |      * @param {Object}  data | ||||||
|  |      * @param {Integer} data.id | ||||||
|  |      * @param {String}  [data.reason] | ||||||
|  |      * @returns {Promise} | ||||||
|  |      */ | ||||||
|  |     delete: (access, data) => { | ||||||
|  |         return access.can('redirection_hosts:delete', data.id) | ||||||
|  |             .then(() => { | ||||||
|  |                 return internalRedirectionHost.get(access, {id: data.id}); | ||||||
|  |             }) | ||||||
|  |             .then(row => { | ||||||
|  |                 if (!row) { | ||||||
|  |                     throw new error.ItemNotFoundError(data.id); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 return redirectionHostModel | ||||||
|  |                     .query() | ||||||
|  |                     .where('id', row.id) | ||||||
|  |                     .patch({ | ||||||
|  |                         is_deleted: 1 | ||||||
|  |                     }); | ||||||
|  |             }) | ||||||
|  |             .then(() => { | ||||||
|  |                 return true; | ||||||
|  |             }); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @param   {Access}  access | ||||||
|  |      * @param   {Object}  data | ||||||
|  |      * @param   {Integer} data.id | ||||||
|  |      * @param   {Object}  data.files | ||||||
|  |      * @returns {Promise} | ||||||
|  |      */ | ||||||
|  |     setCerts: (access, data) => { | ||||||
|  |         return internalRedirectionHost.get(access, {id: data.id}) | ||||||
|  |             .then(row => { | ||||||
|  |                 _.map(data.files, (file, name) => { | ||||||
|  |                     if (internalHost.allowed_ssl_files.indexOf(name) !== -1) { | ||||||
|  |                         row.meta[name] = file.data.toString(); | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |                 return internalRedirectionHost.update(access, { | ||||||
|  |                     id:   data.id, | ||||||
|  |                     meta: row.meta | ||||||
|  |                 }); | ||||||
|  |             }) | ||||||
|  |             .then(row => { | ||||||
|  |                 return _.pick(row.meta, internalHost.allowed_ssl_files); | ||||||
|  |             }); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * All Hosts |      * All Hosts | ||||||
| @@ -26,6 +224,7 @@ const internalProxyHost = { | |||||||
|                     .where('is_deleted', 0) |                     .where('is_deleted', 0) | ||||||
|                     .groupBy('id') |                     .groupBy('id') | ||||||
|                     .omit(['is_deleted']) |                     .omit(['is_deleted']) | ||||||
|  |                     .allowEager('[owner]') | ||||||
|                     .orderBy('domain_names', 'ASC'); |                     .orderBy('domain_names', 'ASC'); | ||||||
|  |  | ||||||
|                 if (access_data.permission_visibility !== 'all') { |                 if (access_data.permission_visibility !== 'all') { | ||||||
| @@ -44,6 +243,13 @@ const internalProxyHost = { | |||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 return query; |                 return query; | ||||||
|  |             }) | ||||||
|  |             .then(rows => { | ||||||
|  |                 rows.map(row => { | ||||||
|  |                     row.meta = internalHost.cleanMeta(row.meta); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |                 return rows; | ||||||
|             }); |             }); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
| @@ -71,4 +277,4 @@ const internalProxyHost = { | |||||||
|     } |     } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| module.exports = internalProxyHost; | module.exports = internalRedirectionHost; | ||||||
|   | |||||||
							
								
								
									
										23
									
								
								src/backend/lib/access/access_lists-create.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/backend/lib/access/access_lists-create.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | { | ||||||
|  |   "anyOf": [ | ||||||
|  |     { | ||||||
|  |       "$ref": "roles#/definitions/admin" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "type": "object", | ||||||
|  |       "required": ["permission_access_lists", "roles"], | ||||||
|  |       "properties": { | ||||||
|  |         "permission_access_lists": { | ||||||
|  |           "$ref": "perms#/definitions/manage" | ||||||
|  |         }, | ||||||
|  |         "roles": { | ||||||
|  |           "type": "array", | ||||||
|  |           "items": { | ||||||
|  |             "type": "string", | ||||||
|  |             "enum": ["user"] | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
							
								
								
									
										23
									
								
								src/backend/lib/access/access_lists-delete.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/backend/lib/access/access_lists-delete.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | { | ||||||
|  |   "anyOf": [ | ||||||
|  |     { | ||||||
|  |       "$ref": "roles#/definitions/admin" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "type": "object", | ||||||
|  |       "required": ["permission_access_lists", "roles"], | ||||||
|  |       "properties": { | ||||||
|  |         "permission_access_lists": { | ||||||
|  |           "$ref": "perms#/definitions/manage" | ||||||
|  |         }, | ||||||
|  |         "roles": { | ||||||
|  |           "type": "array", | ||||||
|  |           "items": { | ||||||
|  |             "type": "string", | ||||||
|  |             "enum": ["user"] | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
							
								
								
									
										23
									
								
								src/backend/lib/access/access_lists-get.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/backend/lib/access/access_lists-get.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | { | ||||||
|  |   "anyOf": [ | ||||||
|  |     { | ||||||
|  |       "$ref": "roles#/definitions/admin" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "type": "object", | ||||||
|  |       "required": ["permission_access_lists", "roles"], | ||||||
|  |       "properties": { | ||||||
|  |         "permission_access_lists": { | ||||||
|  |           "$ref": "perms#/definitions/view" | ||||||
|  |         }, | ||||||
|  |         "roles": { | ||||||
|  |           "type": "array", | ||||||
|  |           "items": { | ||||||
|  |             "type": "string", | ||||||
|  |             "enum": ["user"] | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
							
								
								
									
										23
									
								
								src/backend/lib/access/access_lists-list.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/backend/lib/access/access_lists-list.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | { | ||||||
|  |   "anyOf": [ | ||||||
|  |     { | ||||||
|  |       "$ref": "roles#/definitions/admin" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "type": "object", | ||||||
|  |       "required": ["permission_access_lists", "roles"], | ||||||
|  |       "properties": { | ||||||
|  |         "permission_access_lists": { | ||||||
|  |           "$ref": "perms#/definitions/view" | ||||||
|  |         }, | ||||||
|  |         "roles": { | ||||||
|  |           "type": "array", | ||||||
|  |           "items": { | ||||||
|  |             "type": "string", | ||||||
|  |             "enum": ["user"] | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
							
								
								
									
										23
									
								
								src/backend/lib/access/access_lists-update.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/backend/lib/access/access_lists-update.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | { | ||||||
|  |   "anyOf": [ | ||||||
|  |     { | ||||||
|  |       "$ref": "roles#/definitions/admin" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "type": "object", | ||||||
|  |       "required": ["permission_access_lists", "roles"], | ||||||
|  |       "properties": { | ||||||
|  |         "permission_access_lists": { | ||||||
|  |           "$ref": "perms#/definitions/manage" | ||||||
|  |         }, | ||||||
|  |         "roles": { | ||||||
|  |           "type": "array", | ||||||
|  |           "items": { | ||||||
|  |             "type": "string", | ||||||
|  |             "enum": ["user"] | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
							
								
								
									
										23
									
								
								src/backend/lib/access/redirection_hosts-create.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/backend/lib/access/redirection_hosts-create.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | { | ||||||
|  |   "anyOf": [ | ||||||
|  |     { | ||||||
|  |       "$ref": "roles#/definitions/admin" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "type": "object", | ||||||
|  |       "required": ["permission_redirection_hosts", "roles"], | ||||||
|  |       "properties": { | ||||||
|  |         "permission_redirection_hosts": { | ||||||
|  |           "$ref": "perms#/definitions/manage" | ||||||
|  |         }, | ||||||
|  |         "roles": { | ||||||
|  |           "type": "array", | ||||||
|  |           "items": { | ||||||
|  |             "type": "string", | ||||||
|  |             "enum": ["user"] | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
							
								
								
									
										23
									
								
								src/backend/lib/access/redirection_hosts-delete.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/backend/lib/access/redirection_hosts-delete.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | { | ||||||
|  |   "anyOf": [ | ||||||
|  |     { | ||||||
|  |       "$ref": "roles#/definitions/admin" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "type": "object", | ||||||
|  |       "required": ["permission_redirection_hosts", "roles"], | ||||||
|  |       "properties": { | ||||||
|  |         "permission_redirection_hosts": { | ||||||
|  |           "$ref": "perms#/definitions/manage" | ||||||
|  |         }, | ||||||
|  |         "roles": { | ||||||
|  |           "type": "array", | ||||||
|  |           "items": { | ||||||
|  |             "type": "string", | ||||||
|  |             "enum": ["user"] | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
							
								
								
									
										23
									
								
								src/backend/lib/access/redirection_hosts-get.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/backend/lib/access/redirection_hosts-get.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | { | ||||||
|  |   "anyOf": [ | ||||||
|  |     { | ||||||
|  |       "$ref": "roles#/definitions/admin" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "type": "object", | ||||||
|  |       "required": ["permission_redirection_hosts", "roles"], | ||||||
|  |       "properties": { | ||||||
|  |         "permission_redirection_hosts": { | ||||||
|  |           "$ref": "perms#/definitions/view" | ||||||
|  |         }, | ||||||
|  |         "roles": { | ||||||
|  |           "type": "array", | ||||||
|  |           "items": { | ||||||
|  |             "type": "string", | ||||||
|  |             "enum": ["user"] | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
							
								
								
									
										23
									
								
								src/backend/lib/access/redirection_hosts-update.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/backend/lib/access/redirection_hosts-update.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | { | ||||||
|  |   "anyOf": [ | ||||||
|  |     { | ||||||
|  |       "$ref": "roles#/definitions/admin" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "type": "object", | ||||||
|  |       "required": ["permission_redirection_hosts", "roles"], | ||||||
|  |       "properties": { | ||||||
|  |         "permission_redirection_hosts": { | ||||||
|  |           "$ref": "perms#/definitions/manage" | ||||||
|  |         }, | ||||||
|  |         "roles": { | ||||||
|  |           "type": "array", | ||||||
|  |           "items": { | ||||||
|  |             "type": "string", | ||||||
|  |             "enum": ["user"] | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
| @@ -35,6 +35,7 @@ router.use('/nginx/proxy-hosts', require('./nginx/proxy_hosts')); | |||||||
| router.use('/nginx/redirection-hosts', require('./nginx/redirection_hosts')); | router.use('/nginx/redirection-hosts', require('./nginx/redirection_hosts')); | ||||||
| router.use('/nginx/dead-hosts', require('./nginx/dead_hosts')); | router.use('/nginx/dead-hosts', require('./nginx/dead_hosts')); | ||||||
| router.use('/nginx/streams', require('./nginx/streams')); | router.use('/nginx/streams', require('./nginx/streams')); | ||||||
|  | router.use('/nginx/access-lists', require('./nginx/access_lists')); | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * API 404 for all other routes |  * API 404 for all other routes | ||||||
|   | |||||||
							
								
								
									
										150
									
								
								src/backend/routes/api/nginx/access_lists.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								src/backend/routes/api/nginx/access_lists.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,150 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const express            = require('express'); | ||||||
|  | const validator          = require('../../../lib/validator'); | ||||||
|  | const jwtdecode          = require('../../../lib/express/jwt-decode'); | ||||||
|  | const internalAccessList = require('../../../internal/access-list'); | ||||||
|  | const apiValidator       = require('../../../lib/validator/api'); | ||||||
|  |  | ||||||
|  | let router = express.Router({ | ||||||
|  |     caseSensitive: true, | ||||||
|  |     strict:        true, | ||||||
|  |     mergeParams:   true | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * /api/nginx/access-lists | ||||||
|  |  */ | ||||||
|  | router | ||||||
|  |     .route('/') | ||||||
|  |     .options((req, res) => { | ||||||
|  |         res.sendStatus(204); | ||||||
|  |     }) | ||||||
|  |     .all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * GET /api/nginx/access-lists | ||||||
|  |      * | ||||||
|  |      * Retrieve all access-lists | ||||||
|  |      */ | ||||||
|  |     .get((req, res, next) => { | ||||||
|  |         validator({ | ||||||
|  |             additionalProperties: false, | ||||||
|  |             properties:           { | ||||||
|  |                 expand: { | ||||||
|  |                     $ref: 'definitions#/definitions/expand' | ||||||
|  |                 }, | ||||||
|  |                 query:  { | ||||||
|  |                     $ref: 'definitions#/definitions/query' | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, { | ||||||
|  |             expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null), | ||||||
|  |             query:  (typeof req.query.query === 'string' ? req.query.query : null) | ||||||
|  |         }) | ||||||
|  |             .then(data => { | ||||||
|  |                 return internalAccessList.getAll(res.locals.access, data.expand, data.query); | ||||||
|  |             }) | ||||||
|  |             .then(rows => { | ||||||
|  |                 res.status(200) | ||||||
|  |                     .send(rows); | ||||||
|  |             }) | ||||||
|  |             .catch(next); | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * POST /api/nginx/access-lists | ||||||
|  |      * | ||||||
|  |      * Create a new access-list | ||||||
|  |      */ | ||||||
|  |     .post((req, res, next) => { | ||||||
|  |         apiValidator({$ref: 'endpoints/access-lists#/links/1/schema'}, req.body) | ||||||
|  |             .then(payload => { | ||||||
|  |                 return internalAccessList.create(res.locals.access, payload); | ||||||
|  |             }) | ||||||
|  |             .then(result => { | ||||||
|  |                 res.status(201) | ||||||
|  |                     .send(result); | ||||||
|  |             }) | ||||||
|  |             .catch(next); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Specific access-list | ||||||
|  |  * | ||||||
|  |  * /api/nginx/access-lists/123 | ||||||
|  |  */ | ||||||
|  | router | ||||||
|  |     .route('/:host_id') | ||||||
|  |     .options((req, res) => { | ||||||
|  |         res.sendStatus(204); | ||||||
|  |     }) | ||||||
|  |     .all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * GET /api/nginx/access-lists/123 | ||||||
|  |      * | ||||||
|  |      * Retrieve a specific access-list | ||||||
|  |      */ | ||||||
|  |     .get((req, res, next) => { | ||||||
|  |         validator({ | ||||||
|  |             required:             ['host_id'], | ||||||
|  |             additionalProperties: false, | ||||||
|  |             properties:           { | ||||||
|  |                 host_id: { | ||||||
|  |                     $ref: 'definitions#/definitions/id' | ||||||
|  |                 }, | ||||||
|  |                 expand:  { | ||||||
|  |                     $ref: 'definitions#/definitions/expand' | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, { | ||||||
|  |             host_id: req.params.host_id, | ||||||
|  |             expand:  (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null) | ||||||
|  |         }) | ||||||
|  |             .then(data => { | ||||||
|  |                 return internalAccessList.get(res.locals.access, { | ||||||
|  |                     id:     parseInt(data.host_id, 10), | ||||||
|  |                     expand: data.expand | ||||||
|  |                 }); | ||||||
|  |             }) | ||||||
|  |             .then(row => { | ||||||
|  |                 res.status(200) | ||||||
|  |                     .send(row); | ||||||
|  |             }) | ||||||
|  |             .catch(next); | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * PUT /api/nginx/access-lists/123 | ||||||
|  |      * | ||||||
|  |      * Update and existing access-list | ||||||
|  |      */ | ||||||
|  |     .put((req, res, next) => { | ||||||
|  |         apiValidator({$ref: 'endpoints/access-lists#/links/2/schema'}, req.body) | ||||||
|  |             .then(payload => { | ||||||
|  |                 payload.id = parseInt(req.params.host_id, 10); | ||||||
|  |                 return internalAccessList.update(res.locals.access, payload); | ||||||
|  |             }) | ||||||
|  |             .then(result => { | ||||||
|  |                 res.status(200) | ||||||
|  |                     .send(result); | ||||||
|  |             }) | ||||||
|  |             .catch(next); | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * DELETE /api/nginx/access-lists/123 | ||||||
|  |      * | ||||||
|  |      * Update and existing access-list | ||||||
|  |      */ | ||||||
|  |     .delete((req, res, next) => { | ||||||
|  |         internalAccessList.delete(res.locals.access, {id: parseInt(req.params.host_id, 10)}) | ||||||
|  |             .then(result => { | ||||||
|  |                 res.status(200) | ||||||
|  |                     .send(result); | ||||||
|  |             }) | ||||||
|  |             .catch(next); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  | module.exports = router; | ||||||
| @@ -104,7 +104,7 @@ router | |||||||
|         }) |         }) | ||||||
|             .then(data => { |             .then(data => { | ||||||
|                 return internalRedirectionHost.get(res.locals.access, { |                 return internalRedirectionHost.get(res.locals.access, { | ||||||
|                     id:     data.host_id, |                     id:     parseInt(data.host_id, 10), | ||||||
|                     expand: data.expand |                     expand: data.expand | ||||||
|                 }); |                 }); | ||||||
|             }) |             }) | ||||||
| @@ -123,7 +123,7 @@ router | |||||||
|     .put((req, res, next) => { |     .put((req, res, next) => { | ||||||
|         apiValidator({$ref: 'endpoints/redirection-hosts#/links/2/schema'}, req.body) |         apiValidator({$ref: 'endpoints/redirection-hosts#/links/2/schema'}, req.body) | ||||||
|             .then(payload => { |             .then(payload => { | ||||||
|                 payload.id = req.params.host_id; |                 payload.id = parseInt(req.params.host_id, 10); | ||||||
|                 return internalRedirectionHost.update(res.locals.access, payload); |                 return internalRedirectionHost.update(res.locals.access, payload); | ||||||
|             }) |             }) | ||||||
|             .then(result => { |             .then(result => { | ||||||
| @@ -139,7 +139,7 @@ router | |||||||
|      * Update and existing redirection-host |      * Update and existing redirection-host | ||||||
|      */ |      */ | ||||||
|     .delete((req, res, next) => { |     .delete((req, res, next) => { | ||||||
|         internalRedirectionHost.delete(res.locals.access, {id: req.params.host_id}) |         internalRedirectionHost.delete(res.locals.access, {id: parseInt(req.params.host_id, 10)}) | ||||||
|             .then(result => { |             .then(result => { | ||||||
|                 res.status(200) |                 res.status(200) | ||||||
|                     .send(result); |                     .send(result); | ||||||
| @@ -147,4 +147,38 @@ router | |||||||
|             .catch(next); |             .catch(next); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Specific redirection-host Certificates | ||||||
|  |  * | ||||||
|  |  * /api/nginx/redirection-hosts/123/certificates | ||||||
|  |  */ | ||||||
|  | router | ||||||
|  |     .route('/:host_id/certificates') | ||||||
|  |     .options((req, res) => { | ||||||
|  |         res.sendStatus(204); | ||||||
|  |     }) | ||||||
|  |     .all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * POST /api/nginx/redirection-hosts/123/certificates | ||||||
|  |      * | ||||||
|  |      * Upload certifications | ||||||
|  |      */ | ||||||
|  |     .post((req, res, next) => { | ||||||
|  |         if (!req.files) { | ||||||
|  |             res.status(400) | ||||||
|  |                 .send({error: 'No files were uploaded'}); | ||||||
|  |         } else { | ||||||
|  |             internalRedirectionHost.setCerts(res.locals.access, { | ||||||
|  |                 id:    parseInt(req.params.host_id, 10), | ||||||
|  |                 files: req.files | ||||||
|  |             }) | ||||||
|  |                 .then(result => { | ||||||
|  |                     res.status(200) | ||||||
|  |                         .send(result); | ||||||
|  |                 }) | ||||||
|  |                 .catch(next); | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  |  | ||||||
| module.exports = router; | module.exports = router; | ||||||
|   | |||||||
| @@ -116,6 +116,12 @@ | |||||||
|       "type": "integer", |       "type": "integer", | ||||||
|       "minimum": 1 |       "minimum": 1 | ||||||
|     }, |     }, | ||||||
|  |     "access_list_id": { | ||||||
|  |       "description": "Access List ID", | ||||||
|  |       "example": 1234, | ||||||
|  |       "type": "integer", | ||||||
|  |       "minimum": 0 | ||||||
|  |     }, | ||||||
|     "name": { |     "name": { | ||||||
|       "type": "string", |       "type": "string", | ||||||
|       "minLength": 1, |       "minLength": 1, | ||||||
| @@ -135,6 +141,12 @@ | |||||||
|       "minLength": 8, |       "minLength": 8, | ||||||
|       "maxLength": 255 |       "maxLength": 255 | ||||||
|     }, |     }, | ||||||
|  |     "domain_name": { | ||||||
|  |       "description": "Domain Name", | ||||||
|  |       "example": "jc21.com", | ||||||
|  |       "type": "string", | ||||||
|  |       "pattern": "^(?:[^.*]+\\.?)+[^.]$" | ||||||
|  |     }, | ||||||
|     "domain_names": { |     "domain_names": { | ||||||
|       "description": "Domain Names separated by a comma", |       "description": "Domain Names separated by a comma", | ||||||
|       "example": "*.jc21.com,blog.jc21.com", |       "example": "*.jc21.com,blog.jc21.com", | ||||||
| @@ -159,6 +171,16 @@ | |||||||
|     "ssl_provider": { |     "ssl_provider": { | ||||||
|       "type": "string", |       "type": "string", | ||||||
|       "pattern": "^(letsencrypt|other)$" |       "pattern": "^(letsencrypt|other)$" | ||||||
|  |     }, | ||||||
|  |     "block_exploits": { | ||||||
|  |       "description": "Should we block common exploits", | ||||||
|  |       "example": true, | ||||||
|  |       "type": "boolean" | ||||||
|  |     }, | ||||||
|  |     "caching_enabled": { | ||||||
|  |       "description": "Should we cache assets", | ||||||
|  |       "example": true, | ||||||
|  |       "type": "boolean" | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -36,6 +36,15 @@ | |||||||
|     "ssl_provider": { |     "ssl_provider": { | ||||||
|       "$ref": "../definitions.json#/definitions/ssl_provider" |       "$ref": "../definitions.json#/definitions/ssl_provider" | ||||||
|     }, |     }, | ||||||
|  |     "block_exploits": { | ||||||
|  |       "$ref": "../definitions.json#/definitions/block_exploits" | ||||||
|  |     }, | ||||||
|  |     "caching_enabled": { | ||||||
|  |       "$ref": "../definitions.json#/definitions/caching_enabled" | ||||||
|  |     }, | ||||||
|  |     "access_list_id": { | ||||||
|  |       "$ref": "../definitions.json#/definitions/access_list_id" | ||||||
|  |     }, | ||||||
|     "meta": { |     "meta": { | ||||||
|       "type": "object", |       "type": "object", | ||||||
|       "additionalProperties": false, |       "additionalProperties": false, | ||||||
| @@ -78,6 +87,15 @@ | |||||||
|     "ssl_provider": { |     "ssl_provider": { | ||||||
|       "$ref": "#/definitions/ssl_provider" |       "$ref": "#/definitions/ssl_provider" | ||||||
|     }, |     }, | ||||||
|  |     "block_exploits": { | ||||||
|  |       "$ref": "#/definitions/block_exploits" | ||||||
|  |     }, | ||||||
|  |     "caching_enabled": { | ||||||
|  |       "$ref": "#/definitions/caching_enabled" | ||||||
|  |     }, | ||||||
|  |     "access_list_id": { | ||||||
|  |       "$ref": "#/definitions/access_list_id" | ||||||
|  |     }, | ||||||
|     "meta": { |     "meta": { | ||||||
|       "$ref": "#/definitions/meta" |       "$ref": "#/definitions/meta" | ||||||
|     } |     } | ||||||
| @@ -136,6 +154,15 @@ | |||||||
|           "ssl_provider": { |           "ssl_provider": { | ||||||
|             "$ref": "#/definitions/ssl_provider" |             "$ref": "#/definitions/ssl_provider" | ||||||
|           }, |           }, | ||||||
|  |           "block_exploits": { | ||||||
|  |             "$ref": "#/definitions/block_exploits" | ||||||
|  |           }, | ||||||
|  |           "caching_enabled": { | ||||||
|  |             "$ref": "#/definitions/caching_enabled" | ||||||
|  |           }, | ||||||
|  |           "access_list_id": { | ||||||
|  |             "$ref": "#/definitions/access_list_id" | ||||||
|  |           }, | ||||||
|           "meta": { |           "meta": { | ||||||
|             "$ref": "#/definitions/meta" |             "$ref": "#/definitions/meta" | ||||||
|           } |           } | ||||||
| @@ -178,6 +205,15 @@ | |||||||
|           "ssl_provider": { |           "ssl_provider": { | ||||||
|             "$ref": "#/definitions/ssl_provider" |             "$ref": "#/definitions/ssl_provider" | ||||||
|           }, |           }, | ||||||
|  |           "block_exploits": { | ||||||
|  |             "$ref": "#/definitions/block_exploits" | ||||||
|  |           }, | ||||||
|  |           "caching_enabled": { | ||||||
|  |             "$ref": "#/definitions/caching_enabled" | ||||||
|  |           }, | ||||||
|  |           "access_list_id": { | ||||||
|  |             "$ref": "#/definitions/access_list_id" | ||||||
|  |           }, | ||||||
|           "meta": { |           "meta": { | ||||||
|             "$ref": "#/definitions/meta" |             "$ref": "#/definitions/meta" | ||||||
|           } |           } | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| { | { | ||||||
|   "$schema": "http://json-schema.org/draft-07/schema#", |   "$schema": "http://json-schema.org/draft-07/schema#", | ||||||
|   "$id": "endpoints/redirection-hosts", |   "$id": "endpoints/redirection-hosts", | ||||||
|   "title": "Users", |   "title": "Redirection Hosts", | ||||||
|   "description": "Endpoints relating to Redirection Hosts", |   "description": "Endpoints relating to Redirection Hosts", | ||||||
|   "stability": "stable", |   "stability": "stable", | ||||||
|   "type": "object", |   "type": "object", | ||||||
| @@ -15,49 +15,83 @@ | |||||||
|     "modified_on": { |     "modified_on": { | ||||||
|       "$ref": "../definitions.json#/definitions/modified_on" |       "$ref": "../definitions.json#/definitions/modified_on" | ||||||
|     }, |     }, | ||||||
|     "name": { |     "domain_names": { | ||||||
|       "description": "Name", |       "$ref": "../definitions.json#/definitions/domain_names" | ||||||
|       "example": "Jamie Curnow", |  | ||||||
|       "type": "string", |  | ||||||
|       "minLength": 2, |  | ||||||
|       "maxLength": 100 |  | ||||||
|     }, |     }, | ||||||
|     "nickname": { |     "forward_domain_name": { | ||||||
|       "description": "Nickname", |       "$ref": "../definitions.json#/definitions/domain_name" | ||||||
|       "example": "Jamie", |  | ||||||
|       "type": "string", |  | ||||||
|       "minLength": 2, |  | ||||||
|       "maxLength": 50 |  | ||||||
|     }, |     }, | ||||||
|     "email": { |     "preserve_path": { | ||||||
|       "$ref": "../definitions.json#/definitions/email" |       "description": "Should the path be preserved", | ||||||
|     }, |       "example": true, | ||||||
|     "avatar": { |  | ||||||
|       "description": "Avatar", |  | ||||||
|       "example": "http://somewhere.jpg", |  | ||||||
|       "type": "string", |  | ||||||
|       "minLength": 2, |  | ||||||
|       "maxLength": 150, |  | ||||||
|       "readOnly": true |  | ||||||
|     }, |  | ||||||
|     "roles": { |  | ||||||
|       "description": "Roles", |  | ||||||
|       "example": [ |  | ||||||
|         "admin" |  | ||||||
|       ], |  | ||||||
|       "type": "array" |  | ||||||
|     }, |  | ||||||
|     "is_disabled": { |  | ||||||
|       "description": "Is Disabled", |  | ||||||
|       "example": false, |  | ||||||
|       "type": "boolean" |       "type": "boolean" | ||||||
|  |     }, | ||||||
|  |     "ssl_enabled": { | ||||||
|  |       "$ref": "../definitions.json#/definitions/ssl_enabled" | ||||||
|  |     }, | ||||||
|  |     "ssl_forced": { | ||||||
|  |       "$ref": "../definitions.json#/definitions/ssl_forced" | ||||||
|  |     }, | ||||||
|  |     "ssl_provider": { | ||||||
|  |       "$ref": "../definitions.json#/definitions/ssl_provider" | ||||||
|  |     }, | ||||||
|  |     "block_exploits": { | ||||||
|  |       "$ref": "../definitions.json#/definitions/block_exploits" | ||||||
|  |     }, | ||||||
|  |     "meta": { | ||||||
|  |       "type": "object", | ||||||
|  |       "additionalProperties": false, | ||||||
|  |       "properties": { | ||||||
|  |         "letsencrypt_email": { | ||||||
|  |           "type": "string", | ||||||
|  |           "format": "email" | ||||||
|  |         }, | ||||||
|  |         "letsencrypt_agree": { | ||||||
|  |           "type": "boolean" | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   "properties": { | ||||||
|  |     "id": { | ||||||
|  |       "$ref": "#/definitions/id" | ||||||
|  |     }, | ||||||
|  |     "created_on": { | ||||||
|  |       "$ref": "#/definitions/created_on" | ||||||
|  |     }, | ||||||
|  |     "modified_on": { | ||||||
|  |       "$ref": "#/definitions/modified_on" | ||||||
|  |     }, | ||||||
|  |     "domain_names": { | ||||||
|  |       "$ref": "#/definitions/domain_names" | ||||||
|  |     }, | ||||||
|  |     "forward_domain_name": { | ||||||
|  |       "$ref": "#/definitions/forward_domain_name" | ||||||
|  |     }, | ||||||
|  |     "preserve_path": { | ||||||
|  |       "$ref": "#/definitions/preserve_path" | ||||||
|  |     }, | ||||||
|  |     "ssl_enabled": { | ||||||
|  |       "$ref": "#/definitions/ssl_enabled" | ||||||
|  |     }, | ||||||
|  |     "ssl_forced": { | ||||||
|  |       "$ref": "#/definitions/ssl_forced" | ||||||
|  |     }, | ||||||
|  |     "ssl_provider": { | ||||||
|  |       "$ref": "#/definitions/ssl_provider" | ||||||
|  |     }, | ||||||
|  |     "block_exploits": { | ||||||
|  |       "$ref": "#/definitions/block_exploits" | ||||||
|  |     }, | ||||||
|  |     "meta": { | ||||||
|  |       "$ref": "#/definitions/meta" | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   "links": [ |   "links": [ | ||||||
|     { |     { | ||||||
|       "title": "List", |       "title": "List", | ||||||
|       "description": "Returns a list of Users", |       "description": "Returns a list of Redirection Hosts", | ||||||
|       "href": "/users", |       "href": "/nginx/redirection-hosts", | ||||||
|       "access": "private", |       "access": "private", | ||||||
|       "method": "GET", |       "method": "GET", | ||||||
|       "rel": "self", |       "rel": "self", | ||||||
| @@ -73,8 +107,8 @@ | |||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "title": "Create", |       "title": "Create", | ||||||
|       "description": "Creates a new User", |       "description": "Creates a new Redirection Host", | ||||||
|       "href": "/users", |       "href": "/nginx/redirection-hosts", | ||||||
|       "access": "private", |       "access": "private", | ||||||
|       "method": "POST", |       "method": "POST", | ||||||
|       "rel": "create", |       "rel": "create", | ||||||
| @@ -84,33 +118,33 @@ | |||||||
|       "schema": { |       "schema": { | ||||||
|         "type": "object", |         "type": "object", | ||||||
|         "required": [ |         "required": [ | ||||||
|           "name", |           "domain_names", | ||||||
|           "nickname", |           "forward_domain_name" | ||||||
|           "email" |  | ||||||
|         ], |         ], | ||||||
|         "properties": { |         "properties": { | ||||||
|           "name": { |           "domain_names": { | ||||||
|             "$ref": "#/definitions/name" |             "$ref": "#/definitions/domain_names" | ||||||
|           }, |           }, | ||||||
|           "nickname": { |           "forward_domain_name": { | ||||||
|             "$ref": "#/definitions/nickname" |             "$ref": "#/definitions/forward_domain_name" | ||||||
|           }, |           }, | ||||||
|           "email": { |           "preserve_path": { | ||||||
|             "$ref": "#/definitions/email" |             "$ref": "#/definitions/preserve_path" | ||||||
|           }, |           }, | ||||||
|           "roles": { |           "ssl_enabled": { | ||||||
|             "$ref": "#/definitions/roles" |             "$ref": "#/definitions/ssl_enabled" | ||||||
|           }, |           }, | ||||||
|           "is_disabled": { |           "ssl_forced": { | ||||||
|             "$ref": "#/definitions/is_disabled" |             "$ref": "#/definitions/ssl_forced" | ||||||
|           }, |           }, | ||||||
|           "auth": { |           "ssl_provider": { | ||||||
|             "type": "object", |             "$ref": "#/definitions/ssl_provider" | ||||||
|             "description": "Auth Credentials", |           }, | ||||||
|             "example": { |           "block_exploits": { | ||||||
|               "type": "password", |             "$ref": "#/definitions/block_exploits" | ||||||
|               "secret": "bigredhorsebanana" |           }, | ||||||
|             } |           "meta": { | ||||||
|  |             "$ref": "#/definitions/meta" | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       }, |       }, | ||||||
| @@ -122,8 +156,8 @@ | |||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "title": "Update", |       "title": "Update", | ||||||
|       "description": "Updates a existing User", |       "description": "Updates a existing Redirection Host", | ||||||
|       "href": "/users/{definitions.identity.example}", |       "href": "/nginx/redirection-hosts/{definitions.identity.example}", | ||||||
|       "access": "private", |       "access": "private", | ||||||
|       "method": "PUT", |       "method": "PUT", | ||||||
|       "rel": "update", |       "rel": "update", | ||||||
| @@ -133,20 +167,29 @@ | |||||||
|       "schema": { |       "schema": { | ||||||
|         "type": "object", |         "type": "object", | ||||||
|         "properties": { |         "properties": { | ||||||
|           "name": { |           "domain_names": { | ||||||
|             "$ref": "#/definitions/name" |             "$ref": "#/definitions/domain_names" | ||||||
|           }, |           }, | ||||||
|           "nickname": { |           "forward_domain_name": { | ||||||
|             "$ref": "#/definitions/nickname" |             "$ref": "#/definitions/forward_domain_name" | ||||||
|           }, |           }, | ||||||
|           "email": { |           "preserve_path": { | ||||||
|             "$ref": "#/definitions/email" |             "$ref": "#/definitions/preserve_path" | ||||||
|           }, |           }, | ||||||
|           "roles": { |           "ssl_enabled": { | ||||||
|             "$ref": "#/definitions/roles" |             "$ref": "#/definitions/ssl_enabled" | ||||||
|           }, |           }, | ||||||
|           "is_disabled": { |           "ssl_forced": { | ||||||
|             "$ref": "#/definitions/is_disabled" |             "$ref": "#/definitions/ssl_forced" | ||||||
|  |           }, | ||||||
|  |           "ssl_provider": { | ||||||
|  |             "$ref": "#/definitions/ssl_provider" | ||||||
|  |           }, | ||||||
|  |           "block_exploits": { | ||||||
|  |             "$ref": "#/definitions/block_exploits" | ||||||
|  |           }, | ||||||
|  |           "meta": { | ||||||
|  |             "$ref": "#/definitions/meta" | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       }, |       }, | ||||||
| @@ -158,8 +201,8 @@ | |||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "title": "Delete", |       "title": "Delete", | ||||||
|       "description": "Deletes a existing User", |       "description": "Deletes a existing Redirection Host", | ||||||
|       "href": "/users/{definitions.identity.example}", |       "href": "/nginx/redirection-hosts/{definitions.identity.example}", | ||||||
|       "access": "private", |       "access": "private", | ||||||
|       "method": "DELETE", |       "method": "DELETE", | ||||||
|       "rel": "delete", |       "rel": "delete", | ||||||
| @@ -170,34 +213,5 @@ | |||||||
|         "type": "boolean" |         "type": "boolean" | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   ], |   ] | ||||||
|   "properties": { |  | ||||||
|     "id": { |  | ||||||
|       "$ref": "#/definitions/id" |  | ||||||
|     }, |  | ||||||
|     "created_on": { |  | ||||||
|       "$ref": "#/definitions/created_on" |  | ||||||
|     }, |  | ||||||
|     "modified_on": { |  | ||||||
|       "$ref": "#/definitions/modified_on" |  | ||||||
|     }, |  | ||||||
|     "name": { |  | ||||||
|       "$ref": "#/definitions/name" |  | ||||||
|     }, |  | ||||||
|     "nickname": { |  | ||||||
|       "$ref": "#/definitions/nickname" |  | ||||||
|     }, |  | ||||||
|     "email": { |  | ||||||
|       "$ref": "#/definitions/email" |  | ||||||
|     }, |  | ||||||
|     "avatar": { |  | ||||||
|       "$ref": "#/definitions/avatar" |  | ||||||
|     }, |  | ||||||
|     "roles": { |  | ||||||
|       "$ref": "#/definitions/roles" |  | ||||||
|     }, |  | ||||||
|     "is_disabled": { |  | ||||||
|       "$ref": "#/definitions/is_disabled" |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -377,7 +377,7 @@ module.exports = { | |||||||
|              * @params {Promise} |              * @params {Promise} | ||||||
|              */ |              */ | ||||||
|             setCerts: function (id, form_data) { |             setCerts: function (id, form_data) { | ||||||
|                 return upload('nginx/redirection-hosts/' + id + '/certificates', form_data); |                 return FileUpload('nginx/redirection-hosts/' + id + '/certificates', form_data); | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|  |  | ||||||
| @@ -460,46 +460,46 @@ module.exports = { | |||||||
|              * @params {Promise} |              * @params {Promise} | ||||||
|              */ |              */ | ||||||
|             setCerts: function (id, form_data) { |             setCerts: function (id, form_data) { | ||||||
|                 return upload('nginx/dead-hosts/' + id + '/certificates', form_data); |                 return FileUpload('nginx/dead-hosts/' + id + '/certificates', form_data); | ||||||
|             } |             } | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     AccessLists: { |  | ||||||
|         /** |  | ||||||
|          * @param   {Array}    [expand] |  | ||||||
|          * @param   {String}   [query] |  | ||||||
|          * @returns {Promise} |  | ||||||
|          */ |  | ||||||
|         getAll: function (expand, query) { |  | ||||||
|             return getAllObjects('access-lists', expand, query); |  | ||||||
|         }, |         }, | ||||||
|  |  | ||||||
|         /** |         AccessLists: { | ||||||
|          * @param {Object}  data |             /** | ||||||
|          */ |              * @param   {Array}    [expand] | ||||||
|         create: function (data) { |              * @param   {String}   [query] | ||||||
|             return fetch('post', 'access-lists', data); |              * @returns {Promise} | ||||||
|         }, |              */ | ||||||
|  |             getAll: function (expand, query) { | ||||||
|  |                 return getAllObjects('nginx/access-lists', expand, query); | ||||||
|  |             }, | ||||||
|  |  | ||||||
|         /** |             /** | ||||||
|          * @param   {Object}   data |              * @param {Object}  data | ||||||
|          * @param   {Integer}  data.id |              */ | ||||||
|          * @returns {Promise} |             create: function (data) { | ||||||
|          */ |                 return fetch('post', 'nginx/access-lists', data); | ||||||
|         update: function (data) { |             }, | ||||||
|             let id = data.id; |  | ||||||
|             delete data.id; |  | ||||||
|             return fetch('put', 'access-lists/' + id, data); |  | ||||||
|         }, |  | ||||||
|  |  | ||||||
|         /** |             /** | ||||||
|          * @param   {Integer}  id |              * @param   {Object}   data | ||||||
|          * @returns {Promise} |              * @param   {Integer}  data.id | ||||||
|          */ |              * @returns {Promise} | ||||||
|         delete: function (id) { |              */ | ||||||
|             return fetch('delete', 'access-lists/' + id); |             update: function (data) { | ||||||
|         } |                 let id = data.id; | ||||||
|  |                 delete data.id; | ||||||
|  |                 return fetch('put', 'nginx/access-lists/' + id, data); | ||||||
|  |             }, | ||||||
|  |  | ||||||
|  |             /** | ||||||
|  |              * @param   {Integer}  id | ||||||
|  |              * @returns {Promise} | ||||||
|  |              */ | ||||||
|  |             delete: function (id) { | ||||||
|  |                 return fetch('delete', 'nginx/access-lists/' + id); | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     AuditLog: { |     AuditLog: { | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| <div class="card"> | <div class="card"> | ||||||
|  |     <div class="card-status bg-teal"></div> | ||||||
|     <div class="card-header"> |     <div class="card-header"> | ||||||
|         <h3 class="card-title"><%- i18n('audit-log', 'title') %></h3> |         <h3 class="card-title"><%- i18n('audit-log', 'title') %></h3> | ||||||
|     </div> |     </div> | ||||||
|   | |||||||
| @@ -174,6 +174,32 @@ module.exports = { | |||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Nginx Redirection Host Form | ||||||
|  |      * | ||||||
|  |      * @param [model] | ||||||
|  |      */ | ||||||
|  |     showNginxRedirectionForm: function (model) { | ||||||
|  |         if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) { | ||||||
|  |             require(['./main', './nginx/redirection/form'], function (App, View) { | ||||||
|  |                 App.UI.showModalDialog(new View({model: model})); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Proxy Redirection Delete Confirm | ||||||
|  |      * | ||||||
|  |      * @param model | ||||||
|  |      */ | ||||||
|  |     showNginxRedirectionDeleteConfirm: function (model) { | ||||||
|  |         if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) { | ||||||
|  |             require(['./main', './nginx/redirection/delete'], function (App, View) { | ||||||
|  |                 App.UI.showModalDialog(new View({model: model})); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Nginx Stream Hosts |      * Nginx Stream Hosts | ||||||
|      */ |      */ | ||||||
| @@ -216,6 +242,19 @@ module.exports = { | |||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Nginx Access List Form | ||||||
|  |      * | ||||||
|  |      * @param [model] | ||||||
|  |      */ | ||||||
|  |     showNginxAccessListForm: function (model) { | ||||||
|  |         if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) { | ||||||
|  |             require(['./main', './nginx/access/form'], function (App, View) { | ||||||
|  |                 App.UI.showModalDialog(new View({model: model})); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Audit Log |      * Audit Log | ||||||
|      */ |      */ | ||||||
|   | |||||||
							
								
								
									
										19
									
								
								src/frontend/js/app/nginx/access/delete.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/frontend/js/app/nginx/access/delete.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | <div class="modal-content"> | ||||||
|  |     <div class="modal-header"> | ||||||
|  |         <h5 class="modal-title"><%- i18n('access-lists', 'delete') %></h5> | ||||||
|  |         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> | ||||||
|  |     </div> | ||||||
|  |     <div class="modal-body"> | ||||||
|  |         <form> | ||||||
|  |             <div class="row"> | ||||||
|  |                 <div class="col-sm-12 col-md-12"> | ||||||
|  |                     <%= i18n('access-lists', 'delete-confirm') %> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         </form> | ||||||
|  |     </div> | ||||||
|  |     <div class="modal-footer"> | ||||||
|  |         <button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button> | ||||||
|  |         <button type="button" class="btn btn-danger save"><%- i18n('str', 'sure') %></button> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
							
								
								
									
										36
									
								
								src/frontend/js/app/nginx/access/delete.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/frontend/js/app/nginx/access/delete.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const Mn       = require('backbone.marionette'); | ||||||
|  | const App      = require('../../main'); | ||||||
|  | const template = require('./delete.ejs'); | ||||||
|  |  | ||||||
|  | require('jquery-serializejson'); | ||||||
|  |  | ||||||
|  | module.exports = Mn.View.extend({ | ||||||
|  |     template:  template, | ||||||
|  |     className: 'modal-dialog', | ||||||
|  |  | ||||||
|  |     ui: { | ||||||
|  |         form:    'form', | ||||||
|  |         buttons: '.modal-footer button', | ||||||
|  |         cancel:  'button.cancel', | ||||||
|  |         save:    'button.save' | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     events: { | ||||||
|  |  | ||||||
|  |         'click @ui.save': function (e) { | ||||||
|  |             e.preventDefault(); | ||||||
|  |  | ||||||
|  |             App.Api.Nginx.ProxyHosts.delete(this.model.get('id')) | ||||||
|  |                 .then(() => { | ||||||
|  |                     App.Controller.showNginxAccess(); | ||||||
|  |                     App.UI.closeModal(); | ||||||
|  |                 }) | ||||||
|  |                 .catch(err => { | ||||||
|  |                     alert(err.message); | ||||||
|  |                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||||
|  |                 }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | }); | ||||||
							
								
								
									
										122
									
								
								src/frontend/js/app/nginx/access/form.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								src/frontend/js/app/nginx/access/form.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | |||||||
|  | <div class="modal-content"> | ||||||
|  |     <div class="modal-header"> | ||||||
|  |         <h5 class="modal-title"><%- i18n('proxy-hosts', 'form-title', {id: id}) %></h5> | ||||||
|  |         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> | ||||||
|  |     </div> | ||||||
|  |     <div class="modal-body has-tabs"> | ||||||
|  |         <form> | ||||||
|  |             <ul class="nav nav-tabs" role="tablist"> | ||||||
|  |                 <li role="presentation" class="nav-item"><a href="#details" aria-controls="tab1" role="tab" data-toggle="tab" class="nav-link active"><i class="fe fe-zap"></i> <%- i18n('all-hosts', 'details') %></a></li> | ||||||
|  |                 <li role="presentation" class="nav-item"><a href="#ssl-options" aria-controls="tab2" role="tab" data-toggle="tab" class="nav-link"><i class="fe fe-shield"></i> <%- i18n('str', 'ssl') %></a></li> | ||||||
|  |             </ul> | ||||||
|  |             <div class="tab-content"> | ||||||
|  |                 <!-- Details --> | ||||||
|  |                 <div role="tabpanel" class="tab-pane active" id="details"> | ||||||
|  |                     <div class="row"> | ||||||
|  |  | ||||||
|  |                         <div class="col-sm-12 col-md-12"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="form-label"><%- i18n('all-hosts', 'domain-names') %> <span class="form-required">*</span></label> | ||||||
|  |                                 <input type="text" name="domain_names" class="form-control" id="input-domains" value="<%- domain_names.join(',') %>" required> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="col-sm-8 col-md-8"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="form-label"><%- i18n('proxy-hosts', 'forward-ip') %><span class="form-required">*</span></label> | ||||||
|  |                                 <input type="text" name="forward_ip" class="form-control text-monospace" placeholder="000.000.000.000" value="<%- forward_ip %>" autocomplete="off" maxlength="15" required> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="col-sm-4 col-md-4"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="form-label"><%- i18n('proxy-hosts', 'forward-port') %> <span class="form-required">*</span></label> | ||||||
|  |                                 <input name="forward_port" type="number" class="form-control text-monospace" placeholder="80" value="<%- forward_port %>" required> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |  | ||||||
|  |                 <!-- SSL --> | ||||||
|  |                 <div role="tabpanel" class="tab-pane" id="ssl-options"> | ||||||
|  |                     <div class="row"> | ||||||
|  |                         <div class="col-sm-6 col-md-6"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="custom-switch"> | ||||||
|  |                                     <input type="checkbox" class="custom-switch-input" name="ssl_enabled" value="1"<%- ssl_enabled ? ' checked' : '' %>> | ||||||
|  |                                     <span class="custom-switch-indicator"></span> | ||||||
|  |                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'enable-ssl') %></span> | ||||||
|  |                                 </label> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="col-sm-6 col-md-6"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="custom-switch"> | ||||||
|  |                                     <input type="checkbox" class="custom-switch-input" name="ssl_forced" value="1"<%- ssl_forced ? ' checked' : '' %><%- ssl_enabled ? '' : ' disabled' %>> | ||||||
|  |                                     <span class="custom-switch-indicator"></span> | ||||||
|  |                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'force-ssl') %></span> | ||||||
|  |                                 </label> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="col-sm-12 col-md-12"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="form-label"><%- i18n('all-hosts', 'cert-provider') %></label> | ||||||
|  |                                 <div class="selectgroup w-100"> | ||||||
|  |                                     <label class="selectgroup-item"> | ||||||
|  |                                         <input type="radio" name="ssl_provider" value="letsencrypt" class="selectgroup-input"<%- ssl_provider !== 'other' ? ' checked' : '' %>> | ||||||
|  |                                         <span class="selectgroup-button"><%- i18n('ssl', 'letsencrypt') %></span> | ||||||
|  |                                     </label> | ||||||
|  |                                     <label class="selectgroup-item"> | ||||||
|  |                                         <input type="radio" name="ssl_provider" value="other" class="selectgroup-input"<%- ssl_provider === 'other' ? ' checked' : '' %>> | ||||||
|  |                                         <span class="selectgroup-button"><%- i18n('ssl', 'other') %></span> | ||||||
|  |                                     </label> | ||||||
|  |                                 </div> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |  | ||||||
|  |                         <!-- Lets encrypt --> | ||||||
|  |                         <div class="col-sm-12 col-md-12 letsencrypt-ssl"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="form-label"><%- i18n('ssl', 'letsencrypt-email') %> <span class="form-required">*</span></label> | ||||||
|  |                                 <input name="meta[letsencrypt_email]" type="email" class="form-control" placeholder="" value="<%- getLetsencryptEmail() %>" required> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="col-sm-12 col-md-12 letsencrypt-ssl"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="custom-switch"> | ||||||
|  |                                     <input type="checkbox" class="custom-switch-input" name="meta[letsencrypt_agree]" value="1" required<%- getLetsencryptAgree() ? ' checked' : '' %>> | ||||||
|  |                                     <span class="custom-switch-indicator"></span> | ||||||
|  |                                     <span class="custom-switch-description"><%= i18n('ssl', 'letsencrypt-agree', {url: 'https://letsencrypt.org/repository/'}) %> <span class="form-required">*</span></span> | ||||||
|  |                                 </label> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |  | ||||||
|  |                         <!-- Other --> | ||||||
|  |                         <div class="col-sm-12 col-md-12 other-ssl"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <div class="form-label"><%- i18n('all-hosts', 'other-certificate') %></div> | ||||||
|  |                                 <div class="custom-file"> | ||||||
|  |                                     <input type="file" class="custom-file-input" name="meta[other_ssl_certificate]" id="other_ssl_certificate"> | ||||||
|  |                                     <label class="custom-file-label"><%- i18n('str', 'choose-file') %></label> | ||||||
|  |                                 </div> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="col-sm-12 col-md-12 other-ssl"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <div class="form-label"><%- i18n('all-hosts', 'other-certificate-key') %></div> | ||||||
|  |                                 <div class="custom-file"> | ||||||
|  |                                     <input type="file" class="custom-file-input" name="meta[other_ssl_certificate_key]" id="other_ssl_certificate_key"> | ||||||
|  |                                     <label class="custom-file-label"><%- i18n('str', 'choose-file') %></label> | ||||||
|  |                                 </div> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         </form> | ||||||
|  |     </div> | ||||||
|  |     <div class="modal-footer"> | ||||||
|  |         <button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button> | ||||||
|  |         <button type="button" class="btn btn-teal save"><%- i18n('str', 'save') %></button> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
							
								
								
									
										195
									
								
								src/frontend/js/app/nginx/access/form.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										195
									
								
								src/frontend/js/app/nginx/access/form.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,195 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const _              = require('underscore'); | ||||||
|  | const Mn             = require('backbone.marionette'); | ||||||
|  | const App            = require('../../main'); | ||||||
|  | const ProxyHostModel = require('../../../models/proxy-host'); | ||||||
|  | const template       = require('./form.ejs'); | ||||||
|  |  | ||||||
|  | require('jquery-serializejson'); | ||||||
|  | require('jquery-mask-plugin'); | ||||||
|  | require('selectize'); | ||||||
|  |  | ||||||
|  | module.exports = Mn.View.extend({ | ||||||
|  |     template:  template, | ||||||
|  |     className: 'modal-dialog', | ||||||
|  |     max_file_size: 5120, | ||||||
|  |  | ||||||
|  |     ui: { | ||||||
|  |         form:                      'form', | ||||||
|  |         domain_names:              'input[name="domain_names"]', | ||||||
|  |         forward_ip:                'input[name="forward_ip"]', | ||||||
|  |         buttons:                   '.modal-footer button', | ||||||
|  |         cancel:                    'button.cancel', | ||||||
|  |         save:                      'button.save', | ||||||
|  |         ssl_enabled:               'input[name="ssl_enabled"]', | ||||||
|  |         ssl_options:               '#ssl-options input', | ||||||
|  |         ssl_provider:              'input[name="ssl_provider"]', | ||||||
|  |         other_ssl_certificate:     '#other_ssl_certificate', | ||||||
|  |         other_ssl_certificate_key: '#other_ssl_certificate_key', | ||||||
|  |  | ||||||
|  |         // SSL hiding and showing | ||||||
|  |         all_ssl:         '.letsencrypt-ssl, .other-ssl', | ||||||
|  |         letsencrypt_ssl: '.letsencrypt-ssl', | ||||||
|  |         other_ssl:       '.other-ssl' | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     events: { | ||||||
|  |         'change @ui.ssl_enabled': function () { | ||||||
|  |             let enabled = this.ui.ssl_enabled.prop('checked'); | ||||||
|  |             this.ui.ssl_options.not(this.ui.ssl_enabled).prop('disabled', !enabled).parents('.form-group').css('opacity', enabled ? 1 : 0.5); | ||||||
|  |             this.ui.ssl_provider.trigger('change'); | ||||||
|  |         }, | ||||||
|  |  | ||||||
|  |         'change @ui.ssl_provider': function () { | ||||||
|  |             let enabled  = this.ui.ssl_enabled.prop('checked'); | ||||||
|  |             let provider = this.ui.ssl_provider.filter(':checked').val(); | ||||||
|  |             this.ui.all_ssl.hide().find('input').prop('disabled', true); | ||||||
|  |             this.ui[provider + '_ssl'].show().find('input').prop('disabled', !enabled); | ||||||
|  |         }, | ||||||
|  |  | ||||||
|  |         'click @ui.save': function (e) { | ||||||
|  |             e.preventDefault(); | ||||||
|  |  | ||||||
|  |             if (!this.ui.form[0].checkValidity()) { | ||||||
|  |                 $('<input type="submit">').hide().appendTo(this.ui.form).click().remove(); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             let view = this; | ||||||
|  |             let data = this.ui.form.serializeJSON(); | ||||||
|  |  | ||||||
|  |             // Manipulate | ||||||
|  |             data.forward_port = parseInt(data.forward_port, 10); | ||||||
|  |             _.map(data, function (item, idx) { | ||||||
|  |                 if (typeof item === 'string' && item === '1') { | ||||||
|  |                     item = true; | ||||||
|  |                 } else if (typeof item === 'object' && item !== null) { | ||||||
|  |                     _.map(item, function (item2, idx2) { | ||||||
|  |                         if (typeof item2 === 'string' && item2 === '1') { | ||||||
|  |                             item[idx2] = true; | ||||||
|  |                         } | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |                 data[idx] = item; | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             if (typeof data.domain_names === 'string' && data.domain_names) { | ||||||
|  |                 data.domain_names = data.domain_names.split(','); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             let require_ssl_files = typeof data.ssl_enabled !== 'undefined' && data.ssl_enabled && typeof data.ssl_provider !== 'undefined' && data.ssl_provider === 'other'; | ||||||
|  |             let ssl_files         = []; | ||||||
|  |             let method            = App.Api.Nginx.ProxyHosts.create; | ||||||
|  |             let is_new            = true; | ||||||
|  |  | ||||||
|  |             let must_require_ssl_files = require_ssl_files && !view.model.hasSslFiles('other'); | ||||||
|  |  | ||||||
|  |             if (this.model.get('id')) { | ||||||
|  |                 // edit | ||||||
|  |                 is_new  = false; | ||||||
|  |                 method  = App.Api.Nginx.ProxyHosts.update; | ||||||
|  |                 data.id = this.model.get('id'); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // check files are attached | ||||||
|  |             if (require_ssl_files) { | ||||||
|  |                 if (!this.ui.other_ssl_certificate[0].files.length || !this.ui.other_ssl_certificate[0].files[0].size) { | ||||||
|  |                     if (must_require_ssl_files) { | ||||||
|  |                         alert('certificate file is not attached'); | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     if (this.ui.other_ssl_certificate[0].files[0].size > this.max_file_size) { | ||||||
|  |                         alert('certificate file is too large (> 5kb)'); | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  |                     ssl_files.push({name: 'other_certificate', file: this.ui.other_ssl_certificate[0].files[0]}); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (!this.ui.other_ssl_certificate_key[0].files.length || !this.ui.other_ssl_certificate_key[0].files[0].size) { | ||||||
|  |                     if (must_require_ssl_files) { | ||||||
|  |                         alert('certificate key file is not attached'); | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     if (this.ui.other_ssl_certificate_key[0].files[0].size > this.max_file_size) { | ||||||
|  |                         alert('certificate key file is too large (> 5kb)'); | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  |                     ssl_files.push({name: 'other_certificate_key', file: this.ui.other_ssl_certificate_key[0].files[0]}); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); | ||||||
|  |             method(data) | ||||||
|  |                 .then(result => { | ||||||
|  |                     view.model.set(result); | ||||||
|  |  | ||||||
|  |                     // Now upload the certs if we need to | ||||||
|  |                     if (ssl_files.length) { | ||||||
|  |                         let form_data = new FormData(); | ||||||
|  |  | ||||||
|  |                         ssl_files.map(function (file) { | ||||||
|  |                             form_data.append(file.name, file.file); | ||||||
|  |                         }); | ||||||
|  |  | ||||||
|  |                         return App.Api.Nginx.ProxyHosts.setCerts(view.model.get('id'), form_data) | ||||||
|  |                             .then(result => { | ||||||
|  |                                 view.model.set('meta', _.assign({}, view.model.get('meta'), result)); | ||||||
|  |                             }); | ||||||
|  |                     } | ||||||
|  |                 }) | ||||||
|  |                 .then(() => { | ||||||
|  |                     App.UI.closeModal(function () { | ||||||
|  |                         if (is_new) { | ||||||
|  |                             App.Controller.showNginxProxy(); | ||||||
|  |                         } | ||||||
|  |                     }); | ||||||
|  |                 }) | ||||||
|  |                 .catch(err => { | ||||||
|  |                     alert(err.message); | ||||||
|  |                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||||
|  |                 }); | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     templateContext: { | ||||||
|  |         getLetsencryptEmail: function () { | ||||||
|  |             return typeof this.meta.letsencrypt_email !== 'undefined' ? this.meta.letsencrypt_email : App.Cache.User.get('email'); | ||||||
|  |         }, | ||||||
|  |  | ||||||
|  |         getLetsencryptAgree: function () { | ||||||
|  |             return typeof this.meta.letsencrypt_agree !== 'undefined' ? this.meta.letsencrypt_agree : false; | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     onRender: function () { | ||||||
|  |         this.ui.forward_ip.mask('099.099.099.099', { | ||||||
|  |             clearIfNotMatch: true, | ||||||
|  |             placeholder:     '000.000.000.000' | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         this.ui.ssl_enabled.trigger('change'); | ||||||
|  |         this.ui.ssl_provider.trigger('change'); | ||||||
|  |  | ||||||
|  |         this.ui.domain_names.selectize({ | ||||||
|  |             delimiter:    ',', | ||||||
|  |             persist:      false, | ||||||
|  |             maxOptions:   15, | ||||||
|  |             create:       function (input) { | ||||||
|  |                 return { | ||||||
|  |                     value: input, | ||||||
|  |                     text:  input | ||||||
|  |                 }; | ||||||
|  |             }, | ||||||
|  |             createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/ | ||||||
|  |         }); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     initialize: function (options) { | ||||||
|  |         if (typeof options.model === 'undefined' || !options.model) { | ||||||
|  |             this.model = new ProxyHostModel.Model(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | }); | ||||||
							
								
								
									
										40
									
								
								src/frontend/js/app/nginx/access/list/item.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/frontend/js/app/nginx/access/list/item.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | <td class="text-center"> | ||||||
|  |     <div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>"> | ||||||
|  |         <span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span> | ||||||
|  |     </div> | ||||||
|  | </td> | ||||||
|  | <td> | ||||||
|  |     <div> | ||||||
|  |         <% domain_names.map(function(host) { | ||||||
|  |         %> | ||||||
|  |         <span class="tag"><%- host %></span> | ||||||
|  |         <% | ||||||
|  |         }); | ||||||
|  |         %> | ||||||
|  |     </div> | ||||||
|  |     <div class="small text-muted"> | ||||||
|  |         <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> | ||||||
|  |     </div> | ||||||
|  | </td> | ||||||
|  | <td> | ||||||
|  |     <div class="text-monospace"><%- forward_ip %>:<%- forward_port %></div> | ||||||
|  | </td> | ||||||
|  | <td> | ||||||
|  |     <div><%- ssl_enabled && ssl_provider ? i18n('ssl', ssl_provider) : i18n('ssl', 'none') %></div> | ||||||
|  | </td> | ||||||
|  | <td> | ||||||
|  |     <div><%- access_list_id ? access_list.name : i18n('str', 'public') %></div> | ||||||
|  | </td> | ||||||
|  | <% if (canManage) { %> | ||||||
|  | <td class="text-center"> | ||||||
|  |     <div class="item-action dropdown"> | ||||||
|  |         <a href="#" data-toggle="dropdown" class="icon"><i class="fe fe-more-vertical"></i></a> | ||||||
|  |         <div class="dropdown-menu dropdown-menu-right"> | ||||||
|  |             <a href="#" class="edit dropdown-item"><i class="dropdown-icon fe fe-edit"></i> <%- i18n('str', 'edit') %></a> | ||||||
|  |             <a href="#" class="logs dropdown-item"><i class="dropdown-icon fe fe-book"></i> <%- i18n('str', 'logs') %></a> | ||||||
|  |             <div class="dropdown-divider"></div> | ||||||
|  |             <a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  | </td> | ||||||
|  | <% } %> | ||||||
							
								
								
									
										35
									
								
								src/frontend/js/app/nginx/access/list/item.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/frontend/js/app/nginx/access/list/item.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const Mn       = require('backbone.marionette'); | ||||||
|  | const App      = require('../../../main'); | ||||||
|  | const template = require('./item.ejs'); | ||||||
|  |  | ||||||
|  | module.exports = Mn.View.extend({ | ||||||
|  |     template: template, | ||||||
|  |     tagName:  'tr', | ||||||
|  |  | ||||||
|  |     ui: { | ||||||
|  |         edit:   'a.edit', | ||||||
|  |         delete: 'a.delete' | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     events: { | ||||||
|  |         'click @ui.edit': function (e) { | ||||||
|  |             e.preventDefault(); | ||||||
|  |             App.Controller.showNginxProxyForm(this.model); | ||||||
|  |         }, | ||||||
|  |  | ||||||
|  |         'click @ui.delete': function (e) { | ||||||
|  |             e.preventDefault(); | ||||||
|  |             App.Controller.showNginxProxyDeleteConfirm(this.model); | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     templateContext: { | ||||||
|  |         canManage: App.Cache.User.canManage('proxy_hosts') | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     initialize: function () { | ||||||
|  |         this.listenTo(this.model, 'change', this.render); | ||||||
|  |     } | ||||||
|  | }); | ||||||
							
								
								
									
										13
									
								
								src/frontend/js/app/nginx/access/list/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/frontend/js/app/nginx/access/list/main.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | <thead> | ||||||
|  |     <th width="30"> </th> | ||||||
|  |     <th><%- i18n('str', 'source') %></th> | ||||||
|  |     <th><%- i18n('str', 'destination') %></th> | ||||||
|  |     <th><%- i18n('str', 'ssl') %></th> | ||||||
|  |     <th><%- i18n('str', 'access') %></th> | ||||||
|  |     <% if (canManage) { %> | ||||||
|  |     <th> </th> | ||||||
|  |     <% } %> | ||||||
|  | </thead> | ||||||
|  | <tbody> | ||||||
|  |     <!-- items --> | ||||||
|  | </tbody> | ||||||
							
								
								
									
										34
									
								
								src/frontend/js/app/nginx/access/list/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/frontend/js/app/nginx/access/list/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const Mn       = require('backbone.marionette'); | ||||||
|  | const App      = require('../../../main'); | ||||||
|  | const ItemView = require('./item'); | ||||||
|  | const template = require('./main.ejs'); | ||||||
|  |  | ||||||
|  | const TableBody = Mn.CollectionView.extend({ | ||||||
|  |     tagName:   'tbody', | ||||||
|  |     childView: ItemView | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | module.exports = Mn.View.extend({ | ||||||
|  |     tagName:   'table', | ||||||
|  |     className: 'table table-hover table-outline table-vcenter text-nowrap card-table', | ||||||
|  |     template:  template, | ||||||
|  |  | ||||||
|  |     regions: { | ||||||
|  |         body: { | ||||||
|  |             el:             'tbody', | ||||||
|  |             replaceElement: true | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     templateContext: { | ||||||
|  |         canManage: App.Cache.User.canManage('access_lists') | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     onRender: function () { | ||||||
|  |         this.showChildView('body', new TableBody({ | ||||||
|  |             collection: this.collection | ||||||
|  |         })); | ||||||
|  |     } | ||||||
|  | }); | ||||||
| @@ -1 +1,19 @@ | |||||||
| access | <div class="card"> | ||||||
|  |     <div class="card-status bg-teal"></div> | ||||||
|  |     <div class="card-header"> | ||||||
|  |         <h3 class="card-title"><%- i18n('access-lists', 'title') %></h3> | ||||||
|  |         <div class="card-options"> | ||||||
|  |             <% if (showAddButton) { %> | ||||||
|  |             <a href="#" class="btn btn-outline-teal btn-sm ml-2 add-item"><%- i18n('access-lists', 'add') %></a> | ||||||
|  |             <% } %> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  |     <div class="card-body no-padding min-100"> | ||||||
|  |         <div class="dimmer active"> | ||||||
|  |             <div class="loader"></div> | ||||||
|  |             <div class="dimmer-content list-region"> | ||||||
|  |                 <!-- List Region --> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|   | |||||||
| @@ -1,9 +1,77 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
|  |  | ||||||
| const Mn       = require('backbone.marionette'); | const Mn              = require('backbone.marionette'); | ||||||
| const template = require('./main.ejs'); | const App             = require('../../main'); | ||||||
|  | const AccessListModel = require('../../../models/access-list'); | ||||||
|  | const ListView        = require('./list/main'); | ||||||
|  | const ErrorView       = require('../../error/main'); | ||||||
|  | const EmptyView       = require('../../empty/main'); | ||||||
|  | const template        = require('./main.ejs'); | ||||||
|  |  | ||||||
| module.exports = Mn.View.extend({ | module.exports = Mn.View.extend({ | ||||||
|  |     id:       'nginx-access', | ||||||
|     template: template, |     template: template, | ||||||
|     id:       'nginx-access' |  | ||||||
|  |     ui: { | ||||||
|  |         list_region: '.list-region', | ||||||
|  |         add:         '.add-item', | ||||||
|  |         dimmer:      '.dimmer' | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     regions: { | ||||||
|  |         list_region: '@ui.list_region' | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     events: { | ||||||
|  |         'click @ui.add': function (e) { | ||||||
|  |             e.preventDefault(); | ||||||
|  |             App.Controller.showNginxAccessListForm(); | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     templateContext: { | ||||||
|  |         showAddButton: App.Cache.User.canManage('access_lists') | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     onRender: function () { | ||||||
|  |         let view = this; | ||||||
|  |  | ||||||
|  |         App.Api.Nginx.AccessLists.getAll(['owner']) | ||||||
|  |             .then(response => { | ||||||
|  |                 if (!view.isDestroyed()) { | ||||||
|  |                     if (response && response.length) { | ||||||
|  |                         view.showChildView('list_region', new ListView({ | ||||||
|  |                             collection: new AccessListModel.Collection(response) | ||||||
|  |                         })); | ||||||
|  |                     } else { | ||||||
|  |                         let manage = App.Cache.User.canManage('access_lists'); | ||||||
|  |  | ||||||
|  |                         view.showChildView('list_region', new EmptyView({ | ||||||
|  |                             title:      App.i18n('access-lists', 'empty'), | ||||||
|  |                             subtitle:   App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), | ||||||
|  |                             link:       manage ? App.i18n('access-lists', 'add') : null, | ||||||
|  |                             btn_color:  'teal', | ||||||
|  |                             permission: 'access_lists', | ||||||
|  |                             action:     function () { | ||||||
|  |                                 App.Controller.showNginxAccessListForm(); | ||||||
|  |                             } | ||||||
|  |                         })); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|  |             .catch(err => { | ||||||
|  |                 view.showChildView('list_region', new ErrorView({ | ||||||
|  |                     code:    err.code, | ||||||
|  |                     message: err.message, | ||||||
|  |                     retry:   function () { | ||||||
|  |                         App.Controller.showNginxAccess(); | ||||||
|  |                     } | ||||||
|  |                 })); | ||||||
|  |  | ||||||
|  |                 console.error(err); | ||||||
|  |             }) | ||||||
|  |             .then(() => { | ||||||
|  |                 view.ui.dimmer.removeClass('active'); | ||||||
|  |             }); | ||||||
|  |     } | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -8,10 +8,9 @@ | |||||||
|             <div class="row"> |             <div class="row"> | ||||||
|                 <div class="col-sm-12 col-md-12"> |                 <div class="col-sm-12 col-md-12"> | ||||||
|                     <%= i18n('proxy-hosts', 'delete-confirm', {domains: domain_names.join(', ')}) %> |                     <%= i18n('proxy-hosts', 'delete-confirm', {domains: domain_names.join(', ')}) %> | ||||||
|                     Are you sure you want to delete the Proxy host for: <strong><%- domain_names.join(', ') %></strong>? |  | ||||||
|                     <% if (ssl_enabled) { %> |                     <% if (ssl_enabled) { %> | ||||||
|                         <br><br> |                         <br><br> | ||||||
|                         <%- i18n('proxy-hosts', 'delete-ssl') %> |                         <%- i18n('ssl', 'delete-ssl') %> | ||||||
|                     <% } %> |                     <% } %> | ||||||
|                 </div> |                 </div> | ||||||
|             </div> |             </div> | ||||||
|   | |||||||
| @@ -32,6 +32,32 @@ | |||||||
|                                 <input name="forward_port" type="number" class="form-control text-monospace" placeholder="80" value="<%- forward_port %>" required> |                                 <input name="forward_port" type="number" class="form-control text-monospace" placeholder="80" value="<%- forward_port %>" required> | ||||||
|                             </div> |                             </div> | ||||||
|                         </div> |                         </div> | ||||||
|  |                         <div class="col-sm-6 col-md-6"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="custom-switch"> | ||||||
|  |                                     <input type="checkbox" class="custom-switch-input" name="caching_enabled" value="1"<%- caching_enabled ? ' checked' : '' %>> | ||||||
|  |                                     <span class="custom-switch-indicator"></span> | ||||||
|  |                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'caching-enabled') %></span> | ||||||
|  |                                 </label> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="col-sm-6 col-md-6"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="custom-switch"> | ||||||
|  |                                     <input type="checkbox" class="custom-switch-input" name="block_exploits" value="1"<%- block_exploits ? ' checked' : '' %>> | ||||||
|  |                                     <span class="custom-switch-indicator"></span> | ||||||
|  |                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'block-exploits') %></span> | ||||||
|  |                                 </label> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="col-sm-12 col-md-12"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="form-label">Access List</label> | ||||||
|  |                                 <select name="access_list_id" class="form-control custom-select"> | ||||||
|  |                                     <option value="0" selected="selected"><%- i18n('access-lists', 'public') %></option> | ||||||
|  |                                 </select> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										23
									
								
								src/frontend/js/app/nginx/redirection/delete.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/frontend/js/app/nginx/redirection/delete.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | <div class="modal-content"> | ||||||
|  |     <div class="modal-header"> | ||||||
|  |         <h5 class="modal-title"><%- i18n('redirection-hosts', 'delete') %></h5> | ||||||
|  |         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> | ||||||
|  |     </div> | ||||||
|  |     <div class="modal-body"> | ||||||
|  |         <form> | ||||||
|  |             <div class="row"> | ||||||
|  |                 <div class="col-sm-12 col-md-12"> | ||||||
|  |                     <%= i18n('redirection-hosts', 'delete-confirm', {domains: domain_names.join(', ')}) %> | ||||||
|  |                     <% if (ssl_enabled) { %> | ||||||
|  |                         <br><br> | ||||||
|  |                         <%- i18n('ssl', 'delete-ssl') %> | ||||||
|  |                     <% } %> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         </form> | ||||||
|  |     </div> | ||||||
|  |     <div class="modal-footer"> | ||||||
|  |         <button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button> | ||||||
|  |         <button type="button" class="btn btn-danger save"><%- i18n('str', 'sure') %></button> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
							
								
								
									
										36
									
								
								src/frontend/js/app/nginx/redirection/delete.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/frontend/js/app/nginx/redirection/delete.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const Mn       = require('backbone.marionette'); | ||||||
|  | const App      = require('../../main'); | ||||||
|  | const template = require('./delete.ejs'); | ||||||
|  |  | ||||||
|  | require('jquery-serializejson'); | ||||||
|  |  | ||||||
|  | module.exports = Mn.View.extend({ | ||||||
|  |     template:  template, | ||||||
|  |     className: 'modal-dialog', | ||||||
|  |  | ||||||
|  |     ui: { | ||||||
|  |         form:    'form', | ||||||
|  |         buttons: '.modal-footer button', | ||||||
|  |         cancel:  'button.cancel', | ||||||
|  |         save:    'button.save' | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     events: { | ||||||
|  |  | ||||||
|  |         'click @ui.save': function (e) { | ||||||
|  |             e.preventDefault(); | ||||||
|  |  | ||||||
|  |             App.Api.Nginx.RedirectionHosts.delete(this.model.get('id')) | ||||||
|  |                 .then(() => { | ||||||
|  |                     App.Controller.showNginxRedirection(); | ||||||
|  |                     App.UI.closeModal(); | ||||||
|  |                 }) | ||||||
|  |                 .catch(err => { | ||||||
|  |                     alert(err.message); | ||||||
|  |                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||||
|  |                 }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | }); | ||||||
							
								
								
									
										134
									
								
								src/frontend/js/app/nginx/redirection/form.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								src/frontend/js/app/nginx/redirection/form.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,134 @@ | |||||||
|  | <div class="modal-content"> | ||||||
|  |     <div class="modal-header"> | ||||||
|  |         <h5 class="modal-title"><%- i18n('redirection-hosts', 'form-title', {id: id}) %></h5> | ||||||
|  |         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> | ||||||
|  |     </div> | ||||||
|  |     <div class="modal-body has-tabs"> | ||||||
|  |         <form> | ||||||
|  |             <ul class="nav nav-tabs" role="tablist"> | ||||||
|  |                 <li role="presentation" class="nav-item"><a href="#details" aria-controls="tab1" role="tab" data-toggle="tab" class="nav-link active"><i class="fe fe-zap"></i> <%- i18n('all-hosts', 'details') %></a></li> | ||||||
|  |                 <li role="presentation" class="nav-item"><a href="#ssl-options" aria-controls="tab2" role="tab" data-toggle="tab" class="nav-link"><i class="fe fe-shield"></i> <%- i18n('str', 'ssl') %></a></li> | ||||||
|  |             </ul> | ||||||
|  |             <div class="tab-content"> | ||||||
|  |                 <!-- Details --> | ||||||
|  |                 <div role="tabpanel" class="tab-pane active" id="details"> | ||||||
|  |                     <div class="row"> | ||||||
|  |  | ||||||
|  |                         <div class="col-sm-12 col-md-12"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="form-label"><%- i18n('all-hosts', 'domain-names') %> <span class="form-required">*</span></label> | ||||||
|  |                                 <input type="text" name="domain_names" class="form-control" id="input-domains" value="<%- domain_names.join(',') %>" required> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="col-sm-12 col-md-12"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="form-label"><%- i18n('redirection-hosts', 'forward-domain') %><span class="form-required">*</span></label> | ||||||
|  |                                 <input type="text" name="forward_domain_name" class="form-control text-monospace" placeholder="" value="<%- forward_domain_name %>" required> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="col-sm-6 col-md-6"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="custom-switch"> | ||||||
|  |                                     <input type="checkbox" class="custom-switch-input" name="preserve_path" value="1"<%- preserve_path ? ' checked' : '' %>> | ||||||
|  |                                     <span class="custom-switch-indicator"></span> | ||||||
|  |                                     <span class="custom-switch-description"><%- i18n('redirection-hosts', 'preserve-path') %></span> | ||||||
|  |                                 </label> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="col-sm-6 col-md-6"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="custom-switch"> | ||||||
|  |                                     <input type="checkbox" class="custom-switch-input" name="block_exploits" value="1"<%- block_exploits ? ' checked' : '' %>> | ||||||
|  |                                     <span class="custom-switch-indicator"></span> | ||||||
|  |                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'block-exploits') %></span> | ||||||
|  |                                 </label> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |  | ||||||
|  |                 <!-- SSL --> | ||||||
|  |                 <div role="tabpanel" class="tab-pane" id="ssl-options"> | ||||||
|  |                     <div class="row"> | ||||||
|  |                         <div class="col-sm-6 col-md-6"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="custom-switch"> | ||||||
|  |                                     <input type="checkbox" class="custom-switch-input" name="ssl_enabled" value="1"<%- ssl_enabled ? ' checked' : '' %>> | ||||||
|  |                                     <span class="custom-switch-indicator"></span> | ||||||
|  |                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'enable-ssl') %></span> | ||||||
|  |                                 </label> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="col-sm-6 col-md-6"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="custom-switch"> | ||||||
|  |                                     <input type="checkbox" class="custom-switch-input" name="ssl_forced" value="1"<%- ssl_forced ? ' checked' : '' %><%- ssl_enabled ? '' : ' disabled' %>> | ||||||
|  |                                     <span class="custom-switch-indicator"></span> | ||||||
|  |                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'force-ssl') %></span> | ||||||
|  |                                 </label> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="col-sm-12 col-md-12"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="form-label"><%- i18n('all-hosts', 'cert-provider') %></label> | ||||||
|  |                                 <div class="selectgroup w-100"> | ||||||
|  |                                     <label class="selectgroup-item"> | ||||||
|  |                                         <input type="radio" name="ssl_provider" value="letsencrypt" class="selectgroup-input"<%- ssl_provider !== 'other' ? ' checked' : '' %>> | ||||||
|  |                                         <span class="selectgroup-button"><%- i18n('ssl', 'letsencrypt') %></span> | ||||||
|  |                                     </label> | ||||||
|  |                                     <label class="selectgroup-item"> | ||||||
|  |                                         <input type="radio" name="ssl_provider" value="other" class="selectgroup-input"<%- ssl_provider === 'other' ? ' checked' : '' %>> | ||||||
|  |                                         <span class="selectgroup-button"><%- i18n('ssl', 'other') %></span> | ||||||
|  |                                     </label> | ||||||
|  |                                 </div> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |  | ||||||
|  |                         <!-- Lets encrypt --> | ||||||
|  |                         <div class="col-sm-12 col-md-12 letsencrypt-ssl"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="form-label"><%- i18n('ssl', 'letsencrypt-email') %> <span class="form-required">*</span></label> | ||||||
|  |                                 <input name="meta[letsencrypt_email]" type="email" class="form-control" placeholder="" value="<%- getLetsencryptEmail() %>" required> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="col-sm-12 col-md-12 letsencrypt-ssl"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="custom-switch"> | ||||||
|  |                                     <input type="checkbox" class="custom-switch-input" name="meta[letsencrypt_agree]" value="1" required<%- getLetsencryptAgree() ? ' checked' : '' %>> | ||||||
|  |                                     <span class="custom-switch-indicator"></span> | ||||||
|  |                                     <span class="custom-switch-description"><%= i18n('ssl', 'letsencrypt-agree', {url: 'https://letsencrypt.org/repository/'}) %> <span class="form-required">*</span></span> | ||||||
|  |                                 </label> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |  | ||||||
|  |                         <!-- Other --> | ||||||
|  |                         <div class="col-sm-12 col-md-12 other-ssl"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <div class="form-label"><%- i18n('all-hosts', 'other-certificate') %></div> | ||||||
|  |                                 <div class="custom-file"> | ||||||
|  |                                     <input type="file" class="custom-file-input" name="meta[other_ssl_certificate]" id="other_ssl_certificate"> | ||||||
|  |                                     <label class="custom-file-label"><%- i18n('str', 'choose-file') %></label> | ||||||
|  |                                 </div> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="col-sm-12 col-md-12 other-ssl"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <div class="form-label"><%- i18n('all-hosts', 'other-certificate-key') %></div> | ||||||
|  |                                 <div class="custom-file"> | ||||||
|  |                                     <input type="file" class="custom-file-input" name="meta[other_ssl_certificate_key]" id="other_ssl_certificate_key"> | ||||||
|  |                                     <label class="custom-file-label"><%- i18n('str', 'choose-file') %></label> | ||||||
|  |                                 </div> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         </form> | ||||||
|  |     </div> | ||||||
|  |     <div class="modal-footer"> | ||||||
|  |         <button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button> | ||||||
|  |         <button type="button" class="btn btn-teal save"><%- i18n('str', 'save') %></button> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
							
								
								
									
										187
									
								
								src/frontend/js/app/nginx/redirection/form.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								src/frontend/js/app/nginx/redirection/form.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,187 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const _                    = require('underscore'); | ||||||
|  | const Mn                   = require('backbone.marionette'); | ||||||
|  | const App                  = require('../../main'); | ||||||
|  | const RedirectionHostModel = require('../../../models/redirection-host'); | ||||||
|  | const template             = require('./form.ejs'); | ||||||
|  |  | ||||||
|  | require('jquery-serializejson'); | ||||||
|  | require('selectize'); | ||||||
|  |  | ||||||
|  | module.exports = Mn.View.extend({ | ||||||
|  |     template:      template, | ||||||
|  |     className:     'modal-dialog', | ||||||
|  |     max_file_size: 5120, | ||||||
|  |  | ||||||
|  |     ui: { | ||||||
|  |         form:                      'form', | ||||||
|  |         domain_names:              'input[name="domain_names"]', | ||||||
|  |         buttons:                   '.modal-footer button', | ||||||
|  |         cancel:                    'button.cancel', | ||||||
|  |         save:                      'button.save', | ||||||
|  |         ssl_enabled:               'input[name="ssl_enabled"]', | ||||||
|  |         ssl_options:               '#ssl-options input', | ||||||
|  |         ssl_provider:              'input[name="ssl_provider"]', | ||||||
|  |         other_ssl_certificate:     '#other_ssl_certificate', | ||||||
|  |         other_ssl_certificate_key: '#other_ssl_certificate_key', | ||||||
|  |  | ||||||
|  |         // SSL hiding and showing | ||||||
|  |         all_ssl:         '.letsencrypt-ssl, .other-ssl', | ||||||
|  |         letsencrypt_ssl: '.letsencrypt-ssl', | ||||||
|  |         other_ssl:       '.other-ssl' | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     events: { | ||||||
|  |         'change @ui.ssl_enabled': function () { | ||||||
|  |             let enabled = this.ui.ssl_enabled.prop('checked'); | ||||||
|  |             this.ui.ssl_options.not(this.ui.ssl_enabled).prop('disabled', !enabled).parents('.form-group').css('opacity', enabled ? 1 : 0.5); | ||||||
|  |             this.ui.ssl_provider.trigger('change'); | ||||||
|  |         }, | ||||||
|  |  | ||||||
|  |         'change @ui.ssl_provider': function () { | ||||||
|  |             let enabled  = this.ui.ssl_enabled.prop('checked'); | ||||||
|  |             let provider = this.ui.ssl_provider.filter(':checked').val(); | ||||||
|  |             this.ui.all_ssl.hide().find('input').prop('disabled', true); | ||||||
|  |             this.ui[provider + '_ssl'].show().find('input').prop('disabled', !enabled); | ||||||
|  |         }, | ||||||
|  |  | ||||||
|  |         'click @ui.save': function (e) { | ||||||
|  |             e.preventDefault(); | ||||||
|  |  | ||||||
|  |             if (!this.ui.form[0].checkValidity()) { | ||||||
|  |                 $('<input type="submit">').hide().appendTo(this.ui.form).click().remove(); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             let view = this; | ||||||
|  |             let data = this.ui.form.serializeJSON(); | ||||||
|  |  | ||||||
|  |             // Manipulate | ||||||
|  |             _.map(data, function (item, idx) { | ||||||
|  |                 if (typeof item === 'string' && item === '1') { | ||||||
|  |                     item = true; | ||||||
|  |                 } else if (typeof item === 'object' && item !== null) { | ||||||
|  |                     _.map(item, function (item2, idx2) { | ||||||
|  |                         if (typeof item2 === 'string' && item2 === '1') { | ||||||
|  |                             item[idx2] = true; | ||||||
|  |                         } | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |                 data[idx] = item; | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             if (typeof data.domain_names === 'string' && data.domain_names) { | ||||||
|  |                 data.domain_names = data.domain_names.split(','); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             let require_ssl_files = typeof data.ssl_enabled !== 'undefined' && data.ssl_enabled && typeof data.ssl_provider !== 'undefined' && data.ssl_provider === 'other'; | ||||||
|  |             let ssl_files         = []; | ||||||
|  |             let method            = App.Api.Nginx.RedirectionHosts.create; | ||||||
|  |             let is_new            = true; | ||||||
|  |  | ||||||
|  |             let must_require_ssl_files = require_ssl_files && !view.model.hasSslFiles('other'); | ||||||
|  |  | ||||||
|  |             if (this.model.get('id')) { | ||||||
|  |                 // edit | ||||||
|  |                 is_new  = false; | ||||||
|  |                 method  = App.Api.Nginx.RedirectionHosts.update; | ||||||
|  |                 data.id = this.model.get('id'); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // check files are attached | ||||||
|  |             if (require_ssl_files) { | ||||||
|  |                 if (!this.ui.other_ssl_certificate[0].files.length || !this.ui.other_ssl_certificate[0].files[0].size) { | ||||||
|  |                     if (must_require_ssl_files) { | ||||||
|  |                         alert('certificate file is not attached'); | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     if (this.ui.other_ssl_certificate[0].files[0].size > this.max_file_size) { | ||||||
|  |                         alert('certificate file is too large (> 5kb)'); | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  |                     ssl_files.push({name: 'other_certificate', file: this.ui.other_ssl_certificate[0].files[0]}); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (!this.ui.other_ssl_certificate_key[0].files.length || !this.ui.other_ssl_certificate_key[0].files[0].size) { | ||||||
|  |                     if (must_require_ssl_files) { | ||||||
|  |                         alert('certificate key file is not attached'); | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     if (this.ui.other_ssl_certificate_key[0].files[0].size > this.max_file_size) { | ||||||
|  |                         alert('certificate key file is too large (> 5kb)'); | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  |                     ssl_files.push({name: 'other_certificate_key', file: this.ui.other_ssl_certificate_key[0].files[0]}); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); | ||||||
|  |             method(data) | ||||||
|  |                 .then(result => { | ||||||
|  |                     view.model.set(result); | ||||||
|  |  | ||||||
|  |                     // Now upload the certs if we need to | ||||||
|  |                     if (ssl_files.length) { | ||||||
|  |                         let form_data = new FormData(); | ||||||
|  |  | ||||||
|  |                         ssl_files.map(function (file) { | ||||||
|  |                             form_data.append(file.name, file.file); | ||||||
|  |                         }); | ||||||
|  |  | ||||||
|  |                         return App.Api.Nginx.RedirectionHosts.setCerts(view.model.get('id'), form_data) | ||||||
|  |                             .then(result => { | ||||||
|  |                                 view.model.set('meta', _.assign({}, view.model.get('meta'), result)); | ||||||
|  |                             }); | ||||||
|  |                     } | ||||||
|  |                 }) | ||||||
|  |                 .then(() => { | ||||||
|  |                     App.UI.closeModal(function () { | ||||||
|  |                         if (is_new) { | ||||||
|  |                             App.Controller.showNginxRedirection(); | ||||||
|  |                         } | ||||||
|  |                     }); | ||||||
|  |                 }) | ||||||
|  |                 .catch(err => { | ||||||
|  |                     alert(err.message); | ||||||
|  |                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||||
|  |                 }); | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     templateContext: { | ||||||
|  |         getLetsencryptEmail: function () { | ||||||
|  |             return typeof this.meta.letsencrypt_email !== 'undefined' ? this.meta.letsencrypt_email : App.Cache.User.get('email'); | ||||||
|  |         }, | ||||||
|  |  | ||||||
|  |         getLetsencryptAgree: function () { | ||||||
|  |             return typeof this.meta.letsencrypt_agree !== 'undefined' ? this.meta.letsencrypt_agree : false; | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     onRender: function () { | ||||||
|  |         this.ui.ssl_enabled.trigger('change'); | ||||||
|  |         this.ui.ssl_provider.trigger('change'); | ||||||
|  |  | ||||||
|  |         this.ui.domain_names.selectize({ | ||||||
|  |             delimiter:    ',', | ||||||
|  |             persist:      false, | ||||||
|  |             maxOptions:   15, | ||||||
|  |             create:       function (input) { | ||||||
|  |                 return { | ||||||
|  |                     value: input, | ||||||
|  |                     text:  input | ||||||
|  |                 }; | ||||||
|  |             }, | ||||||
|  |             createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/ | ||||||
|  |         }); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     initialize: function (options) { | ||||||
|  |         if (typeof options.model === 'undefined' || !options.model) { | ||||||
|  |             this.model = new RedirectionHostModel.Model(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | }); | ||||||
| @@ -1,17 +1,15 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
|  |  | ||||||
| const Mn                   = require('backbone.marionette'); | const Mn                   = require('backbone.marionette'); | ||||||
|  | const App                  = require('../../main'); | ||||||
| const RedirectionHostModel = require('../../../models/redirection-host'); | const RedirectionHostModel = require('../../../models/redirection-host'); | ||||||
| const Api                  = require('../../api'); |  | ||||||
| const Cache                = require('../../cache'); |  | ||||||
| const Controller           = require('../../controller'); |  | ||||||
| const ListView             = require('./list/main'); | const ListView             = require('./list/main'); | ||||||
| const ErrorView            = require('../../error/main'); | const ErrorView            = require('../../error/main'); | ||||||
| const template             = require('./main.ejs'); |  | ||||||
| const EmptyView            = require('../../empty/main'); | const EmptyView            = require('../../empty/main'); | ||||||
|  | const template             = require('./main.ejs'); | ||||||
|  |  | ||||||
| module.exports = Mn.View.extend({ | module.exports = Mn.View.extend({ | ||||||
|     id:       'nginx-redirections', |     id:       'nginx-redirection', | ||||||
|     template: template, |     template: template, | ||||||
|  |  | ||||||
|     ui: { |     ui: { | ||||||
| @@ -27,34 +25,35 @@ module.exports = Mn.View.extend({ | |||||||
|     events: { |     events: { | ||||||
|         'click @ui.add': function (e) { |         'click @ui.add': function (e) { | ||||||
|             e.preventDefault(); |             e.preventDefault(); | ||||||
|             Controller.showNginxRedirectionForm(); |             App.Controller.showNginxProxyForm(); | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     templateContext: { |     templateContext: { | ||||||
|         showAddButton: Cache.User.canManage('redirection_hosts') |         showAddButton: App.Cache.User.canManage('proxy_hosts') | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     onRender: function () { |     onRender: function () { | ||||||
|         let view = this; |         let view = this; | ||||||
|  |  | ||||||
|         Api.Nginx.RedirectionHosts.getAll() |         App.Api.Nginx.ProxyHosts.getAll(['owner', 'access_list']) | ||||||
|             .then(response => { |             .then(response => { | ||||||
|                 if (!view.isDestroyed()) { |                 if (!view.isDestroyed()) { | ||||||
|                     if (response && response.length) { |                     if (response && response.length) { | ||||||
|                         view.showChildView('list_region', new ListView({ |                         view.showChildView('list_region', new ListView({ | ||||||
|                             collection: new RedirectionHostModel.Collection(response) |                             collection: new ProxyHostModel.Collection(response) | ||||||
|                         })); |                         })); | ||||||
|                     } else { |                     } else { | ||||||
|                         let manage = Cache.User.canManage('redirection_hosts'); |                         let manage = App.Cache.User.canManage('proxy_hosts'); | ||||||
|  |  | ||||||
|                         view.showChildView('list_region', new EmptyView({ |                         view.showChildView('list_region', new EmptyView({ | ||||||
|                             title:     'There are no Redirection Hosts', |                             title:      App.i18n('proxy-hosts', 'empty'), | ||||||
|                             subtitle:  manage ? 'Why don\'t you create one?' : 'And you don\'t have permission to create one.', |                             subtitle:   App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), | ||||||
|                             link:      manage ? 'Add Redirection Host' : null, |                             link:       manage ? App.i18n('proxy-hosts', 'add') : null, | ||||||
|                             btn_color: 'yellow', |                             btn_color:  'success', | ||||||
|                             action:    function () { |                             permission: 'proxy_hosts', | ||||||
|                                 Controller.showNginxRedirectionForm(); |                             action:     function () { | ||||||
|  |                                 App.Controller.showNginxProxyForm(); | ||||||
|                             } |                             } | ||||||
|                         })); |                         })); | ||||||
|                     } |                     } | ||||||
| @@ -65,7 +64,7 @@ module.exports = Mn.View.extend({ | |||||||
|                     code:    err.code, |                     code:    err.code, | ||||||
|                     message: err.message, |                     message: err.message, | ||||||
|                     retry:   function () { |                     retry:   function () { | ||||||
|                         Controller.showNginxRedirection(); |                         App.Controller.showNginxProxy(); | ||||||
|                     } |                     } | ||||||
|                 })); |                 })); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| <div class="card"> | <div class="card"> | ||||||
|  |     <div class="card-status bg-teal"></div> | ||||||
|     <div class="card-header"> |     <div class="card-header"> | ||||||
|         <h3 class="card-title"><%- i18n('users', 'title') %></h3> |         <h3 class="card-title"><%- i18n('users', 'title') %></h3> | ||||||
|         <div class="card-options"> |         <div class="card-options"> | ||||||
|   | |||||||
| @@ -61,14 +61,17 @@ | |||||||
|       "domain-names": "Domain Names", |       "domain-names": "Domain Names", | ||||||
|       "cert-provider": "Certificate Provider", |       "cert-provider": "Certificate Provider", | ||||||
|       "other-certificate": "Certificate", |       "other-certificate": "Certificate", | ||||||
|       "other-certificate-key": "Certificate Key" |       "other-certificate-key": "Certificate Key", | ||||||
|  |       "block-exploits": "Block Common Exploits", | ||||||
|  |       "caching-enabled": "Cache Assets" | ||||||
|     }, |     }, | ||||||
|     "ssl": { |     "ssl": { | ||||||
|       "letsencrypt": "Let's Encrypt", |       "letsencrypt": "Let's Encrypt", | ||||||
|       "other": "Other", |       "other": "Other", | ||||||
|       "none": "HTTP only", |       "none": "HTTP only", | ||||||
|       "letsencrypt-email": "Email Address for Let's Encrypt", |       "letsencrypt-email": "Email Address for Let's Encrypt", | ||||||
|       "letsencrypt-agree": "I Agree to the <a href=\"{url}\" target=\"_blank\">Let's Encrypt Terms of Service</a>" |       "letsencrypt-agree": "I Agree to the <a href=\"{url}\" target=\"_blank\">Let's Encrypt Terms of Service</a>", | ||||||
|  |       "delete-ssl": "The SSL certificates attached will be removed, this action cannot be recovered." | ||||||
|     }, |     }, | ||||||
|     "proxy-hosts": { |     "proxy-hosts": { | ||||||
|       "title": "Proxy Hosts", |       "title": "Proxy Hosts", | ||||||
| @@ -78,11 +81,15 @@ | |||||||
|       "forward-ip": "Forward IP", |       "forward-ip": "Forward IP", | ||||||
|       "forward-port": "Forward Port", |       "forward-port": "Forward Port", | ||||||
|       "delete": "Delete Proxy Host", |       "delete": "Delete Proxy Host", | ||||||
|       "delete-confirm": "Are you sure you want to delete the Proxy host for: <strong>{domains}</strong>?", |       "delete-confirm": "Are you sure you want to delete the Proxy host for: <strong>{domains}</strong>?" | ||||||
|       "delete-ssl": "The SSL certificates attached will be removed, this action cannot be recovered." |  | ||||||
|     }, |     }, | ||||||
|     "redirection-hosts": { |     "redirection-hosts": { | ||||||
|       "title": "Redirection Hosts" |       "title": "Redirection Hosts", | ||||||
|  |       "form-title": "{id, select, undefined{New} other{Edit}} Redirection Host", | ||||||
|  |       "forward-domain": "Forward Domain", | ||||||
|  |       "preserve-path": "Preserve Path", | ||||||
|  |       "delete": "Delete Proxy Host", | ||||||
|  |       "delete-confirm": "Are you sure you want to delete the Redirection host for: <strong>{domains}</strong>?" | ||||||
|     }, |     }, | ||||||
|     "dead-hosts": { |     "dead-hosts": { | ||||||
|       "title": "404 Hosts" |       "title": "404 Hosts" | ||||||
| @@ -91,7 +98,12 @@ | |||||||
|       "title": "Streams" |       "title": "Streams" | ||||||
|     }, |     }, | ||||||
|     "access-lists": { |     "access-lists": { | ||||||
|       "title": "Access Lists" |       "title": "Access Lists", | ||||||
|  |       "empty": "There are no Access Lists", | ||||||
|  |       "add": "Add Access List", | ||||||
|  |       "delete": "Delete Access List", | ||||||
|  |       "delete-confirm": "Are you sure you want to delete this access list? Any hosts using it will need to be updated later.", | ||||||
|  |       "public": "Publicly Accessible" | ||||||
|     }, |     }, | ||||||
|     "users": { |     "users": { | ||||||
|       "title": "Users", |       "title": "Users", | ||||||
|   | |||||||
							
								
								
									
										24
									
								
								src/frontend/js/models/access-list.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/frontend/js/models/access-list.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const Backbone = require('backbone'); | ||||||
|  |  | ||||||
|  | const model = Backbone.Model.extend({ | ||||||
|  |     idAttribute: 'id', | ||||||
|  |  | ||||||
|  |     defaults: function () { | ||||||
|  |         return { | ||||||
|  |             id:              0, | ||||||
|  |             created_on:      null, | ||||||
|  |             modified_on:     null, | ||||||
|  |             // The following are expansions: | ||||||
|  |             owner:           null | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | module.exports = { | ||||||
|  |     Model:      model, | ||||||
|  |     Collection: Backbone.Collection.extend({ | ||||||
|  |         model: model | ||||||
|  |     }) | ||||||
|  | }; | ||||||
| @@ -13,7 +13,7 @@ const model = Backbone.Model.extend({ | |||||||
|             domain_names:    [], |             domain_names:    [], | ||||||
|             forward_ip:      '', |             forward_ip:      '', | ||||||
|             forward_port:    null, |             forward_port:    null, | ||||||
|             access_list_id:  null, |             access_list_id:  0, | ||||||
|             ssl_enabled:     false, |             ssl_enabled:     false, | ||||||
|             ssl_provider:    false, |             ssl_provider:    false, | ||||||
|             ssl_forced:      false, |             ssl_forced:      false, | ||||||
|   | |||||||
| @@ -10,14 +10,16 @@ const model = Backbone.Model.extend({ | |||||||
|             id:                  0, |             id:                  0, | ||||||
|             created_on:          null, |             created_on:          null, | ||||||
|             modified_on:         null, |             modified_on:         null, | ||||||
|             owner:               null, |             domain_names:        [], | ||||||
|             domain_name:         '', |  | ||||||
|             forward_domain_name: '', |             forward_domain_name: '', | ||||||
|             preserve_path:       false, |             preserve_path:       true, | ||||||
|             ssl_enabled:         false, |             ssl_enabled:         false, | ||||||
|             ssl_provider:        false, |             ssl_provider:        false, | ||||||
|  |             ssl_forced:          false, | ||||||
|             block_exploits:      false, |             block_exploits:      false, | ||||||
|             meta:                [] |             meta:                {}, | ||||||
|  |             // The following are expansions: | ||||||
|  |             owner:               null | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| }); | }); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user