Initial commit

This commit is contained in:
Jamie Curnow
2017-12-21 09:02:37 +10:00
parent dc830df253
commit 6e7435c35d
140 changed files with 19554 additions and 0 deletions

View File

@ -0,0 +1,5 @@
<td colspan="10" class="text-center">
<br><br>
<p>It looks like there are no access lists configured.</p>
<p><button type="button" class="btn btn-sm btn-success">Create your first Access List</button></p>
</td>

View File

@ -0,0 +1,23 @@
'use strict';
import Mn from 'backbone.marionette';
const template = require('./empty.ejs');
const AccessModel = require('../../models/access');
const Controller = require('../controller');
module.exports = Mn.View.extend({
template: template,
tagName: 'tr',
ui: {
create: 'button'
},
events: {
'click @ui.create': function (e) {
e.preventDefault();
Controller.showAccessListForm(new AccessModel.Model);
}
}
});

View File

@ -0,0 +1,11 @@
<table class="table table-condensed table-striped">
<thead>
<th>Access List Name</th>
<th>User Count</th>
<th>Host Count</th>
<th class="text-right"><button type="button" class="btn btn-xs btn-info">Create Access List</button></th>
</thead>
<tbody>
<!-- items -->
</tbody>
</table>

View File

@ -0,0 +1,62 @@
'use strict';
import Mn from 'backbone.marionette';
const Api = require('../api');
const template = require('./main.ejs');
const Controller = require('../controller');
const RowView = require('./row');
const AccessListModel = require('../../models/access');
const EmptyView = require('./empty');
const TableBody = Mn.CollectionView.extend({
tagName: 'tbody',
childView: RowView
});
module.exports = Mn.View.extend({
template: template,
id: 'access',
regions: {
list_region: {
el: 'tbody',
replaceElement: true
}
},
ui: {
'create': 'th button'
},
events: {
'click @ui.create': function (e) {
e.preventDefault();
Controller.showAccessListForm(new AccessListModel.Model);
}
},
onRender: function () {
let view = this;
Api.Access.getAll()
.then(response => {
if (!view.isDestroyed()) {
if (response && response.length) {
view.showChildView('list_region', new TableBody({
collection: new AccessListModel.Collection(response)
}));
} else {
view.showChildView('list_region', new EmptyView());
}
view.trigger('loaded');
}
})
.catch(err => {
Controller.showError(err, 'Could not fetch Access Lists');
view.trigger('loaded');
});
}
});

View File

@ -0,0 +1,7 @@
<td><%- name %></td>
<td><%- items.length %></td>
<td><%- hosts.length %></td>
<td class="text-right">
<button type="button" class="btn btn-default btn-xs edit" title="Edit"><i class="fa fa-pencil" aria-hidden="true"></i></button>
<button type="button" class="btn btn-default btn-xs delete" title="Delete"><i class="fa fa-times" aria-hidden="true"></i></button>
</td>

View File

@ -0,0 +1,32 @@
'use strict';
import Mn from 'backbone.marionette';
const template = require('./row.ejs');
const Controller = require('../controller');
module.exports = Mn.View.extend({
template: template,
tagName: 'tr',
ui: {
edit: 'button.edit',
delete: 'button.delete'
},
events: {
'click @ui.edit': function (e) {
e.preventDefault();
Controller.showAccessListForm(this.model);
},
'click @ui.delete': function (e) {
e.preventDefault();
Controller.showDeleteAccessList(this.model);
}
},
initialize: function () {
this.listenTo(this.model, 'change', this.render);
}
});

View File

@ -0,0 +1,24 @@
<div class="modal-dialog">
<div class="modal-content">
<form class="form-horizontal">
<div class="modal-header text-left">
<h4 class="modal-title">Delete Access List</h4>
</div>
<div class="modal-body">
<p>Are you sure? You cannot undo this.</p>
<% if (hosts && hosts.length) { %>
<p>The following Hosts are using this Access List and will become publicly available upon deletion:</p>
<ul>
<% _.map(hosts, function (host) { %>
<li><%- host.hostname %></li>
<% }) %>
</ul>
<% } %>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-danger delete">Yes I'm Sure</button>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,35 @@
'use strict';
import Mn from 'backbone.marionette';
const template = require('./delete.ejs');
const Controller = require('../controller');
const Api = require('../api');
const App = require('../main');
module.exports = Mn.View.extend({
template: template,
ui: {
buttons: 'form button',
delete: 'button.delete'
},
events: {
'click @ui.delete': function (e) {
e.preventDefault();
this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
Api.Access.delete(this.model.get('_id'))
.then((/*result*/) => {
App.UI.closeModal();
Controller.showAccess();
})
.catch(err => {
alert(err.message);
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
});
}
}
});

View File

@ -0,0 +1,30 @@
<div class="modal-dialog">
<div class="modal-content">
<form>
<div class="modal-header text-left">
<h4 class="modal-title"><% if (typeof _id !== 'undefined') { %>Edit<% } else { %>Create<% } %> Access List</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label for="list_name">List Name</label>
<input type="text" class="form-control" placeholder="Cool People" name="name" id="list_name" value="<%- name %>" required>
</div>
<div class="row">
<div class="col-sm-6">
<strong>Username</strong>
</div>
<div class="col-sm-6">
<strong>Password</strong>
</div>
</div>
<div class="items"><!-- items --></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-success save">Save</button>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,95 @@
'use strict';
import Mn from 'backbone.marionette';
const _ = require('lodash');
const template = require('./form.ejs');
const Controller = require('../controller');
const Api = require('../api');
const App = require('../main');
const ItemView = require('./item');
const AccessItemModel = require('../../models/access_item');
require('jquery-serializejson');
const ItemsView = Mn.CollectionView.extend({
childView: ItemView
});
module.exports = Mn.View.extend({
template: template,
id: 'access-list-form',
ui: {
items_region: '.items',
form: 'form',
buttons: 'form button'
},
regions: {
items_region: '@ui.items_region'
},
events: {
'submit @ui.form': function (e) {
e.preventDefault();
let form_data = this.ui.form.serializeJSON();
let items_data = [];
_.map(form_data.username, (val, idx) => {
if (val.trim().length) {
items_data.push({
username: val.trim(),
password: form_data.password[idx]
});
}
});
if (!items_data.length) {
alert('You must specify at least 1 Username and Password combination');
return;
}
let data = {
name: form_data.name,
items: items_data
};
this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
let method = Api.Access.create;
if (this.model.get('_id')) {
// edit
method = Api.Access.update;
data._id = this.model.get('_id');
}
method(data)
.then((/*result*/) => {
App.UI.closeModal();
Controller.showAccess();
})
.catch((err) => {
alert(err.message);
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
});
}
},
onRender: function () {
let items = this.model.get('items');
// Add empty items to the end of the list. This is cheating but hey I don't have the time to do it right
let items_to_add = 5 - items.length;
if (items_to_add) {
for (let i = 0; i < items_to_add; i++) {
items.push({});
}
}
this.showChildView('items_region', new ItemsView({
collection: new AccessItemModel.Collection(items)
}));
}
});

View File

@ -0,0 +1,8 @@
<div class="row">
<div class="col-sm-6">
<input type="text" class="form-control" placeholder="" name="username[]" value="<%- typeof username !== 'undefined' ? username : '' %>">
</div>
<div class="col-sm-6">
<input type="password" class="form-control" placeholder="<%- typeof hint !== 'undefined' ? hint : '' %>" name="password[]" value="">
</div>
</div>

View File

@ -0,0 +1,9 @@
'use strict';
import Mn from 'backbone.marionette';
const template = require('./item.ejs');
module.exports = Mn.View.extend({
template: template
});

View File

@ -0,0 +1,168 @@
'use strict';
import $ from 'jquery';
/**
* @param {String} message
* @param {*} debug
* @param {Number} code
* @constructor
*/
const ApiError = function (message, debug, code) {
let temp = Error.call(this, message);
temp.name = this.name = 'ApiError';
this.stack = temp.stack;
this.message = temp.message;
this.debug = debug;
this.code = code;
};
ApiError.prototype = Object.create(Error.prototype, {
constructor: {
value: ApiError,
writable: true,
configurable: true
}
});
/**
*
* @param {String} verb
* @param {String} path
* @param {Object} [data]
* @param {Object} [options]
* @returns {Promise}
*/
function fetch (verb, path, data, options) {
options = options || {};
return new Promise(function (resolve, reject) {
let api_url = '/api/';
let url = api_url + path;
$.ajax({
url: url,
data: typeof data === 'object' && data !== null ? JSON.stringify(data) : data,
type: verb,
dataType: 'json',
contentType: 'application/json; charset=UTF-8',
crossDomain: true,
timeout: (options.timeout ? options.timeout : 30000),
xhrFields: {
withCredentials: true
},
success: function (data, textStatus, response) {
resolve(response);
},
error: function (xhr, status, error_thrown) {
let code = 400;
if (typeof xhr.responseJSON !== 'undefined' && typeof xhr.responseJSON.error !== 'undefined' && typeof xhr.responseJSON.error.message !== 'undefined') {
error_thrown = xhr.responseJSON.error.message;
code = xhr.responseJSON.error.code || 500;
}
reject(new ApiError(error_thrown, xhr.responseText, code));
}
});
});
}
module.exports = {
status: function () {
return fetch('get', '');
},
Hosts: {
/**
* @returns {Promise}
*/
getAll: function () {
return fetch('get', 'hosts');
},
/**
* @param {Object} data
* @returns {Promise}
*/
create: function (data) {
return fetch('post', 'hosts', data);
},
/**
* @param {Object} data
* @param {String} data._id
* @returns {Promise}
*/
update: function (data) {
let _id = data._id;
delete data._id;
return fetch('put', 'hosts/' + _id, data);
},
/**
* @param {String} _id
* @returns {Promise}
*/
delete: function (_id) {
return fetch('delete', 'hosts/' + _id);
},
/**
* @param {String} _id
* @returns {Promise}
*/
reconfigure: function (_id) {
return fetch('post', 'hosts/' + _id + '/reconfigure');
},
/**
* @param {String} _id
* @returns {Promise}
*/
renew: function (_id) {
return fetch('post', 'hosts/' + _id + '/renew');
}
},
Access: {
/**
* @returns {Promise}
*/
getAll: function () {
return fetch('get', 'access');
},
/**
* @param {Object} data
* @returns {Promise}
*/
create: function (data) {
return fetch('post', 'access', data);
},
/**
* @param {Object} data
* @param {String} data._id
* @returns {Promise}
*/
update: function (data) {
let _id = data._id;
delete data._id;
return fetch('put', 'access/' + _id, data);
},
/**
* @param {String} _id
* @returns {Promise}
*/
delete: function (_id) {
return fetch('delete', 'access/' + _id);
}
}
};

View File

@ -0,0 +1,5 @@
'use strict';
let cache = {};
module.exports = cache;

View File

@ -0,0 +1,148 @@
'use strict';
import Backbone from 'backbone';
const Cache = require('./cache');
module.exports = {
/**
* @param {String} route
* @param {Object} [options]
* @returns {Boolean}
*/
navigate: function (route, options) {
options = options || {};
Backbone.history.navigate(route.toString(), options);
return true;
},
/**
* Dashboard
*/
showDashboard: function () {
require(['./main', './dashboard/main'], (App, View) => {
this.navigate('/');
App.UI.showMainLoading();
let view = new View();
view.on('loaded', function () {
App.UI.hideMainLoading();
});
App.UI.showChildView('main_region', view);
});
},
/**
* Access
*/
showAccess: function () {
require(['./main', './access/main'], (App, View) => {
this.navigate('/access');
App.UI.showMainLoading();
let view = new View();
view.on('loaded', function () {
App.UI.hideMainLoading();
});
App.UI.showChildView('main_region', view);
});
},
/**
* Show Host Form
*
* @param model
*/
showHostForm: function (model) {
require(['./main', './host/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
},
/**
* Show Delete Host Confirmation
*
* @param model
*/
showDeleteHost: function (model) {
require(['./main', './host/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
},
/**
* Show Reconfigure Host
*
* @param model
*/
showReconfigureHost: function (model) {
require(['./main', './host/reconfigure'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
},
/**
* Show Renew Host
*
* @param model
*/
showRenewHost: function (model) {
require(['./main', './host/renew'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
},
/**
* Show Advanced Host
*
* @param model
*/
showAdvancedHost: function (model) {
require(['./main', './host/advanced'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
},
/**
* Show Access List Form
*
* @param model
*/
showAccessListForm: function (model) {
require(['./main', './access_list/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
},
/**
* Show Delete Access List Confirmation
*
* @param model
*/
showDeleteAccessList: function (model) {
require(['./main', './access_list/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
},
/**
* Error
*
* @param {Error} err
* @param {String} nice_msg
*/
showError: function (err, nice_msg) {
require(['./main', './error/main'], (App, View) => {
App.UI.showChildView('main_region', new View({
err: err,
nice_msg: nice_msg
}));
});
}
};

View File

@ -0,0 +1,5 @@
<td colspan="10" class="text-center">
<br><br>
<p>It looks like there are no hosts configured.</p>
<p><button type="button" class="btn btn-sm btn-success">Create your first Host</button></p>
</td>

View File

@ -0,0 +1,23 @@
'use strict';
import Mn from 'backbone.marionette';
const template = require('./empty.ejs');
const HostModel = require('../../models/host');
const Controller = require('../controller');
module.exports = Mn.View.extend({
template: template,
tagName: 'tr',
ui: {
create: 'button'
},
events: {
'click @ui.create': function (e) {
e.preventDefault();
Controller.showHostForm(new HostModel.Model);
}
}
});

View File

@ -0,0 +1,12 @@
<table class="table table-condensed table-striped">
<thead>
<th>Hostname</th>
<th>Forward</th>
<th>SSL</th>
<th>Access List</th>
<th class="text-right"><button type="button" class="btn btn-xs btn-info">Create Host</button></th>
</thead>
<tbody>
<!-- items -->
</tbody>
</table>

View File

@ -0,0 +1,63 @@
'use strict';
import Mn from 'backbone.marionette';
const Api = require('../api');
const template = require('./main.ejs');
const Controller = require('../controller');
const RowView = require('./row');
const HostModel = require('../../models/host');
const EmptyView = require('./empty');
const TableBody = Mn.CollectionView.extend({
tagName: 'tbody',
childView: RowView
});
module.exports = Mn.View.extend({
template: template,
id: 'dashboard',
regions: {
list_region: {
el: 'tbody',
replaceElement: true
}
},
ui: {
'create': 'th button'
},
events: {
'click @ui.create': function (e) {
e.preventDefault();
Controller.showHostForm(new HostModel.Model);
}
},
onRender: function () {
let view = this;
Api.Hosts.getAll()
.then(response => {
if (!view.isDestroyed()) {
if (response && response.length) {
view.showChildView('list_region', new TableBody({
collection: new HostModel.Collection(response)
}));
} else {
view.showChildView('list_region', new EmptyView());
}
view.trigger('loaded');
}
})
.catch(err => {
Controller.showError(err, 'Could not fetch Hosts');
view.trigger('loaded');
});
}
});

View File

@ -0,0 +1,27 @@
<td><a href="<%- ssl ? 'https' : 'http' %>://<%- hostname %>" target="_blank"><%- hostname %></a></td>
<td><span class="monospace"><%- forward_server %>:<%- forward_port %></span></td>
<td>
<% if (ssl && force_ssl) { %>
Forced
<% } else if (ssl) { %>
Enabled
<% } else { %>
No
<% } %>
</td>
<td>
<% if (access_list) { %>
<a href="#" class="access_list"><%- access_list.name %></a>
<% } else { %>
<em>None</em>
<% } %>
</td>
<td class="text-right">
<% if (ssl) { %>
<button type="button" class="btn btn-default btn-xs renew" title="Renew SSL"><i class="fa fa-shield" aria-hidden="true"></i></button>
<% } %>
<button type="button" class="btn btn-default btn-xs reconfigure" title="Reconfigure Nginx"><i class="fa fa-refresh" aria-hidden="true"></i></button>
<button type="button" class="btn btn-default btn-xs advanced" title="Advanced Configuration"><i class="fa fa-code" aria-hidden="true"></i></button>
<button type="button" class="btn btn-warning btn-xs edit" title="Edit"><i class="fa fa-pencil" aria-hidden="true"></i></button>
<button type="button" class="btn btn-danger btn-xs delete" title="Delete"><i class="fa fa-times" aria-hidden="true"></i></button>
</td>

View File

@ -0,0 +1,57 @@
'use strict';
import Mn from 'backbone.marionette';
const template = require('./row.ejs');
const Controller = require('../controller');
const AccessListModel = require('../../models/access');
module.exports = Mn.View.extend({
template: template,
tagName: 'tr',
ui: {
edit: 'button.edit',
delete: 'button.delete',
access_list: 'a.access_list',
reconfigure: 'button.reconfigure',
renew: 'button.renew',
advanced: 'button.advanced'
},
events: {
'click @ui.edit': function (e) {
e.preventDefault();
Controller.showHostForm(this.model);
},
'click @ui.delete': function (e) {
e.preventDefault();
Controller.showDeleteHost(this.model);
},
'click @ui.access_list': function (e) {
e.preventDefault();
Controller.showAccessListForm(new AccessListModel.Model(this.model.get('access_list')));
},
'click @ui.reconfigure': function (e) {
e.preventDefault();
Controller.showReconfigureHost(this.model);
},
'click @ui.renew': function (e) {
e.preventDefault();
Controller.showRenewHost(this.model);
},
'click @ui.advanced': function (e) {
e.preventDefault();
Controller.showAdvancedHost(this.model);
}
},
initialize: function () {
this.listenTo(this.model, 'change', this.render);
}
});

View File

@ -0,0 +1,3 @@
<div class="alert alert-danger" role="alert">
<strong><%- getNiceMessage() %></strong> &ndash; <%- getErrorMessage() %>
</div>

View File

@ -0,0 +1,33 @@
'use strict';
import Mn from 'backbone.marionette';
const template = require('./main.ejs');
module.exports = Mn.View.extend({
id: 'error',
template: template,
options: {
err: null,
nice_msg: 'Unknown error'
},
templateContext: function () {
let view = this;
return {
getNiceMessage: function () {
return view.options.nice_msg;
},
getErrorMessage: function () {
return view.options.err ? view.options.err.message : '';
}
};
},
initialize: function () {
console.error(this.options.err);
}
});

View File

@ -0,0 +1,23 @@
<div class="modal-dialog">
<div class="modal-content">
<form class="form">
<div class="modal-header text-left">
<h4 class="modal-title">Advanced Configuration for <%- hostname %></h4>
</div>
<div class="modal-body">
<p>This section is for advanced users only! If you don't know Nginx configuration backwards, you should abort now.
You might typically use this for configuring additional location sections, ip restrictions or websocket proxying.</p>
<div class="form-group">
<label for="advanced-input">Additional Nginx Configuration (inside server block)</label>
<textarea class="form-control" rows="10" name="advanced" id="advanced-input"><%- advanced %></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-danger save">Save</button>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,39 @@
'use strict';
import Mn from 'backbone.marionette';
const template = require('./advanced.ejs');
const Controller = require('../controller');
const Api = require('../api');
const App = require('../main');
require('jquery-serializejson');
module.exports = Mn.View.extend({
template: template,
ui: {
form: 'form',
buttons: 'form button'
},
events: {
'submit @ui.form': function (e) {
e.preventDefault();
let data = this.ui.form.serializeJSON();
data._id = this.model.get('_id');
this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
Api.Hosts.update(data)
.then((/*result*/) => {
App.UI.closeModal();
Controller.showDashboard();
})
.catch((err) => {
alert(err.message);
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
});
}
}
});

View File

@ -0,0 +1,16 @@
<div class="modal-dialog">
<div class="modal-content">
<form class="form-horizontal">
<div class="modal-header text-left">
<h4 class="modal-title">Delete Host</h4>
</div>
<div class="modal-body">
<p>Are you sure? You cannot undo this.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-danger delete">Yes I'm Sure</button>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,35 @@
'use strict';
import Mn from 'backbone.marionette';
const template = require('./delete.ejs');
const Controller = require('../controller');
const Api = require('../api');
const App = require('../main');
module.exports = Mn.View.extend({
template: template,
ui: {
buttons: 'form button',
delete: 'button.delete'
},
events: {
'click @ui.delete': function (e) {
e.preventDefault();
this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
Api.Hosts.delete(this.model.get('_id'))
.then((/*result*/) => {
App.UI.closeModal();
Controller.showDashboard();
})
.catch(err => {
alert(err.message);
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
});
}
}
});

View File

@ -0,0 +1,84 @@
<div class="modal-dialog">
<div class="modal-content">
<form class="form-horizontal">
<div class="modal-header text-left">
<h4 class="modal-title"><% if (typeof _id !== 'undefined') { %>Edit<% } else { %>Create<% } %> Host</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label class="col-sm-4 control-label">Hostname</label>
<div class="col-sm-8">
<input type="text" class="form-control" placeholder="myhost.example.com" name="hostname" value="<%- hostname %>" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">Forwarding IP</label>
<div class="col-sm-8">
<input type="text" class="form-control" placeholder="192.168.0.1" name="forward_server" value="<%- forward_server %>" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">Forwarding Port</label>
<div class="col-sm-8">
<input type="number" minimum="1" maximum="65535" class="form-control" placeholder="" name="forward_port" value="<%- forward_port %>" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">Access List</label>
<div class="col-sm-8">
<select class="form-control" name="access_list_id">
<option value="">Loading...</option>
</select>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-4 col-sm-8">
<div class="checkbox">
<label>
<input type="checkbox" name="asset_caching" value="true"<%- asset_caching ? ' checked' : '' %>> Enable Asset Caching
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="block_exploits" value="true"<%- block_exploits ? ' checked' : '' %>> Block Common Exploits
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="ssl" value="true"<%- ssl ? ' checked' : '' %>> Enable SSL with Letsencrypt
</label>
</div>
</div>
</div>
<div class="ssl_options"<%= ssl ? '' : ' style="display: none;"' %>>
<div class="form-group">
<label class="col-sm-4 control-label">Letsencrypt Email</label>
<div class="col-sm-8">
<input type="email" class="form-control" placeholder="" name="letsencrypt_email" value="<%- letsencrypt_email %>"<%- ssl ? ' required' : '' %>>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-4 col-sm-8">
<div class="checkbox">
<label>
<input type="checkbox" name="accept_tos" value="true"<%- accept_tos ? ' checked' : '' %><%- ssl ? ' required' : '' %>> I accept the <a href="https://letsencrypt.org/repository/" target="_blank">Letsencrypt Terms of Service</a>
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="force_ssl" value="true"<%- force_ssl ? ' checked' : '' %>> Redirect HTTP to HTTPS
</label>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-success save">Save</button>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,106 @@
'use strict';
import Mn from 'backbone.marionette';
const _ = require('lodash');
const template = require('./form.ejs');
const Controller = require('../controller');
const Api = require('../api');
const App = require('../main');
require('jquery-serializejson');
module.exports = Mn.View.extend({
template: template,
ui: {
form: 'form',
buttons: 'form button',
ssl_options: '.ssl_options',
ssl: 'input[name="ssl"]',
letsencrypt_email: 'input[name="letsencrypt_email"]',
accept_tos: 'input[name="accept_tos"]',
access_list_id: 'select[name="access_list_id"]'
},
events: {
'change @ui.ssl': function (e) {
let inputs = this.ui.letsencrypt_email.add(this.ui.accept_tos);
if (this.ui.ssl.prop('checked')) {
this.ui.ssl_options.show();
inputs.prop('required', true);
} else {
this.ui.ssl_options.hide();
inputs.prop('required', false);
}
},
'submit @ui.form': function (e) {
e.preventDefault();
let data = _.extend({}, this.ui.form.serializeJSON());
// Change text true's to bools
_.map(data, function (val, key) {
if (val === 'true') {
data[key] = true;
}
});
// Port is integer
data.forward_port = parseInt(data.forward_port, 10);
// accept_tos is not required for backend
delete data.accept_tos;
delete data.access_list;
if (!data.ssl) {
delete data.letsencrypt_email;
delete data.force_ssl;
}
this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
let method = Api.Hosts.create;
if (this.model.get('_id')) {
// edit
method = Api.Hosts.update;
data._id = this.model.get('_id');
}
method(data)
.then((/*result*/) => {
App.UI.closeModal();
Controller.showDashboard();
})
.catch((err) => {
alert(err.message);
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
});
}
},
onRender: function () {
let view = this;
Api.Access.getAll()
.then(response => {
if (!view.isDestroyed()) {
view.ui.access_list_id.empty().append($('<option>').val('').text('None (Publicly Accessible)'));
if (response && response.length) {
_.map(response, access => {
view.ui.access_list_id.append($('<option>').val(access._id).text(access.name));
});
}
if (this.model.get('access_list_id')) {
view.ui.access_list_id.val(this.model.get('access_list_id'));
}
}
})
.catch(err => {
alert("Error loading Access Lists!\n\n" + err.message);
App.UI.closeModal();
});
}
});

View File

@ -0,0 +1,17 @@
<div class="modal-dialog">
<div class="modal-content">
<form class="form-horizontal">
<div class="modal-header text-left">
<h4 class="modal-title">Reconfigure Host</h4>
</div>
<div class="modal-body">
<p>This will simply re-create the Nginx config based on it's settings. You shouldn't need to do this under normal circumstances
but if your host isn't working as expected, this may fix it.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-success reconfigure">Reconfigure</button>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,33 @@
'use strict';
import Mn from 'backbone.marionette';
const template = require('./reconfigure.ejs');
const Api = require('../api');
const App = require('../main');
module.exports = Mn.View.extend({
template: template,
ui: {
buttons: 'form button',
reconfigure: 'button.reconfigure'
},
events: {
'click @ui.reconfigure': function (e) {
e.preventDefault();
this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
Api.Hosts.reconfigure(this.model.get('_id'))
.then((/*result*/) => {
App.UI.closeModal();
})
.catch(err => {
alert(err.message);
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
});
}
}
});

View File

@ -0,0 +1,17 @@
<div class="modal-dialog">
<div class="modal-content">
<form class="form-horizontal">
<div class="modal-header text-left">
<h4 class="modal-title">Renew SSL Certificates</h4>
</div>
<div class="modal-body">
<p>This will renew the SSL Certificates for the host. This normally happens automatically however if you notice
SSL working incorrectly, this may fix it.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-success renew">Renew SSL</button>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,33 @@
'use strict';
import Mn from 'backbone.marionette';
const template = require('./renew.ejs');
const Api = require('../api');
const App = require('../main');
module.exports = Mn.View.extend({
template: template,
ui: {
buttons: 'form button',
renew: 'button.renew'
},
events: {
'click @ui.renew': function (e) {
e.preventDefault();
this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
Api.Hosts.renew(this.model.get('_id'))
.then((/*result*/) => {
App.UI.closeModal();
})
.catch(err => {
alert(err.message);
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
});
}
}
});

View File

@ -0,0 +1,120 @@
'use strict';
import $ from 'jquery';
import _ from 'underscore';
import Backbone from 'backbone';
const Mn = require('../lib/marionette');
const Cache = require('./cache');
const Controller = require('./controller');
const Router = require('./router');
const UI = require('./ui/main');
const Api = require('./api');
const App = Mn.Application.extend({
region: '#app',
Cache: Cache,
Api: Api,
UI: null,
Controller: Controller,
version: null,
onStart: function (app, options) {
console.log('Welcome to Nginx Proxy Manager');
let myapp = this;
Api.status()
.then(result => {
this.version = [result.version.major, result.version.minor, result.version.revision].join('.');
})
.then(Api.Bootstrap)
.then(() => {
this.bootstrapTimer();
this.UI = new UI();
this.UI.on('render', () => {
// If successful, start the history and routing
new Router(options);
Backbone.history.start({});
// Remove loading class
$('#app').removeClass('loading');
});
this.getRegion().show(this.UI);
})
.catch(function (err) {
console.info('Not logged in: ', err.message);
myapp.trigger('after:start');
myapp.UI = new UI();
myapp.UI.on('render', () => {
// Remove loading class
myapp.UI.reset();
Controller.showLogin();
});
myapp.getRegion().show(myapp.UI);
});
},
History: {
replace: function (data) {
window.history.replaceState(_.extend(window.history.state || {}, data), document.title);
},
get: function (attr) {
return window.history.state ? window.history.state[attr] : undefined;
}
},
Error: function (code, message, debug) {
let temp = Error.call(this, message);
temp.name = this.name = 'AppError';
this.stack = temp.stack;
this.message = temp.message;
this.code = code;
this.debug = debug;
},
showError: function () {
let ErrorView = Mn.View.extend({
tagName: 'section',
id: 'error',
template: _.template('Error loading stuff. Please reload the app.')
});
this.getRegion().show(new ErrorView());
},
/**
* Bootstraps the user from time to time
*/
bootstrapTimer: function () {
setTimeout(() => {
Api.status()
.then(result => {
let version = [result.version.major, result.version.minor, result.version.revision].join('.');
if (version !== this.version) {
document.location.reload();
}
})
.then(Api.Bootstrap)
.then(() => {
this.bootstrapTimer();
})
.catch(err => {
if (err.message !== 'timeout' && err.code && err.code !== 400) {
console.log(err);
console.error(err.message);
document.location.reload();
} else {
this.bootstrapTimer();
}
});
}, 30 * 1000);
}
});
const app = new App();
module.exports = app;

View File

@ -0,0 +1,15 @@
'use strict';
const Mn = require('../lib/marionette');
const Controller = require('./controller');
module.exports = Mn.AppRouter.extend({
appRoutes: {
access: 'showAccess',
'*default': 'showDashboard'
},
initialize: function () {
this.controller = Controller;
}
});

View File

@ -0,0 +1,19 @@
<nav class="navbar navbar-default navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#" title="Nginx Proxy Manager"><img src="/images/favicon/android-chrome-192x192.png" alt="Nginx Proxy Manager"></a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="#">Hosts</a></li>
<li><a href="#access">Access Lists</a></li>
</ul>
</div>
</div>
</nav>

View File

@ -0,0 +1,37 @@
'use strict';
import Mn from 'backbone.marionette';
const template = require('./main.ejs');
const Controller = require('../../controller');
module.exports = Mn.View.extend({
template: template,
ui: {
logo: '.navbar-brand',
links: 'a[href^="#"]'
},
events: {
'click @ui.links': function (e) {
e.preventDefault();
let href = e.target.href.replace(/[^#]*#/g, '');
switch (href) {
case 'dashboard':
Controller.showDashboard();
break;
case 'access':
Controller.showAccess();
break;
default:
Controller.showDashboard();
break;
}
}
}
});

View File

@ -0,0 +1,2 @@
<section id="header"></section>
<section id="main" class="container"></section>

View File

@ -0,0 +1,101 @@
'use strict';
import Mn from 'backbone.marionette';
const template = require('./main.ejs');
const HeaderView = require('./header/main');
const Cache = require('../cache');
require('bootstrap');
module.exports = Mn.View.extend({
template: template,
modal: null,
ui: {
header_region: '#header',
main_region: '#main',
modal_region: '#modal-dialog',
main_loader_region: '#main-loader'
},
regions: {
header_region: '@ui.header_region',
main_region: '@ui.main_region',
modal_region: '@ui.modal_region',
main_loader_region: '@ui.main_loader_region'
},
showMainLoading: function () {
this.ui.main_loader_region.show();
},
hideMainLoading: function () {
this.ui.main_loader_region.hide();
},
/**
*
* @param view
* @param [show_callback]
* @param [shown_callback]
*/
showModalDialog: function (view, show_callback, shown_callback) {
this.showChildView('modal_region', view);
this.modal.modal('show');
let ui = this;
this.modal.on('hidden.bs.modal', function (/*e*/) {
if (show_callback) {
ui.modal.off('show.bs.modal', show_callback);
}
if (shown_callback) {
ui.modal.off('shown.bs.modal', shown_callback);
}
ui.modal.off('hidden.bs.modal');
view.destroy();
});
if (show_callback) {
this.modal.on('show.bs.modal', show_callback);
}
if (shown_callback) {
this.modal.on('shown.bs.modal', shown_callback);
}
},
/**
*
* @param [hidden_callback]
*/
closeModal: function (hidden_callback) {
this.modal.modal('hide');
if (hidden_callback) {
this.modal.on('hidden.bs.modal', hidden_callback);
}
},
onRender: function () {
this.showChildView('header_region', new HeaderView({
model: Cache.User
}));
if (this.modal === null) {
this.modal = $('#modal-dialog');
this.modal.modal({
show: false
});
}
},
reset: function () {
this.getRegion('header_region').reset();
this.getRegion('modal_region').reset();
}
});