mirror of
				https://github.com/NginxProxyManager/nginx-proxy-manager.git
				synced 2025-10-31 07:43:33 +00:00 
			
		
		
		
	Certificates ui section and permissions
This commit is contained in:
		| @@ -1,3 +1,4 @@ | ||||
| # WARNING: This is a DEVELOPMENT docker-compose file, it should not be used for production. | ||||
| version: "2" | ||||
| services: | ||||
|   app: | ||||
| @@ -12,6 +13,7 @@ services: | ||||
|     volumes: | ||||
|       - ./data/letsencrypt:/etc/letsencrypt | ||||
|       - .:/app | ||||
|       - ./rootfs/etc/nginx:/etc/nginx | ||||
|     working_dir: /app | ||||
|     depends_on: | ||||
|       - db | ||||
|   | ||||
							
								
								
									
										96
									
								
								rootfs/etc/nginx/mime.types
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								rootfs/etc/nginx/mime.types
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | ||||
| types { | ||||
|     text/html                                        html htm shtml; | ||||
|     text/css                                         css; | ||||
|     text/xml                                         xml; | ||||
|     image/gif                                        gif; | ||||
|     image/jpeg                                       jpeg jpg; | ||||
|     application/javascript                           js; | ||||
|     application/atom+xml                             atom; | ||||
|     application/rss+xml                              rss; | ||||
|  | ||||
|     text/mathml                                      mml; | ||||
|     text/plain                                       txt; | ||||
|     text/vnd.sun.j2me.app-descriptor                 jad; | ||||
|     text/vnd.wap.wml                                 wml; | ||||
|     text/x-component                                 htc; | ||||
|  | ||||
|     image/png                                        png; | ||||
|     image/svg+xml                                    svg svgz; | ||||
|     image/tiff                                       tif tiff; | ||||
|     image/vnd.wap.wbmp                               wbmp; | ||||
|     image/webp                                       webp; | ||||
|     image/x-icon                                     ico; | ||||
|     image/x-jng                                      jng; | ||||
|     image/x-ms-bmp                                   bmp; | ||||
|  | ||||
|     font/woff                                        woff; | ||||
|     font/woff2                                       woff2; | ||||
|  | ||||
|     application/java-archive                         jar war ear; | ||||
|     application/json                                 json; | ||||
|     application/mac-binhex40                         hqx; | ||||
|     application/msword                               doc; | ||||
|     application/pdf                                  pdf; | ||||
|     application/postscript                           ps eps ai; | ||||
|     application/rtf                                  rtf; | ||||
|     application/vnd.apple.mpegurl                    m3u8; | ||||
|     application/vnd.google-earth.kml+xml             kml; | ||||
|     application/vnd.google-earth.kmz                 kmz; | ||||
|     application/vnd.ms-excel                         xls; | ||||
|     application/vnd.ms-fontobject                    eot; | ||||
|     application/vnd.ms-powerpoint                    ppt; | ||||
|     application/vnd.oasis.opendocument.graphics      odg; | ||||
|     application/vnd.oasis.opendocument.presentation  odp; | ||||
|     application/vnd.oasis.opendocument.spreadsheet   ods; | ||||
|     application/vnd.oasis.opendocument.text          odt; | ||||
|     application/vnd.openxmlformats-officedocument.presentationml.presentation | ||||
|                                                      pptx; | ||||
|     application/vnd.openxmlformats-officedocument.spreadsheetml.sheet | ||||
|                                                      xlsx; | ||||
|     application/vnd.openxmlformats-officedocument.wordprocessingml.document | ||||
|                                                      docx; | ||||
|     application/vnd.wap.wmlc                         wmlc; | ||||
|     application/x-7z-compressed                      7z; | ||||
|     application/x-cocoa                              cco; | ||||
|     application/x-java-archive-diff                  jardiff; | ||||
|     application/x-java-jnlp-file                     jnlp; | ||||
|     application/x-makeself                           run; | ||||
|     application/x-perl                               pl pm; | ||||
|     application/x-pilot                              prc pdb; | ||||
|     application/x-rar-compressed                     rar; | ||||
|     application/x-redhat-package-manager             rpm; | ||||
|     application/x-sea                                sea; | ||||
|     application/x-shockwave-flash                    swf; | ||||
|     application/x-stuffit                            sit; | ||||
|     application/x-tcl                                tcl tk; | ||||
|     application/x-x509-ca-cert                       der pem crt; | ||||
|     application/x-xpinstall                          xpi; | ||||
|     application/xhtml+xml                            xhtml; | ||||
|     application/xspf+xml                             xspf; | ||||
|     application/zip                                  zip; | ||||
|  | ||||
|     application/octet-stream                         bin exe dll; | ||||
|     application/octet-stream                         deb; | ||||
|     application/octet-stream                         dmg; | ||||
|     application/octet-stream                         iso img; | ||||
|     application/octet-stream                         msi msp msm; | ||||
|  | ||||
|     audio/midi                                       mid midi kar; | ||||
|     audio/mpeg                                       mp3; | ||||
|     audio/ogg                                        ogg; | ||||
|     audio/x-m4a                                      m4a; | ||||
|     audio/x-realaudio                                ra; | ||||
|  | ||||
|     video/3gpp                                       3gpp 3gp; | ||||
|     video/mp2t                                       ts; | ||||
|     video/mp4                                        mp4; | ||||
|     video/mpeg                                       mpeg mpg; | ||||
|     video/quicktime                                  mov; | ||||
|     video/webm                                       webm; | ||||
|     video/x-flv                                      flv; | ||||
|     video/x-m4v                                      m4v; | ||||
|     video/x-mng                                      mng; | ||||
|     video/x-ms-asf                                   asx asf; | ||||
|     video/x-ms-wmv                                   wmv; | ||||
|     video/x-msvideo                                  avi; | ||||
| } | ||||
| @@ -1,10 +1,12 @@ | ||||
| #!/usr/bin/with-contenv bash | ||||
|  | ||||
| mkdir -p /tmp/nginx \ | ||||
| mkdir -p /tmp/nginx/body \ | ||||
|   /var/log/nginx \ | ||||
|   /data/{nginx,logs,access} \ | ||||
|   /data/nginx/{proxy_host,redirection_host,stream,dead_host} \ | ||||
|   /var/lib/nginx/cache/{public,private} | ||||
|  | ||||
| touch /var/log/nginx/error.log && chmod 777 /var/log/nginx/error.log | ||||
| chown root /tmp/nginx | ||||
| exec nginx | ||||
|  | ||||
| exec nginx | ||||
|   | ||||
| @@ -9,6 +9,7 @@ function appStart () { | ||||
|     const setup        = require('./setup'); | ||||
|     const app          = require('./app'); | ||||
|     const apiValidator = require('./lib/validator/api'); | ||||
|     const internalSsl  = require('./internal/ssl'); | ||||
|  | ||||
|     return migrate.latest() | ||||
|         .then(() => { | ||||
| @@ -18,6 +19,9 @@ function appStart () { | ||||
|             return apiValidator.loadSchemas; | ||||
|         }) | ||||
|         .then(() => { | ||||
|  | ||||
|             internalSsl.initTimer(); | ||||
|  | ||||
|             const server = app.listen(81, () => { | ||||
|                 logger.info('PID ' + process.pid + ' listening on port 81 ...'); | ||||
|  | ||||
|   | ||||
							
								
								
									
										183
									
								
								src/backend/internal/certificate.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								src/backend/internal/certificate.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,183 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const _                = require('lodash'); | ||||
| const error            = require('../lib/error'); | ||||
| const certificateModel = require('../models/certificate'); | ||||
|  | ||||
| function omissions () { | ||||
|     return ['is_deleted']; | ||||
| } | ||||
|  | ||||
| const internalCertificate = { | ||||
|  | ||||
|     /** | ||||
|      * @param   {Access}  access | ||||
|      * @param   {Object}  data | ||||
|      * @returns {Promise} | ||||
|      */ | ||||
|     create: (access, data) => { | ||||
|         return access.can('certificates:create', data) | ||||
|             .then(access_data => { | ||||
|                 // TODO | ||||
|                 return {}; | ||||
|             }); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * @param  {Access}  access | ||||
|      * @param  {Object}  data | ||||
|      * @param  {Integer} data.id | ||||
|      * @param  {String}  [data.email] | ||||
|      * @param  {String}  [data.name] | ||||
|      * @return {Promise} | ||||
|      */ | ||||
|     update: (access, data) => { | ||||
|         return access.can('certificates:update', data.id) | ||||
|             .then(access_data => { | ||||
|                 // TODO | ||||
|                 return {}; | ||||
|             }); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * @param  {Access}   access | ||||
|      * @param  {Object}   data | ||||
|      * @param  {Integer}  data.id | ||||
|      * @param  {Array}    [data.expand] | ||||
|      * @param  {Array}    [data.omit] | ||||
|      * @return {Promise} | ||||
|      */ | ||||
|     get: (access, data) => { | ||||
|         if (typeof data === 'undefined') { | ||||
|             data = {}; | ||||
|         } | ||||
|  | ||||
|         if (typeof data.id === 'undefined' || !data.id) { | ||||
|             data.id = access.token.get('attrs').id; | ||||
|         } | ||||
|  | ||||
|         return access.can('certificates:get', data.id) | ||||
|             .then(access_data => { | ||||
|                 let query = certificateModel | ||||
|                     .query() | ||||
|                     .where('is_deleted', 0) | ||||
|                     .andWhere('id', data.id) | ||||
|                     .allowEager('[owner]') | ||||
|                     .first(); | ||||
|  | ||||
|                 if (access_data.permission_visibility !== 'all') { | ||||
|                     query.andWhere('owner_user_id', access.token.get('attrs').id); | ||||
|                 } | ||||
|  | ||||
|                 // Custom omissions | ||||
|                 if (typeof data.omit !== 'undefined' && data.omit !== null) { | ||||
|                     query.omit(data.omit); | ||||
|                 } | ||||
|  | ||||
|                 if (typeof data.expand !== 'undefined' && data.expand !== null) { | ||||
|                     query.eager('[' + data.expand.join(', ') + ']'); | ||||
|                 } | ||||
|  | ||||
|                 return query; | ||||
|             }) | ||||
|             .then(row => { | ||||
|                 if (row) { | ||||
|                     return _.omit(row, omissions()); | ||||
|                 } else { | ||||
|                     throw new error.ItemNotFoundError(data.id); | ||||
|                 } | ||||
|             }); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * @param   {Access}  access | ||||
|      * @param   {Object}  data | ||||
|      * @param   {Integer} data.id | ||||
|      * @param   {String}  [data.reason] | ||||
|      * @returns {Promise} | ||||
|      */ | ||||
|     delete: (access, data) => { | ||||
|         return access.can('certificates:delete', data.id) | ||||
|             .then(() => { | ||||
|                 return internalCertificate.get(access, {id: data.id}); | ||||
|             }) | ||||
|             .then(row => { | ||||
|                 if (!row) { | ||||
|                     throw new error.ItemNotFoundError(data.id); | ||||
|                 } | ||||
|  | ||||
|                 return certificateModel | ||||
|                     .query() | ||||
|                     .where('id', row.id) | ||||
|                     .patch({ | ||||
|                         is_deleted: 1 | ||||
|                     }); | ||||
|             }) | ||||
|             .then(() => { | ||||
|                 return true; | ||||
|             }); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * All Lists | ||||
|      * | ||||
|      * @param   {Access}  access | ||||
|      * @param   {Array}   [expand] | ||||
|      * @param   {String}  [search_query] | ||||
|      * @returns {Promise} | ||||
|      */ | ||||
|     getAll: (access, expand, search_query) => { | ||||
|         return access.can('certificates:list') | ||||
|             .then(access_data => { | ||||
|                 let query = certificateModel | ||||
|                     .query() | ||||
|                     .where('is_deleted', 0) | ||||
|                     .groupBy('id') | ||||
|                     .omit(['is_deleted']) | ||||
|                     .allowEager('[owner]') | ||||
|                     .orderBy('name', 'ASC'); | ||||
|  | ||||
|                 if (access_data.permission_visibility !== 'all') { | ||||
|                     query.andWhere('owner_user_id', access.token.get('attrs').id); | ||||
|                 } | ||||
|  | ||||
|                 // Query is used for searching | ||||
|                 if (typeof search_query === 'string') { | ||||
|                     query.where(function () { | ||||
|                         this.where('name', 'like', '%' + search_query + '%'); | ||||
|                     }); | ||||
|                 } | ||||
|  | ||||
|                 if (typeof expand !== 'undefined' && expand !== null) { | ||||
|                     query.eager('[' + expand.join(', ') + ']'); | ||||
|                 } | ||||
|  | ||||
|                 return query; | ||||
|             }); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Report use | ||||
|      * | ||||
|      * @param   {Integer} user_id | ||||
|      * @param   {String}  visibility | ||||
|      * @returns {Promise} | ||||
|      */ | ||||
|     getCount: (user_id, visibility) => { | ||||
|         let query = certificateModel | ||||
|             .query() | ||||
|             .count('id as count') | ||||
|             .where('is_deleted', 0); | ||||
|  | ||||
|         if (visibility !== 'all') { | ||||
|             query.andWhere('owner_user_id', user_id); | ||||
|         } | ||||
|  | ||||
|         return query.first() | ||||
|             .then(row => { | ||||
|                 return parseInt(row.count, 10); | ||||
|             }); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| module.exports = internalCertificate; | ||||
| @@ -4,6 +4,7 @@ const _                = require('lodash'); | ||||
| const error            = require('../lib/error'); | ||||
| const deadHostModel    = require('../models/dead_host'); | ||||
| const internalHost     = require('./host'); | ||||
| const internalNginx    = require('./nginx'); | ||||
| const internalAuditLog = require('./audit-log'); | ||||
|  | ||||
| function omissions () { | ||||
| @@ -49,6 +50,13 @@ const internalDeadHost = { | ||||
|                     .omit(omissions()) | ||||
|                     .insertAndFetch(data); | ||||
|             }) | ||||
|             .then(row => { | ||||
|                 // Configure nginx | ||||
|                 return internalNginx.configure(deadHostModel, 'dead_host', row) | ||||
|                     .then(() => { | ||||
|                         return internalDeadHost.get(access, {id: row.id, expand: ['owner']}); | ||||
|                     }); | ||||
|             }) | ||||
|             .then(row => { | ||||
|                 // Add to audit log | ||||
|                 return internalAuditLog.add(access, { | ||||
| @@ -58,7 +66,7 @@ const internalDeadHost = { | ||||
|                     meta:        data | ||||
|                 }) | ||||
|                     .then(() => { | ||||
|                         return _.omit(row, omissions()); | ||||
|                         return row; | ||||
|                     }); | ||||
|             }); | ||||
|     }, | ||||
| @@ -192,6 +200,13 @@ const internalDeadHost = { | ||||
|                     .patch({ | ||||
|                         is_deleted: 1 | ||||
|                     }) | ||||
|                     .then(() => { | ||||
|                         // Delete Nginx Config | ||||
|                         return internalNginx.deleteConfig('dead_host', row) | ||||
|                             .then(() => { | ||||
|                                 return internalNginx.reload(); | ||||
|                             }); | ||||
|                     }) | ||||
|                     .then(() => { | ||||
|                         // Add to audit log | ||||
|                         row.meta = internalHost.cleanMeta(row.meta); | ||||
|   | ||||
| @@ -1,18 +1,94 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const fs     = require('fs'); | ||||
| const Liquid = require('liquidjs'); | ||||
| const logger = require('../logger').nginx; | ||||
| const utils  = require('../lib/utils'); | ||||
| const error  = require('../lib/error'); | ||||
| const _           = require('lodash'); | ||||
| const fs          = require('fs'); | ||||
| const Liquid      = require('liquidjs'); | ||||
| const logger      = require('../logger').nginx; | ||||
| const utils       = require('../lib/utils'); | ||||
| const error       = require('../lib/error'); | ||||
| const internalSsl = require('./ssl'); | ||||
| const debug_mode  = process.env.NODE_ENV !== 'production'; | ||||
|  | ||||
| const internalNginx = { | ||||
|  | ||||
|     /** | ||||
|      * This will: | ||||
|      * - test the nginx config first to make sure it's OK | ||||
|      * - create / recreate the config for the host | ||||
|      * - test again | ||||
|      * - IF OK:  update the meta with online status | ||||
|      * - IF BAD: update the meta with offline status and remove the config entirely | ||||
|      * - then reload nginx | ||||
|      * | ||||
|      * @param   {Object}  model | ||||
|      * @param   {String}  host_type | ||||
|      * @param   {Object}  host | ||||
|      * @returns {Promise} | ||||
|      */ | ||||
|     configure: (model, host_type, host) => { | ||||
|         return internalNginx.test() | ||||
|             .then(() => { | ||||
|                 // Nginx is OK | ||||
|                 // We're deleting this config regardless. | ||||
|                 return internalNginx.deleteConfig(host_type, host); // Don't throw errors, as the file may not exist at all | ||||
|             }) | ||||
|             .then(() => { | ||||
|                 if (host.ssl && !internalSsl.hasValidSslCerts(host_type, host)) { | ||||
|                     return internalSsl.configureSsl(host_type, host); | ||||
|                 } | ||||
|             }) | ||||
|             .then(() => { | ||||
|                 return internalNginx.generateConfig(host_type, host); | ||||
|             }) | ||||
|             .then(() => { | ||||
|                 // Test nginx again and update meta with result | ||||
|                 return internalNginx.test() | ||||
|                     .then(() => { | ||||
|                         // nginx is ok | ||||
|                         return model | ||||
|                             .query() | ||||
|                             .where('id', host.id) | ||||
|                             .patch({ | ||||
|                                 meta: _.assign({}, host.meta, { | ||||
|                                     nginx_online: true, | ||||
|                                     nginx_err:    null | ||||
|                                 }) | ||||
|                             }); | ||||
|                     }) | ||||
|                     .catch(err => { | ||||
|  | ||||
|                         if (debug_mode) { | ||||
|                             logger.error('Nginx test failed:', err.message); | ||||
|                         } | ||||
|  | ||||
|                         // config is bad, update meta and delete config | ||||
|                         return model | ||||
|                             .query() | ||||
|                             .where('id', host.id) | ||||
|                             .patch({ | ||||
|                                 meta: _.assign({}, host.meta, { | ||||
|                                     nginx_online: false, | ||||
|                                     nginx_err:    err.message | ||||
|                                 }) | ||||
|                             }) | ||||
|                             .then(() => { | ||||
|                                 return internalNginx.deleteConfig(host_type, host, true); | ||||
|                             }); | ||||
|                     }); | ||||
|             }) | ||||
|             .then(() => { | ||||
|                 return internalNginx.reload(); | ||||
|             }); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * @returns {Promise} | ||||
|      */ | ||||
|     test: () => { | ||||
|         logger.info('Testing Nginx configuration'); | ||||
|         if (debug_mode) { | ||||
|             logger.info('Testing Nginx configuration'); | ||||
|         } | ||||
|  | ||||
|         return utils.exec('/usr/sbin/nginx -t'); | ||||
|     }, | ||||
|  | ||||
| @@ -43,8 +119,13 @@ const internalNginx = { | ||||
|      * @returns {Promise} | ||||
|      */ | ||||
|     generateConfig: (host_type, host) => { | ||||
|         host_type = host_type.replace(new RegExp('-', 'g'), '_'); | ||||
|  | ||||
|         if (debug_mode) { | ||||
|             logger.info('Generating ' + host_type + ' Config:', host); | ||||
|         } | ||||
|  | ||||
|         let renderEngine = Liquid(); | ||||
|         host_type        = host_type.replace(new RegExp('-', 'g'), '_'); | ||||
|  | ||||
|         return new Promise((resolve, reject) => { | ||||
|             let template = null; | ||||
| @@ -56,14 +137,23 @@ const internalNginx = { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             return renderEngine | ||||
|             renderEngine | ||||
|                 .parseAndRender(template, host) | ||||
|                 .then(config_text => { | ||||
|                     fs.writeFileSync(filename, config_text, {encoding: 'utf8'}); | ||||
|                     return true; | ||||
|  | ||||
|                     if (debug_mode) { | ||||
|                         logger.success('Wrote config:', filename, config_text); | ||||
|                     } | ||||
|  | ||||
|                     resolve(true); | ||||
|                 }) | ||||
|                 .catch(err => { | ||||
|                     throw new error.ConfigurationError(err.message); | ||||
|                     if (debug_mode) { | ||||
|                         logger.warn('Could not write ' + filename + ':', err.message); | ||||
|                     } | ||||
|  | ||||
|                     reject(new error.ConfigurationError(err.message)); | ||||
|                 }); | ||||
|         }); | ||||
|     }, | ||||
| @@ -75,10 +165,22 @@ const internalNginx = { | ||||
|      * @returns {Promise} | ||||
|      */ | ||||
|     deleteConfig: (host_type, host, throw_errors) => { | ||||
|         host_type = host_type.replace(new RegExp('-', 'g'), '_'); | ||||
|  | ||||
|         return new Promise((resolve, reject) => { | ||||
|             try { | ||||
|                 fs.unlinkSync(internalNginx.getConfigName(host_type, host.id)); | ||||
|                 let config_file = internalNginx.getConfigName(host_type, host.id); | ||||
|  | ||||
|                 if (debug_mode) { | ||||
|                     logger.warn('Deleting nginx config: ' + config_file); | ||||
|                 } | ||||
|  | ||||
|                 fs.unlinkSync(config_file); | ||||
|             } catch (err) { | ||||
|                 if (debug_mode) { | ||||
|                     logger.warn('Could not delete config:', err.message); | ||||
|                 } | ||||
|  | ||||
|                 if (throw_errors) { | ||||
|                     reject(err); | ||||
|                 } | ||||
|   | ||||
| @@ -4,6 +4,7 @@ const _                = require('lodash'); | ||||
| const error            = require('../lib/error'); | ||||
| const proxyHostModel   = require('../models/proxy_host'); | ||||
| const internalHost     = require('./host'); | ||||
| const internalNginx    = require('./nginx'); | ||||
| const internalAuditLog = require('./audit-log'); | ||||
|  | ||||
| function omissions () { | ||||
| @@ -50,6 +51,15 @@ const internalProxyHost = { | ||||
|                     .insertAndFetch(data); | ||||
|             }) | ||||
|             .then(row => { | ||||
|                 // Configure nginx | ||||
|                 return internalNginx.configure(proxyHostModel, 'proxy_host', row) | ||||
|                     .then(() => { | ||||
|                         return internalProxyHost.get(access, {id: row.id, expand: ['owner']}); | ||||
|                     }); | ||||
|             }) | ||||
|             .then(row => { | ||||
|                 data.meta = _.assign({}, data.meta || {}, row.meta); | ||||
|  | ||||
|                 // Add to audit log | ||||
|                 return internalAuditLog.add(access, { | ||||
|                     action:      'created', | ||||
| @@ -58,7 +68,7 @@ const internalProxyHost = { | ||||
|                     meta:        data | ||||
|                 }) | ||||
|                     .then(() => { | ||||
|                         return _.omit(row, omissions()); | ||||
|                         return row; | ||||
|                     }); | ||||
|             }); | ||||
|     }, | ||||
| @@ -192,6 +202,13 @@ const internalProxyHost = { | ||||
|                     .patch({ | ||||
|                         is_deleted: 1 | ||||
|                     }) | ||||
|                     .then(() => { | ||||
|                         // Delete Nginx Config | ||||
|                         return internalNginx.deleteConfig('proxy_host', row) | ||||
|                             .then(() => { | ||||
|                                 return internalNginx.reload(); | ||||
|                             }); | ||||
|                     }) | ||||
|                     .then(() => { | ||||
|                         // Add to audit log | ||||
|                         row.meta = internalHost.cleanMeta(row.meta); | ||||
|   | ||||
| @@ -4,6 +4,7 @@ const _                    = require('lodash'); | ||||
| const error                = require('../lib/error'); | ||||
| const redirectionHostModel = require('../models/redirection_host'); | ||||
| const internalHost         = require('./host'); | ||||
| const internalNginx        = require('./nginx'); | ||||
| const internalAuditLog     = require('./audit-log'); | ||||
|  | ||||
| function omissions () { | ||||
| @@ -49,6 +50,13 @@ const internalRedirectionHost = { | ||||
|                     .omit(omissions()) | ||||
|                     .insertAndFetch(data); | ||||
|             }) | ||||
|             .then(row => { | ||||
|                 // Configure nginx | ||||
|                 return internalNginx.configure(redirectionHostModel, 'redirection_host', row) | ||||
|                     .then(() => { | ||||
|                         return internalRedirectionHost.get(access, {id: row.id, expand: ['owner']}); | ||||
|                     }); | ||||
|             }) | ||||
|             .then(row => { | ||||
|                 // Add to audit log | ||||
|                 return internalAuditLog.add(access, { | ||||
| @@ -58,7 +66,7 @@ const internalRedirectionHost = { | ||||
|                     meta:        data | ||||
|                 }) | ||||
|                     .then(() => { | ||||
|                         return _.omit(row, omissions()); | ||||
|                         return row; | ||||
|                     }); | ||||
|             }); | ||||
|     }, | ||||
| @@ -192,6 +200,13 @@ const internalRedirectionHost = { | ||||
|                     .patch({ | ||||
|                         is_deleted: 1 | ||||
|                     }) | ||||
|                     .then(() => { | ||||
|                         // Delete Nginx Config | ||||
|                         return internalNginx.deleteConfig('redirection_host', row) | ||||
|                             .then(() => { | ||||
|                                 return internalNginx.reload(); | ||||
|                             }); | ||||
|                     }) | ||||
|                     .then(() => { | ||||
|                         // Add to audit log | ||||
|                         row.meta = internalHost.cleanMeta(row.meta); | ||||
|   | ||||
| @@ -17,6 +17,7 @@ const internalSsl = { | ||||
|     interval_processing: false, | ||||
|  | ||||
|     initTimer: () => { | ||||
|         logger.info('Let\'s Encrypt Renewal Timer initialized'); | ||||
|         internalSsl.interval = setInterval(internalSsl.processExpiringHosts, internalSsl.interval_timeout); | ||||
|     }, | ||||
|  | ||||
| @@ -51,7 +52,7 @@ const internalSsl = { | ||||
|      */ | ||||
|     hasValidSslCerts: (host_type, host) => { | ||||
|         host_type   = host_type.replace(new RegExp('-', 'g'), '_'); | ||||
|         let le_path = '/etc/letsencrypt/live/' + host_type + '_' + host.id; | ||||
|         let le_path = '/etc/letsencrypt/live/' + host_type + '-' + host.id; | ||||
|  | ||||
|         return fs.existsSync(le_path + '/fullchain.pem') && fs.existsSync(le_path + '/privkey.pem'); | ||||
|     }, | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
| const _                = require('lodash'); | ||||
| const error            = require('../lib/error'); | ||||
| const streamModel      = require('../models/stream'); | ||||
| const internalNginx    = require('./nginx'); | ||||
| const internalAuditLog = require('./audit-log'); | ||||
|  | ||||
| function omissions () { | ||||
| @@ -31,6 +32,13 @@ const internalStream = { | ||||
|                     .omit(omissions()) | ||||
|                     .insertAndFetch(data); | ||||
|             }) | ||||
|             .then(row => { | ||||
|                 // Configure nginx | ||||
|                 return internalNginx.configure(streamModel, 'stream', row) | ||||
|                     .then(() => { | ||||
|                         return internalStream.get(access, {id: row.id, expand: ['owner']}); | ||||
|                     }); | ||||
|             }) | ||||
|             .then(row => { | ||||
|                 // Add to audit log | ||||
|                 return internalAuditLog.add(access, { | ||||
| @@ -40,7 +48,7 @@ const internalStream = { | ||||
|                     meta:        data | ||||
|                 }) | ||||
|                     .then(() => { | ||||
|                         return _.omit(row, omissions()); | ||||
|                         return row; | ||||
|                     }); | ||||
|             }); | ||||
|     }, | ||||
| @@ -153,6 +161,13 @@ const internalStream = { | ||||
|                     .patch({ | ||||
|                         is_deleted: 1 | ||||
|                     }) | ||||
|                     .then(() => { | ||||
|                         // Delete Nginx Config | ||||
|                         return internalNginx.deleteConfig('stream', row) | ||||
|                             .then(() => { | ||||
|                                 return internalNginx.reload(); | ||||
|                             }); | ||||
|                     }) | ||||
|                     .then(() => { | ||||
|                         // Add to audit log | ||||
|                         return internalAuditLog.add(access, { | ||||
|   | ||||
| @@ -70,7 +70,8 @@ const internalUser = { | ||||
|                         redirection_hosts: 'manage', | ||||
|                         dead_hosts:        'manage', | ||||
|                         streams:           'manage', | ||||
|                         access_lists:      'manage' | ||||
|                         access_lists:      'manage', | ||||
|                         certificates:      'manage' | ||||
|                     }) | ||||
|                     .then(() => { | ||||
|                         return internalUser.get(access, {id: user.id, expand: ['permissions']}); | ||||
|   | ||||
| @@ -262,7 +262,8 @@ module.exports = function (token_string) { | ||||
|                                         permission_redirection_hosts: permissions.redirection_hosts, | ||||
|                                         permission_dead_hosts:        permissions.dead_hosts, | ||||
|                                         permission_streams:           permissions.streams, | ||||
|                                         permission_access_lists:      permissions.access_lists | ||||
|                                         permission_access_lists:      permissions.access_lists, | ||||
|                                         permission_certificates:      permissions.certificates | ||||
|                                     } | ||||
|                                 }; | ||||
|  | ||||
|   | ||||
							
								
								
									
										23
									
								
								src/backend/lib/access/certificates-create.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/backend/lib/access/certificates-create.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| { | ||||
|   "anyOf": [ | ||||
|     { | ||||
|       "$ref": "roles#/definitions/admin" | ||||
|     }, | ||||
|     { | ||||
|       "type": "object", | ||||
|       "required": ["permission_certificates", "roles"], | ||||
|       "properties": { | ||||
|         "permission_certificates": { | ||||
|           "$ref": "perms#/definitions/manage" | ||||
|         }, | ||||
|         "roles": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string", | ||||
|             "enum": ["user"] | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										23
									
								
								src/backend/lib/access/certificates-delete.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/backend/lib/access/certificates-delete.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| { | ||||
|   "anyOf": [ | ||||
|     { | ||||
|       "$ref": "roles#/definitions/admin" | ||||
|     }, | ||||
|     { | ||||
|       "type": "object", | ||||
|       "required": ["permission_certificates", "roles"], | ||||
|       "properties": { | ||||
|         "permission_certificates": { | ||||
|           "$ref": "perms#/definitions/manage" | ||||
|         }, | ||||
|         "roles": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string", | ||||
|             "enum": ["user"] | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										23
									
								
								src/backend/lib/access/certificates-get.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/backend/lib/access/certificates-get.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| { | ||||
|   "anyOf": [ | ||||
|     { | ||||
|       "$ref": "roles#/definitions/admin" | ||||
|     }, | ||||
|     { | ||||
|       "type": "object", | ||||
|       "required": ["permission_certificates", "roles"], | ||||
|       "properties": { | ||||
|         "permission_certificates": { | ||||
|           "$ref": "perms#/definitions/view" | ||||
|         }, | ||||
|         "roles": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string", | ||||
|             "enum": ["user"] | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										23
									
								
								src/backend/lib/access/certificates-list.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/backend/lib/access/certificates-list.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| { | ||||
|   "anyOf": [ | ||||
|     { | ||||
|       "$ref": "roles#/definitions/admin" | ||||
|     }, | ||||
|     { | ||||
|       "type": "object", | ||||
|       "required": ["permission_certificates", "roles"], | ||||
|       "properties": { | ||||
|         "permission_certificates": { | ||||
|           "$ref": "perms#/definitions/view" | ||||
|         }, | ||||
|         "roles": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string", | ||||
|             "enum": ["user"] | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										23
									
								
								src/backend/lib/access/certificates-update.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/backend/lib/access/certificates-update.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| { | ||||
|   "anyOf": [ | ||||
|     { | ||||
|       "$ref": "roles#/definitions/admin" | ||||
|     }, | ||||
|     { | ||||
|       "type": "object", | ||||
|       "required": ["permission_certificates", "roles"], | ||||
|       "properties": { | ||||
|         "permission_certificates": { | ||||
|           "$ref": "perms#/definitions/manage" | ||||
|         }, | ||||
|         "roles": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string", | ||||
|             "enum": ["user"] | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   ] | ||||
| } | ||||
| @@ -55,6 +55,7 @@ exports.up = function (knex/*, Promise*/) { | ||||
|                 table.string('dead_hosts').notNull(); | ||||
|                 table.string('streams').notNull(); | ||||
|                 table.string('access_lists').notNull(); | ||||
|                 table.string('certificates').notNull(); | ||||
|                 table.unique('user_id'); | ||||
|             }); | ||||
|         }) | ||||
| @@ -147,6 +148,20 @@ exports.up = function (knex/*, Promise*/) { | ||||
|         .then(() => { | ||||
|             logger.info('[' + migrate_name + '] access_list Table created'); | ||||
|  | ||||
|             return knex.schema.createTable('certificate', table => { | ||||
|                 table.increments().primary(); | ||||
|                 table.dateTime('created_on').notNull(); | ||||
|                 table.dateTime('modified_on').notNull(); | ||||
|                 table.integer('owner_user_id').notNull().unsigned(); | ||||
|                 table.integer('is_deleted').notNull().unsigned().defaultTo(0); | ||||
|                 table.string('name').notNull(); | ||||
|                 // TODO | ||||
|                 table.json('meta').notNull(); | ||||
|             }); | ||||
|         }) | ||||
|         .then(() => { | ||||
|             logger.info('[' + migrate_name + '] certificate Table created'); | ||||
|  | ||||
|             return knex.schema.createTable('access_list_auth', table => { | ||||
|                 table.increments().primary(); | ||||
|                 table.dateTime('created_on').notNull(); | ||||
|   | ||||
							
								
								
									
										52
									
								
								src/backend/models/certificate.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/backend/models/certificate.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| // Objection Docs: | ||||
| // http://vincit.github.io/objection.js/ | ||||
|  | ||||
| 'use strict'; | ||||
|  | ||||
| const db    = require('../db'); | ||||
| const Model = require('objection').Model; | ||||
| const User  = require('./user'); | ||||
|  | ||||
| Model.knex(db); | ||||
|  | ||||
| class Certificate extends Model { | ||||
|     $beforeInsert () { | ||||
|         this.created_on  = Model.raw('NOW()'); | ||||
|         this.modified_on = Model.raw('NOW()'); | ||||
|     } | ||||
|  | ||||
|     $beforeUpdate () { | ||||
|         this.modified_on = Model.raw('NOW()'); | ||||
|     } | ||||
|  | ||||
|     static get name () { | ||||
|         return 'Certificate'; | ||||
|     } | ||||
|  | ||||
|     static get tableName () { | ||||
|         return 'certificate'; | ||||
|     } | ||||
|  | ||||
|     static get jsonAttributes () { | ||||
|         return ['meta']; | ||||
|     } | ||||
|  | ||||
|     static get relationMappings () { | ||||
|         return { | ||||
|             owner: { | ||||
|                 relation:   Model.HasOneRelation, | ||||
|                 modelClass: User, | ||||
|                 join:       { | ||||
|                     from: 'certificate.owner_user_id', | ||||
|                     to:   'user.id' | ||||
|                 }, | ||||
|                 modify:     function (qb) { | ||||
|                     qb.where('user.is_deleted', 0); | ||||
|                     qb.omit(['id', 'created_on', 'modified_on', 'is_deleted', 'email', 'roles']); | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|  | ||||
| module.exports = Certificate; | ||||
| @@ -36,6 +36,7 @@ router.use('/nginx/redirection-hosts', require('./nginx/redirection_hosts')); | ||||
| router.use('/nginx/dead-hosts', require('./nginx/dead_hosts')); | ||||
| router.use('/nginx/streams', require('./nginx/streams')); | ||||
| router.use('/nginx/access-lists', require('./nginx/access_lists')); | ||||
| router.use('/nginx/certificates', require('./nginx/certificates')); | ||||
|  | ||||
| /** | ||||
|  * API 404 for all other routes | ||||
|   | ||||
							
								
								
									
										150
									
								
								src/backend/routes/api/nginx/certificates.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								src/backend/routes/api/nginx/certificates.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,150 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const express             = require('express'); | ||||
| const validator           = require('../../../lib/validator'); | ||||
| const jwtdecode           = require('../../../lib/express/jwt-decode'); | ||||
| const internalCertificate = require('../../../internal/certificate'); | ||||
| const apiValidator        = require('../../../lib/validator/api'); | ||||
|  | ||||
| let router = express.Router({ | ||||
|     caseSensitive: true, | ||||
|     strict:        true, | ||||
|     mergeParams:   true | ||||
| }); | ||||
|  | ||||
| /** | ||||
|  * /api/nginx/certificates | ||||
|  */ | ||||
| router | ||||
|     .route('/') | ||||
|     .options((req, res) => { | ||||
|         res.sendStatus(204); | ||||
|     }) | ||||
|     .all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes | ||||
|  | ||||
|     /** | ||||
|      * GET /api/nginx/certificates | ||||
|      * | ||||
|      * Retrieve all certificates | ||||
|      */ | ||||
|     .get((req, res, next) => { | ||||
|         validator({ | ||||
|             additionalProperties: false, | ||||
|             properties:           { | ||||
|                 expand: { | ||||
|                     $ref: 'definitions#/definitions/expand' | ||||
|                 }, | ||||
|                 query:  { | ||||
|                     $ref: 'definitions#/definitions/query' | ||||
|                 } | ||||
|             } | ||||
|         }, { | ||||
|             expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null), | ||||
|             query:  (typeof req.query.query === 'string' ? req.query.query : null) | ||||
|         }) | ||||
|             .then(data => { | ||||
|                 return internalCertificate.getAll(res.locals.access, data.expand, data.query); | ||||
|             }) | ||||
|             .then(rows => { | ||||
|                 res.status(200) | ||||
|                     .send(rows); | ||||
|             }) | ||||
|             .catch(next); | ||||
|     }) | ||||
|  | ||||
|     /** | ||||
|      * POST /api/nginx/certificates | ||||
|      * | ||||
|      * Create a new certificate | ||||
|      */ | ||||
|     .post((req, res, next) => { | ||||
|         apiValidator({$ref: 'endpoints/certificates#/links/1/schema'}, req.body) | ||||
|             .then(payload => { | ||||
|                 return internalCertificate.create(res.locals.access, payload); | ||||
|             }) | ||||
|             .then(result => { | ||||
|                 res.status(201) | ||||
|                     .send(result); | ||||
|             }) | ||||
|             .catch(next); | ||||
|     }); | ||||
|  | ||||
| /** | ||||
|  * Specific certificate | ||||
|  * | ||||
|  * /api/nginx/certificates/123 | ||||
|  */ | ||||
| router | ||||
|     .route('/:host_id') | ||||
|     .options((req, res) => { | ||||
|         res.sendStatus(204); | ||||
|     }) | ||||
|     .all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes | ||||
|  | ||||
|     /** | ||||
|      * GET /api/nginx/certificates/123 | ||||
|      * | ||||
|      * Retrieve a specific certificate | ||||
|      */ | ||||
|     .get((req, res, next) => { | ||||
|         validator({ | ||||
|             required:             ['host_id'], | ||||
|             additionalProperties: false, | ||||
|             properties:           { | ||||
|                 host_id: { | ||||
|                     $ref: 'definitions#/definitions/id' | ||||
|                 }, | ||||
|                 expand:  { | ||||
|                     $ref: 'definitions#/definitions/expand' | ||||
|                 } | ||||
|             } | ||||
|         }, { | ||||
|             host_id: req.params.host_id, | ||||
|             expand:  (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null) | ||||
|         }) | ||||
|             .then(data => { | ||||
|                 return internalCertificate.get(res.locals.access, { | ||||
|                     id:     parseInt(data.host_id, 10), | ||||
|                     expand: data.expand | ||||
|                 }); | ||||
|             }) | ||||
|             .then(row => { | ||||
|                 res.status(200) | ||||
|                     .send(row); | ||||
|             }) | ||||
|             .catch(next); | ||||
|     }) | ||||
|  | ||||
|     /** | ||||
|      * PUT /api/nginx/certificates/123 | ||||
|      * | ||||
|      * Update and existing certificate | ||||
|      */ | ||||
|     .put((req, res, next) => { | ||||
|         apiValidator({$ref: 'endpoints/certificates#/links/2/schema'}, req.body) | ||||
|             .then(payload => { | ||||
|                 payload.id = parseInt(req.params.host_id, 10); | ||||
|                 return internalCertificate.update(res.locals.access, payload); | ||||
|             }) | ||||
|             .then(result => { | ||||
|                 res.status(200) | ||||
|                     .send(result); | ||||
|             }) | ||||
|             .catch(next); | ||||
|     }) | ||||
|  | ||||
|     /** | ||||
|      * DELETE /api/nginx/certificates/123 | ||||
|      * | ||||
|      * Update and existing certificate | ||||
|      */ | ||||
|     .delete((req, res, next) => { | ||||
|         internalCertificate.delete(res.locals.access, {id: parseInt(req.params.host_id, 10)}) | ||||
|             .then(result => { | ||||
|                 res.status(200) | ||||
|                     .send(result); | ||||
|             }) | ||||
|             .catch(next); | ||||
|     }); | ||||
|  | ||||
| module.exports = router; | ||||
| @@ -243,6 +243,10 @@ | ||||
|           "streams": { | ||||
|             "type": "string", | ||||
|             "pattern": "^(hidden|view|manage)$" | ||||
|           }, | ||||
|           "certificates": { | ||||
|             "type": "string", | ||||
|             "pattern": "^(hidden|view|manage)$" | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|   | ||||
| @@ -91,7 +91,8 @@ module.exports = function () { | ||||
|                                                 redirection_hosts: 'manage', | ||||
|                                                 dead_hosts:        'manage', | ||||
|                                                 streams:           'manage', | ||||
|                                                 access_lists:      'manage' | ||||
|                                                 access_lists:      'manage', | ||||
|                                                 certificates:      'manage' | ||||
|                                             }); | ||||
|                                     }); | ||||
|                             }); | ||||
|   | ||||
| @@ -1,19 +1,23 @@ | ||||
| # <%- hostname %> | ||||
| # {{ domain_names | join: ", " }} | ||||
| server { | ||||
|   listen 80; | ||||
|   <%- typeof ssl !== 'undefined' && ssl ? 'listen 443 ssl;' : '' %> | ||||
|   {%- if ssl_enabled == 1 or ssl_enabled == true -%} | ||||
|   listen 443 ssl; | ||||
|   {%- endif %} | ||||
|   server_name {{ domain_names | join: " " }}; | ||||
|   access_log /data/logs/proxy_host-{{ id }}.log proxy; | ||||
|  | ||||
|   server_name <%- hostname %>; | ||||
|  | ||||
|   access_log /config/logs/<%- hostname %>.log proxy; | ||||
|  | ||||
| <% if (typeof ssl !== 'undefined' && ssl) { -%> | ||||
|   {%- if ssl_enabled == 1 or ssl_enabled == true -%} | ||||
|   {%- if ssl_provider == "letsencrypt" %} | ||||
|   # Let's Encrypt SSL | ||||
|   include conf.d/include/letsencrypt-acme-challenge.conf; | ||||
|   include conf.d/include/ssl-ciphers.conf; | ||||
|   ssl_certificate /etc/letsencrypt/live/<%- hostname %>/fullchain.pem; | ||||
|   ssl_certificate_key /etc/letsencrypt/live/<%- hostname %>/privkey.pem; | ||||
| <% } -%> | ||||
|   ssl_certificate /etc/letsencrypt/live/proxy_host-{{ id }}/fullchain.pem; | ||||
|   ssl_certificate_key /etc/letsencrypt/live/proxy_host-{{ id }}/privkey.pem; | ||||
|   {%- endif -%} | ||||
|   {%- endif %} | ||||
|  | ||||
|   <%- typeof advanced !== 'undefined' && advanced ? advanced : '' %> | ||||
|   # TODO: Advanced config options | ||||
|  | ||||
|   return 404; | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,10 @@ | ||||
| # Letsencrypt Verification Temporary Host: <%- hostname %> | ||||
| # Letsencrypt Verification Temporary Host: {{ domain_names | join: ", " }} | ||||
| server { | ||||
|   listen 80; | ||||
|   server_name <%- hostname %>; | ||||
|  | ||||
|   access_log /config/logs/letsencrypt.log proxy; | ||||
|   server_name {{ domain_names | join: " " }}; | ||||
|   access_log /data/logs/letsencrypt.log proxy; | ||||
|  | ||||
|   location / { | ||||
|     root /config/letsencrypt-acme-challenge; | ||||
|     root /data/letsencrypt-acme-challenge; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,33 +1,51 @@ | ||||
| # <%- hostname %> | ||||
| # {{ domain_names | join: ", " }} | ||||
| server { | ||||
|   listen 80; | ||||
|   <%- typeof ssl !== 'undefined' && ssl ? 'listen 443 ssl;' : '' %> | ||||
|   {%- if ssl_enabled == 1 or ssl_enabled == true -%} | ||||
|   listen 443 ssl; | ||||
|   {%- endif %} | ||||
|   server_name {{ domain_names | join: " " }}; | ||||
|   access_log /data/logs/proxy_host-{{ id }}.log proxy; | ||||
|  | ||||
|   server_name <%- hostname %>; | ||||
|   set $server {{ forward_ip }}; | ||||
|   set $port   {{ forward_port }}; | ||||
|  | ||||
|   access_log /config/logs/<%- hostname %>.log proxy; | ||||
|   {% if caching_enabled == 1 or caching_enabled == true -%} | ||||
|   # Asset Caching | ||||
|   include conf.d/include/assets.conf; | ||||
|   {%- endif %} | ||||
|   {% if block_exploits == 1 or block_exploits == true -%} | ||||
|   # Block Exploits | ||||
|   include conf.d/include/block-exploits.conf; | ||||
|   {%- endif -%} | ||||
|  | ||||
|   set $server <%- forward_server %>; | ||||
|   set $port   <%- forward_port %>; | ||||
|  | ||||
|   <%- typeof asset_caching !== 'undefined' && asset_caching ? 'include conf.d/include/assets.conf;' : '' %> | ||||
|   <%- typeof block_exploits !== 'undefined' && block_exploits ? 'include conf.d/include/block-exploits.conf;' : '' %> | ||||
|  | ||||
| <% if (typeof ssl !== 'undefined' && ssl) { -%> | ||||
|   {%- if ssl_enabled == 1 or ssl_enabled == true -%} | ||||
|   {%- if ssl_provider == "letsencrypt" %} | ||||
|   # Let's Encrypt SSL | ||||
|   include conf.d/include/letsencrypt-acme-challenge.conf; | ||||
|   include conf.d/include/ssl-ciphers.conf; | ||||
|   ssl_certificate /etc/letsencrypt/live/<%- hostname %>/fullchain.pem; | ||||
|   ssl_certificate_key /etc/letsencrypt/live/<%- hostname %>/privkey.pem; | ||||
| <% } -%> | ||||
|   ssl_certificate /etc/letsencrypt/live/proxy_host-{{ id }}/fullchain.pem; | ||||
|   ssl_certificate_key /etc/letsencrypt/live/proxy_host-{{ id }}/privkey.pem; | ||||
|   {%- endif -%} | ||||
|   {%- endif %} | ||||
|  | ||||
| <%- typeof advanced !== 'undefined' && advanced ? advanced : '' %> | ||||
|   # TODO: Advanced config options | ||||
|  | ||||
|   location / { | ||||
|     <% if (typeof access_list_id !== 'undefined' && access_list_id) { -%> | ||||
|     {%- if access_list_id > 0 -%} | ||||
|     # Access List | ||||
|     auth_basic            "Authorization required"; | ||||
|     auth_basic_user_file  /config/access/<%- access_list_id %>; | ||||
|     <% } -%> | ||||
|     <%- typeof force_ssl !== 'undefined' && force_ssl ? 'include conf.d/include/force-ssl.conf;' : '' %> | ||||
|     auth_basic_user_file  /config/access/{{ access_list_id }}; | ||||
|     {%- endif %} | ||||
|  | ||||
|     {%- if ssl_enabled == 1 or ssl_enabled == true -%} | ||||
|     {%- if ssl_forced == 1 or ssl_forced == true -%} | ||||
|     # Force SSL | ||||
|     include conf.d/include/force-ssl.conf; | ||||
|     {%- endif -%} | ||||
|     {%- endif %} | ||||
|  | ||||
|     # Proxy! | ||||
|     include conf.d/include/proxy.conf; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,22 +1,34 @@ | ||||
| # <%- hostname %> | ||||
| # {{ domain_names | join: ", " }} | ||||
| server { | ||||
|   listen 80; | ||||
|   <%- typeof ssl !== 'undefined' && ssl ? 'listen 443 ssl;' : '' %> | ||||
|   {%- if ssl_enabled == 1 or ssl_enabled == true -%} | ||||
|   listen 443 ssl; | ||||
|   {%- endif %} | ||||
|   server_name {{ domain_names | join: " " }}; | ||||
|   access_log /data/logs/proxy_host-{{ id }}.log proxy; | ||||
|  | ||||
|   server_name <%- hostname %>; | ||||
|   {%- if caching_enabled == 1 or caching_enabled == true %} | ||||
|   # Asset Caching | ||||
|   include conf.d/include/assets.conf; | ||||
|   {%- endif %} | ||||
|   {%- if block_exploits == 1 or block_exploits == true %} | ||||
|   # Block Exploits | ||||
|   include conf.d/include/block-exploits.conf; | ||||
|   {%- endif -%} | ||||
|  | ||||
|   access_log /config/logs/<%- hostname %>.log proxy; | ||||
|  | ||||
|   <%- typeof asset_caching !== 'undefined' && asset_caching ? 'include conf.d/include/assets.conf;' : '' %> | ||||
|   <%- typeof block_exploits !== 'undefined' && block_exploits ? 'include conf.d/include/block-exploits.conf;' : '' %> | ||||
|  | ||||
| <% if (typeof ssl !== 'undefined' && ssl) { -%> | ||||
|   {%- if ssl_enabled == 1 or ssl_enabled == true -%} | ||||
|   {%- if ssl_provider == "letsencrypt" %} | ||||
|   # Let's Encrypt SSL | ||||
|   include conf.d/include/letsencrypt-acme-challenge.conf; | ||||
|   include conf.d/include/ssl-ciphers.conf; | ||||
|   ssl_certificate /etc/letsencrypt/live/<%- hostname %>/fullchain.pem; | ||||
|   ssl_certificate_key /etc/letsencrypt/live/<%- hostname %>/privkey.pem; | ||||
| <% } -%> | ||||
|   ssl_certificate /etc/letsencrypt/live/proxy_host-{{ id }}/fullchain.pem; | ||||
|   ssl_certificate_key /etc/letsencrypt/live/proxy_host-{{ id }}/privkey.pem; | ||||
|   {%- endif -%} | ||||
|   {%- endif %} | ||||
|  | ||||
|   <%- typeof advanced !== 'undefined' && advanced ? advanced : '' %> | ||||
|   # TODO: Advanced config options | ||||
|  | ||||
|   return 301 $scheme://<%- forward_host %>$request_uri; | ||||
|   # TODO: Preserve Path Option | ||||
|  | ||||
|   return 301 $scheme://{{ forward_domain_name }}$request_uri; | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,14 @@ | ||||
| # <%- incoming_port %> - <%- protocols.join(',').toUpperCase() %> | ||||
| <% | ||||
| protocols.forEach(function (protocol) { | ||||
| %> | ||||
| # {{ incoming_port }} TCP: {{ tcp_forwarding }} UDP: {{ udp_forwarding }} | ||||
|  | ||||
| {% if tcp_forwarding == 1 or tcp_forwarding == true -%} | ||||
| server { | ||||
|     listen <%- incoming_port %> <%- protocol === 'tcp' ? '' : protocol %>; | ||||
|     proxy_pass <%- forward_server %>:<%- forward_port %>; | ||||
|     listen {{ incoming_port }}; | ||||
|     proxy_pass {{ forward_ip }}:{{ forwarding_port }}; | ||||
| } | ||||
| <% | ||||
| }); | ||||
| %> | ||||
| {% endif %} | ||||
| {% if udp_forwarding == 1 or udp_forwarding == true %} | ||||
| server { | ||||
|     listen {{ incoming_port }} udp; | ||||
|     proxy_pass {{ forward_ip }}:{{ forwarding_port }}; | ||||
| } | ||||
| {% endif %} | ||||
|   | ||||
| @@ -500,6 +500,43 @@ module.exports = { | ||||
|                 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   {Integer}  data.id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             update: function (data) { | ||||
|                 let id = data.id; | ||||
|                 delete data.id; | ||||
|                 return fetch('put', 'nginx/certificates/' + id, data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Integer}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             delete: function (id) { | ||||
|                 return fetch('delete', 'nginx/certificates/' + id); | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     AuditLog: { | ||||
|   | ||||
| @@ -319,6 +319,20 @@ module.exports = { | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * 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()); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Audit Log | ||||
|      */ | ||||
|   | ||||
| @@ -10,7 +10,7 @@ const template   = require('./main.ejs'); | ||||
| module.exports = Mn.View.extend({ | ||||
|     template: template, | ||||
|     id:       'dashboard', | ||||
|     columns: 0, | ||||
|     columns:  0, | ||||
|  | ||||
|     stats: {}, | ||||
|  | ||||
| @@ -46,7 +46,7 @@ module.exports = Mn.View.extend({ | ||||
|             }, | ||||
|  | ||||
|             columns: view.columns | ||||
|         } | ||||
|         }; | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|   | ||||
| @@ -4,8 +4,6 @@ const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../main'); | ||||
| const template = require('./delete.ejs'); | ||||
|  | ||||
| require('jquery-serializejson'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'modal-dialog', | ||||
| @@ -22,7 +20,7 @@ module.exports = Mn.View.extend({ | ||||
|         'click @ui.save': function (e) { | ||||
|             e.preventDefault(); | ||||
|  | ||||
|             App.Api.Nginx.ProxyHosts.delete(this.model.get('id')) | ||||
|             App.Api.Nginx.AccessLists.delete(this.model.get('id')) | ||||
|                 .then(() => { | ||||
|                     App.Controller.showNginxAccess(); | ||||
|                     App.UI.closeModal(); | ||||
|   | ||||
							
								
								
									
										19
									
								
								src/frontend/js/app/nginx/certificates/delete.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/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> | ||||
							
								
								
									
										34
									
								
								src/frontend/js/app/nginx/certificates/delete.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/frontend/js/app/nginx/certificates/delete.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| 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'); | ||||
|                 }); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										122
									
								
								src/frontend/js/app/nginx/certificates/form.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								src/frontend/js/app/nginx/certificates/form.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- i18n('certificates', 'form-title', {id: id}) %></h5> | ||||
|         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> | ||||
|     </div> | ||||
|     <div class="modal-body has-tabs"> | ||||
|         <form> | ||||
|             <ul class="nav nav-tabs" role="tablist"> | ||||
|                 <li role="presentation" class="nav-item"><a href="#details" aria-controls="tab1" role="tab" data-toggle="tab" class="nav-link active"><i class="fe fe-zap"></i> <%- i18n('all-hosts', 'details') %></a></li> | ||||
|                 <li role="presentation" class="nav-item"><a href="#ssl-options" aria-controls="tab2" role="tab" data-toggle="tab" class="nav-link"><i class="fe fe-shield"></i> <%- i18n('str', 'ssl') %></a></li> | ||||
|             </ul> | ||||
|             <div class="tab-content"> | ||||
|                 <!-- Details --> | ||||
|                 <div role="tabpanel" class="tab-pane active" id="details"> | ||||
|                     <div class="row"> | ||||
|  | ||||
|                         <div class="col-sm-12 col-md-12"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('all-hosts', 'domain-names') %> <span class="form-required">*</span></label> | ||||
|                                 <input type="text" name="domain_names" class="form-control" id="input-domains" value="<%- domain_names.join(',') %>" required> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-8 col-md-8"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('proxy-hosts', 'forward-ip') %><span class="form-required">*</span></label> | ||||
|                                 <input type="text" name="forward_ip" class="form-control text-monospace" placeholder="000.000.000.000" value="<%- forward_ip %>" autocomplete="off" maxlength="15" required> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-4 col-md-4"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('proxy-hosts', 'forward-port') %> <span class="form-required">*</span></label> | ||||
|                                 <input name="forward_port" type="number" class="form-control text-monospace" placeholder="80" value="<%- forward_port %>" required> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <!-- SSL --> | ||||
|                 <div role="tabpanel" class="tab-pane" id="ssl-options"> | ||||
|                     <div class="row"> | ||||
|                         <div class="col-sm-6 col-md-6"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="ssl_enabled" value="1"<%- ssl_enabled ? ' checked' : '' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'enable-ssl') %></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-6 col-md-6"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="ssl_forced" value="1"<%- ssl_forced ? ' checked' : '' %><%- ssl_enabled ? '' : ' disabled' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'force-ssl') %></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-12 col-md-12"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('all-hosts', 'cert-provider') %></label> | ||||
|                                 <div class="selectgroup w-100"> | ||||
|                                     <label class="selectgroup-item"> | ||||
|                                         <input type="radio" name="ssl_provider" value="letsencrypt" class="selectgroup-input"<%- ssl_provider !== 'other' ? ' checked' : '' %>> | ||||
|                                         <span class="selectgroup-button"><%- i18n('ssl', 'letsencrypt') %></span> | ||||
|                                     </label> | ||||
|                                     <label class="selectgroup-item"> | ||||
|                                         <input type="radio" name="ssl_provider" value="other" class="selectgroup-input"<%- ssl_provider === 'other' ? ' checked' : '' %>> | ||||
|                                         <span class="selectgroup-button"><%- i18n('ssl', 'other') %></span> | ||||
|                                     </label> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </div> | ||||
|  | ||||
|                         <!-- Lets encrypt --> | ||||
|                         <div class="col-sm-12 col-md-12 letsencrypt-ssl"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('ssl', 'letsencrypt-email') %> <span class="form-required">*</span></label> | ||||
|                                 <input name="meta[letsencrypt_email]" type="email" class="form-control" placeholder="" value="<%- getLetsencryptEmail() %>" required> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-12 col-md-12 letsencrypt-ssl"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="meta[letsencrypt_agree]" value="1" required<%- getLetsencryptAgree() ? ' checked' : '' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%= i18n('ssl', 'letsencrypt-agree', {url: 'https://letsencrypt.org/repository/'}) %> <span class="form-required">*</span></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|  | ||||
|                         <!-- Other --> | ||||
|                         <div class="col-sm-12 col-md-12 other-ssl"> | ||||
|                             <div class="form-group"> | ||||
|                                 <div class="form-label"><%- i18n('all-hosts', 'other-certificate') %></div> | ||||
|                                 <div class="custom-file"> | ||||
|                                     <input type="file" class="custom-file-input" name="meta[other_ssl_certificate]" id="other_ssl_certificate"> | ||||
|                                     <label class="custom-file-label"><%- i18n('str', 'choose-file') %></label> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-12 col-md-12 other-ssl"> | ||||
|                             <div class="form-group"> | ||||
|                                 <div class="form-label"><%- i18n('all-hosts', 'other-certificate-key') %></div> | ||||
|                                 <div class="custom-file"> | ||||
|                                     <input type="file" class="custom-file-input" name="meta[other_ssl_certificate_key]" id="other_ssl_certificate_key"> | ||||
|                                     <label class="custom-file-label"><%- i18n('str', 'choose-file') %></label> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|  | ||||
|  | ||||
|         </form> | ||||
|     </div> | ||||
|     <div class="modal-footer"> | ||||
|         <button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button> | ||||
|         <button type="button" class="btn btn-teal save"><%- i18n('str', 'save') %></button> | ||||
|     </div> | ||||
| </div> | ||||
							
								
								
									
										195
									
								
								src/frontend/js/app/nginx/certificates/form.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										195
									
								
								src/frontend/js/app/nginx/certificates/form.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,195 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| 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('jquery-mask-plugin'); | ||||
| require('selectize'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:      template, | ||||
|     className:     'modal-dialog', | ||||
|     max_file_size: 5120, | ||||
|  | ||||
|     ui: { | ||||
|         form:                      'form', | ||||
|         domain_names:              'input[name="domain_names"]', | ||||
|         forward_ip:                'input[name="forward_ip"]', | ||||
|         buttons:                   '.modal-footer button', | ||||
|         cancel:                    'button.cancel', | ||||
|         save:                      'button.save', | ||||
|         ssl_enabled:               'input[name="ssl_enabled"]', | ||||
|         ssl_options:               '#ssl-options input', | ||||
|         ssl_provider:              'input[name="ssl_provider"]', | ||||
|         other_ssl_certificate:     '#other_ssl_certificate', | ||||
|         other_ssl_certificate_key: '#other_ssl_certificate_key', | ||||
|  | ||||
|         // SSL hiding and showing | ||||
|         all_ssl:         '.letsencrypt-ssl, .other-ssl', | ||||
|         letsencrypt_ssl: '.letsencrypt-ssl', | ||||
|         other_ssl:       '.other-ssl' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'change @ui.ssl_enabled': function () { | ||||
|             let enabled = this.ui.ssl_enabled.prop('checked'); | ||||
|             this.ui.ssl_options.not(this.ui.ssl_enabled).prop('disabled', !enabled).parents('.form-group').css('opacity', enabled ? 1 : 0.5); | ||||
|             this.ui.ssl_provider.trigger('change'); | ||||
|         }, | ||||
|  | ||||
|         'change @ui.ssl_provider': function () { | ||||
|             let enabled  = this.ui.ssl_enabled.prop('checked'); | ||||
|             let provider = this.ui.ssl_provider.filter(':checked').val(); | ||||
|             this.ui.all_ssl.hide().find('input').prop('disabled', true); | ||||
|             this.ui[provider + '_ssl'].show().find('input').prop('disabled', !enabled); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.save': function (e) { | ||||
|             e.preventDefault(); | ||||
|  | ||||
|             if (!this.ui.form[0].checkValidity()) { | ||||
|                 $('<input type="submit">').hide().appendTo(this.ui.form).click().remove(); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             let view = this; | ||||
|             let data = this.ui.form.serializeJSON(); | ||||
|  | ||||
|             // Manipulate | ||||
|             data.forward_port = parseInt(data.forward_port, 10); | ||||
|             _.map(data, function (item, idx) { | ||||
|                 if (typeof item === 'string' && item === '1') { | ||||
|                     item = true; | ||||
|                 } else if (typeof item === 'object' && item !== null) { | ||||
|                     _.map(item, function (item2, idx2) { | ||||
|                         if (typeof item2 === 'string' && item2 === '1') { | ||||
|                             item[idx2] = true; | ||||
|                         } | ||||
|                     }); | ||||
|                 } | ||||
|                 data[idx] = item; | ||||
|             }); | ||||
|  | ||||
|             if (typeof data.domain_names === 'string' && data.domain_names) { | ||||
|                 data.domain_names = data.domain_names.split(','); | ||||
|             } | ||||
|  | ||||
|             let require_ssl_files = typeof data.ssl_enabled !== 'undefined' && data.ssl_enabled && typeof data.ssl_provider !== 'undefined' && data.ssl_provider === 'other'; | ||||
|             let ssl_files         = []; | ||||
|             let method            = App.Api.Nginx.ProxyHosts.create; | ||||
|             let is_new            = true; | ||||
|  | ||||
|             let must_require_ssl_files = require_ssl_files && !view.model.hasSslFiles('other'); | ||||
|  | ||||
|             if (this.model.get('id')) { | ||||
|                 // edit | ||||
|                 is_new  = false; | ||||
|                 method  = App.Api.Nginx.ProxyHosts.update; | ||||
|                 data.id = this.model.get('id'); | ||||
|             } | ||||
|  | ||||
|             // check files are attached | ||||
|             if (require_ssl_files) { | ||||
|                 if (!this.ui.other_ssl_certificate[0].files.length || !this.ui.other_ssl_certificate[0].files[0].size) { | ||||
|                     if (must_require_ssl_files) { | ||||
|                         alert('certificate file is not attached'); | ||||
|                         return; | ||||
|                     } | ||||
|                 } else { | ||||
|                     if (this.ui.other_ssl_certificate[0].files[0].size > this.max_file_size) { | ||||
|                         alert('certificate file is too large (> 5kb)'); | ||||
|                         return; | ||||
|                     } | ||||
|                     ssl_files.push({name: 'other_certificate', file: this.ui.other_ssl_certificate[0].files[0]}); | ||||
|                 } | ||||
|  | ||||
|                 if (!this.ui.other_ssl_certificate_key[0].files.length || !this.ui.other_ssl_certificate_key[0].files[0].size) { | ||||
|                     if (must_require_ssl_files) { | ||||
|                         alert('certificate key file is not attached'); | ||||
|                         return; | ||||
|                     } | ||||
|                 } else { | ||||
|                     if (this.ui.other_ssl_certificate_key[0].files[0].size > this.max_file_size) { | ||||
|                         alert('certificate key file is too large (> 5kb)'); | ||||
|                         return; | ||||
|                     } | ||||
|                     ssl_files.push({name: 'other_certificate_key', file: this.ui.other_ssl_certificate_key[0].files[0]}); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); | ||||
|             method(data) | ||||
|                 .then(result => { | ||||
|                     view.model.set(result); | ||||
|  | ||||
|                     // Now upload the certs if we need to | ||||
|                     if (ssl_files.length) { | ||||
|                         let form_data = new FormData(); | ||||
|  | ||||
|                         ssl_files.map(function (file) { | ||||
|                             form_data.append(file.name, file.file); | ||||
|                         }); | ||||
|  | ||||
|                         return App.Api.Nginx.ProxyHosts.setCerts(view.model.get('id'), form_data) | ||||
|                             .then(result => { | ||||
|                                 view.model.set('meta', _.assign({}, view.model.get('meta'), result)); | ||||
|                             }); | ||||
|                     } | ||||
|                 }) | ||||
|                 .then(() => { | ||||
|                     App.UI.closeModal(function () { | ||||
|                         if (is_new) { | ||||
|                             App.Controller.showNginxProxy(); | ||||
|                         } | ||||
|                     }); | ||||
|                 }) | ||||
|                 .catch(err => { | ||||
|                     alert(err.message); | ||||
|                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||
|                 }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         getLetsencryptEmail: function () { | ||||
|             return typeof this.meta.letsencrypt_email !== 'undefined' ? this.meta.letsencrypt_email : App.Cache.User.get('email'); | ||||
|         }, | ||||
|  | ||||
|         getLetsencryptAgree: function () { | ||||
|             return typeof this.meta.letsencrypt_agree !== 'undefined' ? this.meta.letsencrypt_agree : false; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         this.ui.forward_ip.mask('099.099.099.099', { | ||||
|             clearIfNotMatch: true, | ||||
|             placeholder:     '000.000.000.000' | ||||
|         }); | ||||
|  | ||||
|         this.ui.ssl_enabled.trigger('change'); | ||||
|         this.ui.ssl_provider.trigger('change'); | ||||
|  | ||||
|         this.ui.domain_names.selectize({ | ||||
|             delimiter:    ',', | ||||
|             persist:      false, | ||||
|             maxOptions:   15, | ||||
|             create:       function (input) { | ||||
|                 return { | ||||
|                     value: input, | ||||
|                     text:  input | ||||
|                 }; | ||||
|             }, | ||||
|             createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/ | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     initialize: function (options) { | ||||
|         if (typeof options.model === 'undefined' || !options.model) { | ||||
|             this.model = new CertificateModel.Model(); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										40
									
								
								src/frontend/js/app/nginx/certificates/list/item.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/frontend/js/app/nginx/certificates/list/item.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| <td class="text-center"> | ||||
|     <div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>"> | ||||
|         <span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span> | ||||
|     </div> | ||||
| </td> | ||||
| <td> | ||||
|     <div> | ||||
|         <% domain_names.map(function(host) { | ||||
|         %> | ||||
|         <span class="tag"><%- host %></span> | ||||
|         <% | ||||
|         }); | ||||
|         %> | ||||
|     </div> | ||||
|     <div class="small text-muted"> | ||||
|         <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> | ||||
|     </div> | ||||
| </td> | ||||
| <td> | ||||
|     <div class="text-monospace"><%- forward_ip %>:<%- forward_port %></div> | ||||
| </td> | ||||
| <td> | ||||
|     <div><%- ssl_enabled && ssl_provider ? i18n('ssl', ssl_provider) : i18n('ssl', 'none') %></div> | ||||
| </td> | ||||
| <td> | ||||
|     <div><%- access_list_id ? access_list.name : i18n('str', 'public') %></div> | ||||
| </td> | ||||
| <% if (canManage) { %> | ||||
| <td class="text-center"> | ||||
|     <div class="item-action dropdown"> | ||||
|         <a href="#" data-toggle="dropdown" class="icon"><i class="fe fe-more-vertical"></i></a> | ||||
|         <div class="dropdown-menu dropdown-menu-right"> | ||||
|             <a href="#" class="edit dropdown-item"><i class="dropdown-icon fe fe-edit"></i> <%- i18n('str', 'edit') %></a> | ||||
|             <a href="#" class="logs dropdown-item"><i class="dropdown-icon fe fe-book"></i> <%- i18n('str', 'logs') %></a> | ||||
|             <div class="dropdown-divider"></div> | ||||
|             <a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a> | ||||
|         </div> | ||||
|     </div> | ||||
| </td> | ||||
| <% } %> | ||||
							
								
								
									
										35
									
								
								src/frontend/js/app/nginx/certificates/list/item.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/frontend/js/app/nginx/certificates/list/item.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../../main'); | ||||
| const template = require('./item.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template: template, | ||||
|     tagName:  'tr', | ||||
|  | ||||
|     ui: { | ||||
|         edit:   'a.edit', | ||||
|         delete: 'a.delete' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.edit': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxCertificateForm(this.model); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.delete': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxCertificateDeleteConfirm(this.model); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         canManage: App.Cache.User.canManage('certificates') | ||||
|     }, | ||||
|  | ||||
|     initialize: function () { | ||||
|         this.listenTo(this.model, 'change', this.render); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										13
									
								
								src/frontend/js/app/nginx/certificates/list/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/frontend/js/app/nginx/certificates/list/main.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| <thead> | ||||
|     <th width="30"> </th> | ||||
|     <th><%- i18n('str', 'source') %></th> | ||||
|     <th><%- i18n('str', 'destination') %></th> | ||||
|     <th><%- i18n('str', 'ssl') %></th> | ||||
|     <th><%- i18n('str', 'access') %></th> | ||||
|     <% if (canManage) { %> | ||||
|     <th> </th> | ||||
|     <% } %> | ||||
| </thead> | ||||
| <tbody> | ||||
|     <!-- items --> | ||||
| </tbody> | ||||
							
								
								
									
										34
									
								
								src/frontend/js/app/nginx/certificates/list/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/frontend/js/app/nginx/certificates/list/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../../main'); | ||||
| const ItemView = require('./item'); | ||||
| const template = require('./main.ejs'); | ||||
|  | ||||
| const TableBody = Mn.CollectionView.extend({ | ||||
|     tagName:   'tbody', | ||||
|     childView: ItemView | ||||
| }); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     tagName:   'table', | ||||
|     className: 'table table-hover table-outline table-vcenter text-nowrap card-table', | ||||
|     template:  template, | ||||
|  | ||||
|     regions: { | ||||
|         body: { | ||||
|             el:             'tbody', | ||||
|             replaceElement: true | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         canManage: App.Cache.User.canManage('certificates') | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         this.showChildView('body', new TableBody({ | ||||
|             collection: this.collection | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										20
									
								
								src/frontend/js/app/nginx/certificates/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/frontend/js/app/nginx/certificates/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('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) { %> | ||||
|             <a href="#" class="btn btn-outline-teal btn-sm ml-2 add-item"><%- i18n('certificates', '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> | ||||
							
								
								
									
										83
									
								
								src/frontend/js/app/nginx/certificates/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/frontend/js/app/nginx/certificates/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| 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(); | ||||
|             App.Controller.showNginxCertificateForm(); | ||||
|         }, | ||||
|  | ||||
|         '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:  'teal', | ||||
|                             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'); | ||||
|             }); | ||||
|     } | ||||
| }); | ||||
| @@ -4,8 +4,6 @@ const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../main'); | ||||
| const template = require('./delete.ejs'); | ||||
|  | ||||
| require('jquery-serializejson'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'modal-dialog', | ||||
|   | ||||
| @@ -19,6 +19,17 @@ | ||||
| <td> | ||||
|     <div><%- ssl_enabled && ssl_provider ? i18n('ssl', ssl_provider) : i18n('ssl', 'none') %></div> | ||||
| </td> | ||||
| <td> | ||||
|     <% | ||||
|     var o = isOnline(); | ||||
|     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-center"> | ||||
|     <div class="item-action dropdown"> | ||||
|   | ||||
| @@ -26,7 +26,15 @@ module.exports = Mn.View.extend({ | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         canManage: App.Cache.User.canManage('dead_hosts') | ||||
|         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 () { | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
|     <th width="30"> </th> | ||||
|     <th><%- i18n('str', 'source') %></th> | ||||
|     <th><%- i18n('str', 'ssl') %></th> | ||||
|     <th><%- i18n('str', 'status') %></th> | ||||
|     <% if (canManage) { %> | ||||
|     <th> </th> | ||||
|     <% } %> | ||||
|   | ||||
| @@ -4,8 +4,6 @@ const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../main'); | ||||
| const template = require('./delete.ejs'); | ||||
|  | ||||
| require('jquery-serializejson'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'modal-dialog', | ||||
|   | ||||
| @@ -25,6 +25,17 @@ | ||||
| <td> | ||||
|     <div><%- access_list_id ? access_list.name : i18n('str', 'public') %></div> | ||||
| </td> | ||||
| <td> | ||||
|     <% | ||||
|     var o = isOnline(); | ||||
|     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-center"> | ||||
|     <div class="item-action dropdown"> | ||||
|   | ||||
| @@ -26,7 +26,15 @@ module.exports = Mn.View.extend({ | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         canManage: App.Cache.User.canManage('proxy_hosts') | ||||
|         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 () { | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
|     <th><%- i18n('str', 'destination') %></th> | ||||
|     <th><%- i18n('str', 'ssl') %></th> | ||||
|     <th><%- i18n('str', 'access') %></th> | ||||
|     <th><%- i18n('str', 'status') %></th> | ||||
|     <% if (canManage) { %> | ||||
|     <th> </th> | ||||
|     <% } %> | ||||
|   | ||||
| @@ -4,8 +4,6 @@ const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../main'); | ||||
| const template = require('./delete.ejs'); | ||||
|  | ||||
| require('jquery-serializejson'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'modal-dialog', | ||||
|   | ||||
| @@ -22,6 +22,17 @@ | ||||
| <td> | ||||
|     <div><%- ssl_enabled && ssl_provider ? i18n('ssl', ssl_provider) : i18n('ssl', 'none') %></div> | ||||
| </td> | ||||
| <td> | ||||
|     <% | ||||
|     var o = isOnline(); | ||||
|     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-center"> | ||||
|     <div class="item-action dropdown"> | ||||
|   | ||||
| @@ -26,7 +26,15 @@ module.exports = Mn.View.extend({ | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         canManage: App.Cache.User.canManage('redirection_hosts') | ||||
|         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 () { | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
|     <th><%- i18n('str', 'source') %></th> | ||||
|     <th><%- i18n('str', 'destination') %></th> | ||||
|     <th><%- i18n('str', 'ssl') %></th> | ||||
|     <th><%- i18n('str', 'status') %></th> | ||||
|     <% if (canManage) { %> | ||||
|         <th> </th> | ||||
|     <% } %> | ||||
|   | ||||
| @@ -4,8 +4,6 @@ const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../main'); | ||||
| const template = require('./delete.ejs'); | ||||
|  | ||||
| require('jquery-serializejson'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'modal-dialog', | ||||
|   | ||||
| @@ -24,6 +24,17 @@ | ||||
|         <% } %> | ||||
|     </div> | ||||
| </td> | ||||
| <td> | ||||
|     <% | ||||
|     var o = isOnline(); | ||||
|     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-center"> | ||||
|     <div class="item-action dropdown"> | ||||
|   | ||||
| @@ -26,7 +26,15 @@ module.exports = Mn.View.extend({ | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         canManage: App.Cache.User.canManage('streams') | ||||
|         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 () { | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
|     <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> | ||||
|     <% } %> | ||||
|   | ||||
| @@ -5,15 +5,16 @@ const Controller = require('./controller'); | ||||
|  | ||||
| module.exports = Mn.AppRouter.extend({ | ||||
|     appRoutes: { | ||||
|         users:               'showUsers', | ||||
|         logout:              'logout', | ||||
|         'nginx/proxy':       'showNginxProxy', | ||||
|         'nginx/redirection': 'showNginxRedirection', | ||||
|         'nginx/404':         'showNginxDead', | ||||
|         'nginx/stream':      'showNginxStream', | ||||
|         'nginx/access':      'showNginxAccess', | ||||
|         'audit-log':         'showAuditLog', | ||||
|         '*default':          'showDashboard' | ||||
|         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', | ||||
|         '*default':           'showDashboard' | ||||
|     }, | ||||
|  | ||||
|     initialize: function () { | ||||
|   | ||||
| @@ -27,7 +27,12 @@ | ||||
|                 </li> | ||||
|                 <% if (canShow('access_lists')) { %> | ||||
|                 <li class="nav-item"> | ||||
|                     <a href="/nginx/access" class="nav-link"><i class="fe fe-lock"></i> <%- i18n('access-lists', 'title') %></a> | ||||
|                     <a href="/nginx/access" class="nav-link"><i class="fe fe-shield"></i> <%- i18n('access-lists', 'title') %></a> | ||||
|                 </li> | ||||
|                 <% } %> | ||||
|                 <% if (canShow('certificates')) { %> | ||||
|                 <li class="nav-item"> | ||||
|                     <a href="/nginx/certificates" class="nav-link"><i class="fe fe-lock"></i> <%- i18n('certificates', 'title') %></a> | ||||
|                 </li> | ||||
|                 <% } %> | ||||
|                 <% if (isAdmin()) { %> | ||||
|   | ||||
| @@ -31,7 +31,7 @@ | ||||
|                 </div> | ||||
|  | ||||
|                 <% | ||||
|                 var list = ['proxy-hosts', 'redirection-hosts', 'dead-hosts', 'streams', 'access-lists']; | ||||
|                 var list = ['proxy-hosts', 'redirection-hosts', 'dead-hosts', 'streams', 'access-lists', 'certificates']; | ||||
|                 list.map(function(item) { | ||||
|                     var perm = item.replace('-', '_'); | ||||
|                     %> | ||||
|   | ||||
| @@ -35,7 +35,8 @@ module.exports = Mn.View.extend({ | ||||
|                     dead_hosts:        'manage', | ||||
|                     proxy_hosts:       'manage', | ||||
|                     redirection_hosts: 'manage', | ||||
|                     streams:           'manage' | ||||
|                     streams:           'manage', | ||||
|                     certificates:      'manage' | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|   | ||||
| @@ -23,7 +23,11 @@ | ||||
|       "public": "Public", | ||||
|       "edit": "Edit", | ||||
|       "delete": "Delete", | ||||
|       "logs": "Logs" | ||||
|       "logs": "Logs", | ||||
|       "status": "Status", | ||||
|       "online": "Online", | ||||
|       "offline": "Offline", | ||||
|       "unknown": "Unknown" | ||||
|     }, | ||||
|     "login": { | ||||
|       "title": "Login to your account" | ||||
| @@ -127,6 +131,15 @@ | ||||
|       "help-title": "What is a Stream?", | ||||
|       "help-content": "A relatively new feature for Nginx, a Stream will serve to forward TCP/UDP traffic directly to another computer on the network.\nIf you're running game servers, FTP or SSH servers this can come in handy." | ||||
|     }, | ||||
|     "certificates": { | ||||
|       "title": "Certificates", | ||||
|       "empty": "There are no Certificates", | ||||
|       "add": "Add Certificate", | ||||
|       "delete": "Delete Certificate", | ||||
|       "delete-confirm": "Are you sure you want to delete this certificate? Any hosts using it will need to be updated later.", | ||||
|       "help-title": "SSL Certificates", | ||||
|       "help-content": "TODO" | ||||
|     }, | ||||
|     "access-lists": { | ||||
|       "title": "Access Lists", | ||||
|       "empty": "There are no Access Lists", | ||||
|   | ||||
							
								
								
									
										24
									
								
								src/frontend/js/models/certificate.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/frontend/js/models/certificate.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const Backbone = require('backbone'); | ||||
|  | ||||
| const model = Backbone.Model.extend({ | ||||
|     idAttribute: 'id', | ||||
|  | ||||
|     defaults: function () { | ||||
|         return { | ||||
|             id:              0, | ||||
|             created_on:      null, | ||||
|             modified_on:     null, | ||||
|             // The following are expansions: | ||||
|             owner:           null | ||||
|         }; | ||||
|     } | ||||
| }); | ||||
|  | ||||
| module.exports = { | ||||
|     Model:      model, | ||||
|     Collection: Backbone.Collection.extend({ | ||||
|         model: model | ||||
|     }) | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user