mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-08-02 07:23:38 +00:00
Default Site customisation and new Settings space (#91)
This commit is contained in:
@@ -17,9 +17,9 @@ const internalNginx = {
|
||||
* - IF BAD: update the meta with offline status and remove the config entirely
|
||||
* - then reload nginx
|
||||
*
|
||||
* @param {Object} model
|
||||
* @param {String} host_type
|
||||
* @param {Object} host
|
||||
* @param {Object|String} model
|
||||
* @param {String} host_type
|
||||
* @param {Object} host
|
||||
* @returns {Promise}
|
||||
*/
|
||||
configure: (model, host_type, host) => {
|
||||
@@ -122,6 +122,11 @@ const internalNginx = {
|
||||
*/
|
||||
getConfigName: (host_type, host_id) => {
|
||||
host_type = host_type.replace(new RegExp('-', 'g'), '_');
|
||||
|
||||
if (host_type === 'default') {
|
||||
return '/data/nginx/default_host/site.conf';
|
||||
}
|
||||
|
||||
return '/data/nginx/' + host_type + '/' + host_id + '.conf';
|
||||
},
|
||||
|
||||
@@ -153,9 +158,11 @@ const internalNginx = {
|
||||
}
|
||||
|
||||
// Manipulate the data a bit before sending it to the template
|
||||
host.use_default_location = true;
|
||||
if (typeof host.advanced_config !== 'undefined' && host.advanced_config) {
|
||||
host.use_default_location = !internalNginx.advancedConfigHasDefaultLocation(host.advanced_config);
|
||||
if (host_type !== 'default') {
|
||||
host.use_default_location = true;
|
||||
if (typeof host.advanced_config !== 'undefined' && host.advanced_config) {
|
||||
host.use_default_location = !internalNginx.advancedConfigHasDefaultLocation(host.advanced_config);
|
||||
}
|
||||
}
|
||||
|
||||
renderEngine
|
||||
@@ -260,7 +267,7 @@ const internalNginx = {
|
||||
|
||||
/**
|
||||
* @param {String} host_type
|
||||
* @param {Object} host
|
||||
* @param {Object} [host]
|
||||
* @param {Boolean} [throw_errors]
|
||||
* @returns {Promise}
|
||||
*/
|
||||
@@ -269,7 +276,7 @@ const internalNginx = {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
let config_file = internalNginx.getConfigName(host_type, host.id);
|
||||
let config_file = internalNginx.getConfigName(host_type, typeof host === 'undefined' ? 0 : host.id);
|
||||
|
||||
if (debug_mode) {
|
||||
logger.warn('Deleting nginx config: ' + config_file);
|
||||
|
@@ -108,7 +108,7 @@ const internalProxyHost = {
|
||||
*/
|
||||
update: (access, data) => {
|
||||
let create_certificate = data.certificate_id === 'new';
|
||||
console.log('PH UPDATE:', data);
|
||||
|
||||
if (create_certificate) {
|
||||
delete data.certificate_id;
|
||||
}
|
||||
|
133
src/backend/internal/setting.js
Normal file
133
src/backend/internal/setting.js
Normal file
@@ -0,0 +1,133 @@
|
||||
const fs = require('fs');
|
||||
const error = require('../lib/error');
|
||||
const settingModel = require('../models/setting');
|
||||
const internalNginx = require('./nginx');
|
||||
|
||||
const internalSetting = {
|
||||
|
||||
/**
|
||||
* @param {Access} access
|
||||
* @param {Object} data
|
||||
* @param {String} data.id
|
||||
* @return {Promise}
|
||||
*/
|
||||
update: (access, data) => {
|
||||
return access.can('settings:update', data.id)
|
||||
.then(access_data => {
|
||||
return internalSetting.get(access, {id: data.id});
|
||||
})
|
||||
.then(row => {
|
||||
if (row.id !== data.id) {
|
||||
// Sanity check that something crazy hasn't happened
|
||||
throw new error.InternalValidationError('Setting could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
|
||||
}
|
||||
|
||||
return settingModel
|
||||
.query()
|
||||
.where({id: data.id})
|
||||
.patch(data);
|
||||
})
|
||||
.then(() => {
|
||||
return internalSetting.get(access, {
|
||||
id: data.id
|
||||
});
|
||||
})
|
||||
.then(row => {
|
||||
if (row.id === 'default-site') {
|
||||
// write the html if we need to
|
||||
if (row.value === 'html') {
|
||||
fs.writeFileSync('/data/nginx/default_www/index.html', row.meta.html, {encoding: 'utf8'});
|
||||
}
|
||||
|
||||
// Configure nginx
|
||||
return internalNginx.deleteConfig('default')
|
||||
.then(() => {
|
||||
return internalNginx.generateConfig('default', row);
|
||||
})
|
||||
.then(() => {
|
||||
return internalNginx.test();
|
||||
})
|
||||
.then(() => {
|
||||
return internalNginx.reload();
|
||||
})
|
||||
.then(() => {
|
||||
return row;
|
||||
})
|
||||
.catch((err) => {
|
||||
internalNginx.deleteConfig('default')
|
||||
.then(() => {
|
||||
return internalNginx.test();
|
||||
})
|
||||
.then(() => {
|
||||
return internalNginx.reload();
|
||||
})
|
||||
.then(() => {
|
||||
// I'm being slack here I know..
|
||||
throw new error.ValidationError('Could not reconfigure Nginx. Please check logs.');
|
||||
})
|
||||
});
|
||||
} else {
|
||||
return row;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Access} access
|
||||
* @param {Object} data
|
||||
* @param {String} data.id
|
||||
* @return {Promise}
|
||||
*/
|
||||
get: (access, data) => {
|
||||
return access.can('settings:get', data.id)
|
||||
.then(() => {
|
||||
return settingModel
|
||||
.query()
|
||||
.where('id', data.id)
|
||||
.first();
|
||||
})
|
||||
.then(row => {
|
||||
if (row) {
|
||||
return row;
|
||||
} else {
|
||||
throw new error.ItemNotFoundError(data.id);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* This will only count the settings
|
||||
*
|
||||
* @param {Access} access
|
||||
* @returns {*}
|
||||
*/
|
||||
getCount: (access) => {
|
||||
return access.can('settings:list')
|
||||
.then(() => {
|
||||
return settingModel
|
||||
.query()
|
||||
.count('id as count')
|
||||
.first();
|
||||
})
|
||||
.then(row => {
|
||||
return parseInt(row.count, 10);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* All settings
|
||||
*
|
||||
* @param {Access} access
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getAll: (access) => {
|
||||
return access.can('settings:list')
|
||||
.then(() => {
|
||||
return settingModel
|
||||
.query()
|
||||
.orderBy('description', 'ASC');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = internalSetting;
|
7
src/backend/lib/access/settings-get.json
Normal file
7
src/backend/lib/access/settings-get.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "roles#/definitions/admin"
|
||||
}
|
||||
]
|
||||
}
|
7
src/backend/lib/access/settings-list.json
Normal file
7
src/backend/lib/access/settings-list.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "roles#/definitions/admin"
|
||||
}
|
||||
]
|
||||
}
|
7
src/backend/lib/access/settings-update.json
Normal file
7
src/backend/lib/access/settings-update.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "roles#/definitions/admin"
|
||||
}
|
||||
]
|
||||
}
|
54
src/backend/migrations/20190227065017_settings.js
Normal file
54
src/backend/migrations/20190227065017_settings.js
Normal file
@@ -0,0 +1,54 @@
|
||||
const migrate_name = 'settings';
|
||||
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.createTable('setting', table => {
|
||||
table.string('id').notNull().primary();
|
||||
table.string('name', 100).notNull();
|
||||
table.string('description', 255).notNull();
|
||||
table.string('value', 255).notNull();
|
||||
table.json('meta').notNull();
|
||||
})
|
||||
.then(() => {
|
||||
logger.info('[' + migrate_name + '] setting Table created');
|
||||
|
||||
// TODO: add settings
|
||||
let settingModel = require('../models/setting');
|
||||
|
||||
return settingModel
|
||||
.query()
|
||||
.insert({
|
||||
id: 'default-site',
|
||||
name: 'Default Site',
|
||||
description: 'What to show when Nginx is hit with an unknown Host',
|
||||
value: 'congratulations',
|
||||
meta: {}
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
logger.info('[' + migrate_name + '] Default settings added');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Undo Migrate
|
||||
*
|
||||
* @param {Object} knex
|
||||
* @param {Promise} Promise
|
||||
* @returns {Promise}
|
||||
*/
|
||||
exports.down = function (knex, Promise) {
|
||||
logger.warn('[' + migrate_name + '] You can\'t migrate down the initial data.');
|
||||
return Promise.resolve(true);
|
||||
};
|
30
src/backend/models/setting.js
Normal file
30
src/backend/models/setting.js
Normal file
@@ -0,0 +1,30 @@
|
||||
// Objection Docs:
|
||||
// http://vincit.github.io/objection.js/
|
||||
|
||||
const db = require('../db');
|
||||
const Model = require('objection').Model;
|
||||
|
||||
Model.knex(db);
|
||||
|
||||
class Setting extends Model {
|
||||
$beforeInsert () {
|
||||
// Default for meta
|
||||
if (typeof this.meta === 'undefined') {
|
||||
this.meta = {};
|
||||
}
|
||||
}
|
||||
|
||||
static get name () {
|
||||
return 'Setting';
|
||||
}
|
||||
|
||||
static get tableName () {
|
||||
return 'setting';
|
||||
}
|
||||
|
||||
static get jsonAttributes () {
|
||||
return ['meta'];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Setting;
|
@@ -31,6 +31,7 @@ router.use('/tokens', require('./tokens'));
|
||||
router.use('/users', require('./users'));
|
||||
router.use('/audit-log', require('./audit-log'));
|
||||
router.use('/reports', require('./reports'));
|
||||
router.use('/settings', require('./settings'));
|
||||
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'));
|
||||
|
96
src/backend/routes/api/settings.js
Normal file
96
src/backend/routes/api/settings.js
Normal file
@@ -0,0 +1,96 @@
|
||||
const express = require('express');
|
||||
const validator = require('../../lib/validator');
|
||||
const jwtdecode = require('../../lib/express/jwt-decode');
|
||||
const internalSetting = require('../../internal/setting');
|
||||
const apiValidator = require('../../lib/validator/api');
|
||||
|
||||
let router = express.Router({
|
||||
caseSensitive: true,
|
||||
strict: true,
|
||||
mergeParams: true
|
||||
});
|
||||
|
||||
/**
|
||||
* /api/settings
|
||||
*/
|
||||
router
|
||||
.route('/')
|
||||
.options((req, res) => {
|
||||
res.sendStatus(204);
|
||||
})
|
||||
.all(jwtdecode())
|
||||
|
||||
/**
|
||||
* GET /api/settings
|
||||
*
|
||||
* Retrieve all settings
|
||||
*/
|
||||
.get((req, res, next) => {
|
||||
internalSetting.getAll(res.locals.access)
|
||||
.then(rows => {
|
||||
res.status(200)
|
||||
.send(rows);
|
||||
})
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
/**
|
||||
* Specific setting
|
||||
*
|
||||
* /api/settings/something
|
||||
*/
|
||||
router
|
||||
.route('/:setting_id')
|
||||
.options((req, res) => {
|
||||
res.sendStatus(204);
|
||||
})
|
||||
.all(jwtdecode())
|
||||
|
||||
/**
|
||||
* GET /settings/something
|
||||
*
|
||||
* Retrieve a specific setting
|
||||
*/
|
||||
.get((req, res, next) => {
|
||||
validator({
|
||||
required: ['setting_id'],
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
setting_id: {
|
||||
$ref: 'definitions#/definitions/setting_id'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
setting_id: req.params.setting_id
|
||||
})
|
||||
.then(data => {
|
||||
return internalSetting.get(res.locals.access, {
|
||||
id: data.setting_id
|
||||
});
|
||||
})
|
||||
.then(row => {
|
||||
res.status(200)
|
||||
.send(row);
|
||||
})
|
||||
.catch(next);
|
||||
})
|
||||
|
||||
/**
|
||||
* PUT /api/settings/something
|
||||
*
|
||||
* Update and existing setting
|
||||
*/
|
||||
.put((req, res, next) => {
|
||||
apiValidator({$ref: 'endpoints/settings#/links/1/schema'}, req.body)
|
||||
.then(payload => {
|
||||
payload.id = req.params.setting_id;
|
||||
return internalSetting.update(res.locals.access, payload);
|
||||
})
|
||||
.then(result => {
|
||||
res.status(200)
|
||||
.send(result);
|
||||
})
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
module.exports = router;
|
@@ -9,6 +9,13 @@
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"setting_id": {
|
||||
"description": "Unique identifier for a Setting",
|
||||
"example": "default-site",
|
||||
"readOnly": true,
|
||||
"type": "string",
|
||||
"minLength": 2
|
||||
},
|
||||
"token": {
|
||||
"type": "string",
|
||||
"minLength": 10
|
||||
|
99
src/backend/schema/endpoints/settings.json
Normal file
99
src/backend/schema/endpoints/settings.json
Normal file
@@ -0,0 +1,99 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "endpoints/settings",
|
||||
"title": "Settings",
|
||||
"description": "Endpoints relating to Settings",
|
||||
"stability": "stable",
|
||||
"type": "object",
|
||||
"definitions": {
|
||||
"id": {
|
||||
"$ref": "../definitions.json#/definitions/setting_id"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name",
|
||||
"example": "Default Site",
|
||||
"type": "string",
|
||||
"minLength": 2,
|
||||
"maxLength": 100
|
||||
},
|
||||
"description": {
|
||||
"description": "Description",
|
||||
"example": "Default Site",
|
||||
"type": "string",
|
||||
"minLength": 2,
|
||||
"maxLength": 255
|
||||
},
|
||||
"value": {
|
||||
"description": "Value",
|
||||
"example": "404",
|
||||
"type": "string",
|
||||
"maxLength": 255
|
||||
},
|
||||
"meta": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"title": "List",
|
||||
"description": "Returns a list of Settings",
|
||||
"href": "/settings",
|
||||
"access": "private",
|
||||
"method": "GET",
|
||||
"rel": "self",
|
||||
"http_header": {
|
||||
"$ref": "../examples.json#/definitions/auth_header"
|
||||
},
|
||||
"targetSchema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/properties"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Update",
|
||||
"description": "Updates a existing Setting",
|
||||
"href": "/settings/{definitions.identity.example}",
|
||||
"access": "private",
|
||||
"method": "PUT",
|
||||
"rel": "update",
|
||||
"http_header": {
|
||||
"$ref": "../examples.json#/definitions/auth_header"
|
||||
},
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"value": {
|
||||
"$ref": "#/definitions/value"
|
||||
},
|
||||
"meta": {
|
||||
"$ref": "#/definitions/meta"
|
||||
}
|
||||
}
|
||||
},
|
||||
"targetSchema": {
|
||||
"properties": {
|
||||
"$ref": "#/properties"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/id"
|
||||
},
|
||||
"name": {
|
||||
"$ref": "#/definitions/description"
|
||||
},
|
||||
"description": {
|
||||
"$ref": "#/definitions/description"
|
||||
},
|
||||
"value": {
|
||||
"$ref": "#/definitions/value"
|
||||
},
|
||||
"meta": {
|
||||
"$ref": "#/definitions/meta"
|
||||
}
|
||||
}
|
||||
}
|
@@ -34,6 +34,9 @@
|
||||
},
|
||||
"access-lists": {
|
||||
"$ref": "endpoints/access-lists.json"
|
||||
},
|
||||
"settings": {
|
||||
"$ref": "endpoints/settings.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
32
src/backend/templates/default.conf
Normal file
32
src/backend/templates/default.conf
Normal file
@@ -0,0 +1,32 @@
|
||||
# ------------------------------------------------------------
|
||||
# Default Site
|
||||
# ------------------------------------------------------------
|
||||
{% if value == "congratulations" %}
|
||||
# Skipping output, congratulations page configration is baked in.
|
||||
{%- else %}
|
||||
server {
|
||||
listen 80 default;
|
||||
server_name default-host.localhost;
|
||||
access_log /data/logs/default_host.log combined;
|
||||
{% include "_exploits.conf" %}
|
||||
|
||||
{%- if value == "404" %}
|
||||
location / {
|
||||
return 404;
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
{%- if value == "redirect" %}
|
||||
location / {
|
||||
return 301 {{ meta.redirect }};
|
||||
}
|
||||
{%- endif %}
|
||||
|
||||
{%- if value == "html" %}
|
||||
root /data/nginx/default_www;
|
||||
location / {
|
||||
try_files $uri /index.html ={{ meta.http_code }};
|
||||
}
|
||||
{%- endif %}
|
||||
}
|
||||
{% endif %}
|
Reference in New Issue
Block a user