mirror of
https://github.com/xiaoxinpro/nginx-proxy-manager-zh.git
synced 2025-02-02 09:48:13 -05:00
Audit Log items, backend stuff, help pages
This commit is contained in:
parent
a43c2d74bf
commit
66e25e315b
@ -51,7 +51,9 @@ http {
|
|||||||
access_log /data/logs/default.log proxy;
|
access_log /data/logs/default.log proxy;
|
||||||
|
|
||||||
include /etc/nginx/conf.d/*.conf;
|
include /etc/nginx/conf.d/*.conf;
|
||||||
include /data/nginx/*.conf;
|
include /data/nginx/proxy_host/*.conf;
|
||||||
|
include /data/nginx/redirection_host/*.conf;
|
||||||
|
include /data/nginx/dead_host/*.conf;
|
||||||
}
|
}
|
||||||
|
|
||||||
stream {
|
stream {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
mkdir -p /tmp/nginx \
|
mkdir -p /tmp/nginx \
|
||||||
/data/{nginx,logs,access} \
|
/data/{nginx,logs,access} \
|
||||||
/data/nginx/stream \
|
/data/nginx/{proxy_host,redirection_host,stream,dead_host} \
|
||||||
/var/lib/nginx/cache/{public,private}
|
/var/lib/nginx/cache/{public,private}
|
||||||
|
|
||||||
chown root /tmp/nginx
|
chown root /tmp/nginx
|
||||||
|
@ -1,19 +1,10 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const error = require('../lib/error');
|
||||||
const auditLogModel = require('../models/audit-log');
|
const auditLogModel = require('../models/audit-log');
|
||||||
|
|
||||||
const internalAuditLog = {
|
const internalAuditLog = {
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal use only
|
|
||||||
*
|
|
||||||
* @param {Object} data
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
create: data => {
|
|
||||||
// TODO
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All logs
|
* All logs
|
||||||
*
|
*
|
||||||
@ -28,16 +19,14 @@ const internalAuditLog = {
|
|||||||
let query = auditLogModel
|
let query = auditLogModel
|
||||||
.query()
|
.query()
|
||||||
.orderBy('created_on', 'DESC')
|
.orderBy('created_on', 'DESC')
|
||||||
.limit(100);
|
.limit(100)
|
||||||
|
.allowEager('[user]');
|
||||||
|
|
||||||
// Query is used for searching
|
// Query is used for searching
|
||||||
if (typeof search_query === 'string') {
|
if (typeof search_query === 'string') {
|
||||||
/*
|
|
||||||
query.where(function () {
|
query.where(function () {
|
||||||
this.where('name', 'like', '%' + search_query + '%')
|
this.where('meta', 'like', '%' + search_query + '%');
|
||||||
.orWhere('email', 'like', '%' + search_query + '%');
|
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof expand !== 'undefined' && expand !== null) {
|
if (typeof expand !== 'undefined' && expand !== null) {
|
||||||
@ -46,6 +35,44 @@ const internalAuditLog = {
|
|||||||
|
|
||||||
return query;
|
return query;
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method should not be publicly used, it doesn't check certain things. It will be assumed
|
||||||
|
* that permission to add to audit log is already considered, however the access token is used for
|
||||||
|
* default user id determination.
|
||||||
|
*
|
||||||
|
* @param {Access} access
|
||||||
|
* @param {Object} data
|
||||||
|
* @param {String} data.action
|
||||||
|
* @param {Integer} [data.user_id]
|
||||||
|
* @param {Integer} [data.object_id]
|
||||||
|
* @param {Integer} [data.object_type]
|
||||||
|
* @param {Object} [data.meta]
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
add: (access, data) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// Default the user id
|
||||||
|
if (typeof data.user_id === 'undefined' || !data.user_id) {
|
||||||
|
data.user_id = access.token.get('attrs').id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof data.action === 'undefined' || !data.action) {
|
||||||
|
reject(new error.InternalValidationError('Audit log entry must contain an Action'));
|
||||||
|
} else {
|
||||||
|
// Make sure at least 1 of the IDs are set and action
|
||||||
|
resolve(auditLogModel
|
||||||
|
.query()
|
||||||
|
.insert({
|
||||||
|
user_id: data.user_id,
|
||||||
|
action: data.action,
|
||||||
|
object_type: data.object_type || '',
|
||||||
|
object_id: data.object_id || 0,
|
||||||
|
meta: data.meta || {}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ const _ = require('lodash');
|
|||||||
const error = require('../lib/error');
|
const error = require('../lib/error');
|
||||||
const deadHostModel = require('../models/dead_host');
|
const deadHostModel = require('../models/dead_host');
|
||||||
const internalHost = require('./host');
|
const internalHost = require('./host');
|
||||||
|
const internalAuditLog = require('./audit-log');
|
||||||
|
|
||||||
function omissions () {
|
function omissions () {
|
||||||
return ['is_deleted'];
|
return ['is_deleted'];
|
||||||
@ -49,8 +50,17 @@ const internalDeadHost = {
|
|||||||
.insertAndFetch(data);
|
.insertAndFetch(data);
|
||||||
})
|
})
|
||||||
.then(row => {
|
.then(row => {
|
||||||
|
// Add to audit log
|
||||||
|
return internalAuditLog.add(access, {
|
||||||
|
action: 'created',
|
||||||
|
object_type: 'dead-host',
|
||||||
|
object_id: row.id,
|
||||||
|
meta: data
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
return _.omit(row, omissions());
|
return _.omit(row, omissions());
|
||||||
});
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -97,9 +107,19 @@ const internalDeadHost = {
|
|||||||
.patchAndFetchById(row.id, data)
|
.patchAndFetchById(row.id, data)
|
||||||
.then(saved_row => {
|
.then(saved_row => {
|
||||||
saved_row.meta = internalHost.cleanMeta(saved_row.meta);
|
saved_row.meta = internalHost.cleanMeta(saved_row.meta);
|
||||||
|
|
||||||
|
// Add to audit log
|
||||||
|
return internalAuditLog.add(access, {
|
||||||
|
action: 'updated',
|
||||||
|
object_type: 'dead-host',
|
||||||
|
object_id: row.id,
|
||||||
|
meta: data
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
return _.omit(saved_row, omissions());
|
return _.omit(saved_row, omissions());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -171,6 +191,17 @@ const internalDeadHost = {
|
|||||||
.where('id', row.id)
|
.where('id', row.id)
|
||||||
.patch({
|
.patch({
|
||||||
is_deleted: 1
|
is_deleted: 1
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// Add to audit log
|
||||||
|
row.meta = internalHost.cleanMeta(row.meta);
|
||||||
|
|
||||||
|
return internalAuditLog.add(access, {
|
||||||
|
action: 'deleted',
|
||||||
|
object_type: 'dead-host',
|
||||||
|
object_id: row.id,
|
||||||
|
meta: _.omit(row, omissions())
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -200,8 +231,16 @@ const internalDeadHost = {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(row => {
|
.then(row => {
|
||||||
|
return internalAuditLog.add(access, {
|
||||||
|
action: 'updated',
|
||||||
|
object_type: 'dead-host',
|
||||||
|
object_id: row.id,
|
||||||
|
meta: data
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
return _.pick(row.meta, internalHost.allowed_ssl_files);
|
return _.pick(row.meta, internalHost.allowed_ssl_files);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
92
src/backend/internal/nginx.js
Normal file
92
src/backend/internal/nginx.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const Liquid = require('liquidjs');
|
||||||
|
const logger = require('../logger').nginx;
|
||||||
|
const utils = require('../lib/utils');
|
||||||
|
const error = require('../lib/error');
|
||||||
|
|
||||||
|
const internalNginx = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
test: () => {
|
||||||
|
logger.info('Testing Nginx configuration');
|
||||||
|
return utils.exec('/usr/sbin/nginx -t');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
reload: () => {
|
||||||
|
return internalNginx.test()
|
||||||
|
.then(() => {
|
||||||
|
logger.info('Reloading Nginx');
|
||||||
|
return utils.exec('/usr/sbin/nginx -s reload');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String} host_type
|
||||||
|
* @param {Integer} host_id
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
getConfigName: (host_type, host_id) => {
|
||||||
|
host_type = host_type.replace(new RegExp('-', 'g'), '_');
|
||||||
|
return '/data/nginx/' + host_type + '/' + host_id + '.conf';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String} host_type
|
||||||
|
* @param {Object} host
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
generateConfig: (host_type, host) => {
|
||||||
|
let renderEngine = Liquid();
|
||||||
|
host_type = host_type.replace(new RegExp('-', 'g'), '_');
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let template = null;
|
||||||
|
let filename = internalNginx.getConfigName(host_type, host.id);
|
||||||
|
try {
|
||||||
|
template = fs.readFileSync(__dirname + '/../templates/' + host_type + '.conf', {encoding: 'utf8'});
|
||||||
|
} catch (err) {
|
||||||
|
reject(new error.ConfigurationError(err.message));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderEngine
|
||||||
|
.parseAndRender(template, host)
|
||||||
|
.then(config_text => {
|
||||||
|
fs.writeFileSync(filename, config_text, {encoding: 'utf8'});
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
throw new error.ConfigurationError(err.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String} host_type
|
||||||
|
* @param {Object} host
|
||||||
|
* @param {Boolean} [throw_errors]
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
deleteConfig: (host_type, host, throw_errors) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(internalNginx.getConfigName(host_type, host.id));
|
||||||
|
} catch (err) {
|
||||||
|
if (throw_errors) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = internalNginx;
|
@ -4,6 +4,7 @@ const _ = require('lodash');
|
|||||||
const error = require('../lib/error');
|
const error = require('../lib/error');
|
||||||
const proxyHostModel = require('../models/proxy_host');
|
const proxyHostModel = require('../models/proxy_host');
|
||||||
const internalHost = require('./host');
|
const internalHost = require('./host');
|
||||||
|
const internalAuditLog = require('./audit-log');
|
||||||
|
|
||||||
function omissions () {
|
function omissions () {
|
||||||
return ['is_deleted'];
|
return ['is_deleted'];
|
||||||
@ -49,8 +50,17 @@ const internalProxyHost = {
|
|||||||
.insertAndFetch(data);
|
.insertAndFetch(data);
|
||||||
})
|
})
|
||||||
.then(row => {
|
.then(row => {
|
||||||
|
// Add to audit log
|
||||||
|
return internalAuditLog.add(access, {
|
||||||
|
action: 'created',
|
||||||
|
object_type: 'proxy-host',
|
||||||
|
object_id: row.id,
|
||||||
|
meta: data
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
return _.omit(row, omissions());
|
return _.omit(row, omissions());
|
||||||
});
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -97,9 +107,19 @@ const internalProxyHost = {
|
|||||||
.patchAndFetchById(row.id, data)
|
.patchAndFetchById(row.id, data)
|
||||||
.then(saved_row => {
|
.then(saved_row => {
|
||||||
saved_row.meta = internalHost.cleanMeta(saved_row.meta);
|
saved_row.meta = internalHost.cleanMeta(saved_row.meta);
|
||||||
|
|
||||||
|
// Add to audit log
|
||||||
|
return internalAuditLog.add(access, {
|
||||||
|
action: 'updated',
|
||||||
|
object_type: 'proxy-host',
|
||||||
|
object_id: row.id,
|
||||||
|
meta: data
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
return _.omit(saved_row, omissions());
|
return _.omit(saved_row, omissions());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -171,6 +191,17 @@ const internalProxyHost = {
|
|||||||
.where('id', row.id)
|
.where('id', row.id)
|
||||||
.patch({
|
.patch({
|
||||||
is_deleted: 1
|
is_deleted: 1
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// Add to audit log
|
||||||
|
row.meta = internalHost.cleanMeta(row.meta);
|
||||||
|
|
||||||
|
return internalAuditLog.add(access, {
|
||||||
|
action: 'deleted',
|
||||||
|
object_type: 'proxy-host',
|
||||||
|
object_id: row.id,
|
||||||
|
meta: _.omit(row, omissions())
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -200,8 +231,16 @@ const internalProxyHost = {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(row => {
|
.then(row => {
|
||||||
|
return internalAuditLog.add(access, {
|
||||||
|
action: 'updated',
|
||||||
|
object_type: 'proxy-host',
|
||||||
|
object_id: row.id,
|
||||||
|
meta: data
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
return _.pick(row.meta, internalHost.allowed_ssl_files);
|
return _.pick(row.meta, internalHost.allowed_ssl_files);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4,6 +4,7 @@ const _ = require('lodash');
|
|||||||
const error = require('../lib/error');
|
const error = require('../lib/error');
|
||||||
const redirectionHostModel = require('../models/redirection_host');
|
const redirectionHostModel = require('../models/redirection_host');
|
||||||
const internalHost = require('./host');
|
const internalHost = require('./host');
|
||||||
|
const internalAuditLog = require('./audit-log');
|
||||||
|
|
||||||
function omissions () {
|
function omissions () {
|
||||||
return ['is_deleted'];
|
return ['is_deleted'];
|
||||||
@ -49,8 +50,17 @@ const internalRedirectionHost = {
|
|||||||
.insertAndFetch(data);
|
.insertAndFetch(data);
|
||||||
})
|
})
|
||||||
.then(row => {
|
.then(row => {
|
||||||
|
// Add to audit log
|
||||||
|
return internalAuditLog.add(access, {
|
||||||
|
action: 'created',
|
||||||
|
object_type: 'redirection-host',
|
||||||
|
object_id: row.id,
|
||||||
|
meta: data
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
return _.omit(row, omissions());
|
return _.omit(row, omissions());
|
||||||
});
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -97,9 +107,19 @@ const internalRedirectionHost = {
|
|||||||
.patchAndFetchById(row.id, data)
|
.patchAndFetchById(row.id, data)
|
||||||
.then(saved_row => {
|
.then(saved_row => {
|
||||||
saved_row.meta = internalHost.cleanMeta(saved_row.meta);
|
saved_row.meta = internalHost.cleanMeta(saved_row.meta);
|
||||||
|
|
||||||
|
// Add to audit log
|
||||||
|
return internalAuditLog.add(access, {
|
||||||
|
action: 'updated',
|
||||||
|
object_type: 'redirection-host',
|
||||||
|
object_id: row.id,
|
||||||
|
meta: data
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
return _.omit(saved_row, omissions());
|
return _.omit(saved_row, omissions());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -171,6 +191,17 @@ const internalRedirectionHost = {
|
|||||||
.where('id', row.id)
|
.where('id', row.id)
|
||||||
.patch({
|
.patch({
|
||||||
is_deleted: 1
|
is_deleted: 1
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// Add to audit log
|
||||||
|
row.meta = internalHost.cleanMeta(row.meta);
|
||||||
|
|
||||||
|
return internalAuditLog.add(access, {
|
||||||
|
action: 'deleted',
|
||||||
|
object_type: 'redirection-host',
|
||||||
|
object_id: row.id,
|
||||||
|
meta: _.omit(row, omissions())
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -200,8 +231,16 @@ const internalRedirectionHost = {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(row => {
|
.then(row => {
|
||||||
|
return internalAuditLog.add(access, {
|
||||||
|
action: 'updated',
|
||||||
|
object_type: 'redirection-host',
|
||||||
|
object_id: row.id,
|
||||||
|
meta: data
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
return _.pick(row.meta, internalHost.allowed_ssl_files);
|
return _.pick(row.meta, internalHost.allowed_ssl_files);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
163
src/backend/internal/ssl.js
Normal file
163
src/backend/internal/ssl.js
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const Liquid = require('liquidjs');
|
||||||
|
const timestamp = require('unix-timestamp');
|
||||||
|
const internalNginx = require('./nginx');
|
||||||
|
const logger = require('../logger').ssl;
|
||||||
|
const utils = require('../lib/utils');
|
||||||
|
const error = require('../lib/error');
|
||||||
|
|
||||||
|
timestamp.round = true;
|
||||||
|
|
||||||
|
const internalSsl = {
|
||||||
|
|
||||||
|
interval_timeout: 1000 * 60 * 60 * 12, // 12 hours
|
||||||
|
interval: null,
|
||||||
|
interval_processing: false,
|
||||||
|
|
||||||
|
initTimer: () => {
|
||||||
|
internalSsl.interval = setInterval(internalSsl.processExpiringHosts, internalSsl.interval_timeout);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggered by a timer, this will check for expiring hosts and renew their ssl certs if required
|
||||||
|
*/
|
||||||
|
processExpiringHosts: () => {
|
||||||
|
if (!internalSsl.interval_processing) {
|
||||||
|
logger.info('Renewing SSL certs close to expiry...');
|
||||||
|
return utils.exec('/usr/bin/certbot renew -q')
|
||||||
|
.then(result => {
|
||||||
|
logger.info(result);
|
||||||
|
internalSsl.interval_processing = false;
|
||||||
|
|
||||||
|
return internalNginx.reload()
|
||||||
|
.then(() => {
|
||||||
|
logger.info('Renew Complete');
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
logger.error(err);
|
||||||
|
internalSsl.interval_processing = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String} host_type
|
||||||
|
* @param {Object} host
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
hasValidSslCerts: (host_type, host) => {
|
||||||
|
host_type = host_type.replace(new RegExp('-', 'g'), '_');
|
||||||
|
let le_path = '/etc/letsencrypt/live/' + host_type + '_' + host.id;
|
||||||
|
|
||||||
|
return fs.existsSync(le_path + '/fullchain.pem') && fs.existsSync(le_path + '/privkey.pem');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String} host_type
|
||||||
|
* @param {Object} host
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
requestSsl: (host_type, host) => {
|
||||||
|
logger.info('Requesting SSL certificates for ' + host_type + ' #' + host.id);
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
return utils.exec('/usr/bin/letsencrypt certonly --agree-tos --email "' + host.letsencrypt_email + '" -n -a webroot -d "' + host.hostname + '"')
|
||||||
|
.then(result => {
|
||||||
|
logger.info(result);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String} host_type
|
||||||
|
* @param {Object} host
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
renewSsl: (host_type, host) => {
|
||||||
|
logger.info('Renewing SSL certificates for ' + host_type + ' #' + host.id);
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
return utils.exec('/usr/bin/certbot renew --force-renewal --disable-hook-validation --cert-name "' + host.hostname + '"')
|
||||||
|
.then(result => {
|
||||||
|
logger.info(result);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String} host_type
|
||||||
|
* @param {Object} host
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
deleteCerts: (host_type, host) => {
|
||||||
|
logger.info('Deleting SSL certificates for ' + host_type + ' #' + host.id);
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
return utils.exec('/usr/bin/certbot delete -n --cert-name "' + host.hostname + '"')
|
||||||
|
.then(result => {
|
||||||
|
logger.info(result);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
logger.error(err);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String} host_type
|
||||||
|
* @param {Object} host
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
generateSslSetupConfig: (host_type, host) => {
|
||||||
|
host_type = host_type.replace(new RegExp('-', 'g'), '_');
|
||||||
|
|
||||||
|
let renderEngine = Liquid();
|
||||||
|
let template = null;
|
||||||
|
let filename = internalNginx.getConfigName(host_type, host);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
template = fs.readFileSync(__dirname + '/../templates/letsencrypt.conf', {encoding: 'utf8'});
|
||||||
|
} catch (err) {
|
||||||
|
reject(new error.ConfigurationError(err.message));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderEngine
|
||||||
|
.parseAndRender(template, host)
|
||||||
|
.then(config_text => {
|
||||||
|
fs.writeFileSync(filename, config_text, {encoding: 'utf8'});
|
||||||
|
return template_data;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
throw new error.ConfigurationError(err.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String} host_type
|
||||||
|
* @param {Object} host
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
configureSsl: (host_type, host) => {
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
return internalSsl.generateSslSetupConfig(host)
|
||||||
|
.then(data => {
|
||||||
|
return internalNginx.reload()
|
||||||
|
.then(() => {
|
||||||
|
return internalSsl.requestSsl(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = internalSsl;
|
@ -3,6 +3,7 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const error = require('../lib/error');
|
const error = require('../lib/error');
|
||||||
const streamModel = require('../models/stream');
|
const streamModel = require('../models/stream');
|
||||||
|
const internalAuditLog = require('./audit-log');
|
||||||
|
|
||||||
function omissions () {
|
function omissions () {
|
||||||
return ['is_deleted'];
|
return ['is_deleted'];
|
||||||
@ -31,8 +32,17 @@ const internalStream = {
|
|||||||
.insertAndFetch(data);
|
.insertAndFetch(data);
|
||||||
})
|
})
|
||||||
.then(row => {
|
.then(row => {
|
||||||
|
// Add to audit log
|
||||||
|
return internalAuditLog.add(access, {
|
||||||
|
action: 'created',
|
||||||
|
object_type: 'stream',
|
||||||
|
object_id: row.id,
|
||||||
|
meta: data
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
return _.omit(row, omissions());
|
return _.omit(row, omissions());
|
||||||
});
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,9 +70,18 @@ const internalStream = {
|
|||||||
.omit(omissions())
|
.omit(omissions())
|
||||||
.patchAndFetchById(row.id, data)
|
.patchAndFetchById(row.id, data)
|
||||||
.then(saved_row => {
|
.then(saved_row => {
|
||||||
|
// Add to audit log
|
||||||
|
return internalAuditLog.add(access, {
|
||||||
|
action: 'updated',
|
||||||
|
object_type: 'stream',
|
||||||
|
object_id: row.id,
|
||||||
|
meta: data
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
return _.omit(saved_row, omissions());
|
return _.omit(saved_row, omissions());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -133,6 +152,15 @@ const internalStream = {
|
|||||||
.where('id', row.id)
|
.where('id', row.id)
|
||||||
.patch({
|
.patch({
|
||||||
is_deleted: 1
|
is_deleted: 1
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// Add to audit log
|
||||||
|
return internalAuditLog.add(access, {
|
||||||
|
action: 'deleted',
|
||||||
|
object_type: 'stream',
|
||||||
|
object_id: row.id,
|
||||||
|
meta: _.omit(row, omissions())
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
@ -7,6 +7,7 @@ const userPermissionModel = require('../models/user_permission');
|
|||||||
const authModel = require('../models/auth');
|
const authModel = require('../models/auth');
|
||||||
const gravatar = require('gravatar');
|
const gravatar = require('gravatar');
|
||||||
const internalToken = require('./token');
|
const internalToken = require('./token');
|
||||||
|
const internalAuditLog = require('./audit-log');
|
||||||
|
|
||||||
function omissions () {
|
function omissions () {
|
||||||
return ['is_deleted'];
|
return ['is_deleted'];
|
||||||
@ -74,6 +75,18 @@ const internalUser = {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
return internalUser.get(access, {id: user.id, expand: ['permissions']});
|
return internalUser.get(access, {id: user.id, expand: ['permissions']});
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
.then(user => {
|
||||||
|
// Add to audit log
|
||||||
|
return internalAuditLog.add(access, {
|
||||||
|
action: 'created',
|
||||||
|
object_type: 'user',
|
||||||
|
object_id: user.id,
|
||||||
|
meta: user
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return user;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -136,6 +149,18 @@ const internalUser = {
|
|||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return internalUser.get(access, {id: data.id});
|
return internalUser.get(access, {id: data.id});
|
||||||
|
})
|
||||||
|
.then(user => {
|
||||||
|
// Add to audit log
|
||||||
|
return internalAuditLog.add(access, {
|
||||||
|
action: 'updated',
|
||||||
|
object_type: 'user',
|
||||||
|
object_id: user.id,
|
||||||
|
meta: data
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return user;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -236,6 +261,15 @@ const internalUser = {
|
|||||||
.where('id', user.id)
|
.where('id', user.id)
|
||||||
.patch({
|
.patch({
|
||||||
is_deleted: 1
|
is_deleted: 1
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// Add to audit log
|
||||||
|
return internalAuditLog.add(access, {
|
||||||
|
action: 'deleted',
|
||||||
|
object_type: 'user',
|
||||||
|
object_id: user.id,
|
||||||
|
meta: _.omit(user, omissions())
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -389,6 +423,19 @@ const internalUser = {
|
|||||||
meta: {}
|
meta: {}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// Add to Audit Log
|
||||||
|
return internalAuditLog.add(access, {
|
||||||
|
action: 'updated',
|
||||||
|
object_type: 'user',
|
||||||
|
object_id: user.id,
|
||||||
|
meta: {
|
||||||
|
name: user.name,
|
||||||
|
password_changed: true,
|
||||||
|
auth_type: data.type
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -435,8 +482,21 @@ const internalUser = {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(permissions => {
|
.then(permissions => {
|
||||||
return true;
|
// Add to Audit Log
|
||||||
|
return internalAuditLog.add(access, {
|
||||||
|
action: 'updated',
|
||||||
|
object_type: 'user',
|
||||||
|
object_id: user.id,
|
||||||
|
meta: {
|
||||||
|
name: user.name,
|
||||||
|
permissions: permissions
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -50,6 +50,15 @@ module.exports = {
|
|||||||
this.public = false;
|
this.public = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
ConfigurationError: function (message, previous) {
|
||||||
|
Error.captureStackTrace(this, this.constructor);
|
||||||
|
this.name = this.constructor.name;
|
||||||
|
this.previous = previous;
|
||||||
|
this.message = message;
|
||||||
|
this.status = 400;
|
||||||
|
this.public = true;
|
||||||
|
},
|
||||||
|
|
||||||
CacheError: function (message, previous) {
|
CacheError: function (message, previous) {
|
||||||
Error.captureStackTrace(this, this.constructor);
|
Error.captureStackTrace(this, this.constructor);
|
||||||
this.name = this.constructor.name;
|
this.name = this.constructor.name;
|
||||||
|
22
src/backend/lib/utils.js
Normal file
22
src/backend/lib/utils.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const exec = require('child_process').exec;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String} cmd
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
exec: function (cmd) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
exec(cmd, function (err, stdout, stderr) {
|
||||||
|
if (err && typeof err === 'object') {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(stdout.trim());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -4,5 +4,7 @@ module.exports = {
|
|||||||
global: new Signale({scope: 'Global '}),
|
global: new Signale({scope: 'Global '}),
|
||||||
migrate: new Signale({scope: 'Migrate '}),
|
migrate: new Signale({scope: 'Migrate '}),
|
||||||
express: new Signale({scope: 'Express '}),
|
express: new Signale({scope: 'Express '}),
|
||||||
access: new Signale({scope: 'Access '})
|
access: new Signale({scope: 'Access '}),
|
||||||
|
nginx: new Signale({scope: 'Nginx '}),
|
||||||
|
ssl: new Signale({scope: 'SSL '})
|
||||||
};
|
};
|
||||||
|
@ -165,7 +165,8 @@ exports.up = function (knex/*, Promise*/) {
|
|||||||
table.dateTime('created_on').notNull();
|
table.dateTime('created_on').notNull();
|
||||||
table.dateTime('modified_on').notNull();
|
table.dateTime('modified_on').notNull();
|
||||||
table.integer('user_id').notNull().unsigned();
|
table.integer('user_id').notNull().unsigned();
|
||||||
// TODO
|
table.string('object_type').notNull().defaultTo('');
|
||||||
|
table.integer('object_id').notNull().unsigned().defaultTo(0);
|
||||||
table.string('action').notNull();
|
table.string('action').notNull();
|
||||||
table.json('meta').notNull();
|
table.json('meta').notNull();
|
||||||
});
|
});
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
const db = require('../db');
|
const db = require('../db');
|
||||||
const Model = require('objection').Model;
|
const Model = require('objection').Model;
|
||||||
|
const User = require('./user');
|
||||||
|
|
||||||
Model.knex(db);
|
Model.knex(db);
|
||||||
|
|
||||||
@ -25,6 +26,26 @@ class AuditLog extends Model {
|
|||||||
static get tableName () {
|
static get tableName () {
|
||||||
return 'audit_log';
|
return 'audit_log';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static get jsonAttributes () {
|
||||||
|
return ['meta'];
|
||||||
|
}
|
||||||
|
|
||||||
|
static get relationMappings () {
|
||||||
|
return {
|
||||||
|
user: {
|
||||||
|
relation: Model.HasOneRelation,
|
||||||
|
modelClass: User,
|
||||||
|
join: {
|
||||||
|
from: 'audit_log.user_id',
|
||||||
|
to: 'user.id'
|
||||||
|
},
|
||||||
|
modify: function (qb) {
|
||||||
|
qb.omit(['id', 'created_on', 'modified_on', 'roles']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = AuditLog;
|
module.exports = AuditLog;
|
||||||
|
19
src/backend/templates/dead_host.conf
Normal file
19
src/backend/templates/dead_host.conf
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# <%- hostname %>
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
<%- typeof ssl !== 'undefined' && ssl ? 'listen 443 ssl;' : '' %>
|
||||||
|
|
||||||
|
server_name <%- hostname %>;
|
||||||
|
|
||||||
|
access_log /config/logs/<%- hostname %>.log proxy;
|
||||||
|
|
||||||
|
<% if (typeof ssl !== 'undefined' && ssl) { -%>
|
||||||
|
include conf.d/include/ssl-ciphers.conf;
|
||||||
|
ssl_certificate /etc/letsencrypt/live/<%- hostname %>/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/<%- hostname %>/privkey.pem;
|
||||||
|
<% } -%>
|
||||||
|
|
||||||
|
<%- typeof advanced !== 'undefined' && advanced ? advanced : '' %>
|
||||||
|
|
||||||
|
return 404;
|
||||||
|
}
|
11
src/backend/templates/letsencrypt.conf
Normal file
11
src/backend/templates/letsencrypt.conf
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Letsencrypt Verification Temporary Host: <%- hostname %>
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name <%- hostname %>;
|
||||||
|
|
||||||
|
access_log /config/logs/letsencrypt.log proxy;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /config/letsencrypt-acme-challenge;
|
||||||
|
}
|
||||||
|
}
|
33
src/backend/templates/proxy_host.conf
Normal file
33
src/backend/templates/proxy_host.conf
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# <%- hostname %>
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
<%- typeof ssl !== 'undefined' && ssl ? 'listen 443 ssl;' : '' %>
|
||||||
|
|
||||||
|
server_name <%- hostname %>;
|
||||||
|
|
||||||
|
access_log /config/logs/<%- hostname %>.log proxy;
|
||||||
|
|
||||||
|
set $server <%- forward_server %>;
|
||||||
|
set $port <%- forward_port %>;
|
||||||
|
|
||||||
|
<%- typeof asset_caching !== 'undefined' && asset_caching ? 'include conf.d/include/assets.conf;' : '' %>
|
||||||
|
<%- typeof block_exploits !== 'undefined' && block_exploits ? 'include conf.d/include/block-exploits.conf;' : '' %>
|
||||||
|
|
||||||
|
<% if (typeof ssl !== 'undefined' && ssl) { -%>
|
||||||
|
include conf.d/include/letsencrypt-acme-challenge.conf;
|
||||||
|
include conf.d/include/ssl-ciphers.conf;
|
||||||
|
ssl_certificate /etc/letsencrypt/live/<%- hostname %>/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/<%- hostname %>/privkey.pem;
|
||||||
|
<% } -%>
|
||||||
|
|
||||||
|
<%- typeof advanced !== 'undefined' && advanced ? advanced : '' %>
|
||||||
|
|
||||||
|
location / {
|
||||||
|
<% if (typeof access_list_id !== 'undefined' && access_list_id) { -%>
|
||||||
|
auth_basic "Authorization required";
|
||||||
|
auth_basic_user_file /config/access/<%- access_list_id %>;
|
||||||
|
<% } -%>
|
||||||
|
<%- typeof force_ssl !== 'undefined' && force_ssl ? 'include conf.d/include/force-ssl.conf;' : '' %>
|
||||||
|
include conf.d/include/proxy.conf;
|
||||||
|
}
|
||||||
|
}
|
22
src/backend/templates/redirection_host.conf
Normal file
22
src/backend/templates/redirection_host.conf
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# <%- hostname %>
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
<%- typeof ssl !== 'undefined' && ssl ? 'listen 443 ssl;' : '' %>
|
||||||
|
|
||||||
|
server_name <%- hostname %>;
|
||||||
|
|
||||||
|
access_log /config/logs/<%- hostname %>.log proxy;
|
||||||
|
|
||||||
|
<%- typeof asset_caching !== 'undefined' && asset_caching ? 'include conf.d/include/assets.conf;' : '' %>
|
||||||
|
<%- typeof block_exploits !== 'undefined' && block_exploits ? 'include conf.d/include/block-exploits.conf;' : '' %>
|
||||||
|
|
||||||
|
<% if (typeof ssl !== 'undefined' && ssl) { -%>
|
||||||
|
include conf.d/include/ssl-ciphers.conf;
|
||||||
|
ssl_certificate /etc/letsencrypt/live/<%- hostname %>/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/<%- hostname %>/privkey.pem;
|
||||||
|
<% } -%>
|
||||||
|
|
||||||
|
<%- typeof advanced !== 'undefined' && advanced ? advanced : '' %>
|
||||||
|
|
||||||
|
return 301 $scheme://<%- forward_host %>$request_uri;
|
||||||
|
}
|
11
src/backend/templates/stream.conf
Normal file
11
src/backend/templates/stream.conf
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# <%- incoming_port %> - <%- protocols.join(',').toUpperCase() %>
|
||||||
|
<%
|
||||||
|
protocols.forEach(function (protocol) {
|
||||||
|
%>
|
||||||
|
server {
|
||||||
|
listen <%- incoming_port %> <%- protocol === 'tcp' ? '' : protocol %>;
|
||||||
|
proxy_pass <%- forward_server %>:<%- forward_port %>;
|
||||||
|
}
|
||||||
|
<%
|
||||||
|
});
|
||||||
|
%>
|
@ -1,32 +1,72 @@
|
|||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<div class="avatar d-block" style="background-image: url(<%- avatar || '/images/default-avatar.jpg' %>)">
|
<div class="avatar d-block" style="background-image: url(<%- user.avatar || '/images/default-avatar.jpg' %>)">
|
||||||
<span class="avatar-status <%- is_disabled ? 'bg-red' : 'bg-green' %>"></span>
|
<span class="avatar-status <%- user.is_disabled ? 'bg-red' : 'bg-green' %>"></span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<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;
|
||||||
|
}
|
||||||
|
%> <%- i18n('audit-log', action, {name: i18n('audit-log', object_type)}) %>
|
||||||
|
—
|
||||||
|
<%
|
||||||
|
if (items && items.length) {
|
||||||
|
items.map(function(item) {
|
||||||
|
%>
|
||||||
|
<span class="tag"><%- item %></span>
|
||||||
|
<%
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
%>
|
||||||
|
#<%- object_id %>
|
||||||
|
<%
|
||||||
|
}
|
||||||
|
%>
|
||||||
|
</div>
|
||||||
<div class="small text-muted">
|
<div class="small text-muted">
|
||||||
Created: <%- formatDbDate(created_on, 'Do MMMM YYYY') %>
|
<%- formatDbDate(created_on, 'Do MMMM YYYY, h:mm a') %>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="text-right">
|
||||||
<div><%- email %></div>
|
<a href="#" class="meta btn btn-secondary btn-sm"><%- i18n('audit-log', 'view-meta') %></a>
|
||||||
</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>
|
</td>
|
||||||
|
@ -2,9 +2,6 @@
|
|||||||
|
|
||||||
const Mn = require('backbone.marionette');
|
const Mn = require('backbone.marionette');
|
||||||
const Controller = require('../../controller');
|
const Controller = require('../../controller');
|
||||||
const Api = require('../../api');
|
|
||||||
const Cache = require('../../cache');
|
|
||||||
const Tokens = require('../../tokens');
|
|
||||||
const template = require('./item.ejs');
|
const template = require('./item.ejs');
|
||||||
|
|
||||||
module.exports = Mn.View.extend({
|
module.exports = Mn.View.extend({
|
||||||
@ -12,61 +9,26 @@ module.exports = Mn.View.extend({
|
|||||||
tagName: 'tr',
|
tagName: 'tr',
|
||||||
|
|
||||||
ui: {
|
ui: {
|
||||||
edit: 'a.edit-user',
|
meta: 'a.meta'
|
||||||
permissions: 'a.edit-permissions',
|
|
||||||
password: 'a.set-password',
|
|
||||||
login: 'a.login',
|
|
||||||
delete: 'a.delete-user'
|
|
||||||
},
|
},
|
||||||
|
|
||||||
events: {
|
events: {
|
||||||
'click @ui.edit': function (e) {
|
'click @ui.meta': function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
Controller.showUserForm(this.model);
|
Controller.showAuditMeta(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: {
|
templateContext: {
|
||||||
isSelf: function () {
|
more: function() {
|
||||||
return Cache.User.get('id') === this.id;
|
switch (this.object_type) {
|
||||||
|
case 'redirection-host':
|
||||||
|
case 'stream':
|
||||||
|
case 'proxy-host':
|
||||||
|
return this.meta.domain_names.join(', ');
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
initialize: function () {
|
return '#' + (this.object_id || '?');
|
||||||
this.listenTo(this.model, 'change', this.render);
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<th width="30"> </th>
|
<th width="30"> </th>
|
||||||
<th>Name</th>
|
<th>User</th>
|
||||||
<th>Email</th>
|
<th>Event</th>
|
||||||
<th>Roles</th>
|
|
||||||
<th> </th>
|
<th> </th>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -24,7 +24,7 @@ module.exports = Mn.View.extend({
|
|||||||
onRender: function () {
|
onRender: function () {
|
||||||
let view = this;
|
let view = this;
|
||||||
|
|
||||||
App.Api.AuditLog.getAll()
|
App.Api.AuditLog.getAll(['user'])
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (!view.isDestroyed() && response && response.length) {
|
if (!view.isDestroyed() && response && response.length) {
|
||||||
view.showChildView('list_region', new ListView({
|
view.showChildView('list_region', new ListView({
|
||||||
|
27
src/frontend/js/app/audit-log/meta.ejs
Normal file
27
src/frontend/js/app/audit-log/meta.ejs
Normal 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"> </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>
|
9
src/frontend/js/app/audit-log/meta.js
Normal file
9
src/frontend/js/app/audit-log/meta.js
Normal 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'
|
||||||
|
});
|
@ -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
|
* 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
|
* Logout
|
||||||
*/
|
*/
|
||||||
|
12
src/frontend/js/app/help/main.ejs
Normal file
12
src/frontend/js/app/help/main.ejs
Normal 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"> </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>
|
18
src/frontend/js/app/help/main.js
Normal file
18
src/frontend/js/app/help/main.js
Normal 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>'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
@ -3,6 +3,7 @@
|
|||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title"><%- i18n('access-lists', 'title') %></h3>
|
<h3 class="card-title"><%- i18n('access-lists', 'title') %></h3>
|
||||||
<div class="card-options">
|
<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) { %>
|
<% if (showAddButton) { %>
|
||||||
<a href="#" class="btn btn-outline-teal btn-sm ml-2 add-item"><%- i18n('access-lists', 'add') %></a>
|
<a href="#" class="btn btn-outline-teal btn-sm ml-2 add-item"><%- i18n('access-lists', 'add') %></a>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
@ -15,6 +15,7 @@ module.exports = Mn.View.extend({
|
|||||||
ui: {
|
ui: {
|
||||||
list_region: '.list-region',
|
list_region: '.list-region',
|
||||||
add: '.add-item',
|
add: '.add-item',
|
||||||
|
help: '.help',
|
||||||
dimmer: '.dimmer'
|
dimmer: '.dimmer'
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -26,6 +27,11 @@ module.exports = Mn.View.extend({
|
|||||||
'click @ui.add': function (e) {
|
'click @ui.add': function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
App.Controller.showNginxAccessListForm();
|
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'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
<a href="#" data-toggle="dropdown" class="icon"><i class="fe fe-more-vertical"></i></a>
|
<a href="#" data-toggle="dropdown" class="icon"><i class="fe fe-more-vertical"></i></a>
|
||||||
<div class="dropdown-menu dropdown-menu-right">
|
<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="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>
|
<div class="dropdown-divider"></div>
|
||||||
<a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a>
|
<a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title"><%- i18n('dead-hosts', 'title') %></h3>
|
<h3 class="card-title"><%- i18n('dead-hosts', 'title') %></h3>
|
||||||
<div class="card-options">
|
<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) { %>
|
<% if (showAddButton) { %>
|
||||||
<a href="#" class="btn btn-outline-danger btn-sm ml-2 add-item"><%- i18n('dead-hosts', 'add') %></a>
|
<a href="#" class="btn btn-outline-danger btn-sm ml-2 add-item"><%- i18n('dead-hosts', 'add') %></a>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
@ -15,6 +15,7 @@ module.exports = Mn.View.extend({
|
|||||||
ui: {
|
ui: {
|
||||||
list_region: '.list-region',
|
list_region: '.list-region',
|
||||||
add: '.add-item',
|
add: '.add-item',
|
||||||
|
help: '.help',
|
||||||
dimmer: '.dimmer'
|
dimmer: '.dimmer'
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -26,6 +27,11 @@ module.exports = Mn.View.extend({
|
|||||||
'click @ui.add': function (e) {
|
'click @ui.add': function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
App.Controller.showNginxDeadForm();
|
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'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
<a href="#" data-toggle="dropdown" class="icon"><i class="fe fe-more-vertical"></i></a>
|
<a href="#" data-toggle="dropdown" class="icon"><i class="fe fe-more-vertical"></i></a>
|
||||||
<div class="dropdown-menu dropdown-menu-right">
|
<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="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>
|
<div class="dropdown-divider"></div>
|
||||||
<a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a>
|
<a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title"><%- i18n('proxy-hosts', 'title') %></h3>
|
<h3 class="card-title"><%- i18n('proxy-hosts', 'title') %></h3>
|
||||||
<div class="card-options">
|
<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) { %>
|
<% if (showAddButton) { %>
|
||||||
<a href="#" class="btn btn-outline-success btn-sm ml-2 add-item"><%- i18n('proxy-hosts', 'add') %></a>
|
<a href="#" class="btn btn-outline-success btn-sm ml-2 add-item"><%- i18n('proxy-hosts', 'add') %></a>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
@ -15,6 +15,7 @@ module.exports = Mn.View.extend({
|
|||||||
ui: {
|
ui: {
|
||||||
list_region: '.list-region',
|
list_region: '.list-region',
|
||||||
add: '.add-item',
|
add: '.add-item',
|
||||||
|
help: '.help',
|
||||||
dimmer: '.dimmer'
|
dimmer: '.dimmer'
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -26,6 +27,11 @@ module.exports = Mn.View.extend({
|
|||||||
'click @ui.add': function (e) {
|
'click @ui.add': function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
App.Controller.showNginxProxyForm();
|
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'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
<a href="#" data-toggle="dropdown" class="icon"><i class="fe fe-more-vertical"></i></a>
|
<a href="#" data-toggle="dropdown" class="icon"><i class="fe fe-more-vertical"></i></a>
|
||||||
<div class="dropdown-menu dropdown-menu-right">
|
<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="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>
|
<div class="dropdown-divider"></div>
|
||||||
<a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a>
|
<a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title">Redirection Hosts</h3>
|
<h3 class="card-title">Redirection Hosts</h3>
|
||||||
<div class="card-options">
|
<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) { %>
|
<% if (showAddButton) { %>
|
||||||
<a href="#" class="btn btn-outline-yellow btn-sm ml-2 add-item">Add Redirection Host</a>
|
<a href="#" class="btn btn-outline-yellow btn-sm ml-2 add-item">Add Redirection Host</a>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
@ -15,6 +15,7 @@ module.exports = Mn.View.extend({
|
|||||||
ui: {
|
ui: {
|
||||||
list_region: '.list-region',
|
list_region: '.list-region',
|
||||||
add: '.add-item',
|
add: '.add-item',
|
||||||
|
help: '.help',
|
||||||
dimmer: '.dimmer'
|
dimmer: '.dimmer'
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -26,6 +27,11 @@ module.exports = Mn.View.extend({
|
|||||||
'click @ui.add': function (e) {
|
'click @ui.add': function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
App.Controller.showNginxRedirectionForm();
|
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'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title"><%- i18n('streams', 'title') %></h3>
|
<h3 class="card-title"><%- i18n('streams', 'title') %></h3>
|
||||||
<div class="card-options">
|
<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) { %>
|
<% if (showAddButton) { %>
|
||||||
<a href="#" class="btn btn-outline-blue btn-sm ml-2 add-item"><%- i18n('streams', 'add') %></a>
|
<a href="#" class="btn btn-outline-blue btn-sm ml-2 add-item"><%- i18n('streams', 'add') %></a>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
@ -15,6 +15,7 @@ module.exports = Mn.View.extend({
|
|||||||
ui: {
|
ui: {
|
||||||
list_region: '.list-region',
|
list_region: '.list-region',
|
||||||
add: '.add-item',
|
add: '.add-item',
|
||||||
|
help: '.help',
|
||||||
dimmer: '.dimmer'
|
dimmer: '.dimmer'
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -26,6 +27,11 @@ module.exports = Mn.View.extend({
|
|||||||
'click @ui.add': function (e) {
|
'click @ui.add': function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
App.Controller.showNginxStreamForm();
|
App.Controller.showNginxStreamForm();
|
||||||
|
},
|
||||||
|
|
||||||
|
'click @ui.help': function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
App.Controller.showHelp(App.i18n('streams', 'help-title'), App.i18n('streams', 'help-content'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
<% if (!isSelf()) { %>
|
<% if (!isSelf()) { %>
|
||||||
<a href="#" class="login dropdown-item"><i class="dropdown-icon fe fe-log-in"></i> <%- i18n('users', 'sign-in-as') %></a>
|
<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>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title"><%- i18n('users', 'title') %></h3>
|
<h3 class="card-title"><%- i18n('users', 'title') %></h3>
|
||||||
<div class="card-options">
|
<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>
|
</div>
|
||||||
<div class="card-body no-padding min-100">
|
<div class="card-body no-padding min-100">
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
"created-on": "Created: {date}",
|
"created-on": "Created: {date}",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
|
"close": "Close",
|
||||||
"sure": "Yes I'm Sure",
|
"sure": "Yes I'm Sure",
|
||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
"choose-file": "Choose file",
|
"choose-file": "Choose file",
|
||||||
@ -81,7 +82,9 @@
|
|||||||
"forward-ip": "Forward IP",
|
"forward-ip": "Forward IP",
|
||||||
"forward-port": "Forward Port",
|
"forward-port": "Forward Port",
|
||||||
"delete": "Delete Proxy Host",
|
"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": {
|
"redirection-hosts": {
|
||||||
"title": "Redirection Hosts",
|
"title": "Redirection Hosts",
|
||||||
@ -91,13 +94,19 @@
|
|||||||
"forward-domain": "Forward Domain",
|
"forward-domain": "Forward Domain",
|
||||||
"preserve-path": "Preserve Path",
|
"preserve-path": "Preserve Path",
|
||||||
"delete": "Delete Proxy Host",
|
"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": {
|
"dead-hosts": {
|
||||||
"title": "404 Hosts",
|
"title": "404 Hosts",
|
||||||
"empty": "There are no 404 Hosts",
|
"empty": "There are no 404 Hosts",
|
||||||
"add": "Add 404 Host",
|
"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": {
|
"streams": {
|
||||||
"title": "Streams",
|
"title": "Streams",
|
||||||
@ -114,7 +123,9 @@
|
|||||||
"tcp": "TCP",
|
"tcp": "TCP",
|
||||||
"udp": "UDP",
|
"udp": "UDP",
|
||||||
"delete": "Delete Stream",
|
"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": {
|
"access-lists": {
|
||||||
"title": "Access Lists",
|
"title": "Access Lists",
|
||||||
@ -122,7 +133,9 @@
|
|||||||
"add": "Add Access List",
|
"add": "Add Access List",
|
||||||
"delete": "Delete 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.",
|
"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": {
|
"users": {
|
||||||
"title": "Users",
|
"title": "Users",
|
||||||
@ -153,7 +166,19 @@
|
|||||||
"audit-log": {
|
"audit-log": {
|
||||||
"title": "Audit Log",
|
"title": "Audit Log",
|
||||||
"empty": "There are no logs.",
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ Mn.Renderer.render = function (template, data, view) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {String} date
|
* @param {String} date
|
||||||
|
* @param {String} format
|
||||||
* @returns {String}
|
* @returns {String}
|
||||||
*/
|
*/
|
||||||
data.formatDbDate = function (date, format) {
|
data.formatDbDate = function (date, format) {
|
||||||
|
@ -86,3 +86,12 @@ $blue: #467fcf;
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* modal wide */
|
||||||
|
|
||||||
|
@media (min-width: 576px) {
|
||||||
|
.modal-dialog.wide {
|
||||||
|
max-width: 700px;
|
||||||
|
margin: 1.75rem auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user