mirror of
				https://github.com/NginxProxyManager/nginx-proxy-manager.git
				synced 2025-10-31 07:43:33 +00:00 
			
		
		
		
	Form design for proxy hosts, audit log base
This commit is contained in:
		
							
								
								
									
										52
									
								
								src/backend/internal/audit-log.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/backend/internal/audit-log.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const auditLogModel = require('../models/audit-log'); | ||||||
|  |  | ||||||
|  | const internalAuditLog = { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Internal use only | ||||||
|  |      * | ||||||
|  |      * @param   {Object}  data | ||||||
|  |      * @returns {Promise} | ||||||
|  |      */ | ||||||
|  |     create: data => { | ||||||
|  |         // TODO | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * All logs | ||||||
|  |      * | ||||||
|  |      * @param   {Access}  access | ||||||
|  |      * @param   {Array}   [expand] | ||||||
|  |      * @param   {String}  [search_query] | ||||||
|  |      * @returns {Promise} | ||||||
|  |      */ | ||||||
|  |     getAll: (access, expand, search_query) => { | ||||||
|  |         return access.can('auditlog:list') | ||||||
|  |             .then(() => { | ||||||
|  |                 let query = auditLogModel | ||||||
|  |                     .query() | ||||||
|  |                     .orderBy('created_on', 'DESC') | ||||||
|  |                     .limit(100); | ||||||
|  |  | ||||||
|  |                 // Query is used for searching | ||||||
|  |                 if (typeof search_query === 'string') { | ||||||
|  |                     /* | ||||||
|  |                     query.where(function () { | ||||||
|  |                         this.where('name', 'like', '%' + search_query + '%') | ||||||
|  |                             .orWhere('email', 'like', '%' + search_query + '%'); | ||||||
|  |                     }); | ||||||
|  |                     */ | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (typeof expand !== 'undefined' && expand !== null) { | ||||||
|  |                     query.eager('[' + expand.join(', ') + ']'); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 return query; | ||||||
|  |             }); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | module.exports = internalAuditLog; | ||||||
							
								
								
									
										7
									
								
								src/backend/lib/access/auditlog-list.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/backend/lib/access/auditlog-list.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | { | ||||||
|  |   "anyOf": [ | ||||||
|  |     { | ||||||
|  |       "$ref": "roles#/definitions/admin" | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
| @@ -157,6 +157,19 @@ exports.up = function (knex/*, Promise*/) { | |||||||
|         }) |         }) | ||||||
|         .then(() => { |         .then(() => { | ||||||
|             logger.info('[' + migrate_name + '] access_list_auth Table created'); |             logger.info('[' + migrate_name + '] access_list_auth Table created'); | ||||||
|  |  | ||||||
|  |             return knex.schema.createTable('audit_log', table => { | ||||||
|  |                 table.increments().primary(); | ||||||
|  |                 table.dateTime('created_on').notNull(); | ||||||
|  |                 table.dateTime('modified_on').notNull(); | ||||||
|  |                 table.integer('user_id').notNull().unsigned(); | ||||||
|  |                 // TODO | ||||||
|  |                 table.string('action').notNull(); | ||||||
|  |                 table.json('meta').notNull(); | ||||||
|  |             }); | ||||||
|  |         }) | ||||||
|  |         .then(() => { | ||||||
|  |             logger.info('[' + migrate_name + '] audit_log Table created'); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
| }; | }; | ||||||
|   | |||||||
							
								
								
									
										30
									
								
								src/backend/models/audit-log.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/backend/models/audit-log.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | // Objection Docs: | ||||||
|  | // http://vincit.github.io/objection.js/ | ||||||
|  |  | ||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const db    = require('../db'); | ||||||
|  | const Model = require('objection').Model; | ||||||
|  |  | ||||||
|  | Model.knex(db); | ||||||
|  |  | ||||||
|  | class AuditLog 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 'AuditLog'; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     static get tableName () { | ||||||
|  |         return 'audit_log'; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = AuditLog; | ||||||
							
								
								
									
										54
									
								
								src/backend/routes/api/audit-log.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/backend/routes/api/audit-log.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const express          = require('express'); | ||||||
|  | const validator        = require('../../lib/validator'); | ||||||
|  | const jwtdecode        = require('../../lib/express/jwt-decode'); | ||||||
|  | const internalAuditLog = require('../../internal/audit-log'); | ||||||
|  |  | ||||||
|  | let router = express.Router({ | ||||||
|  |     caseSensitive: true, | ||||||
|  |     strict:        true, | ||||||
|  |     mergeParams:   true | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * /api/audit-log | ||||||
|  |  */ | ||||||
|  | router | ||||||
|  |     .route('/') | ||||||
|  |     .options((req, res) => { | ||||||
|  |         res.sendStatus(204); | ||||||
|  |     }) | ||||||
|  |     .all(jwtdecode()) | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * GET /api/audit-log | ||||||
|  |      * | ||||||
|  |      * Retrieve all logs | ||||||
|  |      */ | ||||||
|  |     .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 internalAuditLog.getAll(res.locals.access, data.expand, data.query); | ||||||
|  |             }) | ||||||
|  |             .then(rows => { | ||||||
|  |                 res.status(200) | ||||||
|  |                     .send(rows); | ||||||
|  |             }) | ||||||
|  |             .catch(next); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  | module.exports = router; | ||||||
| @@ -29,6 +29,7 @@ router.get('/', (req, res/*, next*/) => { | |||||||
|  |  | ||||||
| router.use('/tokens', require('./tokens')); | router.use('/tokens', require('./tokens')); | ||||||
| router.use('/users', require('./users')); | router.use('/users', require('./users')); | ||||||
|  | router.use('/audit-log', require('./audit-log')); | ||||||
| router.use('/reports', require('./reports')); | router.use('/reports', require('./reports')); | ||||||
| router.use('/nginx/proxy-hosts', require('./nginx/proxy_hosts')); | router.use('/nginx/proxy-hosts', require('./nginx/proxy_hosts')); | ||||||
| router.use('/nginx/redirection-hosts', require('./nginx/redirection_hosts')); | router.use('/nginx/redirection-hosts', require('./nginx/redirection_hosts')); | ||||||
|   | |||||||
| @@ -257,6 +257,13 @@ module.exports = { | |||||||
|              */ |              */ | ||||||
|             getAll: function (expand, query) { |             getAll: function (expand, query) { | ||||||
|                 return getAllObjects('nginx/proxy-hosts', expand, query); |                 return getAllObjects('nginx/proxy-hosts', expand, query); | ||||||
|  |             }, | ||||||
|  |  | ||||||
|  |             /** | ||||||
|  |              * @param {Object}  data | ||||||
|  |              */ | ||||||
|  |             create: function (data) { | ||||||
|  |                 return fetch('post', 'nginx/proxy-hosts', data); | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|  |  | ||||||
| @@ -305,6 +312,17 @@ module.exports = { | |||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|  |     AuditLog: { | ||||||
|  |         /** | ||||||
|  |          * @param   {Array}    [expand] | ||||||
|  |          * @param   {String}   [query] | ||||||
|  |          * @returns {Promise} | ||||||
|  |          */ | ||||||
|  |         getAll: function (expand, query) { | ||||||
|  |             return getAllObjects('audit-log', expand, query); | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|     Reports: { |     Reports: { | ||||||
|  |  | ||||||
|         /** |         /** | ||||||
|   | |||||||
							
								
								
									
										32
									
								
								src/frontend/js/app/audit-log/list/item.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/frontend/js/app/audit-log/list/item.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | <td class="text-center"> | ||||||
|  |     <div class="avatar d-block" style="background-image: url(<%- avatar || '/images/default-avatar.jpg' %>)"> | ||||||
|  |         <span class="avatar-status <%- is_disabled ? 'bg-red' : 'bg-green' %>"></span> | ||||||
|  |     </div> | ||||||
|  | </td> | ||||||
|  | <td> | ||||||
|  |     <div><%- name %></div> | ||||||
|  |     <div class="small text-muted"> | ||||||
|  |         Created: <%- formatDbDate(created_on, 'Do MMMM YYYY') %> | ||||||
|  |     </div> | ||||||
|  | </td> | ||||||
|  | <td> | ||||||
|  |     <div><%- email %></div> | ||||||
|  | </td> | ||||||
|  | <td> | ||||||
|  |     <div><%- roles.join(', ') %></div> | ||||||
|  | </td> | ||||||
|  | <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-user dropdown-item"><i class="dropdown-icon fe fe-edit"></i> Edit Details</a> | ||||||
|  |             <a href="#" class="edit-permissions dropdown-item"><i class="dropdown-icon fe fe-shield"></i> Edit Permissions</a> | ||||||
|  |             <a href="#" class="set-password dropdown-item"><i class="dropdown-icon fe fe-lock"></i> Set Password</a> | ||||||
|  |             <% if (!isSelf()) { %> | ||||||
|  |             <a href="#" class="login dropdown-item"><i class="dropdown-icon fe fe-log-in"></i> Sign in as User</a> | ||||||
|  |             <div class="dropdown-divider"></div> | ||||||
|  |             <a href="#" class="delete-user dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> Delete User</a> | ||||||
|  |             <% } %> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  | </td> | ||||||
							
								
								
									
										72
									
								
								src/frontend/js/app/audit-log/list/item.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/frontend/js/app/audit-log/list/item.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const Mn         = require('backbone.marionette'); | ||||||
|  | const Controller = require('../../controller'); | ||||||
|  | const Api        = require('../../api'); | ||||||
|  | const Cache      = require('../../cache'); | ||||||
|  | const Tokens     = require('../../tokens'); | ||||||
|  | const template   = require('./item.ejs'); | ||||||
|  |  | ||||||
|  | module.exports = Mn.View.extend({ | ||||||
|  |     template: template, | ||||||
|  |     tagName:  'tr', | ||||||
|  |  | ||||||
|  |     ui: { | ||||||
|  |         edit:        'a.edit-user', | ||||||
|  |         permissions: 'a.edit-permissions', | ||||||
|  |         password:    'a.set-password', | ||||||
|  |         login:       'a.login', | ||||||
|  |         delete:      'a.delete-user' | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     events: { | ||||||
|  |         'click @ui.edit': function (e) { | ||||||
|  |             e.preventDefault(); | ||||||
|  |             Controller.showUserForm(this.model); | ||||||
|  |         }, | ||||||
|  |  | ||||||
|  |         'click @ui.permissions': function (e) { | ||||||
|  |             e.preventDefault(); | ||||||
|  |             Controller.showUserPermissions(this.model); | ||||||
|  |         }, | ||||||
|  |  | ||||||
|  |         'click @ui.password': function (e) { | ||||||
|  |             e.preventDefault(); | ||||||
|  |             Controller.showUserPasswordForm(this.model); | ||||||
|  |         }, | ||||||
|  |  | ||||||
|  |         'click @ui.delete': function (e) { | ||||||
|  |             e.preventDefault(); | ||||||
|  |             Controller.showUserDeleteConfirm(this.model); | ||||||
|  |         }, | ||||||
|  |  | ||||||
|  |         'click @ui.login': function (e) { | ||||||
|  |             e.preventDefault(); | ||||||
|  |  | ||||||
|  |             if (Cache.User.get('id') !== this.model.get('id')) { | ||||||
|  |                 this.ui.login.prop('disabled', true).addClass('btn-disabled'); | ||||||
|  |  | ||||||
|  |                 Api.Users.loginAs(this.model.get('id')) | ||||||
|  |                     .then(res => { | ||||||
|  |                         Tokens.addToken(res.token, res.user.nickname || res.user.name); | ||||||
|  |                         window.location = '/'; | ||||||
|  |                         window.location.reload(); | ||||||
|  |                     }) | ||||||
|  |                     .catch(err => { | ||||||
|  |                         alert(err.message); | ||||||
|  |                         this.ui.login.prop('disabled', false).removeClass('btn-disabled'); | ||||||
|  |                     }); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     templateContext: { | ||||||
|  |         isSelf: function () { | ||||||
|  |             return Cache.User.get('id') === this.id; | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     initialize: function () { | ||||||
|  |         this.listenTo(this.model, 'change', this.render); | ||||||
|  |     } | ||||||
|  | }); | ||||||
							
								
								
									
										10
									
								
								src/frontend/js/app/audit-log/list/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/frontend/js/app/audit-log/list/main.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | <thead> | ||||||
|  | <th width="30"> </th> | ||||||
|  | <th>Name</th> | ||||||
|  | <th>Email</th> | ||||||
|  | <th>Roles</th> | ||||||
|  | <th> </th> | ||||||
|  | </thead> | ||||||
|  | <tbody> | ||||||
|  | <!-- items --> | ||||||
|  | </tbody> | ||||||
							
								
								
									
										29
									
								
								src/frontend/js/app/audit-log/list/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/frontend/js/app/audit-log/list/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const Mn       = require('backbone.marionette'); | ||||||
|  | const ItemView = require('./item'); | ||||||
|  | const template = require('./main.ejs'); | ||||||
|  |  | ||||||
|  | const TableBody = Mn.CollectionView.extend({ | ||||||
|  |     tagName:   'tbody', | ||||||
|  |     childView: ItemView | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | module.exports = Mn.View.extend({ | ||||||
|  |     tagName:   'table', | ||||||
|  |     className: 'table table-hover table-outline table-vcenter text-nowrap card-table', | ||||||
|  |     template:  template, | ||||||
|  |  | ||||||
|  |     regions: { | ||||||
|  |         body: { | ||||||
|  |             el:             'tbody', | ||||||
|  |             replaceElement: true | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     onRender: function () { | ||||||
|  |         this.showChildView('body', new TableBody({ | ||||||
|  |             collection: this.collection | ||||||
|  |         })); | ||||||
|  |     } | ||||||
|  | }); | ||||||
							
								
								
									
										14
									
								
								src/frontend/js/app/audit-log/main.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/frontend/js/app/audit-log/main.ejs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | <div class="card"> | ||||||
|  |     <div class="card-header"> | ||||||
|  |         <h3 class="card-title">Audit Log</h3> | ||||||
|  |     </div> | ||||||
|  |     <div class="card-body no-padding min-100"> | ||||||
|  |         <div class="dimmer active"> | ||||||
|  |             <div class="loader"></div> | ||||||
|  |             <div class="dimmer-content list-region"> | ||||||
|  |                 <!-- List Region --> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
							
								
								
									
										56
									
								
								src/frontend/js/app/audit-log/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/frontend/js/app/audit-log/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const Mn            = require('backbone.marionette'); | ||||||
|  | const AuditLogModel = require('../../models/audit-log'); | ||||||
|  | const Api           = require('../api'); | ||||||
|  | const Controller    = require('../controller'); | ||||||
|  | const ListView      = require('./list/main'); | ||||||
|  | const template      = require('./main.ejs'); | ||||||
|  | const ErrorView     = require('../error/main'); | ||||||
|  | const EmptyView     = require('../empty/main'); | ||||||
|  |  | ||||||
|  | module.exports = Mn.View.extend({ | ||||||
|  |     id:       'audit-log', | ||||||
|  |     template: template, | ||||||
|  |  | ||||||
|  |     ui: { | ||||||
|  |         list_region: '.list-region', | ||||||
|  |         dimmer:      '.dimmer' | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     regions: { | ||||||
|  |         list_region: '@ui.list_region' | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     onRender: function () { | ||||||
|  |         let view = this; | ||||||
|  |  | ||||||
|  |         Api.AuditLog.getAll() | ||||||
|  |             .then(response => { | ||||||
|  |                 if (!view.isDestroyed() && response && response.length) { | ||||||
|  |                     view.showChildView('list_region', new ListView({ | ||||||
|  |                         collection: new AuditLogModel.Collection(response) | ||||||
|  |                     })); | ||||||
|  |                 } else { | ||||||
|  |                     view.showChildView('list_region', new EmptyView({ | ||||||
|  |                         title:    'There are no logs.', | ||||||
|  |                         subtitle: 'As soon as you or another user changes something, history of those events will show up here.' | ||||||
|  |                     })); | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|  |             .catch(err => { | ||||||
|  |                 view.showChildView('list_region', new ErrorView({ | ||||||
|  |                     code:    err.code, | ||||||
|  |                     message: err.message, | ||||||
|  |                     retry:   function () { | ||||||
|  |                         Controller.showAuditLog(); | ||||||
|  |                     } | ||||||
|  |                 })); | ||||||
|  |  | ||||||
|  |                 console.error(err); | ||||||
|  |             }) | ||||||
|  |             .then(() => { | ||||||
|  |                 view.ui.dimmer.removeClass('active'); | ||||||
|  |             }); | ||||||
|  |     } | ||||||
|  | }); | ||||||
| @@ -204,15 +204,18 @@ module.exports = { | |||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Dashboard |      * Audit Log | ||||||
|      */ |      */ | ||||||
|     showProfile: function () { |     showAuditLog: function () { | ||||||
|         let controller = this; |         let controller = this; | ||||||
|  |         if (Cache.User.isAdmin()) { | ||||||
|         require(['./main', './profile/main'], (App, View) => { |             require(['./main', './audit-log/main'], (App, View) => { | ||||||
|             controller.navigate('/profile'); |                 controller.navigate('/audit-log'); | ||||||
|                 App.UI.showAppContent(new View()); |                 App.UI.showAppContent(new View()); | ||||||
|             }); |             }); | ||||||
|  |         } else { | ||||||
|  |             this.showDashboard(); | ||||||
|  |         } | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|   | |||||||
| @@ -47,6 +47,11 @@ const App = Mn.Application.extend({ | |||||||
|                 this.UI.on('render', () => { |                 this.UI.on('render', () => { | ||||||
|                     new Router(options); |                     new Router(options); | ||||||
|                     Backbone.history.start({pushState: true}); |                     Backbone.history.start({pushState: true}); | ||||||
|  |  | ||||||
|  |                     // Ask the admin use to change their details | ||||||
|  |                     if (Cache.User.get('email') === 'admin@example.com') { | ||||||
|  |                         Controller.showUserForm(Cache.User); | ||||||
|  |                     } | ||||||
|                 }); |                 }); | ||||||
|  |  | ||||||
|                 this.getRegion().show(this.UI); |                 this.getRegion().show(this.UI); | ||||||
|   | |||||||
| @@ -3,13 +3,20 @@ | |||||||
|         <h5 class="modal-title"><% if (typeof id !== 'undefined') { %>Edit<% } else { %>New<% } %> Proxy Host</h5> |         <h5 class="modal-title"><% if (typeof id !== 'undefined') { %>Edit<% } else { %>New<% } %> Proxy Host</h5> | ||||||
|         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> |         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> | ||||||
|     </div> |     </div> | ||||||
|     <div class="modal-body"> |     <div class="modal-body has-tabs"> | ||||||
|         <form> |         <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> 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> SSL</a></li> | ||||||
|  |             </ul> | ||||||
|  |             <div class="tab-content"> | ||||||
|  |                 <!-- Details --> | ||||||
|  |                 <div role="tabpanel" class="tab-pane active" id="details"> | ||||||
|                     <div class="row"> |                     <div class="row"> | ||||||
|                         <div class="col-sm-12 col-md-12"> |                         <div class="col-sm-12 col-md-12"> | ||||||
|                             <div class="form-group"> |                             <div class="form-group"> | ||||||
|                                 <label class="form-label">Domain Name <span class="form-required">*</span></label> |                                 <label class="form-label">Domain Name <span class="form-required">*</span></label> | ||||||
|                         <input name="domain_name" type="text" class="form-control" placeholder="example.com" value="<%- domain_name %>" required> |                                 <input name="domain_name" type="text" class="form-control" placeholder="example.com or *.example.com" value="<%- domain_name %>" pattern="(\*\.)?[a-z0-9\.]+" required title="Please enter a valid domain name. Domain wildcards are allowed: *.yourdomain.com"> | ||||||
|                             </div> |                             </div> | ||||||
|                         </div> |                         </div> | ||||||
|                         <div class="col-sm-8 col-md-8"> |                         <div class="col-sm-8 col-md-8"> | ||||||
| @@ -25,6 +32,86 @@ | |||||||
|                             </div> |                             </div> | ||||||
|                         </div> |                         </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">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">Force SSL</span> | ||||||
|  |                                 </label> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="col-sm-12 col-md-12"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label class="form-label">Certificate 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">Let's Encrypt</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">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">Email Address for Let's Encrypt <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">I Agree to the <a href="https://letsencrypt.org/repository/" target="_blank">Let's Encrypt Terms of Service</a> <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">Certificate</div> | ||||||
|  |                                 <div class="custom-file"> | ||||||
|  |                                     <input type="file" class="custom-file-input" name="meta[other_ssl_certificate]"> | ||||||
|  |                                     <label class="custom-file-label">Choose file</label> | ||||||
|  |                                 </div> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="col-sm-12 col-md-12 other-ssl"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <div class="form-label">Certificate Key</div> | ||||||
|  |                                 <div class="custom-file"> | ||||||
|  |                                     <input type="file" class="custom-file-input" name="meta[other_ssl_certificate_key]"> | ||||||
|  |                                     <label class="custom-file-label">Choose file</label> | ||||||
|  |                                 </div> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |  | ||||||
|  |  | ||||||
|         </form> |         </form> | ||||||
|     </div> |     </div> | ||||||
|     <div class="modal-footer"> |     <div class="modal-footer"> | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
|  |  | ||||||
|  | const _              = require('underscore'); | ||||||
| const Mn             = require('backbone.marionette'); | const Mn             = require('backbone.marionette'); | ||||||
| const template       = require('./form.ejs'); | const template       = require('./form.ejs'); | ||||||
| const Controller     = require('../../controller'); | const Controller     = require('../../controller'); | ||||||
| @@ -17,77 +18,109 @@ module.exports = Mn.View.extend({ | |||||||
|  |  | ||||||
|     ui: { |     ui: { | ||||||
|         form:         'form', |         form:         'form', | ||||||
|  |         domain_name:  'input[name="domain_name"]', | ||||||
|         forward_ip:   'input[name="forward_ip"]', |         forward_ip:   'input[name="forward_ip"]', | ||||||
|         buttons:      '.modal-footer button', |         buttons:      '.modal-footer button', | ||||||
|         cancel:       'button.cancel', |         cancel:       'button.cancel', | ||||||
|         save:         'button.save', |         save:         'button.save', | ||||||
|         error:      '.secret-error' |         ssl_enabled:  'input[name="ssl_enabled"]', | ||||||
|  |         ssl_options:  '#ssl-options input', | ||||||
|  |         ssl_provider: 'input[name="ssl_provider"]', | ||||||
|  |  | ||||||
|  |         // SSL hiding and showing | ||||||
|  |         all_ssl:         '.letsencrypt-ssl, .other-ssl', | ||||||
|  |         letsencrypt_ssl: '.letsencrypt-ssl', | ||||||
|  |         other_ssl:       '.other-ssl' | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     events: { |     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) { |         'click @ui.save': function (e) { | ||||||
|             e.preventDefault(); |             e.preventDefault(); | ||||||
|             return; |  | ||||||
|  |  | ||||||
|             this.ui.error.hide(); |             if (!this.ui.form[0].checkValidity()) { | ||||||
|  |                 $('<input type="submit">').hide().appendTo(this.ui.form).click().remove(); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|             let view = this; |             let view = this; | ||||||
|             let data = this.ui.form.serializeJSON(); |             let data = this.ui.form.serializeJSON(); | ||||||
|  |  | ||||||
|             // Manipulate |             // Manipulate | ||||||
|             data.roles = []; |             data.forward_port = parseInt(data.forward_port, 10); | ||||||
|             if ((this.model.get('id') === Cache.User.get('id') && this.model.isAdmin()) || (typeof data.is_admin !== 'undefined' && data.is_admin)) { |             _.map(data, function (item, idx) { | ||||||
|                 data.roles.push('admin'); |                 if (typeof item === 'string' && item === '1') { | ||||||
|                 delete data.is_admin; |                     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; | ||||||
|  |             }); | ||||||
|  |  | ||||||
|             data.is_disabled = typeof data.is_disabled !== 'undefined' ? !!data.is_disabled : false; |             // Process | ||||||
|             this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); |             this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); | ||||||
|             let method = Api.Users.create; |             let method = Api.Nginx.ProxyHosts.create; | ||||||
|  |  | ||||||
|             if (this.model.get('id')) { |             if (this.model.get('id')) { | ||||||
|                 // edit |                 // edit | ||||||
|                 method  = Api.Users.update; |                 method  = Api.Nginx.ProxyHosts.update; | ||||||
|                 data.id = this.model.get('id'); |                 data.id = this.model.get('id'); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             method(data) |             method(data) | ||||||
|                 .then(result => { |                 .then(result => { | ||||||
|                     if (result.id === Cache.User.get('id')) { |  | ||||||
|                         Cache.User.set(result); |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     if (view.model.get('id') !== Cache.User.get('id')) { |  | ||||||
|                         Controller.showUsers(); |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     view.model.set(result); |                     view.model.set(result); | ||||||
|                     App.UI.closeModal(function () { |                     App.UI.closeModal(function () { | ||||||
|                         if (method === Api.Users.create) { |                         if (method === Api.Nginx.ProxyHosts.create) { | ||||||
|                             // Show permissions dialog immediately |                             Controller.showNginxProxy(); | ||||||
|                             Controller.showUserPermissions(view.model); |  | ||||||
|                         } |                         } | ||||||
|                     }); |                     }); | ||||||
|                 }) |                 }) | ||||||
|                 .catch(err => { |                 .catch(err => { | ||||||
|                     this.ui.error.text(err.message).show(); |                     alert(err.message); | ||||||
|                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); |                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||||
|                 }); |                 }); | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|  |     templateContext: { | ||||||
|  |         getLetsencryptEmail: function () { | ||||||
|  |             return typeof this.meta.letsencrypt_email !== 'undefined' ? this.meta.letsencrypt_email : Cache.User.get('email'); | ||||||
|  |         }, | ||||||
|  |  | ||||||
|  |         getLetsencryptAgree: function () { | ||||||
|  |             return typeof this.meta.letsencrypt_agree !== 'undefined' ? this.meta.letsencrypt_agree : false; | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |  | ||||||
|     onRender: function () { |     onRender: function () { | ||||||
|         this.ui.forward_ip.mask('099.099.099.099', { |         this.ui.forward_ip.mask('099.099.099.099', { | ||||||
|             clearIfNotMatch: true, |             clearIfNotMatch: true, | ||||||
|             placeholder:     '000.000.000.000' |             placeholder:     '000.000.000.000' | ||||||
|         }); |         }); | ||||||
|         /* |  | ||||||
|         this.ui.forward_ip.mask('099.099.099.099', { |         this.ui.ssl_enabled.trigger('change'); | ||||||
|             reverse:         true, |         this.ui.ssl_provider.trigger('change'); | ||||||
|             clearIfNotMatch: true, |  | ||||||
|             placeholder:     '000.000.000.000' |         this.ui.domain_name[0].oninvalid = function () { | ||||||
|         }); |             this.setCustomValidity('Please enter a valid domain name. Domain wildcards are allowed: *.yourdomain.com'); | ||||||
|         */ |         }; | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     initialize: function (options) { |     initialize: function (options) { | ||||||
|   | |||||||
| @@ -1,33 +0,0 @@ | |||||||
| <div class="row"> |  | ||||||
|  |  | ||||||
|     <div class="col-lg-4 col-md-6 col-xs-12"> |  | ||||||
|         <div class="card"> |  | ||||||
|             <div class="card-header"> |  | ||||||
|                 <h3 class="card-title">My Profile</h3> |  | ||||||
|             </div> |  | ||||||
|             <div class="card-body"> |  | ||||||
|                 <form> |  | ||||||
|                     <div class="row"> |  | ||||||
|                         <div class="col-auto"> |  | ||||||
|                             <span class="avatar avatar-xl" style="background-image: url(<%- avatar || '/images/default-avatar.jpg' %>)"></span> |  | ||||||
|                         </div> |  | ||||||
|                         <div class="col"> |  | ||||||
|                             <div class="form-group"> |  | ||||||
|                                 <label class="form-label">Name</label> |  | ||||||
|                                 <input name="name" class="form-control" value="<%- name %>"> |  | ||||||
|                             </div> |  | ||||||
|                         </div> |  | ||||||
|                     </div> |  | ||||||
|                     <div class="form-group"> |  | ||||||
|                         <label class="form-label">Email-Address</label> |  | ||||||
|                         <input name="email" class="form-control" value="<%- email %>"> |  | ||||||
|                     </div> |  | ||||||
|                     <div class="form-footer"> |  | ||||||
|                         <button class="btn btn-primary btn-block">Save</button> |  | ||||||
|                     </div> |  | ||||||
|                 </form> |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|     </div> |  | ||||||
|  |  | ||||||
| </div> |  | ||||||
| @@ -1,21 +0,0 @@ | |||||||
| 'use strict'; |  | ||||||
|  |  | ||||||
| const Mn       = require('backbone.marionette'); |  | ||||||
| const Cache    = require('../cache'); |  | ||||||
| const template = require('./main.ejs'); |  | ||||||
|  |  | ||||||
| module.exports = Mn.View.extend({ |  | ||||||
|     template: template, |  | ||||||
|     id:       'profile', |  | ||||||
|  |  | ||||||
|     templateContext: { |  | ||||||
|         getUserField: function (field, default_val) { |  | ||||||
|             return Cache.User.get(field) || default_val; |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     initialize: function () { |  | ||||||
|         this.model = Cache.User; |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| @@ -6,13 +6,13 @@ const Controller = require('./controller'); | |||||||
| module.exports = Mn.AppRouter.extend({ | module.exports = Mn.AppRouter.extend({ | ||||||
|     appRoutes: { |     appRoutes: { | ||||||
|         users:               'showUsers', |         users:               'showUsers', | ||||||
|         profile:             'showProfile', |  | ||||||
|         logout:              'logout', |         logout:              'logout', | ||||||
|         'nginx/proxy':       'showNginxProxy', |         'nginx/proxy':       'showNginxProxy', | ||||||
|         'nginx/redirection': 'showNginxRedirection', |         'nginx/redirection': 'showNginxRedirection', | ||||||
|         'nginx/404':         'showNginxDead', |         'nginx/404':         'showNginxDead', | ||||||
|         'nginx/stream':      'showNginxStream', |         'nginx/stream':      'showNginxStream', | ||||||
|         'nginx/access':      'showNginxAccess', |         'nginx/access':      'showNginxAccess', | ||||||
|  |         'audit-log':         'showAuditLog', | ||||||
|         '*default':          'showDashboard' |         '*default':          'showDashboard' | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|   | |||||||
| @@ -14,8 +14,11 @@ | |||||||
|                     </span> |                     </span> | ||||||
|                 </a> |                 </a> | ||||||
|                 <div class="dropdown-menu dropdown-menu-right dropdown-menu-arrow"> |                 <div class="dropdown-menu dropdown-menu-right dropdown-menu-arrow"> | ||||||
|                     <a class="dropdown-item profile" href="/profile"> |                     <a class="dropdown-item edit-details" href="#"> | ||||||
|                         <i class="dropdown-icon fe fe-user"></i> Profile |                         <i class="dropdown-icon fe fe-user"></i> Edit Details | ||||||
|  |                     </a> | ||||||
|  |                     <a class="dropdown-item change-password" href="#"> | ||||||
|  |                         <i class="dropdown-icon fe fe-lock"></i> Change Password | ||||||
|                     </a> |                     </a> | ||||||
|                     <div class="dropdown-divider"></div> |                     <div class="dropdown-divider"></div> | ||||||
|                     <a class="dropdown-item logout" href="/logout"> |                     <a class="dropdown-item logout" href="/logout"> | ||||||
|   | |||||||
| @@ -13,10 +13,22 @@ module.exports = Mn.View.extend({ | |||||||
|     template:  template, |     template:  template, | ||||||
|  |  | ||||||
|     ui: { |     ui: { | ||||||
|         link: 'a' |         link:     'a', | ||||||
|  |         details:  'a.edit-details', | ||||||
|  |         password: 'a.change-password' | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     events: { |     events: { | ||||||
|  |         'click @ui.details': function (e) { | ||||||
|  |             e.preventDefault(); | ||||||
|  |             Controller.showUserForm(Cache.User); | ||||||
|  |         }, | ||||||
|  |  | ||||||
|  |         'click @ui.password': function (e) { | ||||||
|  |             e.preventDefault(); | ||||||
|  |             Controller.showUserPasswordForm(Cache.User); | ||||||
|  |         }, | ||||||
|  |  | ||||||
|         'click @ui.link': function (e) { |         'click @ui.link': function (e) { | ||||||
|             e.preventDefault(); |             e.preventDefault(); | ||||||
|             let href = $(e.currentTarget).attr('href'); |             let href = $(e.currentTarget).attr('href'); | ||||||
| @@ -25,9 +37,6 @@ module.exports = Mn.View.extend({ | |||||||
|                 case '/': |                 case '/': | ||||||
|                     Controller.showDashboard(); |                     Controller.showDashboard(); | ||||||
|                     break; |                     break; | ||||||
|                 case '/profile': |  | ||||||
|                     Controller.showProfile(); |  | ||||||
|                     break; |  | ||||||
|                 case '/logout': |                 case '/logout': | ||||||
|                     Controller.logout(); |                     Controller.logout(); | ||||||
|                     break; |                     break; | ||||||
|   | |||||||
| @@ -30,10 +30,13 @@ | |||||||
|                     <a href="/nginx/access" class="nav-link"><i class="fe fe-lock"></i> Access Lists</a> |                     <a href="/nginx/access" class="nav-link"><i class="fe fe-lock"></i> Access Lists</a> | ||||||
|                 </li> |                 </li> | ||||||
|                 <% } %> |                 <% } %> | ||||||
|                 <% if (showUsers()) { %> |                 <% if (isAdmin()) { %> | ||||||
|                 <li class="nav-item"> |                 <li class="nav-item"> | ||||||
|                     <a href="/users" class="nav-link"><i class="fe fe-users"></i> Users</a> |                     <a href="/users" class="nav-link"><i class="fe fe-users"></i> Users</a> | ||||||
|                 </li> |                 </li> | ||||||
|  |                 <li class="nav-item"> | ||||||
|  |                     <a href="/audit-log" class="nav-link"><i class="fe fe-book-open"></i> Audit Log</a> | ||||||
|  |                 </li> | ||||||
|                 <% } %> |                 <% } %> | ||||||
|             </ul> |             </ul> | ||||||
|         </div> |         </div> | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ module.exports = Mn.View.extend({ | |||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     templateContext: { |     templateContext: { | ||||||
|         showUsers: function () { |         isAdmin: function () { | ||||||
|             return Cache.User.isAdmin(); |             return Cache.User.isAdmin(); | ||||||
|         }, |         }, | ||||||
|  |  | ||||||
|   | |||||||
| @@ -25,6 +25,7 @@ | |||||||
|                         <div class="invalid-feedback secret-error"></div> |                         <div class="invalid-feedback secret-error"></div> | ||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|  |                 <% if (isAdmin()) { %> | ||||||
|                 <div class="col-sm-12 col-md-12"> |                 <div class="col-sm-12 col-md-12"> | ||||||
|                     <div class="form-label">Roles</div> |                     <div class="form-label">Roles</div> | ||||||
|                 </div> |                 </div> | ||||||
| @@ -46,6 +47,7 @@ | |||||||
|                         </label> |                         </label> | ||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|  |                 <% } %> | ||||||
|             </div> |             </div> | ||||||
|         </form> |         </form> | ||||||
|     </div> |     </div> | ||||||
|   | |||||||
| @@ -30,6 +30,15 @@ module.exports = Mn.View.extend({ | |||||||
|             let view = this; |             let view = this; | ||||||
|             let data = this.ui.form.serializeJSON(); |             let data = this.ui.form.serializeJSON(); | ||||||
|  |  | ||||||
|  |             let show_password = this.model.get('email') === 'admin@example.com'; | ||||||
|  |  | ||||||
|  |             // admin@example.com is not allowed | ||||||
|  |             if (data.email === 'admin@example.com') { | ||||||
|  |                 this.ui.error.text('Default email address must be changed').show(); | ||||||
|  |                 this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|             // Manipulate |             // Manipulate | ||||||
|             data.roles = []; |             data.roles = []; | ||||||
|             if ((this.model.get('id') === Cache.User.get('id') && this.model.isAdmin()) || (typeof data.is_admin !== 'undefined' && data.is_admin)) { |             if ((this.model.get('id') === Cache.User.get('id') && this.model.isAdmin()) || (typeof data.is_admin !== 'undefined' && data.is_admin)) { | ||||||
| @@ -62,6 +71,8 @@ module.exports = Mn.View.extend({ | |||||||
|                         if (method === Api.Users.create) { |                         if (method === Api.Users.create) { | ||||||
|                             // Show permissions dialog immediately |                             // Show permissions dialog immediately | ||||||
|                             Controller.showUserPermissions(view.model); |                             Controller.showUserPermissions(view.model); | ||||||
|  |                         } else if (show_password) { | ||||||
|  |                             Controller.showUserPasswordForm(view.model); | ||||||
|                         } |                         } | ||||||
|                     }); |                     }); | ||||||
|                 }) |                 }) | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ const Api        = require('../api'); | |||||||
| const Controller = require('../controller'); | const Controller = require('../controller'); | ||||||
| const ListView   = require('./list/main'); | const ListView   = require('./list/main'); | ||||||
| const template   = require('./main.ejs'); | const template   = require('./main.ejs'); | ||||||
|  | const ErrorView  = require('../error/main'); | ||||||
|  |  | ||||||
| module.exports = Mn.View.extend({ | module.exports = Mn.View.extend({ | ||||||
|     id:       'users', |     id:       'users', | ||||||
|   | |||||||
							
								
								
									
										20
									
								
								src/frontend/js/models/audit-log.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/frontend/js/models/audit-log.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const Backbone = require('backbone'); | ||||||
|  |  | ||||||
|  | const model = Backbone.Model.extend({ | ||||||
|  |     idAttribute: 'id', | ||||||
|  |  | ||||||
|  |     defaults: function () { | ||||||
|  |         return { | ||||||
|  |             name: '' | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | module.exports = { | ||||||
|  |     Model:      model, | ||||||
|  |     Collection: Backbone.Collection.extend({ | ||||||
|  |         model: model | ||||||
|  |     }) | ||||||
|  | }; | ||||||
| @@ -72,3 +72,17 @@ $blue: #467fcf; | |||||||
| .dimmer .loader { | .dimmer .loader { | ||||||
|     margin-top: 50px; |     margin-top: 50px; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* modal tabs */ | ||||||
|  |  | ||||||
|  | .modal-body.has-tabs { | ||||||
|  |     padding: 0; | ||||||
|  |  | ||||||
|  |     .nav-tabs { | ||||||
|  |         margin: 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     .tab-content { | ||||||
|  |         padding: 1rem; | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user