Compare commits

...

17 Commits
2.0.2 ... 2.0.4

Author SHA1 Message Date
c15edf318d Better webpack chunking 2018-11-07 09:31:42 +10:00
a73cbc7116 Whoops, stupid missing semicolon 2018-11-05 13:10:55 +10:00
f9876326c9 Use default ciphers for default ssl host to prevent confusing browser errors 2018-11-05 11:53:46 +10:00
7d5ca84501 Remove cruft from webpack 2018-11-05 10:58:57 +11:00
0335370cfb Fixes #19 and also added debug output for dummy ssl generation 2018-11-05 10:58:22 +11:00
9b852f01e3 Merge pull request #23 from jlesage/default-https-site
Added default HTTPs site.
2018-11-05 09:36:29 +10:00
20fd185652 Added default HTTPs site. 2018-11-02 21:12:40 -04:00
ad41986bd5 Updated CI 2018-10-25 10:53:45 +10:00
c826ed8c1f Fix proto forwarded header to industry standard 2018-10-25 08:51:43 +10:00
eaebc48f66 Made the hostnames clickable and open new tabs (yay) 2018-10-19 16:51:31 +10:00
eb391959aa Added resolvers auto generation in order for hostnames to work 2018-10-19 16:24:44 +10:00
dba4340548 Host or IP specified for Proxy Host Forward 2018-10-19 15:38:14 +10:00
0b8a49469f Bumped version 2018-09-29 16:06:36 +10:00
b16a68052f Bumped version 2018-09-29 16:02:28 +10:00
83686c4535 Added websock upgrade option for base proxy host locations 2018-09-29 16:02:05 +10:00
efa1424cad Fixes #11 - After creating keys the app needs to completely restart due to aggressive module caching 2018-09-12 11:14:06 +10:00
4fe26ec4c0 Expose ports for host network mode support 2018-09-05 10:03:43 +10:00
34 changed files with 404 additions and 158 deletions

View File

@ -29,4 +29,11 @@ ADD knexfile.js /app/knexfile.js
VOLUME [ "/data", "/etc/letsencrypt" ] VOLUME [ "/data", "/etc/letsencrypt" ]
CMD [ "/init" ] CMD [ "/init" ]
# Ports
EXPOSE 80
EXPOSE 81
EXPOSE 443
EXPOSE 9876
HEALTHCHECK --interval=15s --timeout=3s CMD curl -f http://localhost:9876/health || exit 1 HEALTHCHECK --interval=15s --timeout=3s CMD curl -f http://localhost:9876/health || exit 1

View File

@ -29,4 +29,10 @@ ADD knexfile.js /app/knexfile.js
VOLUME [ "/data", "/etc/letsencrypt" ] VOLUME [ "/data", "/etc/letsencrypt" ]
CMD [ "/init" ] CMD [ "/init" ]
# Ports
EXPOSE 80
EXPOSE 81
EXPOSE 443
EXPOSE 9876
HEALTHCHECK --interval=15s --timeout=3s CMD curl -f http://localhost:9876/health || exit 1 HEALTHCHECK --interval=15s --timeout=3s CMD curl -f http://localhost:9876/health || exit 1

8
Jenkinsfile vendored
View File

@ -27,10 +27,10 @@ pipeline {
steps { steps {
ansiColor('xterm') { ansiColor('xterm') {
// Codebase // Codebase
sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME yarn --registry=$NPM_REGISTRY install' sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME yarn install'
sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME npm run-script build' sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME npm run-script build'
sh 'rm -rf node_modules' sh 'rm -rf node_modules'
sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME yarn --registry=$NPM_REGISTRY install --prod' sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME yarn install --prod'
sh 'docker run --rm -v $(pwd):/data $DOCKER_CI_TOOLS node-prune' sh 'docker run --rm -v $(pwd):/data $DOCKER_CI_TOOLS node-prune'
// Docker Build // Docker Build
@ -70,10 +70,10 @@ pipeline {
steps { steps {
ansiColor('xterm') { ansiColor('xterm') {
// Codebase // Codebase
sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME-armhf yarn --registry=$NPM_REGISTRY install' sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME-armhf yarn install'
sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME-armhf npm run-script build' sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME-armhf npm run-script build'
sh 'rm -rf node_modules' sh 'rm -rf node_modules'
sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME-armhf yarn --registry=$NPM_REGISTRY install --prod' sh 'docker run --rm -v $(pwd):/app -w /app $BASE_IMAGE_NAME-armhf yarn install --prod'
// Docker Build // Docker Build
sh 'docker build --pull --no-cache --squash --compress -t $TEMP_IMAGE_NAME_ARM -f Dockerfile.armhf .' sh 'docker build --pull --no-cache --squash --compress -t $TEMP_IMAGE_NAME_ARM -f Dockerfile.armhf .'

View File

@ -2,7 +2,7 @@
# Nginx Proxy Manager # Nginx Proxy Manager
![Version](https://img.shields.io/badge/version-2.0.2-green.svg?style=for-the-badge) ![Version](https://img.shields.io/badge/version-2.0.3-green.svg?style=for-the-badge)
![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)

26
bin/migrate_create Executable file
View File

@ -0,0 +1,26 @@
#!/bin/bash
if [ "$1" == "" ]; then
echo "Error: migrate name must be specified as first arg"
exit 1
else
# Code path
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
if hash realpath 2>/dev/null; then
export CODEBASE=$(realpath $SCRIPT_DIR/..)
elif hash grealpath 2>/dev/null; then
export CODEBASE=$(grealpath $SCRIPT_DIR/..)
else
export CODEBASE=$(readlink -e $SCRIPT_DIR/..)
fi
if [ -z "$CODEBASE" ]; then
echo "Unable to determine absolute codebase directory"
exit 1
fi
cd "$CODEBASE"
sudo /usr/local/bin/docker-compose run --rm --no-deps app node node_modules/knex/bin/cli.js migrate:make "$1"
exit $?
fi

View File

@ -2,7 +2,7 @@
# Nginx Proxy Manager # Nginx Proxy Manager
![Version](https://img.shields.io/badge/version-2.0.0-green.svg?style=for-the-badge) ![Version](https://img.shields.io/badge/version-2.0.3-green.svg?style=for-the-badge)
![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)

View File

@ -1,6 +1,6 @@
{ {
"name": "nginx-proxy-manager", "name": "nginx-proxy-manager",
"version": "2.0.2", "version": "2.0.4",
"description": "A beautiful interface for creating Nginx endpoints", "description": "A beautiful interface for creating Nginx endpoints",
"main": "src/backend/index.js", "main": "src/backend/index.js",
"devDependencies": { "devDependencies": {
@ -30,8 +30,8 @@
"style-loader": "^0.22.1", "style-loader": "^0.22.1",
"tabler-ui": "git+https://github.com/tabler/tabler.git", "tabler-ui": "git+https://github.com/tabler/tabler.git",
"underscore": "^1.8.3", "underscore": "^1.8.3",
"webpack": "^4.12.0", "webpack": "^4.25.1",
"webpack-cli": "^3.0.8", "webpack-cli": "^3.1.2",
"webpack-visualizer-plugin": "^0.1.11" "webpack-visualizer-plugin": "^0.1.11"
}, },
"dependencies": { "dependencies": {

View File

@ -36,3 +36,17 @@ server {
root /var/www/html; root /var/www/html;
} }
} }
# Default 443 Host
server {
listen 443 ssl default;
server_name localhost;
access_log /data/logs/default.log proxy;
ssl_certificate /data/nginx/dummycert.pem;
ssl_certificate_key /data/nginx/dummykey.pem;
include conf.d/include/ssl-ciphers.conf;
return 444;
}

View File

@ -1,6 +1,6 @@
add_header X-Served-By $host; add_header X-Served-By $host;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Forwarded-Scheme $scheme; proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-Protocol $scheme; proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass http://$server:$port; proxy_pass http://$server:$port;

View File

@ -0,0 +1 @@
# Intentionally blank

View File

@ -51,6 +51,10 @@ http {
access_log /data/logs/default.log proxy; access_log /data/logs/default.log proxy;
# Dynamically generated resolvers file
include /etc/nginx/conf.d/include/resolvers.conf;
# Files generated by NPM
include /etc/nginx/conf.d/*.conf; include /etc/nginx/conf.d/*.conf;
include /data/nginx/proxy_host/*.conf; include /data/nginx/proxy_host/*.conf;
include /data/nginx/redirection_host/*.conf; include /data/nginx/redirection_host/*.conf;
@ -59,6 +63,7 @@ http {
} }
stream { stream {
# Files generated by NPM
include /data/nginx/stream/*.conf; include /data/nginx/stream/*.conf;
} }

View File

@ -3,4 +3,9 @@
mkdir -p /data/letsencrypt-acme-challenge 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
while :
do
node --abort_on_uncaught_exception --max_old_space_size=250 /app/src/backend/index.js
sleep 1
done

View File

@ -1,5 +1,6 @@
#!/usr/bin/with-contenv bash #!/usr/bin/with-contenv bash
# Create required folders
mkdir -p /tmp/nginx/body \ mkdir -p /tmp/nginx/body \
/var/log/nginx \ /var/log/nginx \
/data/nginx \ /data/nginx \
@ -12,9 +13,30 @@ mkdir -p /tmp/nginx/body \
/data/nginx/dead_host \ /data/nginx/dead_host \
/data/nginx/temp \ /data/nginx/temp \
/var/lib/nginx/cache/public \ /var/lib/nginx/cache/public \
/var/lib/nginx/cache/private /var/lib/nginx/cache/private \
/var/cache/nginx/proxy_temp
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 && chmod -R 777 /var/cache/nginx
chown root /tmp/nginx chown root /tmp/nginx
# Dynamically generate resolvers file
echo resolver $(awk 'BEGIN{ORS=" "} $1=="nameserver" {print $2}' /etc/resolv.conf) ";" > /etc/nginx/conf.d/include/resolvers.conf
# Generate dummy self-signed certificate.
if [ ! -f /data/nginx/dummycert.pem ] || [ ! -f /data/nginx/dummykey.pem ]
then
echo "Generating dummy SSL certificate..."
openssl req \
-new \
-newkey rsa:2048 \
-days 3650 \
-nodes \
-x509 \
-subj '/O=Nginx Proxy Manager/OU=Dummy Certificate/CN=localhost' \
-keyout /data/nginx/dummykey.pem \
-out /data/nginx/dummycert.pem
echo "Complete"
fi
# Run
exec nginx exec nginx

View File

@ -1,9 +1,10 @@
'use strict'; 'use strict';
const fs = require('fs'); const fs = require('fs');
const logger = require('./logger').import; const logger = require('./logger').import;
const utils = require('./lib/utils'); const utils = require('./lib/utils');
const batchflow = require('batchflow'); const batchflow = require('batchflow');
const debug_mode = process.env.NODE_ENV !== 'production';
const internalProxyHost = require('./internal/proxy-host'); const internalProxyHost = require('./internal/proxy-host');
const internalRedirectionHost = require('./internal/redirection-host'); const internalRedirectionHost = require('./internal/redirection-host');
@ -353,7 +354,7 @@ module.exports = function () {
.insertAndFetch({ .insertAndFetch({
owner_user_id: 1, owner_user_id: 1,
domain_names: [host.hostname], domain_names: [host.hostname],
forward_ip: host.forward_server, forward_host: host.forward_server,
forward_port: host.forward_port, forward_port: host.forward_port,
access_list_id: access_list_id, access_list_id: access_list_id,
certificate_id: certificate_id, certificate_id: certificate_id,
@ -534,6 +535,10 @@ module.exports = function () {
); );
} else { } else {
if (debug_mode) {
logger.debug('Importer skipped');
}
resolve(); resolve();
} }
}); });

View File

@ -8,4 +8,5 @@ module.exports = {
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'}), import: new Signale({scope: 'Importer'}),
setup: new Signale({scope: 'Setup '})
}; };

View File

@ -0,0 +1,37 @@
'use strict';
const migrate_name = 'websockets';
const logger = require('../logger').migrate;
/**
* Migrate
*
* @see http://knexjs.org/#Schema
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.up = function (knex/*, Promise*/) {
logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.table('proxy_host', function (proxy_host) {
proxy_host.integer('allow_websocket_upgrade').notNull().unsigned().defaultTo(0);
})
.then(() => {
logger.info('[' + migrate_name + '] proxy_host Table altered');
});
};
/**
* Undo Migrate
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.down = function (knex, Promise) {
logger.warn('[' + migrate_name + '] You can\'t migrate down this one.');
return Promise.resolve(true);
};

View File

@ -0,0 +1,36 @@
'use strict';
const migrate_name = 'forward_host';
const logger = require('../logger').migrate;
/**
* Migrate
*
* @see http://knexjs.org/#Schema
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.up = function (knex/*, Promise*/) {
logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.table('proxy_host', function (proxy_host) {
proxy_host.renameColumn('forward_ip', 'forward_host');
})
.then(() => {
logger.info('[' + migrate_name + '] proxy_host Table altered');
});
};
/**
* Undo Migrate
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.down = function (knex, Promise) {
logger.warn('[' + migrate_name + '] You can\'t migrate down this one.');
return Promise.resolve(true);
};

View File

@ -18,9 +18,10 @@
"domain_names": { "domain_names": {
"$ref": "../definitions.json#/definitions/domain_names" "$ref": "../definitions.json#/definitions/domain_names"
}, },
"forward_ip": { "forward_host": {
"type": "string", "type": "string",
"format": "ipv4" "minLength": 1,
"maxLength": 50
}, },
"forward_port": { "forward_port": {
"type": "integer", "type": "integer",
@ -39,6 +40,11 @@
"caching_enabled": { "caching_enabled": {
"$ref": "../definitions.json#/definitions/caching_enabled" "$ref": "../definitions.json#/definitions/caching_enabled"
}, },
"allow_websocket_upgrade": {
"description": "Allow Websocket Upgrade for all paths",
"example": true,
"type": "boolean"
},
"access_list_id": { "access_list_id": {
"$ref": "../definitions.json#/definitions/access_list_id" "$ref": "../definitions.json#/definitions/access_list_id"
}, },
@ -62,8 +68,8 @@
"domain_names": { "domain_names": {
"$ref": "#/definitions/domain_names" "$ref": "#/definitions/domain_names"
}, },
"forward_ip": { "forward_host": {
"$ref": "#/definitions/forward_ip" "$ref": "#/definitions/forward_host"
}, },
"forward_port": { "forward_port": {
"$ref": "#/definitions/forward_port" "$ref": "#/definitions/forward_port"
@ -80,6 +86,9 @@
"caching_enabled": { "caching_enabled": {
"$ref": "#/definitions/caching_enabled" "$ref": "#/definitions/caching_enabled"
}, },
"allow_websocket_upgrade": {
"$ref": "#/definitions/allow_websocket_upgrade"
},
"access_list_id": { "access_list_id": {
"$ref": "#/definitions/access_list_id" "$ref": "#/definitions/access_list_id"
}, },
@ -123,15 +132,15 @@
"additionalProperties": false, "additionalProperties": false,
"required": [ "required": [
"domain_names", "domain_names",
"forward_ip", "forward_host",
"forward_port" "forward_port"
], ],
"properties": { "properties": {
"domain_names": { "domain_names": {
"$ref": "#/definitions/domain_names" "$ref": "#/definitions/domain_names"
}, },
"forward_ip": { "forward_host": {
"$ref": "#/definitions/forward_ip" "$ref": "#/definitions/forward_host"
}, },
"forward_port": { "forward_port": {
"$ref": "#/definitions/forward_port" "$ref": "#/definitions/forward_port"
@ -148,6 +157,9 @@
"caching_enabled": { "caching_enabled": {
"$ref": "#/definitions/caching_enabled" "$ref": "#/definitions/caching_enabled"
}, },
"allow_websocket_upgrade": {
"$ref": "#/definitions/allow_websocket_upgrade"
},
"access_list_id": { "access_list_id": {
"$ref": "#/definitions/access_list_id" "$ref": "#/definitions/access_list_id"
}, },
@ -182,8 +194,8 @@
"domain_names": { "domain_names": {
"$ref": "#/definitions/domain_names" "$ref": "#/definitions/domain_names"
}, },
"forward_ip": { "forward_host": {
"$ref": "#/definitions/forward_ip" "$ref": "#/definitions/forward_host"
}, },
"forward_port": { "forward_port": {
"$ref": "#/definitions/forward_port" "$ref": "#/definitions/forward_port"
@ -200,6 +212,9 @@
"caching_enabled": { "caching_enabled": {
"$ref": "#/definitions/caching_enabled" "$ref": "#/definitions/caching_enabled"
}, },
"allow_websocket_upgrade": {
"$ref": "#/definitions/allow_websocket_upgrade"
},
"access_list_id": { "access_list_id": {
"$ref": "#/definitions/access_list_id" "$ref": "#/definitions/access_list_id"
}, },

View File

@ -3,10 +3,11 @@
const fs = require('fs'); const fs = require('fs');
const NodeRSA = require('node-rsa'); const NodeRSA = require('node-rsa');
const config = require('config'); const config = require('config');
const logger = require('./logger').global; const logger = require('./logger').setup;
const userModel = require('./models/user'); const userModel = require('./models/user');
const userPermissionModel = require('./models/user_permission'); const userPermissionModel = require('./models/user_permission');
const authModel = require('./models/auth'); const authModel = require('./models/auth');
const debug_mode = process.env.NODE_ENV !== 'production';
module.exports = function () { module.exports = function () {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -22,6 +23,9 @@ module.exports = function () {
config_data = require(filename); config_data = require(filename);
} catch (err) { } catch (err) {
// do nothing // do nothing
if (debug_mode) {
logger.debug(filename + ' config file could not be required');
}
} }
// Now create the keys and save them in the config. // Now create the keys and save them in the config.
@ -40,12 +44,18 @@ module.exports = function () {
reject(err); reject(err);
} else { } else {
logger.info('Wrote JWT key pair to config file: ' + filename); logger.info('Wrote JWT key pair to config file: ' + filename);
config.util.loadFileConfigs();
resolve(); logger.warn('Restarting interface to apply new configuration');
process.exit(0);
} }
}); });
} else { } else {
// JWT key pair exists // JWT key pair exists
if (debug_mode) {
logger.debug('JWT Keypair already exists');
}
resolve(); resolve();
} }
}) })
@ -54,49 +64,54 @@ module.exports = function () {
.query() .query()
.select(userModel.raw('COUNT(`id`) as `count`')) .select(userModel.raw('COUNT(`id`) as `count`'))
.where('is_deleted', 0) .where('is_deleted', 0)
.first('count') .first();
.then(row => { })
if (!row.count) { .then(row => {
// Create a new user and set password if (!row.count) {
logger.info('Creating a new user: admin@example.com with password: changeme'); // Create a new user and set password
logger.info('Creating a new user: admin@example.com with password: changeme');
let data = { let data = {
is_deleted: 0, is_deleted: 0,
email: 'admin@example.com', email: 'admin@example.com',
name: 'Administrator', name: 'Administrator',
nickname: 'Admin', nickname: 'Admin',
avatar: '', avatar: '',
roles: ['admin'] roles: ['admin']
}; };
return userModel return userModel
.query()
.insertAndFetch(data)
.then(user => {
return authModel
.query() .query()
.insertAndFetch(data) .insert({
.then(user => { user_id: user.id,
return authModel type: 'password',
secret: 'changeme',
meta: {}
})
.then(() => {
return userPermissionModel
.query() .query()
.insert({ .insert({
user_id: user.id, user_id: user.id,
type: 'password', visibility: 'all',
secret: 'changeme', proxy_hosts: 'manage',
meta: {} redirection_hosts: 'manage',
}) dead_hosts: 'manage',
.then(() => { streams: 'manage',
return userPermissionModel access_lists: 'manage',
.query() certificates: 'manage'
.insert({
user_id: user.id,
visibility: 'all',
proxy_hosts: 'manage',
redirection_hosts: 'manage',
dead_hosts: 'manage',
streams: 'manage',
access_lists: 'manage',
certificates: 'manage'
});
}); });
}); });
} })
}); .then(() => {
logger.info('Initial setup completed');
});
} else if (debug_mode) {
logger.debug('Admin user setup not required');
}
}); });
}; };

View File

@ -1,7 +1,7 @@
{% include "_header_comment.conf" %} {% include "_header_comment.conf" %}
server { server {
set $server {{ forward_ip }}; set $server "{{ forward_host }}";
set $port {{ forward_port }}; set $port {{ forward_port }};
{% include "_listen.conf" %} {% include "_listen.conf" %}
@ -22,6 +22,12 @@ server {
{% include "_forced_ssl.conf" %} {% include "_forced_ssl.conf" %}
{% if allow_websocket_upgrade == 1 or allow_websocket_upgrade == true %}
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
{% endif %}
# Proxy! # Proxy!
include conf.d/include/proxy.conf; include conf.d/include/proxy.conf;
} }

View File

@ -5,5 +5,5 @@
<span class="loader"></span> <span class="loader"></span>
</div> </div>
<script type="text/javascript" src="/js/main.js?v=<%= version %>"></script> <script type="text/javascript" src="/js/main.bundle.js?v=<%= version %>"></script>
<%- include partials/footer.ejs %> <%- include partials/footer.ejs %>

View File

@ -5,5 +5,5 @@
<span class="loader"></span> <span class="loader"></span>
</div> </div>
<script type="text/javascript" src="/js/login.js?v=<%= version %>"></script> <script type="text/javascript" src="/js/main.bundle.js?v=<%= version %>"></script>
<%- include partials/footer.ejs %> <%- include partials/footer.ejs %>

View File

@ -6,9 +6,15 @@
<td> <td>
<div> <div>
<% domain_names.map(function(host) { <% domain_names.map(function(host) {
%> if (host.indexOf('*') === -1) {
<span class="tag"><%- host %></span> %>
<% <span class="tag host-link hover-red" rel="http<%- certificate_id ? 's' : '' %>://<%- host %>"><%- host %></span>
<%
} else {
%>
<span class="tag"><%- host %></span>
<%
}
}); });
%> %>
</div> </div>

View File

@ -9,8 +9,9 @@ module.exports = Mn.View.extend({
tagName: 'tr', tagName: 'tr',
ui: { ui: {
edit: 'a.edit', edit: 'a.edit',
delete: 'a.delete' delete: 'a.delete',
host_link: '.host-link'
}, },
events: { events: {
@ -22,6 +23,12 @@ module.exports = Mn.View.extend({
'click @ui.delete': function (e) { 'click @ui.delete': function (e) {
e.preventDefault(); e.preventDefault();
App.Controller.showNginxDeadDeleteConfirm(this.model); App.Controller.showNginxDeadDeleteConfirm(this.model);
},
'click @ui.host_link': function (e) {
e.preventDefault();
let win = window.open($(e.currentTarget).attr('rel'), '_blank');
win.focus();
} }
}, },

View File

@ -22,8 +22,8 @@
</div> </div>
<div class="col-sm-8 col-md-8"> <div class="col-sm-8 col-md-8">
<div class="form-group"> <div class="form-group">
<label class="form-label"><%- i18n('proxy-hosts', 'forward-ip') %><span class="form-required">*</span></label> <label class="form-label"><%- i18n('proxy-hosts', 'forward-host') %><span class="form-required">*</span></label>
<input type="text" name="forward_ip" class="form-control text-monospace" placeholder="000.000.000.000" value="<%- forward_ip %>" autocomplete="off" maxlength="15" required> <input type="text" name="forward_host" class="form-control text-monospace" placeholder="" value="<%- forward_host %>" autocomplete="off" maxlength="50" required>
</div> </div>
</div> </div>
<div class="col-sm-4 col-md-4"> <div class="col-sm-4 col-md-4">
@ -50,6 +50,15 @@
</label> </label>
</div> </div>
</div> </div>
<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="custom-switch">
<input type="checkbox" class="custom-switch-input" name="allow_websocket_upgrade" value="1"<%- allow_websocket_upgrade ? ' checked' : '' %>>
<span class="custom-switch-indicator"></span>
<span class="custom-switch-description"><%- i18n('proxy-hosts', 'allow-websocket-upgrade') %></span>
</label>
</div>
</div>
<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>

View File

@ -9,7 +9,6 @@ const accessListItemTemplate = require('./access-list-item.ejs');
const Helpers = require('../../../lib/helpers'); const Helpers = require('../../../lib/helpers');
require('jquery-serializejson'); require('jquery-serializejson');
require('jquery-mask-plugin');
require('selectize'); require('selectize');
module.exports = Mn.View.extend({ module.exports = Mn.View.extend({
@ -19,7 +18,7 @@ module.exports = Mn.View.extend({
ui: { ui: {
form: 'form', form: 'form',
domain_names: 'input[name="domain_names"]', domain_names: 'input[name="domain_names"]',
forward_ip: 'input[name="forward_ip"]', forward_host: 'input[name="forward_host"]',
buttons: '.modal-footer button', buttons: '.modal-footer button',
cancel: 'button.cancel', cancel: 'button.cancel',
save: 'button.save', save: 'button.save',
@ -54,9 +53,10 @@ module.exports = Mn.View.extend({
let data = this.ui.form.serializeJSON(); let data = this.ui.form.serializeJSON();
// Manipulate // Manipulate
data.forward_port = parseInt(data.forward_port, 10); data.forward_port = parseInt(data.forward_port, 10);
data.block_exploits = !!data.block_exploits; data.block_exploits = !!data.block_exploits;
data.caching_enabled = !!data.caching_enabled; data.caching_enabled = !!data.caching_enabled;
data.allow_websocket_upgrade = !!data.allow_websocket_upgrade;
if (typeof data.ssl_forced !== 'undefined' && data.ssl_forced === '1') { if (typeof data.ssl_forced !== 'undefined' && data.ssl_forced === '1') {
data.ssl_forced = true; data.ssl_forced = true;
@ -122,12 +122,6 @@ module.exports = Mn.View.extend({
onRender: function () { onRender: function () {
let view = this; let view = this;
// IP Address
this.ui.forward_ip.mask('099.099.099.099', {
clearIfNotMatch: true,
placeholder: '000.000.000.000'
});
// Domain names // Domain names
this.ui.domain_names.selectize({ this.ui.domain_names.selectize({
delimiter: ',', delimiter: ',',

View File

@ -6,9 +6,15 @@
<td> <td>
<div> <div>
<% domain_names.map(function(host) { <% domain_names.map(function(host) {
%> if (host.indexOf('*') === -1) {
<span class="tag"><%- host %></span> %>
<% <span class="tag host-link hover-green" rel="http<%- certificate_id ? 's' : '' %>://<%- host %>"><%- host %></span>
<%
} else {
%>
<span class="tag"><%- host %></span>
<%
}
}); });
%> %>
</div> </div>
@ -17,7 +23,7 @@
</div> </div>
</td> </td>
<td> <td>
<div class="text-monospace"><%- forward_ip %>:<%- forward_port %></div> <div class="text-monospace"><%- forward_host %>:<%- forward_port %></div>
</td> </td>
<td> <td>
<div><%- certificate && certificate_id ? i18n('ssl', certificate.provider) : i18n('ssl', 'none') %></div> <div><%- certificate && certificate_id ? i18n('ssl', certificate.provider) : i18n('ssl', 'none') %></div>

View File

@ -9,8 +9,9 @@ module.exports = Mn.View.extend({
tagName: 'tr', tagName: 'tr',
ui: { ui: {
edit: 'a.edit', edit: 'a.edit',
delete: 'a.delete' delete: 'a.delete',
host_link: '.host-link'
}, },
events: { events: {
@ -22,6 +23,12 @@ module.exports = Mn.View.extend({
'click @ui.delete': function (e) { 'click @ui.delete': function (e) {
e.preventDefault(); e.preventDefault();
App.Controller.showNginxProxyDeleteConfirm(this.model); App.Controller.showNginxProxyDeleteConfirm(this.model);
},
'click @ui.host_link': function (e) {
e.preventDefault();
let win = window.open($(e.currentTarget).attr('rel'), '_blank');
win.focus();
} }
}, },

View File

@ -6,9 +6,15 @@
<td> <td>
<div> <div>
<% domain_names.map(function(host) { <% domain_names.map(function(host) {
%> if (host.indexOf('*') === -1) {
<span class="tag"><%- host %></span> %>
<% <span class="tag host-link hover-yellow" rel="http<%- certificate_id ? 's' : '' %>://<%- host %>"><%- host %></span>
<%
} else {
%>
<span class="tag"><%- host %></span>
<%
}
}); });
%> %>
</div> </div>

View File

@ -9,8 +9,9 @@ module.exports = Mn.View.extend({
tagName: 'tr', tagName: 'tr',
ui: { ui: {
edit: 'a.edit', edit: 'a.edit',
delete: 'a.delete' delete: 'a.delete',
host_link: '.host-link'
}, },
events: { events: {
@ -22,6 +23,12 @@ module.exports = Mn.View.extend({
'click @ui.delete': function (e) { 'click @ui.delete': function (e) {
e.preventDefault(); e.preventDefault();
App.Controller.showNginxRedirectionDeleteConfirm(this.model); App.Controller.showNginxRedirectionDeleteConfirm(this.model);
},
'click @ui.host_link': function (e) {
e.preventDefault();
let win = window.open($(e.currentTarget).attr('rel'), '_blank');
win.focus();
} }
}, },

View File

@ -92,13 +92,14 @@
"empty": "There are no Proxy Hosts", "empty": "There are no Proxy Hosts",
"add": "Add Proxy Host", "add": "Add Proxy Host",
"form-title": "{id, select, undefined{New} other{Edit}} Proxy Host", "form-title": "{id, select, undefined{New} other{Edit}} Proxy Host",
"forward-ip": "Forward IP", "forward-host": "Forward Hostname / IP",
"forward-port": "Forward Port", "forward-port": "Forward Port",
"delete": "Delete Proxy Host", "delete": "Delete Proxy Host",
"delete-confirm": "Are you sure you want to delete the Proxy host for: <strong>{domains}</strong>?", "delete-confirm": "Are you sure you want to delete the Proxy host for: <strong>{domains}</strong>?",
"help-title": "What is a Proxy Host?", "help-title": "What is a Proxy Host?",
"help-content": "A Proxy Host is the incoming endpoint for a web service that you want to forward.\nIt provides optional SSL termination for your service that might not have SSL support built in.\nProxy Hosts are the most common use for the Nginx Proxy Manager.", "help-content": "A Proxy Host is the incoming endpoint for a web service that you want to forward.\nIt provides optional SSL termination for your service that might not have SSL support built in.\nProxy Hosts are the most common use for the Nginx Proxy Manager.",
"access-list": "Access List" "access-list": "Access List",
"allow-websocket-upgrade": "Allow Websocket HTTP Upgrades"
}, },
"redirection-hosts": { "redirection-hosts": {
"title": "Redirection Hosts", "title": "Redirection Hosts",

View File

@ -7,23 +7,24 @@ const model = Backbone.Model.extend({
defaults: function () { defaults: function () {
return { return {
id: undefined, id: undefined,
created_on: null, created_on: null,
modified_on: null, modified_on: null,
domain_names: [], domain_names: [],
forward_ip: '', forward_host: '',
forward_port: null, forward_port: null,
access_list_id: 0, access_list_id: 0,
certificate_id: 0, certificate_id: 0,
ssl_forced: false, ssl_forced: false,
caching_enabled: false, caching_enabled: false,
block_exploits: false, allow_websocket_upgrade: false,
advanced_config: '', block_exploits: false,
meta: {}, advanced_config: '',
meta: {},
// The following are expansions: // The following are expansions:
owner: null, owner: null,
access_list: null, access_list: null,
certificate: null certificate: null
}; };
} }
}); });

View File

@ -3,6 +3,18 @@ $yellow: #f1c40f;
$blue: #467fcf; $blue: #467fcf;
$pink: #f66d9b; $pink: #f66d9b;
.tag.hover-green:hover, .tag.hover-green:active, .tag.hover-green:focus {
background-color: #5eba00;
cursor: pointer;
color: #fff;
}
.tag.hover-red:hover, .tag.hover-red:active, .tag.hover-red:focus {
background-color: #cd201f;
cursor: pointer;
color: #fff;
}
/* For Card bodies where I don't want padding */ /* For Card bodies where I don't want padding */
.card-body.no-padding { .card-body.no-padding {
padding: 0; padding: 0;
@ -28,6 +40,12 @@ $pink: #f66d9b;
border-color: $teal; border-color: $teal;
} }
.tag.hover-teal:hover, .tag.hover-teal:active, .tag.hover-teal:focus {
background-color: $teal;
color: #fff;
cursor: pointer;
}
/* Yellow Outline Buttons */ /* Yellow Outline Buttons */
.btn-outline-yellow { .btn-outline-yellow {
color: $yellow; color: $yellow;
@ -48,6 +66,12 @@ $pink: #f66d9b;
border-color: $yellow; border-color: $yellow;
} }
.tag.hover-yellow:hover, .tag.hover-yellow:active, .tag.hover-yellow:focus {
background-color: $yellow;
cursor: pointer;
color: #fff;
}
/* Blue Outline Buttons */ /* Blue Outline Buttons */
.btn-outline-blue { .btn-outline-blue {
color: $blue; color: $blue;
@ -68,6 +92,12 @@ $pink: #f66d9b;
border-color: $blue; border-color: $blue;
} }
.tag.hover-blue:hover, .tag.hover-blue:active, .tag.hover-blue:focus {
background-color: $blue;
cursor: pointer;
color: #fff;
}
/* Pink Outline Buttons */ /* Pink Outline Buttons */
.btn-outline-pink { .btn-outline-pink {
color: $pink; color: $pink;
@ -88,6 +118,11 @@ $pink: #f66d9b;
border-color: $pink; border-color: $pink;
} }
.tag.hover-pink:hover, .tag.hover-pink:active, .tag.hover-pink:focus {
background-color: $pink;
cursor: pointer;
}
/* dimmer */ /* dimmer */
.dimmer .loader { .dimmer .loader {

View File

@ -10,9 +10,10 @@ module.exports = {
login: './src/frontend/js/login.js' login: './src/frontend/js/login.js'
}, },
output: { output: {
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].js', filename: 'js/[name].bundle.js',
publicPath: '/' chunkFilename: 'js/[name].bundle.[id].js',
publicPath: '/'
}, },
resolve: { resolve: {
alias: { alias: {
@ -108,41 +109,6 @@ module.exports = {
to: 'images', to: 'images',
toType: 'dir', toType: 'dir',
context: '/app' context: '/app'
}]), }])
new webpack.optimize.LimitChunkCountPlugin({ ]
maxChunks: 1, // Must be greater than or equal to one
minChunkSize: 999999999
})
],
/*
optimization: {
splitChunks: {
chunks (chunk) {
// exclude `my-excluded-chunk`
return false;
},
minSize: 999999999,
minChunks: 1,
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
},
*/
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 8080,
disableHostCheck: true,
host: '0.0.0.0'
}
}; };