mirror of
https://github.com/xiaoxinpro/nginx-proxy-manager-zh.git
synced 2025-01-22 21:08:13 -05:00
Default Site customisation and new Settings space (#91)
This commit is contained in:
parent
6f1d38a0e2
commit
133d66c2fe
@ -22,10 +22,10 @@ server {
|
||||
}
|
||||
}
|
||||
|
||||
# Default 80 Host, which shows a "You are not configured" page
|
||||
# "You are not configured" page, which is the default if another default doesn't exist
|
||||
server {
|
||||
listen 80 default;
|
||||
server_name localhost;
|
||||
listen 80;
|
||||
server_name localhost-nginx-proxy-manager;
|
||||
|
||||
access_log /data/logs/default.log proxy;
|
||||
|
||||
@ -38,9 +38,9 @@ server {
|
||||
}
|
||||
}
|
||||
|
||||
# Default 443 Host
|
||||
# First 443 Host, which is the default if another default doesn't exist
|
||||
server {
|
||||
listen 443 ssl default;
|
||||
listen 443 ssl;
|
||||
server_name localhost;
|
||||
|
||||
access_log /data/logs/default.log proxy;
|
||||
|
@ -70,6 +70,7 @@ http {
|
||||
|
||||
# Files generated by NPM
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
include /data/nginx/default_host/*.conf;
|
||||
include /data/nginx/proxy_host/*.conf;
|
||||
include /data/nginx/redirection_host/*.conf;
|
||||
include /data/nginx/dead_host/*.conf;
|
||||
|
@ -7,6 +7,8 @@ mkdir -p /tmp/nginx/body \
|
||||
/data/custom_ssl \
|
||||
/data/logs \
|
||||
/data/access \
|
||||
/data/nginx/default_host \
|
||||
/data/nginx/default_www \
|
||||
/data/nginx/proxy_host \
|
||||
/data/nginx/redirection_host \
|
||||
/data/nginx/stream \
|
||||
|
@ -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 %}
|
@ -662,5 +662,34 @@ module.exports = {
|
||||
getHostStats: function () {
|
||||
return fetch('get', 'reports/hosts');
|
||||
}
|
||||
},
|
||||
|
||||
Settings: {
|
||||
|
||||
/**
|
||||
* @param {String} setting_id
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getById: function (setting_id) {
|
||||
return fetch('get', 'settings/' + setting_id);
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getAll: function () {
|
||||
return getAllObjects('settings');
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Object} data
|
||||
* @param {Number} data.id
|
||||
* @returns {Promise}
|
||||
*/
|
||||
update: function (data) {
|
||||
let id = data.id;
|
||||
delete data.id;
|
||||
return fetch('put', 'settings/' + id, data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -383,6 +383,36 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Settings
|
||||
*/
|
||||
showSettings: function () {
|
||||
let controller = this;
|
||||
if (Cache.User.isAdmin()) {
|
||||
require(['./main', './settings/main'], (App, View) => {
|
||||
controller.navigate('/settings');
|
||||
App.UI.showAppContent(new View());
|
||||
});
|
||||
} else {
|
||||
this.showDashboard();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Settings Item Form
|
||||
*
|
||||
* @param model
|
||||
*/
|
||||
showSettingForm: function (model) {
|
||||
if (Cache.User.isAdmin()) {
|
||||
if (model.get('id') === 'default-site') {
|
||||
require(['./main', './settings/default-site/main'], function (App, View) {
|
||||
App.UI.showModalDialog(new View({model: model}));
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Logout
|
||||
*/
|
||||
|
@ -15,6 +15,7 @@ module.exports = AppRouter.default.extend({
|
||||
'nginx/access': 'showNginxAccess',
|
||||
'nginx/certificates': 'showNginxCertificates',
|
||||
'audit-log': 'showAuditLog',
|
||||
'settings': 'showSettings',
|
||||
'*default': 'showDashboard'
|
||||
}
|
||||
});
|
||||
|
77
src/frontend/js/app/settings/default-site/main.ejs
Normal file
77
src/frontend/js/app/settings/default-site/main.ejs
Normal file
@ -0,0 +1,77 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title"><%- i18n('settings', id) %></h5>
|
||||
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group">
|
||||
<div class="form-label"><%- description %></div>
|
||||
<div class="custom-controls-stacked">
|
||||
<label class="custom-control custom-radio">
|
||||
<input class="custom-control-input" name="value" value="congratulations" type="radio" required <%- value === 'congratulations' ? 'checked' : '' %>>
|
||||
<div class="custom-control-label"><%- i18n('settings', 'default-site-congratulations') %></div>
|
||||
</label>
|
||||
<label class="custom-control custom-radio">
|
||||
<input class="custom-control-input" name="value" value="404" type="radio" required <%- value === '404' ? 'checked' : '' %>>
|
||||
<div class="custom-control-label"><%- i18n('settings', 'default-site-404') %></div>
|
||||
</label>
|
||||
<label class="custom-control custom-radio">
|
||||
<input class="custom-control-input" name="value" value="redirect" type="radio" required <%- value === 'redirect' ? 'checked' : '' %>>
|
||||
<div class="custom-control-label"><%- i18n('settings', 'default-site-redirect') %></div>
|
||||
</label>
|
||||
<label class="custom-control custom-radio">
|
||||
<input class="custom-control-input" name="value" value="html" type="radio" required <%- value === 'html' ? 'checked' : '' %>>
|
||||
<div class="custom-control-label"><%- i18n('settings', 'default-site-html') %></div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-12 option-item option-redirect">
|
||||
<div class="form-group">
|
||||
<div class="form-label">Redirect to</div>
|
||||
<input class="form-control redirect-input" name="meta[redirect]" placeholder="https://" type="url" value="<%- meta && typeof meta.redirect !== 'undefined' ? meta.redirect : '' %>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-12 option-item option-html">
|
||||
<div class="form-group">
|
||||
<label class="form-label">HTTP Status Code</label>
|
||||
<%
|
||||
var code = meta && typeof meta.http_code !== 'undefined' ? parseInt(meta.http_code, 10) : 200;
|
||||
var codes = [
|
||||
[200, 'OK'],
|
||||
[204, 'No Content'],
|
||||
[400, 'Bad Request'],
|
||||
[401, 'Unauthorized'],
|
||||
[403, 'Forbidden'],
|
||||
[404, 'Not Found'],
|
||||
[418, 'I\'m a Teapot'],
|
||||
[500, 'Internal Server Error'],
|
||||
[501, 'Not Implemented'],
|
||||
[502, 'Bad Gateway'],
|
||||
[503, 'Service Unavailable']
|
||||
];
|
||||
%>
|
||||
<select class="custom-select" name="meta[http_code]">
|
||||
<% codes.map(function(item) { %>
|
||||
<option value="<%- item[0] %>"<%- code === item[0] ? ' selected' : '' %>><%- item[0] %> - <%- item[1] %></option>
|
||||
<% }); %>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-label">HTML Content</div>
|
||||
<textarea class="form-control text-monospace html-content" name="meta[html]" rows="6" placeholder="<!-- Enter your HTML here -->"><%- meta && typeof meta.html !== 'undefined' ? meta.html : '' %></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button>
|
||||
<button type="button" class="btn btn-teal save"><%- i18n('str', 'save') %></button>
|
||||
</div>
|
||||
</div>
|
71
src/frontend/js/app/settings/default-site/main.js
Normal file
71
src/frontend/js/app/settings/default-site/main.js
Normal file
@ -0,0 +1,71 @@
|
||||
'use strict';
|
||||
|
||||
const Mn = require('backbone.marionette');
|
||||
const App = require('../../main');
|
||||
const template = require('./main.ejs');
|
||||
|
||||
require('jquery-serializejson');
|
||||
require('selectize');
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
template: template,
|
||||
className: 'modal-dialog',
|
||||
|
||||
ui: {
|
||||
form: 'form',
|
||||
buttons: '.modal-footer button',
|
||||
cancel: 'button.cancel',
|
||||
save: 'button.save',
|
||||
options: '.option-item',
|
||||
value: 'input[name="value"]',
|
||||
redirect: '.redirect-input',
|
||||
html: '.html-content'
|
||||
},
|
||||
|
||||
events: {
|
||||
'change @ui.value': function (e) {
|
||||
let val = this.ui.value.filter(':checked').val();
|
||||
this.ui.options.hide();
|
||||
this.ui.options.filter('.option-' + val).show();
|
||||
},
|
||||
|
||||
'click @ui.save': function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
let val = this.ui.value.filter(':checked').val();
|
||||
|
||||
// Clear redirect field before validation
|
||||
if (val !== 'redirect') {
|
||||
this.ui.redirect.val('').attr('required', false);
|
||||
} else {
|
||||
this.ui.redirect.attr('required', true);
|
||||
}
|
||||
|
||||
this.ui.html.attr('required', val === 'html');
|
||||
|
||||
if (!this.ui.form[0].checkValidity()) {
|
||||
$('<input type="submit">').hide().appendTo(this.ui.form).click().remove();
|
||||
return;
|
||||
}
|
||||
|
||||
let view = this;
|
||||
let data = this.ui.form.serializeJSON();
|
||||
data.id = this.model.get('id');
|
||||
|
||||
this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
|
||||
App.Api.Settings.update(data)
|
||||
.then(result => {
|
||||
view.model.set(result);
|
||||
App.UI.closeModal();
|
||||
})
|
||||
.catch(err => {
|
||||
alert(err.message);
|
||||
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onRender: function () {
|
||||
this.ui.value.trigger('change');
|
||||
}
|
||||
});
|
21
src/frontend/js/app/settings/list/item.ejs
Normal file
21
src/frontend/js/app/settings/list/item.ejs
Normal file
@ -0,0 +1,21 @@
|
||||
<td>
|
||||
<div><%- name %></div>
|
||||
<div class="small text-muted">
|
||||
<%- description %>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div>
|
||||
<% if (id === 'default-site') { %>
|
||||
<%- i18n('settings', 'default-site-' + value) %>
|
||||
<% } %>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<div class="item-action dropdown">
|
||||
<a href="#" data-toggle="dropdown" class="icon"><i class="fe fe-more-vertical"></i></a>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a href="#" class="edit dropdown-item"><i class="dropdown-icon fe fe-edit"></i> <%- i18n('str', 'edit') %></a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
25
src/frontend/js/app/settings/list/item.js
Normal file
25
src/frontend/js/app/settings/list/item.js
Normal file
@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
const Mn = require('backbone.marionette');
|
||||
const App = require('../../main');
|
||||
const template = require('./item.ejs');
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
template: template,
|
||||
tagName: 'tr',
|
||||
|
||||
ui: {
|
||||
edit: 'a.edit'
|
||||
},
|
||||
|
||||
events: {
|
||||
'click @ui.edit': function (e) {
|
||||
e.preventDefault();
|
||||
App.Controller.showSettingForm(this.model);
|
||||
}
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
}
|
||||
});
|
8
src/frontend/js/app/settings/list/main.ejs
Normal file
8
src/frontend/js/app/settings/list/main.ejs
Normal file
@ -0,0 +1,8 @@
|
||||
<thead>
|
||||
<th><%- i18n('str', 'name') %></th>
|
||||
<th><%- i18n('str', 'value') %></th>
|
||||
<th> </th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- items -->
|
||||
</tbody>
|
29
src/frontend/js/app/settings/list/main.js
Normal file
29
src/frontend/js/app/settings/list/main.js
Normal file
@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
const Mn = require('backbone.marionette');
|
||||
const ItemView = require('./item');
|
||||
const template = require('./main.ejs');
|
||||
|
||||
const TableBody = Mn.CollectionView.extend({
|
||||
tagName: 'tbody',
|
||||
childView: ItemView
|
||||
});
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
tagName: 'table',
|
||||
className: 'table table-hover table-outline table-vcenter text-nowrap card-table',
|
||||
template: template,
|
||||
|
||||
regions: {
|
||||
body: {
|
||||
el: 'tbody',
|
||||
replaceElement: true
|
||||
}
|
||||
},
|
||||
|
||||
onRender: function () {
|
||||
this.showChildView('body', new TableBody({
|
||||
collection: this.collection
|
||||
}));
|
||||
}
|
||||
});
|
14
src/frontend/js/app/settings/main.ejs
Normal file
14
src/frontend/js/app/settings/main.ejs
Normal file
@ -0,0 +1,14 @@
|
||||
<div class="card">
|
||||
<div class="card-status bg-teal"></div>
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><%- i18n('settings', 'title') %></h3>
|
||||
</div>
|
||||
<div class="card-body no-padding min-100">
|
||||
<div class="dimmer active">
|
||||
<div class="loader"></div>
|
||||
<div class="dimmer-content list-region">
|
||||
<!-- List Region -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
50
src/frontend/js/app/settings/main.js
Normal file
50
src/frontend/js/app/settings/main.js
Normal file
@ -0,0 +1,50 @@
|
||||
'use strict';
|
||||
|
||||
const Mn = require('backbone.marionette');
|
||||
const App = require('../main');
|
||||
const SettingModel = require('../../models/setting');
|
||||
const ListView = require('./list/main');
|
||||
const ErrorView = require('../error/main');
|
||||
const template = require('./main.ejs');
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
id: 'settings',
|
||||
template: template,
|
||||
|
||||
ui: {
|
||||
list_region: '.list-region',
|
||||
add: '.add-item',
|
||||
dimmer: '.dimmer'
|
||||
},
|
||||
|
||||
regions: {
|
||||
list_region: '@ui.list_region'
|
||||
},
|
||||
|
||||
onRender: function () {
|
||||
let view = this;
|
||||
|
||||
App.Api.Settings.getAll()
|
||||
.then(response => {
|
||||
if (!view.isDestroyed() && response && response.length) {
|
||||
view.showChildView('list_region', new ListView({
|
||||
collection: new SettingModel.Collection(response)
|
||||
}));
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
view.showChildView('list_region', new ErrorView({
|
||||
code: err.code,
|
||||
message: err.message,
|
||||
retry: function () {
|
||||
App.Controller.showSettings();
|
||||
}
|
||||
}));
|
||||
|
||||
console.error(err);
|
||||
})
|
||||
.then(() => {
|
||||
view.ui.dimmer.removeClass('active');
|
||||
});
|
||||
}
|
||||
});
|
@ -42,6 +42,9 @@
|
||||
<li class="nav-item">
|
||||
<a href="/audit-log" class="nav-link"><i class="fe fe-book-open"></i> <%- i18n('audit-log', 'title') %></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/settings" class="nav-link"><i class="fe fe-settings"></i> <%- i18n('settings', 'title') %></a>
|
||||
</li>
|
||||
<% } %>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -31,7 +31,8 @@
|
||||
"online": "Online",
|
||||
"offline": "Offline",
|
||||
"unknown": "Unknown",
|
||||
"expires": "Expires"
|
||||
"expires": "Expires",
|
||||
"value": "Value"
|
||||
},
|
||||
"login": {
|
||||
"title": "Login to your account"
|
||||
@ -222,6 +223,14 @@
|
||||
"meta-title": "Details for Event",
|
||||
"view-meta": "View Details",
|
||||
"date": "Date"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
"default-site": "Default Site",
|
||||
"default-site-congratulations": "Congratulations Page",
|
||||
"default-site-404": "404 Page",
|
||||
"default-site-html": "Custom Page",
|
||||
"default-site-redirect": "Redirect"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
25
src/frontend/js/models/setting.js
Normal file
25
src/frontend/js/models/setting.js
Normal file
@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('underscore');
|
||||
const Backbone = require('backbone');
|
||||
|
||||
const model = Backbone.Model.extend({
|
||||
idAttribute: 'id',
|
||||
|
||||
defaults: function () {
|
||||
return {
|
||||
id: undefined,
|
||||
name: '',
|
||||
description: '',
|
||||
value: null,
|
||||
meta: []
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
Model: model,
|
||||
Collection: Backbone.Collection.extend({
|
||||
model: model
|
||||
})
|
||||
};
|
Loading…
Reference in New Issue
Block a user