mirror of
https://github.com/jc21/nginx-proxy-manager.git
synced 2024-08-30 18:22:48 +00:00
Ongoing rewrite work
This commit is contained in:
parent
30924a6922
commit
54d220a191
74
src/backend/internal/dead-host.js
Normal file
74
src/backend/internal/dead-host.js
Normal file
@ -0,0 +1,74 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const error = require('../lib/error');
|
||||
const deadHostModel = require('../models/dead_host');
|
||||
|
||||
function omissions () {
|
||||
return ['is_deleted'];
|
||||
}
|
||||
|
||||
const internalDeadHost = {
|
||||
|
||||
/**
|
||||
* All Hosts
|
||||
*
|
||||
* @param {Access} access
|
||||
* @param {Array} [expand]
|
||||
* @param {String} [search_query]
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getAll: (access, expand, search_query) => {
|
||||
return access.can('dead_hosts:list')
|
||||
.then(access_data => {
|
||||
let query = deadHostModel
|
||||
.query()
|
||||
.where('is_deleted', 0)
|
||||
.groupBy('id')
|
||||
.omit(['is_deleted'])
|
||||
.orderBy('domain_name', 'ASC');
|
||||
|
||||
if (access_data.permission_visibility !== 'all') {
|
||||
query.andWhere('owner_user_id', access.token.get('attrs').id);
|
||||
}
|
||||
|
||||
// Query is used for searching
|
||||
if (typeof search_query === 'string') {
|
||||
query.where(function () {
|
||||
this.where('domain_name', 'like', '%' + search_query + '%');
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof expand !== 'undefined' && expand !== null) {
|
||||
query.eager('[' + expand.join(', ') + ']');
|
||||
}
|
||||
|
||||
return query;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Report use
|
||||
*
|
||||
* @param {Integer} user_id
|
||||
* @param {String} visibility
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getCount: (user_id, visibility) => {
|
||||
let query = deadHostModel
|
||||
.query()
|
||||
.count('id as count')
|
||||
.where('is_deleted', 0);
|
||||
|
||||
if (visibility !== 'all') {
|
||||
query.andWhere('owner_user_id', user_id);
|
||||
}
|
||||
|
||||
return query.first()
|
||||
.then(row => {
|
||||
return parseInt(row.count, 10);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = internalDeadHost;
|
279
src/backend/internal/proxy-host.js
Normal file
279
src/backend/internal/proxy-host.js
Normal file
@ -0,0 +1,279 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const error = require('../lib/error');
|
||||
const proxyHostModel = require('../models/proxy_host');
|
||||
|
||||
function omissions () {
|
||||
return ['is_deleted'];
|
||||
}
|
||||
|
||||
const internalProxyHost = {
|
||||
|
||||
/**
|
||||
* @param {Access} access
|
||||
* @param {Object} data
|
||||
* @returns {Promise}
|
||||
*/
|
||||
create: (access, data) => {
|
||||
let auth = data.auth || null;
|
||||
delete data.auth;
|
||||
|
||||
data.avatar = data.avatar || '';
|
||||
data.roles = data.roles || [];
|
||||
|
||||
if (typeof data.is_disabled !== 'undefined') {
|
||||
data.is_disabled = data.is_disabled ? 1 : 0;
|
||||
}
|
||||
|
||||
return access.can('proxy_hosts:create', data)
|
||||
.then(() => {
|
||||
data.avatar = gravatar.url(data.email, {default: 'mm'});
|
||||
|
||||
return userModel
|
||||
.query()
|
||||
.omit(omissions())
|
||||
.insertAndFetch(data);
|
||||
})
|
||||
.then(user => {
|
||||
if (auth) {
|
||||
return authModel
|
||||
.query()
|
||||
.insert({
|
||||
user_id: user.id,
|
||||
type: auth.type,
|
||||
secret: auth.secret,
|
||||
meta: {}
|
||||
})
|
||||
.then(() => {
|
||||
return user;
|
||||
});
|
||||
} else {
|
||||
return user;
|
||||
}
|
||||
})
|
||||
.then(user => {
|
||||
// Create permissions row as well
|
||||
let is_admin = data.roles.indexOf('admin') !== -1;
|
||||
|
||||
return userPermissionModel
|
||||
.query()
|
||||
.insert({
|
||||
user_id: user.id,
|
||||
visibility: is_admin ? 'all' : 'user',
|
||||
proxy_hosts: 'manage',
|
||||
redirection_hosts: 'manage',
|
||||
dead_hosts: 'manage',
|
||||
streams: 'manage',
|
||||
access_lists: 'manage'
|
||||
})
|
||||
.then(() => {
|
||||
return internalProxyHost.get(access, {id: user.id, expand: ['permissions']});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Access} access
|
||||
* @param {Object} data
|
||||
* @param {Integer} data.id
|
||||
* @param {String} [data.email]
|
||||
* @param {String} [data.name]
|
||||
* @return {Promise}
|
||||
*/
|
||||
update: (access, data) => {
|
||||
if (typeof data.is_disabled !== 'undefined') {
|
||||
data.is_disabled = data.is_disabled ? 1 : 0;
|
||||
}
|
||||
|
||||
return access.can('proxy_hosts:update', data.id)
|
||||
.then(() => {
|
||||
|
||||
// Make sure that the user being updated doesn't change their email to another user that is already using it
|
||||
// 1. get user we want to update
|
||||
return internalProxyHost.get(access, {id: data.id})
|
||||
.then(user => {
|
||||
|
||||
// 2. if email is to be changed, find other users with that email
|
||||
if (typeof data.email !== 'undefined') {
|
||||
data.email = data.email.toLowerCase().trim();
|
||||
|
||||
if (user.email !== data.email) {
|
||||
return internalProxyHost.isEmailAvailable(data.email, data.id)
|
||||
.then(available => {
|
||||
if (!available) {
|
||||
throw new error.ValidationError('Email address already in use - ' + data.email);
|
||||
}
|
||||
|
||||
return user;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// No change to email:
|
||||
return user;
|
||||
});
|
||||
})
|
||||
.then(user => {
|
||||
if (user.id !== data.id) {
|
||||
// Sanity check that something crazy hasn't happened
|
||||
throw new error.InternalValidationError('User could not be updated, IDs do not match: ' + user.id + ' !== ' + data.id);
|
||||
}
|
||||
|
||||
data.avatar = gravatar.url(data.email || user.email, {default: 'mm'});
|
||||
|
||||
return userModel
|
||||
.query()
|
||||
.omit(omissions())
|
||||
.patchAndFetchById(user.id, data)
|
||||
.then(saved_user => {
|
||||
return _.omit(saved_user, omissions());
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
return internalProxyHost.get(access, {id: data.id});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Access} access
|
||||
* @param {Object} [data]
|
||||
* @param {Integer} [data.id] Defaults to the token user
|
||||
* @param {Array} [data.expand]
|
||||
* @param {Array} [data.omit]
|
||||
* @return {Promise}
|
||||
*/
|
||||
get: (access, data) => {
|
||||
if (typeof data === 'undefined') {
|
||||
data = {};
|
||||
}
|
||||
|
||||
if (typeof data.id === 'undefined' || !data.id) {
|
||||
data.id = access.token.get('attrs').id;
|
||||
}
|
||||
|
||||
return access.can('proxy_hosts:get', data.id)
|
||||
.then(() => {
|
||||
let query = userModel
|
||||
.query()
|
||||
.where('is_deleted', 0)
|
||||
.andWhere('id', data.id)
|
||||
.allowEager('[permissions]')
|
||||
.first();
|
||||
|
||||
// Custom omissions
|
||||
if (typeof data.omit !== 'undefined' && data.omit !== null) {
|
||||
query.omit(data.omit);
|
||||
}
|
||||
|
||||
if (typeof data.expand !== 'undefined' && data.expand !== null) {
|
||||
query.eager('[' + data.expand.join(', ') + ']');
|
||||
}
|
||||
|
||||
return query;
|
||||
})
|
||||
.then(row => {
|
||||
if (row) {
|
||||
return _.omit(row, omissions());
|
||||
} else {
|
||||
throw new error.ItemNotFoundError(data.id);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Access} access
|
||||
* @param {Object} data
|
||||
* @param {Integer} data.id
|
||||
* @param {String} [data.reason]
|
||||
* @returns {Promise}
|
||||
*/
|
||||
delete: (access, data) => {
|
||||
return access.can('proxy_hosts:delete', data.id)
|
||||
.then(() => {
|
||||
return internalProxyHost.get(access, {id: data.id});
|
||||
})
|
||||
.then(user => {
|
||||
if (!user) {
|
||||
throw new error.ItemNotFoundError(data.id);
|
||||
}
|
||||
|
||||
// Make sure user can't delete themselves
|
||||
if (user.id === access.token.get('attrs').id) {
|
||||
throw new error.PermissionError('You cannot delete yourself.');
|
||||
}
|
||||
|
||||
return userModel
|
||||
.query()
|
||||
.where('id', user.id)
|
||||
.patch({
|
||||
is_deleted: 1
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
return true;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* All Hosts
|
||||
*
|
||||
* @param {Access} access
|
||||
* @param {Array} [expand]
|
||||
* @param {String} [search_query]
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getAll: (access, expand, search_query) => {
|
||||
return access.can('proxy_hosts:list')
|
||||
.then(access_data => {
|
||||
let query = proxyHostModel
|
||||
.query()
|
||||
.where('is_deleted', 0)
|
||||
.groupBy('id')
|
||||
.omit(['is_deleted'])
|
||||
.orderBy('domain_name', 'ASC');
|
||||
|
||||
if (access_data.permission_visibility !== 'all') {
|
||||
query.andWhere('owner_user_id', access.token.get('attrs').id);
|
||||
}
|
||||
|
||||
// Query is used for searching
|
||||
if (typeof search_query === 'string') {
|
||||
query.where(function () {
|
||||
this.where('domain_name', 'like', '%' + search_query + '%');
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof expand !== 'undefined' && expand !== null) {
|
||||
query.eager('[' + expand.join(', ') + ']');
|
||||
}
|
||||
|
||||
return query;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Report use
|
||||
*
|
||||
* @param {Integer} user_id
|
||||
* @param {String} visibility
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getCount: (user_id, visibility) => {
|
||||
let query = proxyHostModel
|
||||
.query()
|
||||
.count('id as count')
|
||||
.where('is_deleted', 0);
|
||||
|
||||
if (visibility !== 'all') {
|
||||
query.andWhere('owner_user_id', user_id);
|
||||
}
|
||||
|
||||
return query.first()
|
||||
.then(row => {
|
||||
return parseInt(row.count, 10);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = internalProxyHost;
|
74
src/backend/internal/redirection-host.js
Normal file
74
src/backend/internal/redirection-host.js
Normal file
@ -0,0 +1,74 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const error = require('../lib/error');
|
||||
const redirectionHostModel = require('../models/redirection_host');
|
||||
|
||||
function omissions () {
|
||||
return ['is_deleted'];
|
||||
}
|
||||
|
||||
const internalProxyHost = {
|
||||
|
||||
/**
|
||||
* All Hosts
|
||||
*
|
||||
* @param {Access} access
|
||||
* @param {Array} [expand]
|
||||
* @param {String} [search_query]
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getAll: (access, expand, search_query) => {
|
||||
return access.can('redirection_hosts:list')
|
||||
.then(access_data => {
|
||||
let query = redirectionHostModel
|
||||
.query()
|
||||
.where('is_deleted', 0)
|
||||
.groupBy('id')
|
||||
.omit(['is_deleted'])
|
||||
.orderBy('domain_name', 'ASC');
|
||||
|
||||
if (access_data.permission_visibility !== 'all') {
|
||||
query.andWhere('owner_user_id', access.token.get('attrs').id);
|
||||
}
|
||||
|
||||
// Query is used for searching
|
||||
if (typeof search_query === 'string') {
|
||||
query.where(function () {
|
||||
this.where('domain_name', 'like', '%' + search_query + '%');
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof expand !== 'undefined' && expand !== null) {
|
||||
query.eager('[' + expand.join(', ') + ']');
|
||||
}
|
||||
|
||||
return query;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Report use
|
||||
*
|
||||
* @param {Integer} user_id
|
||||
* @param {String} visibility
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getCount: (user_id, visibility) => {
|
||||
let query = redirectionHostModel
|
||||
.query()
|
||||
.count('id as count')
|
||||
.where('is_deleted', 0);
|
||||
|
||||
if (visibility !== 'all') {
|
||||
query.andWhere('owner_user_id', user_id);
|
||||
}
|
||||
|
||||
return query.first()
|
||||
.then(row => {
|
||||
return parseInt(row.count, 10);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = internalProxyHost;
|
@ -1,7 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const error = require('../lib/error');
|
||||
const _ = require('lodash');
|
||||
const error = require('../lib/error');
|
||||
const internalProxyHost = require('./proxy-host');
|
||||
const internalRedirectionHost = require('./redirection-host');
|
||||
const internalDeadHost = require('./dead-host');
|
||||
const internalStream = require('./stream');
|
||||
|
||||
const internalReport = {
|
||||
|
||||
@ -11,14 +15,27 @@ const internalReport = {
|
||||
*/
|
||||
getHostsReport: access => {
|
||||
return access.can('reports:hosts', 1)
|
||||
.then(() => {
|
||||
.then(access_data => {
|
||||
let user_id = access.token.get('attrs').id;
|
||||
|
||||
let promises = [
|
||||
internalProxyHost.getCount(user_id, access_data.visibility),
|
||||
internalRedirectionHost.getCount(user_id, access_data.visibility),
|
||||
internalStream.getCount(user_id, access_data.visibility),
|
||||
internalDeadHost.getCount(user_id, access_data.visibility)
|
||||
];
|
||||
|
||||
return Promise.all(promises);
|
||||
})
|
||||
.then(counts => {
|
||||
return {
|
||||
proxy: 12,
|
||||
redirection: 2,
|
||||
stream: 1,
|
||||
'404': 0
|
||||
proxy: counts.shift(),
|
||||
redirection: counts.shift(),
|
||||
stream: counts.shift(),
|
||||
dead: counts.shift()
|
||||
};
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
74
src/backend/internal/stream.js
Normal file
74
src/backend/internal/stream.js
Normal file
@ -0,0 +1,74 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const error = require('../lib/error');
|
||||
const streamModel = require('../models/stream');
|
||||
|
||||
function omissions () {
|
||||
return ['is_deleted'];
|
||||
}
|
||||
|
||||
const internalStream = {
|
||||
|
||||
/**
|
||||
* All Hosts
|
||||
*
|
||||
* @param {Access} access
|
||||
* @param {Array} [expand]
|
||||
* @param {String} [search_query]
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getAll: (access, expand, search_query) => {
|
||||
return access.can('streams:list')
|
||||
.then(access_data => {
|
||||
let query = streamModel
|
||||
.query()
|
||||
.where('is_deleted', 0)
|
||||
.groupBy('id')
|
||||
.omit(['is_deleted'])
|
||||
.orderBy('incoming_port', 'ASC');
|
||||
|
||||
if (access_data.permission_visibility !== 'all') {
|
||||
query.andWhere('owner_user_id', access.token.get('attrs').id);
|
||||
}
|
||||
|
||||
// Query is used for searching
|
||||
if (typeof search_query === 'string') {
|
||||
query.where(function () {
|
||||
this.where('incoming_port', 'like', '%' + search_query + '%');
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof expand !== 'undefined' && expand !== null) {
|
||||
query.eager('[' + expand.join(', ') + ']');
|
||||
}
|
||||
|
||||
return query;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Report use
|
||||
*
|
||||
* @param {Integer} user_id
|
||||
* @param {String} visibility
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getCount: (user_id, visibility) => {
|
||||
let query = streamModel
|
||||
.query()
|
||||
.count('id as count')
|
||||
.where('is_deleted', 0);
|
||||
|
||||
if (visibility !== 'all') {
|
||||
query.andWhere('owner_user_id', user_id);
|
||||
}
|
||||
|
||||
return query.first()
|
||||
.then(row => {
|
||||
return parseInt(row.count, 10);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = internalStream;
|
@ -1,11 +1,24 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const validator = require('ajv');
|
||||
const error = require('./error');
|
||||
const userModel = require('../models/user');
|
||||
const TokenModel = require('../models/token');
|
||||
const roleSchema = require('./access/roles.json');
|
||||
/**
|
||||
* Some Notes: This is a friggin complicated piece of code.
|
||||
*
|
||||
* "scope" in this file means "where did this token come from and what is using it", so 99% of the time
|
||||
* the "scope" is going to be "user" because it would be a user token. This is not to be confused with
|
||||
* the "role" which could be "user" or "admin". The scope in fact, could be "worker" or anything else.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const _ = require('lodash');
|
||||
const logger = require('../logger').access;
|
||||
const validator = require('ajv');
|
||||
const error = require('./error');
|
||||
const userModel = require('../models/user');
|
||||
const proxyHostModel = require('../models/proxy_host');
|
||||
const TokenModel = require('../models/token');
|
||||
const roleSchema = require('./access/roles.json');
|
||||
const permsSchema = require('./access/permissions.json');
|
||||
|
||||
module.exports = function (token_string) {
|
||||
let Token = new TokenModel();
|
||||
@ -14,6 +27,7 @@ module.exports = function (token_string) {
|
||||
let object_cache = {};
|
||||
let allow_internal_access = false;
|
||||
let user_roles = [];
|
||||
let permissions = {};
|
||||
|
||||
/**
|
||||
* Loads the Token object from the token string
|
||||
@ -28,7 +42,7 @@ module.exports = function (token_string) {
|
||||
reject(new error.PermissionError('Permission Denied'));
|
||||
} else {
|
||||
resolve(Token.load(token_string)
|
||||
.then((data) => {
|
||||
.then(data => {
|
||||
token_data = data;
|
||||
|
||||
// At this point we need to load the user from the DB and make sure they:
|
||||
@ -43,8 +57,10 @@ module.exports = function (token_string) {
|
||||
.where('id', token_data.attrs.id)
|
||||
.andWhere('is_deleted', 0)
|
||||
.andWhere('is_disabled', 0)
|
||||
.first('id')
|
||||
.then((user) => {
|
||||
.allowEager('[permissions]')
|
||||
.eager('[permissions]')
|
||||
.first()
|
||||
.then(user => {
|
||||
if (user) {
|
||||
// make sure user has all scopes of the token
|
||||
// The `user` role is not added against the user row, so we have to just add it here to get past this check.
|
||||
@ -62,7 +78,9 @@ module.exports = function (token_string) {
|
||||
} else {
|
||||
initialised = true;
|
||||
user_roles = user.roles;
|
||||
permissions = user.permissions;
|
||||
}
|
||||
|
||||
} else {
|
||||
throw new error.AuthError('User cannot be loaded for Token');
|
||||
}
|
||||
@ -99,6 +117,34 @@ module.exports = function (token_string) {
|
||||
resolve(token_user_id ? [token_user_id] : []);
|
||||
break;
|
||||
|
||||
// Proxy Hosts
|
||||
case 'proxy_hosts':
|
||||
let query = proxyHostModel
|
||||
.query()
|
||||
.select('id')
|
||||
.andWhere('is_deleted', 0);
|
||||
|
||||
if (permissions.visibility === 'user') {
|
||||
query.andWhere('owner_user_id', token_user_id);
|
||||
}
|
||||
|
||||
resolve(query
|
||||
.then(rows => {
|
||||
let result = [];
|
||||
_.forEach(rows, (rule_row) => {
|
||||
result.push(rule_row.id);
|
||||
});
|
||||
|
||||
// enum should not have less than 1 item
|
||||
if (!result.length) {
|
||||
result.push(0);
|
||||
}
|
||||
|
||||
return result;
|
||||
})
|
||||
);
|
||||
break;
|
||||
|
||||
// DEFAULT: null
|
||||
default:
|
||||
resolve(null);
|
||||
@ -121,7 +167,7 @@ module.exports = function (token_string) {
|
||||
/**
|
||||
* Creates a schema object on the fly with the IDs and other values required to be checked against the permissionSchema
|
||||
*
|
||||
* @param {String} permission_label
|
||||
* @param {String} permission_label
|
||||
* @returns {Object}
|
||||
*/
|
||||
this.getObjectSchema = permission_label => {
|
||||
@ -207,9 +253,15 @@ module.exports = function (token_string) {
|
||||
.then(objectSchema => {
|
||||
let data_schema = {
|
||||
[permission]: {
|
||||
data: data,
|
||||
scope: Token.get('scope'),
|
||||
roles: user_roles
|
||||
data: data,
|
||||
scope: Token.get('scope'),
|
||||
roles: user_roles,
|
||||
permission_visibility: permissions.visibility,
|
||||
permission_proxy_hosts: permissions.proxy_hosts,
|
||||
permission_redirection_hosts: permissions.redirection_hosts,
|
||||
permission_dead_hosts: permissions.dead_hosts,
|
||||
permission_streams: permissions.streams,
|
||||
permission_access_lists: permissions.access_lists
|
||||
}
|
||||
};
|
||||
|
||||
@ -223,9 +275,9 @@ module.exports = function (token_string) {
|
||||
|
||||
permissionSchema.properties[permission] = require('./access/' + permission.replace(/:/gim, '-') + '.json');
|
||||
|
||||
//console.log('objectSchema:', JSON.stringify(objectSchema, null, 2));
|
||||
//console.log('permissionSchema:', JSON.stringify(permissionSchema, null, 2));
|
||||
//console.log('data_schema:', JSON.stringify(data_schema, null, 2));
|
||||
//logger.debug('objectSchema:', JSON.stringify(objectSchema, null, 2));
|
||||
//logger.debug('permissionSchema:', JSON.stringify(permissionSchema, null, 2));
|
||||
//logger.debug('data_schema:', JSON.stringify(data_schema, null, 2));
|
||||
|
||||
let ajv = validator({
|
||||
verbose: true,
|
||||
@ -236,17 +288,21 @@ module.exports = function (token_string) {
|
||||
coerceTypes: true,
|
||||
schemas: [
|
||||
roleSchema,
|
||||
permsSchema,
|
||||
objectSchema,
|
||||
permissionSchema
|
||||
]
|
||||
});
|
||||
|
||||
return ajv.validate('permissions', data_schema);
|
||||
return ajv.validate('permissions', data_schema)
|
||||
.then(() => {
|
||||
return data_schema[permission];
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
//console.log(err.message);
|
||||
//console.log(err.errors);
|
||||
//logger.error(err.message);
|
||||
//logger.error(err.errors);
|
||||
|
||||
throw new error.PermissionError('Permission Denied', err);
|
||||
});
|
||||
|
23
src/backend/lib/access/dead_hosts-list.json
Normal file
23
src/backend/lib/access/dead_hosts-list.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "roles#/definitions/admin"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["permission_dead_hosts", "roles"],
|
||||
"properties": {
|
||||
"permission_dead_hosts": {
|
||||
"$ref": "perms#/definitions/view"
|
||||
},
|
||||
"roles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["user"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
15
src/backend/lib/access/permissions.json
Normal file
15
src/backend/lib/access/permissions.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "perms",
|
||||
"definitions": {
|
||||
"view": {
|
||||
"type": "string",
|
||||
"pattern": "^(view|manage)$"
|
||||
},
|
||||
"manage": {
|
||||
"type": "string",
|
||||
"pattern": "^(manage)$"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
23
src/backend/lib/access/proxy_hosts-list.json
Normal file
23
src/backend/lib/access/proxy_hosts-list.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "roles#/definitions/admin"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["permission_proxy_hosts", "roles"],
|
||||
"properties": {
|
||||
"permission_proxy_hosts": {
|
||||
"$ref": "perms#/definitions/view"
|
||||
},
|
||||
"roles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["user"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
23
src/backend/lib/access/redirection_hosts-list.json
Normal file
23
src/backend/lib/access/redirection_hosts-list.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "roles#/definitions/admin"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["permission_redirection_hosts", "roles"],
|
||||
"properties": {
|
||||
"permission_redirection_hosts": {
|
||||
"$ref": "perms#/definitions/view"
|
||||
},
|
||||
"roles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["user"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
23
src/backend/lib/access/streams-list.json
Normal file
23
src/backend/lib/access/streams-list.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "roles#/definitions/admin"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["permission_streams", "roles"],
|
||||
"properties": {
|
||||
"permission_streams": {
|
||||
"$ref": "perms#/definitions/view"
|
||||
},
|
||||
"roles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["user"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -3,5 +3,6 @@ const {Signale} = require('signale');
|
||||
module.exports = {
|
||||
global: new Signale({scope: 'Global '}),
|
||||
migrate: new Signale({scope: 'Migrate '}),
|
||||
express: new Signale({scope: 'Express '})
|
||||
express: new Signale({scope: 'Express '}),
|
||||
access: new Signale({scope: 'Access '})
|
||||
};
|
||||
|
@ -77,7 +77,6 @@ exports.up = function (knex/*, Promise*/) {
|
||||
table.integer('caching_enabled').notNull().unsigned().defaultTo(0);
|
||||
table.integer('block_exploits').notNull().unsigned().defaultTo(0);
|
||||
table.json('meta').notNull();
|
||||
table.unique(['domain_name', 'is_deleted']);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
@ -96,7 +95,6 @@ exports.up = function (knex/*, Promise*/) {
|
||||
table.string('ssl_provider').notNull().defaultTo('');
|
||||
table.integer('block_exploits').notNull().unsigned().defaultTo(0);
|
||||
table.json('meta').notNull();
|
||||
table.unique(['domain_name', 'is_deleted']);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
@ -112,7 +110,6 @@ exports.up = function (knex/*, Promise*/) {
|
||||
table.integer('ssl_enabled').notNull().unsigned().defaultTo(0);
|
||||
table.string('ssl_provider').notNull().defaultTo('');
|
||||
table.json('meta').notNull();
|
||||
table.unique(['domain_name', 'is_deleted']);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
@ -130,7 +127,6 @@ exports.up = function (knex/*, Promise*/) {
|
||||
table.integer('tcp_forwarding').notNull().unsigned().defaultTo(0);
|
||||
table.integer('udp_forwarding').notNull().unsigned().defaultTo(0);
|
||||
table.json('meta').notNull();
|
||||
table.unique(['incoming_port', 'is_deleted']);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
|
48
src/backend/models/dead_host.js
Normal file
48
src/backend/models/dead_host.js
Normal file
@ -0,0 +1,48 @@
|
||||
// Objection Docs:
|
||||
// http://vincit.github.io/objection.js/
|
||||
|
||||
'use strict';
|
||||
|
||||
const db = require('../db');
|
||||
const Model = require('objection').Model;
|
||||
const User = require('./user');
|
||||
|
||||
Model.knex(db);
|
||||
|
||||
class DeadHost extends Model {
|
||||
$beforeInsert () {
|
||||
this.created_on = Model.raw('NOW()');
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
}
|
||||
|
||||
$beforeUpdate () {
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
}
|
||||
|
||||
static get name () {
|
||||
return 'DeadHost';
|
||||
}
|
||||
|
||||
static get tableName () {
|
||||
return 'dead_host';
|
||||
}
|
||||
|
||||
static get relationMappings () {
|
||||
return {
|
||||
owner: {
|
||||
relation: Model.HasOneRelation,
|
||||
modelClass: User,
|
||||
join: {
|
||||
from: 'dead_host.owner_user_id',
|
||||
to: 'user.id'
|
||||
},
|
||||
modify: function (qb) {
|
||||
qb.where('user.is_deleted', 0);
|
||||
qb.omit(['created_on', 'modified_on', 'is_deleted', 'email', 'roles']);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DeadHost;
|
48
src/backend/models/proxy_host.js
Normal file
48
src/backend/models/proxy_host.js
Normal file
@ -0,0 +1,48 @@
|
||||
// Objection Docs:
|
||||
// http://vincit.github.io/objection.js/
|
||||
|
||||
'use strict';
|
||||
|
||||
const db = require('../db');
|
||||
const Model = require('objection').Model;
|
||||
const User = require('./user');
|
||||
|
||||
Model.knex(db);
|
||||
|
||||
class ProxyHost extends Model {
|
||||
$beforeInsert () {
|
||||
this.created_on = Model.raw('NOW()');
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
}
|
||||
|
||||
$beforeUpdate () {
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
}
|
||||
|
||||
static get name () {
|
||||
return 'ProxyHost';
|
||||
}
|
||||
|
||||
static get tableName () {
|
||||
return 'proxy_host';
|
||||
}
|
||||
|
||||
static get relationMappings () {
|
||||
return {
|
||||
owner: {
|
||||
relation: Model.HasOneRelation,
|
||||
modelClass: User,
|
||||
join: {
|
||||
from: 'proxy_host.owner_user_id',
|
||||
to: 'user.id'
|
||||
},
|
||||
modify: function (qb) {
|
||||
qb.where('user.is_deleted', 0);
|
||||
qb.omit(['created_on', 'modified_on', 'is_deleted', 'email', 'roles']);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ProxyHost;
|
48
src/backend/models/redirection_host.js
Normal file
48
src/backend/models/redirection_host.js
Normal file
@ -0,0 +1,48 @@
|
||||
// Objection Docs:
|
||||
// http://vincit.github.io/objection.js/
|
||||
|
||||
'use strict';
|
||||
|
||||
const db = require('../db');
|
||||
const Model = require('objection').Model;
|
||||
const User = require('./user');
|
||||
|
||||
Model.knex(db);
|
||||
|
||||
class RedirectionHost extends Model {
|
||||
$beforeInsert () {
|
||||
this.created_on = Model.raw('NOW()');
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
}
|
||||
|
||||
$beforeUpdate () {
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
}
|
||||
|
||||
static get name () {
|
||||
return 'RedirectionHost';
|
||||
}
|
||||
|
||||
static get tableName () {
|
||||
return 'redirection_host';
|
||||
}
|
||||
|
||||
static get relationMappings () {
|
||||
return {
|
||||
owner: {
|
||||
relation: Model.HasOneRelation,
|
||||
modelClass: User,
|
||||
join: {
|
||||
from: 'redirection_host.owner_user_id',
|
||||
to: 'user.id'
|
||||
},
|
||||
modify: function (qb) {
|
||||
qb.where('user.is_deleted', 0);
|
||||
qb.omit(['created_on', 'modified_on', 'is_deleted', 'email', 'roles']);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RedirectionHost;
|
48
src/backend/models/stream.js
Normal file
48
src/backend/models/stream.js
Normal file
@ -0,0 +1,48 @@
|
||||
// Objection Docs:
|
||||
// http://vincit.github.io/objection.js/
|
||||
|
||||
'use strict';
|
||||
|
||||
const db = require('../db');
|
||||
const Model = require('objection').Model;
|
||||
const User = require('./user');
|
||||
|
||||
Model.knex(db);
|
||||
|
||||
class Stream extends Model {
|
||||
$beforeInsert () {
|
||||
this.created_on = Model.raw('NOW()');
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
}
|
||||
|
||||
$beforeUpdate () {
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
}
|
||||
|
||||
static get name () {
|
||||
return 'Stream';
|
||||
}
|
||||
|
||||
static get tableName () {
|
||||
return 'stream';
|
||||
}
|
||||
|
||||
static get relationMappings () {
|
||||
return {
|
||||
owner: {
|
||||
relation: Model.HasOneRelation,
|
||||
modelClass: User,
|
||||
join: {
|
||||
from: 'stream.owner_user_id',
|
||||
to: 'user.id'
|
||||
},
|
||||
modify: function (qb) {
|
||||
qb.where('user.is_deleted', 0);
|
||||
qb.omit(['created_on', 'modified_on', 'is_deleted', 'email', 'roles']);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Stream;
|
@ -30,6 +30,10 @@ router.get('/', (req, res/*, next*/) => {
|
||||
router.use('/tokens', require('./tokens'));
|
||||
router.use('/users', require('./users'));
|
||||
router.use('/reports', require('./reports'));
|
||||
router.use('/nginx/proxy-hosts', require('./nginx/proxy_hosts'));
|
||||
router.use('/nginx/redirection-hosts', require('./nginx/redirection_hosts'));
|
||||
router.use('/nginx/dead-hosts', require('./nginx/dead_hosts'));
|
||||
router.use('/nginx/streams', require('./nginx/streams'));
|
||||
|
||||
/**
|
||||
* API 404 for all other routes
|
||||
|
150
src/backend/routes/api/nginx/dead_hosts.js
Normal file
150
src/backend/routes/api/nginx/dead_hosts.js
Normal file
@ -0,0 +1,150 @@
|
||||
'use strict';
|
||||
|
||||
const express = require('express');
|
||||
const validator = require('../../../lib/validator');
|
||||
const jwtdecode = require('../../../lib/express/jwt-decode');
|
||||
const internalDeadHost = require('../../../internal/dead-host');
|
||||
const apiValidator = require('../../../lib/validator/api');
|
||||
|
||||
let router = express.Router({
|
||||
caseSensitive: true,
|
||||
strict: true,
|
||||
mergeParams: true
|
||||
});
|
||||
|
||||
/**
|
||||
* /api/nginx/dead-hosts
|
||||
*/
|
||||
router
|
||||
.route('/')
|
||||
.options((req, res) => {
|
||||
res.sendStatus(204);
|
||||
})
|
||||
.all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes
|
||||
|
||||
/**
|
||||
* GET /api/nginx/dead-hosts
|
||||
*
|
||||
* Retrieve all dead-hosts
|
||||
*/
|
||||
.get((req, res, next) => {
|
||||
validator({
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
expand: {
|
||||
$ref: 'definitions#/definitions/expand'
|
||||
},
|
||||
query: {
|
||||
$ref: 'definitions#/definitions/query'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null),
|
||||
query: (typeof req.query.query === 'string' ? req.query.query : null)
|
||||
})
|
||||
.then(data => {
|
||||
return internalDeadHost.getAll(res.locals.access, data.expand, data.query);
|
||||
})
|
||||
.then(rows => {
|
||||
res.status(200)
|
||||
.send(rows);
|
||||
})
|
||||
.catch(next);
|
||||
})
|
||||
|
||||
/**
|
||||
* POST /api/nginx/dead-hosts
|
||||
*
|
||||
* Create a new dead-host
|
||||
*/
|
||||
.post((req, res, next) => {
|
||||
apiValidator({$ref: 'endpoints/dead-hosts#/links/1/schema'}, req.body)
|
||||
.then(payload => {
|
||||
return internalDeadHost.create(res.locals.access, payload);
|
||||
})
|
||||
.then(result => {
|
||||
res.status(201)
|
||||
.send(result);
|
||||
})
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
/**
|
||||
* Specific dead-host
|
||||
*
|
||||
* /api/nginx/dead-hosts/123
|
||||
*/
|
||||
router
|
||||
.route('/:host_id')
|
||||
.options((req, res) => {
|
||||
res.sendStatus(204);
|
||||
})
|
||||
.all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes
|
||||
|
||||
/**
|
||||
* GET /api/nginx/dead-hosts/123
|
||||
*
|
||||
* Retrieve a specific dead-host
|
||||
*/
|
||||
.get((req, res, next) => {
|
||||
validator({
|
||||
required: ['host_id'],
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
host_id: {
|
||||
$ref: 'definitions#/definitions/id'
|
||||
},
|
||||
expand: {
|
||||
$ref: 'definitions#/definitions/expand'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
host_id: req.params.host_id,
|
||||
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null)
|
||||
})
|
||||
.then(data => {
|
||||
return internalDeadHost.get(res.locals.access, {
|
||||
id: data.host_id,
|
||||
expand: data.expand
|
||||
});
|
||||
})
|
||||
.then(row => {
|
||||
res.status(200)
|
||||
.send(row);
|
||||
})
|
||||
.catch(next);
|
||||
})
|
||||
|
||||
/**
|
||||
* PUT /api/nginx/dead-hosts/123
|
||||
*
|
||||
* Update and existing dead-host
|
||||
*/
|
||||
.put((req, res, next) => {
|
||||
apiValidator({$ref: 'endpoints/dead-hosts#/links/2/schema'}, req.body)
|
||||
.then(payload => {
|
||||
payload.id = req.params.host_id;
|
||||
return internalDeadHost.update(res.locals.access, payload);
|
||||
})
|
||||
.then(result => {
|
||||
res.status(200)
|
||||
.send(result);
|
||||
})
|
||||
.catch(next);
|
||||
})
|
||||
|
||||
/**
|
||||
* DELETE /api/nginx/dead-hosts/123
|
||||
*
|
||||
* Update and existing dead-host
|
||||
*/
|
||||
.delete((req, res, next) => {
|
||||
internalDeadHost.delete(res.locals.access, {id: req.params.host_id})
|
||||
.then(result => {
|
||||
res.status(200)
|
||||
.send(result);
|
||||
})
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
module.exports = router;
|
150
src/backend/routes/api/nginx/proxy_hosts.js
Normal file
150
src/backend/routes/api/nginx/proxy_hosts.js
Normal file
@ -0,0 +1,150 @@
|
||||
'use strict';
|
||||
|
||||
const express = require('express');
|
||||
const validator = require('../../../lib/validator');
|
||||
const jwtdecode = require('../../../lib/express/jwt-decode');
|
||||
const internalProxyHost = require('../../../internal/proxy-host');
|
||||
const apiValidator = require('../../../lib/validator/api');
|
||||
|
||||
let router = express.Router({
|
||||
caseSensitive: true,
|
||||
strict: true,
|
||||
mergeParams: true
|
||||
});
|
||||
|
||||
/**
|
||||
* /api/nginx/proxy-hosts
|
||||
*/
|
||||
router
|
||||
.route('/')
|
||||
.options((req, res) => {
|
||||
res.sendStatus(204);
|
||||
})
|
||||
.all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes
|
||||
|
||||
/**
|
||||
* GET /api/nginx/proxy-hosts
|
||||
*
|
||||
* Retrieve all proxy-hosts
|
||||
*/
|
||||
.get((req, res, next) => {
|
||||
validator({
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
expand: {
|
||||
$ref: 'definitions#/definitions/expand'
|
||||
},
|
||||
query: {
|
||||
$ref: 'definitions#/definitions/query'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null),
|
||||
query: (typeof req.query.query === 'string' ? req.query.query : null)
|
||||
})
|
||||
.then(data => {
|
||||
return internalProxyHost.getAll(res.locals.access, data.expand, data.query);
|
||||
})
|
||||
.then(rows => {
|
||||
res.status(200)
|
||||
.send(rows);
|
||||
})
|
||||
.catch(next);
|
||||
})
|
||||
|
||||
/**
|
||||
* POST /api/nginx/proxy-hosts
|
||||
*
|
||||
* Create a new proxy-host
|
||||
*/
|
||||
.post((req, res, next) => {
|
||||
apiValidator({$ref: 'endpoints/proxy-hosts#/links/1/schema'}, req.body)
|
||||
.then(payload => {
|
||||
return internalProxyHost.create(res.locals.access, payload);
|
||||
})
|
||||
.then(result => {
|
||||
res.status(201)
|
||||
.send(result);
|
||||
})
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
/**
|
||||
* Specific proxy-host
|
||||
*
|
||||
* /api/nginx/proxy-hosts/123
|
||||
*/
|
||||
router
|
||||
.route('/:host_id')
|
||||
.options((req, res) => {
|
||||
res.sendStatus(204);
|
||||
})
|
||||
.all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes
|
||||
|
||||
/**
|
||||
* GET /api/nginx/proxy-hosts/123
|
||||
*
|
||||
* Retrieve a specific proxy-host
|
||||
*/
|
||||
.get((req, res, next) => {
|
||||
validator({
|
||||
required: ['host_id'],
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
host_id: {
|
||||
$ref: 'definitions#/definitions/id'
|
||||
},
|
||||
expand: {
|
||||
$ref: 'definitions#/definitions/expand'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
host_id: req.params.host_id,
|
||||
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null)
|
||||
})
|
||||
.then(data => {
|
||||
return internalProxyHost.get(res.locals.access, {
|
||||
id: data.host_id,
|
||||
expand: data.expand
|
||||
});
|
||||
})
|
||||
.then(row => {
|
||||
res.status(200)
|
||||
.send(row);
|
||||
})
|
||||
.catch(next);
|
||||
})
|
||||
|
||||
/**
|
||||
* PUT /api/nginx/proxy-hosts/123
|
||||
*
|
||||
* Update and existing proxy-host
|
||||
*/
|
||||
.put((req, res, next) => {
|
||||
apiValidator({$ref: 'endpoints/proxy-hosts#/links/2/schema'}, req.body)
|
||||
.then(payload => {
|
||||
payload.id = req.params.host_id;
|
||||
return internalProxyHost.update(res.locals.access, payload);
|
||||
})
|
||||
.then(result => {
|
||||
res.status(200)
|
||||
.send(result);
|
||||
})
|
||||
.catch(next);
|
||||
})
|
||||
|
||||
/**
|
||||
* DELETE /api/nginx/proxy-hosts/123
|
||||
*
|
||||
* Update and existing proxy-host
|
||||
*/
|
||||
.delete((req, res, next) => {
|
||||
internalProxyHost.delete(res.locals.access, {id: req.params.host_id})
|
||||
.then(result => {
|
||||
res.status(200)
|
||||
.send(result);
|
||||
})
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
module.exports = router;
|
150
src/backend/routes/api/nginx/redirection_hosts.js
Normal file
150
src/backend/routes/api/nginx/redirection_hosts.js
Normal file
@ -0,0 +1,150 @@
|
||||
'use strict';
|
||||
|
||||
const express = require('express');
|
||||
const validator = require('../../../lib/validator');
|
||||
const jwtdecode = require('../../../lib/express/jwt-decode');
|
||||
const internalRedirectionHost = require('../../../internal/redirection-host');
|
||||
const apiValidator = require('../../../lib/validator/api');
|
||||
|
||||
let router = express.Router({
|
||||
caseSensitive: true,
|
||||
strict: true,
|
||||
mergeParams: true
|
||||
});
|
||||
|
||||
/**
|
||||
* /api/nginx/redirection-hosts
|
||||
*/
|
||||
router
|
||||
.route('/')
|
||||
.options((req, res) => {
|
||||
res.sendStatus(204);
|
||||
})
|
||||
.all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes
|
||||
|
||||
/**
|
||||
* GET /api/nginx/redirection-hosts
|
||||
*
|
||||
* Retrieve all redirection-hosts
|
||||
*/
|
||||
.get((req, res, next) => {
|
||||
validator({
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
expand: {
|
||||
$ref: 'definitions#/definitions/expand'
|
||||
},
|
||||
query: {
|
||||
$ref: 'definitions#/definitions/query'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null),
|
||||
query: (typeof req.query.query === 'string' ? req.query.query : null)
|
||||
})
|
||||
.then(data => {
|
||||
return internalRedirectionHost.getAll(res.locals.access, data.expand, data.query);
|
||||
})
|
||||
.then(rows => {
|
||||
res.status(200)
|
||||
.send(rows);
|
||||
})
|
||||
.catch(next);
|
||||
})
|
||||
|
||||
/**
|
||||
* POST /api/nginx/redirection-hosts
|
||||
*
|
||||
* Create a new redirection-host
|
||||
*/
|
||||
.post((req, res, next) => {
|
||||
apiValidator({$ref: 'endpoints/redirection-hosts#/links/1/schema'}, req.body)
|
||||
.then(payload => {
|
||||
return internalRedirectionHost.create(res.locals.access, payload);
|
||||
})
|
||||
.then(result => {
|
||||
res.status(201)
|
||||
.send(result);
|
||||
})
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
/**
|
||||
* Specific redirection-host
|
||||
*
|
||||
* /api/nginx/redirection-hosts/123
|
||||
*/
|
||||
router
|
||||
.route('/:host_id')
|
||||
.options((req, res) => {
|
||||
res.sendStatus(204);
|
||||
})
|
||||
.all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes
|
||||
|
||||
/**
|
||||
* GET /api/nginx/redirection-hosts/123
|
||||
*
|
||||
* Retrieve a specific redirection-host
|
||||
*/
|
||||
.get((req, res, next) => {
|
||||
validator({
|
||||
required: ['host_id'],
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
host_id: {
|
||||
$ref: 'definitions#/definitions/id'
|
||||
},
|
||||
expand: {
|
||||
$ref: 'definitions#/definitions/expand'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
host_id: req.params.host_id,
|
||||
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null)
|
||||
})
|
||||
.then(data => {
|
||||
return internalRedirectionHost.get(res.locals.access, {
|
||||
id: data.host_id,
|
||||
expand: data.expand
|
||||
});
|
||||
})
|
||||
.then(row => {
|
||||
res.status(200)
|
||||
.send(row);
|
||||
})
|
||||
.catch(next);
|
||||
})
|
||||
|
||||
/**
|
||||
* PUT /api/nginx/redirection-hosts/123
|
||||
*
|
||||
* Update and existing redirection-host
|
||||
*/
|
||||
.put((req, res, next) => {
|
||||
apiValidator({$ref: 'endpoints/redirection-hosts#/links/2/schema'}, req.body)
|
||||
.then(payload => {
|
||||
payload.id = req.params.host_id;
|
||||
return internalRedirectionHost.update(res.locals.access, payload);
|
||||
})
|
||||
.then(result => {
|
||||
res.status(200)
|
||||
.send(result);
|
||||
})
|
||||
.catch(next);
|
||||
})
|
||||
|
||||
/**
|
||||
* DELETE /api/nginx/redirection-hosts/123
|
||||
*
|
||||
* Update and existing redirection-host
|
||||
*/
|
||||
.delete((req, res, next) => {
|
||||
internalRedirectionHost.delete(res.locals.access, {id: req.params.host_id})
|
||||
.then(result => {
|
||||
res.status(200)
|
||||
.send(result);
|
||||
})
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
module.exports = router;
|
150
src/backend/routes/api/nginx/streams.js
Normal file
150
src/backend/routes/api/nginx/streams.js
Normal file
@ -0,0 +1,150 @@
|
||||
'use strict';
|
||||
|
||||
const express = require('express');
|
||||
const validator = require('../../../lib/validator');
|
||||
const jwtdecode = require('../../../lib/express/jwt-decode');
|
||||
const internalStream = require('../../../internal/stream');
|
||||
const apiValidator = require('../../../lib/validator/api');
|
||||
|
||||
let router = express.Router({
|
||||
caseSensitive: true,
|
||||
strict: true,
|
||||
mergeParams: true
|
||||
});
|
||||
|
||||
/**
|
||||
* /api/nginx/streams
|
||||
*/
|
||||
router
|
||||
.route('/')
|
||||
.options((req, res) => {
|
||||
res.sendStatus(204);
|
||||
})
|
||||
.all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes
|
||||
|
||||
/**
|
||||
* GET /api/nginx/streams
|
||||
*
|
||||
* Retrieve all streams
|
||||
*/
|
||||
.get((req, res, next) => {
|
||||
validator({
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
expand: {
|
||||
$ref: 'definitions#/definitions/expand'
|
||||
},
|
||||
query: {
|
||||
$ref: 'definitions#/definitions/query'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null),
|
||||
query: (typeof req.query.query === 'string' ? req.query.query : null)
|
||||
})
|
||||
.then(data => {
|
||||
return internalStream.getAll(res.locals.access, data.expand, data.query);
|
||||
})
|
||||
.then(rows => {
|
||||
res.status(200)
|
||||
.send(rows);
|
||||
})
|
||||
.catch(next);
|
||||
})
|
||||
|
||||
/**
|
||||
* POST /api/nginx/streams
|
||||
*
|
||||
* Create a new stream
|
||||
*/
|
||||
.post((req, res, next) => {
|
||||
apiValidator({$ref: 'endpoints/streams#/links/1/schema'}, req.body)
|
||||
.then(payload => {
|
||||
return internalStream.create(res.locals.access, payload);
|
||||
})
|
||||
.then(result => {
|
||||
res.status(201)
|
||||
.send(result);
|
||||
})
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
/**
|
||||
* Specific stream
|
||||
*
|
||||
* /api/nginx/streams/123
|
||||
*/
|
||||
router
|
||||
.route('/:stream_id')
|
||||
.options((req, res) => {
|
||||
res.sendStatus(204);
|
||||
})
|
||||
.all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes
|
||||
|
||||
/**
|
||||
* GET /api/nginx/streams/123
|
||||
*
|
||||
* Retrieve a specific stream
|
||||
*/
|
||||
.get((req, res, next) => {
|
||||
validator({
|
||||
required: ['stream_id'],
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
stream_id: {
|
||||
$ref: 'definitions#/definitions/id'
|
||||
},
|
||||
expand: {
|
||||
$ref: 'definitions#/definitions/expand'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
stream_id: req.params.stream_id,
|
||||
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null)
|
||||
})
|
||||
.then(data => {
|
||||
return internalStream.get(res.locals.access, {
|
||||
id: data.stream_id,
|
||||
expand: data.expand
|
||||
});
|
||||
})
|
||||
.then(row => {
|
||||
res.status(200)
|
||||
.send(row);
|
||||
})
|
||||
.catch(next);
|
||||
})
|
||||
|
||||
/**
|
||||
* PUT /api/nginx/streams/123
|
||||
*
|
||||
* Update and existing stream
|
||||
*/
|
||||
.put((req, res, next) => {
|
||||
apiValidator({$ref: 'endpoints/streams#/links/2/schema'}, req.body)
|
||||
.then(payload => {
|
||||
payload.id = req.params.stream_id;
|
||||
return internalStream.update(res.locals.access, payload);
|
||||
})
|
||||
.then(result => {
|
||||
res.status(200)
|
||||
.send(result);
|
||||
})
|
||||
.catch(next);
|
||||
})
|
||||
|
||||
/**
|
||||
* DELETE /api/nginx/streams/123
|
||||
*
|
||||
* Update and existing stream
|
||||
*/
|
||||
.delete((req, res, next) => {
|
||||
internalStream.delete(res.locals.access, {id: req.params.stream_id})
|
||||
.then(result => {
|
||||
res.status(200)
|
||||
.send(result);
|
||||
})
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
module.exports = router;
|
@ -3,7 +3,6 @@
|
||||
const express = require('express');
|
||||
const validator = require('../../lib/validator');
|
||||
const jwtdecode = require('../../lib/express/jwt-decode');
|
||||
const pagination = require('../../lib/express/pagination');
|
||||
const userIdFromMe = require('../../lib/express/user-id-from-me');
|
||||
const internalUser = require('../../internal/user');
|
||||
const apiValidator = require('../../lib/validator/api');
|
||||
|
203
src/backend/schema/endpoints/dead-hosts.json
Normal file
203
src/backend/schema/endpoints/dead-hosts.json
Normal file
@ -0,0 +1,203 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "endpoints/dead-hosts",
|
||||
"title": "Users",
|
||||
"description": "Endpoints relating to Dead Hosts",
|
||||
"stability": "stable",
|
||||
"type": "object",
|
||||
"definitions": {
|
||||
"id": {
|
||||
"$ref": "../definitions.json#/definitions/id"
|
||||
},
|
||||
"created_on": {
|
||||
"$ref": "../definitions.json#/definitions/created_on"
|
||||
},
|
||||
"modified_on": {
|
||||
"$ref": "../definitions.json#/definitions/modified_on"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name",
|
||||
"example": "Jamie Curnow",
|
||||
"type": "string",
|
||||
"minLength": 2,
|
||||
"maxLength": 100
|
||||
},
|
||||
"nickname": {
|
||||
"description": "Nickname",
|
||||
"example": "Jamie",
|
||||
"type": "string",
|
||||
"minLength": 2,
|
||||
"maxLength": 50
|
||||
},
|
||||
"email": {
|
||||
"$ref": "../definitions.json#/definitions/email"
|
||||
},
|
||||
"avatar": {
|
||||
"description": "Avatar",
|
||||
"example": "http://somewhere.jpg",
|
||||
"type": "string",
|
||||
"minLength": 2,
|
||||
"maxLength": 150,
|
||||
"readOnly": true
|
||||
},
|
||||
"roles": {
|
||||
"description": "Roles",
|
||||
"example": [
|
||||
"admin"
|
||||
],
|
||||
"type": "array"
|
||||
},
|
||||
"is_disabled": {
|
||||
"description": "Is Disabled",
|
||||
"example": false,
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"title": "List",
|
||||
"description": "Returns a list of Users",
|
||||
"href": "/users",
|
||||
"access": "private",
|
||||
"method": "GET",
|
||||
"rel": "self",
|
||||
"http_header": {
|
||||
"$ref": "../examples.json#/definitions/auth_header"
|
||||
},
|
||||
"targetSchema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/properties"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Create",
|
||||
"description": "Creates a new User",
|
||||
"href": "/users",
|
||||
"access": "private",
|
||||
"method": "POST",
|
||||
"rel": "create",
|
||||
"http_header": {
|
||||
"$ref": "../examples.json#/definitions/auth_header"
|
||||
},
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"nickname",
|
||||
"email"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"$ref": "#/definitions/name"
|
||||
},
|
||||
"nickname": {
|
||||
"$ref": "#/definitions/nickname"
|
||||
},
|
||||
"email": {
|
||||
"$ref": "#/definitions/email"
|
||||
},
|
||||
"roles": {
|
||||
"$ref": "#/definitions/roles"
|
||||
},
|
||||
"is_disabled": {
|
||||
"$ref": "#/definitions/is_disabled"
|
||||
},
|
||||
"auth": {
|
||||
"type": "object",
|
||||
"description": "Auth Credentials",
|
||||
"example": {
|
||||
"type": "password",
|
||||
"secret": "bigredhorsebanana"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"targetSchema": {
|
||||
"properties": {
|
||||
"$ref": "#/properties"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Update",
|
||||
"description": "Updates a existing User",
|
||||
"href": "/users/{definitions.identity.example}",
|
||||
"access": "private",
|
||||
"method": "PUT",
|
||||
"rel": "update",
|
||||
"http_header": {
|
||||
"$ref": "../examples.json#/definitions/auth_header"
|
||||
},
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"$ref": "#/definitions/name"
|
||||
},
|
||||
"nickname": {
|
||||
"$ref": "#/definitions/nickname"
|
||||
},
|
||||
"email": {
|
||||
"$ref": "#/definitions/email"
|
||||
},
|
||||
"roles": {
|
||||
"$ref": "#/definitions/roles"
|
||||
},
|
||||
"is_disabled": {
|
||||
"$ref": "#/definitions/is_disabled"
|
||||
}
|
||||
}
|
||||
},
|
||||
"targetSchema": {
|
||||
"properties": {
|
||||
"$ref": "#/properties"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Delete",
|
||||
"description": "Deletes a existing User",
|
||||
"href": "/users/{definitions.identity.example}",
|
||||
"access": "private",
|
||||
"method": "DELETE",
|
||||
"rel": "delete",
|
||||
"http_header": {
|
||||
"$ref": "../examples.json#/definitions/auth_header"
|
||||
},
|
||||
"targetSchema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/id"
|
||||
},
|
||||
"created_on": {
|
||||
"$ref": "#/definitions/created_on"
|
||||
},
|
||||
"modified_on": {
|
||||
"$ref": "#/definitions/modified_on"
|
||||
},
|
||||
"name": {
|
||||
"$ref": "#/definitions/name"
|
||||
},
|
||||
"nickname": {
|
||||
"$ref": "#/definitions/nickname"
|
||||
},
|
||||
"email": {
|
||||
"$ref": "#/definitions/email"
|
||||
},
|
||||
"avatar": {
|
||||
"$ref": "#/definitions/avatar"
|
||||
},
|
||||
"roles": {
|
||||
"$ref": "#/definitions/roles"
|
||||
},
|
||||
"is_disabled": {
|
||||
"$ref": "#/definitions/is_disabled"
|
||||
}
|
||||
}
|
||||
}
|
203
src/backend/schema/endpoints/proxy-hosts.json
Normal file
203
src/backend/schema/endpoints/proxy-hosts.json
Normal file
@ -0,0 +1,203 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "endpoints/proxy-hosts",
|
||||
"title": "Users",
|
||||
"description": "Endpoints relating to Proxy Hosts",
|
||||
"stability": "stable",
|
||||
"type": "object",
|
||||
"definitions": {
|
||||
"id": {
|
||||
"$ref": "../definitions.json#/definitions/id"
|
||||
},
|
||||
"created_on": {
|
||||
"$ref": "../definitions.json#/definitions/created_on"
|
||||
},
|
||||
"modified_on": {
|
||||
"$ref": "../definitions.json#/definitions/modified_on"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name",
|
||||
"example": "Jamie Curnow",
|
||||
"type": "string",
|
||||
"minLength": 2,
|
||||
"maxLength": 100
|
||||
},
|
||||
"nickname": {
|
||||
"description": "Nickname",
|
||||
"example": "Jamie",
|
||||
"type": "string",
|
||||
"minLength": 2,
|
||||
"maxLength": 50
|
||||
},
|
||||
"email": {
|
||||
"$ref": "../definitions.json#/definitions/email"
|
||||
},
|
||||
"avatar": {
|
||||
"description": "Avatar",
|
||||
"example": "http://somewhere.jpg",
|
||||
"type": "string",
|
||||
"minLength": 2,
|
||||
"maxLength": 150,
|
||||
"readOnly": true
|
||||
},
|
||||
"roles": {
|
||||
"description": "Roles",
|
||||
"example": [
|
||||
"admin"
|
||||
],
|
||||
"type": "array"
|
||||
},
|
||||
"is_disabled": {
|
||||
"description": "Is Disabled",
|
||||
"example": false,
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"title": "List",
|
||||
"description": "Returns a list of Users",
|
||||
"href": "/users",
|
||||
"access": "private",
|
||||
"method": "GET",
|
||||
"rel": "self",
|
||||
"http_header": {
|
||||
"$ref": "../examples.json#/definitions/auth_header"
|
||||
},
|
||||
"targetSchema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/properties"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Create",
|
||||
"description": "Creates a new User",
|
||||
"href": "/users",
|
||||
"access": "private",
|
||||
"method": "POST",
|
||||
"rel": "create",
|
||||
"http_header": {
|
||||
"$ref": "../examples.json#/definitions/auth_header"
|
||||
},
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"nickname",
|
||||
"email"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"$ref": "#/definitions/name"
|
||||
},
|
||||
"nickname": {
|
||||
"$ref": "#/definitions/nickname"
|
||||
},
|
||||
"email": {
|
||||
"$ref": "#/definitions/email"
|
||||
},
|
||||
"roles": {
|
||||
"$ref": "#/definitions/roles"
|
||||
},
|
||||
"is_disabled": {
|
||||
"$ref": "#/definitions/is_disabled"
|
||||
},
|
||||
"auth": {
|
||||
"type": "object",
|
||||
"description": "Auth Credentials",
|
||||
"example": {
|
||||
"type": "password",
|
||||
"secret": "bigredhorsebanana"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"targetSchema": {
|
||||
"properties": {
|
||||
"$ref": "#/properties"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Update",
|
||||
"description": "Updates a existing User",
|
||||
"href": "/users/{definitions.identity.example}",
|
||||
"access": "private",
|
||||
"method": "PUT",
|
||||
"rel": "update",
|
||||
"http_header": {
|
||||
"$ref": "../examples.json#/definitions/auth_header"
|
||||
},
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"$ref": "#/definitions/name"
|
||||
},
|
||||
"nickname": {
|
||||
"$ref": "#/definitions/nickname"
|
||||
},
|
||||
"email": {
|
||||
"$ref": "#/definitions/email"
|
||||
},
|
||||
"roles": {
|
||||
"$ref": "#/definitions/roles"
|
||||
},
|
||||
"is_disabled": {
|
||||
"$ref": "#/definitions/is_disabled"
|
||||
}
|
||||
}
|
||||
},
|
||||
"targetSchema": {
|
||||
"properties": {
|
||||
"$ref": "#/properties"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Delete",
|
||||
"description": "Deletes a existing User",
|
||||
"href": "/users/{definitions.identity.example}",
|
||||
"access": "private",
|
||||
"method": "DELETE",
|
||||
"rel": "delete",
|
||||
"http_header": {
|
||||
"$ref": "../examples.json#/definitions/auth_header"
|
||||
},
|
||||
"targetSchema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/id"
|
||||
},
|
||||
"created_on": {
|
||||
"$ref": "#/definitions/created_on"
|
||||
},
|
||||
"modified_on": {
|
||||
"$ref": "#/definitions/modified_on"
|
||||
},
|
||||
"name": {
|
||||
"$ref": "#/definitions/name"
|
||||
},
|
||||
"nickname": {
|
||||
"$ref": "#/definitions/nickname"
|
||||
},
|
||||
"email": {
|
||||
"$ref": "#/definitions/email"
|
||||
},
|
||||
"avatar": {
|
||||
"$ref": "#/definitions/avatar"
|
||||
},
|
||||
"roles": {
|
||||
"$ref": "#/definitions/roles"
|
||||
},
|
||||
"is_disabled": {
|
||||
"$ref": "#/definitions/is_disabled"
|
||||
}
|
||||
}
|
||||
}
|
203
src/backend/schema/endpoints/redirection-hosts.json
Normal file
203
src/backend/schema/endpoints/redirection-hosts.json
Normal file
@ -0,0 +1,203 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "endpoints/redirection-hosts",
|
||||
"title": "Users",
|
||||
"description": "Endpoints relating to Redirection Hosts",
|
||||
"stability": "stable",
|
||||
"type": "object",
|
||||
"definitions": {
|
||||
"id": {
|
||||
"$ref": "../definitions.json#/definitions/id"
|
||||
},
|
||||
"created_on": {
|
||||
"$ref": "../definitions.json#/definitions/created_on"
|
||||
},
|
||||
"modified_on": {
|
||||
"$ref": "../definitions.json#/definitions/modified_on"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name",
|
||||
"example": "Jamie Curnow",
|
||||
"type": "string",
|
||||
"minLength": 2,
|
||||
"maxLength": 100
|
||||
},
|
||||
"nickname": {
|
||||
"description": "Nickname",
|
||||
"example": "Jamie",
|
||||
"type": "string",
|
||||
"minLength": 2,
|
||||
"maxLength": 50
|
||||
},
|
||||
"email": {
|
||||
"$ref": "../definitions.json#/definitions/email"
|
||||
},
|
||||
"avatar": {
|
||||
"description": "Avatar",
|
||||
"example": "http://somewhere.jpg",
|
||||
"type": "string",
|
||||
"minLength": 2,
|
||||
"maxLength": 150,
|
||||
"readOnly": true
|
||||
},
|
||||
"roles": {
|
||||
"description": "Roles",
|
||||
"example": [
|
||||
"admin"
|
||||
],
|
||||
"type": "array"
|
||||
},
|
||||
"is_disabled": {
|
||||
"description": "Is Disabled",
|
||||
"example": false,
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"title": "List",
|
||||
"description": "Returns a list of Users",
|
||||
"href": "/users",
|
||||
"access": "private",
|
||||
"method": "GET",
|
||||
"rel": "self",
|
||||
"http_header": {
|
||||
"$ref": "../examples.json#/definitions/auth_header"
|
||||
},
|
||||
"targetSchema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/properties"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Create",
|
||||
"description": "Creates a new User",
|
||||
"href": "/users",
|
||||
"access": "private",
|
||||
"method": "POST",
|
||||
"rel": "create",
|
||||
"http_header": {
|
||||
"$ref": "../examples.json#/definitions/auth_header"
|
||||
},
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"nickname",
|
||||
"email"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"$ref": "#/definitions/name"
|
||||
},
|
||||
"nickname": {
|
||||
"$ref": "#/definitions/nickname"
|
||||
},
|
||||
"email": {
|
||||
"$ref": "#/definitions/email"
|
||||
},
|
||||
"roles": {
|
||||
"$ref": "#/definitions/roles"
|
||||
},
|
||||
"is_disabled": {
|
||||
"$ref": "#/definitions/is_disabled"
|
||||
},
|
||||
"auth": {
|
||||
"type": "object",
|
||||
"description": "Auth Credentials",
|
||||
"example": {
|
||||
"type": "password",
|
||||
"secret": "bigredhorsebanana"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"targetSchema": {
|
||||
"properties": {
|
||||
"$ref": "#/properties"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Update",
|
||||
"description": "Updates a existing User",
|
||||
"href": "/users/{definitions.identity.example}",
|
||||
"access": "private",
|
||||
"method": "PUT",
|
||||
"rel": "update",
|
||||
"http_header": {
|
||||
"$ref": "../examples.json#/definitions/auth_header"
|
||||
},
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"$ref": "#/definitions/name"
|
||||
},
|
||||
"nickname": {
|
||||
"$ref": "#/definitions/nickname"
|
||||
},
|
||||
"email": {
|
||||
"$ref": "#/definitions/email"
|
||||
},
|
||||
"roles": {
|
||||
"$ref": "#/definitions/roles"
|
||||
},
|
||||
"is_disabled": {
|
||||
"$ref": "#/definitions/is_disabled"
|
||||
}
|
||||
}
|
||||
},
|
||||
"targetSchema": {
|
||||
"properties": {
|
||||
"$ref": "#/properties"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Delete",
|
||||
"description": "Deletes a existing User",
|
||||
"href": "/users/{definitions.identity.example}",
|
||||
"access": "private",
|
||||
"method": "DELETE",
|
||||
"rel": "delete",
|
||||
"http_header": {
|
||||
"$ref": "../examples.json#/definitions/auth_header"
|
||||
},
|
||||
"targetSchema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/id"
|
||||
},
|
||||
"created_on": {
|
||||
"$ref": "#/definitions/created_on"
|
||||
},
|
||||
"modified_on": {
|
||||
"$ref": "#/definitions/modified_on"
|
||||
},
|
||||
"name": {
|
||||
"$ref": "#/definitions/name"
|
||||
},
|
||||
"nickname": {
|
||||
"$ref": "#/definitions/nickname"
|
||||
},
|
||||
"email": {
|
||||
"$ref": "#/definitions/email"
|
||||
},
|
||||
"avatar": {
|
||||
"$ref": "#/definitions/avatar"
|
||||
},
|
||||
"roles": {
|
||||
"$ref": "#/definitions/roles"
|
||||
},
|
||||
"is_disabled": {
|
||||
"$ref": "#/definitions/is_disabled"
|
||||
}
|
||||
}
|
||||
}
|
203
src/backend/schema/endpoints/streams.json
Normal file
203
src/backend/schema/endpoints/streams.json
Normal file
@ -0,0 +1,203 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "endpoints/streams",
|
||||
"title": "Users",
|
||||
"description": "Endpoints relating to Streams",
|
||||
"stability": "stable",
|
||||
"type": "object",
|
||||
"definitions": {
|
||||
"id": {
|
||||
"$ref": "../definitions.json#/definitions/id"
|
||||
},
|
||||
"created_on": {
|
||||
"$ref": "../definitions.json#/definitions/created_on"
|
||||
},
|
||||
"modified_on": {
|
||||
"$ref": "../definitions.json#/definitions/modified_on"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name",
|
||||
"example": "Jamie Curnow",
|
||||
"type": "string",
|
||||
"minLength": 2,
|
||||
"maxLength": 100
|
||||
},
|
||||
"nickname": {
|
||||
"description": "Nickname",
|
||||
"example": "Jamie",
|
||||
"type": "string",
|
||||
"minLength": 2,
|
||||
"maxLength": 50
|
||||
},
|
||||
"email": {
|
||||
"$ref": "../definitions.json#/definitions/email"
|
||||
},
|
||||
"avatar": {
|
||||
"description": "Avatar",
|
||||
"example": "http://somewhere.jpg",
|
||||
"type": "string",
|
||||
"minLength": 2,
|
||||
"maxLength": 150,
|
||||
"readOnly": true
|
||||
},
|
||||
"roles": {
|
||||
"description": "Roles",
|
||||
"example": [
|
||||
"admin"
|
||||
],
|
||||
"type": "array"
|
||||
},
|
||||
"is_disabled": {
|
||||
"description": "Is Disabled",
|
||||
"example": false,
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"title": "List",
|
||||
"description": "Returns a list of Users",
|
||||
"href": "/users",
|
||||
"access": "private",
|
||||
"method": "GET",
|
||||
"rel": "self",
|
||||
"http_header": {
|
||||
"$ref": "../examples.json#/definitions/auth_header"
|
||||
},
|
||||
"targetSchema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/properties"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Create",
|
||||
"description": "Creates a new User",
|
||||
"href": "/users",
|
||||
"access": "private",
|
||||
"method": "POST",
|
||||
"rel": "create",
|
||||
"http_header": {
|
||||
"$ref": "../examples.json#/definitions/auth_header"
|
||||
},
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"nickname",
|
||||
"email"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"$ref": "#/definitions/name"
|
||||
},
|
||||
"nickname": {
|
||||
"$ref": "#/definitions/nickname"
|
||||
},
|
||||
"email": {
|
||||
"$ref": "#/definitions/email"
|
||||
},
|
||||
"roles": {
|
||||
"$ref": "#/definitions/roles"
|
||||
},
|
||||
"is_disabled": {
|
||||
"$ref": "#/definitions/is_disabled"
|
||||
},
|
||||
"auth": {
|
||||
"type": "object",
|
||||
"description": "Auth Credentials",
|
||||
"example": {
|
||||
"type": "password",
|
||||
"secret": "bigredhorsebanana"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"targetSchema": {
|
||||
"properties": {
|
||||
"$ref": "#/properties"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Update",
|
||||
"description": "Updates a existing User",
|
||||
"href": "/users/{definitions.identity.example}",
|
||||
"access": "private",
|
||||
"method": "PUT",
|
||||
"rel": "update",
|
||||
"http_header": {
|
||||
"$ref": "../examples.json#/definitions/auth_header"
|
||||
},
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"$ref": "#/definitions/name"
|
||||
},
|
||||
"nickname": {
|
||||
"$ref": "#/definitions/nickname"
|
||||
},
|
||||
"email": {
|
||||
"$ref": "#/definitions/email"
|
||||
},
|
||||
"roles": {
|
||||
"$ref": "#/definitions/roles"
|
||||
},
|
||||
"is_disabled": {
|
||||
"$ref": "#/definitions/is_disabled"
|
||||
}
|
||||
}
|
||||
},
|
||||
"targetSchema": {
|
||||
"properties": {
|
||||
"$ref": "#/properties"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Delete",
|
||||
"description": "Deletes a existing User",
|
||||
"href": "/users/{definitions.identity.example}",
|
||||
"access": "private",
|
||||
"method": "DELETE",
|
||||
"rel": "delete",
|
||||
"http_header": {
|
||||
"$ref": "../examples.json#/definitions/auth_header"
|
||||
},
|
||||
"targetSchema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/id"
|
||||
},
|
||||
"created_on": {
|
||||
"$ref": "#/definitions/created_on"
|
||||
},
|
||||
"modified_on": {
|
||||
"$ref": "#/definitions/modified_on"
|
||||
},
|
||||
"name": {
|
||||
"$ref": "#/definitions/name"
|
||||
},
|
||||
"nickname": {
|
||||
"$ref": "#/definitions/nickname"
|
||||
},
|
||||
"email": {
|
||||
"$ref": "#/definitions/email"
|
||||
},
|
||||
"avatar": {
|
||||
"$ref": "#/definitions/avatar"
|
||||
},
|
||||
"roles": {
|
||||
"$ref": "#/definitions/roles"
|
||||
},
|
||||
"is_disabled": {
|
||||
"$ref": "#/definitions/is_disabled"
|
||||
}
|
||||
}
|
||||
}
|
@ -16,6 +16,18 @@
|
||||
},
|
||||
"users": {
|
||||
"$ref": "endpoints/users.json"
|
||||
},
|
||||
"proxy-hosts": {
|
||||
"$ref": "endpoints/proxy-hosts.json"
|
||||
},
|
||||
"redirection-hosts": {
|
||||
"$ref": "endpoints/redirection-hosts.json"
|
||||
},
|
||||
"dead-hosts": {
|
||||
"$ref": "endpoints/dead-hosts.json"
|
||||
},
|
||||
"streams": {
|
||||
"$ref": "endpoints/streams.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -103,6 +103,26 @@ function makeExpansionString (expand) {
|
||||
return items.join(',');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} path
|
||||
* @param {Array} [expand]
|
||||
* @param {String} [query]
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function getAllObjects (path, expand, query) {
|
||||
let params = [];
|
||||
|
||||
if (typeof expand === 'object' && expand !== null && expand.length) {
|
||||
params.push('expand=' + makeExpansionString(expand));
|
||||
}
|
||||
|
||||
if (typeof query === 'string') {
|
||||
params.push('query=' + query);
|
||||
}
|
||||
|
||||
return fetch('get', path + (params.length ? '?' + params.join('&') : ''));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
status: function () {
|
||||
return fetch('get', '');
|
||||
@ -168,17 +188,7 @@ module.exports = {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getAll: function (expand, query) {
|
||||
let params = [];
|
||||
|
||||
if (typeof expand === 'object' && expand !== null && expand.length) {
|
||||
params.push('expand=' + makeExpansionString(expand));
|
||||
}
|
||||
|
||||
if (typeof query === 'string') {
|
||||
params.push('query=' + query);
|
||||
}
|
||||
|
||||
return fetch('get', 'users' + (params.length ? '?' + params.join('&') : ''));
|
||||
return getAllObjects('users', expand, query);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -237,6 +247,64 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
Nginx: {
|
||||
|
||||
ProxyHosts: {
|
||||
/**
|
||||
* @param {Array} [expand]
|
||||
* @param {String} [query]
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getAll: function (expand, query) {
|
||||
return getAllObjects('nginx/proxy-hosts', expand, query);
|
||||
}
|
||||
},
|
||||
|
||||
RedirectionHosts: {
|
||||
/**
|
||||
* @param {Array} [expand]
|
||||
* @param {String} [query]
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getAll: function (expand, query) {
|
||||
return getAllObjects('nginx/redirection-hosts', expand, query);
|
||||
}
|
||||
},
|
||||
|
||||
Streams: {
|
||||
/**
|
||||
* @param {Array} [expand]
|
||||
* @param {String} [query]
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getAll: function (expand, query) {
|
||||
return getAllObjects('nginx/streams', expand, query);
|
||||
}
|
||||
},
|
||||
|
||||
DeadHosts: {
|
||||
/**
|
||||
* @param {Array} [expand]
|
||||
* @param {String} [query]
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getAll: function (expand, query) {
|
||||
return getAllObjects('nginx/dead-hosts', expand, query);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
AccessLists: {
|
||||
/**
|
||||
* @param {Array} [expand]
|
||||
* @param {String} [query]
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getAll: function (expand, query) {
|
||||
return getAllObjects('access-lists', expand, query);
|
||||
}
|
||||
},
|
||||
|
||||
Reports: {
|
||||
|
||||
/**
|
||||
@ -244,6 +312,6 @@ module.exports = {
|
||||
*/
|
||||
getHostStats: function () {
|
||||
return fetch('get', 'reports/hosts');
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -124,60 +124,70 @@ module.exports = {
|
||||
* Nginx Proxy Hosts
|
||||
*/
|
||||
showNginxProxy: function () {
|
||||
let controller = this;
|
||||
if (Cache.User.isAdmin() || Cache.User.canView('proxy_hosts')) {
|
||||
let controller = this;
|
||||
|
||||
require(['./main', './nginx/proxy/main'], (App, View) => {
|
||||
controller.navigate('/nginx/proxy');
|
||||
App.UI.showAppContent(new View());
|
||||
});
|
||||
require(['./main', './nginx/proxy/main'], (App, View) => {
|
||||
controller.navigate('/nginx/proxy');
|
||||
App.UI.showAppContent(new View());
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Nginx Redirection Hosts
|
||||
*/
|
||||
showNginxRedirection: function () {
|
||||
let controller = this;
|
||||
if (Cache.User.isAdmin() || Cache.User.canView('redirection_hosts')) {
|
||||
let controller = this;
|
||||
|
||||
require(['./main', './nginx/redirection/main'], (App, View) => {
|
||||
controller.navigate('/nginx/redirection');
|
||||
App.UI.showAppContent(new View());
|
||||
});
|
||||
require(['./main', './nginx/redirection/main'], (App, View) => {
|
||||
controller.navigate('/nginx/redirection');
|
||||
App.UI.showAppContent(new View());
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Nginx Stream Hosts
|
||||
*/
|
||||
showNginxStream: function () {
|
||||
let controller = this;
|
||||
if (Cache.User.isAdmin() || Cache.User.canView('streams')) {
|
||||
let controller = this;
|
||||
|
||||
require(['./main', './nginx/stream/main'], (App, View) => {
|
||||
controller.navigate('/nginx/stream');
|
||||
App.UI.showAppContent(new View());
|
||||
});
|
||||
require(['./main', './nginx/stream/main'], (App, View) => {
|
||||
controller.navigate('/nginx/stream');
|
||||
App.UI.showAppContent(new View());
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Nginx 404 Hosts
|
||||
* Nginx Dead Hosts
|
||||
*/
|
||||
showNginx404: function () {
|
||||
let controller = this;
|
||||
showNginxDead: function () {
|
||||
if (Cache.User.isAdmin() || Cache.User.canView('dead_hosts')) {
|
||||
let controller = this;
|
||||
|
||||
require(['./main', './nginx/404/main'], (App, View) => {
|
||||
controller.navigate('/nginx/404');
|
||||
App.UI.showAppContent(new View());
|
||||
});
|
||||
require(['./main', './nginx/dead/main'], (App, View) => {
|
||||
controller.navigate('/nginx/404');
|
||||
App.UI.showAppContent(new View());
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Nginx Access
|
||||
*/
|
||||
showNginxAccess: function () {
|
||||
let controller = this;
|
||||
if (Cache.User.isAdmin() || Cache.User.canView('access_lists')) {
|
||||
let controller = this;
|
||||
|
||||
require(['./main', './nginx/access/main'], (App, View) => {
|
||||
controller.navigate('/nginx/access');
|
||||
App.UI.showAppContent(new View());
|
||||
});
|
||||
require(['./main', './nginx/access/main'], (App, View) => {
|
||||
controller.navigate('/nginx/access');
|
||||
App.UI.showAppContent(new View());
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -2,8 +2,10 @@
|
||||
<h1 class="page-title">Hi <%- getUserName() %></h1>
|
||||
</div>
|
||||
|
||||
<% if (columns) { %>
|
||||
<div class="row">
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<% if (canShow('proxy_hosts')) { %>
|
||||
<div class="col-sm-<%- 24 / columns %> col-lg-<%- 12 / columns %>">
|
||||
<div class="card p-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="stamp stamp-md bg-green mr-3">
|
||||
@ -15,8 +17,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<% if (canShow('redirection_hosts')) { %>
|
||||
<div class="col-sm-<%- 24 / columns %> col-lg-<%- 12 / columns %>">
|
||||
<div class="card p-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="stamp stamp-md bg-yellow mr-3">
|
||||
@ -28,8 +32,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<% if (canShow('streams')) { %>
|
||||
<div class="col-sm-<%- 24 / columns %> col-lg-<%- 12 / columns %>">
|
||||
<div class="card p-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="stamp stamp-md bg-blue mr-3">
|
||||
@ -41,17 +47,21 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<% if (canShow('dead_hosts')) { %>
|
||||
<div class="col-sm-<%- 24 / columns %> col-lg-<%- 12 / columns %>">
|
||||
<div class="card p-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="stamp stamp-md bg-red mr-3">
|
||||
<i class="fe fe-zap-off"></i>
|
||||
</span>
|
||||
<div>
|
||||
<h4 class="m-0"><a href="/nginx/404"><%- getHostStat('404') %> <small>404 Hosts</small></a></h4>
|
||||
<h4 class="m-0"><a href="/nginx/404"><%- getHostStat('dead') %> <small>404 Hosts</small></a></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
<% } %>
|
||||
|
@ -10,6 +10,7 @@ const template = require('./main.ejs');
|
||||
module.exports = Mn.View.extend({
|
||||
template: template,
|
||||
id: 'dashboard',
|
||||
columns: 0,
|
||||
|
||||
stats: {},
|
||||
|
||||
@ -38,7 +39,13 @@ module.exports = Mn.View.extend({
|
||||
}
|
||||
|
||||
return '-';
|
||||
}
|
||||
},
|
||||
|
||||
canShow: function (perm) {
|
||||
return Cache.User.isAdmin() || Cache.User.canView(perm);
|
||||
},
|
||||
|
||||
columns: view.columns
|
||||
}
|
||||
},
|
||||
|
||||
@ -57,5 +64,31 @@ module.exports = Mn.View.extend({
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Object} [model]
|
||||
*/
|
||||
preRender: function (model) {
|
||||
this.columns = 0;
|
||||
|
||||
// calculate the available columns based on permissions for the objects
|
||||
// and store as a variable
|
||||
//let view = this;
|
||||
let perms = ['proxy_hosts', 'redirection_hosts', 'streams', 'dead_hosts'];
|
||||
|
||||
perms.map(perm => {
|
||||
this.columns += Cache.User.isAdmin() || Cache.User.canView(perm) ? 1 : 0;
|
||||
});
|
||||
|
||||
// Prevent double rendering on initial calls
|
||||
if (typeof model !== 'undefined') {
|
||||
this.render();
|
||||
}
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
this.preRender();
|
||||
this.listenTo(Cache.User, 'change', this.preRender);
|
||||
}
|
||||
});
|
||||
|
11
src/frontend/js/app/empty/main.ejs
Normal file
11
src/frontend/js/app/empty/main.ejs
Normal file
@ -0,0 +1,11 @@
|
||||
<% if (title) { %>
|
||||
<h1 class="h2 mb-3"><%- title %></h1>
|
||||
<% }
|
||||
|
||||
if (subtitle) { %>
|
||||
<p class="h4 text-muted font-weight-normal mb-7"><%- subtitle %></p>
|
||||
<% }
|
||||
|
||||
if (link) { %>
|
||||
<a class="btn btn-teal" href="#"><%- link %></a>
|
||||
<% } %>
|
30
src/frontend/js/app/empty/main.js
Normal file
30
src/frontend/js/app/empty/main.js
Normal file
@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
|
||||
const Mn = require('backbone.marionette');
|
||||
const template = require('./main.ejs');
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
className: 'text-center m-7',
|
||||
template: template,
|
||||
|
||||
ui: {
|
||||
action: 'a'
|
||||
},
|
||||
|
||||
events: {
|
||||
'click @ui.action': function (e) {
|
||||
e.preventDefault();
|
||||
this.getOption('action')();
|
||||
}
|
||||
},
|
||||
|
||||
templateContext: function () {
|
||||
return {
|
||||
title: this.getOption('title'),
|
||||
subtitle: this.getOption('subtitle'),
|
||||
link: this.getOption('link'),
|
||||
action: typeof this.getOption('action') === 'function'
|
||||
};
|
||||
}
|
||||
|
||||
});
|
7
src/frontend/js/app/error/main.ejs
Normal file
7
src/frontend/js/app/error/main.ejs
Normal file
@ -0,0 +1,7 @@
|
||||
<i class="fe fe-alert-triangle mr-2" aria-hidden="true"></i>
|
||||
<%= code ? '<strong>' + code + '</strong> — ' : '' %>
|
||||
<%- message %>
|
||||
|
||||
<% if (retry) { %>
|
||||
<br><br><a href="#" class="btn btn-sm btn-warning retry">Try again</a>
|
||||
<% } %>
|
29
src/frontend/js/app/error/main.js
Normal file
29
src/frontend/js/app/error/main.js
Normal file
@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
const Mn = require('backbone.marionette');
|
||||
const template = require('./main.ejs');
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
template: template,
|
||||
className: 'alert alert-icon alert-warning m-5',
|
||||
|
||||
ui: {
|
||||
retry: 'a.retry'
|
||||
},
|
||||
|
||||
events: {
|
||||
'click @ui.retry': function (e) {
|
||||
e.preventDefault();
|
||||
this.getOption('retry')();
|
||||
}
|
||||
},
|
||||
|
||||
templateContext: function () {
|
||||
return {
|
||||
message: this.getOption('message'),
|
||||
code: this.getOption('code'),
|
||||
retry: typeof this.getOption('retry') === 'function'
|
||||
};
|
||||
}
|
||||
|
||||
});
|
@ -1 +0,0 @@
|
||||
404
|
@ -1,9 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const Mn = require('backbone.marionette');
|
||||
const template = require('./main.ejs');
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
template: template,
|
||||
id: 'nginx-404'
|
||||
});
|
32
src/frontend/js/app/nginx/dead/list/item.ejs
Normal file
32
src/frontend/js/app/nginx/dead/list/item.ejs
Normal file
@ -0,0 +1,32 @@
|
||||
<td class="text-center">
|
||||
<div class="avatar d-block" style="background-image: url(<%- avatar || '/images/default-avatar.jpg' %>)">
|
||||
<span class="avatar-status <%- is_disabled ? 'bg-red' : 'bg-green' %>"></span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div><%- name %></div>
|
||||
<div class="small text-muted">
|
||||
Created: <%- formatDbDate(created_on, 'Do MMMM YYYY') %>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div><%- email %></div>
|
||||
</td>
|
||||
<td>
|
||||
<div><%- roles.join(', ') %></div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<div class="item-action dropdown">
|
||||
<a href="#" data-toggle="dropdown" class="icon"><i class="fe fe-more-vertical"></i></a>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a href="#" class="edit-user dropdown-item"><i class="dropdown-icon fe fe-edit"></i> Edit Details</a>
|
||||
<a href="#" class="edit-permissions dropdown-item"><i class="dropdown-icon fe fe-shield"></i> Edit Permissions</a>
|
||||
<a href="#" class="set-password dropdown-item"><i class="dropdown-icon fe fe-lock"></i> Set Password</a>
|
||||
<% if (!isSelf()) { %>
|
||||
<a href="#" class="login dropdown-item"><i class="dropdown-icon fe fe-log-in"></i> Sign in as User</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="#" class="delete-user dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> Delete User</a>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
72
src/frontend/js/app/nginx/dead/list/item.js
Normal file
72
src/frontend/js/app/nginx/dead/list/item.js
Normal file
@ -0,0 +1,72 @@
|
||||
'use strict';
|
||||
|
||||
const Mn = require('backbone.marionette');
|
||||
const Controller = require('../../../controller');
|
||||
const Api = require('../../../api');
|
||||
const Cache = require('../../../cache');
|
||||
const Tokens = require('../../../tokens');
|
||||
const template = require('./item.ejs');
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
template: template,
|
||||
tagName: 'tr',
|
||||
|
||||
ui: {
|
||||
edit: 'a.edit-user',
|
||||
permissions: 'a.edit-permissions',
|
||||
password: 'a.set-password',
|
||||
login: 'a.login',
|
||||
delete: 'a.delete-user'
|
||||
},
|
||||
|
||||
events: {
|
||||
'click @ui.edit': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.showUserForm(this.model);
|
||||
},
|
||||
|
||||
'click @ui.permissions': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.showUserPermissions(this.model);
|
||||
},
|
||||
|
||||
'click @ui.password': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.showUserPasswordForm(this.model);
|
||||
},
|
||||
|
||||
'click @ui.delete': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.showUserDeleteConfirm(this.model);
|
||||
},
|
||||
|
||||
'click @ui.login': function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (Cache.User.get('id') !== this.model.get('id')) {
|
||||
this.ui.login.prop('disabled', true).addClass('btn-disabled');
|
||||
|
||||
Api.Users.loginAs(this.model.get('id'))
|
||||
.then(res => {
|
||||
Tokens.addToken(res.token, res.user.nickname || res.user.name);
|
||||
window.location = '/';
|
||||
window.location.reload();
|
||||
})
|
||||
.catch(err => {
|
||||
alert(err.message);
|
||||
this.ui.login.prop('disabled', false).removeClass('btn-disabled');
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
templateContext: {
|
||||
isSelf: function () {
|
||||
return Cache.User.get('id') === this.id;
|
||||
}
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
}
|
||||
});
|
10
src/frontend/js/app/nginx/dead/list/main.ejs
Normal file
10
src/frontend/js/app/nginx/dead/list/main.ejs
Normal file
@ -0,0 +1,10 @@
|
||||
<thead>
|
||||
<th width="30"> </th>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Roles</th>
|
||||
<th> </th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- items -->
|
||||
</tbody>
|
29
src/frontend/js/app/nginx/dead/list/main.js
Normal file
29
src/frontend/js/app/nginx/dead/list/main.js
Normal file
@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
const Mn = require('backbone.marionette');
|
||||
const ItemView = require('./item');
|
||||
const template = require('./main.ejs');
|
||||
|
||||
const TableBody = Mn.CollectionView.extend({
|
||||
tagName: 'tbody',
|
||||
childView: ItemView
|
||||
});
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
tagName: 'table',
|
||||
className: 'table table-hover table-outline table-vcenter text-nowrap card-table',
|
||||
template: template,
|
||||
|
||||
regions: {
|
||||
body: {
|
||||
el: 'tbody',
|
||||
replaceElement: true
|
||||
}
|
||||
},
|
||||
|
||||
onRender: function () {
|
||||
this.showChildView('body', new TableBody({
|
||||
collection: this.collection
|
||||
}));
|
||||
}
|
||||
});
|
16
src/frontend/js/app/nginx/dead/main.ejs
Normal file
16
src/frontend/js/app/nginx/dead/main.ejs
Normal file
@ -0,0 +1,16 @@
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">404 Hosts</h3>
|
||||
<div class="card-options">
|
||||
<a href="#" class="btn btn-outline-teal btn-sm ml-2 add-item">Add 404 Host</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>
|
70
src/frontend/js/app/nginx/dead/main.js
Normal file
70
src/frontend/js/app/nginx/dead/main.js
Normal file
@ -0,0 +1,70 @@
|
||||
'use strict';
|
||||
|
||||
const Mn = require('backbone.marionette');
|
||||
const DeadHostModel = require('../../../models/dead-host');
|
||||
const Api = require('../../api');
|
||||
const Controller = require('../../controller');
|
||||
const ListView = require('./list/main');
|
||||
const ErrorView = require('../../error/main');
|
||||
const template = require('./main.ejs');
|
||||
const EmptyView = require('../../empty/main');
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
id: 'nginx-dead',
|
||||
template: template,
|
||||
|
||||
ui: {
|
||||
list_region: '.list-region',
|
||||
add: '.add-item',
|
||||
dimmer: '.dimmer'
|
||||
},
|
||||
|
||||
regions: {
|
||||
list_region: '@ui.list_region'
|
||||
},
|
||||
|
||||
events: {
|
||||
'click @ui.add': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.showNginxDeadForm();
|
||||
}
|
||||
},
|
||||
|
||||
onRender: function () {
|
||||
let view = this;
|
||||
|
||||
Api.Nginx.DeadHosts.getAll()
|
||||
.then(response => {
|
||||
if (!view.isDestroyed()) {
|
||||
if (response && response.length) {
|
||||
view.showChildView('list_region', new ListView({
|
||||
collection: new DeadHostModel.Collection(response)
|
||||
}));
|
||||
} else {
|
||||
view.showChildView('list_region', new EmptyView({
|
||||
title: 'There are no 404 Hosts',
|
||||
subtitle: 'Why don\'t you create one?',
|
||||
link: 'Add 404 Host',
|
||||
action: function () {
|
||||
Controller.showNginxDeadForm();
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
view.showChildView('list_region', new ErrorView({
|
||||
code: err.code,
|
||||
message: err.message,
|
||||
retry: function () {
|
||||
Controller.showNginxDead();
|
||||
}
|
||||
}));
|
||||
|
||||
console.error(err);
|
||||
})
|
||||
.then(() => {
|
||||
view.ui.dimmer.removeClass('active');
|
||||
});
|
||||
}
|
||||
});
|
32
src/frontend/js/app/nginx/proxy/list/item.ejs
Normal file
32
src/frontend/js/app/nginx/proxy/list/item.ejs
Normal file
@ -0,0 +1,32 @@
|
||||
<td class="text-center">
|
||||
<div class="avatar d-block" style="background-image: url(<%- avatar || '/images/default-avatar.jpg' %>)">
|
||||
<span class="avatar-status <%- is_disabled ? 'bg-red' : 'bg-green' %>"></span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div><%- name %></div>
|
||||
<div class="small text-muted">
|
||||
Created: <%- formatDbDate(created_on, 'Do MMMM YYYY') %>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div><%- email %></div>
|
||||
</td>
|
||||
<td>
|
||||
<div><%- roles.join(', ') %></div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<div class="item-action dropdown">
|
||||
<a href="#" data-toggle="dropdown" class="icon"><i class="fe fe-more-vertical"></i></a>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a href="#" class="edit-user dropdown-item"><i class="dropdown-icon fe fe-edit"></i> Edit Details</a>
|
||||
<a href="#" class="edit-permissions dropdown-item"><i class="dropdown-icon fe fe-shield"></i> Edit Permissions</a>
|
||||
<a href="#" class="set-password dropdown-item"><i class="dropdown-icon fe fe-lock"></i> Set Password</a>
|
||||
<% if (!isSelf()) { %>
|
||||
<a href="#" class="login dropdown-item"><i class="dropdown-icon fe fe-log-in"></i> Sign in as User</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="#" class="delete-user dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> Delete User</a>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
72
src/frontend/js/app/nginx/proxy/list/item.js
Normal file
72
src/frontend/js/app/nginx/proxy/list/item.js
Normal file
@ -0,0 +1,72 @@
|
||||
'use strict';
|
||||
|
||||
const Mn = require('backbone.marionette');
|
||||
const Controller = require('../../../controller');
|
||||
const Api = require('../../../api');
|
||||
const Cache = require('../../../cache');
|
||||
const Tokens = require('../../../tokens');
|
||||
const template = require('./item.ejs');
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
template: template,
|
||||
tagName: 'tr',
|
||||
|
||||
ui: {
|
||||
edit: 'a.edit-user',
|
||||
permissions: 'a.edit-permissions',
|
||||
password: 'a.set-password',
|
||||
login: 'a.login',
|
||||
delete: 'a.delete-user'
|
||||
},
|
||||
|
||||
events: {
|
||||
'click @ui.edit': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.showUserForm(this.model);
|
||||
},
|
||||
|
||||
'click @ui.permissions': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.showUserPermissions(this.model);
|
||||
},
|
||||
|
||||
'click @ui.password': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.showUserPasswordForm(this.model);
|
||||
},
|
||||
|
||||
'click @ui.delete': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.showUserDeleteConfirm(this.model);
|
||||
},
|
||||
|
||||
'click @ui.login': function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (Cache.User.get('id') !== this.model.get('id')) {
|
||||
this.ui.login.prop('disabled', true).addClass('btn-disabled');
|
||||
|
||||
Api.Users.loginAs(this.model.get('id'))
|
||||
.then(res => {
|
||||
Tokens.addToken(res.token, res.user.nickname || res.user.name);
|
||||
window.location = '/';
|
||||
window.location.reload();
|
||||
})
|
||||
.catch(err => {
|
||||
alert(err.message);
|
||||
this.ui.login.prop('disabled', false).removeClass('btn-disabled');
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
templateContext: {
|
||||
isSelf: function () {
|
||||
return Cache.User.get('id') === this.id;
|
||||
}
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
}
|
||||
});
|
10
src/frontend/js/app/nginx/proxy/list/main.ejs
Normal file
10
src/frontend/js/app/nginx/proxy/list/main.ejs
Normal file
@ -0,0 +1,10 @@
|
||||
<thead>
|
||||
<th width="30"> </th>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Roles</th>
|
||||
<th> </th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- items -->
|
||||
</tbody>
|
29
src/frontend/js/app/nginx/proxy/list/main.js
Normal file
29
src/frontend/js/app/nginx/proxy/list/main.js
Normal file
@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
const Mn = require('backbone.marionette');
|
||||
const ItemView = require('./item');
|
||||
const template = require('./main.ejs');
|
||||
|
||||
const TableBody = Mn.CollectionView.extend({
|
||||
tagName: 'tbody',
|
||||
childView: ItemView
|
||||
});
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
tagName: 'table',
|
||||
className: 'table table-hover table-outline table-vcenter text-nowrap card-table',
|
||||
template: template,
|
||||
|
||||
regions: {
|
||||
body: {
|
||||
el: 'tbody',
|
||||
replaceElement: true
|
||||
}
|
||||
},
|
||||
|
||||
onRender: function () {
|
||||
this.showChildView('body', new TableBody({
|
||||
collection: this.collection
|
||||
}));
|
||||
}
|
||||
});
|
@ -1 +1,16 @@
|
||||
proxy
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Proxy Hosts</h3>
|
||||
<div class="card-options">
|
||||
<a href="#" class="btn btn-outline-teal btn-sm ml-2 add-item">Add Proxy Host</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>
|
||||
|
@ -1,9 +1,70 @@
|
||||
'use strict';
|
||||
|
||||
const Mn = require('backbone.marionette');
|
||||
const template = require('./main.ejs');
|
||||
const Mn = require('backbone.marionette');
|
||||
const ProxyHostModel = require('../../../models/proxy-host');
|
||||
const Api = require('../../api');
|
||||
const Controller = require('../../controller');
|
||||
const ListView = require('./list/main');
|
||||
const ErrorView = require('../../error/main');
|
||||
const template = require('./main.ejs');
|
||||
const EmptyView = require('../../empty/main');
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
id: 'nginx-proxy',
|
||||
template: template,
|
||||
id: 'nginx-proxy'
|
||||
|
||||
ui: {
|
||||
list_region: '.list-region',
|
||||
add: '.add-item',
|
||||
dimmer: '.dimmer'
|
||||
},
|
||||
|
||||
regions: {
|
||||
list_region: '@ui.list_region'
|
||||
},
|
||||
|
||||
events: {
|
||||
'click @ui.add': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.showNginxProxyForm();
|
||||
}
|
||||
},
|
||||
|
||||
onRender: function () {
|
||||
let view = this;
|
||||
|
||||
Api.Nginx.ProxyHosts.getAll()
|
||||
.then(response => {
|
||||
if (!view.isDestroyed()) {
|
||||
if (response && response.length) {
|
||||
view.showChildView('list_region', new ListView({
|
||||
collection: new ProxyHostModel.Collection(response)
|
||||
}));
|
||||
} else {
|
||||
view.showChildView('list_region', new EmptyView({
|
||||
title: 'There are no Proxy Hosts',
|
||||
subtitle: 'Why don\'t you create one?',
|
||||
link: 'Add Proxy Host',
|
||||
action: function () {
|
||||
Controller.showNginxProxyForm();
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
view.showChildView('list_region', new ErrorView({
|
||||
code: err.code,
|
||||
message: err.message,
|
||||
retry: function () {
|
||||
Controller.showNginxProxy();
|
||||
}
|
||||
}));
|
||||
|
||||
console.error(err);
|
||||
})
|
||||
.then(() => {
|
||||
view.ui.dimmer.removeClass('active');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
32
src/frontend/js/app/nginx/redirection/list/item.ejs
Normal file
32
src/frontend/js/app/nginx/redirection/list/item.ejs
Normal file
@ -0,0 +1,32 @@
|
||||
<td class="text-center">
|
||||
<div class="avatar d-block" style="background-image: url(<%- avatar || '/images/default-avatar.jpg' %>)">
|
||||
<span class="avatar-status <%- is_disabled ? 'bg-red' : 'bg-green' %>"></span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div><%- name %></div>
|
||||
<div class="small text-muted">
|
||||
Created: <%- formatDbDate(created_on, 'Do MMMM YYYY') %>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div><%- email %></div>
|
||||
</td>
|
||||
<td>
|
||||
<div><%- roles.join(', ') %></div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<div class="item-action dropdown">
|
||||
<a href="#" data-toggle="dropdown" class="icon"><i class="fe fe-more-vertical"></i></a>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a href="#" class="edit-user dropdown-item"><i class="dropdown-icon fe fe-edit"></i> Edit Details</a>
|
||||
<a href="#" class="edit-permissions dropdown-item"><i class="dropdown-icon fe fe-shield"></i> Edit Permissions</a>
|
||||
<a href="#" class="set-password dropdown-item"><i class="dropdown-icon fe fe-lock"></i> Set Password</a>
|
||||
<% if (!isSelf()) { %>
|
||||
<a href="#" class="login dropdown-item"><i class="dropdown-icon fe fe-log-in"></i> Sign in as User</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="#" class="delete-user dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> Delete User</a>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
72
src/frontend/js/app/nginx/redirection/list/item.js
Normal file
72
src/frontend/js/app/nginx/redirection/list/item.js
Normal file
@ -0,0 +1,72 @@
|
||||
'use strict';
|
||||
|
||||
const Mn = require('backbone.marionette');
|
||||
const Controller = require('../../../controller');
|
||||
const Api = require('../../../api');
|
||||
const Cache = require('../../../cache');
|
||||
const Tokens = require('../../../tokens');
|
||||
const template = require('./item.ejs');
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
template: template,
|
||||
tagName: 'tr',
|
||||
|
||||
ui: {
|
||||
edit: 'a.edit-user',
|
||||
permissions: 'a.edit-permissions',
|
||||
password: 'a.set-password',
|
||||
login: 'a.login',
|
||||
delete: 'a.delete-user'
|
||||
},
|
||||
|
||||
events: {
|
||||
'click @ui.edit': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.showUserForm(this.model);
|
||||
},
|
||||
|
||||
'click @ui.permissions': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.showUserPermissions(this.model);
|
||||
},
|
||||
|
||||
'click @ui.password': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.showUserPasswordForm(this.model);
|
||||
},
|
||||
|
||||
'click @ui.delete': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.showUserDeleteConfirm(this.model);
|
||||
},
|
||||
|
||||
'click @ui.login': function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (Cache.User.get('id') !== this.model.get('id')) {
|
||||
this.ui.login.prop('disabled', true).addClass('btn-disabled');
|
||||
|
||||
Api.Users.loginAs(this.model.get('id'))
|
||||
.then(res => {
|
||||
Tokens.addToken(res.token, res.user.nickname || res.user.name);
|
||||
window.location = '/';
|
||||
window.location.reload();
|
||||
})
|
||||
.catch(err => {
|
||||
alert(err.message);
|
||||
this.ui.login.prop('disabled', false).removeClass('btn-disabled');
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
templateContext: {
|
||||
isSelf: function () {
|
||||
return Cache.User.get('id') === this.id;
|
||||
}
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
}
|
||||
});
|
10
src/frontend/js/app/nginx/redirection/list/main.ejs
Normal file
10
src/frontend/js/app/nginx/redirection/list/main.ejs
Normal file
@ -0,0 +1,10 @@
|
||||
<thead>
|
||||
<th width="30"> </th>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Roles</th>
|
||||
<th> </th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- items -->
|
||||
</tbody>
|
29
src/frontend/js/app/nginx/redirection/list/main.js
Normal file
29
src/frontend/js/app/nginx/redirection/list/main.js
Normal file
@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
const Mn = require('backbone.marionette');
|
||||
const ItemView = require('./item');
|
||||
const template = require('./main.ejs');
|
||||
|
||||
const TableBody = Mn.CollectionView.extend({
|
||||
tagName: 'tbody',
|
||||
childView: ItemView
|
||||
});
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
tagName: 'table',
|
||||
className: 'table table-hover table-outline table-vcenter text-nowrap card-table',
|
||||
template: template,
|
||||
|
||||
regions: {
|
||||
body: {
|
||||
el: 'tbody',
|
||||
replaceElement: true
|
||||
}
|
||||
},
|
||||
|
||||
onRender: function () {
|
||||
this.showChildView('body', new TableBody({
|
||||
collection: this.collection
|
||||
}));
|
||||
}
|
||||
});
|
@ -1 +1,16 @@
|
||||
redirection
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Redirection Hosts</h3>
|
||||
<div class="card-options">
|
||||
<a href="#" class="btn btn-outline-teal btn-sm ml-2 add-item">Add Redirection Host</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>
|
||||
|
@ -1,9 +1,70 @@
|
||||
'use strict';
|
||||
|
||||
const Mn = require('backbone.marionette');
|
||||
const template = require('./main.ejs');
|
||||
const Mn = require('backbone.marionette');
|
||||
const RedirectionHostModel = require('../../../models/redirection-host');
|
||||
const Api = require('../../api');
|
||||
const Controller = require('../../controller');
|
||||
const ListView = require('./list/main');
|
||||
const ErrorView = require('../../error/main');
|
||||
const template = require('./main.ejs');
|
||||
const EmptyView = require('../../empty/main');
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
id: 'nginx-redirections',
|
||||
template: template,
|
||||
id: 'nginx-redirection'
|
||||
|
||||
ui: {
|
||||
list_region: '.list-region',
|
||||
add: '.add-item',
|
||||
dimmer: '.dimmer'
|
||||
},
|
||||
|
||||
regions: {
|
||||
list_region: '@ui.list_region'
|
||||
},
|
||||
|
||||
events: {
|
||||
'click @ui.add': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.showNginxRedirectionForm();
|
||||
}
|
||||
},
|
||||
|
||||
onRender: function () {
|
||||
let view = this;
|
||||
|
||||
Api.Nginx.RedirectionHosts.getAll()
|
||||
.then(response => {
|
||||
if (!view.isDestroyed()) {
|
||||
if (response && response.length) {
|
||||
view.showChildView('list_region', new ListView({
|
||||
collection: new RedirectionHostModel.Collection(response)
|
||||
}));
|
||||
} else {
|
||||
view.showChildView('list_region', new EmptyView({
|
||||
title: 'There are no Redirection Hosts',
|
||||
subtitle: 'Why don\'t you create one?',
|
||||
link: 'Add Redirection Host',
|
||||
action: function () {
|
||||
Controller.showNginxRedirectionForm();
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
view.showChildView('list_region', new ErrorView({
|
||||
code: err.code,
|
||||
message: err.message,
|
||||
retry: function () {
|
||||
Controller.showNginxRedirection();
|
||||
}
|
||||
}));
|
||||
|
||||
console.error(err);
|
||||
})
|
||||
.then(() => {
|
||||
view.ui.dimmer.removeClass('active');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
32
src/frontend/js/app/nginx/stream/list/item.ejs
Normal file
32
src/frontend/js/app/nginx/stream/list/item.ejs
Normal file
@ -0,0 +1,32 @@
|
||||
<td class="text-center">
|
||||
<div class="avatar d-block" style="background-image: url(<%- avatar || '/images/default-avatar.jpg' %>)">
|
||||
<span class="avatar-status <%- is_disabled ? 'bg-red' : 'bg-green' %>"></span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div><%- name %></div>
|
||||
<div class="small text-muted">
|
||||
Created: <%- formatDbDate(created_on, 'Do MMMM YYYY') %>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div><%- email %></div>
|
||||
</td>
|
||||
<td>
|
||||
<div><%- roles.join(', ') %></div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<div class="item-action dropdown">
|
||||
<a href="#" data-toggle="dropdown" class="icon"><i class="fe fe-more-vertical"></i></a>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a href="#" class="edit-user dropdown-item"><i class="dropdown-icon fe fe-edit"></i> Edit Details</a>
|
||||
<a href="#" class="edit-permissions dropdown-item"><i class="dropdown-icon fe fe-shield"></i> Edit Permissions</a>
|
||||
<a href="#" class="set-password dropdown-item"><i class="dropdown-icon fe fe-lock"></i> Set Password</a>
|
||||
<% if (!isSelf()) { %>
|
||||
<a href="#" class="login dropdown-item"><i class="dropdown-icon fe fe-log-in"></i> Sign in as User</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="#" class="delete-user dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> Delete User</a>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
72
src/frontend/js/app/nginx/stream/list/item.js
Normal file
72
src/frontend/js/app/nginx/stream/list/item.js
Normal file
@ -0,0 +1,72 @@
|
||||
'use strict';
|
||||
|
||||
const Mn = require('backbone.marionette');
|
||||
const Controller = require('../../../controller');
|
||||
const Api = require('../../../api');
|
||||
const Cache = require('../../../cache');
|
||||
const Tokens = require('../../../tokens');
|
||||
const template = require('./item.ejs');
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
template: template,
|
||||
tagName: 'tr',
|
||||
|
||||
ui: {
|
||||
edit: 'a.edit-user',
|
||||
permissions: 'a.edit-permissions',
|
||||
password: 'a.set-password',
|
||||
login: 'a.login',
|
||||
delete: 'a.delete-user'
|
||||
},
|
||||
|
||||
events: {
|
||||
'click @ui.edit': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.showUserForm(this.model);
|
||||
},
|
||||
|
||||
'click @ui.permissions': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.showUserPermissions(this.model);
|
||||
},
|
||||
|
||||
'click @ui.password': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.showUserPasswordForm(this.model);
|
||||
},
|
||||
|
||||
'click @ui.delete': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.showUserDeleteConfirm(this.model);
|
||||
},
|
||||
|
||||
'click @ui.login': function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (Cache.User.get('id') !== this.model.get('id')) {
|
||||
this.ui.login.prop('disabled', true).addClass('btn-disabled');
|
||||
|
||||
Api.Users.loginAs(this.model.get('id'))
|
||||
.then(res => {
|
||||
Tokens.addToken(res.token, res.user.nickname || res.user.name);
|
||||
window.location = '/';
|
||||
window.location.reload();
|
||||
})
|
||||
.catch(err => {
|
||||
alert(err.message);
|
||||
this.ui.login.prop('disabled', false).removeClass('btn-disabled');
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
templateContext: {
|
||||
isSelf: function () {
|
||||
return Cache.User.get('id') === this.id;
|
||||
}
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
}
|
||||
});
|
10
src/frontend/js/app/nginx/stream/list/main.ejs
Normal file
10
src/frontend/js/app/nginx/stream/list/main.ejs
Normal file
@ -0,0 +1,10 @@
|
||||
<thead>
|
||||
<th width="30"> </th>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Roles</th>
|
||||
<th> </th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- items -->
|
||||
</tbody>
|
29
src/frontend/js/app/nginx/stream/list/main.js
Normal file
29
src/frontend/js/app/nginx/stream/list/main.js
Normal file
@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
const Mn = require('backbone.marionette');
|
||||
const ItemView = require('./item');
|
||||
const template = require('./main.ejs');
|
||||
|
||||
const TableBody = Mn.CollectionView.extend({
|
||||
tagName: 'tbody',
|
||||
childView: ItemView
|
||||
});
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
tagName: 'table',
|
||||
className: 'table table-hover table-outline table-vcenter text-nowrap card-table',
|
||||
template: template,
|
||||
|
||||
regions: {
|
||||
body: {
|
||||
el: 'tbody',
|
||||
replaceElement: true
|
||||
}
|
||||
},
|
||||
|
||||
onRender: function () {
|
||||
this.showChildView('body', new TableBody({
|
||||
collection: this.collection
|
||||
}));
|
||||
}
|
||||
});
|
@ -1 +1,16 @@
|
||||
stream
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Streams</h3>
|
||||
<div class="card-options">
|
||||
<a href="#" class="btn btn-outline-teal btn-sm ml-2 add-item">Add Stream</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>
|
||||
|
@ -1,9 +1,70 @@
|
||||
'use strict';
|
||||
|
||||
const Mn = require('backbone.marionette');
|
||||
const template = require('./main.ejs');
|
||||
const Mn = require('backbone.marionette');
|
||||
const StreamModel = require('../../../models/stream');
|
||||
const Api = require('../../api');
|
||||
const Controller = require('../../controller');
|
||||
const ListView = require('./list/main');
|
||||
const ErrorView = require('../../error/main');
|
||||
const template = require('./main.ejs');
|
||||
const EmptyView = require('../../empty/main');
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
id: 'nginx-streams',
|
||||
template: template,
|
||||
id: 'nginx-stream'
|
||||
|
||||
ui: {
|
||||
list_region: '.list-region',
|
||||
add: '.add-item',
|
||||
dimmer: '.dimmer'
|
||||
},
|
||||
|
||||
regions: {
|
||||
list_region: '@ui.list_region'
|
||||
},
|
||||
|
||||
events: {
|
||||
'click @ui.add': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.showNginxStreamForm();
|
||||
}
|
||||
},
|
||||
|
||||
onRender: function () {
|
||||
let view = this;
|
||||
|
||||
Api.Nginx.RedirectionHosts.getAll()
|
||||
.then(response => {
|
||||
if (!view.isDestroyed()) {
|
||||
if (response && response.length) {
|
||||
view.showChildView('list_region', new ListView({
|
||||
collection: new StreamModel.Collection(response)
|
||||
}));
|
||||
} else {
|
||||
view.showChildView('list_region', new EmptyView({
|
||||
title: 'There are no Streams',
|
||||
subtitle: 'Why don\'t you create one?',
|
||||
link: 'Add Stream',
|
||||
action: function () {
|
||||
Controller.showNginxStreamForm();
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
view.showChildView('list_region', new ErrorView({
|
||||
code: err.code,
|
||||
message: err.message,
|
||||
retry: function () {
|
||||
Controller.showNginxStream();
|
||||
}
|
||||
}));
|
||||
|
||||
console.error(err);
|
||||
})
|
||||
.then(() => {
|
||||
view.ui.dimmer.removeClass('active');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -10,7 +10,7 @@ module.exports = Mn.AppRouter.extend({
|
||||
logout: 'logout',
|
||||
'nginx/proxy': 'showNginxProxy',
|
||||
'nginx/redirection': 'showNginxRedirection',
|
||||
'nginx/404': 'showNginx404',
|
||||
'nginx/404': 'showNginxDead',
|
||||
'nginx/stream': 'showNginxStream',
|
||||
'nginx/access': 'showNginxAccess',
|
||||
'*default': 'showDashboard'
|
||||
|
@ -51,5 +51,9 @@ module.exports = Mn.View.extend({
|
||||
|
||||
return 'Sign out';
|
||||
}
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
this.listenTo(Cache.User, 'change', this.render);
|
||||
}
|
||||
});
|
||||
|
@ -8,15 +8,28 @@
|
||||
<li class="nav-item dropdown">
|
||||
<a href="#" class="nav-link" data-toggle="dropdown"><i class="fe fe-monitor"></i> Hosts</a>
|
||||
<div class="dropdown-menu dropdown-menu-arrow">
|
||||
<% if (canShow('proxy_hosts')) { %>
|
||||
<a href="/nginx/proxy" class="dropdown-item ">Proxy Hosts</a>
|
||||
<% } %>
|
||||
|
||||
<% if (canShow('redirection_hosts')) { %>
|
||||
<a href="/nginx/redirection" class="dropdown-item ">Redirections</a>
|
||||
<% } %>
|
||||
|
||||
<% if (canShow('streams')) { %>
|
||||
<a href="/nginx/stream" class="dropdown-item ">Streams</a>
|
||||
<% } %>
|
||||
|
||||
<% if (canShow('dead_hosts')) { %>
|
||||
<a href="/nginx/404" class="dropdown-item ">404 Hosts</a>
|
||||
<% } %>
|
||||
</div>
|
||||
</li>
|
||||
<% if (canShow('access_lists')) { %>
|
||||
<li class="nav-item">
|
||||
<a href="/nginx/access" class="nav-link"><i class="fe fe-lock"></i> Access Lists</a>
|
||||
</li>
|
||||
<% } %>
|
||||
<% if (showUsers()) { %>
|
||||
<li class="nav-item">
|
||||
<a href="/users" class="nav-link"><i class="fe fe-users"></i> Users</a>
|
||||
|
@ -17,14 +17,25 @@ module.exports = Mn.View.extend({
|
||||
|
||||
events: {
|
||||
'click @ui.links': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.navigate($(e.currentTarget).attr('href'), true);
|
||||
let href = $(e.currentTarget).attr('href');
|
||||
if (href !== '#') {
|
||||
e.preventDefault();
|
||||
Controller.navigate(href, true);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
templateContext: {
|
||||
showUsers: function () {
|
||||
return Cache.User.isAdmin();
|
||||
},
|
||||
|
||||
canShow: function (perm) {
|
||||
return Cache.User.isAdmin() || Cache.User.canView(perm);
|
||||
}
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
this.listenTo(Cache.User, 'change', this.render);
|
||||
}
|
||||
});
|
||||
|
@ -8,12 +8,12 @@ const ListView = require('./list/main');
|
||||
const template = require('./main.ejs');
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
id: 'users',
|
||||
template: template,
|
||||
id: 'users',
|
||||
template: template,
|
||||
|
||||
ui: {
|
||||
list_region: '.list-region',
|
||||
add_user: '.add-user',
|
||||
add: '.add-item',
|
||||
dimmer: '.dimmer'
|
||||
},
|
||||
|
||||
@ -22,7 +22,7 @@ module.exports = Mn.View.extend({
|
||||
},
|
||||
|
||||
events: {
|
||||
'click @ui.add_user': function (e) {
|
||||
'click @ui.add': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.showUserForm(new UserModel.Model());
|
||||
}
|
||||
@ -37,15 +37,19 @@ module.exports = Mn.View.extend({
|
||||
view.showChildView('list_region', new ListView({
|
||||
collection: new UserModel.Collection(response)
|
||||
}));
|
||||
|
||||
// Remove loader
|
||||
view.ui.dimmer.removeClass('active');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
//Controller.showError(err, 'Could not fetch Users');
|
||||
//view.trigger('loaded');
|
||||
view.showChildView('list_region', new ErrorView({
|
||||
code: err.code,
|
||||
message: err.message,
|
||||
retry: function () { Controller.showUsers(); }
|
||||
}));
|
||||
|
||||
console.error(err);
|
||||
})
|
||||
.then(() => {
|
||||
view.ui.dimmer.removeClass('active');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
26
src/frontend/js/models/dead-host.js
Normal file
26
src/frontend/js/models/dead-host.js
Normal file
@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
const Backbone = require('backbone');
|
||||
|
||||
const model = Backbone.Model.extend({
|
||||
idAttribute: 'id',
|
||||
|
||||
defaults: function () {
|
||||
return {
|
||||
created_on: null,
|
||||
modified_on: null,
|
||||
owner: null,
|
||||
domain_name: '',
|
||||
ssl_enabled: false,
|
||||
ssl_provider: false,
|
||||
meta: []
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
Model: model,
|
||||
Collection: Backbone.Collection.extend({
|
||||
model: model
|
||||
})
|
||||
};
|
32
src/frontend/js/models/proxy-host.js
Normal file
32
src/frontend/js/models/proxy-host.js
Normal file
@ -0,0 +1,32 @@
|
||||
'use strict';
|
||||
|
||||
const Backbone = require('backbone');
|
||||
|
||||
const model = Backbone.Model.extend({
|
||||
idAttribute: 'id',
|
||||
|
||||
defaults: function () {
|
||||
return {
|
||||
created_on: null,
|
||||
modified_on: null,
|
||||
owner: null,
|
||||
domain_name: '',
|
||||
forward_ip: '',
|
||||
forward_port: 0,
|
||||
access_list_id: 0,
|
||||
ssl_enabled: false,
|
||||
ssl_provider: false,
|
||||
ssl_forced: false,
|
||||
caching_enabled: false,
|
||||
block_exploits: false,
|
||||
meta: []
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
Model: model,
|
||||
Collection: Backbone.Collection.extend({
|
||||
model: model
|
||||
})
|
||||
};
|
29
src/frontend/js/models/redirection-host.js
Normal file
29
src/frontend/js/models/redirection-host.js
Normal file
@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
const Backbone = require('backbone');
|
||||
|
||||
const model = Backbone.Model.extend({
|
||||
idAttribute: 'id',
|
||||
|
||||
defaults: function () {
|
||||
return {
|
||||
created_on: null,
|
||||
modified_on: null,
|
||||
owner: null,
|
||||
domain_name: '',
|
||||
forward_domain_name: '',
|
||||
preserve_path: false,
|
||||
ssl_enabled: false,
|
||||
ssl_provider: false,
|
||||
block_exploits: false,
|
||||
meta: []
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
Model: model,
|
||||
Collection: Backbone.Collection.extend({
|
||||
model: model
|
||||
})
|
||||
};
|
28
src/frontend/js/models/stream.js
Normal file
28
src/frontend/js/models/stream.js
Normal file
@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
const Backbone = require('backbone');
|
||||
|
||||
const model = Backbone.Model.extend({
|
||||
idAttribute: 'id',
|
||||
|
||||
defaults: function () {
|
||||
return {
|
||||
created_on: null,
|
||||
modified_on: null,
|
||||
owner: null,
|
||||
incoming_port: 0,
|
||||
forward_ip: '',
|
||||
forwarding_port: 0,
|
||||
tcp_forwarding: true,
|
||||
udp_forwarding: false,
|
||||
meta: []
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
Model: model,
|
||||
Collection: Backbone.Collection.extend({
|
||||
model: model
|
||||
})
|
||||
};
|
@ -17,8 +17,33 @@ const model = Backbone.Model.extend({
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
isAdmin: function () {
|
||||
return _.indexOf(this.get('roles'), 'admin') !== -1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the perm has either `view` or `manage` value
|
||||
*
|
||||
* @param {String} item
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
canView: function (item) {
|
||||
let permissions = this.get('permissions');
|
||||
return permissions !== null && typeof permissions[item] !== 'undefined' && ['view', 'manage'].indexOf(permissions[item]) !== -1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the perm has `manage` value
|
||||
*
|
||||
* @param {String} item
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
canManage: function (item) {
|
||||
let permissions = this.get('permissions');
|
||||
return permissions !== null && typeof permissions[item] !== 'undefined' && permissions[item] === 'manage';
|
||||
}
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user