mirror of
				https://github.com/NginxProxyManager/nginx-proxy-manager.git
				synced 2025-10-31 15:53:33 +00:00 
			
		
		
		
	v2.1.0 (#293)
* Fix wrapping when too many hosts are shown (#207) * Update npm packages, fixes CVE-2019-10757 * Revert some breaking packages * Major overhaul - Docker buildx support in CI - Cypress API Testing in CI - Restructured folder layout (insert clean face meme) - Added Swagger documentation and validate API against that (to be completed) - Use common base image for all supported archs, which includes updated nginx with ipv6 support - Updated certbot and changes required for it - Large amount of Hosts names will wrap in UI - Updated packages for frontend - Version bump 2.1.0 * Updated documentation * Fix JWT expire time going crazy. Now set to 1day * Backend JS formatting rules * Remove v1 importer, I doubt anyone is using v1 anymore * Added backend formatting rules and enforce them in Jenkins builds * Fix CI, doesn't need a tty * Thanks bcrypt. Why can't you just be normal. * Cleanup after syntax check Co-authored-by: Marcelo Castagna <margaale@users.noreply.github.com>
This commit is contained in:
		
							
								
								
									
										688
									
								
								frontend/js/app/api.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										688
									
								
								frontend/js/app/api.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,688 @@ | ||||
| const $      = require('jquery'); | ||||
| const _      = require('underscore'); | ||||
| const Tokens = require('./tokens'); | ||||
|  | ||||
| /** | ||||
|  * @param {String}  message | ||||
|  * @param {*}       debug | ||||
|  * @param {Number} code | ||||
|  * @constructor | ||||
|  */ | ||||
| const ApiError = function (message, debug, code) { | ||||
|     let temp     = Error.call(this, message); | ||||
|     temp.name    = this.name = 'ApiError'; | ||||
|     this.stack   = temp.stack; | ||||
|     this.message = temp.message; | ||||
|     this.debug   = debug; | ||||
|     this.code    = code; | ||||
| }; | ||||
|  | ||||
| ApiError.prototype = Object.create(Error.prototype, { | ||||
|     constructor: { | ||||
|         value:        ApiError, | ||||
|         writable:     true, | ||||
|         configurable: true | ||||
|     } | ||||
| }); | ||||
|  | ||||
| /** | ||||
|  * | ||||
|  * @param   {String} verb | ||||
|  * @param   {String} path | ||||
|  * @param   {Object} [data] | ||||
|  * @param   {Object} [options] | ||||
|  * @returns {Promise} | ||||
|  */ | ||||
| function fetch(verb, path, data, options) { | ||||
|     options = options || {}; | ||||
|  | ||||
|     return new Promise(function (resolve, reject) { | ||||
|         let api_url = '/api/'; | ||||
|         let url     = api_url + path; | ||||
|         let token   = Tokens.getTopToken(); | ||||
|  | ||||
|         if ((typeof options.contentType === 'undefined' || options.contentType.match(/json/im)) && typeof data === 'object') { | ||||
|             data = JSON.stringify(data); | ||||
|         } | ||||
|  | ||||
|         $.ajax({ | ||||
|             url:         url, | ||||
|             data:        typeof data === 'object' ? JSON.stringify(data) : data, | ||||
|             type:        verb, | ||||
|             dataType:    'json', | ||||
|             contentType: options.contentType || 'application/json; charset=UTF-8', | ||||
|             processData: options.processData || true, | ||||
|             crossDomain: true, | ||||
|             timeout:     options.timeout ? options.timeout : 30000, | ||||
|             xhrFields:   { | ||||
|                 withCredentials: true | ||||
|             }, | ||||
|  | ||||
|             beforeSend: function (xhr) { | ||||
|                 xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null)); | ||||
|             }, | ||||
|  | ||||
|             success: function (data, textStatus, response) { | ||||
|                 let total = response.getResponseHeader('X-Dataset-Total'); | ||||
|                 if (total !== null) { | ||||
|                     resolve({ | ||||
|                         data:       data, | ||||
|                         pagination: { | ||||
|                             total:  parseInt(total, 10), | ||||
|                             offset: parseInt(response.getResponseHeader('X-Dataset-Offset'), 10), | ||||
|                             limit:  parseInt(response.getResponseHeader('X-Dataset-Limit'), 10) | ||||
|                         } | ||||
|                     }); | ||||
|                 } else { | ||||
|                     resolve(response); | ||||
|                 } | ||||
|             }, | ||||
|  | ||||
|             error: function (xhr, status, error_thrown) { | ||||
|                 let code = 400; | ||||
|  | ||||
|                 if (typeof xhr.responseJSON !== 'undefined' && typeof xhr.responseJSON.error !== 'undefined' && typeof xhr.responseJSON.error.message !== 'undefined') { | ||||
|                     error_thrown = xhr.responseJSON.error.message; | ||||
|                     code         = xhr.responseJSON.error.code || 500; | ||||
|                 } | ||||
|  | ||||
|                 reject(new ApiError(error_thrown, xhr.responseText, code)); | ||||
|             } | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * | ||||
|  * @param {Array} expand | ||||
|  * @returns {String} | ||||
|  */ | ||||
| function makeExpansionString(expand) { | ||||
|     let items = []; | ||||
|     _.forEach(expand, function (exp) { | ||||
|         items.push(encodeURIComponent(exp)); | ||||
|     }); | ||||
|  | ||||
|     return items.join(','); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @param   {String}   path | ||||
|  * @param   {Array}    [expand] | ||||
|  * @param   {String}   [query] | ||||
|  * @returns {Promise} | ||||
|  */ | ||||
| function getAllObjects(path, expand, query) { | ||||
|     let params = []; | ||||
|  | ||||
|     if (typeof expand === 'object' && expand !== null && expand.length) { | ||||
|         params.push('expand=' + makeExpansionString(expand)); | ||||
|     } | ||||
|  | ||||
|     if (typeof query === 'string') { | ||||
|         params.push('query=' + query); | ||||
|     } | ||||
|  | ||||
|     return fetch('get', path + (params.length ? '?' + params.join('&') : '')); | ||||
| } | ||||
|  | ||||
| function FileUpload(path, fd) { | ||||
|     return new Promise((resolve, reject) => { | ||||
|         let xhr   = new XMLHttpRequest(); | ||||
|         let token = Tokens.getTopToken(); | ||||
|  | ||||
|         xhr.open('POST', '/api/' + path); | ||||
|         xhr.overrideMimeType('text/plain'); | ||||
|         xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null)); | ||||
|         xhr.send(fd); | ||||
|  | ||||
|         xhr.onreadystatechange = function () { | ||||
|             if (this.readyState === XMLHttpRequest.DONE) { | ||||
|                 if (xhr.status !== 200 && xhr.status !== 201) { | ||||
|                     reject(new Error('Upload failed: ' + xhr.status)); | ||||
|                 } else { | ||||
|                     resolve(xhr.responseText); | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|     }); | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|     status: function () { | ||||
|         return fetch('get', ''); | ||||
|     }, | ||||
|  | ||||
|     Tokens: { | ||||
|  | ||||
|         /** | ||||
|          * @param   {String}  identity | ||||
|          * @param   {String}  secret | ||||
|          * @param   {Boolean} [wipe]       Will wipe the stack before adding to it again if login was successful | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         login: function (identity, secret, wipe) { | ||||
|             return fetch('post', 'tokens', {identity: identity, secret: secret}) | ||||
|                 .then(response => { | ||||
|                     if (response.token) { | ||||
|                         if (wipe) { | ||||
|                             Tokens.clearTokens(); | ||||
|                         } | ||||
|  | ||||
|                         // Set storage token | ||||
|                         Tokens.addToken(response.token); | ||||
|                         return response.token; | ||||
|                     } else { | ||||
|                         Tokens.clearTokens(); | ||||
|                         throw(new Error('No token returned')); | ||||
|                     } | ||||
|                 }); | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         refresh: function () { | ||||
|             return fetch('get', 'tokens') | ||||
|                 .then(response => { | ||||
|                     if (response.token) { | ||||
|                         Tokens.setCurrentToken(response.token); | ||||
|                         return response.token; | ||||
|                     } else { | ||||
|                         Tokens.clearTokens(); | ||||
|                         throw(new Error('No token returned')); | ||||
|                     } | ||||
|                 }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     Users: { | ||||
|  | ||||
|         /** | ||||
|          * @param   {Number|String}  user_id | ||||
|          * @param   {Array}           [expand] | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         getById: function (user_id, expand) { | ||||
|             return fetch('get', 'users/' + user_id + (typeof expand === 'object' && expand.length ? '?expand=' + makeExpansionString(expand) : '')); | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * @param   {Array}    [expand] | ||||
|          * @param   {String}   [query] | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         getAll: function (expand, query) { | ||||
|             return getAllObjects('users', expand, query); | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * @param   {Object}  data | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         create: function (data) { | ||||
|             return fetch('post', 'users', data); | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * @param   {Object}   data | ||||
|          * @param   {Number}   data.id | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         update: function (data) { | ||||
|             let id = data.id; | ||||
|             delete data.id; | ||||
|             return fetch('put', 'users/' + id, data); | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * @param   {Number}  id | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         delete: function (id) { | ||||
|             return fetch('delete', 'users/' + id); | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * | ||||
|          * @param   {Number}   id | ||||
|          * @param   {Object}   auth | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         setPassword: function (id, auth) { | ||||
|             return fetch('put', 'users/' + id + '/auth', auth); | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * @param   {Number}  id | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         loginAs: function (id) { | ||||
|             return fetch('post', 'users/' + id + '/login'); | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * | ||||
|          * @param   {Number}   id | ||||
|          * @param   {Object}   perms | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         setPermissions: function (id, perms) { | ||||
|             return fetch('put', 'users/' + id + '/permissions', perms); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     Nginx: { | ||||
|  | ||||
|         ProxyHosts: { | ||||
|             /** | ||||
|              * @param   {Array}    [expand] | ||||
|              * @param   {String}   [query] | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             getAll: function (expand, query) { | ||||
|                 return getAllObjects('nginx/proxy-hosts', expand, query); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param {Object}  data | ||||
|              */ | ||||
|             create: function (data) { | ||||
|                 return fetch('post', 'nginx/proxy-hosts', data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Object}   data | ||||
|              * @param   {Number}  data.id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             update: function (data) { | ||||
|                 let id = data.id; | ||||
|                 delete data.id; | ||||
|                 return fetch('put', 'nginx/proxy-hosts/' + id, data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             delete: function (id) { | ||||
|                 return fetch('delete', 'nginx/proxy-hosts/' + id); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             get: function (id) { | ||||
|                 return fetch('get', 'nginx/proxy-hosts/' + id); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             enable: function (id) { | ||||
|                 return fetch('post', 'nginx/proxy-hosts/' + id + '/enable'); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             disable: function (id) { | ||||
|                 return fetch('post', 'nginx/proxy-hosts/' + id + '/disable'); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         RedirectionHosts: { | ||||
|             /** | ||||
|              * @param   {Array}    [expand] | ||||
|              * @param   {String}   [query] | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             getAll: function (expand, query) { | ||||
|                 return getAllObjects('nginx/redirection-hosts', expand, query); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param {Object}  data | ||||
|              */ | ||||
|             create: function (data) { | ||||
|                 return fetch('post', 'nginx/redirection-hosts', data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Object}   data | ||||
|              * @param   {Number}   data.id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             update: function (data) { | ||||
|                 let id = data.id; | ||||
|                 delete data.id; | ||||
|                 return fetch('put', 'nginx/redirection-hosts/' + id, data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             delete: function (id) { | ||||
|                 return fetch('delete', 'nginx/redirection-hosts/' + id); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             get: function (id) { | ||||
|                 return fetch('get', 'nginx/redirection-hosts/' + id); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param  {Number}   id | ||||
|              * @param  {FormData} form_data | ||||
|              * @params {Promise} | ||||
|              */ | ||||
|             setCerts: function (id, form_data) { | ||||
|                 return FileUpload('nginx/redirection-hosts/' + id + '/certificates', form_data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             enable: function (id) { | ||||
|                 return fetch('post', 'nginx/redirection-hosts/' + id + '/enable'); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             disable: function (id) { | ||||
|                 return fetch('post', 'nginx/redirection-hosts/' + id + '/disable'); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         Streams: { | ||||
|             /** | ||||
|              * @param   {Array}    [expand] | ||||
|              * @param   {String}   [query] | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             getAll: function (expand, query) { | ||||
|                 return getAllObjects('nginx/streams', expand, query); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param {Object}  data | ||||
|              */ | ||||
|             create: function (data) { | ||||
|                 return fetch('post', 'nginx/streams', data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Object}   data | ||||
|              * @param   {Number}   data.id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             update: function (data) { | ||||
|                 let id = data.id; | ||||
|                 delete data.id; | ||||
|                 return fetch('put', 'nginx/streams/' + id, data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             delete: function (id) { | ||||
|                 return fetch('delete', 'nginx/streams/' + id); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             get: function (id) { | ||||
|                 return fetch('get', 'nginx/streams/' + id); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             enable: function (id) { | ||||
|                 return fetch('post', 'nginx/streams/' + id + '/enable'); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             disable: function (id) { | ||||
|                 return fetch('post', 'nginx/streams/' + id + '/disable'); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         DeadHosts: { | ||||
|             /** | ||||
|              * @param   {Array}    [expand] | ||||
|              * @param   {String}   [query] | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             getAll: function (expand, query) { | ||||
|                 return getAllObjects('nginx/dead-hosts', expand, query); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param {Object}  data | ||||
|              */ | ||||
|             create: function (data) { | ||||
|                 return fetch('post', 'nginx/dead-hosts', data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Object}   data | ||||
|              * @param   {Number}   data.id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             update: function (data) { | ||||
|                 let id = data.id; | ||||
|                 delete data.id; | ||||
|                 return fetch('put', 'nginx/dead-hosts/' + id, data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             delete: function (id) { | ||||
|                 return fetch('delete', 'nginx/dead-hosts/' + id); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             get: function (id) { | ||||
|                 return fetch('get', 'nginx/dead-hosts/' + id); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param  {Number}   id | ||||
|              * @param  {FormData} form_data | ||||
|              * @params {Promise} | ||||
|              */ | ||||
|             setCerts: function (id, form_data) { | ||||
|                 return FileUpload('nginx/dead-hosts/' + id + '/certificates', form_data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             enable: function (id) { | ||||
|                 return fetch('post', 'nginx/dead-hosts/' + id + '/enable'); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             disable: function (id) { | ||||
|                 return fetch('post', 'nginx/dead-hosts/' + id + '/disable'); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         AccessLists: { | ||||
|             /** | ||||
|              * @param   {Array}    [expand] | ||||
|              * @param   {String}   [query] | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             getAll: function (expand, query) { | ||||
|                 return getAllObjects('nginx/access-lists', expand, query); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param {Object}  data | ||||
|              */ | ||||
|             create: function (data) { | ||||
|                 return fetch('post', 'nginx/access-lists', data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Object}   data | ||||
|              * @param   {Number}   data.id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             update: function (data) { | ||||
|                 let id = data.id; | ||||
|                 delete data.id; | ||||
|                 return fetch('put', 'nginx/access-lists/' + id, data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             delete: function (id) { | ||||
|                 return fetch('delete', 'nginx/access-lists/' + id); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         Certificates: { | ||||
|             /** | ||||
|              * @param   {Array}    [expand] | ||||
|              * @param   {String}   [query] | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             getAll: function (expand, query) { | ||||
|                 return getAllObjects('nginx/certificates', expand, query); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param {Object}  data | ||||
|              */ | ||||
|             create: function (data) { | ||||
|                 return fetch('post', 'nginx/certificates', data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Object}   data | ||||
|              * @param   {Number}   data.id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             update: function (data) { | ||||
|                 let id = data.id; | ||||
|                 delete data.id; | ||||
|                 return fetch('put', 'nginx/certificates/' + id, data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             delete: function (id) { | ||||
|                 return fetch('delete', 'nginx/certificates/' + id); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param  {Number}  id | ||||
|              * @param  {FormData} form_data | ||||
|              * @params {Promise} | ||||
|              */ | ||||
|             upload: function (id, form_data) { | ||||
|                 return FileUpload('nginx/certificates/' + id + '/upload', form_data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param  {FormData} form_data | ||||
|              * @params {Promise} | ||||
|              */ | ||||
|             validate: function (form_data) { | ||||
|                 return FileUpload('nginx/certificates/validate', form_data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             renew: function (id) { | ||||
|                 return fetch('post', 'nginx/certificates/' + id + '/renew'); | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     AuditLog: { | ||||
|         /** | ||||
|          * @param   {Array}    [expand] | ||||
|          * @param   {String}   [query] | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         getAll: function (expand, query) { | ||||
|             return getAllObjects('audit-log', expand, query); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     Reports: { | ||||
|  | ||||
|         /** | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         getHostStats: function () { | ||||
|             return fetch('get', 'reports/hosts'); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     Settings: { | ||||
|  | ||||
|         /** | ||||
|          * @param   {String}  setting_id | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         getById: function (setting_id) { | ||||
|             return fetch('get', 'settings/' + setting_id); | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         getAll: function () { | ||||
|             return getAllObjects('settings'); | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * @param   {Object}   data | ||||
|          * @param   {Number}   data.id | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         update: function (data) { | ||||
|             let id = data.id; | ||||
|             delete data.id; | ||||
|             return fetch('put', 'settings/' + id, data); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
							
								
								
									
										80
									
								
								frontend/js/app/audit-log/list/item.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								frontend/js/app/audit-log/list/item.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | ||||
| <td class="text-center"> | ||||
|     <div class="avatar d-block" style="background-image: url(<%- user.avatar || '/images/default-avatar.jpg' %>)"> | ||||
|         <span class="avatar-status <%- user.is_disabled ? 'bg-red' : 'bg-green' %>"></span> | ||||
|     </div> | ||||
| </td> | ||||
| <td> | ||||
|     <div> | ||||
|         <% if (user.is_deleted) { | ||||
|             %> | ||||
|             <span class="mdi-format-strikethrough" title="Deleted"><%- user.name %></span> | ||||
|             <% | ||||
|         } else { | ||||
|             %> | ||||
|             <%- user.name %> | ||||
|             <% | ||||
|         } | ||||
|         %> | ||||
|     </div> | ||||
| </td> | ||||
| <td> | ||||
|     <div> | ||||
|         <% | ||||
|         var items = []; | ||||
|         switch (object_type) { | ||||
|             case 'proxy-host': | ||||
|                 %> <span class="text-success"><i class="fe fe-zap"></i></span> <% | ||||
|                 items = meta.domain_names; | ||||
|                 break; | ||||
|             case 'redirection-host': | ||||
|                 %> <span class="text-yellow"><i class="fe fe-shuffle"></i></span> <% | ||||
|                 items = meta.domain_names; | ||||
|                 break; | ||||
|             case 'stream': | ||||
|                 %> <span class="text-blue"><i class="fe fe-radio"></i></span> <% | ||||
|                 items.push(meta.incoming_port); | ||||
|                 break; | ||||
|             case 'dead-host': | ||||
|                 %> <span class="text-danger"><i class="fe fe-zap-off"></i></span> <% | ||||
|                 items = meta.domain_names; | ||||
|                 break; | ||||
|             case 'access-list': | ||||
|                 %> <span class="text-teal"><i class="fe fe-lock"></i></span> <% | ||||
|                 items.push(meta.name); | ||||
|                 break; | ||||
|             case 'user': | ||||
|                 %> <span class="text-teal"><i class="fe fe-user"></i></span> <% | ||||
|                 items.push(meta.name); | ||||
|                 break; | ||||
|             case 'certificate': | ||||
|                 %> <span class="text-pink"><i class="fe fe-shield"></i></span> <% | ||||
|                 if (meta.provider === 'letsencrypt') { | ||||
|                     items = meta.domain_names; | ||||
|                 } else { | ||||
|                     items.push(meta.nice_name); | ||||
|                 } | ||||
|                 break; | ||||
|         } | ||||
|         %> <%- i18n('audit-log', action, {name: i18n('audit-log', object_type)}) %> | ||||
|         — | ||||
|         <% | ||||
|         if (items && items.length) { | ||||
|             items.map(function(item) { | ||||
|                 %> | ||||
|                 <span class="tag"><%- item %></span> | ||||
|                 <% | ||||
|             }); | ||||
|         } else { | ||||
|             %> | ||||
|             #<%- object_id %> | ||||
|             <% | ||||
|         } | ||||
|         %> | ||||
|     </div> | ||||
|     <div class="small text-muted"> | ||||
|         <%- formatDbDate(created_on, 'Do MMMM YYYY, h:mm a') %> | ||||
|     </div> | ||||
| </td> | ||||
| <td class="text-right"> | ||||
|     <a href="#" class="meta btn btn-secondary btn-sm"><%- i18n('audit-log', 'view-meta') %></a> | ||||
| </td> | ||||
							
								
								
									
										32
									
								
								frontend/js/app/audit-log/list/item.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								frontend/js/app/audit-log/list/item.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| const Mn         = require('backbone.marionette'); | ||||
| const Controller = require('../../controller'); | ||||
| const template   = require('./item.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template: template, | ||||
|     tagName:  'tr', | ||||
|  | ||||
|     ui: { | ||||
|         meta: 'a.meta' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.meta': function (e) { | ||||
|             e.preventDefault(); | ||||
|             Controller.showAuditMeta(this.model); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         more: function() { | ||||
|             switch (this.object_type) { | ||||
|                 case 'redirection-host': | ||||
|                 case 'stream': | ||||
|                 case 'proxy-host': | ||||
|                     return this.meta.domain_names.join(', '); | ||||
|             } | ||||
|  | ||||
|             return '#' + (this.object_id || '?'); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										9
									
								
								frontend/js/app/audit-log/list/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								frontend/js/app/audit-log/list/main.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| <thead> | ||||
|     <th width="30"> </th> | ||||
|     <th>User</th> | ||||
|     <th>Event</th> | ||||
|     <th> </th> | ||||
| </thead> | ||||
| <tbody> | ||||
|     <!-- items --> | ||||
| </tbody> | ||||
							
								
								
									
										27
									
								
								frontend/js/app/audit-log/list/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								frontend/js/app/audit-log/list/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| 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 card-table', | ||||
|     template:  template, | ||||
|  | ||||
|     regions: { | ||||
|         body: { | ||||
|             el:             'tbody', | ||||
|             replaceElement: true | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         this.showChildView('body', new TableBody({ | ||||
|             collection: this.collection | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										15
									
								
								frontend/js/app/audit-log/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								frontend/js/app/audit-log/main.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| <div class="card"> | ||||
|     <div class="card-status bg-teal"></div> | ||||
|     <div class="card-header"> | ||||
|         <h3 class="card-title"><%- i18n('audit-log', 'title') %></h3> | ||||
|     </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> | ||||
							
								
								
									
										53
									
								
								frontend/js/app/audit-log/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								frontend/js/app/audit-log/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| const Mn            = require('backbone.marionette'); | ||||
| const App           = require('../main'); | ||||
| const AuditLogModel = require('../../models/audit-log'); | ||||
| const ListView      = require('./list/main'); | ||||
| const template      = require('./main.ejs'); | ||||
| const ErrorView     = require('../error/main'); | ||||
| const EmptyView     = require('../empty/main'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     id:       'audit-log', | ||||
|     template: template, | ||||
|  | ||||
|     ui: { | ||||
|         list_region: '.list-region', | ||||
|         dimmer:      '.dimmer' | ||||
|     }, | ||||
|  | ||||
|     regions: { | ||||
|         list_region: '@ui.list_region' | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         let view = this; | ||||
|  | ||||
|         App.Api.AuditLog.getAll(['user']) | ||||
|             .then(response => { | ||||
|                 if (!view.isDestroyed() && response && response.length) { | ||||
|                     view.showChildView('list_region', new ListView({ | ||||
|                         collection: new AuditLogModel.Collection(response) | ||||
|                     })); | ||||
|                 } else { | ||||
|                     view.showChildView('list_region', new EmptyView({ | ||||
|                         title:    App.i18n('audit-log', 'empty'), | ||||
|                         subtitle: App.i18n('audit-log', 'empty-subtitle') | ||||
|                     })); | ||||
|                 } | ||||
|             }) | ||||
|             .catch(err => { | ||||
|                 view.showChildView('list_region', new ErrorView({ | ||||
|                     code:    err.code, | ||||
|                     message: err.message, | ||||
|                     retry:   function () { | ||||
|                         App.Controller.showAuditLog(); | ||||
|                     } | ||||
|                 })); | ||||
|  | ||||
|                 console.error(err); | ||||
|             }) | ||||
|             .then(() => { | ||||
|                 view.ui.dimmer.removeClass('active'); | ||||
|             }); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										27
									
								
								frontend/js/app/audit-log/meta.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								frontend/js/app/audit-log/meta.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- i18n('audit-log', 'meta-title') %></h5> | ||||
|         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> | ||||
|     </div> | ||||
|     <div class="modal-body"> | ||||
|         <div class="mb-2"> | ||||
|             <div class="tag tag-dark"> | ||||
|                 <%- i18n('audit-log', action, {name: i18n('audit-log', object_type)}) %> | ||||
|                 <span class="tag-addon tag-orange">#<%- object_id %></span> | ||||
|             </div> | ||||
|             <div class="tag tag-dark"> | ||||
|                 <%- i18n('audit-log', 'user') %> | ||||
|                 <span class="tag-addon tag-teal"><%- user.name %></span> | ||||
|             </div> | ||||
|             <div class="tag tag-dark"> | ||||
|                 <%- i18n('audit-log', 'date') %> | ||||
|                 <span class="tag-addon tag-primary"><%- formatDbDate(created_on, 'Do MMMM YYYY, h:mm a') %></span> | ||||
|             </div> | ||||
|         </div> | ||||
|  | ||||
|         <pre><%- JSON.stringify(meta, null, 2) %></pre> | ||||
|     </div> | ||||
|     <div class="modal-footer"> | ||||
|         <button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'close') %></button> | ||||
|     </div> | ||||
| </div> | ||||
							
								
								
									
										7
									
								
								frontend/js/app/audit-log/meta.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								frontend/js/app/audit-log/meta.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const template = require('./meta.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'modal-dialog wide' | ||||
| }); | ||||
							
								
								
									
										10
									
								
								frontend/js/app/cache.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								frontend/js/app/cache.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| const UserModel = require('../models/user'); | ||||
|  | ||||
| let cache = { | ||||
|     User:    new UserModel.Model(), | ||||
|     locale:  'en', | ||||
|     version: null | ||||
| }; | ||||
|  | ||||
| module.exports = cache; | ||||
|  | ||||
							
								
								
									
										434
									
								
								frontend/js/app/controller.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										434
									
								
								frontend/js/app/controller.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,434 @@ | ||||
| const Backbone = require('backbone'); | ||||
| const Cache    = require('./cache'); | ||||
| const Tokens   = require('./tokens'); | ||||
|  | ||||
| module.exports = { | ||||
|  | ||||
|     /** | ||||
|      * @param {String} route | ||||
|      * @param {Object} [options] | ||||
|      * @returns {Boolean} | ||||
|      */ | ||||
|     navigate: function (route, options) { | ||||
|         options = options || {}; | ||||
|         Backbone.history.navigate(route.toString(), options); | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Login | ||||
|      */ | ||||
|     showLogin: function () { | ||||
|         window.location = '/login'; | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Users | ||||
|      */ | ||||
|     showUsers: function () { | ||||
|         let controller = this; | ||||
|         if (Cache.User.isAdmin()) { | ||||
|             require(['./main', './users/main'], (App, View) => { | ||||
|                 controller.navigate('/users'); | ||||
|                 App.UI.showAppContent(new View()); | ||||
|             }); | ||||
|         } else { | ||||
|             this.showDashboard(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * User Form | ||||
|      * | ||||
|      * @param [model] | ||||
|      */ | ||||
|     showUserForm: function (model) { | ||||
|         if (Cache.User.isAdmin()) { | ||||
|             require(['./main', './user/form'], function (App, View) { | ||||
|                 App.UI.showModalDialog(new View({model: model})); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * User Permissions Form | ||||
|      * | ||||
|      * @param model | ||||
|      */ | ||||
|     showUserPermissions: function (model) { | ||||
|         if (Cache.User.isAdmin()) { | ||||
|             require(['./main', './user/permissions'], function (App, View) { | ||||
|                 App.UI.showModalDialog(new View({model: model})); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * User Password Form | ||||
|      * | ||||
|      * @param model | ||||
|      */ | ||||
|     showUserPasswordForm: function (model) { | ||||
|         if (Cache.User.isAdmin() || model.get('id') === Cache.User.get('id')) { | ||||
|             require(['./main', './user/password'], function (App, View) { | ||||
|                 App.UI.showModalDialog(new View({model: model})); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * User Delete Confirm | ||||
|      * | ||||
|      * @param model | ||||
|      */ | ||||
|     showUserDeleteConfirm: function (model) { | ||||
|         if (Cache.User.isAdmin() && model.get('id') !== Cache.User.get('id')) { | ||||
|             require(['./main', './user/delete'], function (App, View) { | ||||
|                 App.UI.showModalDialog(new View({model: model})); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Dashboard | ||||
|      */ | ||||
|     showDashboard: function () { | ||||
|         let controller = this; | ||||
|  | ||||
|         require(['./main', './dashboard/main'], (App, View) => { | ||||
|             controller.navigate('/'); | ||||
|             App.UI.showAppContent(new View()); | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Nginx Proxy Hosts | ||||
|      */ | ||||
|     showNginxProxy: function () { | ||||
|         if (Cache.User.isAdmin() || Cache.User.canView('proxy_hosts')) { | ||||
|             let controller = this; | ||||
|  | ||||
|             require(['./main', './nginx/proxy/main'], (App, View) => { | ||||
|                 controller.navigate('/nginx/proxy'); | ||||
|                 App.UI.showAppContent(new View()); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Nginx Proxy Host Form | ||||
|      * | ||||
|      * @param [model] | ||||
|      */ | ||||
|     showNginxProxyForm: function (model) { | ||||
|         if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) { | ||||
|             require(['./main', './nginx/proxy/form'], function (App, View) { | ||||
|                 App.UI.showModalDialog(new View({model: model})); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Proxy Host Delete Confirm | ||||
|      * | ||||
|      * @param model | ||||
|      */ | ||||
|     showNginxProxyDeleteConfirm: function (model) { | ||||
|         if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) { | ||||
|             require(['./main', './nginx/proxy/delete'], function (App, View) { | ||||
|                 App.UI.showModalDialog(new View({model: model})); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Nginx Redirection Hosts | ||||
|      */ | ||||
|     showNginxRedirection: function () { | ||||
|         if (Cache.User.isAdmin() || Cache.User.canView('redirection_hosts')) { | ||||
|             let controller = this; | ||||
|  | ||||
|             require(['./main', './nginx/redirection/main'], (App, View) => { | ||||
|                 controller.navigate('/nginx/redirection'); | ||||
|                 App.UI.showAppContent(new View()); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * 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 | ||||
|      */ | ||||
|     showNginxStream: function () { | ||||
|         if (Cache.User.isAdmin() || Cache.User.canView('streams')) { | ||||
|             let controller = this; | ||||
|  | ||||
|             require(['./main', './nginx/stream/main'], (App, View) => { | ||||
|                 controller.navigate('/nginx/stream'); | ||||
|                 App.UI.showAppContent(new View()); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Stream Form | ||||
|      * | ||||
|      * @param [model] | ||||
|      */ | ||||
|     showNginxStreamForm: function (model) { | ||||
|         if (Cache.User.isAdmin() || Cache.User.canManage('streams')) { | ||||
|             require(['./main', './nginx/stream/form'], function (App, View) { | ||||
|                 App.UI.showModalDialog(new View({model: model})); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Stream Delete Confirm | ||||
|      * | ||||
|      * @param model | ||||
|      */ | ||||
|     showNginxStreamDeleteConfirm: function (model) { | ||||
|         if (Cache.User.isAdmin() || Cache.User.canManage('streams')) { | ||||
|             require(['./main', './nginx/stream/delete'], function (App, View) { | ||||
|                 App.UI.showModalDialog(new View({model: model})); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Nginx Dead Hosts | ||||
|      */ | ||||
|     showNginxDead: function () { | ||||
|         if (Cache.User.isAdmin() || Cache.User.canView('dead_hosts')) { | ||||
|             let controller = this; | ||||
|  | ||||
|             require(['./main', './nginx/dead/main'], (App, View) => { | ||||
|                 controller.navigate('/nginx/404'); | ||||
|                 App.UI.showAppContent(new View()); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Dead Host Form | ||||
|      * | ||||
|      * @param [model] | ||||
|      */ | ||||
|     showNginxDeadForm: function (model) { | ||||
|         if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) { | ||||
|             require(['./main', './nginx/dead/form'], function (App, View) { | ||||
|                 App.UI.showModalDialog(new View({model: model})); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Dead Host Delete Confirm | ||||
|      * | ||||
|      * @param model | ||||
|      */ | ||||
|     showNginxDeadDeleteConfirm: function (model) { | ||||
|         if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) { | ||||
|             require(['./main', './nginx/dead/delete'], function (App, View) { | ||||
|                 App.UI.showModalDialog(new View({model: model})); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Help Dialog | ||||
|      * | ||||
|      * @param {String}  title | ||||
|      * @param {String}  content | ||||
|      */ | ||||
|     showHelp: function (title, content) { | ||||
|         require(['./main', './help/main'], function (App, View) { | ||||
|             App.UI.showModalDialog(new View({title: title, content: content})); | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Nginx Access | ||||
|      */ | ||||
|     showNginxAccess: function () { | ||||
|         if (Cache.User.isAdmin() || Cache.User.canView('access_lists')) { | ||||
|             let controller = this; | ||||
|  | ||||
|             require(['./main', './nginx/access/main'], (App, View) => { | ||||
|                 controller.navigate('/nginx/access'); | ||||
|                 App.UI.showAppContent(new View()); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * 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})); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Access List Delete Confirm | ||||
|      * | ||||
|      * @param model | ||||
|      */ | ||||
|     showNginxAccessListDeleteConfirm: function (model) { | ||||
|         if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) { | ||||
|             require(['./main', './nginx/access/delete'], function (App, View) { | ||||
|                 App.UI.showModalDialog(new View({model: model})); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Nginx Certificates | ||||
|      */ | ||||
|     showNginxCertificates: function () { | ||||
|         if (Cache.User.isAdmin() || Cache.User.canView('certificates')) { | ||||
|             let controller = this; | ||||
|  | ||||
|             require(['./main', './nginx/certificates/main'], (App, View) => { | ||||
|                 controller.navigate('/nginx/certificates'); | ||||
|                 App.UI.showAppContent(new View()); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Nginx Certificate Form | ||||
|      * | ||||
|      * @param [model] | ||||
|      */ | ||||
|     showNginxCertificateForm: function (model) { | ||||
|         if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { | ||||
|             require(['./main', './nginx/certificates/form'], function (App, View) { | ||||
|                 App.UI.showModalDialog(new View({model: model})); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Certificate Renew | ||||
|      * | ||||
|      * @param model | ||||
|      */ | ||||
|     showNginxCertificateRenew: function (model) { | ||||
|         if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { | ||||
|             require(['./main', './nginx/certificates/renew'], function (App, View) { | ||||
|                 App.UI.showModalDialog(new View({model: model})); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Certificate Delete Confirm | ||||
|      * | ||||
|      * @param model | ||||
|      */ | ||||
|     showNginxCertificateDeleteConfirm: function (model) { | ||||
|         if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { | ||||
|             require(['./main', './nginx/certificates/delete'], function (App, View) { | ||||
|                 App.UI.showModalDialog(new View({model: model})); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Audit Log | ||||
|      */ | ||||
|     showAuditLog: function () { | ||||
|         let controller = this; | ||||
|         if (Cache.User.isAdmin()) { | ||||
|             require(['./main', './audit-log/main'], (App, View) => { | ||||
|                 controller.navigate('/audit-log'); | ||||
|                 App.UI.showAppContent(new View()); | ||||
|             }); | ||||
|         } else { | ||||
|             this.showDashboard(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Audit Log Metadata | ||||
|      * | ||||
|      * @param model | ||||
|      */ | ||||
|     showAuditMeta: function (model) { | ||||
|         if (Cache.User.isAdmin()) { | ||||
|             require(['./main', './audit-log/meta'], function (App, View) { | ||||
|                 App.UI.showModalDialog(new View({model: model})); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Settings | ||||
|      */ | ||||
|     showSettings: function () { | ||||
|         let controller = this; | ||||
|         if (Cache.User.isAdmin()) { | ||||
|             require(['./main', './settings/main'], (App, View) => { | ||||
|                 controller.navigate('/settings'); | ||||
|                 App.UI.showAppContent(new View()); | ||||
|             }); | ||||
|         } else { | ||||
|             this.showDashboard(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Settings Item Form | ||||
|      * | ||||
|      * @param model | ||||
|      */ | ||||
|     showSettingForm: function (model) { | ||||
|         if (Cache.User.isAdmin()) { | ||||
|             if (model.get('id') === 'default-site') { | ||||
|                 require(['./main', './settings/default-site/main'], function (App, View) { | ||||
|                     App.UI.showModalDialog(new View({model: model})); | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Logout | ||||
|      */ | ||||
|     logout: function () { | ||||
|         Tokens.dropTopToken(); | ||||
|         this.showLogin(); | ||||
|     } | ||||
| }; | ||||
							
								
								
									
										67
									
								
								frontend/js/app/dashboard/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								frontend/js/app/dashboard/main.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | ||||
| <div class="page-header"> | ||||
|     <h1 class="page-title"><%- i18n('dashboard', 'title', {name: getUserName()}) %></h1> | ||||
| </div> | ||||
|  | ||||
| <% if (columns) { %> | ||||
| <div class="row"> | ||||
|     <% if (canShow('proxy_hosts')) { %> | ||||
|     <div class="col-sm-<%- 24 / columns %> col-lg-<%- 12 / columns %>"> | ||||
|         <div class="card p-3"> | ||||
|             <div class="d-flex align-items-center"> | ||||
|                     <span class="stamp stamp-md bg-green mr-3"> | ||||
|                       <i class="fe fe-zap"></i> | ||||
|                     </span> | ||||
|                 <div> | ||||
|                     <h4 class="m-0"><a href="/nginx/proxy"><%- getHostStat('proxy') %> <small><%- i18n('proxy-hosts', 'title') %></small></a></h4> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
|     <% } %> | ||||
|  | ||||
|     <% if (canShow('redirection_hosts')) { %> | ||||
|     <div class="col-sm-<%- 24 / columns %> col-lg-<%- 12 / columns %>"> | ||||
|         <div class="card p-3"> | ||||
|             <div class="d-flex align-items-center"> | ||||
|                     <span class="stamp stamp-md bg-yellow mr-3"> | ||||
|                       <i class="fe fe-shuffle"></i> | ||||
|                     </span> | ||||
|                 <div> | ||||
|                     <h4 class="m-0"><a href="/nginx/redirection"><%- getHostStat('redirection') %> <small><%- i18n('redirection-hosts', 'title') %></small></a></h4> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
|     <% } %> | ||||
|  | ||||
|     <% if (canShow('streams')) { %> | ||||
|     <div class="col-sm-<%- 24 / columns %> col-lg-<%- 12 / columns %>"> | ||||
|         <div class="card p-3"> | ||||
|             <div class="d-flex align-items-center"> | ||||
|                     <span class="stamp stamp-md bg-blue mr-3"> | ||||
|                       <i class="fe fe-radio"></i> | ||||
|                     </span> | ||||
|                 <div> | ||||
|                     <h4 class="m-0"><a href="/nginx/stream"><%- getHostStat('stream') %> <small> <%- i18n('streams', 'title') %></small></a></h4> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
|     <% } %> | ||||
|  | ||||
|     <% if (canShow('dead_hosts')) { %> | ||||
|     <div class="col-sm-<%- 24 / columns %> col-lg-<%- 12 / columns %>"> | ||||
|         <div class="card p-3"> | ||||
|             <div class="d-flex align-items-center"> | ||||
|                     <span class="stamp stamp-md bg-red mr-3"> | ||||
|                       <i class="fe fe-zap-off"></i> | ||||
|                     </span> | ||||
|                 <div> | ||||
|                     <h4 class="m-0"><a href="/nginx/404"><%- getHostStat('dead') %> <small><%- i18n('dead-hosts', 'title') %></small></a></h4> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
|     <% } %> | ||||
| </div> | ||||
| <% } %> | ||||
							
								
								
									
										92
									
								
								frontend/js/app/dashboard/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								frontend/js/app/dashboard/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| const Mn         = require('backbone.marionette'); | ||||
| const Cache      = require('../cache'); | ||||
| const Controller = require('../controller'); | ||||
| const Api        = require('../api'); | ||||
| const Helpers    = require('../../lib/helpers'); | ||||
| const template   = require('./main.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template: template, | ||||
|     id:       'dashboard', | ||||
|     columns:  0, | ||||
|  | ||||
|     stats: {}, | ||||
|  | ||||
|     ui: { | ||||
|         links: 'a' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.links': function (e) { | ||||
|             e.preventDefault(); | ||||
|             Controller.navigate($(e.currentTarget).attr('href'), true); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: function () { | ||||
|         let view = this; | ||||
|  | ||||
|         return { | ||||
|             getUserName: function () { | ||||
|                 return Cache.User.get('nickname') || Cache.User.get('name'); | ||||
|             }, | ||||
|  | ||||
|             getHostStat: function (type) { | ||||
|                 if (view.stats && typeof view.stats.hosts !== 'undefined' && typeof view.stats.hosts[type] !== 'undefined') { | ||||
|                     return Helpers.niceNumber(view.stats.hosts[type]); | ||||
|                 } | ||||
|  | ||||
|                 return '-'; | ||||
|             }, | ||||
|  | ||||
|             canShow: function (perm) { | ||||
|                 return Cache.User.isAdmin() || Cache.User.canView(perm); | ||||
|             }, | ||||
|  | ||||
|             columns: view.columns | ||||
|         }; | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         let view = this; | ||||
|  | ||||
|         if (typeof view.stats.hosts === 'undefined') { | ||||
|             Api.Reports.getHostStats() | ||||
|                 .then(response => { | ||||
|                     if (!view.isDestroyed()) { | ||||
|                         view.stats.hosts = response; | ||||
|                         view.render(); | ||||
|                     } | ||||
|                 }) | ||||
|                 .catch(err => { | ||||
|                     console.log(err); | ||||
|                 }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * @param {Object}  [model] | ||||
|      */ | ||||
|     preRender: function (model) { | ||||
|         this.columns = 0; | ||||
|  | ||||
|         // calculate the available columns based on permissions for the objects | ||||
|         // and store as a variable | ||||
|         //let view = this; | ||||
|         let perms = ['proxy_hosts', 'redirection_hosts', 'streams', 'dead_hosts']; | ||||
|  | ||||
|         perms.map(perm => { | ||||
|             this.columns += Cache.User.isAdmin() || Cache.User.canView(perm) ? 1 : 0; | ||||
|         }); | ||||
|  | ||||
|         // Prevent double rendering on initial calls | ||||
|         if (typeof model !== 'undefined') { | ||||
|             this.render(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     initialize: function () { | ||||
|         this.preRender(); | ||||
|         this.listenTo(Cache.User, 'change', this.preRender); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										11
									
								
								frontend/js/app/empty/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								frontend/js/app/empty/main.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| <% if (title) { %> | ||||
|     <h1 class="h2 mb-3"><%- title %></h1> | ||||
| <% } | ||||
|  | ||||
| if (subtitle) { %> | ||||
|     <p class="h4 text-muted font-weight-normal mb-7"><%- subtitle %></p> | ||||
| <% } | ||||
|  | ||||
| if (link) { %> | ||||
|     <a class="btn btn-<%- btn_color %>" href="#"><%- link %></a> | ||||
| <% } %> | ||||
							
								
								
									
										33
									
								
								frontend/js/app/empty/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								frontend/js/app/empty/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const template = require('./main.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     className: 'text-center m-7', | ||||
|     template:  template, | ||||
|  | ||||
|     options: { | ||||
|         btn_color: 'teal' | ||||
|     }, | ||||
|  | ||||
|     ui: { | ||||
|         action: 'a' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.action': function (e) { | ||||
|             e.preventDefault(); | ||||
|             this.getOption('action')(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: function () { | ||||
|         return { | ||||
|             title:     this.getOption('title'), | ||||
|             subtitle:  this.getOption('subtitle'), | ||||
|             link:      this.getOption('link'), | ||||
|             action:    typeof this.getOption('action') === 'function', | ||||
|             btn_color: this.getOption('btn_color') | ||||
|         }; | ||||
|     } | ||||
|  | ||||
| }); | ||||
							
								
								
									
										7
									
								
								frontend/js/app/error/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								frontend/js/app/error/main.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| <i class="fe fe-alert-triangle mr-2" aria-hidden="true"></i> | ||||
| <%= code ? '<strong>' + code + '</strong> — ' : '' %> | ||||
| <%- message %> | ||||
|  | ||||
| <% if (retry) { %> | ||||
|     <br><br><a href="#" class="btn btn-sm btn-warning retry"><%- i18n('str', 'try-again') %></a> | ||||
| <% } %> | ||||
							
								
								
									
										27
									
								
								frontend/js/app/error/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								frontend/js/app/error/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const template = require('./main.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'alert alert-icon alert-warning m-5', | ||||
|  | ||||
|     ui: { | ||||
|         retry: 'a.retry' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.retry': function (e) { | ||||
|             e.preventDefault(); | ||||
|             this.getOption('retry')(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: function () { | ||||
|         return { | ||||
|             message: this.getOption('message'), | ||||
|             code:    this.getOption('code'), | ||||
|             retry:   typeof this.getOption('retry') === 'function' | ||||
|         }; | ||||
|     } | ||||
|  | ||||
| }); | ||||
							
								
								
									
										12
									
								
								frontend/js/app/help/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								frontend/js/app/help/main.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- title %></h5> | ||||
|         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> | ||||
|     </div> | ||||
|     <div class="modal-body"> | ||||
|         <%= content %> | ||||
|     </div> | ||||
|     <div class="modal-footer"> | ||||
|         <button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'close') %></button> | ||||
|     </div> | ||||
| </div> | ||||
							
								
								
									
										16
									
								
								frontend/js/app/help/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								frontend/js/app/help/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const template = require('./main.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'modal-dialog wide', | ||||
|  | ||||
|     templateContext: function () { | ||||
|         let content = this.getOption('content').split("\n"); | ||||
|  | ||||
|         return { | ||||
|             title:   this.getOption('title'), | ||||
|             content: '<p>' + content.join('</p><p>') + '</p>' | ||||
|         }; | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										23
									
								
								frontend/js/app/i18n.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								frontend/js/app/i18n.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| const Cache    = ('./cache'); | ||||
| const messages = require('../i18n/messages.json'); | ||||
|  | ||||
| /** | ||||
|  * @param {String}  namespace | ||||
|  * @param {String}  key | ||||
|  * @param {Object}  [data] | ||||
|  */ | ||||
| module.exports = function (namespace, key, data) { | ||||
|     let locale = Cache.locale; | ||||
|     // check that the locale exists | ||||
|     if (typeof messages[locale] === 'undefined') { | ||||
|         locale = 'en'; | ||||
|     } | ||||
|  | ||||
|     if (typeof messages[locale][namespace] !== 'undefined' && typeof messages[locale][namespace][key] !== 'undefined') { | ||||
|         return messages[locale][namespace][key](data); | ||||
|     } else if (locale !== 'en' && typeof messages['en'][namespace] !== 'undefined' && typeof messages['en'][namespace][key] !== 'undefined') { | ||||
|         return messages['en'][namespace][key](data); | ||||
|     } | ||||
|  | ||||
|     return '(MISSING: ' + namespace + '/' + key + ')'; | ||||
| }; | ||||
							
								
								
									
										155
									
								
								frontend/js/app/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								frontend/js/app/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,155 @@ | ||||
| const _          = require('underscore'); | ||||
| const Backbone   = require('backbone'); | ||||
| const Mn         = require('../lib/marionette'); | ||||
| const Cache      = require('./cache'); | ||||
| const Controller = require('./controller'); | ||||
| const Router     = require('./router'); | ||||
| const Api        = require('./api'); | ||||
| const Tokens     = require('./tokens'); | ||||
| const UI         = require('./ui/main'); | ||||
| const i18n       = require('./i18n'); | ||||
|  | ||||
| const App = Mn.Application.extend({ | ||||
|  | ||||
|     Cache:      Cache, | ||||
|     Api:        Api, | ||||
|     UI:         null, | ||||
|     i18n:       i18n, | ||||
|     Controller: Controller, | ||||
|  | ||||
|     region: { | ||||
|         el:             '#app', | ||||
|         replaceElement: true | ||||
|     }, | ||||
|  | ||||
|     onStart: function (app, options) { | ||||
|         console.log(i18n('main', 'welcome')); | ||||
|  | ||||
|         // Check if token is coming through | ||||
|         if (this.getParam('token')) { | ||||
|             Tokens.addToken(this.getParam('token')); | ||||
|         } | ||||
|  | ||||
|         // Check if we are still logged in by refreshing the token | ||||
|         Api.status() | ||||
|             .then(result => { | ||||
|                 Cache.version = [result.version.major, result.version.minor, result.version.revision].join('.'); | ||||
|             }) | ||||
|             .then(Api.Tokens.refresh) | ||||
|             .then(this.bootstrap) | ||||
|             .then(() => { | ||||
|                 console.info(i18n('main', 'logged-in', Cache.User.attributes)); | ||||
|                 this.bootstrapTimer(); | ||||
|                 this.refreshTokenTimer(); | ||||
|  | ||||
|                 this.UI = new UI(); | ||||
|                 this.UI.on('render', () => { | ||||
|                     new Router(options); | ||||
|                     Backbone.history.start({pushState: true}); | ||||
|  | ||||
|                     // Ask the admin use to change their details | ||||
|                     if (Cache.User.get('email') === 'admin@example.com') { | ||||
|                         Controller.showUserForm(Cache.User); | ||||
|                     } | ||||
|                 }); | ||||
|  | ||||
|                 this.getRegion().show(this.UI); | ||||
|             }) | ||||
|             .catch(err => { | ||||
|                 console.warn('Not logged in:', err.message); | ||||
|                 Controller.showLogin(); | ||||
|             }); | ||||
|     }, | ||||
|  | ||||
|     History: { | ||||
|         replace: function (data) { | ||||
|             window.history.replaceState(_.extend(window.history.state || {}, data), document.title); | ||||
|         }, | ||||
|  | ||||
|         get: function (attr) { | ||||
|             return window.history.state ? window.history.state[attr] : undefined; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     getParam: function (name) { | ||||
|         name        = name.replace(/[\[\]]/g, '\\$&'); | ||||
|         let url     = window.location.href; | ||||
|         let regex   = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'); | ||||
|         let results = regex.exec(url); | ||||
|  | ||||
|         if (!results) { | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         if (!results[2]) { | ||||
|             return ''; | ||||
|         } | ||||
|  | ||||
|         return decodeURIComponent(results[2].replace(/\+/g, ' ')); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Get user and other base info to start prime the cache and the application | ||||
|      * | ||||
|      * @returns {Promise} | ||||
|      */ | ||||
|     bootstrap: function () { | ||||
|         return Api.Users.getById('me', ['permissions']) | ||||
|             .then(response => { | ||||
|                 Cache.User.set(response); | ||||
|                 Tokens.setCurrentName(response.nickname || response.name); | ||||
|             }); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Bootstraps the user from time to time | ||||
|      */ | ||||
|     bootstrapTimer: function () { | ||||
|         setTimeout(() => { | ||||
|             Api.status() | ||||
|                 .then(result => { | ||||
|                     let version = [result.version.major, result.version.minor, result.version.revision].join('.'); | ||||
|                     if (version !== Cache.version) { | ||||
|                         document.location.reload(); | ||||
|                     } | ||||
|                 }) | ||||
|                 .then(this.bootstrap) | ||||
|                 .then(() => { | ||||
|                     this.bootstrapTimer(); | ||||
|                 }) | ||||
|                 .catch(err => { | ||||
|                     if (err.message !== 'timeout' && err.code && err.code !== 400) { | ||||
|                         console.log(err); | ||||
|                         console.error(err.message); | ||||
|                         console.info('Not logged in?'); | ||||
|                         Controller.showLogin(); | ||||
|                     } else { | ||||
|                         this.bootstrapTimer(); | ||||
|                     } | ||||
|                 }); | ||||
|         }, 30 * 1000); // 30 seconds | ||||
|     }, | ||||
|  | ||||
|     refreshTokenTimer: function () { | ||||
|         setTimeout(() => { | ||||
|             return Api.Tokens.refresh() | ||||
|                 .then(this.bootstrap) | ||||
|                 .then(() => { | ||||
|                     this.refreshTokenTimer(); | ||||
|                 }) | ||||
|                 .catch(err => { | ||||
|                     if (err.message !== 'timeout' && err.code && err.code !== 400) { | ||||
|                         console.log(err); | ||||
|                         console.error(err.message); | ||||
|                         console.info('Not logged in?'); | ||||
|                         Controller.showLogin(); | ||||
|                     } else { | ||||
|                         this.refreshTokenTimer(); | ||||
|                     } | ||||
|                 }); | ||||
|         }, 10 * 60 * 1000); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const app      = new App(); | ||||
| module.exports = app; | ||||
							
								
								
									
										23
									
								
								frontend/js/app/nginx/access/delete.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								frontend/js/app/nginx/access/delete.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| <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') %> | ||||
|                     <% if (proxy_host_count) { %> | ||||
|                         <br><br> | ||||
|                         <%- i18n('access-lists', 'delete-has-hosts', {count: proxy_host_count}) %> | ||||
|                     <% } %> | ||||
|                 </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> | ||||
							
								
								
									
										32
									
								
								frontend/js/app/nginx/access/delete.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								frontend/js/app/nginx/access/delete.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../main'); | ||||
| const template = require('./delete.ejs'); | ||||
|  | ||||
| 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.AccessLists.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'); | ||||
|                 }); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										34
									
								
								frontend/js/app/nginx/access/form.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								frontend/js/app/nginx/access/form.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- i18n('access-lists', 'form-title', {id: id}) %></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"> | ||||
|                     <div class="form-group"> | ||||
|                         <label class="form-label"><%- i18n('str', 'name') %> <span class="form-required">*</span></label> | ||||
|                         <input type="text" name="name" class="form-control" value="<%- name %>" required> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <div class="col-sm-6 col-md-6"> | ||||
|                     <div class="form-group"> | ||||
|                         <label class="form-label"><%- i18n('str', 'username') %></label> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <div class="col-sm-6 col-md-6"> | ||||
|                     <div class="form-group"> | ||||
|                         <label class="form-label"><%- i18n('str', 'password') %></label> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|  | ||||
|             <div class="items"><!-- items --></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> | ||||
							
								
								
									
										110
									
								
								frontend/js/app/nginx/access/form.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								frontend/js/app/nginx/access/form.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | ||||
| const Mn              = require('backbone.marionette'); | ||||
| const App             = require('../../main'); | ||||
| const AccessListModel = require('../../../models/access-list'); | ||||
| const template        = require('./form.ejs'); | ||||
| const ItemView        = require('./form/item'); | ||||
|  | ||||
| require('jquery-serializejson'); | ||||
|  | ||||
| const ItemsView = Mn.CollectionView.extend({ | ||||
|     childView: ItemView | ||||
| }); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'modal-dialog', | ||||
|  | ||||
|     ui: { | ||||
|         items_region: '.items', | ||||
|         form:         'form', | ||||
|         buttons:      '.modal-footer button', | ||||
|         cancel:       'button.cancel', | ||||
|         save:         'button.save' | ||||
|     }, | ||||
|  | ||||
|     regions: { | ||||
|         items_region: '@ui.items_region' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         '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 form_data  = this.ui.form.serializeJSON(); | ||||
|             let items_data = []; | ||||
|  | ||||
|             form_data.username.map(function (val, idx) { | ||||
|                 if (val.trim().length) { | ||||
|                     items_data.push({ | ||||
|                         username: val.trim(), | ||||
|                         password: form_data.password[idx] | ||||
|                     }); | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             if (!items_data.length) { | ||||
|                 alert('You must specify at least 1 Username and Password combination'); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             let data = { | ||||
|                 name:  form_data.name, | ||||
|                 items: items_data | ||||
|             }; | ||||
|  | ||||
|             let method = App.Api.Nginx.AccessLists.create; | ||||
|             let is_new = true; | ||||
|  | ||||
|             if (this.model.get('id')) { | ||||
|                 // edit | ||||
|                 is_new  = false; | ||||
|                 method  = App.Api.Nginx.AccessLists.update; | ||||
|                 data.id = this.model.get('id'); | ||||
|             } | ||||
|  | ||||
|             this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); | ||||
|             method(data) | ||||
|                 .then(result => { | ||||
|                     view.model.set(result); | ||||
|  | ||||
|                     App.UI.closeModal(function () { | ||||
|                         if (is_new) { | ||||
|                             App.Controller.showNginxAccess(); | ||||
|                         } | ||||
|                     }); | ||||
|                 }) | ||||
|                 .catch(err => { | ||||
|                     alert(err.message); | ||||
|                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||
|                 }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         let items = this.model.get('items'); | ||||
|  | ||||
|         // Add empty items to the end of the list. This is cheating but hey I don't have the time to do it right | ||||
|         let items_to_add = 5 - items.length; | ||||
|         if (items_to_add) { | ||||
|             for (let i = 0; i < items_to_add; i++) { | ||||
|                 items.push({}); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         this.showChildView('items_region', new ItemsView({ | ||||
|             collection: new Backbone.Collection(items) | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     initialize: function (options) { | ||||
|         if (typeof options.model === 'undefined' || !options.model) { | ||||
|             this.model = new AccessListModel.Model(); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										10
									
								
								frontend/js/app/nginx/access/form/item.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								frontend/js/app/nginx/access/form/item.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| <div class="col-sm-6 col-md-6"> | ||||
|     <div class="form-group"> | ||||
|         <input type="text" name="username[]" class="form-control" value="<%- typeof username !== 'undefined' ? username : '' %>"> | ||||
|     </div> | ||||
| </div> | ||||
| <div class="col-sm-6 col-md-6"> | ||||
|     <div class="form-group"> | ||||
|         <input type="password" name="password[]" class="form-control" placeholder="<%- typeof hint !== 'undefined' ? hint : '' %>" value=""> | ||||
|     </div> | ||||
| </div> | ||||
							
								
								
									
										7
									
								
								frontend/js/app/nginx/access/form/item.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								frontend/js/app/nginx/access/form/item.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const template = require('./item.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'row' | ||||
| }); | ||||
							
								
								
									
										31
									
								
								frontend/js/app/nginx/access/list/item.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								frontend/js/app/nginx/access/list/item.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| <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> | ||||
|         <%- name %> | ||||
|     </div> | ||||
|     <div class="small text-muted"> | ||||
|         <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> | ||||
|     </div> | ||||
| </td> | ||||
| <td> | ||||
|     <%- i18n('access-lists', 'item-count', {count: items.length || 0}) %> | ||||
| </td> | ||||
| <td> | ||||
|     <%- i18n('access-lists', 'proxy-host-count', {count: proxy_host_count}) %> | ||||
| </td> | ||||
| <% if (canManage) { %> | ||||
| <td class="text-right"> | ||||
|     <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> | ||||
|             <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> | ||||
| <% } %> | ||||
							
								
								
									
										33
									
								
								frontend/js/app/nginx/access/list/item.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								frontend/js/app/nginx/access/list/item.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| 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.showNginxAccessListForm(this.model); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.delete': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxAccessListDeleteConfirm(this.model); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         canManage: App.Cache.User.canManage('access_lists') | ||||
|     }, | ||||
|  | ||||
|     initialize: function () { | ||||
|         this.listenTo(this.model, 'change', this.render); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										12
									
								
								frontend/js/app/nginx/access/list/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								frontend/js/app/nginx/access/list/main.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| <thead> | ||||
|     <th width="30"> </th> | ||||
|     <th><%- i18n('str', 'name') %></th> | ||||
|     <th><%- i18n('users', 'title') %></th> | ||||
|     <th><%- i18n('proxy-hosts', 'title') %></th> | ||||
|     <% if (canManage) { %> | ||||
|     <th> </th> | ||||
|     <% } %> | ||||
| </thead> | ||||
| <tbody> | ||||
|     <!-- items --> | ||||
| </tbody> | ||||
							
								
								
									
										32
									
								
								frontend/js/app/nginx/access/list/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								frontend/js/app/nginx/access/list/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| 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 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 | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										20
									
								
								frontend/js/app/nginx/access/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								frontend/js/app/nginx/access/main.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| <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"> | ||||
|             <a href="#" class="btn btn-outline-secondary btn-sm ml-2 help"><i class="fe fe-help-circle"></i></a> | ||||
|             <% 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> | ||||
							
								
								
									
										81
									
								
								frontend/js/app/nginx/access/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								frontend/js/app/nginx/access/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | ||||
| const Mn              = require('backbone.marionette'); | ||||
| 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({ | ||||
|     id:       'nginx-access', | ||||
|     template: template, | ||||
|  | ||||
|     ui: { | ||||
|         list_region: '.list-region', | ||||
|         add:         '.add-item', | ||||
|         help:        '.help', | ||||
|         dimmer:      '.dimmer' | ||||
|     }, | ||||
|  | ||||
|     regions: { | ||||
|         list_region: '@ui.list_region' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.add': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxAccessListForm(); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.help': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showHelp(App.i18n('access-lists', 'help-title'), App.i18n('access-lists', 'help-content')); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         showAddButton: App.Cache.User.canManage('access_lists') | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         let view = this; | ||||
|  | ||||
|         App.Api.Nginx.AccessLists.getAll(['owner', 'items']) | ||||
|             .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'); | ||||
|             }); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										18
									
								
								frontend/js/app/nginx/certificates-list-item.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								frontend/js/app/nginx/certificates-list-item.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| <div> | ||||
|     <% if (id === 'new') { %> | ||||
|         <div class="title"> | ||||
|             <i class="fe fe-shield text-success"></i> <%- i18n('all-hosts', 'new-cert') %> | ||||
|         </div> | ||||
|         <span class="description"><%- i18n('all-hosts', 'with-le') %></span> | ||||
|     <% } else if (id > 0) { %> | ||||
|         <div class="title"> | ||||
|             <i class="fe fe-shield text-pink"></i> <%- provider === 'other' ? nice_name : domain_names.join(', ') %> | ||||
|         </div> | ||||
|         <span class="description"><%- i18n('ssl', provider) %> – Expires: <%- formatDbDate(expires_on, 'Do MMMM YYYY, h:mm a') %></span> | ||||
|     <% } else { %> | ||||
|         <div class="title"> | ||||
|             <i class="fe fe-shield-off text-danger"></i> <%- i18n('all-hosts', 'none') %> | ||||
|         </div> | ||||
|         <span class="description"><%- i18n('all-hosts', 'no-ssl') %></span> | ||||
|     <% } %> | ||||
| </div> | ||||
							
								
								
									
										19
									
								
								frontend/js/app/nginx/certificates/delete.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								frontend/js/app/nginx/certificates/delete.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- i18n('certificates', '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('certificates', '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> | ||||
							
								
								
									
										31
									
								
								frontend/js/app/nginx/certificates/delete.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								frontend/js/app/nginx/certificates/delete.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../main'); | ||||
| const template = require('./delete.ejs'); | ||||
|  | ||||
| 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.Certificates.delete(this.model.get('id')) | ||||
|                 .then(() => { | ||||
|                     App.Controller.showNginxCertificates(); | ||||
|                     App.UI.closeModal(); | ||||
|                 }) | ||||
|                 .catch(err => { | ||||
|                     alert(err.message); | ||||
|                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||
|                 }); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										76
									
								
								frontend/js/app/nginx/certificates/form.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								frontend/js/app/nginx/certificates/form.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- i18n('certificates', 'form-title', {provider: provider}) %></h5> | ||||
|         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> | ||||
|     </div> | ||||
|     <div class="modal-body"> | ||||
|         <form> | ||||
|             <div class="row"> | ||||
|                 <% if (provider === 'letsencrypt') { %> | ||||
|                     <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 class="text-blue"><i class="fe fe-alert-triangle"></i> <%- i18n('ssl', 'hosts-warning') %></div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                     <div class="col-sm-12 col-md-12"> | ||||
|                         <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"> | ||||
|                         <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> | ||||
|                 <% } else if (provider === 'other') { %> | ||||
|                     <!-- Other --> | ||||
|                     <div class="col-sm-12 col-md-12"> | ||||
|                         <div class="form-group"> | ||||
|                             <label class="form-label"><%- i18n('str', 'name') %> <span class="form-required">*</span></label> | ||||
|                             <input name="nice_name" type="text" class="form-control" placeholder="" value="<%- nice_name %>" required> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                     <div class="col-sm-12 col-md-12 other-ssl"> | ||||
|                         <div class="form-group"> | ||||
|                             <div class="form-label"><%- i18n('certificates', 'other-certificate-key') %><span class="form-required">*</span></div> | ||||
|                             <div class="custom-file"> | ||||
|                                 <input type="file" class="custom-file-input" name="meta[other_certificate_key]" id="other_certificate_key" required> | ||||
|                                 <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('certificates', 'other-certificate') %><span class="form-required">*</span></div> | ||||
|                             <div class="custom-file"> | ||||
|                                 <input type="file" class="custom-file-input" name="meta[other_certificate]" id="other_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('certificates', 'other-intermediate-certificate') %></div> | ||||
|                             <div class="custom-file"> | ||||
|                                 <input type="file" class="custom-file-input" name="meta[other_intermediate_certificate]" id="other_intermediate_certificate"> | ||||
|                                 <label class="custom-file-label"><%- i18n('str', 'choose-file') %></label> | ||||
|                             </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> | ||||
							
								
								
									
										156
									
								
								frontend/js/app/nginx/certificates/form.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								frontend/js/app/nginx/certificates/form.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,156 @@ | ||||
| const _                = require('underscore'); | ||||
| const Mn               = require('backbone.marionette'); | ||||
| const App              = require('../../main'); | ||||
| const CertificateModel = require('../../../models/certificate'); | ||||
| const template         = require('./form.ejs'); | ||||
|  | ||||
| require('jquery-serializejson'); | ||||
| require('selectize'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:      template, | ||||
|     className:     'modal-dialog', | ||||
|     max_file_size: 102400, | ||||
|  | ||||
|     ui: { | ||||
|         form:                           'form', | ||||
|         domain_names:                   'input[name="domain_names"]', | ||||
|         buttons:                        '.modal-footer button', | ||||
|         cancel:                         'button.cancel', | ||||
|         save:                           'button.save', | ||||
|         other_certificate:              '#other_certificate', | ||||
|         other_certificate_key:          '#other_certificate_key', | ||||
|         other_intermediate_certificate: '#other_intermediate_certificate' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         '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(); | ||||
|             data.provider = this.model.get('provider'); | ||||
|  | ||||
|             // Manipulate | ||||
|             if (typeof data.meta !== 'undefined' && typeof data.meta.letsencrypt_agree !== 'undefined') { | ||||
|                 data.meta.letsencrypt_agree = !!data.meta.letsencrypt_agree; | ||||
|             } | ||||
|  | ||||
|             if (typeof data.domain_names === 'string' && data.domain_names) { | ||||
|                 data.domain_names = data.domain_names.split(','); | ||||
|             } | ||||
|  | ||||
|             let ssl_files = []; | ||||
|  | ||||
|             // check files are attached | ||||
|             if (this.model.get('provider') === 'other' && !this.model.hasSslFiles()) { | ||||
|                 if (!this.ui.other_certificate[0].files.length || !this.ui.other_certificate[0].files[0].size) { | ||||
|                     alert('Certificate file is not attached'); | ||||
|                     return; | ||||
|                 } else { | ||||
|                     if (this.ui.other_certificate[0].files[0].size > this.max_file_size) { | ||||
|                         alert('Certificate file is too large (> 100kb)'); | ||||
|                         return; | ||||
|                     } | ||||
|                     ssl_files.push({name: 'certificate', file: this.ui.other_certificate[0].files[0]}); | ||||
|                 } | ||||
|  | ||||
|                 if (!this.ui.other_certificate_key[0].files.length || !this.ui.other_certificate_key[0].files[0].size) { | ||||
|                     alert('Certificate key file is not attached'); | ||||
|                     return; | ||||
|                 } else { | ||||
|                     if (this.ui.other_certificate_key[0].files[0].size > this.max_file_size) { | ||||
|                         alert('Certificate key file is too large (> 100kb)'); | ||||
|                         return; | ||||
|                     } | ||||
|                     ssl_files.push({name: 'certificate_key', file: this.ui.other_certificate_key[0].files[0]}); | ||||
|                 } | ||||
|  | ||||
|                 if (this.ui.other_intermediate_certificate[0].files.length && this.ui.other_intermediate_certificate[0].files[0].size) { | ||||
|                     if (this.ui.other_intermediate_certificate[0].files[0].size > this.max_file_size) { | ||||
|                         alert('Intermediate Certificate file is too large (> 100kb)'); | ||||
|                         return; | ||||
|                     } | ||||
|                     ssl_files.push({name: 'intermediate_certificate', file: this.ui.other_intermediate_certificate[0].files[0]}); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); | ||||
|  | ||||
|             // compile file data | ||||
|             let form_data = new FormData(); | ||||
|             if (view.model.get('provider') && ssl_files.length) { | ||||
|                 ssl_files.map(function (file) { | ||||
|                     form_data.append(file.name, file.file); | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|             new Promise(resolve => { | ||||
|                 if (view.model.get('provider') === 'other') { | ||||
|                     resolve(App.Api.Nginx.Certificates.validate(form_data)); | ||||
|                 } else { | ||||
|                     resolve(); | ||||
|                 } | ||||
|             }) | ||||
|                 .then(() => { | ||||
|                     return App.Api.Nginx.Certificates.create(data); | ||||
|                 }) | ||||
|                 .then(result => { | ||||
|                     view.model.set(result); | ||||
|  | ||||
|                     // Now upload the certs if we need to | ||||
|                     if (view.model.get('provider') === 'other') { | ||||
|                         return App.Api.Nginx.Certificates.upload(view.model.get('id'), form_data) | ||||
|                             .then(result => { | ||||
|                                 view.model.set('meta', _.assign({}, view.model.get('meta'), result)); | ||||
|                             }); | ||||
|                     } | ||||
|                 }) | ||||
|                 .then(() => { | ||||
|                     App.UI.closeModal(function () { | ||||
|                         App.Controller.showNginxCertificates(); | ||||
|                     }); | ||||
|                 }) | ||||
|                 .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.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 CertificateModel.Model({provider: 'letsencrypt'}); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										49
									
								
								frontend/js/app/nginx/certificates/list/item.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								frontend/js/app/nginx/certificates/list/item.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| <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 class="wrap"> | ||||
|         <% | ||||
|         if (provider === 'letsencrypt') { | ||||
|             domain_names.map(function(host) { | ||||
|                 if (host.indexOf('*') === -1) { | ||||
|                     %> | ||||
|                     <span class="tag host-link hover-pink" rel="https://<%- host %>"><%- host %></span> | ||||
|                     <% | ||||
|                 } else { | ||||
|                     %> | ||||
|                     <span class="tag"><%- host %></span> | ||||
|                     <% | ||||
|                 } | ||||
|             }); | ||||
|         } else { | ||||
|             %><%- nice_name %><% | ||||
|         } | ||||
|         %> | ||||
|     </div> | ||||
|     <div class="small text-muted"> | ||||
|         <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> | ||||
|     </div> | ||||
| </td> | ||||
| <td> | ||||
|     <%- i18n('ssl', provider) %> | ||||
| </td> | ||||
| <td class="<%- isExpired() ? 'text-danger' : '' %>"> | ||||
|     <%- formatDbDate(expires_on, 'Do MMMM YYYY, h:mm a') %> | ||||
| </td> | ||||
| <% if (canManage) { %> | ||||
| <td class="text-right"> | ||||
|     <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"> | ||||
|             <% if (provider === 'letsencrypt') { %> | ||||
|                 <a href="#" class="renew dropdown-item"><i class="dropdown-icon fe fe-refresh-cw"></i> <%- i18n('certificates', 'force-renew') %></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> | ||||
| <% } %> | ||||
							
								
								
									
										44
									
								
								frontend/js/app/nginx/certificates/list/item.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								frontend/js/app/nginx/certificates/list/item.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const moment   = require('moment'); | ||||
| const App      = require('../../../main'); | ||||
| const template = require('./item.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template: template, | ||||
|     tagName:  'tr', | ||||
|  | ||||
|     ui: { | ||||
|         host_link: '.host-link', | ||||
|         renew:     'a.renew', | ||||
|         delete:    'a.delete' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.renew': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxCertificateRenew(this.model); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.delete': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxCertificateDeleteConfirm(this.model); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.host_link': function (e) { | ||||
|             e.preventDefault(); | ||||
|             let win = window.open($(e.currentTarget).attr('rel'), '_blank'); | ||||
|             win.focus(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         canManage: App.Cache.User.canManage('certificates'), | ||||
|         isExpired: function () { | ||||
|             return moment(this.expires_on).isBefore(moment()); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     initialize: function () { | ||||
|         this.listenTo(this.model, 'change', this.render); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										12
									
								
								frontend/js/app/nginx/certificates/list/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								frontend/js/app/nginx/certificates/list/main.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| <thead> | ||||
|     <th width="30"> </th> | ||||
|     <th><%- i18n('str', 'name') %></th> | ||||
|     <th><%- i18n('all-hosts', 'cert-provider') %></th> | ||||
|     <th><%- i18n('str', 'expires') %></th> | ||||
|     <% if (canManage) { %> | ||||
|     <th> </th> | ||||
|     <% } %> | ||||
| </thead> | ||||
| <tbody> | ||||
|     <!-- items --> | ||||
| </tbody> | ||||
							
								
								
									
										32
									
								
								frontend/js/app/nginx/certificates/list/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								frontend/js/app/nginx/certificates/list/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| 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 card-table', | ||||
|     template:  template, | ||||
|  | ||||
|     regions: { | ||||
|         body: { | ||||
|             el:             'tbody', | ||||
|             replaceElement: true | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         canManage: App.Cache.User.canManage('certificates') | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         this.showChildView('body', new TableBody({ | ||||
|             collection: this.collection | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										28
									
								
								frontend/js/app/nginx/certificates/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								frontend/js/app/nginx/certificates/main.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| <div class="card"> | ||||
|     <div class="card-status bg-pink"></div> | ||||
|     <div class="card-header"> | ||||
|         <h3 class="card-title"><%- i18n('certificates', 'title') %></h3> | ||||
|         <div class="card-options"> | ||||
|             <a href="#" class="btn btn-outline-secondary btn-sm ml-2 help"><i class="fe fe-help-circle"></i></a> | ||||
|             <% if (showAddButton) { %> | ||||
|             <div class="dropdown"> | ||||
|                 <button type="button" class="btn btn-outline-pink btn-sm ml-2 dropdown-toggle" data-toggle="dropdown"> | ||||
|                     <%- i18n('certificates', 'add') %> | ||||
|                 </button> | ||||
|                 <div class="dropdown-menu"> | ||||
|                     <a class="dropdown-item add-item" data-cert="letsencrypt" href="#"><%- i18n('ssl', 'letsencrypt') %></a> | ||||
|                     <a class="dropdown-item add-item" data-cert="other" href="#"><%- i18n('ssl', 'other') %></a> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <% } %> | ||||
|         </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> | ||||
							
								
								
									
										82
									
								
								frontend/js/app/nginx/certificates/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								frontend/js/app/nginx/certificates/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| const Mn               = require('backbone.marionette'); | ||||
| const App              = require('../../main'); | ||||
| const CertificateModel = require('../../../models/certificate'); | ||||
| 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({ | ||||
|     id:       'nginx-certificates', | ||||
|     template: template, | ||||
|  | ||||
|     ui: { | ||||
|         list_region: '.list-region', | ||||
|         add:         '.add-item', | ||||
|         help:        '.help', | ||||
|         dimmer:      '.dimmer' | ||||
|     }, | ||||
|  | ||||
|     regions: { | ||||
|         list_region: '@ui.list_region' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.add': function (e) { | ||||
|             e.preventDefault(); | ||||
|             let model = new CertificateModel.Model({provider: $(e.currentTarget).data('cert')}); | ||||
|             App.Controller.showNginxCertificateForm(model); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.help': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showHelp(App.i18n('certificates', 'help-title'), App.i18n('certificates', 'help-content')); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         showAddButton: App.Cache.User.canManage('certificates') | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         let view = this; | ||||
|  | ||||
|         App.Api.Nginx.Certificates.getAll(['owner']) | ||||
|             .then(response => { | ||||
|                 if (!view.isDestroyed()) { | ||||
|                     if (response && response.length) { | ||||
|                         view.showChildView('list_region', new ListView({ | ||||
|                             collection: new CertificateModel.Collection(response) | ||||
|                         })); | ||||
|                     } else { | ||||
|                         let manage = App.Cache.User.canManage('certificates'); | ||||
|  | ||||
|                         view.showChildView('list_region', new EmptyView({ | ||||
|                             title:      App.i18n('certificates', 'empty'), | ||||
|                             subtitle:   App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), | ||||
|                             link:       manage ? App.i18n('certificates', 'add') : null, | ||||
|                             btn_color:  'pink', | ||||
|                             permission: 'certificates', | ||||
|                             action:     function () { | ||||
|                                 App.Controller.showNginxCertificateForm(); | ||||
|                             } | ||||
|                         })); | ||||
|                     } | ||||
|                 } | ||||
|             }) | ||||
|             .catch(err => { | ||||
|                 view.showChildView('list_region', new ErrorView({ | ||||
|                     code:    err.code, | ||||
|                     message: err.message, | ||||
|                     retry:   function () { | ||||
|                         App.Controller.showNginxCertificates(); | ||||
|                     } | ||||
|                 })); | ||||
|  | ||||
|                 console.error(err); | ||||
|             }) | ||||
|             .then(() => { | ||||
|                 view.ui.dimmer.removeClass('active'); | ||||
|             }); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										14
									
								
								frontend/js/app/nginx/certificates/renew.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								frontend/js/app/nginx/certificates/renew.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- i18n('certificates', 'renew-title') %></h5> | ||||
|     </div> | ||||
|     <div class="modal-body"> | ||||
|         <div class="waiting text-center"> | ||||
|             <%= i18n('str', 'please-wait') %> | ||||
|         </div> | ||||
|         <div class="alert alert-danger error" role="alert"></div> | ||||
|     </div> | ||||
|     <div class="modal-footer"> | ||||
|         <button type="button" class="btn btn-secondary cancel" data-dismiss="modal" disabled><%- i18n('str', 'close') %></button> | ||||
|     </div> | ||||
| </div> | ||||
							
								
								
									
										31
									
								
								frontend/js/app/nginx/certificates/renew.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								frontend/js/app/nginx/certificates/renew.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../main'); | ||||
| const template = require('./renew.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'modal-dialog', | ||||
|  | ||||
|     ui: { | ||||
|         waiting: '.waiting', | ||||
|         error:   '.error', | ||||
|         close:   'button.cancel' | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         this.ui.error.hide(); | ||||
|  | ||||
|         App.Api.Nginx.Certificates.renew(this.model.get('id')) | ||||
|             .then((result) => { | ||||
|                 this.model.set(result); | ||||
|                 setTimeout(() => { | ||||
|                     App.UI.closeModal(); | ||||
|                 }, 1000); | ||||
|             }) | ||||
|             .catch((err) => { | ||||
|                 this.ui.waiting.hide(); | ||||
|                 this.ui.error.text(err.message).show(); | ||||
|                 this.ui.close.prop('disabled', false); | ||||
|             }); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										23
									
								
								frontend/js/app/nginx/dead/delete.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								frontend/js/app/nginx/dead/delete.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- i18n('dead-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('dead-hosts', 'delete-confirm', {domains: domain_names.join(', ')}) %> | ||||
|                     <% if (certificate_id) { %> | ||||
|                         <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> | ||||
							
								
								
									
										32
									
								
								frontend/js/app/nginx/dead/delete.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								frontend/js/app/nginx/dead/delete.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../main'); | ||||
| const template = require('./delete.ejs'); | ||||
|  | ||||
| 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.DeadHosts.delete(this.model.get('id')) | ||||
|                 .then(() => { | ||||
|                     App.Controller.showNginxDead(); | ||||
|                     App.UI.closeModal(); | ||||
|                 }) | ||||
|                 .catch(err => { | ||||
|                     alert(err.message); | ||||
|                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||
|                 }); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										113
									
								
								frontend/js/app/nginx/dead/form.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								frontend/js/app/nginx/dead/form.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- i18n('dead-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> | ||||
|                 <li role="presentation" class="nav-item"><a href="#advanced" aria-controls="tab3" role="tab" data-toggle="tab" class="nav-link"><i class="fe fe-settings"></i> <%- i18n('all-hosts', 'advanced') %></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> | ||||
|                 </div> | ||||
|  | ||||
|                 <!-- SSL --> | ||||
|                 <div role="tabpanel" class="tab-pane" id="ssl-options"> | ||||
|                     <div class="row"> | ||||
|                         <div class="col-sm-12 col-md-12"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('all-hosts', 'ssl-certificate') %></label> | ||||
|                                 <select name="certificate_id" class="form-control custom-select" placeholder="<%- i18n('all-hosts', 'none') %>"> | ||||
|                                     <option selected value="0" data-data="{"id":0}" <%- certificate_id ? '' : 'selected' %>><%- i18n('all-hosts', 'none') %></option> | ||||
|                                     <option selected value="new" data-data="{"id":"new"}"><%- i18n('all-hosts', 'new-cert') %></option> | ||||
|                                 </select> | ||||
|                             </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' : '' %><%- certificate_id ? '' : ' 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-6 col-md-6"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="http2_support" value="1"<%- http2_support ? ' checked' : '' %><%- certificate_id ? '' : ' disabled' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'http2-support') %></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="hsts_enabled" value="1"<%- hsts_enabled ? ' checked' : '' %><%- certificate_id && ssl_forced ? '' : ' disabled' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'hsts-enabled') %> <a href="https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security" target="_blank"><i class="fe fe-help-circle"></i></a></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="hsts_subdomains" value="1"<%- hsts_subdomains ? ' checked' : '' %><%- certificate_id && ssl_forced && hsts_enabled ? '' : ' disabled' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'hsts-subdomains') %></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|  | ||||
|                         <!-- Lets encrypt --> | ||||
|                         <div class="col-sm-12 col-md-12 letsencrypt"> | ||||
|                             <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 disabled> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-12 col-md-12 letsencrypt"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="meta[letsencrypt_agree]" value="1" required disabled> | ||||
|                                     <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> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <!-- Advanced --> | ||||
|                 <div role="tabpanel" class="tab-pane" id="advanced"> | ||||
|                     <div class="row"> | ||||
|                         <div class="col-md-12"> | ||||
|                             <div class="form-group mb-0"> | ||||
|                                 <label class="form-label"><%- i18n('all-hosts', 'advanced-config') %></label> | ||||
|                                 <textarea name="advanced_config" rows="8" class="form-control text-monospace" placeholder="# <%- i18n('all-hosts', 'advanced-warning') %>"><%- advanced_config %></textarea> | ||||
|                             </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> | ||||
							
								
								
									
										207
									
								
								frontend/js/app/nginx/dead/form.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								frontend/js/app/nginx/dead/form.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,207 @@ | ||||
| const Mn                   = require('backbone.marionette'); | ||||
| const App                  = require('../../main'); | ||||
| const DeadHostModel        = require('../../../models/dead-host'); | ||||
| const template             = require('./form.ejs'); | ||||
| const certListItemTemplate = require('../certificates-list-item.ejs'); | ||||
| const Helpers              = require('../../../lib/helpers'); | ||||
|  | ||||
| require('jquery-serializejson'); | ||||
| require('selectize'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'modal-dialog', | ||||
|  | ||||
|     ui: { | ||||
|         form:               'form', | ||||
|         domain_names:       'input[name="domain_names"]', | ||||
|         buttons:            '.modal-footer button', | ||||
|         cancel:             'button.cancel', | ||||
|         save:               'button.save', | ||||
|         certificate_select: 'select[name="certificate_id"]', | ||||
|         ssl_forced:         'input[name="ssl_forced"]', | ||||
|         hsts_enabled:       'input[name="hsts_enabled"]', | ||||
|         hsts_subdomains:    'input[name="hsts_subdomains"]', | ||||
|         http2_support:      'input[name="http2_support"]', | ||||
|         letsencrypt:        '.letsencrypt' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'change @ui.certificate_select': function () { | ||||
|             let id = this.ui.certificate_select.val(); | ||||
|             if (id === 'new') { | ||||
|                 this.ui.letsencrypt.show().find('input').prop('disabled', false); | ||||
|             } else { | ||||
|                 this.ui.letsencrypt.hide().find('input').prop('disabled', true); | ||||
|             } | ||||
|  | ||||
|             let enabled = id === 'new' || parseInt(id, 10) > 0; | ||||
|  | ||||
|             let inputs = this.ui.ssl_forced.add(this.ui.http2_support); | ||||
|             inputs | ||||
|                 .prop('disabled', !enabled) | ||||
|                 .parents('.form-group') | ||||
|                 .css('opacity', enabled ? 1 : 0.5); | ||||
|  | ||||
|             if (!enabled) { | ||||
|                 inputs.prop('checked', false); | ||||
|             } | ||||
|  | ||||
|             inputs.trigger('change'); | ||||
|         }, | ||||
|  | ||||
|         'change @ui.ssl_forced': function () { | ||||
|             let checked = this.ui.ssl_forced.prop('checked'); | ||||
|             this.ui.hsts_enabled | ||||
|                 .prop('disabled', !checked) | ||||
|                 .parents('.form-group') | ||||
|                 .css('opacity', checked ? 1 : 0.5); | ||||
|  | ||||
|             if (!checked) { | ||||
|                 this.ui.hsts_enabled.prop('checked', false); | ||||
|             } | ||||
|  | ||||
|             this.ui.hsts_enabled.trigger('change'); | ||||
|         }, | ||||
|  | ||||
|         'change @ui.hsts_enabled': function () { | ||||
|             let checked = this.ui.hsts_enabled.prop('checked'); | ||||
|             this.ui.hsts_subdomains | ||||
|                 .prop('disabled', !checked) | ||||
|                 .parents('.form-group') | ||||
|                 .css('opacity', checked ? 1 : 0.5); | ||||
|  | ||||
|             if (!checked) { | ||||
|                 this.ui.hsts_subdomains.prop('checked', false); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         '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.hsts_enabled    = !!data.hsts_enabled; | ||||
|             data.hsts_subdomains = !!data.hsts_subdomains; | ||||
|             data.http2_support   = !!data.http2_support; | ||||
|             data.ssl_forced      = !!data.ssl_forced; | ||||
|  | ||||
|             if (typeof data.domain_names === 'string' && data.domain_names) { | ||||
|                 data.domain_names = data.domain_names.split(','); | ||||
|             } | ||||
|  | ||||
|             // Check for any domain names containing wildcards, which are not allowed with letsencrypt | ||||
|             if (data.certificate_id === 'new') { | ||||
|                 let domain_err = false; | ||||
|                 data.domain_names.map(function (name) { | ||||
|                     if (name.match(/\*/im)) { | ||||
|                         domain_err = true; | ||||
|                     } | ||||
|                 }); | ||||
|  | ||||
|                 if (domain_err) { | ||||
|                     alert('Cannot request Let\'s Encrypt Certificate for wildcard domains'); | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1'; | ||||
|             } else { | ||||
|                 data.certificate_id = parseInt(data.certificate_id, 10); | ||||
|             } | ||||
|  | ||||
|             let method = App.Api.Nginx.DeadHosts.create; | ||||
|             let is_new = true; | ||||
|  | ||||
|             if (this.model.get('id')) { | ||||
|                 // edit | ||||
|                 is_new  = false; | ||||
|                 method  = App.Api.Nginx.DeadHosts.update; | ||||
|                 data.id = this.model.get('id'); | ||||
|             } | ||||
|  | ||||
|             this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); | ||||
|             method(data) | ||||
|                 .then(result => { | ||||
|                     view.model.set(result); | ||||
|  | ||||
|                     App.UI.closeModal(function () { | ||||
|                         if (is_new) { | ||||
|                             App.Controller.showNginxDead(); | ||||
|                         } | ||||
|                     }); | ||||
|                 }) | ||||
|                 .catch(err => { | ||||
|                     alert(err.message); | ||||
|                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||
|                 }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         getLetsencryptEmail: function () { | ||||
|             return App.Cache.User.get('email'); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         let view = this; | ||||
|  | ||||
|         // Domain names | ||||
|         this.ui.domain_names.selectize({ | ||||
|             delimiter:    ',', | ||||
|             persist:      false, | ||||
|             maxOptions:   15, | ||||
|             create:       function (input) { | ||||
|                 return { | ||||
|                     value: input, | ||||
|                     text:  input | ||||
|                 }; | ||||
|             }, | ||||
|             createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/ | ||||
|         }); | ||||
|  | ||||
|         // Certificates | ||||
|         this.ui.letsencrypt.hide(); | ||||
|         this.ui.certificate_select.selectize({ | ||||
|             valueField:       'id', | ||||
|             labelField:       'nice_name', | ||||
|             searchField:      ['nice_name', 'domain_names'], | ||||
|             create:           false, | ||||
|             preload:          true, | ||||
|             allowEmptyOption: true, | ||||
|             render:           { | ||||
|                 option: function (item) { | ||||
|                     item.i18n         = App.i18n; | ||||
|                     item.formatDbDate = Helpers.formatDbDate; | ||||
|                     return certListItemTemplate(item); | ||||
|                 } | ||||
|             }, | ||||
|             load:             function (query, callback) { | ||||
|                 App.Api.Nginx.Certificates.getAll() | ||||
|                     .then(rows => { | ||||
|                         callback(rows); | ||||
|                     }) | ||||
|                     .catch(err => { | ||||
|                         console.error(err); | ||||
|                         callback(); | ||||
|                     }); | ||||
|             }, | ||||
|             onLoad:           function () { | ||||
|                 view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id')); | ||||
|             } | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     initialize: function (options) { | ||||
|         if (typeof options.model === 'undefined' || !options.model) { | ||||
|             this.model = new DeadHostModel.Model(); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										53
									
								
								frontend/js/app/nginx/dead/list/item.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								frontend/js/app/nginx/dead/list/item.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| <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) { | ||||
|             if (host.indexOf('*') === -1) { | ||||
|                 %> | ||||
|                 <span class="tag host-link hover-red" rel="http<%- certificate_id ? 's' : '' %>://<%- host %>"><%- host %></span> | ||||
|                 <% | ||||
|             } else { | ||||
|                 %> | ||||
|                 <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><%- certificate ? i18n('ssl', certificate.provider) : i18n('ssl', 'none') %></div> | ||||
| </td> | ||||
| <td> | ||||
|     <% | ||||
|     var o = isOnline(); | ||||
|     if (!enabled) { %> | ||||
|         <span class="status-icon bg-warning"></span> <%- i18n('str', 'disabled') %> | ||||
|     <% } else if (o === true) { %> | ||||
|         <span class="status-icon bg-success"></span> <%- i18n('str', 'online') %> | ||||
|     <% } else if (o === false) { %> | ||||
|         <span title="<%- getOfflineError() %>"><span class="status-icon bg-danger"></span> <%- i18n('str', 'offline') %></span> | ||||
|     <% } else { %> | ||||
|         <span class="status-icon bg-warning"></span> <%- i18n('str', 'unknown') %> | ||||
|     <% } %> | ||||
| </td> | ||||
| <% if (canManage) { %> | ||||
| <td class="text-right"> | ||||
|     <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="able dropdown-item"><i class="dropdown-icon fe fe-power"></i> <%- i18n('str', enabled ? 'disable' : 'enable') %></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> | ||||
| <% } %> | ||||
							
								
								
									
										61
									
								
								frontend/js/app/nginx/dead/list/item.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								frontend/js/app/nginx/dead/list/item.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../../main'); | ||||
| const template = require('./item.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template: template, | ||||
|     tagName:  'tr', | ||||
|  | ||||
|     ui: { | ||||
|         able:      'a.able', | ||||
|         edit:      'a.edit', | ||||
|         delete:    'a.delete', | ||||
|         host_link: '.host-link' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.able': function (e) { | ||||
|             e.preventDefault(); | ||||
|             let id = this.model.get('id'); | ||||
|             App.Api.Nginx.DeadHosts[this.model.get('enabled') ? 'disable' : 'enable'](id) | ||||
|                 .then(() => { | ||||
|                     return App.Api.Nginx.DeadHosts.get(id) | ||||
|                         .then(row => { | ||||
|                             this.model.set(row); | ||||
|                         }); | ||||
|                 }); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.edit': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxDeadForm(this.model); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.delete': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxDeadDeleteConfirm(this.model); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.host_link': function (e) { | ||||
|             e.preventDefault(); | ||||
|             let win = window.open($(e.currentTarget).attr('rel'), '_blank'); | ||||
|             win.focus(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         canManage: App.Cache.User.canManage('dead_hosts'), | ||||
|  | ||||
|         isOnline: function () { | ||||
|             return typeof this.meta.nginx_online === 'undefined' ? null : this.meta.nginx_online; | ||||
|         }, | ||||
|  | ||||
|         getOfflineError: function () { | ||||
|             return this.meta.nginx_err || ''; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     initialize: function () { | ||||
|         this.listenTo(this.model, 'change', this.render); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										12
									
								
								frontend/js/app/nginx/dead/list/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								frontend/js/app/nginx/dead/list/main.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| <thead> | ||||
|     <th width="30"> </th> | ||||
|     <th><%- i18n('str', 'source') %></th> | ||||
|     <th><%- i18n('str', 'ssl') %></th> | ||||
|     <th><%- i18n('str', 'status') %></th> | ||||
|     <% if (canManage) { %> | ||||
|     <th> </th> | ||||
|     <% } %> | ||||
| </thead> | ||||
| <tbody> | ||||
|     <!-- items --> | ||||
| </tbody> | ||||
							
								
								
									
										32
									
								
								frontend/js/app/nginx/dead/list/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								frontend/js/app/nginx/dead/list/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| 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 card-table', | ||||
|     template:  template, | ||||
|  | ||||
|     regions: { | ||||
|         body: { | ||||
|             el:             'tbody', | ||||
|             replaceElement: true | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         canManage: App.Cache.User.canManage('dead_hosts') | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         this.showChildView('body', new TableBody({ | ||||
|             collection: this.collection | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										20
									
								
								frontend/js/app/nginx/dead/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								frontend/js/app/nginx/dead/main.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| <div class="card"> | ||||
|     <div class="card-status bg-danger"></div> | ||||
|     <div class="card-header"> | ||||
|         <h3 class="card-title"><%- i18n('dead-hosts', 'title') %></h3> | ||||
|         <div class="card-options"> | ||||
|             <a href="#" class="btn btn-outline-secondary btn-sm ml-2 help"><i class="fe fe-help-circle"></i></a> | ||||
|             <% if (showAddButton) { %> | ||||
|             <a href="#" class="btn btn-outline-danger btn-sm ml-2 add-item"><%- i18n('dead-hosts', '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> | ||||
							
								
								
									
										81
									
								
								frontend/js/app/nginx/dead/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								frontend/js/app/nginx/dead/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | ||||
| const Mn            = require('backbone.marionette'); | ||||
| const App           = require('../../main'); | ||||
| const DeadHostModel = require('../../../models/dead-host'); | ||||
| 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({ | ||||
|     id:       'nginx-dead', | ||||
|     template: template, | ||||
|  | ||||
|     ui: { | ||||
|         list_region: '.list-region', | ||||
|         add:         '.add-item', | ||||
|         help:        '.help', | ||||
|         dimmer:      '.dimmer' | ||||
|     }, | ||||
|  | ||||
|     regions: { | ||||
|         list_region: '@ui.list_region' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.add': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxDeadForm(); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.help': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showHelp(App.i18n('dead-hosts', 'help-title'), App.i18n('dead-hosts', 'help-content')); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         showAddButton: App.Cache.User.canManage('dead_hosts') | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         let view = this; | ||||
|  | ||||
|         App.Api.Nginx.DeadHosts.getAll(['owner', 'certificate']) | ||||
|             .then(response => { | ||||
|                 if (!view.isDestroyed()) { | ||||
|                     if (response && response.length) { | ||||
|                         view.showChildView('list_region', new ListView({ | ||||
|                             collection: new DeadHostModel.Collection(response) | ||||
|                         })); | ||||
|                     } else { | ||||
|                         let manage = App.Cache.User.canManage('dead_hosts'); | ||||
|  | ||||
|                         view.showChildView('list_region', new EmptyView({ | ||||
|                             title:      App.i18n('dead-hosts', 'empty'), | ||||
|                             subtitle:   App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), | ||||
|                             link:       manage ? App.i18n('dead-hosts', 'add') : null, | ||||
|                             btn_color:  'danger', | ||||
|                             permission: 'dead_hosts', | ||||
|                             action:     function () { | ||||
|                                 App.Controller.showNginxDeadForm(); | ||||
|                             } | ||||
|                         })); | ||||
|                     } | ||||
|                 } | ||||
|             }) | ||||
|             .catch(err => { | ||||
|                 view.showChildView('list_region', new ErrorView({ | ||||
|                     code:    err.code, | ||||
|                     message: err.message, | ||||
|                     retry:   function () { | ||||
|                         App.Controller.showNginxDead(); | ||||
|                     } | ||||
|                 })); | ||||
|  | ||||
|                 console.error(err); | ||||
|             }) | ||||
|             .then(() => { | ||||
|                 view.ui.dimmer.removeClass('active'); | ||||
|             }); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										13
									
								
								frontend/js/app/nginx/proxy/access-list-item.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								frontend/js/app/nginx/proxy/access-list-item.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| <div> | ||||
|     <% if (id > 0) { %> | ||||
|         <div class="title"> | ||||
|             <i class="fe fe-lock text-teal"></i> <%- name %> | ||||
|         </div> | ||||
|         <span class="description"><%- i18n('access-lists', 'item-count', {count: items.length || 0}) %> – Created: <%- formatDbDate(created_on, 'Do MMMM YYYY, h:mm a') %></span> | ||||
|     <% } else { %> | ||||
|         <div class="title"> | ||||
|             <i class="fe fe-unlock text-yellow"></i> <%- i18n('access-lists', 'public') %> | ||||
|         </div> | ||||
|         <span class="description"><%- i18n('access-lists', 'public-sub') %></span> | ||||
|     <% } %> | ||||
| </div> | ||||
							
								
								
									
										23
									
								
								frontend/js/app/nginx/proxy/delete.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								frontend/js/app/nginx/proxy/delete.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- i18n('proxy-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('proxy-hosts', 'delete-confirm', {domains: domain_names.join(', ')}) %> | ||||
|                     <% if (certificate_id) { %> | ||||
|                         <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> | ||||
							
								
								
									
										32
									
								
								frontend/js/app/nginx/proxy/delete.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								frontend/js/app/nginx/proxy/delete.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../main'); | ||||
| const template = require('./delete.ejs'); | ||||
|  | ||||
| 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.showNginxProxy(); | ||||
|                     App.UI.closeModal(); | ||||
|                 }) | ||||
|                 .catch(err => { | ||||
|                     alert(err.message); | ||||
|                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||
|                 }); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										187
									
								
								frontend/js/app/nginx/proxy/form.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								frontend/js/app/nginx/proxy/form.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,187 @@ | ||||
| <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="#locations" aria-controls="tab4" role="tab" data-toggle="tab" class="nav-link"><i class="fe fe-layers"></i> <%- i18n('all-hosts', 'locations') %></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> | ||||
|                 <li role="presentation" class="nav-item"><a href="#advanced" aria-controls="tab3" role="tab" data-toggle="tab" class="nav-link"><i class="fe fe-settings"></i> <%- i18n('all-hosts', 'advanced') %></a></li> | ||||
|             </ul> | ||||
|             <div class="tab-content"> | ||||
|  | ||||
|                 <!-- Locations --> | ||||
|                 <div class="tab-pane" id="locations"> | ||||
|                     <div class="row"> | ||||
|                         <div class="col-sm-12"> | ||||
|                             <button type="button" class="btn btn-secondary add_location"><%- i18n('locations', 'new_location') %></button> | ||||
|                             <div class="locations_container mt-3"></div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <!-- 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-3 col-md-3"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('proxy-hosts', 'forward-scheme') %><span class="form-required">*</span></label> | ||||
|                                 <select name="forward_scheme" class="form-control custom-select" placeholder="http"> | ||||
|                                     <option value="http" <%- forward_scheme === 'http' ? 'selected' : '' %>>http</option> | ||||
|                                     <option value="https" <%- forward_scheme === 'https' ? 'selected' : '' %>>https</option> | ||||
|                                 </select> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-5 col-md-5"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('proxy-hosts', 'forward-host') %><span class="form-required">*</span></label> | ||||
|                                 <input type="text" name="forward_host" class="form-control text-monospace" placeholder="" value="<%- forward_host %>" autocomplete="off" maxlength="50" 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 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="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="allow_websocket_upgrade" value="1"<%- allow_websocket_upgrade ? ' checked' : '' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('proxy-hosts', 'allow-websocket-upgrade') %></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|  | ||||
|                         <div class="col-sm-12 col-md-12"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('proxy-hosts', 'access-list') %></label> | ||||
|                                 <select name="access_list_id" class="form-control custom-select" placeholder="<%- i18n('access-lists', 'public') %>"> | ||||
|                                     <option selected value="0" data-data="{"id":0}" <%- access_list_id ? '' : 'selected' %>><%- i18n('access-lists', 'public') %></option> | ||||
|                                 </select> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <!-- SSL --> | ||||
|                 <div role="tabpanel" class="tab-pane" id="ssl-options"> | ||||
|                     <div class="row"> | ||||
|                         <div class="col-sm-12 col-md-12"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('all-hosts', 'ssl-certificate') %></label> | ||||
|                                 <select name="certificate_id" class="form-control custom-select" placeholder="<%- i18n('all-hosts', 'none') %>"> | ||||
|                                     <option selected value="0" data-data="{"id":0}" <%- certificate_id ? '' : 'selected' %>><%- i18n('all-hosts', 'none') %></option> | ||||
|                                     <option selected value="new" data-data="{"id":"new"}"><%- i18n('all-hosts', 'new-cert') %></option> | ||||
|                                 </select> | ||||
|                             </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' : '' %><%- certificate_id ? '' : ' 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-6 col-md-6"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="http2_support" value="1"<%- http2_support ? ' checked' : '' %><%- certificate_id ? '' : ' disabled' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'http2-support') %></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="hsts_enabled" value="1"<%- hsts_enabled ? ' checked' : '' %><%- certificate_id && ssl_forced ? '' : ' disabled' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'hsts-enabled') %> <a href="https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security" target="_blank"><i class="fe fe-help-circle"></i></a></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="hsts_subdomains" value="1"<%- hsts_subdomains ? ' checked' : '' %><%- certificate_id && ssl_forced && hsts_enabled ? '' : ' disabled' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'hsts-subdomains') %></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|  | ||||
|                         <!-- Lets encrypt --> | ||||
|                         <div class="col-sm-12 col-md-12 letsencrypt"> | ||||
|                             <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 disabled> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-12 col-md-12 letsencrypt"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="meta[letsencrypt_agree]" value="1" required disabled> | ||||
|                                     <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> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <!-- Advanced --> | ||||
|                 <div role="tabpanel" class="tab-pane" id="advanced"> | ||||
|                     <div class="row"> | ||||
|                         <div class="col-md-12"> | ||||
|                             <p>Nginx variables available to you are:</p> | ||||
|                             <ul class="text-monospace"> | ||||
|                                 <li>$server          # Host/IP</li> | ||||
|                                 <li>$port            # Port Number</li> | ||||
|                                 <li>$forward_scheme  # http or https</li> | ||||
|                             </ul> | ||||
|                             <div class="form-group mb-0"> | ||||
|                                 <label class="form-label"><%- i18n('all-hosts', 'advanced-config') %></label> | ||||
|                                 <textarea name="advanced_config" rows="8" class="form-control text-monospace" placeholder="# <%- i18n('all-hosts', 'advanced-warning') %>"><%- advanced_config %></textarea> | ||||
|                             </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> | ||||
							
								
								
									
										291
									
								
								frontend/js/app/nginx/proxy/form.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										291
									
								
								frontend/js/app/nginx/proxy/form.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,291 @@ | ||||
| const Mn                     = require('backbone.marionette'); | ||||
| const App                    = require('../../main'); | ||||
| const ProxyHostModel         = require('../../../models/proxy-host'); | ||||
| const ProxyLocationModel     = require('../../../models/proxy-host-location'); | ||||
| const template               = require('./form.ejs'); | ||||
| const certListItemTemplate   = require('../certificates-list-item.ejs'); | ||||
| const accessListItemTemplate = require('./access-list-item.ejs'); | ||||
| const CustomLocation         = require('./location'); | ||||
| const Helpers                = require('../../../lib/helpers'); | ||||
|  | ||||
|  | ||||
| require('jquery-serializejson'); | ||||
| require('selectize'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'modal-dialog', | ||||
|  | ||||
|     locationsCollection: new ProxyLocationModel.Collection(), | ||||
|  | ||||
|     ui: { | ||||
|         form:               'form', | ||||
|         domain_names:       'input[name="domain_names"]', | ||||
|         forward_host:       'input[name="forward_host"]', | ||||
|         buttons:            '.modal-footer button', | ||||
|         cancel:             'button.cancel', | ||||
|         save:               'button.save', | ||||
|         add_location_btn:   'button.add_location', | ||||
|         locations_container:'.locations_container', | ||||
|         certificate_select: 'select[name="certificate_id"]', | ||||
|         access_list_select: 'select[name="access_list_id"]', | ||||
|         ssl_forced:         'input[name="ssl_forced"]', | ||||
|         hsts_enabled:       'input[name="hsts_enabled"]', | ||||
|         hsts_subdomains:    'input[name="hsts_subdomains"]', | ||||
|         http2_support:      'input[name="http2_support"]', | ||||
|         forward_scheme:     'select[name="forward_scheme"]', | ||||
|         letsencrypt:        '.letsencrypt' | ||||
|     }, | ||||
|  | ||||
|     regions: { | ||||
|         locations_regions: '@ui.locations_container' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'change @ui.certificate_select': function () { | ||||
|             let id = this.ui.certificate_select.val(); | ||||
|             if (id === 'new') { | ||||
|                 this.ui.letsencrypt.show().find('input').prop('disabled', false); | ||||
|             } else { | ||||
|                 this.ui.letsencrypt.hide().find('input').prop('disabled', true); | ||||
|             } | ||||
|  | ||||
|             let enabled = id === 'new' || parseInt(id, 10) > 0; | ||||
|  | ||||
|             let inputs = this.ui.ssl_forced.add(this.ui.http2_support); | ||||
|             inputs | ||||
|                 .prop('disabled', !enabled) | ||||
|                 .parents('.form-group') | ||||
|                 .css('opacity', enabled ? 1 : 0.5); | ||||
|  | ||||
|             if (!enabled) { | ||||
|                 inputs.prop('checked', false); | ||||
|             } | ||||
|  | ||||
|             inputs.trigger('change'); | ||||
|         }, | ||||
|  | ||||
|         'change @ui.ssl_forced': function () { | ||||
|             let checked = this.ui.ssl_forced.prop('checked'); | ||||
|             this.ui.hsts_enabled | ||||
|                 .prop('disabled', !checked) | ||||
|                 .parents('.form-group') | ||||
|                 .css('opacity', checked ? 1 : 0.5); | ||||
|  | ||||
|             if (!checked) { | ||||
|                 this.ui.hsts_enabled.prop('checked', false); | ||||
|             } | ||||
|  | ||||
|             this.ui.hsts_enabled.trigger('change'); | ||||
|         }, | ||||
|  | ||||
|         'change @ui.hsts_enabled': function () { | ||||
|             let checked = this.ui.hsts_enabled.prop('checked'); | ||||
|             this.ui.hsts_subdomains | ||||
|                 .prop('disabled', !checked) | ||||
|                 .parents('.form-group') | ||||
|                 .css('opacity', checked ? 1 : 0.5); | ||||
|  | ||||
|             if (!checked) { | ||||
|                 this.ui.hsts_subdomains.prop('checked', false); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         'click @ui.add_location_btn': function (e) { | ||||
|             e.preventDefault(); | ||||
|              | ||||
|             const model = new ProxyLocationModel.Model(); | ||||
|             this.locationsCollection.add(model); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.save': function (e) { | ||||
|             e.preventDefault(); | ||||
|  | ||||
|             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(); | ||||
|  | ||||
|             // Add locations | ||||
|             data.locations = []; | ||||
|             this.locationsCollection.models.forEach((location) => { | ||||
|                 data.locations.push(location.toJSON()); | ||||
|             }); | ||||
|  | ||||
|             // Serialize collects path from custom locations | ||||
|             // This field must be removed from root object | ||||
|             delete data.path; | ||||
|  | ||||
|             // Manipulate | ||||
|             data.forward_port            = parseInt(data.forward_port, 10); | ||||
|             data.block_exploits          = !!data.block_exploits; | ||||
|             data.caching_enabled         = !!data.caching_enabled; | ||||
|             data.allow_websocket_upgrade = !!data.allow_websocket_upgrade; | ||||
|             data.http2_support           = !!data.http2_support; | ||||
|             data.hsts_enabled            = !!data.hsts_enabled; | ||||
|             data.hsts_subdomains         = !!data.hsts_subdomains; | ||||
|             data.ssl_forced              = !!data.ssl_forced; | ||||
|  | ||||
|             if (typeof data.domain_names === 'string' && data.domain_names) { | ||||
|                 data.domain_names = data.domain_names.split(','); | ||||
|             } | ||||
|  | ||||
|             // Check for any domain names containing wildcards, which are not allowed with letsencrypt | ||||
|             if (data.certificate_id === 'new') { | ||||
|                 let domain_err = false; | ||||
|                 data.domain_names.map(function (name) { | ||||
|                     if (name.match(/\*/im)) { | ||||
|                         domain_err = true; | ||||
|                     } | ||||
|                 }); | ||||
|  | ||||
|                 if (domain_err) { | ||||
|                     alert('Cannot request Let\'s Encrypt Certificate for wildcard domains'); | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1'; | ||||
|             } else { | ||||
|                 data.certificate_id = parseInt(data.certificate_id, 10); | ||||
|             } | ||||
|  | ||||
|             let method = App.Api.Nginx.ProxyHosts.create; | ||||
|             let is_new = true; | ||||
|  | ||||
|             if (this.model.get('id')) { | ||||
|                 // edit | ||||
|                 is_new  = false; | ||||
|                 method  = App.Api.Nginx.ProxyHosts.update; | ||||
|                 data.id = this.model.get('id'); | ||||
|             } | ||||
|  | ||||
|             this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); | ||||
|             method(data) | ||||
|                 .then(result => { | ||||
|                     view.model.set(result); | ||||
|  | ||||
|                     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 App.Cache.User.get('email'); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         let view = this; | ||||
|  | ||||
|         this.ui.ssl_forced.trigger('change'); | ||||
|         this.ui.hsts_enabled.trigger('change'); | ||||
|  | ||||
|         // Domain names | ||||
|         this.ui.domain_names.selectize({ | ||||
|             delimiter:    ',', | ||||
|             persist:      false, | ||||
|             maxOptions:   15, | ||||
|             create:       function (input) { | ||||
|                 return { | ||||
|                     value: input, | ||||
|                     text:  input | ||||
|                 }; | ||||
|             }, | ||||
|             createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/ | ||||
|         }); | ||||
|  | ||||
|         // Access Lists | ||||
|         this.ui.access_list_select.selectize({ | ||||
|             valueField:       'id', | ||||
|             labelField:       'name', | ||||
|             searchField:      ['name'], | ||||
|             create:           false, | ||||
|             preload:          true, | ||||
|             allowEmptyOption: true, | ||||
|             render:           { | ||||
|                 option: function (item) { | ||||
|                     item.i18n         = App.i18n; | ||||
|                     item.formatDbDate = Helpers.formatDbDate; | ||||
|                     return accessListItemTemplate(item); | ||||
|                 } | ||||
|             }, | ||||
|             load:             function (query, callback) { | ||||
|                 App.Api.Nginx.AccessLists.getAll(['items']) | ||||
|                     .then(rows => { | ||||
|                         callback(rows); | ||||
|                     }) | ||||
|                     .catch(err => { | ||||
|                         console.error(err); | ||||
|                         callback(); | ||||
|                     }); | ||||
|             }, | ||||
|             onLoad:           function () { | ||||
|                 view.ui.access_list_select[0].selectize.setValue(view.model.get('access_list_id')); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         // Certificates | ||||
|         this.ui.letsencrypt.hide(); | ||||
|         this.ui.certificate_select.selectize({ | ||||
|             valueField:       'id', | ||||
|             labelField:       'nice_name', | ||||
|             searchField:      ['nice_name', 'domain_names'], | ||||
|             create:           false, | ||||
|             preload:          true, | ||||
|             allowEmptyOption: true, | ||||
|             render:           { | ||||
|                 option: function (item) { | ||||
|                     item.i18n         = App.i18n; | ||||
|                     item.formatDbDate = Helpers.formatDbDate; | ||||
|                     return certListItemTemplate(item); | ||||
|                 } | ||||
|             }, | ||||
|             load:             function (query, callback) { | ||||
|                 App.Api.Nginx.Certificates.getAll() | ||||
|                     .then(rows => { | ||||
|                         callback(rows); | ||||
|                     }) | ||||
|                     .catch(err => { | ||||
|                         console.error(err); | ||||
|                         callback(); | ||||
|                     }); | ||||
|             }, | ||||
|             onLoad:           function () { | ||||
|                 view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id')); | ||||
|             } | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     initialize: function (options) { | ||||
|         if (typeof options.model === 'undefined' || !options.model) { | ||||
|             this.model = new ProxyHostModel.Model(); | ||||
|         } | ||||
|  | ||||
|         this.locationsCollection = new ProxyLocationModel.Collection(); | ||||
|  | ||||
|         // Custom locations | ||||
|         this.showChildView('locations_regions', new CustomLocation.LocationCollectionView({ | ||||
|             collection: this.locationsCollection | ||||
|         })); | ||||
|  | ||||
|         // Check wether there are any location defined | ||||
|         if (options.model && Array.isArray(options.model.attributes.locations)) { | ||||
|             options.model.attributes.locations.forEach((location) => { | ||||
|                 let m = new ProxyLocationModel.Model(location); | ||||
|                 this.locationsCollection.add(m); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										59
									
								
								frontend/js/app/nginx/proxy/list/item.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								frontend/js/app/nginx/proxy/list/item.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| <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 class="wrap"> | ||||
|         <% domain_names.map(function(host) { | ||||
|             if (host.indexOf('*') === -1) { | ||||
|                 %> | ||||
|                 <span class="tag host-link hover-green" rel="http<%- certificate_id ? 's' : '' %>://<%- host %>"><%- host %></span> | ||||
|                 <% | ||||
|             } else { | ||||
|                 %> | ||||
|                 <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_scheme %>://<%- forward_host %>:<%- forward_port %></div> | ||||
| </td> | ||||
| <td> | ||||
|     <div><%- certificate && certificate_id ? i18n('ssl', certificate.provider) : i18n('ssl', 'none') %></div> | ||||
| </td> | ||||
| <td> | ||||
|     <div><%- access_list_id ? access_list.name : i18n('str', 'public') %></div> | ||||
| </td> | ||||
| <td> | ||||
|     <% | ||||
|     var o = isOnline(); | ||||
|     if (!enabled) { %> | ||||
|         <span class="status-icon bg-warning"></span> <%- i18n('str', 'disabled') %> | ||||
|     <% } else if (o === true) { %> | ||||
|         <span class="status-icon bg-success"></span> <%- i18n('str', 'online') %> | ||||
|     <% } else if (o === false) { %> | ||||
|         <span title="<%- getOfflineError() %>"><span class="status-icon bg-danger"></span> <%- i18n('str', 'offline') %></span> | ||||
|     <% } else { %> | ||||
|         <span class="status-icon bg-warning"></span> <%- i18n('str', 'unknown') %> | ||||
|     <% } %> | ||||
| </td> | ||||
| <% if (canManage) { %> | ||||
| <td class="text-right"> | ||||
|     <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="able dropdown-item"><i class="dropdown-icon fe fe-power"></i> <%- i18n('str', enabled ? 'disable' : 'enable') %></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> | ||||
| <% } %> | ||||
							
								
								
									
										61
									
								
								frontend/js/app/nginx/proxy/list/item.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								frontend/js/app/nginx/proxy/list/item.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../../main'); | ||||
| const template = require('./item.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template: template, | ||||
|     tagName:  'tr', | ||||
|  | ||||
|     ui: { | ||||
|         able:      'a.able', | ||||
|         edit:      'a.edit', | ||||
|         delete:    'a.delete', | ||||
|         host_link: '.host-link' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.able': function (e) { | ||||
|             e.preventDefault(); | ||||
|             let id = this.model.get('id'); | ||||
|             App.Api.Nginx.ProxyHosts[this.model.get('enabled') ? 'disable' : 'enable'](id) | ||||
|                 .then(() => { | ||||
|                     return App.Api.Nginx.ProxyHosts.get(id) | ||||
|                         .then(row => { | ||||
|                             this.model.set(row); | ||||
|                         }); | ||||
|                 }); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.edit': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxProxyForm(this.model); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.delete': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxProxyDeleteConfirm(this.model); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.host_link': function (e) { | ||||
|             e.preventDefault(); | ||||
|             let win = window.open($(e.currentTarget).attr('rel'), '_blank'); | ||||
|             win.focus(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         canManage: App.Cache.User.canManage('proxy_hosts'), | ||||
|  | ||||
|         isOnline: function () { | ||||
|             return typeof this.meta.nginx_online === 'undefined' ? null : this.meta.nginx_online; | ||||
|         }, | ||||
|  | ||||
|         getOfflineError: function () { | ||||
|             return this.meta.nginx_err || ''; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     initialize: function () { | ||||
|         this.listenTo(this.model, 'change', this.render); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										14
									
								
								frontend/js/app/nginx/proxy/list/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								frontend/js/app/nginx/proxy/list/main.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| <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> | ||||
|     <th><%- i18n('str', 'status') %></th> | ||||
|     <% if (canManage) { %> | ||||
|     <th> </th> | ||||
|     <% } %> | ||||
| </thead> | ||||
| <tbody> | ||||
|     <!-- items --> | ||||
| </tbody> | ||||
							
								
								
									
										32
									
								
								frontend/js/app/nginx/proxy/list/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								frontend/js/app/nginx/proxy/list/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| 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 card-table', | ||||
|     template:  template, | ||||
|  | ||||
|     regions: { | ||||
|         body: { | ||||
|             el:             'tbody', | ||||
|             replaceElement: true | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         canManage: App.Cache.User.canManage('proxy_hosts') | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         this.showChildView('body', new TableBody({ | ||||
|             collection: this.collection | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										64
									
								
								frontend/js/app/nginx/proxy/location-item.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								frontend/js/app/nginx/proxy/location-item.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| <div class="location-block card"> | ||||
|     <div class="card-body"> | ||||
|         <div class="row"> | ||||
|             <div class="col-sm-12"> | ||||
|                 <div class="form-group"> | ||||
|                     <label class="form-label"><%- i18n('locations', 'location_label') %> <span class="form-required">*</span></label> | ||||
|                     <div class="row gutter-xs"> | ||||
|                         <div class="col"> | ||||
|                             <div class="input-group"> | ||||
|                                 <span class="input-group-prepend"> | ||||
|                                     <span class="input-group-text">location</span> | ||||
|                                 </span> | ||||
|                                 <input type="text" name="path" class="form-control model" value="<%- path %>" placeholder="<%- i18n('locations', 'path') %>" required> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-auto"> | ||||
|                             <div class="selectgroup"> | ||||
|                                 <label class="selectgroup-item"> | ||||
|                                     <input type="checkbox" class="selectgroup-input"> | ||||
|                                     <span class="selectgroup-button"> | ||||
|                                         <i class="fe fe-settings"></i> | ||||
|                                     </span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="col-sm-3 col-md-3"> | ||||
|                 <div class="form-group"> | ||||
|                     <label class="form-label"><%- i18n('proxy-hosts', 'forward-scheme') %><span class="form-required">*</span></label> | ||||
|                     <select name="forward_scheme" class="form-control custom-select model" placeholder="http"> | ||||
|                         <option value="http" <%- forward_scheme === 'http' ? 'selected' : '' %>>http</option> | ||||
|                         <option value="https" <%- forward_scheme === 'https' ? 'selected' : '' %>>https</option> | ||||
|                     </select> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="col-sm-5 col-md-5"> | ||||
|                 <div class="form-group"> | ||||
|                     <label class="form-label"><%- i18n('proxy-hosts', 'forward-host') %><span class="form-required">*</span></label> | ||||
|                     <input type="text" name="forward_host" class="form-control text-monospace model" placeholder="" value="<%- forward_host %>" autocomplete="off" maxlength="50" required> | ||||
|                     <span style="font-size: 9px;"><%- i18n('proxy-hosts', 'custom-forward-host-help') %></span> | ||||
|                 </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 model" placeholder="80" value="<%- forward_port %>" required> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|         <div class="row config"> | ||||
|             <div class="col-md-12"> | ||||
|                 <div class="form-group"> | ||||
|                     <textarea name="advanced_config" rows="8" class="form-control text-monospace model" placeholder="# <%- i18n('all-hosts', 'advanced-warning') %>"><%- advanced_config %></textarea> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|  | ||||
|         <a href="#" class="card-link location-delete"> | ||||
|             <i class="fa fa-trash"></i> <%- i18n('locations', 'delete') %> | ||||
|         </a> | ||||
|     </div> | ||||
| </div>     | ||||
							
								
								
									
										54
									
								
								frontend/js/app/nginx/proxy/location.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								frontend/js/app/nginx/proxy/location.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| const locationItemTemplate   = require('./location-item.ejs'); | ||||
| const Mn                     = require('backbone.marionette'); | ||||
| const App                    = require('../../main'); | ||||
|  | ||||
| const LocationView = Mn.View.extend({ | ||||
|     template: locationItemTemplate, | ||||
|     className: 'location_block', | ||||
|  | ||||
|     ui: { | ||||
|         toggle:     'input[type="checkbox"]', | ||||
|         config:     '.config', | ||||
|         delete:     '.location-delete' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'change @ui.toggle': function(el) { | ||||
|             if (el.target.checked) { | ||||
|                 this.ui.config.show(); | ||||
|             } else { | ||||
|                 this.ui.config.hide(); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         'change .model': function (e) { | ||||
|             const map = {}; | ||||
|             map[e.target.name] = e.target.value; | ||||
|             this.model.set(map); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.delete': function () { | ||||
|             this.model.destroy(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     onRender: function() { | ||||
|         $(this.ui.config).hide(); | ||||
|     }, | ||||
|  | ||||
|     templateContext: function() { | ||||
|         return { | ||||
|             i18n: App.i18n | ||||
|         } | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const LocationCollectionView = Mn.CollectionView.extend({ | ||||
|     className: 'locations_container', | ||||
|     childView: LocationView | ||||
| }); | ||||
|  | ||||
| module.exports = { | ||||
|     LocationCollectionView, | ||||
|     LocationView | ||||
| } | ||||
							
								
								
									
										20
									
								
								frontend/js/app/nginx/proxy/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								frontend/js/app/nginx/proxy/main.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| <div class="card"> | ||||
|     <div class="card-status bg-success"></div> | ||||
|     <div class="card-header"> | ||||
|         <h3 class="card-title"><%- i18n('proxy-hosts', 'title') %></h3> | ||||
|         <div class="card-options"> | ||||
|             <a href="#" class="btn btn-outline-secondary btn-sm ml-2 help"><i class="fe fe-help-circle"></i></a> | ||||
|             <% if (showAddButton) { %> | ||||
|             <a href="#" class="btn btn-outline-success btn-sm ml-2 add-item"><%- i18n('proxy-hosts', '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> | ||||
							
								
								
									
										81
									
								
								frontend/js/app/nginx/proxy/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								frontend/js/app/nginx/proxy/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | ||||
| const Mn             = require('backbone.marionette'); | ||||
| const App            = require('../../main'); | ||||
| const ProxyHostModel = require('../../../models/proxy-host'); | ||||
| 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({ | ||||
|     id:       'nginx-proxy', | ||||
|     template: template, | ||||
|  | ||||
|     ui: { | ||||
|         list_region: '.list-region', | ||||
|         add:         '.add-item', | ||||
|         help:        '.help', | ||||
|         dimmer:      '.dimmer' | ||||
|     }, | ||||
|  | ||||
|     regions: { | ||||
|         list_region: '@ui.list_region' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.add': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxProxyForm(); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.help': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showHelp(App.i18n('proxy-hosts', 'help-title'), App.i18n('proxy-hosts', 'help-content')); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         showAddButton: App.Cache.User.canManage('proxy_hosts') | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         let view = this; | ||||
|  | ||||
|         App.Api.Nginx.ProxyHosts.getAll(['owner', 'access_list', 'certificate']) | ||||
|             .then(response => { | ||||
|                 if (!view.isDestroyed()) { | ||||
|                     if (response && response.length) { | ||||
|                         view.showChildView('list_region', new ListView({ | ||||
|                             collection: new ProxyHostModel.Collection(response) | ||||
|                         })); | ||||
|                     } else { | ||||
|                         let manage = App.Cache.User.canManage('proxy_hosts'); | ||||
|  | ||||
|                         view.showChildView('list_region', new EmptyView({ | ||||
|                             title:      App.i18n('proxy-hosts', 'empty'), | ||||
|                             subtitle:   App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), | ||||
|                             link:       manage ? App.i18n('proxy-hosts', 'add') : null, | ||||
|                             btn_color:  'success', | ||||
|                             permission: 'proxy_hosts', | ||||
|                             action:     function () { | ||||
|                                 App.Controller.showNginxProxyForm(); | ||||
|                             } | ||||
|                         })); | ||||
|                     } | ||||
|                 } | ||||
|             }) | ||||
|             .catch(err => { | ||||
|                 view.showChildView('list_region', new ErrorView({ | ||||
|                     code:    err.code, | ||||
|                     message: err.message, | ||||
|                     retry:   function () { | ||||
|                         App.Controller.showNginxProxy(); | ||||
|                     } | ||||
|                 })); | ||||
|  | ||||
|                 console.error(err); | ||||
|             }) | ||||
|             .then(() => { | ||||
|                 view.ui.dimmer.removeClass('active'); | ||||
|             }); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										23
									
								
								frontend/js/app/nginx/redirection/delete.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								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 (certificate_id) { %> | ||||
|                         <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> | ||||
							
								
								
									
										32
									
								
								frontend/js/app/nginx/redirection/delete.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								frontend/js/app/nginx/redirection/delete.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../main'); | ||||
| const template = require('./delete.ejs'); | ||||
|  | ||||
| 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'); | ||||
|                 }); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										137
									
								
								frontend/js/app/nginx/redirection/form.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								frontend/js/app/nginx/redirection/form.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,137 @@ | ||||
| <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> | ||||
|                 <li role="presentation" class="nav-item"><a href="#advanced" aria-controls="tab3" role="tab" data-toggle="tab" class="nav-link"><i class="fe fe-settings"></i> <%- i18n('all-hosts', 'advanced') %></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-12 col-md-12"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('all-hosts', 'ssl-certificate') %></label> | ||||
|                                 <select name="certificate_id" class="form-control custom-select" placeholder="<%- i18n('all-hosts', 'none') %>"> | ||||
|                                     <option selected value="0" data-data="{"id":0}" <%- certificate_id ? '' : 'selected' %>><%- i18n('all-hosts', 'none') %></option> | ||||
|                                     <option selected value="new" data-data="{"id":"new"}"><%- i18n('all-hosts', 'new-cert') %></option> | ||||
|                                 </select> | ||||
|                             </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' : '' %><%- certificate_id ? '' : ' 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-6 col-md-6"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="http2_support" value="1"<%- http2_support ? ' checked' : '' %><%- certificate_id ? '' : ' disabled' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'http2-support') %></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="hsts_enabled" value="1"<%- hsts_enabled ? ' checked' : '' %><%- certificate_id && ssl_forced ? '' : ' disabled' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'hsts-enabled') %> <a href="https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security" target="_blank"><i class="fe fe-help-circle"></i></a></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="hsts_subdomains" value="1"<%- hsts_subdomains ? ' checked' : '' %><%- certificate_id && ssl_forced && hsts_enabled ? '' : ' disabled' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'hsts-subdomains') %></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|  | ||||
|                         <!-- Lets encrypt --> | ||||
|                         <div class="col-sm-12 col-md-12 letsencrypt"> | ||||
|                             <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 disabled> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-12 col-md-12 letsencrypt"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="meta[letsencrypt_agree]" value="1" required disabled> | ||||
|                                     <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> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <!-- Advanced --> | ||||
|                 <div role="tabpanel" class="tab-pane" id="advanced"> | ||||
|                     <div class="row"> | ||||
|                         <div class="col-md-12"> | ||||
|                             <div class="form-group mb-0"> | ||||
|                                 <label class="form-label"><%- i18n('all-hosts', 'advanced-config') %></label> | ||||
|                                 <textarea name="advanced_config" rows="8" class="form-control text-monospace" placeholder="# <%- i18n('all-hosts', 'advanced-warning') %>"><%- advanced_config %></textarea> | ||||
|                             </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> | ||||
							
								
								
									
										209
									
								
								frontend/js/app/nginx/redirection/form.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								frontend/js/app/nginx/redirection/form.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,209 @@ | ||||
| const Mn                   = require('backbone.marionette'); | ||||
| const App                  = require('../../main'); | ||||
| const RedirectionHostModel = require('../../../models/redirection-host'); | ||||
| const template             = require('./form.ejs'); | ||||
| const certListItemTemplate = require('../certificates-list-item.ejs'); | ||||
| const Helpers              = require('../../../lib/helpers'); | ||||
|  | ||||
| require('jquery-serializejson'); | ||||
| require('selectize'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'modal-dialog', | ||||
|  | ||||
|     ui: { | ||||
|         form:               'form', | ||||
|         domain_names:       'input[name="domain_names"]', | ||||
|         buttons:            '.modal-footer button', | ||||
|         cancel:             'button.cancel', | ||||
|         save:               'button.save', | ||||
|         certificate_select: 'select[name="certificate_id"]', | ||||
|         ssl_forced:         'input[name="ssl_forced"]', | ||||
|         hsts_enabled:       'input[name="hsts_enabled"]', | ||||
|         hsts_subdomains:    'input[name="hsts_subdomains"]', | ||||
|         http2_support:      'input[name="http2_support"]', | ||||
|         letsencrypt:        '.letsencrypt' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'change @ui.certificate_select': function () { | ||||
|             let id = this.ui.certificate_select.val(); | ||||
|             if (id === 'new') { | ||||
|                 this.ui.letsencrypt.show().find('input').prop('disabled', false); | ||||
|             } else { | ||||
|                 this.ui.letsencrypt.hide().find('input').prop('disabled', true); | ||||
|             } | ||||
|  | ||||
|             let enabled = id === 'new' || parseInt(id, 10) > 0; | ||||
|  | ||||
|             let inputs = this.ui.ssl_forced.add(this.ui.http2_support); | ||||
|             inputs | ||||
|                 .prop('disabled', !enabled) | ||||
|                 .parents('.form-group') | ||||
|                 .css('opacity', enabled ? 1 : 0.5); | ||||
|  | ||||
|             if (!enabled) { | ||||
|                 inputs.prop('checked', false); | ||||
|             } | ||||
|  | ||||
|             inputs.trigger('change'); | ||||
|         }, | ||||
|  | ||||
|         'change @ui.ssl_forced': function () { | ||||
|             let checked = this.ui.ssl_forced.prop('checked'); | ||||
|             this.ui.hsts_enabled | ||||
|                 .prop('disabled', !checked) | ||||
|                 .parents('.form-group') | ||||
|                 .css('opacity', checked ? 1 : 0.5); | ||||
|  | ||||
|             if (!checked) { | ||||
|                 this.ui.hsts_enabled.prop('checked', false); | ||||
|             } | ||||
|  | ||||
|             this.ui.hsts_enabled.trigger('change'); | ||||
|         }, | ||||
|  | ||||
|         'change @ui.hsts_enabled': function () { | ||||
|             let checked = this.ui.hsts_enabled.prop('checked'); | ||||
|             this.ui.hsts_subdomains | ||||
|                 .prop('disabled', !checked) | ||||
|                 .parents('.form-group') | ||||
|                 .css('opacity', checked ? 1 : 0.5); | ||||
|  | ||||
|             if (!checked) { | ||||
|                 this.ui.hsts_subdomains.prop('checked', false); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         '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.block_exploits  = !!data.block_exploits; | ||||
|             data.preserve_path   = !!data.preserve_path; | ||||
|             data.http2_support   = !!data.http2_support; | ||||
|             data.hsts_enabled    = !!data.hsts_enabled; | ||||
|             data.hsts_subdomains = !!data.hsts_subdomains; | ||||
|             data.ssl_forced      = !!data.ssl_forced; | ||||
|  | ||||
|             if (typeof data.domain_names === 'string' && data.domain_names) { | ||||
|                 data.domain_names = data.domain_names.split(','); | ||||
|             } | ||||
|  | ||||
|             // Check for any domain names containing wildcards, which are not allowed with letsencrypt | ||||
|             if (data.certificate_id === 'new') { | ||||
|                 let domain_err = false; | ||||
|                 data.domain_names.map(function (name) { | ||||
|                     if (name.match(/\*/im)) { | ||||
|                         domain_err = true; | ||||
|                     } | ||||
|                 }); | ||||
|  | ||||
|                 if (domain_err) { | ||||
|                     alert('Cannot request Let\'s Encrypt Certificate for wildcard domains'); | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1'; | ||||
|             } else { | ||||
|                 data.certificate_id = parseInt(data.certificate_id, 10); | ||||
|             } | ||||
|  | ||||
|             let method = App.Api.Nginx.RedirectionHosts.create; | ||||
|             let is_new = true; | ||||
|  | ||||
|             if (this.model.get('id')) { | ||||
|                 // edit | ||||
|                 is_new  = false; | ||||
|                 method  = App.Api.Nginx.RedirectionHosts.update; | ||||
|                 data.id = this.model.get('id'); | ||||
|             } | ||||
|  | ||||
|             this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); | ||||
|             method(data) | ||||
|                 .then(result => { | ||||
|                     view.model.set(result); | ||||
|  | ||||
|                     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 App.Cache.User.get('email'); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         let view = this; | ||||
|  | ||||
|         // Domain names | ||||
|         this.ui.domain_names.selectize({ | ||||
|             delimiter:    ',', | ||||
|             persist:      false, | ||||
|             maxOptions:   15, | ||||
|             create:       function (input) { | ||||
|                 return { | ||||
|                     value: input, | ||||
|                     text:  input | ||||
|                 }; | ||||
|             }, | ||||
|             createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/ | ||||
|         }); | ||||
|  | ||||
|         // Certificates | ||||
|         this.ui.letsencrypt.hide(); | ||||
|         this.ui.certificate_select.selectize({ | ||||
|             valueField:       'id', | ||||
|             labelField:       'nice_name', | ||||
|             searchField:      ['nice_name', 'domain_names'], | ||||
|             create:           false, | ||||
|             preload:          true, | ||||
|             allowEmptyOption: true, | ||||
|             render:           { | ||||
|                 option: function (item) { | ||||
|                     item.i18n         = App.i18n; | ||||
|                     item.formatDbDate = Helpers.formatDbDate; | ||||
|                     return certListItemTemplate(item); | ||||
|                 } | ||||
|             }, | ||||
|             load:             function (query, callback) { | ||||
|                 App.Api.Nginx.Certificates.getAll() | ||||
|                     .then(rows => { | ||||
|                         callback(rows); | ||||
|                     }) | ||||
|                     .catch(err => { | ||||
|                         console.error(err); | ||||
|                         callback(); | ||||
|                     }); | ||||
|             }, | ||||
|             onLoad:           function () { | ||||
|                 view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id')); | ||||
|             } | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     initialize: function (options) { | ||||
|         if (typeof options.model === 'undefined' || !options.model) { | ||||
|             this.model = new RedirectionHostModel.Model(); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										56
									
								
								frontend/js/app/nginx/redirection/list/item.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								frontend/js/app/nginx/redirection/list/item.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| <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) { | ||||
|             if (host.indexOf('*') === -1) { | ||||
|                 %> | ||||
|                 <span class="tag host-link hover-yellow" rel="http<%- certificate_id ? 's' : '' %>://<%- host %>"><%- host %></span> | ||||
|                 <% | ||||
|             } else { | ||||
|                 %> | ||||
|                 <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_domain_name %></div> | ||||
| </td> | ||||
| <td> | ||||
|     <div><%- certificate ? i18n('ssl', certificate.provider) : i18n('ssl', 'none') %></div> | ||||
| </td> | ||||
| <td> | ||||
|     <% | ||||
|     var o = isOnline(); | ||||
|     if (!enabled) { %> | ||||
|         <span class="status-icon bg-warning"></span> <%- i18n('str', 'disabled') %> | ||||
|     <% } else if (o === true) { %> | ||||
|         <span class="status-icon bg-success"></span> <%- i18n('str', 'online') %> | ||||
|     <% } else if (o === false) { %> | ||||
|         <span title="<%- getOfflineError() %>"><span class="status-icon bg-danger"></span> <%- i18n('str', 'offline') %></span> | ||||
|     <% } else { %> | ||||
|         <span class="status-icon bg-warning"></span> <%- i18n('str', 'unknown') %> | ||||
|     <% } %> | ||||
| </td> | ||||
| <% if (canManage) { %> | ||||
| <td class="text-right"> | ||||
|     <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="able dropdown-item"><i class="dropdown-icon fe fe-power"></i> <%- i18n('str', enabled ? 'disable' : 'enable') %></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> | ||||
| <% } %> | ||||
							
								
								
									
										61
									
								
								frontend/js/app/nginx/redirection/list/item.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								frontend/js/app/nginx/redirection/list/item.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../../main'); | ||||
| const template = require('./item.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template: template, | ||||
|     tagName:  'tr', | ||||
|  | ||||
|     ui: { | ||||
|         able:      'a.able', | ||||
|         edit:      'a.edit', | ||||
|         delete:    'a.delete', | ||||
|         host_link: '.host-link' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.able': function (e) { | ||||
|             e.preventDefault(); | ||||
|             let id = this.model.get('id'); | ||||
|             App.Api.Nginx.RedirectionHosts[this.model.get('enabled') ? 'disable' : 'enable'](id) | ||||
|                 .then(() => { | ||||
|                     return App.Api.Nginx.RedirectionHosts.get(id) | ||||
|                         .then(row => { | ||||
|                             this.model.set(row); | ||||
|                         }); | ||||
|                 }); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.edit': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxRedirectionForm(this.model); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.delete': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxRedirectionDeleteConfirm(this.model); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.host_link': function (e) { | ||||
|             e.preventDefault(); | ||||
|             let win = window.open($(e.currentTarget).attr('rel'), '_blank'); | ||||
|             win.focus(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         canManage: App.Cache.User.canManage('redirection_hosts'), | ||||
|  | ||||
|         isOnline: function () { | ||||
|             return typeof this.meta.nginx_online === 'undefined' ? null : this.meta.nginx_online; | ||||
|         }, | ||||
|  | ||||
|         getOfflineError: function () { | ||||
|             return this.meta.nginx_err || ''; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     initialize: function () { | ||||
|         this.listenTo(this.model, 'change', this.render); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										13
									
								
								frontend/js/app/nginx/redirection/list/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								frontend/js/app/nginx/redirection/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', 'status') %></th> | ||||
|     <% if (canManage) { %> | ||||
|         <th> </th> | ||||
|     <% } %> | ||||
| </thead> | ||||
| <tbody> | ||||
|     <!-- items --> | ||||
| </tbody> | ||||
							
								
								
									
										32
									
								
								frontend/js/app/nginx/redirection/list/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								frontend/js/app/nginx/redirection/list/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| 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 card-table', | ||||
|     template:  template, | ||||
|  | ||||
|     regions: { | ||||
|         body: { | ||||
|             el:             'tbody', | ||||
|             replaceElement: true | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         canManage: App.Cache.User.canManage('redirection_hosts') | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         this.showChildView('body', new TableBody({ | ||||
|             collection: this.collection | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										20
									
								
								frontend/js/app/nginx/redirection/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								frontend/js/app/nginx/redirection/main.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| <div class="card"> | ||||
|     <div class="card-status bg-yellow"></div> | ||||
|     <div class="card-header"> | ||||
|         <h3 class="card-title">Redirection Hosts</h3> | ||||
|         <div class="card-options"> | ||||
|             <a href="#" class="btn btn-outline-secondary btn-sm ml-2 help"><i class="fe fe-help-circle"></i></a> | ||||
|             <% if (showAddButton) { %> | ||||
|             <a href="#" class="btn btn-outline-yellow btn-sm ml-2 add-item">Add Redirection Host</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> | ||||
							
								
								
									
										81
									
								
								frontend/js/app/nginx/redirection/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								frontend/js/app/nginx/redirection/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | ||||
| const Mn                   = require('backbone.marionette'); | ||||
| const App                  = require('../../main'); | ||||
| const RedirectionHostModel = require('../../../models/redirection-host'); | ||||
| 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({ | ||||
|     id:       'nginx-redirection', | ||||
|     template: template, | ||||
|  | ||||
|     ui: { | ||||
|         list_region: '.list-region', | ||||
|         add:         '.add-item', | ||||
|         help:        '.help', | ||||
|         dimmer:      '.dimmer' | ||||
|     }, | ||||
|  | ||||
|     regions: { | ||||
|         list_region: '@ui.list_region' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.add': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxRedirectionForm(); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.help': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showHelp(App.i18n('redirection-hosts', 'help-title'), App.i18n('redirection-hosts', 'help-content')); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         showAddButton: App.Cache.User.canManage('proxy_hosts') | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         let view = this; | ||||
|  | ||||
|         App.Api.Nginx.RedirectionHosts.getAll(['owner', 'certificate']) | ||||
|             .then(response => { | ||||
|                 if (!view.isDestroyed()) { | ||||
|                     if (response && response.length) { | ||||
|                         view.showChildView('list_region', new ListView({ | ||||
|                             collection: new RedirectionHostModel.Collection(response) | ||||
|                         })); | ||||
|                     } else { | ||||
|                         let manage = App.Cache.User.canManage('redirection_hosts'); | ||||
|  | ||||
|                         view.showChildView('list_region', new EmptyView({ | ||||
|                             title:      App.i18n('redirection-hosts', 'empty'), | ||||
|                             subtitle:   App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), | ||||
|                             link:       manage ? App.i18n('redirection-hosts', 'add') : null, | ||||
|                             btn_color:  'yellow', | ||||
|                             permission: 'redirection_hosts', | ||||
|                             action:     function () { | ||||
|                                 App.Controller.showNginxRedirectionForm(); | ||||
|                             } | ||||
|                         })); | ||||
|                     } | ||||
|                 } | ||||
|             }) | ||||
|             .catch(err => { | ||||
|                 view.showChildView('list_region', new ErrorView({ | ||||
|                     code:    err.code, | ||||
|                     message: err.message, | ||||
|                     retry:   function () { | ||||
|                         App.Controller.showNginxRedirection(); | ||||
|                     } | ||||
|                 })); | ||||
|  | ||||
|                 console.error(err); | ||||
|             }) | ||||
|             .then(() => { | ||||
|                 view.ui.dimmer.removeClass('active'); | ||||
|             }); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										19
									
								
								frontend/js/app/nginx/stream/delete.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								frontend/js/app/nginx/stream/delete.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- i18n('streams', '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('streams', '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> | ||||
							
								
								
									
										32
									
								
								frontend/js/app/nginx/stream/delete.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								frontend/js/app/nginx/stream/delete.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../main'); | ||||
| const template = require('./delete.ejs'); | ||||
|  | ||||
| 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.Streams.delete(this.model.get('id')) | ||||
|                 .then(() => { | ||||
|                     App.Controller.showNginxStream(); | ||||
|                     App.UI.closeModal(); | ||||
|                 }) | ||||
|                 .catch(err => { | ||||
|                     alert(err.message); | ||||
|                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||
|                 }); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										55
									
								
								frontend/js/app/nginx/stream/form.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								frontend/js/app/nginx/stream/form.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- i18n('streams', 'form-title', {id: id}) %></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"> | ||||
|                     <div class="form-group"> | ||||
|                         <label class="form-label"><%- i18n('streams', 'incoming-port') %> <span class="form-required">*</span></label> | ||||
|                         <input name="incoming_port" type="number" class="form-control text-monospace" placeholder="eg: 8080" value="<%- incoming_port %>" required> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <div class="col-sm-8 col-md-8"> | ||||
|                     <div class="form-group"> | ||||
|                         <label class="form-label"><%- i18n('streams', '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('streams', 'forwarding-port') %> <span class="form-required">*</span></label> | ||||
|                         <input name="forwarding_port" type="number" class="form-control text-monospace" placeholder="eg: 80" value="<%- forwarding_port %>" 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="tcp_forwarding" value="1"<%- tcp_forwarding ? ' checked' : '' %>> | ||||
|                             <span class="custom-switch-indicator"></span> | ||||
|                             <span class="custom-switch-description"><%- i18n('streams', 'tcp-forwarding') %></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="udp_forwarding" value="1"<%- udp_forwarding ? ' checked' : '' %>> | ||||
|                             <span class="custom-switch-indicator"></span> | ||||
|                             <span class="custom-switch-description"><%- i18n('streams', 'udp-forwarding') %></span> | ||||
|                         </label> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <div class="col-sm-12 col-md-12"> | ||||
|                     <div class="forward-type-error invalid-feedback"><%- i18n('streams', 'forward-type-error') %></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> | ||||
							
								
								
									
										91
									
								
								frontend/js/app/nginx/stream/form.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								frontend/js/app/nginx/stream/form.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | ||||
| const Mn          = require('backbone.marionette'); | ||||
| const App         = require('../../main'); | ||||
| const StreamModel = require('../../../models/stream'); | ||||
| const template    = require('./form.ejs'); | ||||
|  | ||||
| require('jquery-serializejson'); | ||||
| require('jquery-mask-plugin'); | ||||
| require('selectize'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'modal-dialog', | ||||
|  | ||||
|     ui: { | ||||
|         form:       'form', | ||||
|         forward_ip: 'input[name="forward_ip"]', | ||||
|         type_error: '.forward-type-error', | ||||
|         buttons:    '.modal-footer button', | ||||
|         switches:   '.custom-switch-input', | ||||
|         cancel:     'button.cancel', | ||||
|         save:       'button.save' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'change @ui.switches': function () { | ||||
|             this.ui.type_error.hide(); | ||||
|         }, | ||||
|  | ||||
|         '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(); | ||||
|  | ||||
|             if (!data.tcp_forwarding && !data.udp_forwarding) { | ||||
|                 this.ui.type_error.show(); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             // Manipulate | ||||
|             data.incoming_port   = parseInt(data.incoming_port, 10); | ||||
|             data.forwarding_port = parseInt(data.forwarding_port, 10); | ||||
|             data.tcp_forwarding  = !!data.tcp_forwarding; | ||||
|             data.udp_forwarding  = !!data.udp_forwarding; | ||||
|  | ||||
|             let method = App.Api.Nginx.Streams.create; | ||||
|             let is_new = true; | ||||
|  | ||||
|             if (this.model.get('id')) { | ||||
|                 // edit | ||||
|                 is_new  = false; | ||||
|                 method  = App.Api.Nginx.Streams.update; | ||||
|                 data.id = this.model.get('id'); | ||||
|             } | ||||
|  | ||||
|             this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); | ||||
|             method(data) | ||||
|                 .then(result => { | ||||
|                     view.model.set(result); | ||||
|  | ||||
|                     App.UI.closeModal(function () { | ||||
|                         if (is_new) { | ||||
|                             App.Controller.showNginxStream(); | ||||
|                         } | ||||
|                     }); | ||||
|                 }) | ||||
|                 .catch(err => { | ||||
|                     alert(err.message); | ||||
|                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||
|                 }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         this.ui.forward_ip.mask('099.099.099.099', { | ||||
|             clearIfNotMatch: true, | ||||
|             placeholder:     '000.000.000.000' | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     initialize: function (options) { | ||||
|         if (typeof options.model === 'undefined' || !options.model) { | ||||
|             this.model = new StreamModel.Model(); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										52
									
								
								frontend/js/app/nginx/stream/list/item.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								frontend/js/app/nginx/stream/list/item.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| <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 class="text-monospace"> | ||||
|         <%- incoming_port %> | ||||
|     </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 %>:<%- forwarding_port %></div> | ||||
| </td> | ||||
| <td> | ||||
|     <div> | ||||
|         <% if (tcp_forwarding) { %> | ||||
|             <span class="tag"><%- i18n('streams', 'tcp') %></span> | ||||
|         <% } | ||||
|         if (udp_forwarding) { %> | ||||
|             <span class="tag"><%- i18n('streams', 'udp') %></span> | ||||
|         <% } %> | ||||
|     </div> | ||||
| </td> | ||||
| <td> | ||||
|     <% | ||||
|     var o = isOnline(); | ||||
|     if (!enabled) { %> | ||||
|         <span class="status-icon bg-warning"></span> <%- i18n('str', 'disabled') %> | ||||
|     <% } else if (o === true) { %> | ||||
|         <span class="status-icon bg-success"></span> <%- i18n('str', 'online') %> | ||||
|     <% } else if (o === false) { %> | ||||
|         <span title="<%- getOfflineError() %>"><span class="status-icon bg-danger"></span> <%- i18n('str', 'offline') %></span> | ||||
|     <% } else { %> | ||||
|         <span class="status-icon bg-warning"></span> <%- i18n('str', 'unknown') %> | ||||
|     <% } %> | ||||
| </td> | ||||
| <% if (canManage) { %> | ||||
| <td class="text-right"> | ||||
|     <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="able dropdown-item"><i class="dropdown-icon fe fe-power"></i> <%- i18n('str', enabled ? 'disable' : 'enable') %></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> | ||||
| <% } %> | ||||
							
								
								
									
										54
									
								
								frontend/js/app/nginx/stream/list/item.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								frontend/js/app/nginx/stream/list/item.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../../main'); | ||||
| const template = require('./item.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template: template, | ||||
|     tagName:  'tr', | ||||
|  | ||||
|     ui: { | ||||
|         able:   'a.able', | ||||
|         edit:   'a.edit', | ||||
|         delete: 'a.delete' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.able': function (e) { | ||||
|             e.preventDefault(); | ||||
|             let id = this.model.get('id'); | ||||
|             App.Api.Nginx.Streams[this.model.get('enabled') ? 'disable' : 'enable'](id) | ||||
|                 .then(() => { | ||||
|                     return App.Api.Nginx.Streams.get(id) | ||||
|                         .then(row => { | ||||
|                             this.model.set(row); | ||||
|                         }); | ||||
|                 }); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.edit': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxStreamForm(this.model); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.delete': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxStreamDeleteConfirm(this.model); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         canManage: App.Cache.User.canManage('streams'), | ||||
|  | ||||
|         isOnline: function () { | ||||
|             return typeof this.meta.nginx_online === 'undefined' ? null : this.meta.nginx_online; | ||||
|         }, | ||||
|  | ||||
|         getOfflineError: function () { | ||||
|             return this.meta.nginx_err || ''; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     initialize: function () { | ||||
|         this.listenTo(this.model, 'change', this.render); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										13
									
								
								frontend/js/app/nginx/stream/list/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								frontend/js/app/nginx/stream/list/main.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| <thead> | ||||
|     <th width="30"> </th> | ||||
|     <th><%- i18n('streams', 'incoming-port') %></th> | ||||
|     <th><%- i18n('str', 'destination') %></th> | ||||
|     <th><%- i18n('streams', 'protocol') %></th> | ||||
|     <th><%- i18n('str', 'status') %></th> | ||||
|     <% if (canManage) { %> | ||||
|     <th> </th> | ||||
|     <% } %> | ||||
| </thead> | ||||
| <tbody> | ||||
|     <!-- items --> | ||||
| </tbody> | ||||
							
								
								
									
										32
									
								
								frontend/js/app/nginx/stream/list/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								frontend/js/app/nginx/stream/list/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| 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 card-table', | ||||
|     template:  template, | ||||
|  | ||||
|     regions: { | ||||
|         body: { | ||||
|             el:             'tbody', | ||||
|             replaceElement: true | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         canManage: App.Cache.User.canManage('streams') | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         this.showChildView('body', new TableBody({ | ||||
|             collection: this.collection | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										20
									
								
								frontend/js/app/nginx/stream/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								frontend/js/app/nginx/stream/main.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| <div class="card"> | ||||
|     <div class="card-status bg-blue"></div> | ||||
|     <div class="card-header"> | ||||
|         <h3 class="card-title"><%- i18n('streams', 'title') %></h3> | ||||
|         <div class="card-options"> | ||||
|             <a href="#" class="btn btn-outline-secondary btn-sm ml-2 help"><i class="fe fe-help-circle"></i></a> | ||||
|             <% if (showAddButton) { %> | ||||
|             <a href="#" class="btn btn-outline-blue btn-sm ml-2 add-item"><%- i18n('streams', '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> | ||||
							
								
								
									
										81
									
								
								frontend/js/app/nginx/stream/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								frontend/js/app/nginx/stream/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | ||||
| const Mn          = require('backbone.marionette'); | ||||
| const App         = require('../../main'); | ||||
| const StreamModel = require('../../../models/stream'); | ||||
| 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({ | ||||
|     id:       'nginx-stream', | ||||
|     template: template, | ||||
|  | ||||
|     ui: { | ||||
|         list_region: '.list-region', | ||||
|         add:         '.add-item', | ||||
|         help:        '.help', | ||||
|         dimmer:      '.dimmer' | ||||
|     }, | ||||
|  | ||||
|     regions: { | ||||
|         list_region: '@ui.list_region' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.add': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxStreamForm(); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.help': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showHelp(App.i18n('streams', 'help-title'), App.i18n('streams', 'help-content')); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         showAddButton: App.Cache.User.canManage('streams') | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         let view = this; | ||||
|  | ||||
|         App.Api.Nginx.Streams.getAll(['owner']) | ||||
|             .then(response => { | ||||
|                 if (!view.isDestroyed()) { | ||||
|                     if (response && response.length) { | ||||
|                         view.showChildView('list_region', new ListView({ | ||||
|                             collection: new StreamModel.Collection(response) | ||||
|                         })); | ||||
|                     } else { | ||||
|                         let manage = App.Cache.User.canManage('streams'); | ||||
|  | ||||
|                         view.showChildView('list_region', new EmptyView({ | ||||
|                             title:      App.i18n('streams', 'empty'), | ||||
|                             subtitle:   App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), | ||||
|                             link:       manage ? App.i18n('streams', 'add') : null, | ||||
|                             btn_color:  'blue', | ||||
|                             permission: 'streams', | ||||
|                             action:     function () { | ||||
|                                 App.Controller.showNginxStreamForm(); | ||||
|                             } | ||||
|                         })); | ||||
|                     } | ||||
|                 } | ||||
|             }) | ||||
|             .catch(err => { | ||||
|                 view.showChildView('list_region', new ErrorView({ | ||||
|                     code:    err.code, | ||||
|                     message: err.message, | ||||
|                     retry:   function () { | ||||
|                         App.Controller.showNginxStream(); | ||||
|                     } | ||||
|                 })); | ||||
|  | ||||
|                 console.error(err); | ||||
|             }) | ||||
|             .then(() => { | ||||
|                 view.ui.dimmer.removeClass('active'); | ||||
|             }); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										19
									
								
								frontend/js/app/router.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								frontend/js/app/router.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| const AppRouter  = require('marionette.approuter'); | ||||
| const Controller = require('./controller'); | ||||
|  | ||||
| module.exports = AppRouter.default.extend({ | ||||
|     controller: Controller, | ||||
|     appRoutes:  { | ||||
|         users:                'showUsers', | ||||
|         logout:               'logout', | ||||
|         'nginx/proxy':        'showNginxProxy', | ||||
|         'nginx/redirection':  'showNginxRedirection', | ||||
|         'nginx/404':          'showNginxDead', | ||||
|         'nginx/stream':       'showNginxStream', | ||||
|         'nginx/access':       'showNginxAccess', | ||||
|         'nginx/certificates': 'showNginxCertificates', | ||||
|         'audit-log':          'showAuditLog', | ||||
|         'settings':           'showSettings', | ||||
|         '*default':           'showDashboard' | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										53
									
								
								frontend/js/app/settings/default-site/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								frontend/js/app/settings/default-site/main.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- i18n('settings', id) %></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"> | ||||
|                     <div class="form-group"> | ||||
|                         <div class="form-label"><%- description %></div> | ||||
|                         <div class="custom-controls-stacked"> | ||||
|                             <label class="custom-control custom-radio"> | ||||
|                                 <input class="custom-control-input" name="value" value="congratulations" type="radio" required <%- value === 'congratulations' ? 'checked' : '' %>> | ||||
|                                 <div class="custom-control-label"><%- i18n('settings', 'default-site-congratulations') %></div> | ||||
|                             </label> | ||||
|                             <label class="custom-control custom-radio"> | ||||
|                                 <input class="custom-control-input" name="value" value="404" type="radio" required <%- value === '404' ? 'checked' : '' %>> | ||||
|                                 <div class="custom-control-label"><%- i18n('settings', 'default-site-404') %></div> | ||||
|                             </label> | ||||
|                             <label class="custom-control custom-radio"> | ||||
|                                 <input class="custom-control-input" name="value" value="redirect" type="radio" required <%- value === 'redirect' ? 'checked' : '' %>> | ||||
|                                 <div class="custom-control-label"><%- i18n('settings', 'default-site-redirect') %></div> | ||||
|                             </label> | ||||
|                             <label class="custom-control custom-radio"> | ||||
|                                 <input class="custom-control-input" name="value" value="html" type="radio" required <%- value === 'html' ? 'checked' : '' %>> | ||||
|                                 <div class="custom-control-label"><%- i18n('settings', 'default-site-html') %></div> | ||||
|                             </label> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <div class="col-sm-12 col-md-12 option-item option-redirect"> | ||||
|                     <div class="form-group"> | ||||
|                         <div class="form-label">Redirect to</div> | ||||
|                         <input class="form-control redirect-input" name="meta[redirect]" placeholder="https://" type="url" value="<%- meta && typeof meta.redirect !== 'undefined' ? meta.redirect : '' %>"> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <div class="col-sm-12 col-md-12 option-item option-html"> | ||||
|                     <div class="form-group"> | ||||
|                         <div class="form-label">HTML Content</div> | ||||
|                         <textarea class="form-control text-monospace html-content" name="meta[html]" rows="6" placeholder="<!-- Enter your HTML here -->"><%- meta && typeof meta.html !== 'undefined' ? meta.html : '' %></textarea> | ||||
|                     </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> | ||||
							
								
								
									
										69
									
								
								frontend/js/app/settings/default-site/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								frontend/js/app/settings/default-site/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../main'); | ||||
| const template = require('./main.ejs'); | ||||
|  | ||||
| require('jquery-serializejson'); | ||||
| require('selectize'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'modal-dialog', | ||||
|  | ||||
|     ui: { | ||||
|         form:     'form', | ||||
|         buttons:  '.modal-footer button', | ||||
|         cancel:   'button.cancel', | ||||
|         save:     'button.save', | ||||
|         options:  '.option-item', | ||||
|         value:    'input[name="value"]', | ||||
|         redirect: '.redirect-input', | ||||
|         html:     '.html-content' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'change @ui.value': function (e) { | ||||
|             let val = this.ui.value.filter(':checked').val(); | ||||
|             this.ui.options.hide(); | ||||
|             this.ui.options.filter('.option-' + val).show(); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.save': function (e) { | ||||
|             e.preventDefault(); | ||||
|  | ||||
|             let val = this.ui.value.filter(':checked').val(); | ||||
|  | ||||
|             // Clear redirect field before validation | ||||
|             if (val !== 'redirect') { | ||||
|                 this.ui.redirect.val('').attr('required', false); | ||||
|             } else { | ||||
|                 this.ui.redirect.attr('required', true); | ||||
|             } | ||||
|  | ||||
|             this.ui.html.attr('required', val === 'html'); | ||||
|  | ||||
|             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(); | ||||
|             data.id  = this.model.get('id'); | ||||
|  | ||||
|             this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); | ||||
|             App.Api.Settings.update(data) | ||||
|                 .then(result => { | ||||
|                     view.model.set(result); | ||||
|                     App.UI.closeModal(); | ||||
|                 }) | ||||
|                 .catch(err => { | ||||
|                     alert(err.message); | ||||
|                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||
|                 }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         this.ui.value.trigger('change'); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										21
									
								
								frontend/js/app/settings/list/item.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								frontend/js/app/settings/list/item.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| <td> | ||||
|     <div><%- name %></div> | ||||
|     <div class="small text-muted"> | ||||
|         <%- description %> | ||||
|     </div> | ||||
| </td> | ||||
| <td> | ||||
|     <div> | ||||
|         <% if (id === 'default-site') { %> | ||||
|             <%- i18n('settings', 'default-site-' + value) %> | ||||
|         <% } %> | ||||
|     </div> | ||||
| </td> | ||||
| <td class="text-right"> | ||||
|     <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> | ||||
|         </div> | ||||
|     </div> | ||||
| </td> | ||||
							
								
								
									
										23
									
								
								frontend/js/app/settings/list/item.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								frontend/js/app/settings/list/item.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| 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' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.edit': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showSettingForm(this.model); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     initialize: function () { | ||||
|         this.listenTo(this.model, 'change', this.render); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										8
									
								
								frontend/js/app/settings/list/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								frontend/js/app/settings/list/main.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| <thead> | ||||
|     <th><%- i18n('str', 'name') %></th> | ||||
|     <th><%- i18n('str', 'value') %></th> | ||||
|     <th> </th> | ||||
| </thead> | ||||
| <tbody> | ||||
|     <!-- items --> | ||||
| </tbody> | ||||
							
								
								
									
										27
									
								
								frontend/js/app/settings/list/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								frontend/js/app/settings/list/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| 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 card-table', | ||||
|     template:  template, | ||||
|  | ||||
|     regions: { | ||||
|         body: { | ||||
|             el:             'tbody', | ||||
|             replaceElement: true | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         this.showChildView('body', new TableBody({ | ||||
|             collection: this.collection | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										14
									
								
								frontend/js/app/settings/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								frontend/js/app/settings/main.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| <div class="card"> | ||||
|     <div class="card-status bg-teal"></div> | ||||
|     <div class="card-header"> | ||||
|         <h3 class="card-title"><%- i18n('settings', 'title') %></h3> | ||||
|     </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> | ||||
							
								
								
									
										48
									
								
								frontend/js/app/settings/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								frontend/js/app/settings/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| const Mn           = require('backbone.marionette'); | ||||
| const App          = require('../main'); | ||||
| const SettingModel = require('../../models/setting'); | ||||
| const ListView     = require('./list/main'); | ||||
| const ErrorView    = require('../error/main'); | ||||
| const template     = require('./main.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     id:       'settings', | ||||
|     template: template, | ||||
|  | ||||
|     ui: { | ||||
|         list_region: '.list-region', | ||||
|         add:         '.add-item', | ||||
|         dimmer:      '.dimmer' | ||||
|     }, | ||||
|  | ||||
|     regions: { | ||||
|         list_region: '@ui.list_region' | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         let view = this; | ||||
|  | ||||
|         App.Api.Settings.getAll() | ||||
|             .then(response => { | ||||
|                 if (!view.isDestroyed() && response && response.length) { | ||||
|                     view.showChildView('list_region', new ListView({ | ||||
|                         collection: new SettingModel.Collection(response) | ||||
|                     })); | ||||
|                 } | ||||
|             }) | ||||
|             .catch(err => { | ||||
|                 view.showChildView('list_region', new ErrorView({ | ||||
|                     code:    err.code, | ||||
|                     message: err.message, | ||||
|                     retry:   function () { | ||||
|                         App.Controller.showSettings(); | ||||
|                     } | ||||
|                 })); | ||||
|  | ||||
|                 console.error(err); | ||||
|             }) | ||||
|             .then(() => { | ||||
|                 view.ui.dimmer.removeClass('active'); | ||||
|             }); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										126
									
								
								frontend/js/app/tokens.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								frontend/js/app/tokens.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,126 @@ | ||||
| const STORAGE_NAME = 'nginx-proxy-manager-tokens'; | ||||
|  | ||||
| /** | ||||
|  * @returns {Array} | ||||
|  */ | ||||
| const getStorageTokens = function () { | ||||
|     let json = window.localStorage.getItem(STORAGE_NAME); | ||||
|     if (json) { | ||||
|         try { | ||||
|             return JSON.parse(json); | ||||
|         } catch (err) { | ||||
|             return []; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return []; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * @param  {Array}  tokens | ||||
|  */ | ||||
| const setStorageTokens = function (tokens) { | ||||
|     window.localStorage.setItem(STORAGE_NAME, JSON.stringify(tokens)); | ||||
| }; | ||||
|  | ||||
| const Tokens = { | ||||
|  | ||||
|     /** | ||||
|      * @returns {Number} | ||||
|      */ | ||||
|     getTokenCount: () => { | ||||
|         return getStorageTokens().length; | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * @returns {Object}    t,n | ||||
|      */ | ||||
|     getTopToken: () => { | ||||
|         let tokens = getStorageTokens(); | ||||
|         if (tokens && tokens.length) { | ||||
|             return tokens[0]; | ||||
|         } | ||||
|  | ||||
|         return null; | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * @returns {String} | ||||
|      */ | ||||
|     getNextTokenName: () => { | ||||
|         let tokens = getStorageTokens(); | ||||
|         if (tokens && tokens.length > 1 && typeof tokens[1] !== 'undefined' && typeof tokens[1].n !== 'undefined') { | ||||
|             return tokens[1].n; | ||||
|         } | ||||
|  | ||||
|         return null; | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * | ||||
|      * @param   {String}  token | ||||
|      * @param   {String}  [name] | ||||
|      * @returns {Number} | ||||
|      */ | ||||
|     addToken: (token, name) => { | ||||
|         // Get top token and if it's the same, ignore this call | ||||
|         let top = Tokens.getTopToken(); | ||||
|         if (!top || top.t !== token) { | ||||
|             let tokens = getStorageTokens(); | ||||
|             tokens.unshift({t: token, n: name || null}); | ||||
|             setStorageTokens(tokens); | ||||
|         } | ||||
|  | ||||
|         return Tokens.getTokenCount(); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * @param   {String}  token | ||||
|      * @returns {Boolean} | ||||
|      */ | ||||
|     setCurrentToken: token => { | ||||
|         let tokens = getStorageTokens(); | ||||
|         if (tokens.length) { | ||||
|             tokens[0].t = token; | ||||
|             setStorageTokens(tokens); | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * @param   {String}  name | ||||
|      * @returns {Boolean} | ||||
|      */ | ||||
|     setCurrentName: name => { | ||||
|         let tokens = getStorageTokens(); | ||||
|         if (tokens.length) { | ||||
|             tokens[0].n = name; | ||||
|             setStorageTokens(tokens); | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * @returns {Number} | ||||
|      */ | ||||
|     dropTopToken: () => { | ||||
|         let tokens = getStorageTokens(); | ||||
|         tokens.shift(); | ||||
|         setStorageTokens(tokens); | ||||
|         return tokens.length; | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * | ||||
|      */ | ||||
|     clearTokens: () => { | ||||
|         window.localStorage.removeItem(STORAGE_NAME); | ||||
|     } | ||||
|  | ||||
| }; | ||||
|  | ||||
| module.exports = Tokens; | ||||
							
								
								
									
										16
									
								
								frontend/js/app/ui/footer/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								frontend/js/app/ui/footer/main.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| <div class="row align-items-center flex-row-reverse"> | ||||
|     <div class="col-auto ml-auto"> | ||||
|         <div class="row align-items-center"> | ||||
|             <div class="col-auto"> | ||||
|                 <ul class="list-inline list-inline-dots mb-0"> | ||||
|                     <li class="list-inline-item"><a href="https://github.com/jc21/nginx-proxy-manager?utm_source=nginx-proxy-manager"><%- i18n('footer', 'fork-me') %></a></li> | ||||
|                 </ul> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
|     <div class="col-12 col-lg-auto mt-3 mt-lg-0 text-center"> | ||||
|         <%- i18n('main', 'version', {version: getVersion()}) %> | ||||
|         <%= i18n('footer', 'copy', {url: 'https://jc21.com?utm_source=nginx-proxy-manager'}) %> | ||||
|         <%= i18n('footer', 'theme', {url: 'https://tabler.github.io/?utm_source=nginx-proxy-manager'}) %> | ||||
|     </div> | ||||
| </div> | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user