Audit Log items, backend stuff, help pages

This commit is contained in:
Jamie Curnow
2018-08-01 21:18:17 +10:00
parent a43c2d74bf
commit 66e25e315b
47 changed files with 936 additions and 134 deletions

View File

@ -1,32 +1,72 @@
<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 class="avatar d-block" style="background-image: url(<%- user.avatar || '/images/default-avatar.jpg' %>)">
<span class="avatar-status <%- user.is_disabled ? 'bg-red' : 'bg-green' %>"></span>
</div>
</td>
<td>
<div><%- name %></div>
<div>
<% if (user.is_deleted) {
%>
<span class="mdi-format-strikethrough" title="Deleted"><%- user.name %></span>
<%
} else {
%>
<%- user.name %>
<%
}
%>
</div>
</td>
<td>
<div>
<%
var items = [];
switch (object_type) {
case 'proxy-host':
%> <span class="text-success"><i class="fe fe-zap"></i></span> <%
items = meta.domain_names;
break;
case 'redirection-host':
%> <span class="text-yellow"><i class="fe fe-shuffle"></i></span> <%
items = meta.domain_names;
break;
case 'stream':
%> <span class="text-blue"><i class="fe fe-radio"></i></span> <%
items.push(meta.incoming_port);
break;
case 'dead-host':
%> <span class="text-danger"><i class="fe fe-zap-off"></i></span> <%
items = meta.domain_names;
break;
case 'access-list':
%> <span class="text-teal"><i class="fe fe-lock"></i></span> <%
items.push(meta.name);
break;
case 'user':
%> <span class="text-teal"><i class="fe fe-user"></i></span> <%
items.push(meta.name);
break;
}
%>&nbsp;<%- i18n('audit-log', action, {name: i18n('audit-log', object_type)}) %>
&mdash;
<%
if (items && items.length) {
items.map(function(item) {
%>
<span class="tag"><%- item %></span>
<%
});
} else {
%>
#<%- object_id %>
<%
}
%>
</div>
<div class="small text-muted">
Created: <%- formatDbDate(created_on, 'Do MMMM YYYY') %>
<%- formatDbDate(created_on, 'Do MMMM YYYY, h:mm a') %>
</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 class="text-right">
<a href="#" class="meta btn btn-secondary btn-sm"><%- i18n('audit-log', 'view-meta') %></a>
</td>

View File

@ -2,9 +2,6 @@
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({
@ -12,61 +9,26 @@ module.exports = Mn.View.extend({
tagName: 'tr',
ui: {
edit: 'a.edit-user',
permissions: 'a.edit-permissions',
password: 'a.set-password',
login: 'a.login',
delete: 'a.delete-user'
meta: 'a.meta'
},
events: {
'click @ui.edit': function (e) {
'click @ui.meta': 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');
});
}
Controller.showAuditMeta(this.model);
}
},
templateContext: {
isSelf: function () {
return Cache.User.get('id') === this.id;
}
},
more: function() {
switch (this.object_type) {
case 'redirection-host':
case 'stream':
case 'proxy-host':
return this.meta.domain_names.join(', ');
}
initialize: function () {
this.listenTo(this.model, 'change', this.render);
return '#' + (this.object_id || '?');
}
}
});

View File

@ -1,8 +1,7 @@
<thead>
<th width="30">&nbsp;</th>
<th>Name</th>
<th>Email</th>
<th>Roles</th>
<th>User</th>
<th>Event</th>
<th>&nbsp;</th>
</thead>
<tbody>

View File

@ -24,7 +24,7 @@ module.exports = Mn.View.extend({
onRender: function () {
let view = this;
App.Api.AuditLog.getAll()
App.Api.AuditLog.getAll(['user'])
.then(response => {
if (!view.isDestroyed() && response && response.length) {
view.showChildView('list_region', new ListView({

View File

@ -0,0 +1,27 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><%- i18n('audit-log', 'meta-title') %></h5>
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal">&nbsp;</button>
</div>
<div class="modal-body">
<div class="mb-2">
<div class="tag tag-dark">
<%- i18n('audit-log', 'user') %>
<span class="tag-addon tag-teal"><%- user.name %></span>
</div>
<div class="tag tag-dark">
<%- i18n('audit-log', 'action') %>
<span class="tag-addon tag-warning"><%- i18n('audit-log', action, {name: i18n('audit-log', object_type)}) %></span>
</div>
<div class="tag tag-dark">
<%- i18n('audit-log', 'date') %>
<span class="tag-addon tag-primary"><%- formatDbDate(created_on, 'Do MMMM YYYY, h:mm a') %></span>
</div>
</div>
<pre><%- JSON.stringify(meta, null, 2) %></pre>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'close') %></button>
</div>
</div>

View File

@ -0,0 +1,9 @@
'use strict';
const Mn = require('backbone.marionette');
const template = require('./meta.ejs');
module.exports = Mn.View.extend({
template: template,
className: 'modal-dialog wide'
});

View File

@ -280,6 +280,18 @@ module.exports = {
}
},
/**
* Help Dialog
*
* @param {String} title
* @param {String} content
*/
showHelp: function (title, content) {
require(['./main', './help/main'], function (App, View) {
App.UI.showModalDialog(new View({title: title, content: content}));
});
},
/**
* Nginx Access
*/
@ -322,6 +334,19 @@ module.exports = {
}
},
/**
* Audit Log Metadata
*
* @param model
*/
showAuditMeta: function (model) {
if (Cache.User.isAdmin()) {
require(['./main', './audit-log/meta'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Logout
*/

View File

@ -0,0 +1,12 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><%- title %></h5>
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal">&nbsp;</button>
</div>
<div class="modal-body">
<%= content %>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'close') %></button>
</div>
</div>

View File

@ -0,0 +1,18 @@
'use strict';
const Mn = require('backbone.marionette');
const template = require('./main.ejs');
module.exports = Mn.View.extend({
template: template,
className: 'modal-dialog wide',
templateContext: function () {
let content = this.getOption('content').split("\n");
return {
title: this.getOption('title'),
content: '<p>' + content.join('</p><p>') + '</p>'
};
}
});

View File

@ -3,6 +3,7 @@
<div class="card-header">
<h3 class="card-title"><%- i18n('access-lists', 'title') %></h3>
<div class="card-options">
<a href="#" class="btn btn-outline-secondary btn-sm ml-2 help"><i class="fe fe-help-circle"></i></a>
<% if (showAddButton) { %>
<a href="#" class="btn btn-outline-teal btn-sm ml-2 add-item"><%- i18n('access-lists', 'add') %></a>
<% } %>

View File

@ -15,6 +15,7 @@ module.exports = Mn.View.extend({
ui: {
list_region: '.list-region',
add: '.add-item',
help: '.help',
dimmer: '.dimmer'
},
@ -26,6 +27,11 @@ module.exports = Mn.View.extend({
'click @ui.add': function (e) {
e.preventDefault();
App.Controller.showNginxAccessListForm();
},
'click @ui.help': function (e) {
e.preventDefault();
App.Controller.showHelp(App.i18n('access-lists', 'help-title'), App.i18n('access-lists', 'help-content'));
}
},

View File

@ -25,7 +25,7 @@
<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>
<!--<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>

View File

@ -3,6 +3,7 @@
<div class="card-header">
<h3 class="card-title"><%- i18n('dead-hosts', 'title') %></h3>
<div class="card-options">
<a href="#" class="btn btn-outline-secondary btn-sm ml-2 help"><i class="fe fe-help-circle"></i></a>
<% if (showAddButton) { %>
<a href="#" class="btn btn-outline-danger btn-sm ml-2 add-item"><%- i18n('dead-hosts', 'add') %></a>
<% } %>

View File

@ -15,6 +15,7 @@ module.exports = Mn.View.extend({
ui: {
list_region: '.list-region',
add: '.add-item',
help: '.help',
dimmer: '.dimmer'
},
@ -26,6 +27,11 @@ module.exports = Mn.View.extend({
'click @ui.add': function (e) {
e.preventDefault();
App.Controller.showNginxDeadForm();
},
'click @ui.help': function (e) {
e.preventDefault();
App.Controller.showHelp(App.i18n('dead-hosts', 'help-title'), App.i18n('dead-hosts', 'help-content'));
}
},

View File

@ -31,7 +31,7 @@
<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>
<!--<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>

View File

@ -3,6 +3,7 @@
<div class="card-header">
<h3 class="card-title"><%- i18n('proxy-hosts', 'title') %></h3>
<div class="card-options">
<a href="#" class="btn btn-outline-secondary btn-sm ml-2 help"><i class="fe fe-help-circle"></i></a>
<% if (showAddButton) { %>
<a href="#" class="btn btn-outline-success btn-sm ml-2 add-item"><%- i18n('proxy-hosts', 'add') %></a>
<% } %>

View File

@ -15,6 +15,7 @@ module.exports = Mn.View.extend({
ui: {
list_region: '.list-region',
add: '.add-item',
help: '.help',
dimmer: '.dimmer'
},
@ -26,6 +27,11 @@ module.exports = Mn.View.extend({
'click @ui.add': function (e) {
e.preventDefault();
App.Controller.showNginxProxyForm();
},
'click @ui.help': function (e) {
e.preventDefault();
App.Controller.showHelp(App.i18n('proxy-hosts', 'help-title'), App.i18n('proxy-hosts', 'help-content'));
}
},

View File

@ -28,7 +28,7 @@
<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>
<!--<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>

View File

@ -3,6 +3,7 @@
<div class="card-header">
<h3 class="card-title">Redirection Hosts</h3>
<div class="card-options">
<a href="#" class="btn btn-outline-secondary btn-sm ml-2 help"><i class="fe fe-help-circle"></i></a>
<% if (showAddButton) { %>
<a href="#" class="btn btn-outline-yellow btn-sm ml-2 add-item">Add Redirection Host</a>
<% } %>

View File

@ -15,6 +15,7 @@ module.exports = Mn.View.extend({
ui: {
list_region: '.list-region',
add: '.add-item',
help: '.help',
dimmer: '.dimmer'
},
@ -26,6 +27,11 @@ module.exports = Mn.View.extend({
'click @ui.add': function (e) {
e.preventDefault();
App.Controller.showNginxRedirectionForm();
},
'click @ui.help': function (e) {
e.preventDefault();
App.Controller.showHelp(App.i18n('redirection-hosts', 'help-title'), App.i18n('redirection-hosts', 'help-content'));
}
},

View File

@ -3,6 +3,7 @@
<div class="card-header">
<h3 class="card-title"><%- i18n('streams', 'title') %></h3>
<div class="card-options">
<a href="#" class="btn btn-outline-secondary btn-sm ml-2 help"><i class="fe fe-help-circle"></i></a>
<% if (showAddButton) { %>
<a href="#" class="btn btn-outline-blue btn-sm ml-2 add-item"><%- i18n('streams', 'add') %></a>
<% } %>

View File

@ -15,6 +15,7 @@ module.exports = Mn.View.extend({
ui: {
list_region: '.list-region',
add: '.add-item',
help: '.help',
dimmer: '.dimmer'
},
@ -26,6 +27,11 @@ module.exports = Mn.View.extend({
'click @ui.add': function (e) {
e.preventDefault();
App.Controller.showNginxStreamForm();
},
'click @ui.help': function (e) {
e.preventDefault();
App.Controller.showHelp(App.i18n('streams', 'help-title'), App.i18n('streams', 'help-content'));
}
},

View File

@ -35,7 +35,7 @@
<% if (!isSelf()) { %>
<a href="#" class="login dropdown-item"><i class="dropdown-icon fe fe-log-in"></i> <%- i18n('users', 'sign-in-as') %></a>
<div class="dropdown-divider"></div>
<a href="#" class="delete-user dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('users', 'delete') %></a>
<a href="#" class="delete-user dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('users', 'delete', {name: name}) %></a>
<% } %>
</div>
</div>

View File

@ -3,7 +3,7 @@
<div class="card-header">
<h3 class="card-title"><%- i18n('users', 'title') %></h3>
<div class="card-options">
<a href="#" class="btn btn-outline-teal btn-sm ml-2 add-user"><%- i18n('users', 'add') %></a>
<a href="#" class="btn btn-outline-teal btn-sm ml-2 add-item"><%- i18n('users', 'add') %></a>
</div>
</div>
<div class="card-body no-padding min-100">

View File

@ -12,6 +12,7 @@
"created-on": "Created: {date}",
"save": "Save",
"cancel": "Cancel",
"close": "Close",
"sure": "Yes I'm Sure",
"disabled": "Disabled",
"choose-file": "Choose file",
@ -81,7 +82,9 @@
"forward-ip": "Forward IP",
"forward-port": "Forward Port",
"delete": "Delete Proxy Host",
"delete-confirm": "Are you sure you want to delete the Proxy host for: <strong>{domains}</strong>?"
"delete-confirm": "Are you sure you want to delete the Proxy host for: <strong>{domains}</strong>?",
"help-title": "What is a Proxy Host?",
"help-content": "A Proxy Host is the incoming endpoint for a web service that you want to forward.\nIt provides optional SSL termination for your service that might not have SSL support built in.\nProxy Hosts are the most common use for the Nginx Proxy Manager."
},
"redirection-hosts": {
"title": "Redirection Hosts",
@ -91,13 +94,19 @@
"forward-domain": "Forward Domain",
"preserve-path": "Preserve Path",
"delete": "Delete Proxy Host",
"delete-confirm": "Are you sure you want to delete the Redirection host for: <strong>{domains}</strong>?"
"delete-confirm": "Are you sure you want to delete the Redirection host for: <strong>{domains}</strong>?",
"help-title": "What is a Redirection Host?",
"help-content": "A Redirection Host will redirect requests from the incoming domain and push the viewer to another domain.\nThe most common reason to use this type of host is when your website changes domains but you still have search engine or referrer links pointing to the old domain."
},
"dead-hosts": {
"title": "404 Hosts",
"empty": "There are no 404 Hosts",
"add": "Add 404 Host",
"form-title": "{id, select, undefined{New} other{Edit}} 404 Host"
"form-title": "{id, select, undefined{New} other{Edit}} 404 Host",
"delete": "Delete 404 Host",
"delete-confirm": "Are you sure you want to delete this 404 Host?",
"help-title": "What is a 404 Host?",
"help-content": "A 404 Host is simply a host setup that shows a 404 page.\nThis can be useful when your domain is listed in search engines and you want to provide a nicer error page or specifically to tell the search indexers that the domain pages no longer exist.\nAnother benefit of having this host is to track the logs for hits to it and view the referrers."
},
"streams": {
"title": "Streams",
@ -114,7 +123,9 @@
"tcp": "TCP",
"udp": "UDP",
"delete": "Delete Stream",
"delete-confirm": "Are you sure you want to delete this Stream?"
"delete-confirm": "Are you sure you want to delete this 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."
},
"access-lists": {
"title": "Access Lists",
@ -122,7 +133,9 @@
"add": "Add Access List",
"delete": "Delete Access List",
"delete-confirm": "Are you sure you want to delete this access list? Any hosts using it will need to be updated later.",
"public": "Publicly Accessible"
"public": "Publicly Accessible",
"help-title": "What is an Access List?",
"help-content": "Access Lists provide authentication for the Proxy Hosts via Basic HTTP Authentication.\nYou can configure multiple usernames and passwords for a single Access List and then apply that to a Proxy Host.\nThis is most useful for forwarded web services that do not have authentication mechanisms built in."
},
"users": {
"title": "Users",
@ -153,7 +166,19 @@
"audit-log": {
"title": "Audit Log",
"empty": "There are no logs.",
"empty-subtitle": "As soon as you or another user changes something, history of those events will show up here."
"empty-subtitle": "As soon as you or another user changes something, history of those events will show up here.",
"proxy-host": "Proxy Host",
"redirection-host": "Redirection Host",
"dead-host": "404 Host",
"stream": "Stream",
"user": "User",
"created": "Created {name}",
"updated": "Updated {name}",
"deleted": "Deleted {name}",
"meta-title": "Details for Event",
"view-meta": "View Details",
"action": "Action",
"date": "Date"
}
}
}

View File

@ -15,6 +15,7 @@ Mn.Renderer.render = function (template, data, view) {
/**
* @param {String} date
* @param {String} format
* @returns {String}
*/
data.formatDbDate = function (date, format) {

View File

@ -86,3 +86,12 @@ $blue: #467fcf;
padding: 1rem;
}
}
/* modal wide */
@media (min-width: 576px) {
.modal-dialog.wide {
max-width: 700px;
margin: 1.75rem auto;
}
}