WIP: started adding new host type ssl passthrough

This commit is contained in:
chaptergy
2021-10-10 23:49:07 +02:00
parent 85128f08f3
commit 5b1f0cead1
35 changed files with 1743 additions and 49 deletions

View File

@ -515,6 +515,67 @@ module.exports = {
}
},
SslPassthroughHosts: {
/**
* @param {Array} [expand]
* @param {String} [query]
* @returns {Promise}
*/
getAll: function (expand, query) {
return getAllObjects('nginx/ssl-passthrough-hosts', expand, query);
},
/**
* @param {Object} data
*/
create: function (data) {
return fetch('post', 'nginx/ssl-passthrough-hosts', data);
},
/**
* @param {Object} data
* @param {Number} data.id
* @returns {Promise}
*/
update: function (data) {
let id = data.id;
delete data.id;
return fetch('put', 'nginx/ssl-passthrough-hosts/' + id, data);
},
/**
* @param {Number} id
* @returns {Promise}
*/
delete: function (id) {
return fetch('delete', 'nginx/ssl-passthrough-hosts/' + id);
},
/**
* @param {Number} id
* @returns {Promise}
*/
get: function (id) {
return fetch('get', 'nginx/ssl-passthrough-hosts/' + id);
},
/**
* @param {Number} id
* @returns {Promise}
*/
enable: function (id) {
return fetch('post', 'nginx/ssl-passthrough-hosts/' + id + '/enable');
},
/**
* @param {Number} id
* @returns {Promise}
*/
disable: function (id) {
return fetch('post', 'nginx/ssl-passthrough-hosts/' + id + '/disable');
}
},
DeadHosts: {
/**
* @param {Array} [expand]

View File

@ -221,6 +221,46 @@ module.exports = {
}
},
/**
* Nginx SSL Passthrough Hosts
*/
showNginxSslPassthrough: function () {
if (Cache.User.isAdmin() || Cache.User.canView('ssl_passthrough_hosts')) {
let controller = this;
require(['./main', './nginx/ssl-passthrough/main'], (App, View) => {
controller.navigate('/nginx/ssl-passthrough');
App.UI.showAppContent(new View());
});
}
},
/**
* SSL Passthrough Hosts Form
*
* @param [model]
*/
showNginxSslPassthroughForm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('ssl_passthrough_hosts')) {
require(['./main', './nginx/ssl-passthrough/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* SSL Passthrough Hosts Delete Confirm
*
* @param model
*/
showNginxSslPassthroughConfirm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('ssl_passthrough_hosts')) {
require(['./main', './nginx/ssl-passthrough/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Nginx Dead Hosts
*/

View File

@ -0,0 +1,19 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><%- i18n('ssl-passthrough-hosts', 'delete') %></h5>
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal">&nbsp;</button>
</div>
<div class="modal-body">
<form>
<div class="row">
<div class="col-sm-12 col-md-12">
<%= i18n('ssl-passthrough-hosts', 'delete-confirm') %>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button>
<button type="button" class="btn btn-danger save"><%- i18n('str', 'sure') %></button>
</div>
</div>

View File

@ -0,0 +1,32 @@
const Mn = require('backbone.marionette');
const App = require('../../main');
const template = require('./delete.ejs');
module.exports = Mn.View.extend({
template: template,
className: 'modal-dialog',
ui: {
form: 'form',
buttons: '.modal-footer button',
cancel: 'button.cancel',
save: 'button.save'
},
events: {
'click @ui.save': function (e) {
e.preventDefault();
App.Api.Nginx.SslPassthroughHosts.delete(this.model.get('id'))
.then(() => {
App.Controller.showNginxSslPassthrough();
App.UI.closeModal();
})
.catch(err => {
alert(err.message);
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
});
}
}
});

View File

@ -0,0 +1,34 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><%- i18n('ssl-passthrough-hosts', 'form-title', {id: id}) %></h5>
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal">&nbsp;</button>
</div>
<div class="modal-body">
<form>
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="form-label"><%- i18n('all-hosts', 'domain-names') %> <span class="form-required">*</span></label>
<input type="text" name="domain_name" class="form-control" id="input-domain" placeholder="example.com" value="<%- domain_name %>" required>
</div>
</div>
<div class="col-sm-8 col-md-8">
<div class="form-group">
<label class="form-label"><%- i18n('ssl-passthrough-hosts', 'forwarding-host') %><span class="form-required">*</span></label>
<input type="text" name="forwarding_host" class="form-control text-monospace" placeholder="example.com or 10.0.0.1 or 2001:db8:3333:4444:5555:6666:7777:8888" value="<%- forwarding_host %>" autocomplete="off" maxlength="255" required>
</div>
</div>
<div class="col-sm-4 col-md-4">
<div class="form-group">
<label class="form-label"><%- i18n('ssl-passthrough-hosts', 'forwarding-port') %> <span class="form-required">*</span></label>
<input name="forwarding_port" type="number" class="form-control text-monospace" placeholder="eg: 80" value="<%- forwarding_port %>" required>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button>
<button type="button" class="btn btn-teal save"><%- i18n('str', 'save') %></button>
</div>
</div>

View File

@ -0,0 +1,77 @@
const Mn = require('backbone.marionette');
const App = require('../../main');
const SslPassthroughModel = require('../../../models/ssl-passthrough-host');
const template = require('./form.ejs');
require('jquery-serializejson');
require('jquery-mask-plugin');
require('selectize');
module.exports = Mn.View.extend({
template: template,
className: 'modal-dialog',
ui: {
form: 'form',
forwarding_host: 'input[name="forwarding_host"]',
type_error: '.forward-type-error',
buttons: '.modal-footer button',
switches: '.custom-switch-input',
cancel: 'button.cancel',
save: 'button.save'
},
events: {
'change @ui.switches': function () {
this.ui.type_error.hide();
},
'click @ui.save': function (e) {
e.preventDefault();
if (!this.ui.form[0].checkValidity()) {
$('<input type="submit">').hide().appendTo(this.ui.form).click().remove();
return;
}
let view = this;
let data = this.ui.form.serializeJSON();
// Manipulate
data.incoming_port = parseInt(data.incoming_port, 10);
data.forwarding_port = parseInt(data.forwarding_port, 10);
let method = App.Api.Nginx.SslPassthroughHosts.create;
let is_new = true;
if (this.model.get('id')) {
// edit
is_new = false;
method = App.Api.Nginx.SslPassthroughHosts.update;
data.id = this.model.get('id');
}
this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
method(data)
.then(result => {
view.model.set(result);
App.UI.closeModal(function () {
if (is_new) {
App.Controller.showNginxSslPassthrough();
}
});
})
.catch(err => {
alert(err.message);
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
});
}
},
initialize: function (options) {
if (typeof options.model === 'undefined' || !options.model) {
this.model = new SslPassthroughModel.Model();
}
}
});

View File

@ -0,0 +1,43 @@
<td class="text-center">
<div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>">
<span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span>
</div>
</td>
<td>
<div class="text-monospace">
<%- domain_name %>
</div>
<div class="small text-muted">
<%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %>
</div>
</td>
<td>
<div class="text-monospace"><%- forwarding_host %>:<%- forwarding_port %></div>
</td>
<td>
<%
var o = isOnline();
if (!enabled) { %>
<span class="status-icon bg-warning"></span> <%- i18n('str', 'disabled') %>
<% } else if (o === true) { %>
<span class="status-icon bg-success"></span> <%- i18n('str', 'online') %>
<% } else if (o === false) { %>
<span title="<%- getOfflineError() %>"><span class="status-icon bg-danger"></span> <%- i18n('str', 'offline') %></span>
<% } else { %>
<span class="status-icon bg-warning"></span> <%- i18n('str', 'unknown') %>
<% } %>
</td>
<% if (canManage) { %>
<td class="text-right">
<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">
<span class="dropdown-header"><%- i18n('audit-log', 'ssl-passthrough-host') %> #<%- id %></span>
<a href="#" class="edit dropdown-item"><i class="dropdown-icon fe fe-edit"></i> <%- i18n('str', 'edit') %></a>
<a href="#" class="able dropdown-item"><i class="dropdown-icon fe fe-power"></i> <%- i18n('str', enabled ? 'disable' : 'enable') %></a>
<div class="dropdown-divider"></div>
<a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a>
</div>
</div>
</td>
<% } %>

View File

@ -0,0 +1,54 @@
const Mn = require('backbone.marionette');
const App = require('../../../main');
const template = require('./item.ejs');
module.exports = Mn.View.extend({
template: template,
tagName: 'tr',
ui: {
able: 'a.able',
edit: 'a.edit',
delete: 'a.delete'
},
events: {
'click @ui.able': function (e) {
e.preventDefault();
let id = this.model.get('id');
App.Api.Nginx.SslPassthroughHosts[this.model.get('enabled') ? 'disable' : 'enable'](id)
.then(() => {
return App.Api.Nginx.SslPassthroughHosts.get(id)
.then(row => {
this.model.set(row);
});
});
},
'click @ui.edit': function (e) {
e.preventDefault();
App.Controller.showNginxSslPassthroughForm(this.model);
},
'click @ui.delete': function (e) {
e.preventDefault();
App.Controller.showNginxSslPassthroughDeleteConfirm(this.model);
}
},
templateContext: {
canManage: App.Cache.User.canManage('ssl_passthrough_hosts'),
isOnline: function () {
return typeof this.meta.nginx_online === 'undefined' ? null : this.meta.nginx_online;
},
getOfflineError: function () {
return this.meta.nginx_err || '';
}
},
initialize: function () {
this.listenTo(this.model, 'change', this.render);
}
});

View File

@ -0,0 +1,12 @@
<thead>
<th width="30">&nbsp;</th>
<th><%- i18n('ssl-passthrough-hosts', 'domain-name') %></th>
<th><%- i18n('str', 'destination') %></th>
<th><%- i18n('str', 'status') %></th>
<% if (canManage) { %>
<th>&nbsp;</th>
<% } %>
</thead>
<tbody>
<!-- items -->
</tbody>

View File

@ -0,0 +1,32 @@
const Mn = require('backbone.marionette');
const App = require('../../../main');
const ItemView = require('./item');
const template = require('./main.ejs');
const TableBody = Mn.CollectionView.extend({
tagName: 'tbody',
childView: ItemView
});
module.exports = Mn.View.extend({
tagName: 'table',
className: 'table table-hover table-outline table-vcenter card-table',
template: template,
regions: {
body: {
el: 'tbody',
replaceElement: true
}
},
templateContext: {
canManage: App.Cache.User.canManage('ssl_passthrough_hosts')
},
onRender: function () {
this.showChildView('body', new TableBody({
collection: this.collection
}));
}
});

View File

@ -0,0 +1,20 @@
<div class="card">
<div class="card-status bg-blue"></div>
<div class="card-header">
<h3 class="card-title"><%- i18n('ssl-passthrough-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-blue btn-sm ml-2 add-item"><%- i18n('ssl-passthrough-hosts', 'add') %></a>
<% } %>
</div>
</div>
<div class="card-body no-padding min-100">
<div class="dimmer active">
<div class="loader"></div>
<div class="dimmer-content list-region">
<!-- List Region -->
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,81 @@
const Mn = require('backbone.marionette');
const App = require('../../main');
const SslPassthroughModel = require('../../../models/ssl-passthrough-host');
const ListView = require('./list/main');
const ErrorView = require('../../error/main');
const EmptyView = require('../../empty/main');
const template = require('./main.ejs');
module.exports = Mn.View.extend({
id: 'nginx-ssl-passthrough',
template: template,
ui: {
list_region: '.list-region',
add: '.add-item',
help: '.help',
dimmer: '.dimmer'
},
regions: {
list_region: '@ui.list_region'
},
events: {
'click @ui.add': function (e) {
e.preventDefault();
App.Controller.showNginxSslPassthroughForm();
},
'click @ui.help': function (e) {
e.preventDefault();
App.Controller.showHelp(App.i18n('ssl-passthrough-hosts', 'help-title'), App.i18n('ssl-passthrough-hosts', 'help-content'));
}
},
templateContext: {
showAddButton: App.Cache.User.canManage('ssl_passthrough_hosts')
},
onRender: function () {
let view = this;
App.Api.Nginx.SslPassthroughHosts.getAll(['owner'])
.then(response => {
if (!view.isDestroyed()) {
if (response && response.length) {
view.showChildView('list_region', new ListView({
collection: new SslPassthroughModel.Collection(response)
}));
} else {
let manage = App.Cache.User.canManage('ssl_passthrough_hosts');
view.showChildView('list_region', new EmptyView({
title: App.i18n('ssl-passthrough-hosts', 'empty'),
subtitle: App.i18n('all-hosts', 'empty-subtitle', {manage: manage}),
link: manage ? App.i18n('ssl_passthrough_hosts', 'add') : null,
btn_color: 'blue',
permission: 'ssl-passthrough-hosts',
action: function () {
App.Controller.showNginxSslPassthroughForm();
}
}));
}
}
})
.catch(err => {
view.showChildView('list_region', new ErrorView({
code: err.code,
message: err.message,
retry: function () {
App.Controller.showNginxSslPassthrough();
}
}));
console.error(err);
})
.then(() => {
view.ui.dimmer.removeClass('active');
});
}
});

View File

@ -4,16 +4,17 @@ const Controller = require('./controller');
module.exports = AppRouter.default.extend({
controller: Controller,
appRoutes: {
users: 'showUsers',
logout: 'logout',
'nginx/proxy': 'showNginxProxy',
'nginx/redirection': 'showNginxRedirection',
'nginx/404': 'showNginxDead',
'nginx/stream': 'showNginxStream',
'nginx/access': 'showNginxAccess',
'nginx/certificates': 'showNginxCertificates',
'audit-log': 'showAuditLog',
'settings': 'showSettings',
'*default': 'showDashboard'
users: 'showUsers',
logout: 'logout',
'nginx/proxy': 'showNginxProxy',
'nginx/redirection': 'showNginxRedirection',
'nginx/404': 'showNginxDead',
'nginx/ssl-passthrough': 'showNginxSslPassthrough',
'nginx/stream': 'showNginxStream',
'nginx/access': 'showNginxAccess',
'nginx/certificates': 'showNginxCertificates',
'audit-log': 'showAuditLog',
'settings': 'showSettings',
'*default': 'showDashboard'
}
});

View File

@ -115,6 +115,19 @@
"processing-info": "Processing... This might take a few minutes.",
"passphrase-protection-support-info": "Key files protected with a passphrase are not supported."
},
"ssl-passthrough-hosts": {
"title": "SSL Passthrough Hosts",
"empty": "There are no SSL Passthrough Hosts",
"add": "Add SSL Passthrough Hosts",
"form-title": "{id, select, undefined{New} other{Edit}} SSL Passthrough Host",
"domain-name": "Domain Name",
"forwarding-host": "Forward Host",
"forwarding-port": "Forward Port",
"delete": "Delete SSL Passthrough Host",
"delete-confirm": "Are you sure you want to delete this SSL Passthrough Host?",
"help-title": "What is a SSL Passthrough Host?",
"help-content": "An SSL Passthrough Host will allow you to proxy a server without SSL termination. This means the SSL encryption of the server will be passed right through the proxy, retaining the upstream certificates.\nThough this also means the proxy does not know anything about the traffic, and it just relies on an SSL feature called Server Name Indication, to know where to send this packet. This also means, if the client does not provide this additional information, accessing the site through the proxy won't be possible. However most modern browsers include this information in HTTP requests.\n\nHowever using SSL Passthrough comes with a performance penalty, since all hosts (including normal proxy hosts) now have to pass through this additional step of checking the destination. If you do not need your service to be available on port 443, it is recommended to use a stream host instead."
},
"proxy-hosts": {
"title": "Proxy Hosts",
"empty": "There are no Proxy Hosts",
@ -248,6 +261,7 @@
"proxy-host": "Proxy Host",
"redirection-host": "Redirection Host",
"dead-host": "404 Host",
"ssl-passthrough-host": "SSL Passthrough Host",
"stream": "Stream",
"user": "User",
"certificate": "Certificate",

View File

@ -0,0 +1,27 @@
const Backbone = require('backbone');
const model = Backbone.Model.extend({
idAttribute: 'id',
defaults: function () {
return {
id: undefined,
created_on: null,
modified_on: null,
domain_name: null,
forwarding_host: null,
forwarding_port: null,
enabled: true,
meta: {},
// The following are expansions:
owner: null
};
}
});
module.exports = {
Model: model,
Collection: Backbone.Collection.extend({
model: model
})
};