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" | version: "2" | ||||||
| services: | services: | ||||||
|   app: |   app: | ||||||
| @@ -12,6 +13,7 @@ services: | |||||||
|     volumes: |     volumes: | ||||||
|       - ./data/letsencrypt:/etc/letsencrypt |       - ./data/letsencrypt:/etc/letsencrypt | ||||||
|       - .:/app |       - .:/app | ||||||
|  |       - ./rootfs/etc/nginx:/etc/nginx | ||||||
|     working_dir: /app |     working_dir: /app | ||||||
|     depends_on: |     depends_on: | ||||||
|       - db |       - 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 | #!/usr/bin/with-contenv bash | ||||||
|  |  | ||||||
| mkdir -p /tmp/nginx \ | mkdir -p /tmp/nginx/body \ | ||||||
|  |   /var/log/nginx \ | ||||||
|   /data/{nginx,logs,access} \ |   /data/{nginx,logs,access} \ | ||||||
|   /data/nginx/{proxy_host,redirection_host,stream,dead_host} \ |   /data/nginx/{proxy_host,redirection_host,stream,dead_host} \ | ||||||
|   /var/lib/nginx/cache/{public,private} |   /var/lib/nginx/cache/{public,private} | ||||||
|  |  | ||||||
|  | touch /var/log/nginx/error.log && chmod 777 /var/log/nginx/error.log | ||||||
| chown root /tmp/nginx | chown root /tmp/nginx | ||||||
| exec nginx |  | ||||||
|  |  | ||||||
|  | exec nginx | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ function appStart () { | |||||||
|     const setup        = require('./setup'); |     const setup        = require('./setup'); | ||||||
|     const app          = require('./app'); |     const app          = require('./app'); | ||||||
|     const apiValidator = require('./lib/validator/api'); |     const apiValidator = require('./lib/validator/api'); | ||||||
|  |     const internalSsl  = require('./internal/ssl'); | ||||||
|  |  | ||||||
|     return migrate.latest() |     return migrate.latest() | ||||||
|         .then(() => { |         .then(() => { | ||||||
| @@ -18,6 +19,9 @@ function appStart () { | |||||||
|             return apiValidator.loadSchemas; |             return apiValidator.loadSchemas; | ||||||
|         }) |         }) | ||||||
|         .then(() => { |         .then(() => { | ||||||
|  |  | ||||||
|  |             internalSsl.initTimer(); | ||||||
|  |  | ||||||
|             const server = app.listen(81, () => { |             const server = app.listen(81, () => { | ||||||
|                 logger.info('PID ' + process.pid + ' listening on port 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 error            = require('../lib/error'); | ||||||
| const deadHostModel    = require('../models/dead_host'); | const deadHostModel    = require('../models/dead_host'); | ||||||
| const internalHost     = require('./host'); | const internalHost     = require('./host'); | ||||||
|  | const internalNginx    = require('./nginx'); | ||||||
| const internalAuditLog = require('./audit-log'); | const internalAuditLog = require('./audit-log'); | ||||||
|  |  | ||||||
| function omissions () { | function omissions () { | ||||||
| @@ -49,6 +50,13 @@ const internalDeadHost = { | |||||||
|                     .omit(omissions()) |                     .omit(omissions()) | ||||||
|                     .insertAndFetch(data); |                     .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 => { |             .then(row => { | ||||||
|                 // Add to audit log |                 // Add to audit log | ||||||
|                 return internalAuditLog.add(access, { |                 return internalAuditLog.add(access, { | ||||||
| @@ -58,7 +66,7 @@ const internalDeadHost = { | |||||||
|                     meta:        data |                     meta:        data | ||||||
|                 }) |                 }) | ||||||
|                     .then(() => { |                     .then(() => { | ||||||
|                         return _.omit(row, omissions()); |                         return row; | ||||||
|                     }); |                     }); | ||||||
|             }); |             }); | ||||||
|     }, |     }, | ||||||
| @@ -192,6 +200,13 @@ const internalDeadHost = { | |||||||
|                     .patch({ |                     .patch({ | ||||||
|                         is_deleted: 1 |                         is_deleted: 1 | ||||||
|                     }) |                     }) | ||||||
|  |                     .then(() => { | ||||||
|  |                         // Delete Nginx Config | ||||||
|  |                         return internalNginx.deleteConfig('dead_host', row) | ||||||
|  |                             .then(() => { | ||||||
|  |                                 return internalNginx.reload(); | ||||||
|  |                             }); | ||||||
|  |                     }) | ||||||
|                     .then(() => { |                     .then(() => { | ||||||
|                         // Add to audit log |                         // Add to audit log | ||||||
|                         row.meta = internalHost.cleanMeta(row.meta); |                         row.meta = internalHost.cleanMeta(row.meta); | ||||||
|   | |||||||
| @@ -1,18 +1,94 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
|  |  | ||||||
|  | const _           = require('lodash'); | ||||||
| const fs          = require('fs'); | const fs          = require('fs'); | ||||||
| const Liquid      = require('liquidjs'); | const Liquid      = require('liquidjs'); | ||||||
| const logger      = require('../logger').nginx; | const logger      = require('../logger').nginx; | ||||||
| const utils       = require('../lib/utils'); | const utils       = require('../lib/utils'); | ||||||
| const error       = require('../lib/error'); | const error       = require('../lib/error'); | ||||||
|  | const internalSsl = require('./ssl'); | ||||||
|  | const debug_mode  = process.env.NODE_ENV !== 'production'; | ||||||
|  |  | ||||||
| const internalNginx = { | 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} |      * @returns {Promise} | ||||||
|      */ |      */ | ||||||
|     test: () => { |     test: () => { | ||||||
|  |         if (debug_mode) { | ||||||
|             logger.info('Testing Nginx configuration'); |             logger.info('Testing Nginx configuration'); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         return utils.exec('/usr/sbin/nginx -t'); |         return utils.exec('/usr/sbin/nginx -t'); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
| @@ -43,9 +119,14 @@ const internalNginx = { | |||||||
|      * @returns {Promise} |      * @returns {Promise} | ||||||
|      */ |      */ | ||||||
|     generateConfig: (host_type, host) => { |     generateConfig: (host_type, host) => { | ||||||
|         let renderEngine = Liquid(); |  | ||||||
|         host_type = host_type.replace(new RegExp('-', 'g'), '_'); |         host_type = host_type.replace(new RegExp('-', 'g'), '_'); | ||||||
|  |  | ||||||
|  |         if (debug_mode) { | ||||||
|  |             logger.info('Generating ' + host_type + ' Config:', host); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         let renderEngine = Liquid(); | ||||||
|  |  | ||||||
|         return new Promise((resolve, reject) => { |         return new Promise((resolve, reject) => { | ||||||
|             let template = null; |             let template = null; | ||||||
|             let filename = internalNginx.getConfigName(host_type, host.id); |             let filename = internalNginx.getConfigName(host_type, host.id); | ||||||
| @@ -56,14 +137,23 @@ const internalNginx = { | |||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             return renderEngine |             renderEngine | ||||||
|                 .parseAndRender(template, host) |                 .parseAndRender(template, host) | ||||||
|                 .then(config_text => { |                 .then(config_text => { | ||||||
|                     fs.writeFileSync(filename, config_text, {encoding: 'utf8'}); |                     fs.writeFileSync(filename, config_text, {encoding: 'utf8'}); | ||||||
|                     return true; |  | ||||||
|  |                     if (debug_mode) { | ||||||
|  |                         logger.success('Wrote config:', filename, config_text); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     resolve(true); | ||||||
|                 }) |                 }) | ||||||
|                 .catch(err => { |                 .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} |      * @returns {Promise} | ||||||
|      */ |      */ | ||||||
|     deleteConfig: (host_type, host, throw_errors) => { |     deleteConfig: (host_type, host, throw_errors) => { | ||||||
|  |         host_type = host_type.replace(new RegExp('-', 'g'), '_'); | ||||||
|  |  | ||||||
|         return new Promise((resolve, reject) => { |         return new Promise((resolve, reject) => { | ||||||
|             try { |             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) { |             } catch (err) { | ||||||
|  |                 if (debug_mode) { | ||||||
|  |                     logger.warn('Could not delete config:', err.message); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|                 if (throw_errors) { |                 if (throw_errors) { | ||||||
|                     reject(err); |                     reject(err); | ||||||
|                 } |                 } | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ const _                = require('lodash'); | |||||||
| const error            = require('../lib/error'); | const error            = require('../lib/error'); | ||||||
| const proxyHostModel   = require('../models/proxy_host'); | const proxyHostModel   = require('../models/proxy_host'); | ||||||
| const internalHost     = require('./host'); | const internalHost     = require('./host'); | ||||||
|  | const internalNginx    = require('./nginx'); | ||||||
| const internalAuditLog = require('./audit-log'); | const internalAuditLog = require('./audit-log'); | ||||||
|  |  | ||||||
| function omissions () { | function omissions () { | ||||||
| @@ -50,6 +51,15 @@ const internalProxyHost = { | |||||||
|                     .insertAndFetch(data); |                     .insertAndFetch(data); | ||||||
|             }) |             }) | ||||||
|             .then(row => { |             .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 |                 // Add to audit log | ||||||
|                 return internalAuditLog.add(access, { |                 return internalAuditLog.add(access, { | ||||||
|                     action:      'created', |                     action:      'created', | ||||||
| @@ -58,7 +68,7 @@ const internalProxyHost = { | |||||||
|                     meta:        data |                     meta:        data | ||||||
|                 }) |                 }) | ||||||
|                     .then(() => { |                     .then(() => { | ||||||
|                         return _.omit(row, omissions()); |                         return row; | ||||||
|                     }); |                     }); | ||||||
|             }); |             }); | ||||||
|     }, |     }, | ||||||
| @@ -192,6 +202,13 @@ const internalProxyHost = { | |||||||
|                     .patch({ |                     .patch({ | ||||||
|                         is_deleted: 1 |                         is_deleted: 1 | ||||||
|                     }) |                     }) | ||||||
|  |                     .then(() => { | ||||||
|  |                         // Delete Nginx Config | ||||||
|  |                         return internalNginx.deleteConfig('proxy_host', row) | ||||||
|  |                             .then(() => { | ||||||
|  |                                 return internalNginx.reload(); | ||||||
|  |                             }); | ||||||
|  |                     }) | ||||||
|                     .then(() => { |                     .then(() => { | ||||||
|                         // Add to audit log |                         // Add to audit log | ||||||
|                         row.meta = internalHost.cleanMeta(row.meta); |                         row.meta = internalHost.cleanMeta(row.meta); | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ const _                    = require('lodash'); | |||||||
| const error                = require('../lib/error'); | const error                = require('../lib/error'); | ||||||
| const redirectionHostModel = require('../models/redirection_host'); | const redirectionHostModel = require('../models/redirection_host'); | ||||||
| const internalHost         = require('./host'); | const internalHost         = require('./host'); | ||||||
|  | const internalNginx        = require('./nginx'); | ||||||
| const internalAuditLog     = require('./audit-log'); | const internalAuditLog     = require('./audit-log'); | ||||||
|  |  | ||||||
| function omissions () { | function omissions () { | ||||||
| @@ -49,6 +50,13 @@ const internalRedirectionHost = { | |||||||
|                     .omit(omissions()) |                     .omit(omissions()) | ||||||
|                     .insertAndFetch(data); |                     .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 => { |             .then(row => { | ||||||
|                 // Add to audit log |                 // Add to audit log | ||||||
|                 return internalAuditLog.add(access, { |                 return internalAuditLog.add(access, { | ||||||
| @@ -58,7 +66,7 @@ const internalRedirectionHost = { | |||||||
|                     meta:        data |                     meta:        data | ||||||
|                 }) |                 }) | ||||||
|                     .then(() => { |                     .then(() => { | ||||||
|                         return _.omit(row, omissions()); |                         return row; | ||||||
|                     }); |                     }); | ||||||
|             }); |             }); | ||||||
|     }, |     }, | ||||||
| @@ -192,6 +200,13 @@ const internalRedirectionHost = { | |||||||
|                     .patch({ |                     .patch({ | ||||||
|                         is_deleted: 1 |                         is_deleted: 1 | ||||||
|                     }) |                     }) | ||||||
|  |                     .then(() => { | ||||||
|  |                         // Delete Nginx Config | ||||||
|  |                         return internalNginx.deleteConfig('redirection_host', row) | ||||||
|  |                             .then(() => { | ||||||
|  |                                 return internalNginx.reload(); | ||||||
|  |                             }); | ||||||
|  |                     }) | ||||||
|                     .then(() => { |                     .then(() => { | ||||||
|                         // Add to audit log |                         // Add to audit log | ||||||
|                         row.meta = internalHost.cleanMeta(row.meta); |                         row.meta = internalHost.cleanMeta(row.meta); | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ const internalSsl = { | |||||||
|     interval_processing: false, |     interval_processing: false, | ||||||
|  |  | ||||||
|     initTimer: () => { |     initTimer: () => { | ||||||
|  |         logger.info('Let\'s Encrypt Renewal Timer initialized'); | ||||||
|         internalSsl.interval = setInterval(internalSsl.processExpiringHosts, internalSsl.interval_timeout); |         internalSsl.interval = setInterval(internalSsl.processExpiringHosts, internalSsl.interval_timeout); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
| @@ -51,7 +52,7 @@ const internalSsl = { | |||||||
|      */ |      */ | ||||||
|     hasValidSslCerts: (host_type, host) => { |     hasValidSslCerts: (host_type, host) => { | ||||||
|         host_type   = host_type.replace(new RegExp('-', 'g'), '_'); |         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'); |         return fs.existsSync(le_path + '/fullchain.pem') && fs.existsSync(le_path + '/privkey.pem'); | ||||||
|     }, |     }, | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ | |||||||
| const _                = require('lodash'); | const _                = require('lodash'); | ||||||
| const error            = require('../lib/error'); | const error            = require('../lib/error'); | ||||||
| const streamModel      = require('../models/stream'); | const streamModel      = require('../models/stream'); | ||||||
|  | const internalNginx    = require('./nginx'); | ||||||
| const internalAuditLog = require('./audit-log'); | const internalAuditLog = require('./audit-log'); | ||||||
|  |  | ||||||
| function omissions () { | function omissions () { | ||||||
| @@ -31,6 +32,13 @@ const internalStream = { | |||||||
|                     .omit(omissions()) |                     .omit(omissions()) | ||||||
|                     .insertAndFetch(data); |                     .insertAndFetch(data); | ||||||
|             }) |             }) | ||||||
|  |             .then(row => { | ||||||
|  |                 // Configure nginx | ||||||
|  |                 return internalNginx.configure(streamModel, 'stream', row) | ||||||
|  |                     .then(() => { | ||||||
|  |                         return internalStream.get(access, {id: row.id, expand: ['owner']}); | ||||||
|  |                     }); | ||||||
|  |             }) | ||||||
|             .then(row => { |             .then(row => { | ||||||
|                 // Add to audit log |                 // Add to audit log | ||||||
|                 return internalAuditLog.add(access, { |                 return internalAuditLog.add(access, { | ||||||
| @@ -40,7 +48,7 @@ const internalStream = { | |||||||
|                     meta:        data |                     meta:        data | ||||||
|                 }) |                 }) | ||||||
|                     .then(() => { |                     .then(() => { | ||||||
|                         return _.omit(row, omissions()); |                         return row; | ||||||
|                     }); |                     }); | ||||||
|             }); |             }); | ||||||
|     }, |     }, | ||||||
| @@ -153,6 +161,13 @@ const internalStream = { | |||||||
|                     .patch({ |                     .patch({ | ||||||
|                         is_deleted: 1 |                         is_deleted: 1 | ||||||
|                     }) |                     }) | ||||||
|  |                     .then(() => { | ||||||
|  |                         // Delete Nginx Config | ||||||
|  |                         return internalNginx.deleteConfig('stream', row) | ||||||
|  |                             .then(() => { | ||||||
|  |                                 return internalNginx.reload(); | ||||||
|  |                             }); | ||||||
|  |                     }) | ||||||
|                     .then(() => { |                     .then(() => { | ||||||
|                         // Add to audit log |                         // Add to audit log | ||||||
|                         return internalAuditLog.add(access, { |                         return internalAuditLog.add(access, { | ||||||
|   | |||||||
| @@ -70,7 +70,8 @@ const internalUser = { | |||||||
|                         redirection_hosts: 'manage', |                         redirection_hosts: 'manage', | ||||||
|                         dead_hosts:        'manage', |                         dead_hosts:        'manage', | ||||||
|                         streams:           'manage', |                         streams:           'manage', | ||||||
|                         access_lists:      'manage' |                         access_lists:      'manage', | ||||||
|  |                         certificates:      'manage' | ||||||
|                     }) |                     }) | ||||||
|                     .then(() => { |                     .then(() => { | ||||||
|                         return internalUser.get(access, {id: user.id, expand: ['permissions']}); |                         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_redirection_hosts: permissions.redirection_hosts, | ||||||
|                                         permission_dead_hosts:        permissions.dead_hosts, |                                         permission_dead_hosts:        permissions.dead_hosts, | ||||||
|                                         permission_streams:           permissions.streams, |                                         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('dead_hosts').notNull(); | ||||||
|                 table.string('streams').notNull(); |                 table.string('streams').notNull(); | ||||||
|                 table.string('access_lists').notNull(); |                 table.string('access_lists').notNull(); | ||||||
|  |                 table.string('certificates').notNull(); | ||||||
|                 table.unique('user_id'); |                 table.unique('user_id'); | ||||||
|             }); |             }); | ||||||
|         }) |         }) | ||||||
| @@ -147,6 +148,20 @@ exports.up = function (knex/*, Promise*/) { | |||||||
|         .then(() => { |         .then(() => { | ||||||
|             logger.info('[' + migrate_name + '] access_list Table created'); |             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 => { |             return knex.schema.createTable('access_list_auth', table => { | ||||||
|                 table.increments().primary(); |                 table.increments().primary(); | ||||||
|                 table.dateTime('created_on').notNull(); |                 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/dead-hosts', require('./nginx/dead_hosts')); | ||||||
| router.use('/nginx/streams', require('./nginx/streams')); | router.use('/nginx/streams', require('./nginx/streams')); | ||||||
| router.use('/nginx/access-lists', require('./nginx/access_lists')); | router.use('/nginx/access-lists', require('./nginx/access_lists')); | ||||||
|  | router.use('/nginx/certificates', require('./nginx/certificates')); | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * API 404 for all other routes |  * 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": { |           "streams": { | ||||||
|             "type": "string", |             "type": "string", | ||||||
|             "pattern": "^(hidden|view|manage)$" |             "pattern": "^(hidden|view|manage)$" | ||||||
|  |           }, | ||||||
|  |           "certificates": { | ||||||
|  |             "type": "string", | ||||||
|  |             "pattern": "^(hidden|view|manage)$" | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       }, |       }, | ||||||
|   | |||||||
| @@ -91,7 +91,8 @@ module.exports = function () { | |||||||
|                                                 redirection_hosts: 'manage', |                                                 redirection_hosts: 'manage', | ||||||
|                                                 dead_hosts:        'manage', |                                                 dead_hosts:        'manage', | ||||||
|                                                 streams:           'manage', |                                                 streams:           'manage', | ||||||
|                                                 access_lists:      'manage' |                                                 access_lists:      'manage', | ||||||
|  |                                                 certificates:      'manage' | ||||||
|                                             }); |                                             }); | ||||||
|                                     }); |                                     }); | ||||||
|                             }); |                             }); | ||||||
|   | |||||||
| @@ -1,19 +1,23 @@ | |||||||
| # <%- hostname %> | # {{ domain_names | join: ", " }} | ||||||
| server { | server { | ||||||
|   listen 80; |   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 ssl_enabled == 1 or ssl_enabled == true -%} | ||||||
|  |   {%- if ssl_provider == "letsencrypt" %} | ||||||
|   access_log /config/logs/<%- hostname %>.log proxy; |   # Let's Encrypt SSL | ||||||
|  |   include conf.d/include/letsencrypt-acme-challenge.conf; | ||||||
| <% if (typeof ssl !== 'undefined' && ssl) { -%> |  | ||||||
|   include conf.d/include/ssl-ciphers.conf; |   include conf.d/include/ssl-ciphers.conf; | ||||||
|   ssl_certificate /etc/letsencrypt/live/<%- hostname %>/fullchain.pem; |   ssl_certificate /etc/letsencrypt/live/proxy_host-{{ id }}/fullchain.pem; | ||||||
|   ssl_certificate_key /etc/letsencrypt/live/<%- hostname %>/privkey.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; |   return 404; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,11 +1,10 @@ | |||||||
| # Letsencrypt Verification Temporary Host: <%- hostname %> | # Letsencrypt Verification Temporary Host: {{ domain_names | join: ", " }} | ||||||
| server { | server { | ||||||
|   listen 80; |   listen 80; | ||||||
|   server_name <%- hostname %>; |   server_name {{ domain_names | join: " " }}; | ||||||
|  |   access_log /data/logs/letsencrypt.log proxy; | ||||||
|   access_log /config/logs/letsencrypt.log proxy; |  | ||||||
|  |  | ||||||
|   location / { |   location / { | ||||||
|     root /config/letsencrypt-acme-challenge; |     root /data/letsencrypt-acme-challenge; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,33 +1,51 @@ | |||||||
| # <%- hostname %> | # {{ domain_names | join: ", " }} | ||||||
| server { | server { | ||||||
|   listen 80; |   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 %>; |   {%- if ssl_enabled == 1 or ssl_enabled == true -%} | ||||||
|   set $port   <%- forward_port %>; |   {%- if ssl_provider == "letsencrypt" %} | ||||||
|  |   # Let's Encrypt SSL | ||||||
|   <%- 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) { -%> |  | ||||||
|   include conf.d/include/letsencrypt-acme-challenge.conf; |   include conf.d/include/letsencrypt-acme-challenge.conf; | ||||||
|   include conf.d/include/ssl-ciphers.conf; |   include conf.d/include/ssl-ciphers.conf; | ||||||
|   ssl_certificate /etc/letsencrypt/live/<%- hostname %>/fullchain.pem; |   ssl_certificate /etc/letsencrypt/live/proxy_host-{{ id }}/fullchain.pem; | ||||||
|   ssl_certificate_key /etc/letsencrypt/live/<%- hostname %>/privkey.pem; |   ssl_certificate_key /etc/letsencrypt/live/proxy_host-{{ id }}/privkey.pem; | ||||||
| <% } -%> |   {%- endif -%} | ||||||
|  |   {%- endif %} | ||||||
|  |  | ||||||
| <%- typeof advanced !== 'undefined' && advanced ? advanced : '' %> |   # TODO: Advanced config options | ||||||
|  |  | ||||||
|   location / { |   location / { | ||||||
|     <% if (typeof access_list_id !== 'undefined' && access_list_id) { -%> |     {%- if access_list_id > 0 -%} | ||||||
|  |     # Access List | ||||||
|     auth_basic            "Authorization required"; |     auth_basic            "Authorization required"; | ||||||
|     auth_basic_user_file  /config/access/<%- access_list_id %>; |     auth_basic_user_file  /config/access/{{ access_list_id }}; | ||||||
|     <% } -%> |     {%- endif %} | ||||||
|     <%- typeof force_ssl !== 'undefined' && force_ssl ? 'include conf.d/include/force-ssl.conf;' : '' %> |  | ||||||
|  |     {%- 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; |     include conf.d/include/proxy.conf; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,22 +1,34 @@ | |||||||
| # <%- hostname %> | # {{ domain_names | join: ", " }} | ||||||
| server { | server { | ||||||
|   listen 80; |   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; |   {%- if ssl_enabled == 1 or ssl_enabled == true -%} | ||||||
|  |   {%- if ssl_provider == "letsencrypt" %} | ||||||
|   <%- typeof asset_caching !== 'undefined' && asset_caching ? 'include conf.d/include/assets.conf;' : '' %> |   # Let's Encrypt SSL | ||||||
|   <%- typeof block_exploits !== 'undefined' && block_exploits ? 'include conf.d/include/block-exploits.conf;' : '' %> |   include conf.d/include/letsencrypt-acme-challenge.conf; | ||||||
|  |  | ||||||
| <% if (typeof ssl !== 'undefined' && ssl) { -%> |  | ||||||
|   include conf.d/include/ssl-ciphers.conf; |   include conf.d/include/ssl-ciphers.conf; | ||||||
|   ssl_certificate /etc/letsencrypt/live/<%- hostname %>/fullchain.pem; |   ssl_certificate /etc/letsencrypt/live/proxy_host-{{ id }}/fullchain.pem; | ||||||
|   ssl_certificate_key /etc/letsencrypt/live/<%- hostname %>/privkey.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() %> | # {{ incoming_port }} TCP: {{ tcp_forwarding }} UDP: {{ udp_forwarding }} | ||||||
| <% |  | ||||||
| protocols.forEach(function (protocol) { | {% if tcp_forwarding == 1 or tcp_forwarding == true -%} | ||||||
| %> |  | ||||||
| server { | server { | ||||||
|     listen <%- incoming_port %> <%- protocol === 'tcp' ? '' : protocol %>; |     listen {{ incoming_port }}; | ||||||
|     proxy_pass <%- forward_server %>:<%- forward_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); |                 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: { |     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 |      * Audit Log | ||||||
|      */ |      */ | ||||||
|   | |||||||
| @@ -46,7 +46,7 @@ module.exports = Mn.View.extend({ | |||||||
|             }, |             }, | ||||||
|  |  | ||||||
|             columns: view.columns |             columns: view.columns | ||||||
|         } |         }; | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     onRender: function () { |     onRender: function () { | ||||||
|   | |||||||
| @@ -4,8 +4,6 @@ const Mn       = require('backbone.marionette'); | |||||||
| const App      = require('../../main'); | const App      = require('../../main'); | ||||||
| const template = require('./delete.ejs'); | const template = require('./delete.ejs'); | ||||||
|  |  | ||||||
| require('jquery-serializejson'); |  | ||||||
|  |  | ||||||
| module.exports = Mn.View.extend({ | module.exports = Mn.View.extend({ | ||||||
|     template:  template, |     template:  template, | ||||||
|     className: 'modal-dialog', |     className: 'modal-dialog', | ||||||
| @@ -22,7 +20,7 @@ module.exports = Mn.View.extend({ | |||||||
|         'click @ui.save': function (e) { |         'click @ui.save': function (e) { | ||||||
|             e.preventDefault(); |             e.preventDefault(); | ||||||
|  |  | ||||||
|             App.Api.Nginx.ProxyHosts.delete(this.model.get('id')) |             App.Api.Nginx.AccessLists.delete(this.model.get('id')) | ||||||
|                 .then(() => { |                 .then(() => { | ||||||
|                     App.Controller.showNginxAccess(); |                     App.Controller.showNginxAccess(); | ||||||
|                     App.UI.closeModal(); |                     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 App      = require('../../main'); | ||||||
| const template = require('./delete.ejs'); | const template = require('./delete.ejs'); | ||||||
|  |  | ||||||
| require('jquery-serializejson'); |  | ||||||
|  |  | ||||||
| module.exports = Mn.View.extend({ | module.exports = Mn.View.extend({ | ||||||
|     template:  template, |     template:  template, | ||||||
|     className: 'modal-dialog', |     className: 'modal-dialog', | ||||||
|   | |||||||
| @@ -19,6 +19,17 @@ | |||||||
| <td> | <td> | ||||||
|     <div><%- ssl_enabled && ssl_provider ? i18n('ssl', ssl_provider) : i18n('ssl', 'none') %></div> |     <div><%- ssl_enabled && ssl_provider ? i18n('ssl', ssl_provider) : i18n('ssl', 'none') %></div> | ||||||
| </td> | </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) { %> | <% if (canManage) { %> | ||||||
| <td class="text-center"> | <td class="text-center"> | ||||||
|     <div class="item-action dropdown"> |     <div class="item-action dropdown"> | ||||||
|   | |||||||
| @@ -26,7 +26,15 @@ module.exports = Mn.View.extend({ | |||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     templateContext: { |     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 () { |     initialize: function () { | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
|     <th width="30"> </th> |     <th width="30"> </th> | ||||||
|     <th><%- i18n('str', 'source') %></th> |     <th><%- i18n('str', 'source') %></th> | ||||||
|     <th><%- i18n('str', 'ssl') %></th> |     <th><%- i18n('str', 'ssl') %></th> | ||||||
|  |     <th><%- i18n('str', 'status') %></th> | ||||||
|     <% if (canManage) { %> |     <% if (canManage) { %> | ||||||
|     <th> </th> |     <th> </th> | ||||||
|     <% } %> |     <% } %> | ||||||
|   | |||||||
| @@ -4,8 +4,6 @@ const Mn       = require('backbone.marionette'); | |||||||
| const App      = require('../../main'); | const App      = require('../../main'); | ||||||
| const template = require('./delete.ejs'); | const template = require('./delete.ejs'); | ||||||
|  |  | ||||||
| require('jquery-serializejson'); |  | ||||||
|  |  | ||||||
| module.exports = Mn.View.extend({ | module.exports = Mn.View.extend({ | ||||||
|     template:  template, |     template:  template, | ||||||
|     className: 'modal-dialog', |     className: 'modal-dialog', | ||||||
|   | |||||||
| @@ -25,6 +25,17 @@ | |||||||
| <td> | <td> | ||||||
|     <div><%- access_list_id ? access_list.name : i18n('str', 'public') %></div> |     <div><%- access_list_id ? access_list.name : i18n('str', 'public') %></div> | ||||||
| </td> | </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) { %> | <% if (canManage) { %> | ||||||
| <td class="text-center"> | <td class="text-center"> | ||||||
|     <div class="item-action dropdown"> |     <div class="item-action dropdown"> | ||||||
|   | |||||||
| @@ -26,7 +26,15 @@ module.exports = Mn.View.extend({ | |||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     templateContext: { |     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 () { |     initialize: function () { | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ | |||||||
|     <th><%- i18n('str', 'destination') %></th> |     <th><%- i18n('str', 'destination') %></th> | ||||||
|     <th><%- i18n('str', 'ssl') %></th> |     <th><%- i18n('str', 'ssl') %></th> | ||||||
|     <th><%- i18n('str', 'access') %></th> |     <th><%- i18n('str', 'access') %></th> | ||||||
|  |     <th><%- i18n('str', 'status') %></th> | ||||||
|     <% if (canManage) { %> |     <% if (canManage) { %> | ||||||
|     <th> </th> |     <th> </th> | ||||||
|     <% } %> |     <% } %> | ||||||
|   | |||||||
| @@ -4,8 +4,6 @@ const Mn       = require('backbone.marionette'); | |||||||
| const App      = require('../../main'); | const App      = require('../../main'); | ||||||
| const template = require('./delete.ejs'); | const template = require('./delete.ejs'); | ||||||
|  |  | ||||||
| require('jquery-serializejson'); |  | ||||||
|  |  | ||||||
| module.exports = Mn.View.extend({ | module.exports = Mn.View.extend({ | ||||||
|     template:  template, |     template:  template, | ||||||
|     className: 'modal-dialog', |     className: 'modal-dialog', | ||||||
|   | |||||||
| @@ -22,6 +22,17 @@ | |||||||
| <td> | <td> | ||||||
|     <div><%- ssl_enabled && ssl_provider ? i18n('ssl', ssl_provider) : i18n('ssl', 'none') %></div> |     <div><%- ssl_enabled && ssl_provider ? i18n('ssl', ssl_provider) : i18n('ssl', 'none') %></div> | ||||||
| </td> | </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) { %> | <% if (canManage) { %> | ||||||
| <td class="text-center"> | <td class="text-center"> | ||||||
|     <div class="item-action dropdown"> |     <div class="item-action dropdown"> | ||||||
|   | |||||||
| @@ -26,7 +26,15 @@ module.exports = Mn.View.extend({ | |||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     templateContext: { |     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 () { |     initialize: function () { | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ | |||||||
|     <th><%- i18n('str', 'source') %></th> |     <th><%- i18n('str', 'source') %></th> | ||||||
|     <th><%- i18n('str', 'destination') %></th> |     <th><%- i18n('str', 'destination') %></th> | ||||||
|     <th><%- i18n('str', 'ssl') %></th> |     <th><%- i18n('str', 'ssl') %></th> | ||||||
|  |     <th><%- i18n('str', 'status') %></th> | ||||||
|     <% if (canManage) { %> |     <% if (canManage) { %> | ||||||
|         <th> </th> |         <th> </th> | ||||||
|     <% } %> |     <% } %> | ||||||
|   | |||||||
| @@ -4,8 +4,6 @@ const Mn       = require('backbone.marionette'); | |||||||
| const App      = require('../../main'); | const App      = require('../../main'); | ||||||
| const template = require('./delete.ejs'); | const template = require('./delete.ejs'); | ||||||
|  |  | ||||||
| require('jquery-serializejson'); |  | ||||||
|  |  | ||||||
| module.exports = Mn.View.extend({ | module.exports = Mn.View.extend({ | ||||||
|     template:  template, |     template:  template, | ||||||
|     className: 'modal-dialog', |     className: 'modal-dialog', | ||||||
|   | |||||||
| @@ -24,6 +24,17 @@ | |||||||
|         <% } %> |         <% } %> | ||||||
|     </div> |     </div> | ||||||
| </td> | </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) { %> | <% if (canManage) { %> | ||||||
| <td class="text-center"> | <td class="text-center"> | ||||||
|     <div class="item-action dropdown"> |     <div class="item-action dropdown"> | ||||||
|   | |||||||
| @@ -26,7 +26,15 @@ module.exports = Mn.View.extend({ | |||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     templateContext: { |     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 () { |     initialize: function () { | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ | |||||||
|     <th><%- i18n('streams', 'incoming-port') %></th> |     <th><%- i18n('streams', 'incoming-port') %></th> | ||||||
|     <th><%- i18n('str', 'destination') %></th> |     <th><%- i18n('str', 'destination') %></th> | ||||||
|     <th><%- i18n('streams', 'protocol') %></th> |     <th><%- i18n('streams', 'protocol') %></th> | ||||||
|  |     <th><%- i18n('str', 'status') %></th> | ||||||
|     <% if (canManage) { %> |     <% if (canManage) { %> | ||||||
|     <th> </th> |     <th> </th> | ||||||
|     <% } %> |     <% } %> | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ module.exports = Mn.AppRouter.extend({ | |||||||
|         'nginx/404':          'showNginxDead', |         'nginx/404':          'showNginxDead', | ||||||
|         'nginx/stream':       'showNginxStream', |         'nginx/stream':       'showNginxStream', | ||||||
|         'nginx/access':       'showNginxAccess', |         'nginx/access':       'showNginxAccess', | ||||||
|  |         'nginx/certificates': 'showNginxCertificates', | ||||||
|         'audit-log':          'showAuditLog', |         'audit-log':          'showAuditLog', | ||||||
|         '*default':           'showDashboard' |         '*default':           'showDashboard' | ||||||
|     }, |     }, | ||||||
|   | |||||||
| @@ -27,7 +27,12 @@ | |||||||
|                 </li> |                 </li> | ||||||
|                 <% if (canShow('access_lists')) { %> |                 <% if (canShow('access_lists')) { %> | ||||||
|                 <li class="nav-item"> |                 <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> |                 </li> | ||||||
|                 <% } %> |                 <% } %> | ||||||
|                 <% if (isAdmin()) { %> |                 <% if (isAdmin()) { %> | ||||||
|   | |||||||
| @@ -31,7 +31,7 @@ | |||||||
|                 </div> |                 </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) { |                 list.map(function(item) { | ||||||
|                     var perm = item.replace('-', '_'); |                     var perm = item.replace('-', '_'); | ||||||
|                     %> |                     %> | ||||||
|   | |||||||
| @@ -35,7 +35,8 @@ module.exports = Mn.View.extend({ | |||||||
|                     dead_hosts:        'manage', |                     dead_hosts:        'manage', | ||||||
|                     proxy_hosts:       'manage', |                     proxy_hosts:       'manage', | ||||||
|                     redirection_hosts: 'manage', |                     redirection_hosts: 'manage', | ||||||
|                     streams:           'manage' |                     streams:           'manage', | ||||||
|  |                     certificates:      'manage' | ||||||
|                 }); |                 }); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -23,7 +23,11 @@ | |||||||
|       "public": "Public", |       "public": "Public", | ||||||
|       "edit": "Edit", |       "edit": "Edit", | ||||||
|       "delete": "Delete", |       "delete": "Delete", | ||||||
|       "logs": "Logs" |       "logs": "Logs", | ||||||
|  |       "status": "Status", | ||||||
|  |       "online": "Online", | ||||||
|  |       "offline": "Offline", | ||||||
|  |       "unknown": "Unknown" | ||||||
|     }, |     }, | ||||||
|     "login": { |     "login": { | ||||||
|       "title": "Login to your account" |       "title": "Login to your account" | ||||||
| @@ -127,6 +131,15 @@ | |||||||
|       "help-title": "What is a Stream?", |       "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." |       "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": { |     "access-lists": { | ||||||
|       "title": "Access Lists", |       "title": "Access Lists", | ||||||
|       "empty": "There are no 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