mirror of
https://github.com/jc21/nginx-proxy-manager.git
synced 2024-08-30 18:22:48 +00:00
Access polish, import v1 stsarted
This commit is contained in:
parent
7d9e716c7c
commit
8d925deeb0
2
.gitignore
vendored
2
.gitignore
vendored
@ -10,3 +10,5 @@ data/*
|
|||||||
yarn-error.log
|
yarn-error.log
|
||||||
yarn.lock
|
yarn.lock
|
||||||
tmp
|
tmp
|
||||||
|
certbot.log
|
||||||
|
|
||||||
|
14
README.md
14
README.md
@ -6,11 +6,15 @@
|
|||||||
![Stars](https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge)
|
![Stars](https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge)
|
||||||
![Pulls](https://img.shields.io/docker/pulls/jc21/nginx-proxy-manager.svg?style=for-the-badge)
|
![Pulls](https://img.shields.io/docker/pulls/jc21/nginx-proxy-manager.svg?style=for-the-badge)
|
||||||
|
|
||||||
**NOTE: Version 2 is a work in progress. Not all of the areas are complete and is definitely not ready for production use.**
|
|
||||||
|
|
||||||
This project comes as a pre-built docker image that enables you to easily forward to your websites
|
This project comes as a pre-built docker image that enables you to easily forward to your websites
|
||||||
running at home or otherwise, including free SSL, without having to know too much about Nginx or Letsencrypt.
|
running at home or otherwise, including free SSL, without having to know too much about Nginx or Letsencrypt.
|
||||||
|
|
||||||
|
----------
|
||||||
|
|
||||||
|
**WARNING: Version 2 a complete rewrite!** If you are using the `latest` docker tag and update to version 2
|
||||||
|
without preparation, horrible things might happen. Refer to the [Migrating Documentation](doc/MIGRATING.md).
|
||||||
|
|
||||||
|
----------
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@ -18,13 +22,9 @@ running at home or otherwise, including free SSL, without having to know too muc
|
|||||||
- Easily create forwarding domains, redirections, streams and 404 hosts without knowing anything about Nginx
|
- Easily create forwarding domains, redirections, streams and 404 hosts without knowing anything about Nginx
|
||||||
- Free SSL using Let's Encrypt or provide your own custom SSL certificates
|
- Free SSL using Let's Encrypt or provide your own custom SSL certificates
|
||||||
- Access Lists and basic HTTP Authentication for your hosts
|
- Access Lists and basic HTTP Authentication for your hosts
|
||||||
- Advanced Nginx configuration available for super users
|
- -Advanced Nginx configuration available for super users- TODO
|
||||||
- User management, permissions and audit log
|
- User management, permissions and audit log
|
||||||
|
|
||||||
#### Future Features
|
|
||||||
|
|
||||||
- Live log tail
|
|
||||||
|
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
|
8
TODO.md
8
TODO.md
@ -2,16 +2,16 @@
|
|||||||
|
|
||||||
In order of importance, somewhat..
|
In order of importance, somewhat..
|
||||||
|
|
||||||
- Manual certificate writing to disk and usage in nginx configs - MIGRATING.md
|
- Custom ssl certificate saving to disk and usage in nginx configs
|
||||||
- Access Lists UI and Nginx usage
|
|
||||||
- Make modal dialogs unclosable in overlay
|
|
||||||
- Dashboard stats are caching instead of querying
|
- Dashboard stats are caching instead of querying
|
||||||
- Create a nice way of importing from v1 let's encrypt certs and config data
|
- Create a nice way of importing from v1 let's encrypt certs and config data
|
||||||
- UI Log tail
|
- UI Log tail
|
||||||
|
- Custom Nginx Config Editor
|
||||||
|
|
||||||
Testing
|
Testing:
|
||||||
|
|
||||||
- Access Levels
|
- Access Levels
|
||||||
|
- Adding a proxy host without access to read certs or access lists
|
||||||
- Visibility
|
- Visibility
|
||||||
- Forwarding
|
- Forwarding
|
||||||
- Cert renewals
|
- Cert renewals
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
There's a few ways to configure this app depending on:
|
There's a few ways to configure this app depending on:
|
||||||
|
|
||||||
- Whether you use `docker-compose` or vanilla docker
|
- Whether you use `docker-compose` or vanilla docker
|
||||||
- Which Database you want to use (mysql or postgres)
|
|
||||||
- Which architecture you're running it on (raspberry pi also supported)
|
- Which architecture you're running it on (raspberry pi also supported)
|
||||||
|
|
||||||
### Configuration File
|
### Configuration File
|
||||||
@ -12,9 +11,9 @@ There's a few ways to configure this app depending on:
|
|||||||
|
|
||||||
Don't worry, this is easy to do.
|
Don't worry, this is easy to do.
|
||||||
|
|
||||||
The app requires a configuration file to let it know what database you're using and where it is.
|
The app requires a configuration file to let it know what database you're using.
|
||||||
|
|
||||||
Here's an example configuration for `mysql`:
|
Here's an example configuration for `mysql` (or mariadb):
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@ -29,22 +28,6 @@ Here's an example configuration for `mysql`:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
and here's one for `postgres`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"database": {
|
|
||||||
"engine": "pg",
|
|
||||||
"version": "7.2",
|
|
||||||
"host": "127.0.0.1",
|
|
||||||
"name": "nginxproxymanager",
|
|
||||||
"user": "nginxproxymanager",
|
|
||||||
"password": "password123",
|
|
||||||
"port": 5432
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Once you've created your configuration file it's easy to mount it in the docker container, examples below.
|
Once you've created your configuration file it's easy to mount it in the docker container, examples below.
|
||||||
|
|
||||||
**Note:** After the first run of the application, the config file will be altered to include generated encryption keys unique to your installation. These keys
|
**Note:** After the first run of the application, the config file will be altered to include generated encryption keys unique to your installation. These keys
|
||||||
@ -138,3 +121,24 @@ docker run -d \
|
|||||||
-v /path/to/letsencrypt:/etc/letsencrypt \
|
-v /path/to/letsencrypt:/etc/letsencrypt \
|
||||||
jc21/nginx-proxy-manager:2-armhf
|
jc21/nginx-proxy-manager:2-armhf
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Initial Run
|
||||||
|
|
||||||
|
After the app is running for the first time, the following will happen:
|
||||||
|
|
||||||
|
- The database will initialize with table structures
|
||||||
|
- GPG keys will be generated and saved in the configuration file
|
||||||
|
- A default admin user will be created
|
||||||
|
|
||||||
|
This process can take a couple of minutes depending on your machine.
|
||||||
|
|
||||||
|
|
||||||
|
### Default Administrator User
|
||||||
|
|
||||||
|
```
|
||||||
|
Email: admin@example.com
|
||||||
|
Password: changeme
|
||||||
|
```
|
||||||
|
|
||||||
|
Immediately after logging in with this default user you will be asked to modify your details and change your password.
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
"body-parser": "^1.18.3",
|
"body-parser": "^1.18.3",
|
||||||
"compression": "^1.7.2",
|
"compression": "^1.7.2",
|
||||||
"config": "^2.0.1",
|
"config": "^2.0.1",
|
||||||
|
"diskdb": "^0.1.17",
|
||||||
"ejs": "^2.6.1",
|
"ejs": "^2.6.1",
|
||||||
"express": "^4.16.3",
|
"express": "^4.16.3",
|
||||||
"express-fileupload": "^0.4.0",
|
"express-fileupload": "^0.4.0",
|
||||||
@ -56,7 +57,6 @@
|
|||||||
"node-rsa": "^1.0.0",
|
"node-rsa": "^1.0.0",
|
||||||
"objection": "^1.1.10",
|
"objection": "^1.1.10",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"pg": "^7.4.3",
|
|
||||||
"restler": "^3.4.0",
|
"restler": "^3.4.0",
|
||||||
"signale": "^1.2.1",
|
"signale": "^1.2.1",
|
||||||
"temp-write": "^3.4.0",
|
"temp-write": "^3.4.0",
|
||||||
|
@ -4,4 +4,3 @@ mkdir -p /data/letsencrypt-acme-challenge
|
|||||||
|
|
||||||
cd /app
|
cd /app
|
||||||
node --abort_on_uncaught_exception --max_old_space_size=250 /app/src/backend/index.js
|
node --abort_on_uncaught_exception --max_old_space_size=250 /app/src/backend/index.js
|
||||||
|
|
||||||
|
@ -2,9 +2,16 @@
|
|||||||
|
|
||||||
mkdir -p /tmp/nginx/body \
|
mkdir -p /tmp/nginx/body \
|
||||||
/var/log/nginx \
|
/var/log/nginx \
|
||||||
/data/{nginx,logs,access} \
|
/data/nginx \
|
||||||
/data/nginx/{proxy_host,redirection_host,stream,dead_host,temp} \
|
/data/logs \
|
||||||
/var/lib/nginx/cache/{public,private}
|
/data/access \
|
||||||
|
/data/nginx/proxy_host \
|
||||||
|
/data/nginx/redirection_host \
|
||||||
|
/data/nginx/stream \
|
||||||
|
/data/nginx/dead_host \
|
||||||
|
/data/nginx/temp \
|
||||||
|
/var/lib/nginx/cache/public \
|
||||||
|
/var/lib/nginx/cache/private
|
||||||
|
|
||||||
touch /var/log/nginx/error.log && chmod 777 /var/log/nginx/error.log
|
touch /var/log/nginx/error.log && chmod 777 /var/log/nginx/error.log
|
||||||
chown root /tmp/nginx
|
chown root /tmp/nginx
|
||||||
|
68
src/backend/importer.js
Normal file
68
src/backend/importer.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const logger = require('./logger').import;
|
||||||
|
const utils = require('./lib/utils');
|
||||||
|
|
||||||
|
module.exports = function () {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (fs.existsSync('/config') && !fs.existsSync('/config/v2-imported')) {
|
||||||
|
|
||||||
|
logger.info('Beginning import from V1 ...');
|
||||||
|
|
||||||
|
// Setup
|
||||||
|
const batchflow = require('batchflow');
|
||||||
|
const db = require('diskdb');
|
||||||
|
module.exports = db.connect('/config', ['hosts', 'access']);
|
||||||
|
|
||||||
|
// Create a fake access object
|
||||||
|
const Access = require('./lib/access');
|
||||||
|
let access = new Access(null);
|
||||||
|
resolve(access.load(true)
|
||||||
|
.then(access => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Import access lists first
|
||||||
|
let lists = db.access.find();
|
||||||
|
lists.map(list => {
|
||||||
|
logger.warn('List:', list);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
let hosts = db.hosts.find();
|
||||||
|
hosts.map(host => {
|
||||||
|
logger.warn('Host:', host);
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Looks like we need to import from version 1
|
||||||
|
// There are numerous parts to this import:
|
||||||
|
//
|
||||||
|
// 1. The letsencrypt certificates, the need to be added to the database and files renamed
|
||||||
|
// 2. The access lists from the previous datastore
|
||||||
|
// 3. The Hosts from the previous datastore
|
||||||
|
|
||||||
|
// get all hosts:
|
||||||
|
// resolve(db.hosts.find());
|
||||||
|
|
||||||
|
// get specific host:
|
||||||
|
// existing_host = db.hosts.findOne({incoming_port: payload.incoming_port});
|
||||||
|
|
||||||
|
// remove host:
|
||||||
|
// db.hosts.remove({hostname: payload.hostname});
|
||||||
|
|
||||||
|
// get all access:
|
||||||
|
// resolve(db.access.find());
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@ -7,14 +7,14 @@ const logger = require('./logger').global;
|
|||||||
function appStart () {
|
function appStart () {
|
||||||
const migrate = require('./migrate');
|
const migrate = require('./migrate');
|
||||||
const setup = require('./setup');
|
const setup = require('./setup');
|
||||||
|
const importer = require('./importer');
|
||||||
const app = require('./app');
|
const app = require('./app');
|
||||||
const apiValidator = require('./lib/validator/api');
|
const apiValidator = require('./lib/validator/api');
|
||||||
const internalCertificate = require('./internal/certificate');
|
const internalCertificate = require('./internal/certificate');
|
||||||
|
|
||||||
return migrate.latest()
|
return migrate.latest()
|
||||||
.then(() => {
|
.then(setup)
|
||||||
return setup();
|
.then(importer)
|
||||||
})
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return apiValidator.loadSchemas;
|
return apiValidator.loadSchemas;
|
||||||
})
|
})
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
const fs = require('fs');
|
||||||
|
const batchflow = require('batchflow');
|
||||||
|
const logger = require('../logger').access;
|
||||||
const error = require('../lib/error');
|
const error = require('../lib/error');
|
||||||
const accessListModel = require('../models/access_list');
|
const accessListModel = require('../models/access_list');
|
||||||
const accessListAuthModel = require('../models/access_list_auth');
|
const accessListAuthModel = require('../models/access_list_auth');
|
||||||
|
const proxyHostModel = require('../models/proxy_host');
|
||||||
const internalAuditLog = require('./audit-log');
|
const internalAuditLog = require('./audit-log');
|
||||||
|
const internalNginx = require('./nginx');
|
||||||
|
const utils = require('../lib/utils');
|
||||||
|
|
||||||
function omissions () {
|
function omissions () {
|
||||||
return ['is_deleted'];
|
return ['is_deleted'];
|
||||||
@ -29,6 +35,8 @@ const internalAccessList = {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(row => {
|
.then(row => {
|
||||||
|
data.id = row.id;
|
||||||
|
|
||||||
// Now add the items
|
// Now add the items
|
||||||
let promises = [];
|
let promises = [];
|
||||||
data.items.map(function (item) {
|
data.items.map(function (item) {
|
||||||
@ -44,26 +52,34 @@ const internalAccessList = {
|
|||||||
|
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
})
|
})
|
||||||
.then(row => {
|
.then(() => {
|
||||||
// re-fetch with cert
|
// re-fetch with expansions
|
||||||
return internalAccessList.get(access, {
|
return internalAccessList.get(access, {
|
||||||
id: row.id,
|
id: data.id,
|
||||||
expand: ['owner', 'items']
|
expand: ['owner', 'items']
|
||||||
});
|
}, true /* <- skip masking */);
|
||||||
})
|
})
|
||||||
.then(row => {
|
.then(row => {
|
||||||
// Audit log
|
// Audit log
|
||||||
data.meta = _.assign({}, data.meta || {}, row.meta);
|
data.meta = _.assign({}, data.meta || {}, row.meta);
|
||||||
|
|
||||||
|
return internalAccessList.build(row)
|
||||||
|
.then(() => {
|
||||||
|
if (row.proxy_host_count) {
|
||||||
|
return internalNginx.reload();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
// Add to audit log
|
// Add to audit log
|
||||||
return internalAuditLog.add(access, {
|
return internalAuditLog.add(access, {
|
||||||
action: 'created',
|
action: 'created',
|
||||||
object_type: 'access-list',
|
object_type: 'access-list',
|
||||||
object_id: row.id,
|
object_id: row.id,
|
||||||
meta: data
|
meta: internalAccessList.maskItems(data)
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return row;
|
return internalAccessList.maskItems(row);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -72,15 +88,99 @@ const internalAccessList = {
|
|||||||
* @param {Access} access
|
* @param {Access} access
|
||||||
* @param {Object} data
|
* @param {Object} data
|
||||||
* @param {Integer} data.id
|
* @param {Integer} data.id
|
||||||
* @param {String} [data.email]
|
|
||||||
* @param {String} [data.name]
|
* @param {String} [data.name]
|
||||||
|
* @param {String} [data.items]
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
update: (access, data) => {
|
update: (access, data) => {
|
||||||
return access.can('access_lists:update', data.id)
|
return access.can('access_lists:update', data.id)
|
||||||
.then(access_data => {
|
.then(access_data => {
|
||||||
// TODO
|
return internalAccessList.get(access, {id: data.id});
|
||||||
return {};
|
})
|
||||||
|
.then(row => {
|
||||||
|
if (row.id !== data.id) {
|
||||||
|
// Sanity check that something crazy hasn't happened
|
||||||
|
throw new error.InternalValidationError('Access List could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// patch name if specified
|
||||||
|
if (typeof data.name !== 'undefined' && data.name) {
|
||||||
|
return accessListModel
|
||||||
|
.query()
|
||||||
|
.where({id: data.id})
|
||||||
|
.patch({
|
||||||
|
name: data.name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// Check for items and add/update/remove them
|
||||||
|
if (typeof data.items !== 'undefined' && data.items) {
|
||||||
|
let promises = [];
|
||||||
|
let items_to_keep = [];
|
||||||
|
|
||||||
|
data.items.map(function (item) {
|
||||||
|
if (item.password) {
|
||||||
|
promises.push(accessListAuthModel
|
||||||
|
.query()
|
||||||
|
.insert({
|
||||||
|
access_list_id: data.id,
|
||||||
|
username: item.username,
|
||||||
|
password: item.password
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// This was supplied with an empty password, which means keep it but don't change the password
|
||||||
|
items_to_keep.push(item.username);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let query = accessListAuthModel
|
||||||
|
.query()
|
||||||
|
.delete()
|
||||||
|
.where('access_list_id', data.id);
|
||||||
|
|
||||||
|
if (items_to_keep.length) {
|
||||||
|
query.andWhere('username', 'NOT IN', items_to_keep);
|
||||||
|
}
|
||||||
|
|
||||||
|
return query
|
||||||
|
.then(() => {
|
||||||
|
// Add new items
|
||||||
|
if (promises.length) {
|
||||||
|
return Promise.all(promises);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// Add to audit log
|
||||||
|
return internalAuditLog.add(access, {
|
||||||
|
action: 'updated',
|
||||||
|
object_type: 'access-list',
|
||||||
|
object_id: data.id,
|
||||||
|
meta: internalAccessList.maskItems(data)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// re-fetch with expansions
|
||||||
|
return internalAccessList.get(access, {
|
||||||
|
id: data.id,
|
||||||
|
expand: ['owner', 'items']
|
||||||
|
}, true /* <- skip masking */);
|
||||||
|
})
|
||||||
|
.then(row => {
|
||||||
|
return internalAccessList.build(row)
|
||||||
|
.then(() => {
|
||||||
|
if (row.proxy_host_count) {
|
||||||
|
return internalNginx.reload();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return internalAccessList.maskItems(row);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -90,9 +190,10 @@ const internalAccessList = {
|
|||||||
* @param {Integer} data.id
|
* @param {Integer} data.id
|
||||||
* @param {Array} [data.expand]
|
* @param {Array} [data.expand]
|
||||||
* @param {Array} [data.omit]
|
* @param {Array} [data.omit]
|
||||||
|
* @param {Boolean} [skip_masking]
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
get: (access, data) => {
|
get: (access, data, skip_masking) => {
|
||||||
if (typeof data === 'undefined') {
|
if (typeof data === 'undefined') {
|
||||||
data = {};
|
data = {};
|
||||||
}
|
}
|
||||||
@ -105,9 +206,12 @@ const internalAccessList = {
|
|||||||
.then(access_data => {
|
.then(access_data => {
|
||||||
let query = accessListModel
|
let query = accessListModel
|
||||||
.query()
|
.query()
|
||||||
.where('is_deleted', 0)
|
.select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count'))
|
||||||
.andWhere('id', data.id)
|
.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0')
|
||||||
.allowEager('[owner,items]')
|
.where('access_list.is_deleted', 0)
|
||||||
|
.andWhere('access_list.id', data.id)
|
||||||
|
.allowEager('[owner,items,proxy_hosts]')
|
||||||
|
.omit(['access_list.is_deleted'])
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
if (access_data.permission_visibility !== 'all') {
|
if (access_data.permission_visibility !== 'all') {
|
||||||
@ -127,7 +231,7 @@ const internalAccessList = {
|
|||||||
})
|
})
|
||||||
.then(row => {
|
.then(row => {
|
||||||
if (row) {
|
if (row) {
|
||||||
if (typeof row.items !== 'undefined' && row.items) {
|
if (!skip_masking && typeof row.items !== 'undefined' && row.items) {
|
||||||
row = internalAccessList.maskItems(row);
|
row = internalAccessList.maskItems(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,19 +252,66 @@ const internalAccessList = {
|
|||||||
delete: (access, data) => {
|
delete: (access, data) => {
|
||||||
return access.can('access_lists:delete', data.id)
|
return access.can('access_lists:delete', data.id)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return internalAccessList.get(access, {id: data.id});
|
return internalAccessList.get(access, {id: data.id, expand: ['proxy_hosts', 'items']});
|
||||||
})
|
})
|
||||||
.then(row => {
|
.then(row => {
|
||||||
if (!row) {
|
if (!row) {
|
||||||
throw new error.ItemNotFoundError(data.id);
|
throw new error.ItemNotFoundError(data.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 1. update row to be deleted
|
||||||
|
// 2. update any proxy hosts that were using it (ignoring permissions)
|
||||||
|
// 3. reconfigure those hosts
|
||||||
|
// 4. audit log
|
||||||
|
|
||||||
|
// 1. update row to be deleted
|
||||||
return accessListModel
|
return accessListModel
|
||||||
.query()
|
.query()
|
||||||
.where('id', row.id)
|
.where('id', row.id)
|
||||||
.patch({
|
.patch({
|
||||||
is_deleted: 1
|
is_deleted: 1
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// 2. update any proxy hosts that were using it (ignoring permissions)
|
||||||
|
if (row.proxy_hosts) {
|
||||||
|
return proxyHostModel
|
||||||
|
.query()
|
||||||
|
.where('access_list_id', '=', row.id)
|
||||||
|
.patch({access_list_id: 0})
|
||||||
|
.then(() => {
|
||||||
|
// 3. reconfigure those hosts, then reload nginx
|
||||||
|
|
||||||
|
// set the access_list_id to zero for these items
|
||||||
|
row.proxy_hosts.map(function (val, idx) {
|
||||||
|
row.proxy_hosts[idx].access_list_id = 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return internalNginx.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// delete the htpasswd file
|
||||||
|
let htpasswd_file = internalAccessList.getFilename(row);
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(htpasswd_file);
|
||||||
|
} catch (err) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// 4. audit log
|
||||||
|
return internalAuditLog.add(access, {
|
||||||
|
action: 'deleted',
|
||||||
|
object_type: 'access-list',
|
||||||
|
object_id: row.id,
|
||||||
|
meta: _.omit(internalAccessList.maskItems(row), ['is_deleted', 'proxy_hosts'])
|
||||||
|
});
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return true;
|
return true;
|
||||||
@ -180,9 +331,8 @@ const internalAccessList = {
|
|||||||
.then(access_data => {
|
.then(access_data => {
|
||||||
let query = accessListModel
|
let query = accessListModel
|
||||||
.query()
|
.query()
|
||||||
.select('access_list.*', accessListModel.raw('COUNT(proxy_hosts.id) as proxy_host_count'), accessListModel.raw('COUNT(items.id) as item_count'))
|
.select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count'))
|
||||||
.leftJoinRelation('proxy_hosts')
|
.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0')
|
||||||
.leftJoinRelation('items')
|
|
||||||
.where('access_list.is_deleted', 0)
|
.where('access_list.is_deleted', 0)
|
||||||
.groupBy('access_list.id')
|
.groupBy('access_list.id')
|
||||||
.omit(['access_list.is_deleted'])
|
.omit(['access_list.is_deleted'])
|
||||||
@ -249,12 +399,89 @@ const internalAccessList = {
|
|||||||
maskItems: list => {
|
maskItems: list => {
|
||||||
if (list && typeof list.items !== 'undefined') {
|
if (list && typeof list.items !== 'undefined') {
|
||||||
list.items.map(function (val, idx) {
|
list.items.map(function (val, idx) {
|
||||||
list.items[idx].hint = val.password.charAt(0) + ('*').repeat(val.password.length - 1);
|
let repeat_for = 8;
|
||||||
|
let first_char = '*';
|
||||||
|
|
||||||
|
if (typeof val.password !== 'undefined' && val.password) {
|
||||||
|
repeat_for = val.password.length - 1;
|
||||||
|
first_char = val.password.charAt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
list.items[idx].hint = first_char + ('*').repeat(repeat_for);
|
||||||
list.items[idx].password = '';
|
list.items[idx].password = '';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} list
|
||||||
|
* @param {Integer} list.id
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
getFilename: list => {
|
||||||
|
return '/data/access/' + list.id;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} list
|
||||||
|
* @param {Integer} list.id
|
||||||
|
* @param {String} list.name
|
||||||
|
* @param {Array} list.items
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
build: list => {
|
||||||
|
logger.info('Building Access file #' + list.id + ' for: ' + list.name);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let htpasswd_file = internalAccessList.getFilename(list);
|
||||||
|
|
||||||
|
// 1. remove any existing access file
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(htpasswd_file);
|
||||||
|
} catch (err) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. create empty access file
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(htpasswd_file, '', {encoding: 'utf8'});
|
||||||
|
resolve(htpasswd_file);
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(htpasswd_file => {
|
||||||
|
// 3. generate password for each user
|
||||||
|
if (list.items.length) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
batchflow(list.items).sequential()
|
||||||
|
.each((i, item, next) => {
|
||||||
|
if (typeof item.password !== 'undefined' && item.password.length) {
|
||||||
|
logger.info('Adding: ' + item.username);
|
||||||
|
|
||||||
|
utils.exec('/usr/bin/htpasswd -b "' + htpasswd_file + '" "' + item.username + '" "' + item.password + '"')
|
||||||
|
.then((/*result*/) => {
|
||||||
|
next();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
logger.error(err);
|
||||||
|
next(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.error(err => {
|
||||||
|
logger.error(err);
|
||||||
|
reject(err);
|
||||||
|
})
|
||||||
|
.end(results => {
|
||||||
|
logger.success('Built Access file #' + list.id + ' for: ' + list.name);
|
||||||
|
resolve(results);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ const internalProxyHost = {
|
|||||||
// re-fetch with cert
|
// re-fetch with cert
|
||||||
return internalProxyHost.get(access, {
|
return internalProxyHost.get(access, {
|
||||||
id: row.id,
|
id: row.id,
|
||||||
expand: ['certificate', 'owner']
|
expand: ['certificate', 'owner', 'access_list']
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(row => {
|
.then(row => {
|
||||||
@ -185,7 +185,7 @@ const internalProxyHost = {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
return internalProxyHost.get(access, {
|
return internalProxyHost.get(access, {
|
||||||
id: data.id,
|
id: data.id,
|
||||||
expand: ['owner', 'certificate']
|
expand: ['owner', 'certificate', 'access_list']
|
||||||
})
|
})
|
||||||
.then(row => {
|
.then(row => {
|
||||||
// Configure nginx
|
// Configure nginx
|
||||||
|
@ -6,5 +6,6 @@ module.exports = {
|
|||||||
express: new Signale({scope: 'Express '}),
|
express: new Signale({scope: 'Express '}),
|
||||||
access: new Signale({scope: 'Access '}),
|
access: new Signale({scope: 'Access '}),
|
||||||
nginx: new Signale({scope: 'Nginx '}),
|
nginx: new Signale({scope: 'Nginx '}),
|
||||||
ssl: new Signale({scope: 'SSL '})
|
ssl: new Signale({scope: 'SSL '}),
|
||||||
|
import: new Signale({scope: 'Importer'}),
|
||||||
};
|
};
|
||||||
|
@ -56,7 +56,7 @@ class AccessList extends Model {
|
|||||||
to: 'access_list_auth.access_list_id'
|
to: 'access_list_auth.access_list_id'
|
||||||
},
|
},
|
||||||
modify: function (qb) {
|
modify: function (qb) {
|
||||||
qb.omit(['id', 'created_on', 'modified_on']);
|
qb.omit(['id', 'created_on', 'modified_on', 'access_list_id', 'meta']);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
proxy_hosts: {
|
proxy_hosts: {
|
||||||
@ -68,7 +68,7 @@ class AccessList extends Model {
|
|||||||
},
|
},
|
||||||
modify: function (qb) {
|
modify: function (qb) {
|
||||||
qb.where('proxy_host.is_deleted', 0);
|
qb.where('proxy_host.is_deleted', 0);
|
||||||
qb.omit(['id', 'created_on', 'modified_on', 'is_deleted', 'meta']);
|
qb.omit(['is_deleted', 'meta']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -136,7 +136,7 @@ router
|
|||||||
/**
|
/**
|
||||||
* DELETE /api/nginx/access-lists/123
|
* DELETE /api/nginx/access-lists/123
|
||||||
*
|
*
|
||||||
* Update and existing access-list
|
* Delete and existing access-list
|
||||||
*/
|
*/
|
||||||
.delete((req, res, next) => {
|
.delete((req, res, next) => {
|
||||||
internalAccessList.delete(res.locals.access, {id: parseInt(req.params.list_id, 10)})
|
internalAccessList.delete(res.locals.access, {id: parseInt(req.params.list_id, 10)})
|
||||||
|
@ -107,6 +107,49 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "Update",
|
||||||
|
"description": "Updates a existing Access List",
|
||||||
|
"href": "/nginx/access-list/{definitions.identity.example}",
|
||||||
|
"access": "private",
|
||||||
|
"method": "PUT",
|
||||||
|
"rel": "update",
|
||||||
|
"http_header": {
|
||||||
|
"$ref": "../examples.json#/definitions/auth_header"
|
||||||
|
},
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"$ref": "#/definitions/name"
|
||||||
|
},
|
||||||
|
"items": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"username": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targetSchema": {
|
||||||
|
"properties": {
|
||||||
|
"$ref": "#/properties"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "Delete",
|
"title": "Delete",
|
||||||
"description": "Deletes a existing Access List",
|
"description": "Deletes a existing Access List",
|
||||||
|
@ -17,7 +17,7 @@ server {
|
|||||||
{%- if access_list_id > 0 -%}
|
{%- if access_list_id > 0 -%}
|
||||||
# Access List
|
# Access List
|
||||||
auth_basic "Authorization required";
|
auth_basic "Authorization required";
|
||||||
auth_basic_user_file /config/access/{{ access_list_id }};
|
auth_basic_user_file /data/access/{{ access_list_id }};
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
{% include "_forced_ssl.conf" %}
|
{% include "_forced_ssl.conf" %}
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
items = meta.domain_names;
|
items = meta.domain_names;
|
||||||
break;
|
break;
|
||||||
case 'access-list':
|
case 'access-list':
|
||||||
%> <span class="text-teal"><i class="fe fe-lock"></i></span> <%
|
%> <span class="text-teal"><i class="fe fe-shield"></i></span> <%
|
||||||
items.push(meta.name);
|
items.push(meta.name);
|
||||||
break;
|
break;
|
||||||
case 'user':
|
case 'user':
|
||||||
@ -47,7 +47,7 @@
|
|||||||
items.push(meta.name);
|
items.push(meta.name);
|
||||||
break;
|
break;
|
||||||
case 'certificate':
|
case 'certificate':
|
||||||
%> <span class="text-pink"><i class="fe fe-shield"></i></span> <%
|
%> <span class="text-pink"><i class="fe fe-lock"></i></span> <%
|
||||||
if (meta.provider === 'letsencrypt') {
|
if (meta.provider === 'letsencrypt') {
|
||||||
items = meta.domain_names;
|
items = meta.domain_names;
|
||||||
} else {
|
} else {
|
||||||
|
@ -91,23 +91,6 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Error
|
|
||||||
*
|
|
||||||
* @param {Error} err
|
|
||||||
* @param {String} nice_msg
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
showError: function (err, nice_msg) {
|
|
||||||
require(['./main', './error/main'], (App, View) => {
|
|
||||||
App.UI.showAppContent(new View({
|
|
||||||
err: err,
|
|
||||||
nice_msg: nice_msg
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dashboard
|
* Dashboard
|
||||||
*/
|
*/
|
||||||
@ -319,6 +302,19 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access List Delete Confirm
|
||||||
|
*
|
||||||
|
* @param model
|
||||||
|
*/
|
||||||
|
showNginxAccessListDeleteConfirm: function (model) {
|
||||||
|
if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) {
|
||||||
|
require(['./main', './nginx/access/delete'], function (App, View) {
|
||||||
|
App.UI.showModalDialog(new View({model: model}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Nginx Certificates
|
* Nginx Certificates
|
||||||
*/
|
*/
|
||||||
|
@ -8,6 +8,10 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12 col-md-12">
|
<div class="col-sm-12 col-md-12">
|
||||||
<%= i18n('access-lists', 'delete-confirm') %>
|
<%= i18n('access-lists', 'delete-confirm') %>
|
||||||
|
<% if (proxy_host_count) { %>
|
||||||
|
<br><br>
|
||||||
|
<%- i18n('access-lists', 'delete-has-hosts', {count: proxy_host_count}) %>
|
||||||
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<%- i18n('access-lists', 'item-count', {count: item_count}) %>
|
<%- i18n('access-lists', 'item-count', {count: items.length || 0}) %>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<%- i18n('access-lists', 'proxy-host-count', {count: proxy_host_count}) %>
|
<%- i18n('access-lists', 'proxy-host-count', {count: proxy_host_count}) %>
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
<div>
|
<div>
|
||||||
<% if (id === 'new') { %>
|
<% if (id === 'new') { %>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<i class="fe fe-shield text-success"></i> Request a new SSL Certificate
|
<i class="fe fe-lock text-success"></i>
|
||||||
</div>
|
</div>
|
||||||
<span class="description">with Let's Encrypt</span>
|
<span class="description"><%- i18n('all-hosts', 'with-le') %></span>
|
||||||
<% } else if (id > 0) { %>
|
<% } else if (id > 0) { %>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<i class="fe fe-shield text-pink"></i> <%- provider === 'other' ? nice_name : domain_names.join(', ') %>
|
<i class="fe fe-lock text-pink"></i> <%- provider === 'other' ? nice_name : domain_names.join(', ') %>
|
||||||
</div>
|
</div>
|
||||||
<span class="description"><%- i18n('ssl', provider) %> – Expires: <%- formatDbDate(expires_on, 'Do MMMM YYYY, h:mm a') %></span>
|
<span class="description"><%- i18n('ssl', provider) %> – Expires: <%- formatDbDate(expires_on, 'Do MMMM YYYY, h:mm a') %></span>
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<i class="fe fe-shield-off text-danger"></i> None
|
<i class="fe fe-lock-off text-danger"></i> <%- i18n('all-hosts', 'none') %>
|
||||||
</div>
|
</div>
|
||||||
<span class="description">This host will not use HTTPS</span>
|
<span class="description"><%- i18n('all-hosts', 'no-ssl') %></span>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
|
@ -28,10 +28,10 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12 col-md-12">
|
<div class="col-sm-12 col-md-12">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">SSL Certificate</label>
|
<label class="form-label"><%- i18n('all-hosts', 'ssl-certificate') %></label>
|
||||||
<select name="certificate_id" class="form-control custom-select" placeholder="None">
|
<select name="certificate_id" class="form-control custom-select" placeholder="<%- i18n('all-hosts', 'none') %>">
|
||||||
<option selected value="0" data-data="{"id":0}" <%- certificate_id ? '' : 'selected' %>>None</option>
|
<option selected value="0" data-data="{"id":0}" <%- certificate_id ? '' : 'selected' %>><%- i18n('all-hosts', 'none') %></option>
|
||||||
<option selected value="new" data-data="{"id":"new"}">Request a new SSL Certificate</option>
|
<option selected value="new" data-data="{"id":"new"}"><%- i18n('all-hosts', 'new-cert') %></option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
13
src/frontend/js/app/nginx/proxy/access-list-item.ejs
Normal file
13
src/frontend/js/app/nginx/proxy/access-list-item.ejs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<div>
|
||||||
|
<% if (id > 0) { %>
|
||||||
|
<div class="title">
|
||||||
|
<i class="fe fe-shield text-teal"></i> <%- name %>
|
||||||
|
</div>
|
||||||
|
<span class="description"><%- i18n('access-lists', 'item-count', {count: items.length || 0}) %> – Created: <%- formatDbDate(created_on, 'Do MMMM YYYY, h:mm a') %></span>
|
||||||
|
<% } else { %>
|
||||||
|
<div class="title">
|
||||||
|
<i class="fe fe-shield-off text-yellow"></i> <%- i18n('access-lists', 'public') %>
|
||||||
|
</div>
|
||||||
|
<span class="description"><%- i18n('access-lists', 'public-sub') %></span>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
@ -53,8 +53,8 @@
|
|||||||
<div class="col-sm-12 col-md-12">
|
<div class="col-sm-12 col-md-12">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label"><%- i18n('proxy-hosts', 'access-list') %></label>
|
<label class="form-label"><%- i18n('proxy-hosts', 'access-list') %></label>
|
||||||
<select name="access_list_id" class="form-control custom-select">
|
<select name="access_list_id" class="form-control custom-select" placeholder="<%- i18n('access-lists', 'public') %>">
|
||||||
<option value="0" selected="selected"><%- i18n('access-lists', 'public') %></option>
|
<option selected value="0" data-data="{"id":0}" <%- access_list_id ? '' : 'selected' %>><%- i18n('access-lists', 'public') %></option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -66,10 +66,10 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12 col-md-12">
|
<div class="col-sm-12 col-md-12">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">SSL Certificate</label>
|
<label class="form-label"><%- i18n('all-hosts', 'ssl-certificate') %></label>
|
||||||
<select name="certificate_id" class="form-control custom-select" placeholder="None">
|
<select name="certificate_id" class="form-control custom-select" placeholder="<%- i18n('all-hosts', 'none') %>">
|
||||||
<option selected value="0" data-data="{"id":0}" <%- certificate_id ? '' : 'selected' %>>None</option>
|
<option selected value="0" data-data="{"id":0}" <%- certificate_id ? '' : 'selected' %>><%- i18n('all-hosts', 'none') %></option>
|
||||||
<option selected value="new" data-data="{"id":"new"}">Request a new SSL Certificate</option>
|
<option selected value="new" data-data="{"id":"new"}"><%- i18n('all-hosts', 'new-cert') %></option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,6 +5,7 @@ const App = require('../../main');
|
|||||||
const ProxyHostModel = require('../../../models/proxy-host');
|
const ProxyHostModel = require('../../../models/proxy-host');
|
||||||
const template = require('./form.ejs');
|
const template = require('./form.ejs');
|
||||||
const certListItemTemplate = require('../certificates-list-item.ejs');
|
const certListItemTemplate = require('../certificates-list-item.ejs');
|
||||||
|
const accessListItemTemplate = require('./access-list-item.ejs');
|
||||||
const Helpers = require('../../../lib/helpers');
|
const Helpers = require('../../../lib/helpers');
|
||||||
|
|
||||||
require('jquery-serializejson');
|
require('jquery-serializejson');
|
||||||
@ -23,6 +24,7 @@ module.exports = Mn.View.extend({
|
|||||||
cancel: 'button.cancel',
|
cancel: 'button.cancel',
|
||||||
save: 'button.save',
|
save: 'button.save',
|
||||||
certificate_select: 'select[name="certificate_id"]',
|
certificate_select: 'select[name="certificate_id"]',
|
||||||
|
access_list_select: 'select[name="access_list_id"]',
|
||||||
ssl_forced: 'input[name="ssl_forced"]',
|
ssl_forced: 'input[name="ssl_forced"]',
|
||||||
letsencrypt: '.letsencrypt'
|
letsencrypt: '.letsencrypt'
|
||||||
},
|
},
|
||||||
@ -140,6 +142,37 @@ module.exports = Mn.View.extend({
|
|||||||
createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/
|
createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Access Lists
|
||||||
|
this.ui.letsencrypt.hide();
|
||||||
|
this.ui.access_list_select.selectize({
|
||||||
|
valueField: 'id',
|
||||||
|
labelField: 'name',
|
||||||
|
searchField: ['name'],
|
||||||
|
create: false,
|
||||||
|
preload: true,
|
||||||
|
allowEmptyOption: true,
|
||||||
|
render: {
|
||||||
|
option: function (item) {
|
||||||
|
item.i18n = App.i18n;
|
||||||
|
item.formatDbDate = Helpers.formatDbDate;
|
||||||
|
return accessListItemTemplate(item);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
load: function (query, callback) {
|
||||||
|
App.Api.Nginx.AccessLists.getAll(['items'])
|
||||||
|
.then(rows => {
|
||||||
|
callback(rows);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onLoad: function () {
|
||||||
|
view.ui.access_list_select[0].selectize.setValue(view.model.get('access_list_id'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Certificates
|
// Certificates
|
||||||
this.ui.letsencrypt.hide();
|
this.ui.letsencrypt.hide();
|
||||||
this.ui.certificate_select.selectize({
|
this.ui.certificate_select.selectize({
|
||||||
|
@ -52,10 +52,10 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12 col-md-12">
|
<div class="col-sm-12 col-md-12">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">SSL Certificate</label>
|
<label class="form-label"><%- i18n('all-hosts', 'ssl-certificate') %></label>
|
||||||
<select name="certificate_id" class="form-control custom-select" placeholder="None">
|
<select name="certificate_id" class="form-control custom-select" placeholder="<%- i18n('all-hosts', 'none') %>">
|
||||||
<option selected value="0" data-data="{"id":0}" <%- certificate_id ? '' : 'selected' %>>None</option>
|
<option selected value="0" data-data="{"id":0}" <%- certificate_id ? '' : 'selected' %>><%- i18n('all-hosts', 'none') %></option>
|
||||||
<option selected value="new" data-data="{"id":"new"}">Request a new SSL Certificate</option>
|
<option selected value="new" data-data="{"id":"new"}"><%- i18n('all-hosts', 'new-cert') %></option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -68,7 +68,12 @@
|
|||||||
"domain-names": "Domain Names",
|
"domain-names": "Domain Names",
|
||||||
"cert-provider": "Certificate Provider",
|
"cert-provider": "Certificate Provider",
|
||||||
"block-exploits": "Block Common Exploits",
|
"block-exploits": "Block Common Exploits",
|
||||||
"caching-enabled": "Cache Assets"
|
"caching-enabled": "Cache Assets",
|
||||||
|
"ssl-certificate": "SSL Certificate",
|
||||||
|
"none": "None",
|
||||||
|
"new-cert": "Request a new SSL Certificate",
|
||||||
|
"with-le": "with Let's Encrypt",
|
||||||
|
"no-ssl": "This host will not use HTTPS"
|
||||||
},
|
},
|
||||||
"ssl": {
|
"ssl": {
|
||||||
"letsencrypt": "Let's Encrypt",
|
"letsencrypt": "Let's Encrypt",
|
||||||
@ -152,12 +157,14 @@
|
|||||||
"add": "Add Access List",
|
"add": "Add Access List",
|
||||||
"form-title": "{id, select, undefined{New} other{Edit}} Access List",
|
"form-title": "{id, select, undefined{New} other{Edit}} Access List",
|
||||||
"delete": "Delete Access List",
|
"delete": "Delete Access List",
|
||||||
"delete-confirm": "Are you sure you want to delete this access list? Any hosts using it will need to be updated later.",
|
"delete-confirm": "Are you sure you want to delete this access list?",
|
||||||
"public": "Publicly Accessible",
|
"public": "Publicly Accessible",
|
||||||
|
"public-sub": "No Access Restrictions",
|
||||||
"help-title": "What is an Access List?",
|
"help-title": "What is an Access List?",
|
||||||
"help-content": "Access Lists provide authentication for the Proxy Hosts via Basic HTTP Authentication.\nYou can configure multiple usernames and passwords for a single Access List and then apply that to a Proxy Host.\nThis is most useful for forwarded web services that do not have authentication mechanisms built in.",
|
"help-content": "Access Lists provide authentication for the Proxy Hosts via Basic HTTP Authentication.\nYou can configure multiple usernames and passwords for a single Access List and then apply that to a Proxy Host.\nThis is most useful for forwarded web services that do not have authentication mechanisms built in.",
|
||||||
"item-count": "{count} {count, select, 1{User} other{Users}}",
|
"item-count": "{count} {count, select, 1{User} other{Users}}",
|
||||||
"proxy-host-count": "{count} {count, select, 1{Proxy Host} other{Proxy Hosts}}"
|
"proxy-host-count": "{count} {count, select, 1{Proxy Host} other{Proxy Hosts}}",
|
||||||
|
"delete-has-hosts": "This Access List is associated with {count} Proxy Hosts. They will become publicly available upon deletion."
|
||||||
},
|
},
|
||||||
"users": {
|
"users": {
|
||||||
"title": "Users",
|
"title": "Users",
|
||||||
@ -195,6 +202,7 @@
|
|||||||
"stream": "Stream",
|
"stream": "Stream",
|
||||||
"user": "User",
|
"user": "User",
|
||||||
"certificate": "Certificate",
|
"certificate": "Certificate",
|
||||||
|
"access-list": "Access List",
|
||||||
"created": "Created {name}",
|
"created": "Created {name}",
|
||||||
"updated": "Updated {name}",
|
"updated": "Updated {name}",
|
||||||
"deleted": "Deleted {name}",
|
"deleted": "Deleted {name}",
|
||||||
|
Loading…
Reference in New Issue
Block a user