Browse Source

Merge pull request #543 from jc21/develop

v2.4.0
jc21 5 years ago
parent
commit
74010acd85

+ 0 - 0
.jenkins/config.json → .jenkins/config-mysql.json


+ 11 - 0
.jenkins/config-sqlite.json

@@ -0,0 +1,11 @@
+{
+	"database": {
+		"engine": "knex-native",
+		"knex": {
+			"client": "sqlite3",
+			"connection": {
+				"filename": "/data/database.sqlite"
+			}
+		}
+	}
+}

+ 1 - 1
.version

@@ -1 +1 @@
-2.3.1
+2.4.0

+ 34 - 7
Jenkinsfile

@@ -83,23 +83,49 @@ pipeline {
 				'''
 				'''
 			}
 			}
 		}
 		}
-		stage('Test') {
+		stage('Integration Tests Sqlite') {
 			steps {
 			steps {
 				// Bring up a stack
 				// Bring up a stack
-				sh 'docker-compose up -d fullstack'
-				sh './scripts/wait-healthy $(docker-compose ps -q fullstack) 120'
+				sh 'docker-compose up -d fullstack-sqlite'
+				sh './scripts/wait-healthy $(docker-compose ps -q fullstack-sqlite) 120'
 
 
 				// Run tests
 				// Run tests
 				sh 'rm -rf test/results'
 				sh 'rm -rf test/results'
-				sh 'docker-compose up cypress'
+				sh 'docker-compose up cypress-sqlite'
 				// Get results
 				// Get results
-				sh 'docker cp -L "$(docker-compose ps -q cypress):/results" test/'
+				sh 'docker cp -L "$(docker-compose ps -q cypress-sqlite):/test/results" test/'
 			}
 			}
 			post {
 			post {
 				always {
 				always {
 					// Dumps to analyze later
 					// Dumps to analyze later
 					sh 'mkdir -p debug'
 					sh 'mkdir -p debug'
-					sh 'docker-compose logs fullstack | gzip > debug/docker_fullstack.log.gz'
+					sh 'docker-compose logs fullstack-sqlite | gzip > debug/docker_fullstack_sqlite.log.gz'
+					sh 'docker-compose logs db | gzip > debug/docker_db.log.gz'
+					// Cypress videos and screenshot artifacts
+					dir(path: 'test/results') {
+						archiveArtifacts allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml'
+					}
+					junit 'test/results/junit/*'
+				}
+			}
+		}
+		stage('Integration Tests Mysql') {
+			steps {
+				// Bring up a stack
+				sh 'docker-compose up -d fullstack-mysql'
+				sh './scripts/wait-healthy $(docker-compose ps -q fullstack-mysql) 120'
+
+				// Run tests
+				sh 'rm -rf test/results'
+				sh 'docker-compose up cypress-mysql'
+				// Get results
+				sh 'docker cp -L "$(docker-compose ps -q cypress-mysql):/test/results" test/'
+			}
+			post {
+				always {
+					// Dumps to analyze later
+					sh 'mkdir -p debug'
+					sh 'docker-compose logs fullstack-mysql | gzip > debug/docker_fullstack_mysql.log.gz'
 					sh 'docker-compose logs db | gzip > debug/docker_db.log.gz'
 					sh 'docker-compose logs db | gzip > debug/docker_db.log.gz'
 					// Cypress videos and screenshot artifacts
 					// Cypress videos and screenshot artifacts
 					dir(path: 'test/results') {
 					dir(path: 'test/results') {
@@ -136,8 +162,9 @@ pipeline {
 			}
 			}
 			steps {
 			steps {
 				withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
 				withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
+					// Docker Login
 					sh "docker login -u '${duser}' -p '${dpass}'"
 					sh "docker login -u '${duser}' -p '${dpass}'"
-					// Buildx with push
+					// Buildx with push from cache
 					sh "./scripts/buildx --push ${BUILDX_PUSH_TAGS}"
 					sh "./scripts/buildx --push ${BUILDX_PUSH_TAGS}"
 				}
 				}
 			}
 			}

+ 13 - 3
README.md

@@ -1,7 +1,7 @@
 <p align="center">
 <p align="center">
 	<img src="https://nginxproxymanager.com/github.png">
 	<img src="https://nginxproxymanager.com/github.png">
 	<br><br>
 	<br><br>
-	<img src="https://img.shields.io/badge/version-2.3.1-green.svg?style=for-the-badge">
+	<img src="https://img.shields.io/badge/version-2.4.0-green.svg?style=for-the-badge">
 	<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager">
 	<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager">
 		<img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge">
 		<img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge">
 	</a>
 	</a>
@@ -155,14 +155,24 @@ Special thanks to the following contributors:
 				<br /><sub><b>OhHeyAlan</b></sub>
 				<br /><sub><b>OhHeyAlan</b></sub>
 			</a>
 			</a>
 		</td>
 		</td>
-	</tr>
-	<tr>
 		<td align="center">
 		<td align="center">
 			<a href="https://github.com/dogmatic69">
 			<a href="https://github.com/dogmatic69">
 				<img src="https://avatars2.githubusercontent.com/u/94674?s=460&u=ca7647de53145c6283b6373ade5dc94ba99347db&v=4" width="80px;" alt=""/>
 				<img src="https://avatars2.githubusercontent.com/u/94674?s=460&u=ca7647de53145c6283b6373ade5dc94ba99347db&v=4" width="80px;" alt=""/>
 				<br /><sub><b>Carl Sutton</b></sub>
 				<br /><sub><b>Carl Sutton</b></sub>
 			</a>
 			</a>
 		</td>
 		</td>
+		<td align="center">
+			<a href="https://github.com/tg44">
+				<img src="https://avatars0.githubusercontent.com/u/31839?s=460&u=ad32f4cadfef5e5fb09cdfa4b7b7b36a99ba6811&v=4" width="80px;" alt=""/>
+				<br /><sub><b>Gergő Törcsvári</b></sub>
+			</a>
+		</td>
+		<td align="center">
+			<a href="https://github.com/vrenjith">
+				<img src="https://avatars3.githubusercontent.com/u/2093241?s=460&u=96ce93a9bebabdd0a60a2dc96cd093a41d5edaba&v=4" width="80px;" alt=""/>
+				<br /><sub><b>vrenjith</b></sub>
+			</a>
+		</td>
 	</tr>
 	</tr>
 </table>
 </table>
 <!-- markdownlint-enable -->
 <!-- markdownlint-enable -->

+ 26 - 0
backend/config/sqlite-test-db.json

@@ -0,0 +1,26 @@
+{
+  "database": {
+      "engine": "knex-native",
+      "knex": {
+        "client": "sqlite3",
+        "connection": {
+          "filename": "/app/backend/config/mydb.sqlite"
+        },
+        "pool": {
+          "min": 0,
+          "max": 1,
+          "createTimeoutMillis": 3000,
+          "acquireTimeoutMillis": 30000,
+          "idleTimeoutMillis": 30000,
+          "reapIntervalMillis": 1000,
+          "createRetryIntervalMillis": 100,
+          "propagateCreateError": false
+        },
+        "migrations": {
+          "tableName": "migrations",
+          "stub": "src/backend/lib/migrate_template.js",
+          "directory": "src/backend/migrations"
+        }
+      }
+    }
+}

+ 21 - 13
backend/db.js

@@ -4,19 +4,27 @@ if (!config.has('database')) {
 	throw new Error('Database config does not exist! Please read the instructions: https://github.com/jc21/nginx-proxy-manager/blob/master/doc/INSTALL.md');
 	throw new Error('Database config does not exist! Please read the instructions: https://github.com/jc21/nginx-proxy-manager/blob/master/doc/INSTALL.md');
 }
 }
 
 
-let data = {
-	client:     config.database.engine,
-	connection: {
-		host:     config.database.host,
-		user:     config.database.user,
-		password: config.database.password,
-		database: config.database.name,
-		port:     config.database.port
-	},
-	migrations: {
-		tableName: 'migrations'
-	}
-};
+function generateDbConfig() {
+	if (config.database.engine === 'knex-native') {
+		return config.database.knex;
+	} else
+		return {
+			client:     config.database.engine,
+			connection: {
+				host:     config.database.host,
+				user:     config.database.user,
+				password: config.database.password,
+				database: config.database.name,
+				port:     config.database.port
+			},
+			migrations: {
+				tableName: 'migrations'
+			}
+		};
+}
+
+
+let data = generateDbConfig();
 
 
 if (typeof config.database.version !== 'undefined') {
 if (typeof config.database.version !== 'undefined') {
 	data.version = config.database.version;
 	data.version = config.database.version;

+ 0 - 16
backend/migrations/20190227065017_settings.js

@@ -22,22 +22,6 @@ exports.up = function (knex/*, Promise*/) {
 	})
 	})
 		.then(() => {
 		.then(() => {
 			logger.info('[' + migrate_name + '] setting Table created');
 			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');
 		});
 		});
 };
 };
 
 

+ 4 - 3
backend/models/access_list.js

@@ -6,13 +6,14 @@ const Model            = require('objection').Model;
 const User             = require('./user');
 const User             = require('./user');
 const AccessListAuth   = require('./access_list_auth');
 const AccessListAuth   = require('./access_list_auth');
 const AccessListClient = require('./access_list_client');
 const AccessListClient = require('./access_list_client');
+const now              = require('./now_helper');
 
 
 Model.knex(db);
 Model.knex(db);
 
 
 class AccessList extends Model {
 class AccessList extends Model {
 	$beforeInsert () {
 	$beforeInsert () {
-		this.created_on  = Model.raw('NOW()');
-		this.modified_on = Model.raw('NOW()');
+		this.created_on  = now();
+		this.modified_on = now();
 
 
 		// Default for meta
 		// Default for meta
 		if (typeof this.meta === 'undefined') {
 		if (typeof this.meta === 'undefined') {
@@ -21,7 +22,7 @@ class AccessList extends Model {
 	}
 	}
 
 
 	$beforeUpdate () {
 	$beforeUpdate () {
-		this.modified_on = Model.raw('NOW()');
+		this.modified_on = now();
 	}
 	}
 
 
 	static get name () {
 	static get name () {

+ 4 - 3
backend/models/access_list_auth.js

@@ -3,13 +3,14 @@
 
 
 const db    = require('../db');
 const db    = require('../db');
 const Model = require('objection').Model;
 const Model = require('objection').Model;
+const now   = require('./now_helper');
 
 
 Model.knex(db);
 Model.knex(db);
 
 
 class AccessListAuth extends Model {
 class AccessListAuth extends Model {
 	$beforeInsert () {
 	$beforeInsert () {
-		this.created_on  = Model.raw('NOW()');
-		this.modified_on = Model.raw('NOW()');
+		this.created_on  = now();
+		this.modified_on = now();
 
 
 		// Default for meta
 		// Default for meta
 		if (typeof this.meta === 'undefined') {
 		if (typeof this.meta === 'undefined') {
@@ -18,7 +19,7 @@ class AccessListAuth extends Model {
 	}
 	}
 
 
 	$beforeUpdate () {
 	$beforeUpdate () {
-		this.modified_on = Model.raw('NOW()');
+		this.modified_on = now();
 	}
 	}
 
 
 	static get name () {
 	static get name () {

+ 4 - 3
backend/models/access_list_client.js

@@ -3,13 +3,14 @@
 
 
 const db    = require('../db');
 const db    = require('../db');
 const Model = require('objection').Model;
 const Model = require('objection').Model;
+const now   = require('./now_helper');
 
 
 Model.knex(db);
 Model.knex(db);
 
 
 class AccessListClient extends Model {
 class AccessListClient extends Model {
 	$beforeInsert () {
 	$beforeInsert () {
-		this.created_on  = Model.raw('NOW()');
-		this.modified_on = Model.raw('NOW()');
+		this.created_on  = now();
+		this.modified_on = now();
 
 
 		// Default for meta
 		// Default for meta
 		if (typeof this.meta === 'undefined') {
 		if (typeof this.meta === 'undefined') {
@@ -18,7 +19,7 @@ class AccessListClient extends Model {
 	}
 	}
 
 
 	$beforeUpdate () {
 	$beforeUpdate () {
-		this.modified_on = Model.raw('NOW()');
+		this.modified_on = now();
 	}
 	}
 
 
 	static get name () {
 	static get name () {

+ 4 - 3
backend/models/audit-log.js

@@ -4,13 +4,14 @@
 const db    = require('../db');
 const db    = require('../db');
 const Model = require('objection').Model;
 const Model = require('objection').Model;
 const User  = require('./user');
 const User  = require('./user');
+const now   = require('./now_helper');
 
 
 Model.knex(db);
 Model.knex(db);
 
 
 class AuditLog extends Model {
 class AuditLog extends Model {
 	$beforeInsert () {
 	$beforeInsert () {
-		this.created_on  = Model.raw('NOW()');
-		this.modified_on = Model.raw('NOW()');
+		this.created_on  = now();
+		this.modified_on = now();
 
 
 		// Default for meta
 		// Default for meta
 		if (typeof this.meta === 'undefined') {
 		if (typeof this.meta === 'undefined') {
@@ -19,7 +20,7 @@ class AuditLog extends Model {
 	}
 	}
 
 
 	$beforeUpdate () {
 	$beforeUpdate () {
-		this.modified_on = Model.raw('NOW()');
+		this.modified_on = now();
 	}
 	}
 
 
 	static get name () {
 	static get name () {

+ 4 - 3
backend/models/auth.js

@@ -5,6 +5,7 @@ const bcrypt = require('bcrypt');
 const db     = require('../db');
 const db     = require('../db');
 const Model  = require('objection').Model;
 const Model  = require('objection').Model;
 const User   = require('./user');
 const User   = require('./user');
+const now    = require('./now_helper');
 
 
 Model.knex(db);
 Model.knex(db);
 
 
@@ -24,8 +25,8 @@ function encryptPassword () {
 
 
 class Auth extends Model {
 class Auth extends Model {
 	$beforeInsert (queryContext) {
 	$beforeInsert (queryContext) {
-		this.created_on  = Model.raw('NOW()');
-		this.modified_on = Model.raw('NOW()');
+		this.created_on  = now();
+		this.modified_on = now();
 
 
 		// Default for meta
 		// Default for meta
 		if (typeof this.meta === 'undefined') {
 		if (typeof this.meta === 'undefined') {
@@ -36,7 +37,7 @@ class Auth extends Model {
 	}
 	}
 
 
 	$beforeUpdate (queryContext) {
 	$beforeUpdate (queryContext) {
-		this.modified_on = Model.raw('NOW()');
+		this.modified_on = now();
 		return encryptPassword.apply(this, queryContext);
 		return encryptPassword.apply(this, queryContext);
 	}
 	}
 
 

+ 5 - 4
backend/models/certificate.js

@@ -4,17 +4,18 @@
 const db    = require('../db');
 const db    = require('../db');
 const Model = require('objection').Model;
 const Model = require('objection').Model;
 const User  = require('./user');
 const User  = require('./user');
+const now   = require('./now_helper');
 
 
 Model.knex(db);
 Model.knex(db);
 
 
 class Certificate extends Model {
 class Certificate extends Model {
 	$beforeInsert () {
 	$beforeInsert () {
-		this.created_on  = Model.raw('NOW()');
-		this.modified_on = Model.raw('NOW()');
+		this.created_on  = now();
+		this.modified_on = now();
 
 
 		// Default for expires_on
 		// Default for expires_on
 		if (typeof this.expires_on === 'undefined') {
 		if (typeof this.expires_on === 'undefined') {
-			this.expires_on = Model.raw('NOW()');
+			this.expires_on = now();
 		}
 		}
 
 
 		// Default for domain_names
 		// Default for domain_names
@@ -31,7 +32,7 @@ class Certificate extends Model {
 	}
 	}
 
 
 	$beforeUpdate () {
 	$beforeUpdate () {
-		this.modified_on = Model.raw('NOW()');
+		this.modified_on = now();
 
 
 		// Sort domain_names
 		// Sort domain_names
 		if (typeof this.domain_names !== 'undefined') {
 		if (typeof this.domain_names !== 'undefined') {

+ 4 - 3
backend/models/dead_host.js

@@ -5,13 +5,14 @@ const db          = require('../db');
 const Model       = require('objection').Model;
 const Model       = require('objection').Model;
 const User        = require('./user');
 const User        = require('./user');
 const Certificate = require('./certificate');
 const Certificate = require('./certificate');
+const now         = require('./now_helper');
 
 
 Model.knex(db);
 Model.knex(db);
 
 
 class DeadHost extends Model {
 class DeadHost extends Model {
 	$beforeInsert () {
 	$beforeInsert () {
-		this.created_on  = Model.raw('NOW()');
-		this.modified_on = Model.raw('NOW()');
+		this.created_on  = now();
+		this.modified_on = now();
 
 
 		// Default for domain_names
 		// Default for domain_names
 		if (typeof this.domain_names === 'undefined') {
 		if (typeof this.domain_names === 'undefined') {
@@ -27,7 +28,7 @@ class DeadHost extends Model {
 	}
 	}
 
 
 	$beforeUpdate () {
 	$beforeUpdate () {
-		this.modified_on = Model.raw('NOW()');
+		this.modified_on = now();
 
 
 		// Sort domain_names
 		// Sort domain_names
 		if (typeof this.domain_names !== 'undefined') {
 		if (typeof this.domain_names !== 'undefined') {

+ 13 - 0
backend/models/now_helper.js

@@ -0,0 +1,13 @@
+const db     = require('../db');
+const config = require('config');
+const Model  = require('objection').Model;
+
+Model.knex(db);
+
+module.exports = function () {
+	if (config.database.knex && config.database.knex.client === 'sqlite3') {
+		return Model.raw('date(\'now\')');
+	} else {
+		return Model.raw('NOW()');
+	}
+};

+ 4 - 3
backend/models/proxy_host.js

@@ -6,13 +6,14 @@ const Model       = require('objection').Model;
 const User        = require('./user');
 const User        = require('./user');
 const AccessList  = require('./access_list');
 const AccessList  = require('./access_list');
 const Certificate = require('./certificate');
 const Certificate = require('./certificate');
+const now         = require('./now_helper');
 
 
 Model.knex(db);
 Model.knex(db);
 
 
 class ProxyHost extends Model {
 class ProxyHost extends Model {
 	$beforeInsert () {
 	$beforeInsert () {
-		this.created_on  = Model.raw('NOW()');
-		this.modified_on = Model.raw('NOW()');
+		this.created_on  = now();
+		this.modified_on = now();
 
 
 		// Default for domain_names
 		// Default for domain_names
 		if (typeof this.domain_names === 'undefined') {
 		if (typeof this.domain_names === 'undefined') {
@@ -28,7 +29,7 @@ class ProxyHost extends Model {
 	}
 	}
 
 
 	$beforeUpdate () {
 	$beforeUpdate () {
-		this.modified_on = Model.raw('NOW()');
+		this.modified_on = now();
 
 
 		// Sort domain_names
 		// Sort domain_names
 		if (typeof this.domain_names !== 'undefined') {
 		if (typeof this.domain_names !== 'undefined') {

+ 4 - 3
backend/models/redirection_host.js

@@ -5,13 +5,14 @@ const db          = require('../db');
 const Model       = require('objection').Model;
 const Model       = require('objection').Model;
 const User        = require('./user');
 const User        = require('./user');
 const Certificate = require('./certificate');
 const Certificate = require('./certificate');
+const now         = require('./now_helper');
 
 
 Model.knex(db);
 Model.knex(db);
 
 
 class RedirectionHost extends Model {
 class RedirectionHost extends Model {
 	$beforeInsert () {
 	$beforeInsert () {
-		this.created_on  = Model.raw('NOW()');
-		this.modified_on = Model.raw('NOW()');
+		this.created_on  = now();
+		this.modified_on = now();
 
 
 		// Default for domain_names
 		// Default for domain_names
 		if (typeof this.domain_names === 'undefined') {
 		if (typeof this.domain_names === 'undefined') {
@@ -27,7 +28,7 @@ class RedirectionHost extends Model {
 	}
 	}
 
 
 	$beforeUpdate () {
 	$beforeUpdate () {
-		this.modified_on = Model.raw('NOW()');
+		this.modified_on = now();
 
 
 		// Sort domain_names
 		// Sort domain_names
 		if (typeof this.domain_names !== 'undefined') {
 		if (typeof this.domain_names !== 'undefined') {

+ 4 - 3
backend/models/stream.js

@@ -4,13 +4,14 @@
 const db    = require('../db');
 const db    = require('../db');
 const Model = require('objection').Model;
 const Model = require('objection').Model;
 const User  = require('./user');
 const User  = require('./user');
+const now   = require('./now_helper');
 
 
 Model.knex(db);
 Model.knex(db);
 
 
 class Stream extends Model {
 class Stream extends Model {
 	$beforeInsert () {
 	$beforeInsert () {
-		this.created_on  = Model.raw('NOW()');
-		this.modified_on = Model.raw('NOW()');
+		this.created_on  = now();
+		this.modified_on = now();
 
 
 		// Default for meta
 		// Default for meta
 		if (typeof this.meta === 'undefined') {
 		if (typeof this.meta === 'undefined') {
@@ -19,7 +20,7 @@ class Stream extends Model {
 	}
 	}
 
 
 	$beforeUpdate () {
 	$beforeUpdate () {
-		this.modified_on = Model.raw('NOW()');
+		this.modified_on = now();
 	}
 	}
 
 
 	static get name () {
 	static get name () {

+ 4 - 3
backend/models/user.js

@@ -4,13 +4,14 @@
 const db             = require('../db');
 const db             = require('../db');
 const Model          = require('objection').Model;
 const Model          = require('objection').Model;
 const UserPermission = require('./user_permission');
 const UserPermission = require('./user_permission');
+const now            = require('./now_helper');
 
 
 Model.knex(db);
 Model.knex(db);
 
 
 class User extends Model {
 class User extends Model {
 	$beforeInsert () {
 	$beforeInsert () {
-		this.created_on  = Model.raw('NOW()');
-		this.modified_on = Model.raw('NOW()');
+		this.created_on  = now();
+		this.modified_on = now();
 
 
 		// Default for roles
 		// Default for roles
 		if (typeof this.roles === 'undefined') {
 		if (typeof this.roles === 'undefined') {
@@ -19,7 +20,7 @@ class User extends Model {
 	}
 	}
 
 
 	$beforeUpdate () {
 	$beforeUpdate () {
-		this.modified_on = Model.raw('NOW()');
+		this.modified_on = now();
 	}
 	}
 
 
 	static get name () {
 	static get name () {

+ 4 - 3
backend/models/user_permission.js

@@ -3,17 +3,18 @@
 
 
 const db    = require('../db');
 const db    = require('../db');
 const Model = require('objection').Model;
 const Model = require('objection').Model;
+const now   = require('./now_helper');
 
 
 Model.knex(db);
 Model.knex(db);
 
 
 class UserPermission extends Model {
 class UserPermission extends Model {
 	$beforeInsert () {
 	$beforeInsert () {
-		this.created_on  = Model.raw('NOW()');
-		this.modified_on = Model.raw('NOW()');
+		this.created_on  = now();
+		this.modified_on = now();
 	}
 	}
 
 
 	$beforeUpdate () {
 	$beforeUpdate () {
-		this.modified_on = Model.raw('NOW()');
+		this.modified_on = now();
 	}
 	}
 
 
 	static get name () {
 	static get name () {

+ 4 - 2
backend/package.json

@@ -12,22 +12,24 @@
 		"config": "^3.3.1",
 		"config": "^3.3.1",
 		"diskdb": "^0.1.17",
 		"diskdb": "^0.1.17",
 		"express": "^4.17.1",
 		"express": "^4.17.1",
-		"express-fileupload": "^1.1.6",
+		"express-fileupload": "^1.1.9",
 		"gravatar": "^1.8.0",
 		"gravatar": "^1.8.0",
 		"html-entities": "^1.2.1",
 		"html-entities": "^1.2.1",
 		"json-schema-ref-parser": "^8.0.0",
 		"json-schema-ref-parser": "^8.0.0",
 		"jsonwebtoken": "^8.5.1",
 		"jsonwebtoken": "^8.5.1",
 		"knex": "^0.20.13",
 		"knex": "^0.20.13",
 		"liquidjs": "^9.11.10",
 		"liquidjs": "^9.11.10",
-		"lodash": "^4.17.15",
+		"lodash": "^4.17.19",
 		"moment": "^2.24.0",
 		"moment": "^2.24.0",
 		"mysql": "^2.18.1",
 		"mysql": "^2.18.1",
 		"node-rsa": "^1.0.8",
 		"node-rsa": "^1.0.8",
 		"nodemon": "^2.0.2",
 		"nodemon": "^2.0.2",
 		"objection": "^2.1.3",
 		"objection": "^2.1.3",
 		"path": "^0.12.7",
 		"path": "^0.12.7",
+		"pg": "^7.12.1",
 		"restler": "^3.4.0",
 		"restler": "^3.4.0",
 		"signale": "^1.4.0",
 		"signale": "^1.4.0",
+		"sqlite3": "^4.1.1",
 		"temp-write": "^4.0.0",
 		"temp-write": "^4.0.0",
 		"unix-timestamp": "^0.2.0"
 		"unix-timestamp": "^0.2.0"
 	},
 	},

+ 74 - 27
backend/setup.js

@@ -5,9 +5,15 @@ 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 settingModel        = require('./models/setting');
 const debug_mode          = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG;
 const debug_mode          = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG;
 
 
-module.exports = function () {
+/**
+ * Creates a new JWT RSA Keypair if not alread set on the config
+ *
+ * @returns {Promise}
+ */
+const setupJwt = () => {
 	return new Promise((resolve, reject) => {
 	return new Promise((resolve, reject) => {
 		// Now go and check if the jwt gpg keys have been created and if not, create them
 		// Now go and check if the jwt gpg keys have been created and if not, create them
 		if (!config.has('jwt') || !config.has('jwt.key') || !config.has('jwt.pub')) {
 		if (!config.has('jwt') || !config.has('jwt.key') || !config.has('jwt.pub')) {
@@ -27,12 +33,12 @@ module.exports = function () {
 			}
 			}
 
 
 			// Now create the keys and save them in the config.
 			// Now create the keys and save them in the config.
-			let key = new NodeRSA({b: 2048});
+			let key = new NodeRSA({ b: 2048 });
 			key.generateKeyPair();
 			key.generateKeyPair();
 
 
 			config_data.jwt = {
 			config_data.jwt = {
 				key: key.exportKey('private').toString(),
 				key: key.exportKey('private').toString(),
-				pub: key.exportKey('public').toString()
+				pub: key.exportKey('public').toString(),
 			};
 			};
 
 
 			// Write config
 			// Write config
@@ -47,7 +53,6 @@ module.exports = function () {
 					process.exit(0);
 					process.exit(0);
 				}
 				}
 			});
 			});
-
 		} else {
 		} else {
 			// JWT key pair exists
 			// JWT key pair exists
 			if (debug_mode) {
 			if (debug_mode) {
@@ -56,14 +61,20 @@ module.exports = function () {
 
 
 			resolve();
 			resolve();
 		}
 		}
-	})
-		.then(() => {
-			return userModel
-				.query()
-				.select(userModel.raw('COUNT(`id`) as `count`'))
-				.where('is_deleted', 0)
-				.first();
-		})
+	});
+};
+
+/**
+ * Creates a default admin users if one doesn't already exist in the database
+ *
+ * @returns {Promise}
+ */
+const setupDefaultUser = () => {
+	return userModel
+		.query()
+		.select(userModel.raw('COUNT(`id`) as `count`'))
+		.where('is_deleted', 0)
+		.first()
 		.then((row) => {
 		.then((row) => {
 			if (!row.count) {
 			if (!row.count) {
 				// Create a new user and set password
 				// Create a new user and set password
@@ -75,7 +86,7 @@ module.exports = function () {
 					name:       'Administrator',
 					name:       'Administrator',
 					nickname:   'Admin',
 					nickname:   'Admin',
 					avatar:     '',
 					avatar:     '',
-					roles:      ['admin']
+					roles:      ['admin'],
 				};
 				};
 
 
 				return userModel
 				return userModel
@@ -88,28 +99,64 @@ module.exports = function () {
 								user_id: user.id,
 								user_id: user.id,
 								type:    'password',
 								type:    'password',
 								secret:  'changeme',
 								secret:  'changeme',
-								meta:    {}
+								meta:    {},
 							})
 							})
 							.then(() => {
 							.then(() => {
-								return userPermissionModel
-									.query()
-									.insert({
-										user_id:           user.id,
-										visibility:        'all',
-										proxy_hosts:       'manage',
-										redirection_hosts: 'manage',
-										dead_hosts:        'manage',
-										streams:           'manage',
-										access_lists:      'manage',
-										certificates:      'manage'
-									});
+								return userPermissionModel.query().insert({
+									user_id:           user.id,
+									visibility:        'all',
+									proxy_hosts:       'manage',
+									redirection_hosts: 'manage',
+									dead_hosts:        'manage',
+									streams:           'manage',
+									access_lists:      'manage',
+									certificates:      'manage',
+								});
 							});
 							});
 					})
 					})
 					.then(() => {
 					.then(() => {
-						logger.info('Initial setup completed');
+						logger.info('Initial admin setup completed');
 					});
 					});
 			} else if (debug_mode) {
 			} else if (debug_mode) {
 				logger.debug('Admin user setup not required');
 				logger.debug('Admin user setup not required');
 			}
 			}
 		});
 		});
 };
 };
+
+/**
+ * Creates default settings if they don't already exist in the database
+ *
+ * @returns {Promise}
+ */
+const setupDefaultSettings = () => {
+	return settingModel
+		.query()
+		.select(settingModel.raw('COUNT(`id`) as `count`'))
+		.where({id: 'default-site'})
+		.first()
+		.then((row) => {
+			if (!row.count) {
+				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('Default settings added');
+					});
+			}
+			if (debug_mode) {
+				logger.debug('Default setting setup not required');
+			}
+		});
+};
+
+module.exports = function () {
+	return setupJwt()
+		.then(setupDefaultUser)
+		.then(setupDefaultSettings);
+};

File diff suppressed because it is too large
+ 313 - 289
backend/yarn.lock


+ 28 - 4
docker/docker-compose.ci.yml

@@ -2,14 +2,14 @@
 version: "3"
 version: "3"
 services:
 services:
 
 
-  fullstack:
+  fullstack-mysql:
     image: ${IMAGE}:ci-${BUILD_NUMBER}
     image: ${IMAGE}:ci-${BUILD_NUMBER}
     environment:
     environment:
       - NODE_ENV=development
       - NODE_ENV=development
       - FORCE_COLOR=1
       - FORCE_COLOR=1
     volumes:
     volumes:
       - npm_data:/data
       - npm_data:/data
-      - ../.jenkins/config.json:/app/config/production.json
+      - ../.jenkins/config-mysql.json:/app/config/development.json
     expose:
     expose:
       - 81
       - 81
       - 80
       - 80
@@ -17,6 +17,19 @@ services:
     depends_on:
     depends_on:
       - db
       - db
 
 
+  fullstack-sqlite:
+    image: ${IMAGE}:ci-${BUILD_NUMBER}
+    environment:
+      - NODE_ENV=development
+      - FORCE_COLOR=1
+    volumes:
+      - npm_data:/data
+      - ../.jenkins/config-sqlite.json:/app/config/development.json
+    expose:
+      - 81
+      - 80
+      - 443
+
   db:
   db:
     image: jc21/mariadb-aria
     image: jc21/mariadb-aria
     environment:
     environment:
@@ -27,13 +40,24 @@ services:
     volumes:
     volumes:
       - db_data:/var/lib/mysql
       - db_data:/var/lib/mysql
 
 
-  cypress:
+  cypress-mysql:
+    image: ${IMAGE}-cypress:ci-${BUILD_NUMBER}
+    build:
+      context: ../
+      dockerfile: test/cypress/Dockerfile
+    environment:
+      CYPRESS_baseUrl: "http://fullstack-mysql:81"
+    volumes:
+      - cypress-logs:/results
+    command: cypress run --browser chrome --config-file=${CYPRESS_CONFIG:-cypress/config/ci.json}
+
+  cypress-sqlite:
     image: ${IMAGE}-cypress:ci-${BUILD_NUMBER}
     image: ${IMAGE}-cypress:ci-${BUILD_NUMBER}
     build:
     build:
       context: ../
       context: ../
       dockerfile: test/cypress/Dockerfile
       dockerfile: test/cypress/Dockerfile
     environment:
     environment:
-      CYPRESS_baseUrl: "http://fullstack:81"
+      CYPRESS_baseUrl: "http://fullstack-sqlite:81"
     volumes:
     volumes:
       - cypress-logs:/results
       - cypress-logs:/results
     command: cypress run --browser chrome --config-file=${CYPRESS_CONFIG:-cypress/config/ci.json}
     command: cypress run --browser chrome --config-file=${CYPRESS_CONFIG:-cypress/config/ci.json}

+ 154 - 154
docs/package.json

@@ -4,15 +4,15 @@
   "description": "",
   "description": "",
   "main": "index.js",
   "main": "index.js",
   "dependencies": {
   "dependencies": {
-    "@vuepress/plugin-google-analytics": "^1.4.0",
+    "@vuepress/plugin-google-analytics": "^1.5.3",
     "abbrev": "^1.1.1",
     "abbrev": "^1.1.1",
     "accepts": "^1.3.7",
     "accepts": "^1.3.7",
-    "acorn": "^7.1.1",
-    "agentkeepalive": "^4.1.0",
-    "ajv": "^6.12.0",
+    "acorn": "^7.4.0",
+    "agentkeepalive": "^4.1.3",
+    "ajv": "^6.12.3",
     "ajv-errors": "^1.0.1",
     "ajv-errors": "^1.0.1",
-    "ajv-keywords": "^3.4.1",
-    "algoliasearch": "^4.1.0",
+    "ajv-keywords": "^3.5.2",
+    "algoliasearch": "^4.3.1",
     "alphanum-sort": "^1.0.2",
     "alphanum-sort": "^1.0.2",
     "ansi-colors": "^4.1.1",
     "ansi-colors": "^4.1.1",
     "ansi-escapes": "^4.3.1",
     "ansi-escapes": "^4.3.1",
@@ -30,7 +30,7 @@
     "array-uniq": "^2.1.0",
     "array-uniq": "^2.1.0",
     "array-unique": "^0.3.2",
     "array-unique": "^0.3.2",
     "asn1": "^0.2.4",
     "asn1": "^0.2.4",
-    "asn1.js": "^5.3.0",
+    "asn1.js": "^5.4.1",
     "assert": "^2.0.0",
     "assert": "^2.0.0",
     "assert-plus": "^1.0.0",
     "assert-plus": "^1.0.0",
     "assign-symbols": "^2.0.2",
     "assign-symbols": "^2.0.2",
@@ -40,11 +40,11 @@
     "asynckit": "^0.4.0",
     "asynckit": "^0.4.0",
     "atob": "^2.1.2",
     "atob": "^2.1.2",
     "autocomplete.js": "^0.37.1",
     "autocomplete.js": "^0.37.1",
-    "autoprefixer": "^9.7.6",
+    "autoprefixer": "^9.8.6",
     "aws-sign2": "^0.7.0",
     "aws-sign2": "^0.7.0",
-    "aws4": "^1.9.1",
+    "aws4": "^1.10.0",
     "babel-loader": "^8.1.0",
     "babel-loader": "^8.1.0",
-    "babel-plugin-dynamic-import-node": "^2.3.0",
+    "babel-plugin-dynamic-import-node": "^2.3.3",
     "babel-plugin-module-resolver": "^4.0.0",
     "babel-plugin-module-resolver": "^4.0.0",
     "balanced-match": "^1.0.0",
     "balanced-match": "^1.0.0",
     "base": "^3.0.0",
     "base": "^3.0.0",
@@ -52,9 +52,9 @@
     "batch": "^0.6.1",
     "batch": "^0.6.1",
     "bcrypt-pbkdf": "^1.0.2",
     "bcrypt-pbkdf": "^1.0.2",
     "big.js": "^5.2.2",
     "big.js": "^5.2.2",
-    "binary-extensions": "^2.0.0",
+    "binary-extensions": "^2.1.0",
     "bluebird": "^3.7.2",
     "bluebird": "^3.7.2",
-    "bn.js": "^5.1.1",
+    "bn.js": "^5.1.2",
     "body-parser": "^1.19.0",
     "body-parser": "^1.19.0",
     "bonjour": "^3.5.0",
     "bonjour": "^3.5.0",
     "boolbase": "^1.0.0",
     "boolbase": "^1.0.0",
@@ -65,18 +65,18 @@
     "browserify-cipher": "^1.0.1",
     "browserify-cipher": "^1.0.1",
     "browserify-des": "^1.0.2",
     "browserify-des": "^1.0.2",
     "browserify-rsa": "^4.0.1",
     "browserify-rsa": "^4.0.1",
-    "browserify-sign": "^4.0.4",
+    "browserify-sign": "^4.2.1",
     "browserify-zlib": "^0.2.0",
     "browserify-zlib": "^0.2.0",
-    "browserslist": "^4.11.1",
-    "buffer": "^5.5.0",
+    "browserslist": "^4.13.0",
+    "buffer": "^5.6.0",
     "buffer-from": "^1.1.1",
     "buffer-from": "^1.1.1",
     "buffer-indexof": "^1.1.1",
     "buffer-indexof": "^1.1.1",
     "buffer-json": "^2.0.0",
     "buffer-json": "^2.0.0",
     "buffer-xor": "^2.0.2",
     "buffer-xor": "^2.0.2",
     "builtin-status-codes": "^3.0.0",
     "builtin-status-codes": "^3.0.0",
     "bytes": "^3.1.0",
     "bytes": "^3.1.0",
-    "cac": "^6.5.8",
-    "cacache": "^15.0.0",
+    "cac": "^6.6.1",
+    "cacache": "^15.0.5",
     "cache-base": "^4.0.0",
     "cache-base": "^4.0.0",
     "cache-loader": "^4.1.0",
     "cache-loader": "^4.1.0",
     "call-me-maybe": "^1.0.1",
     "call-me-maybe": "^1.0.1",
@@ -84,12 +84,12 @@
     "caller-path": "^3.0.0",
     "caller-path": "^3.0.0",
     "callsites": "^3.1.0",
     "callsites": "^3.1.0",
     "camel-case": "^4.1.1",
     "camel-case": "^4.1.1",
-    "camelcase": "^5.3.1",
+    "camelcase": "^6.0.0",
     "caniuse-api": "^3.0.0",
     "caniuse-api": "^3.0.0",
-    "caniuse-lite": "^1.0.30001039",
+    "caniuse-lite": "^1.0.30001111",
     "caseless": "^0.12.0",
     "caseless": "^0.12.0",
-    "chalk": "^4.0.0",
-    "chokidar": "^3.3.1",
+    "chalk": "^4.1.0",
+    "chokidar": "^3.4.1",
     "chownr": "^2.0.0",
     "chownr": "^2.0.0",
     "chrome-trace-event": "^1.0.2",
     "chrome-trace-event": "^1.0.2",
     "ci-info": "^2.0.0",
     "ci-info": "^2.0.0",
@@ -106,7 +106,7 @@
     "color-name": "^1.1.4",
     "color-name": "^1.1.4",
     "color-string": "^1.5.3",
     "color-string": "^1.5.3",
     "combined-stream": "^1.0.8",
     "combined-stream": "^1.0.8",
-    "commander": "^5.0.0",
+    "commander": "^6.0.0",
     "commondir": "^1.0.1",
     "commondir": "^1.0.1",
     "component-emitter": "^1.3.0",
     "component-emitter": "^1.3.0",
     "compressible": "^2.0.18",
     "compressible": "^2.0.18",
@@ -114,36 +114,36 @@
     "concat-map": "^0.0.1",
     "concat-map": "^0.0.1",
     "concat-stream": "^2.0.0",
     "concat-stream": "^2.0.0",
     "connect-history-api-fallback": "^1.6.0",
     "connect-history-api-fallback": "^1.6.0",
-    "consola": "^2.11.3",
+    "consola": "^2.15.0",
     "console-browserify": "^1.2.0",
     "console-browserify": "^1.2.0",
     "consolidate": "^0.15.1",
     "consolidate": "^0.15.1",
     "constants-browserify": "^1.0.0",
     "constants-browserify": "^1.0.0",
     "content-disposition": "^0.5.3",
     "content-disposition": "^0.5.3",
     "content-type": "^1.0.4",
     "content-type": "^1.0.4",
     "convert-source-map": "^1.7.0",
     "convert-source-map": "^1.7.0",
-    "cookie": "^0.4.0",
+    "cookie": "^0.4.1",
     "cookie-signature": "^1.1.0",
     "cookie-signature": "^1.1.0",
     "copy-concurrently": "^1.0.5",
     "copy-concurrently": "^1.0.5",
     "copy-descriptor": "^0.1.1",
     "copy-descriptor": "^0.1.1",
-    "copy-webpack-plugin": "^5.1.1",
-    "core-js": "^3.6.4",
+    "copy-webpack-plugin": "^6.0.3",
+    "core-js": "^3.6.5",
     "core-util-is": "^1.0.2",
     "core-util-is": "^1.0.2",
-    "cosmiconfig": "^6.0.0",
-    "create-ecdh": "^4.0.3",
+    "cosmiconfig": "^7.0.0",
+    "create-ecdh": "^4.0.4",
     "create-hash": "^1.2.0",
     "create-hash": "^1.2.0",
     "create-hmac": "^1.1.7",
     "create-hmac": "^1.1.7",
-    "cross-spawn": "^7.0.2",
+    "cross-spawn": "^7.0.3",
     "crypto-browserify": "^3.12.0",
     "crypto-browserify": "^3.12.0",
-    "css": "^2.2.4",
+    "css": "^3.0.0",
     "css-color-names": "^1.0.1",
     "css-color-names": "^1.0.1",
     "css-declaration-sorter": "^5.1.2",
     "css-declaration-sorter": "^5.1.2",
-    "css-loader": "^3.5.0",
+    "css-loader": "^4.2.0",
     "css-parse": "^2.0.0",
     "css-parse": "^2.0.0",
     "css-select": "^2.1.0",
     "css-select": "^2.1.0",
     "css-select-base-adapter": "^0.1.1",
     "css-select-base-adapter": "^0.1.1",
     "css-tree": "^1.0.0-alpha.39",
     "css-tree": "^1.0.0-alpha.39",
-    "css-unit-converter": "^1.1.1",
-    "css-what": "^3.2.1",
+    "css-unit-converter": "^1.1.2",
+    "css-what": "^3.3.0",
     "cssesc": "^3.0.0",
     "cssesc": "^3.0.0",
     "cssnano": "^4.1.10",
     "cssnano": "^4.1.10",
     "cssnano-preset-default": "^4.0.7",
     "cssnano-preset-default": "^4.0.7",
@@ -158,9 +158,9 @@
     "debug": "^4.1.1",
     "debug": "^4.1.1",
     "decamelize": "^4.0.0",
     "decamelize": "^4.0.0",
     "decode-uri-component": "^0.2.0",
     "decode-uri-component": "^0.2.0",
-    "deep-equal": "^2.0.2",
+    "deep-equal": "^2.0.3",
     "deepmerge": "^4.2.2",
     "deepmerge": "^4.2.2",
-    "default-gateway": "^6.0.0",
+    "default-gateway": "^6.0.1",
     "define-properties": "^1.1.3",
     "define-properties": "^1.1.3",
     "define-property": "^2.0.2",
     "define-property": "^2.0.2",
     "del": "^5.1.0",
     "del": "^5.1.0",
@@ -178,52 +178,52 @@
     "dns-txt": "^2.0.2",
     "dns-txt": "^2.0.2",
     "docsearch.js": "^2.6.3",
     "docsearch.js": "^2.6.3",
     "dom-converter": "^0.2.0",
     "dom-converter": "^0.2.0",
-    "dom-serializer": "^0.2.2",
+    "dom-serializer": "^1.0.1",
     "dom-walk": "^0.1.2",
     "dom-walk": "^0.1.2",
-    "domain-browser": "^4.0.0",
+    "domain-browser": "^4.16.0",
     "domelementtype": "^2.0.1",
     "domelementtype": "^2.0.1",
     "domhandler": "^3.0.0",
     "domhandler": "^3.0.0",
-    "domutils": "^2.0.0",
+    "domutils": "^2.1.0",
     "dot-prop": "^5.2.0",
     "dot-prop": "^5.2.0",
     "duplexify": "^4.1.1",
     "duplexify": "^4.1.1",
     "ecc-jsbn": "^0.2.0",
     "ecc-jsbn": "^0.2.0",
     "ee-first": "^1.1.1",
     "ee-first": "^1.1.1",
-    "electron-to-chromium": "^1.3.397",
-    "elliptic": "^6.5.2",
-    "emoji-regex": "^8.0.0",
+    "electron-to-chromium": "^1.3.522",
+    "elliptic": "^6.5.3",
+    "emoji-regex": "^9.0.0",
     "emojis-list": "^3.0.0",
     "emojis-list": "^3.0.0",
     "encodeurl": "^1.0.2",
     "encodeurl": "^1.0.2",
     "end-of-stream": "^1.4.4",
     "end-of-stream": "^1.4.4",
-    "enhanced-resolve": "^4.1.1",
-    "entities": "^2.0.0",
+    "enhanced-resolve": "^4.3.0",
+    "entities": "^2.0.3",
     "envify": "^4.1.0",
     "envify": "^4.1.0",
-    "envinfo": "^7.5.0",
+    "envinfo": "^7.7.2",
     "errno": "^0.1.7",
     "errno": "^0.1.7",
     "error-ex": "^1.3.2",
     "error-ex": "^1.3.2",
-    "es-abstract": "^1.17.5",
+    "es-abstract": "^1.17.6",
     "es-to-primitive": "^1.2.1",
     "es-to-primitive": "^1.2.1",
     "es6-promise": "^4.2.8",
     "es6-promise": "^4.2.8",
     "escape-html": "^1.0.3",
     "escape-html": "^1.0.3",
-    "escape-string-regexp": "^2.0.0",
-    "eslint-scope": "^5.0.0",
+    "escape-string-regexp": "^4.0.0",
+    "eslint-scope": "^5.1.0",
     "esprima": "^4.0.1",
     "esprima": "^4.0.1",
     "esrecurse": "^4.2.1",
     "esrecurse": "^4.2.1",
-    "estraverse": "^5.0.0",
+    "estraverse": "^5.2.0",
     "esutils": "^2.0.3",
     "esutils": "^2.0.3",
     "etag": "^1.8.1",
     "etag": "^1.8.1",
-    "eventemitter3": "^4.0.0",
-    "events": "^3.1.0",
+    "eventemitter3": "^4.0.4",
+    "events": "^3.2.0",
     "eventsource": "^1.0.7",
     "eventsource": "^1.0.7",
     "evp_bytestokey": "^1.0.3",
     "evp_bytestokey": "^1.0.3",
-    "execa": "^4.0.0",
+    "execa": "^4.0.3",
     "expand-brackets": "^4.0.0",
     "expand-brackets": "^4.0.0",
     "express": "^4.17.1",
     "express": "^4.17.1",
     "extend": "^3.0.2",
     "extend": "^3.0.2",
     "extend-shallow": "^3.0.2",
     "extend-shallow": "^3.0.2",
     "extglob": "^3.0.0",
     "extglob": "^3.0.0",
     "extsprintf": "^1.4.0",
     "extsprintf": "^1.4.0",
-    "fast-deep-equal": "^3.1.1",
-    "fast-glob": "^3.2.2",
+    "fast-deep-equal": "^3.1.3",
+    "fast-glob": "^3.2.4",
     "fast-json-stable-stringify": "^2.1.0",
     "fast-json-stable-stringify": "^2.1.0",
     "faye-websocket": "^0.11.3",
     "faye-websocket": "^0.11.3",
     "figgy-pudding": "^3.5.2",
     "figgy-pudding": "^3.5.2",
@@ -235,7 +235,7 @@
     "find-cache-dir": "^3.3.1",
     "find-cache-dir": "^3.3.1",
     "find-up": "^4.1.0",
     "find-up": "^4.1.0",
     "flush-write-stream": "^2.0.0",
     "flush-write-stream": "^2.0.0",
-    "follow-redirects": "^1.11.0",
+    "follow-redirects": "^1.12.1",
     "for-in": "^1.0.2",
     "for-in": "^1.0.2",
     "foreach": "^2.0.5",
     "foreach": "^2.0.5",
     "forever-agent": "^0.6.1",
     "forever-agent": "^0.6.1",
@@ -244,7 +244,7 @@
     "fragment-cache": "^0.2.1",
     "fragment-cache": "^0.2.1",
     "fresh": "^0.5.2",
     "fresh": "^0.5.2",
     "from2": "^2.3.0",
     "from2": "^2.3.0",
-    "fs-extra": "^9.0.0",
+    "fs-extra": "^9.0.1",
     "fs-write-stream-atomic": "^1.0.10",
     "fs-write-stream-atomic": "^1.0.10",
     "fs.realpath": "^1.0.0",
     "fs.realpath": "^1.0.0",
     "function-bind": "^1.1.1",
     "function-bind": "^1.1.1",
@@ -257,21 +257,21 @@
     "glob-parent": "^5.1.1",
     "glob-parent": "^5.1.1",
     "glob-to-regexp": "^0.4.1",
     "glob-to-regexp": "^0.4.1",
     "global": "^4.4.0",
     "global": "^4.4.0",
-    "globals": "^12.4.0",
-    "globby": "^11.0.0",
+    "globals": "^13.1.0",
+    "globby": "^11.0.1",
     "good-listener": "^1.2.2",
     "good-listener": "^1.2.2",
-    "graceful-fs": "^4.2.3",
+    "graceful-fs": "^4.2.4",
     "gray-matter": "^4.0.2",
     "gray-matter": "^4.0.2",
     "handle-thing": "^2.0.1",
     "handle-thing": "^2.0.1",
     "har-schema": "^2.0.0",
     "har-schema": "^2.0.0",
-    "har-validator": "^5.1.3",
+    "har-validator": "^5.1.5",
     "has": "^1.0.3",
     "has": "^1.0.3",
     "has-ansi": "^4.0.0",
     "has-ansi": "^4.0.0",
     "has-flag": "^4.0.0",
     "has-flag": "^4.0.0",
     "has-symbols": "^1.0.1",
     "has-symbols": "^1.0.1",
     "has-value": "^2.0.2",
     "has-value": "^2.0.2",
     "has-values": "^2.0.1",
     "has-values": "^2.0.1",
-    "hash-base": "^3.0.4",
+    "hash-base": "^3.1.0",
     "hash-sum": "^2.0.0",
     "hash-sum": "^2.0.0",
     "hash.js": "^1.1.7",
     "hash.js": "^1.1.7",
     "he": "^1.2.0",
     "he": "^1.2.0",
@@ -282,24 +282,24 @@
     "hsl-regex": "^1.0.0",
     "hsl-regex": "^1.0.0",
     "hsla-regex": "^1.0.0",
     "hsla-regex": "^1.0.0",
     "html-comment-regex": "^1.1.2",
     "html-comment-regex": "^1.1.2",
-    "html-entities": "^1.2.1",
+    "html-entities": "^1.3.1",
     "html-minifier": "^4.0.0",
     "html-minifier": "^4.0.0",
     "html-tags": "^3.1.0",
     "html-tags": "^3.1.0",
     "htmlparser2": "^4.1.0",
     "htmlparser2": "^4.1.0",
     "http-deceiver": "^1.2.7",
     "http-deceiver": "^1.2.7",
-    "http-errors": "^1.7.3",
+    "http-errors": "^1.8.0",
     "http-parser-js": "^0.5.2",
     "http-parser-js": "^0.5.2",
-    "http-proxy": "^1.18.0",
-    "http-proxy-middleware": "^1.0.3",
+    "http-proxy": "^1.18.1",
+    "http-proxy-middleware": "^1.0.5",
     "http-signature": "^1.3.4",
     "http-signature": "^1.3.4",
     "https-browserify": "^1.0.0",
     "https-browserify": "^1.0.0",
-    "iconv-lite": "^0.5.1",
+    "iconv-lite": "^0.6.2",
     "icss-replace-symbols": "^1.1.0",
     "icss-replace-symbols": "^1.1.0",
     "icss-utils": "^4.1.1",
     "icss-utils": "^4.1.1",
     "ieee754": "^1.1.13",
     "ieee754": "^1.1.13",
     "iferr": "^1.0.2",
     "iferr": "^1.0.2",
-    "ignore": "^5.1.4",
-    "immediate": "^3.2.3",
+    "ignore": "^5.1.8",
+    "immediate": "^3.3.0",
     "import-cwd": "^3.0.0",
     "import-cwd": "^3.0.0",
     "import-fresh": "^3.2.1",
     "import-fresh": "^3.2.1",
     "import-from": "^3.0.0",
     "import-from": "^3.0.0",
@@ -309,9 +309,9 @@
     "infer-owner": "^1.0.4",
     "infer-owner": "^1.0.4",
     "inflight": "^1.0.6",
     "inflight": "^1.0.6",
     "inherits": "^2.0.4",
     "inherits": "^2.0.4",
-    "internal-ip": "^6.0.0",
+    "internal-ip": "^6.1.0",
     "invariant": "^2.2.4",
     "invariant": "^2.2.4",
-    "invert-kv": "^3.0.0",
+    "invert-kv": "^3.0.1",
     "ip": "^1.1.5",
     "ip": "^1.1.5",
     "ip-regex": "^4.1.0",
     "ip-regex": "^4.1.0",
     "ipaddr.js": "^1.9.1",
     "ipaddr.js": "^1.9.1",
@@ -321,7 +321,7 @@
     "is-arrayish": "^0.3.2",
     "is-arrayish": "^0.3.2",
     "is-binary-path": "^2.1.0",
     "is-binary-path": "^2.1.0",
     "is-buffer": "^2.0.4",
     "is-buffer": "^2.0.4",
-    "is-callable": "^1.1.5",
+    "is-callable": "^1.2.0",
     "is-color-stop": "^1.1.0",
     "is-color-stop": "^1.1.0",
     "is-data-descriptor": "^2.0.0",
     "is-data-descriptor": "^2.0.0",
     "is-date-object": "^1.0.2",
     "is-date-object": "^1.0.2",
@@ -337,25 +337,25 @@
     "is-path-in-cwd": "^3.0.0",
     "is-path-in-cwd": "^3.0.0",
     "is-path-inside": "^3.0.2",
     "is-path-inside": "^3.0.2",
     "is-plain-obj": "^2.1.0",
     "is-plain-obj": "^2.1.0",
-    "is-plain-object": "^3.0.0",
-    "is-regex": "^1.0.5",
+    "is-plain-object": "^4.1.1",
+    "is-regex": "^1.1.1",
     "is-resolvable": "^1.1.0",
     "is-resolvable": "^1.1.0",
     "is-stream": "^2.0.0",
     "is-stream": "^2.0.0",
     "is-svg": "^4.2.1",
     "is-svg": "^4.2.1",
     "is-symbol": "^1.0.3",
     "is-symbol": "^1.0.3",
     "is-typedarray": "^1.0.0",
     "is-typedarray": "^1.0.0",
     "is-windows": "^1.0.2",
     "is-windows": "^1.0.2",
-    "is-wsl": "^2.1.1",
+    "is-wsl": "^2.2.0",
     "isarray": "^2.0.5",
     "isarray": "^2.0.5",
     "isexe": "^2.0.0",
     "isexe": "^2.0.0",
     "isobject": "^4.0.0",
     "isobject": "^4.0.0",
     "isstream": "^0.1.2",
     "isstream": "^0.1.2",
     "javascript-stringify": "^2.0.1",
     "javascript-stringify": "^2.0.1",
     "js-levenshtein": "^1.1.6",
     "js-levenshtein": "^1.1.6",
-    "js-tokens": "^5.0.0",
-    "js-yaml": "^3.13.1",
+    "js-tokens": "^6.0.0",
+    "js-yaml": "^3.14.0",
     "jsbn": "^1.1.0",
     "jsbn": "^1.1.0",
-    "jsesc": "^2.5.2",
+    "jsesc": "^3.0.1",
     "json-parse-better-errors": "^1.0.2",
     "json-parse-better-errors": "^1.0.2",
     "json-schema": "^0.2.5",
     "json-schema": "^0.2.5",
     "json-schema-traverse": "^0.4.1",
     "json-schema-traverse": "^0.4.1",
@@ -368,12 +368,12 @@
     "kind-of": "^6.0.3",
     "kind-of": "^6.0.3",
     "last-call-webpack-plugin": "^3.0.0",
     "last-call-webpack-plugin": "^3.0.0",
     "lcid": "^3.1.1",
     "lcid": "^3.1.1",
-    "linkify-it": "^2.2.0",
+    "linkify-it": "^3.0.2",
     "load-script": "^1.0.0",
     "load-script": "^1.0.0",
-    "loader-runner": "^3.1.0",
+    "loader-runner": "^4.0.0",
     "loader-utils": "^2.0.0",
     "loader-utils": "^2.0.0",
     "locate-path": "^5.0.0",
     "locate-path": "^5.0.0",
-    "lodash": "^4.17.15",
+    "lodash": "^4.17.19",
     "lodash._reinterpolate": "^3.0.0",
     "lodash._reinterpolate": "^3.0.0",
     "lodash.chunk": "^4.2.0",
     "lodash.chunk": "^4.2.0",
     "lodash.clonedeep": "^4.5.0",
     "lodash.clonedeep": "^4.5.0",
@@ -385,37 +385,37 @@
     "lodash.template": "^4.5.0",
     "lodash.template": "^4.5.0",
     "lodash.templatesettings": "^4.2.0",
     "lodash.templatesettings": "^4.2.0",
     "lodash.uniq": "^4.5.0",
     "lodash.uniq": "^4.5.0",
-    "loglevel": "^1.6.7",
+    "loglevel": "^1.6.8",
     "loose-envify": "^1.4.0",
     "loose-envify": "^1.4.0",
     "lower-case": "^2.0.1",
     "lower-case": "^2.0.1",
-    "lru-cache": "^5.1.1",
-    "make-dir": "^3.0.2",
+    "lru-cache": "^6.0.0",
+    "make-dir": "^3.1.0",
     "mamacro": "^0.0.7",
     "mamacro": "^0.0.7",
     "map-age-cleaner": "^0.1.3",
     "map-age-cleaner": "^0.1.3",
     "map-cache": "^0.2.2",
     "map-cache": "^0.2.2",
     "map-visit": "^1.0.0",
     "map-visit": "^1.0.0",
-    "markdown-it": "^10.0.0",
-    "markdown-it-anchor": "^5.2.7",
+    "markdown-it": "^11.0.0",
+    "markdown-it-anchor": "^5.3.0",
     "markdown-it-chain": "^1.3.0",
     "markdown-it-chain": "^1.3.0",
-    "markdown-it-container": "^2.0.0",
+    "markdown-it-container": "^3.0.0",
     "markdown-it-emoji": "^1.4.0",
     "markdown-it-emoji": "^1.4.0",
     "markdown-it-table-of-contents": "^0.4.4",
     "markdown-it-table-of-contents": "^0.4.4",
     "md5.js": "^1.3.5",
     "md5.js": "^1.3.5",
-    "mdn-data": "^2.0.8",
+    "mdn-data": "^2.0.11",
     "mdurl": "^1.0.1",
     "mdurl": "^1.0.1",
     "media-typer": "^1.1.0",
     "media-typer": "^1.1.0",
-    "mem": "^6.0.1",
+    "mem": "^6.1.0",
     "memory-fs": "^0.5.0",
     "memory-fs": "^0.5.0",
     "merge-descriptors": "^1.0.1",
     "merge-descriptors": "^1.0.1",
     "merge-source-map": "^1.1.0",
     "merge-source-map": "^1.1.0",
-    "merge2": "^1.3.0",
+    "merge2": "^1.4.1",
     "methods": "^1.1.2",
     "methods": "^1.1.2",
     "micromatch": "^4.0.2",
     "micromatch": "^4.0.2",
     "miller-rabin": "^4.0.1",
     "miller-rabin": "^4.0.1",
-    "mime": "^2.4.4",
-    "mime-db": "^1.43.0",
-    "mime-types": "^2.1.26",
-    "mimic-fn": "^3.0.0",
+    "mime": "^2.4.6",
+    "mime-db": "^1.44.0",
+    "mime-types": "^2.1.27",
+    "mimic-fn": "^3.1.0",
     "min-document": "^2.19.0",
     "min-document": "^2.19.0",
     "mini-css-extract-plugin": "^0.9.0",
     "mini-css-extract-plugin": "^0.9.0",
     "minimalistic-assert": "^1.0.1",
     "minimalistic-assert": "^1.0.1",
@@ -431,16 +431,16 @@
     "multicast-dns-service-types": "^1.1.0",
     "multicast-dns-service-types": "^1.1.0",
     "nanomatch": "^1.2.13",
     "nanomatch": "^1.2.13",
     "negotiator": "^0.6.2",
     "negotiator": "^0.6.2",
-    "neo-async": "^2.6.1",
+    "neo-async": "^2.6.2",
     "nice-try": "^2.0.1",
     "nice-try": "^2.0.1",
     "no-case": "^3.0.3",
     "no-case": "^3.0.3",
     "node-forge": "^0.9.1",
     "node-forge": "^0.9.1",
     "node-libs-browser": "^2.2.1",
     "node-libs-browser": "^2.2.1",
-    "node-releases": "^1.1.53",
+    "node-releases": "^1.1.60",
     "nopt": "^4.0.3",
     "nopt": "^4.0.3",
     "normalize-path": "^3.0.0",
     "normalize-path": "^3.0.0",
     "normalize-range": "^0.1.2",
     "normalize-range": "^0.1.2",
-    "normalize-url": "^5.0.0",
+    "normalize-url": "^5.1.0",
     "npm-run-path": "^4.0.1",
     "npm-run-path": "^4.0.1",
     "nprogress": "^0.2.0",
     "nprogress": "^0.2.0",
     "nth-check": "^1.0.2",
     "nth-check": "^1.0.2",
@@ -449,8 +449,8 @@
     "oauth-sign": "^0.9.0",
     "oauth-sign": "^0.9.0",
     "object-assign": "^4.1.1",
     "object-assign": "^4.1.1",
     "object-copy": "^1.0.0",
     "object-copy": "^1.0.0",
-    "object-inspect": "^1.7.0",
-    "object-is": "^1.0.2",
+    "object-inspect": "^1.8.0",
+    "object-is": "^1.1.2",
     "object-keys": "^1.1.1",
     "object-keys": "^1.1.1",
     "object-visit": "^1.0.1",
     "object-visit": "^1.0.1",
     "object.assign": "^4.1.0",
     "object.assign": "^4.1.0",
@@ -461,7 +461,7 @@
     "on-finished": "^2.3.0",
     "on-finished": "^2.3.0",
     "on-headers": "^1.0.2",
     "on-headers": "^1.0.2",
     "once": "^1.4.0",
     "once": "^1.4.0",
-    "opencollective-postinstall": "^2.0.2",
+    "opencollective-postinstall": "^2.0.3",
     "opn": "^6.0.0",
     "opn": "^6.0.0",
     "optimize-css-assets-webpack-plugin": "^5.0.3",
     "optimize-css-assets-webpack-plugin": "^5.0.3",
     "original": "^1.0.2",
     "original": "^1.0.2",
@@ -470,7 +470,7 @@
     "p-defer": "^3.0.0",
     "p-defer": "^3.0.0",
     "p-finally": "^2.0.1",
     "p-finally": "^2.0.1",
     "p-is-promise": "^3.0.0",
     "p-is-promise": "^3.0.0",
-    "p-limit": "^2.3.0",
+    "p-limit": "^3.0.2",
     "p-locate": "^4.1.0",
     "p-locate": "^4.1.0",
     "p-map": "^4.0.0",
     "p-map": "^4.0.0",
     "p-retry": "^4.2.0",
     "p-retry": "^4.2.0",
@@ -479,7 +479,7 @@
     "parallel-transform": "^1.2.0",
     "parallel-transform": "^1.2.0",
     "param-case": "^3.0.3",
     "param-case": "^3.0.3",
     "parse-asn1": "^5.1.5",
     "parse-asn1": "^5.1.5",
-    "parse-json": "^5.0.0",
+    "parse-json": "^5.0.1",
     "parseurl": "^1.3.3",
     "parseurl": "^1.3.3",
     "pascalcase": "^1.0.0",
     "pascalcase": "^1.0.0",
     "path-browserify": "^1.0.1",
     "path-browserify": "^1.0.1",
@@ -491,16 +491,16 @@
     "path-parse": "^1.0.6",
     "path-parse": "^1.0.6",
     "path-to-regexp": "^6.1.0",
     "path-to-regexp": "^6.1.0",
     "path-type": "^4.0.0",
     "path-type": "^4.0.0",
-    "pbkdf2": "^3.0.17",
+    "pbkdf2": "^3.1.1",
     "performance-now": "^2.1.0",
     "performance-now": "^2.1.0",
     "pify": "^5.0.0",
     "pify": "^5.0.0",
     "pinkie": "^2.0.4",
     "pinkie": "^2.0.4",
     "pinkie-promise": "^2.0.1",
     "pinkie-promise": "^2.0.1",
     "pkg-dir": "^4.2.0",
     "pkg-dir": "^4.2.0",
     "pkg-up": "^3.1.0",
     "pkg-up": "^3.1.0",
-    "portfinder": "^1.0.25",
+    "portfinder": "^1.0.28",
     "posix-character-classes": "^1.0.0",
     "posix-character-classes": "^1.0.0",
-    "postcss": "^7.0.27",
+    "postcss": "^7.0.32",
     "postcss-calc": "^7.0.2",
     "postcss-calc": "^7.0.2",
     "postcss-colormin": "^4.0.3",
     "postcss-colormin": "^4.0.3",
     "postcss-convert-values": "^4.0.1",
     "postcss-convert-values": "^4.0.1",
@@ -517,7 +517,7 @@
     "postcss-minify-params": "^4.0.2",
     "postcss-minify-params": "^4.0.2",
     "postcss-minify-selectors": "^4.0.2",
     "postcss-minify-selectors": "^4.0.2",
     "postcss-modules-extract-imports": "^2.0.0",
     "postcss-modules-extract-imports": "^2.0.0",
-    "postcss-modules-local-by-default": "^3.0.2",
+    "postcss-modules-local-by-default": "^3.0.3",
     "postcss-modules-scope": "^2.2.0",
     "postcss-modules-scope": "^2.2.0",
     "postcss-modules-values": "^3.0.0",
     "postcss-modules-values": "^3.0.0",
     "postcss-normalize-charset": "^4.0.1",
     "postcss-normalize-charset": "^4.0.1",
@@ -536,9 +536,9 @@
     "postcss-selector-parser": "^6.0.2",
     "postcss-selector-parser": "^6.0.2",
     "postcss-svgo": "^4.0.2",
     "postcss-svgo": "^4.0.2",
     "postcss-unique-selectors": "^4.0.1",
     "postcss-unique-selectors": "^4.0.1",
-    "postcss-value-parser": "^4.0.3",
+    "postcss-value-parser": "^4.1.0",
     "prepend-http": "^3.0.1",
     "prepend-http": "^3.0.1",
-    "prettier": "^2.0.4",
+    "prettier": "^2.0.5",
     "pretty-error": "^2.1.1",
     "pretty-error": "^2.1.1",
     "pretty-time": "^1.1.0",
     "pretty-time": "^1.1.0",
     "prismjs": "^1.20.0",
     "prismjs": "^1.20.0",
@@ -555,8 +555,8 @@
     "pumpify": "^2.0.1",
     "pumpify": "^2.0.1",
     "punycode": "^2.1.1",
     "punycode": "^2.1.1",
     "q": "^1.5.1",
     "q": "^1.5.1",
-    "qs": "^6.9.3",
-    "query-string": "^6.12.0",
+    "qs": "^6.9.4",
+    "query-string": "^6.13.1",
     "querystring": "^0.2.0",
     "querystring": "^0.2.0",
     "querystring-es3": "^0.2.1",
     "querystring-es3": "^0.2.1",
     "querystringify": "^2.1.1",
     "querystringify": "^2.1.1",
@@ -567,14 +567,14 @@
     "readable-stream": "^3.6.0",
     "readable-stream": "^3.6.0",
     "readdirp": "^3.4.0",
     "readdirp": "^3.4.0",
     "reduce": "^1.0.2",
     "reduce": "^1.0.2",
-    "regenerate": "^1.4.0",
+    "regenerate": "^1.4.1",
     "regenerate-unicode-properties": "^8.2.0",
     "regenerate-unicode-properties": "^8.2.0",
-    "regenerator-runtime": "^0.13.5",
-    "regenerator-transform": "^0.14.4",
+    "regenerator-runtime": "^0.13.7",
+    "regenerator-transform": "^0.14.5",
     "regex-not": "^1.0.2",
     "regex-not": "^1.0.2",
     "regexp.prototype.flags": "^1.3.0",
     "regexp.prototype.flags": "^1.3.0",
     "regexpu-core": "^4.7.0",
     "regexpu-core": "^4.7.0",
-    "regjsgen": "^0.5.1",
+    "regjsgen": "^0.5.2",
     "regjsparser": "^0.6.4",
     "regjsparser": "^0.6.4",
     "relateurl": "^0.2.7",
     "relateurl": "^0.2.7",
     "remove-trailing-separator": "^1.1.0",
     "remove-trailing-separator": "^1.1.0",
@@ -586,7 +586,7 @@
     "require-main-filename": "^2.0.0",
     "require-main-filename": "^2.0.0",
     "requires-port": "^1.0.0",
     "requires-port": "^1.0.0",
     "reselect": "^4.0.0",
     "reselect": "^4.0.0",
-    "resolve": "^1.15.1",
+    "resolve": "^1.17.0",
     "resolve-cwd": "^3.0.0",
     "resolve-cwd": "^3.0.0",
     "resolve-from": "^5.0.0",
     "resolve-from": "^5.0.0",
     "resolve-url": "^0.2.1",
     "resolve-url": "^0.2.1",
@@ -597,18 +597,18 @@
     "rimraf": "^3.0.2",
     "rimraf": "^3.0.2",
     "ripemd160": "^2.0.2",
     "ripemd160": "^2.0.2",
     "run-queue": "^2.0.1",
     "run-queue": "^2.0.1",
-    "safe-buffer": "^5.2.0",
+    "safe-buffer": "^5.2.1",
     "safe-regex": "^2.1.1",
     "safe-regex": "^2.1.1",
     "safer-buffer": "^2.1.2",
     "safer-buffer": "^2.1.2",
     "sax": "^1.2.4",
     "sax": "^1.2.4",
-    "schema-utils": "^2.6.5",
+    "schema-utils": "^2.7.0",
     "section-matter": "^1.0.0",
     "section-matter": "^1.0.0",
     "select": "^1.1.2",
     "select": "^1.1.2",
     "select-hose": "^2.0.0",
     "select-hose": "^2.0.0",
     "selfsigned": "^1.10.7",
     "selfsigned": "^1.10.7",
-    "semver": "^7.2.1",
+    "semver": "^7.3.2",
     "send": "^0.17.1",
     "send": "^0.17.1",
-    "serialize-javascript": "^3.0.0",
+    "serialize-javascript": "^4.0.0",
     "serve-index": "^1.9.1",
     "serve-index": "^1.9.1",
     "serve-static": "^1.14.1",
     "serve-static": "^1.14.1",
     "set-blocking": "^2.0.0",
     "set-blocking": "^2.0.0",
@@ -620,19 +620,19 @@
     "shebang-regex": "^3.0.0",
     "shebang-regex": "^3.0.0",
     "signal-exit": "^3.0.3",
     "signal-exit": "^3.0.3",
     "simple-swizzle": "^0.2.2",
     "simple-swizzle": "^0.2.2",
-    "sitemap": "^6.1.0",
+    "sitemap": "^6.2.0",
     "slash": "^3.0.0",
     "slash": "^3.0.0",
     "smoothscroll-polyfill": "^0.4.4",
     "smoothscroll-polyfill": "^0.4.4",
     "snapdragon": "^0.12.0",
     "snapdragon": "^0.12.0",
     "snapdragon-node": "^3.0.0",
     "snapdragon-node": "^3.0.0",
     "snapdragon-util": "^5.0.1",
     "snapdragon-util": "^5.0.1",
-    "sockjs": "^0.3.20",
-    "sockjs-client": "^1.4.0",
+    "sockjs": "^0.3.21",
+    "sockjs-client": "^1.5.0",
     "sort-keys": "^4.0.0",
     "sort-keys": "^4.0.0",
     "source-list-map": "^2.0.1",
     "source-list-map": "^2.0.1",
     "source-map": "^0.7.3",
     "source-map": "^0.7.3",
     "source-map-resolve": "^0.6.0",
     "source-map-resolve": "^0.6.0",
-    "source-map-support": "^0.5.16",
+    "source-map-support": "^0.5.19",
     "source-map-url": "^0.4.0",
     "source-map-url": "^0.4.0",
     "spdy": "^4.0.2",
     "spdy": "^4.0.2",
     "spdy-transport": "^3.0.0",
     "spdy-transport": "^3.0.0",
@@ -641,13 +641,13 @@
     "sshpk": "^1.16.1",
     "sshpk": "^1.16.1",
     "ssri": "^8.0.0",
     "ssri": "^8.0.0",
     "stable": "^0.1.8",
     "stable": "^0.1.8",
-    "stack-utils": "^2.0.1",
+    "stack-utils": "^2.0.2",
     "static-extend": "^0.1.2",
     "static-extend": "^0.1.2",
-    "statuses": "^1.5.0",
+    "statuses": "^2.0.0",
     "std-env": "^2.2.1",
     "std-env": "^2.2.1",
-    "stream-browserify": "^2.0.2",
+    "stream-browserify": "^3.0.0",
     "stream-each": "^1.2.3",
     "stream-each": "^1.2.3",
-    "stream-http": "^3.1.0",
+    "stream-http": "^3.1.1",
     "stream-shift": "^1.0.1",
     "stream-shift": "^1.0.1",
     "strict-uri-encode": "^2.0.0",
     "strict-uri-encode": "^2.0.0",
     "string-width": "^4.2.0",
     "string-width": "^4.2.0",
@@ -658,17 +658,17 @@
     "strip-bom-string": "^1.0.0",
     "strip-bom-string": "^1.0.0",
     "strip-eof": "^2.0.0",
     "strip-eof": "^2.0.0",
     "stylehacks": "^4.0.3",
     "stylehacks": "^4.0.3",
-    "stylus": "^0.54.7",
+    "stylus": "^0.54.8",
     "stylus-loader": "^3.0.2",
     "stylus-loader": "^3.0.2",
     "supports-color": "^7.1.0",
     "supports-color": "^7.1.0",
     "svg-tags": "^1.0.0",
     "svg-tags": "^1.0.0",
     "svgo": "^1.3.2",
     "svgo": "^1.3.2",
     "tapable": "^1.1.3",
     "tapable": "^1.1.3",
-    "terser": "^4.6.10",
-    "terser-webpack-plugin": "^2.3.5",
+    "terser": "^5.0.0",
+    "terser-webpack-plugin": "^4.0.0",
     "text-table": "^0.2.0",
     "text-table": "^0.2.0",
     "through": "^2.3.8",
     "through": "^2.3.8",
-    "through2": "^3.0.1",
+    "through2": "^4.0.2",
     "thunky": "^1.1.0",
     "thunky": "^1.1.0",
     "timers-browserify": "^2.0.11",
     "timers-browserify": "^2.0.11",
     "timsort": "^0.3.0",
     "timsort": "^0.3.0",
@@ -684,15 +684,15 @@
     "toposort": "^2.0.2",
     "toposort": "^2.0.2",
     "tough-cookie": "^4.0.0",
     "tough-cookie": "^4.0.0",
     "tr46": "^2.0.2",
     "tr46": "^2.0.2",
-    "tslib": "^1.11.1",
+    "tslib": "^2.0.0",
     "tty-browserify": "^0.0.1",
     "tty-browserify": "^0.0.1",
     "tunnel-agent": "^0.6.0",
     "tunnel-agent": "^0.6.0",
     "tweetnacl": "^1.0.3",
     "tweetnacl": "^1.0.3",
-    "type-fest": "^0.13.0",
+    "type-fest": "^0.16.0",
     "type-is": "^1.6.18",
     "type-is": "^1.6.18",
     "typedarray": "^0.0.6",
     "typedarray": "^0.0.6",
     "uc.micro": "^1.0.6",
     "uc.micro": "^1.0.6",
-    "uglify-js": "^3.8.1",
+    "uglify-js": "^3.10.1",
     "unicode-canonical-property-names-ecmascript": "^1.0.4",
     "unicode-canonical-property-names-ecmascript": "^1.0.4",
     "unicode-match-property-ecmascript": "^1.0.4",
     "unicode-match-property-ecmascript": "^1.0.4",
     "unicode-match-property-value-ecmascript": "^1.2.0",
     "unicode-match-property-value-ecmascript": "^1.2.0",
@@ -702,7 +702,7 @@
     "uniqs": "^2.0.0",
     "uniqs": "^2.0.0",
     "unique-filename": "^1.1.1",
     "unique-filename": "^1.1.1",
     "unique-slug": "^2.0.2",
     "unique-slug": "^2.0.2",
-    "universalify": "^1.0.0",
+    "universalify": "^2.0.0",
     "unpipe": "^1.0.0",
     "unpipe": "^1.0.0",
     "unquote": "^1.1.1",
     "unquote": "^1.1.1",
     "unset-value": "^1.0.0",
     "unset-value": "^1.0.0",
@@ -711,60 +711,60 @@
     "uri-js": "^4.2.2",
     "uri-js": "^4.2.2",
     "urix": "^0.1.0",
     "urix": "^0.1.0",
     "url": "^0.11.0",
     "url": "^0.11.0",
-    "url-loader": "^4.0.0",
+    "url-loader": "^4.1.0",
     "url-parse": "^1.4.7",
     "url-parse": "^1.4.7",
     "use": "^3.1.1",
     "use": "^3.1.1",
-    "util": "^0.12.2",
+    "util": "^0.12.3",
     "util-deprecate": "^1.0.2",
     "util-deprecate": "^1.0.2",
     "util.promisify": "^1.0.1",
     "util.promisify": "^1.0.1",
     "utila": "^0.4.0",
     "utila": "^0.4.0",
     "utils-merge": "^1.0.1",
     "utils-merge": "^1.0.1",
-    "uuid": "^7.0.3",
+    "uuid": "^8.3.0",
     "vary": "^1.1.2",
     "vary": "^1.1.2",
     "vendors": "^1.0.4",
     "vendors": "^1.0.4",
     "verror": "^1.10.0",
     "verror": "^1.10.0",
     "vm-browserify": "^1.1.2",
     "vm-browserify": "^1.1.2",
     "vue": "^2.6.11",
     "vue": "^2.6.11",
     "vue-hot-reload-api": "^2.3.4",
     "vue-hot-reload-api": "^2.3.4",
-    "vue-loader": "^15.9.1",
-    "vue-router": "^3.1.6",
+    "vue-loader": "^15.9.3",
+    "vue-router": "^3.4.0",
     "vue-server-renderer": "^2.6.11",
     "vue-server-renderer": "^2.6.11",
     "vue-style-loader": "^4.1.2",
     "vue-style-loader": "^4.1.2",
     "vue-template-compiler": "^2.6.11",
     "vue-template-compiler": "^2.6.11",
     "vue-template-es2015-compiler": "^1.9.1",
     "vue-template-es2015-compiler": "^1.9.1",
-    "vuepress": "^1.4.0",
+    "vuepress": "^1.5.3",
     "vuepress-html-webpack-plugin": "^3.2.0",
     "vuepress-html-webpack-plugin": "^3.2.0",
-    "vuepress-plugin-container": "^2.1.2",
+    "vuepress-plugin-container": "^2.1.4",
     "vuepress-plugin-sitemap": "^2.3.1",
     "vuepress-plugin-sitemap": "^2.3.1",
     "vuepress-plugin-smooth-scroll": "^0.0.9",
     "vuepress-plugin-smooth-scroll": "^0.0.9",
     "vuepress-plugin-zooming": "^1.1.7",
     "vuepress-plugin-zooming": "^1.1.7",
-    "watchpack": "^1.6.1",
+    "watchpack": "^1.7.4",
     "wbuf": "^1.7.3",
     "wbuf": "^1.7.3",
-    "webidl-conversions": "^6.0.0",
-    "webpack": "^4.42.1",
-    "webpack-chain": "^6.4.0",
+    "webidl-conversions": "^6.1.0",
+    "webpack": "^4.44.1",
+    "webpack-chain": "^6.5.1",
     "webpack-dev-middleware": "^3.7.2",
     "webpack-dev-middleware": "^3.7.2",
-    "webpack-dev-server": "^3.10.3",
+    "webpack-dev-server": "^3.11.0",
     "webpack-log": "^3.0.1",
     "webpack-log": "^3.0.1",
-    "webpack-merge": "^4.2.2",
+    "webpack-merge": "^5.1.1",
     "webpack-sources": "^1.4.3",
     "webpack-sources": "^1.4.3",
     "webpackbar": "^4.0.0",
     "webpackbar": "^4.0.0",
-    "websocket-driver": "^0.7.3",
-    "websocket-extensions": "^0.1.3",
-    "whatwg-url": "^8.0.0",
+    "websocket-driver": "^0.7.4",
+    "websocket-extensions": "^0.1.4",
+    "whatwg-url": "^8.1.0",
     "when": "^3.7.8",
     "when": "^3.7.8",
     "which": "^2.0.2",
     "which": "^2.0.2",
     "which-module": "^2.0.0",
     "which-module": "^2.0.0",
     "worker-farm": "^1.7.0",
     "worker-farm": "^1.7.0",
-    "wrap-ansi": "^6.2.0",
+    "wrap-ansi": "^7.0.0",
     "wrappy": "^1.0.2",
     "wrappy": "^1.0.2",
-    "ws": "^7.2.3",
-    "xmlbuilder": "^15.1.0",
+    "ws": "^7.3.1",
+    "xmlbuilder": "^15.1.1",
     "xtend": "^4.0.2",
     "xtend": "^4.0.2",
     "y18n": "^4.0.0",
     "y18n": "^4.0.0",
     "yallist": "^4.0.0",
     "yallist": "^4.0.0",
-    "yargs": "^15.3.1",
-    "yargs-parser": "^18.1.2",
+    "yargs": "^15.4.1",
+    "yargs-parser": "^18.1.3",
     "zepto": "^1.2.0"
     "zepto": "^1.2.0"
   },
   },
   "devDependencies": {},
   "devDependencies": {},

+ 18 - 2
docs/setup/README.md

@@ -23,15 +23,31 @@ Here's an example configuration for `mysql` (or mariadb) that is compatible with
 }
 }
 ```
 ```
 
 
+Alternatively if you would like to use a Sqlite database file:
+
+```json
+{
+  "database": {
+    "engine": "knex-native",
+    "knex": {
+      "client": "sqlite3",
+      "connection": {
+        "filename": "/data/database.sqlite"
+      }
+    }
+  }
+}
+```
+
 Once you've created your configuration file it's easy to mount it in the docker container.
 Once you've created your configuration file it's easy to mount it in the docker container.
 
 
 **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
 affect the login and session management of the application. If these keys change for any reason, all users will be logged out.
 affect the login and session management of the application. If these keys change for any reason, all users will be logged out.
 
 
 
 
-### Database
+### MySQL Database
 
 
-This app doesn't come with a database, you have to provide one yourself. Currently only `mysql/mariadb` is supported for the minimum versions:
+If you opt for the MySQL configuration you will have to provide the database server yourself. You can also use MariaDB. Here are the minimum supported versions:
 
 
 - MySQL v5.7.8+
 - MySQL v5.7.8+
 - MariaDB v10.2.7+
 - MariaDB v10.2.7+

File diff suppressed because it is too large
+ 698 - 630
docs/yarn.lock


+ 2 - 2
frontend/js/app/nginx/proxy/location-item.ejs

@@ -38,7 +38,7 @@
             <div class="col-sm-5 col-md-5">
             <div class="col-sm-5 col-md-5">
                 <div class="form-group">
                 <div class="form-group">
                     <label class="form-label"><%- i18n('proxy-hosts', 'forward-host') %><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_host" class="form-control text-monospace model" placeholder="" value="<%- forward_host %>" autocomplete="off" maxlength="50" required>
+                    <input type="text" name="forward_host" class="form-control text-monospace model" placeholder="" value="<%- forward_host %>" autocomplete="off" maxlength="200" required>
                     <span style="font-size: 9px;"><%- i18n('proxy-hosts', 'custom-forward-host-help') %></span>
                     <span style="font-size: 9px;"><%- i18n('proxy-hosts', 'custom-forward-host-help') %></span>
                 </div>
                 </div>
             </div>
             </div>
@@ -61,4 +61,4 @@
             <i class="fa fa-trash"></i> <%- i18n('locations', 'delete') %>
             <i class="fa fa-trash"></i> <%- i18n('locations', 'delete') %>
         </a>
         </a>
     </div>
     </div>
-</div>    
+</div>    

File diff suppressed because it is too large
+ 360 - 370
frontend/yarn.lock


+ 2 - 1
test/.gitignore

@@ -1,3 +1,4 @@
 .vscode
 .vscode
 node_modules
 node_modules
-
+results
+cypress/videos

+ 1 - 1
test/cypress/Dockerfile

@@ -1,4 +1,4 @@
-FROM cypress/included:4.6.0
+FROM cypress/included:4.12.1
 
 
 COPY --chown=1000 ./test /test
 COPY --chown=1000 ./test /test
 
 

+ 4 - 7
test/cypress/config/ci.json

@@ -1,15 +1,12 @@
 {
 {
 	"requestTimeout": 30000,
 	"requestTimeout": 30000,
 	"defaultCommandTimeout": 20000,
 	"defaultCommandTimeout": 20000,
-	"reporter": "mocha-junit-reporter",
+	"reporter": "cypress-multi-reporters",
 	"reporterOptions": {
 	"reporterOptions": {
-		"jenkinsMode": true,
-		"rootSuiteTitle": "Cypress",
-		"jenkinsClassnamePrefix": "Cypress.",
-		"mochaFile": "/results/junit/my-test-output-[hash].xml"
+		"configFile": "multi-reporter.json"
 	},
 	},
-	"videosFolder": "/results/videos",
-	"screenshotsFolder": "/results/screenshots",
+	"videosFolder": "results/videos",
+	"screenshotsFolder": "results/screenshots",
 	"env": {
 	"env": {
 		"swaggerBase": "{{baseUrl}}/api/schema",
 		"swaggerBase": "{{baseUrl}}/api/schema",
 		"RETRIES": 4
 		"RETRIES": 4

+ 6 - 5
test/cypress/config/dev.json

@@ -1,13 +1,14 @@
 {
 {
 	"requestTimeout": 30000,
 	"requestTimeout": 30000,
 	"defaultCommandTimeout": 20000,
 	"defaultCommandTimeout": 20000,
-	"reporter": "junit",
+	"reporter": "cypress-multi-reporters",
 	"reporterOptions": {
 	"reporterOptions": {
-		"mochaFile": "results/junit/my-test-output-[hash].xml"
+		"configFile": "multi-reporter.json"
 	},
 	},
-	"video": false,
-	"screenshotsFolder": "cypress/results/screenshots",
+	"videos": false,
+	"screenshotsFolder": "results/screenshots",
 	"env": {
 	"env": {
-		"swaggerBase": "{{baseUrl}}/api/schema"
+		"swaggerBase": "{{baseUrl}}/api/schema",
+		"RETRIES": 0
 	}
 	}
 }
 }

+ 1 - 3
test/cypress/integration/Health.spec.js → test/cypress/integration/api/Health.spec.js

@@ -2,17 +2,15 @@
 
 
 describe('Basic API checks', () => {
 describe('Basic API checks', () => {
 	it('Should return a valid health payload', function () {
 	it('Should return a valid health payload', function () {
-		cy.wait(2000);
 		cy.task('backendApiGet', {
 		cy.task('backendApiGet', {
 			path: '/api/',
 			path: '/api/',
 		}).then((data) => {
 		}).then((data) => {
 			// Check the swagger schema:
 			// Check the swagger schema:
-			cy.validateSwaggerSchema('get', '/', data);
+			cy.validateSwaggerSchema('get', 200, '/', data);
 		});
 		});
 	});
 	});
 
 
 	it('Should return a valid schema payload', function () {
 	it('Should return a valid schema payload', function () {
-		cy.wait(2000);
 		cy.task('backendApiGet', {
 		cy.task('backendApiGet', {
 			path: '/api/schema',
 			path: '/api/schema',
 		}).then((data) => {
 		}).then((data) => {

+ 48 - 0
test/cypress/integration/api/Users.spec.js

@@ -0,0 +1,48 @@
+/// <reference types="Cypress" />
+
+describe('Users endpoints', () => {
+	let token;
+
+	before(() => {
+		cy.getToken().then((tok) => {
+			token = tok;
+		});
+	});
+
+	it('Should be able to get yourself', function() {
+		cy.task('backendApiGet', {
+			token: token,
+			path:  '/api/users/me'
+		}).then((data) => {
+			cy.validateSwaggerSchema('get', 200, '/users/{userID}', data);
+			expect(data).to.have.property('id');
+			expect(data.id).to.be.greaterThan(0);
+		});
+	});
+
+	it('Should be able to get all users', function() {
+		cy.task('backendApiGet', {
+			token: token,
+			path:  '/api/users'
+		}).then((data) => {
+			cy.validateSwaggerSchema('get', 200, '/users', data);
+			expect(data.length).to.be.greaterThan(0);
+		});
+	});
+
+	it('Should be able to update yourself', function() {
+		cy.task('backendApiPut', {
+			token: token,
+			path:  '/api/users/me',
+			data:  {
+				name: 'changed name'
+			}
+		}).then((data) => {
+			cy.validateSwaggerSchema('put', 200, '/users/{userID}', data);
+			expect(data).to.have.property('id');
+			expect(data.id).to.be.greaterThan(0);
+			expect(data.name).to.be.equal('changed name');
+		});
+	});
+
+});

+ 10 - 62
test/cypress/support/commands.js

@@ -13,82 +13,30 @@
  * Check the swagger schema:
  * Check the swagger schema:
  *
  *
  * @param {string}  method        API Method in swagger doc, "get", "put", "post", "delete"
  * @param {string}  method        API Method in swagger doc, "get", "put", "post", "delete"
+ * @param {number}  statusCode    API status code in swagger doc
  * @param {string}  path          Swagger doc endpoint path, exactly as defined in swagger doc
  * @param {string}  path          Swagger doc endpoint path, exactly as defined in swagger doc
  * @param {*}       data          The API response data to check against the swagger schema
  * @param {*}       data          The API response data to check against the swagger schema
  */
  */
-Cypress.Commands.add('validateSwaggerSchema', (method, path, data) => {
+Cypress.Commands.add('validateSwaggerSchema', (method, statusCode, path, data) => {
 	cy.task('validateSwaggerSchema', {
 	cy.task('validateSwaggerSchema', {
 		file:           Cypress.env('swaggerBase'),
 		file:           Cypress.env('swaggerBase'),
 		endpoint:       path,
 		endpoint:       path,
 		method:         method,
 		method:         method,
-		statusCode:     200,
+		statusCode:     statusCode,
 		responseSchema: data,
 		responseSchema: data,
 		verbose:        true
 		verbose:        true
 	}).should('equal', null);
 	}).should('equal', null);
 });
 });
 
 
 Cypress.Commands.add('getToken', () => {
 Cypress.Commands.add('getToken', () => {
-	cy.task('backendApiGet', {
-		path: '/api/',
-	}).then((data) => {
-		// Check the swagger schema:
-		cy.task('validateSwaggerSchema', {
-			endpoint:       '/',
-			method:         'get',
-			statusCode:     200,
-			responseSchema: data,
-			verbose:        true,
-		}).should('equal', null);
-
-		if (!data.result.setup) {
-			cy.log('Setup = false');
-			// create a new user
-			cy.createInitialUser().then(() => {
-				return cy.getToken();
-			});
-		} else {
-			cy.log('Setup = true');
-			// login with existing user
-			cy.task('backendApiPost', {
-				path: '/api/tokens',
-				data: {
-					type:     'password',
-					identity: '[email protected]',
-					secret:   'changeme'
-				}
-			}).then(res => {
-				cy.wrap(res.result.token);
-			});
-		}
-	});
-});
-
-Cypress.Commands.add('createInitialUser', () => {
-	return cy.task('backendApiPost', {
-		path: '/api/users',
+	// login with existing user
+	cy.task('backendApiPost', {
+		path: '/api/tokens',
 		data: {
 		data: {
-			name:        'Jamie Curnow',
-			nickname:    'James',
-			email:       '[email protected]',
-			roles:       [],
-			is_disabled: false,
-			auth:        {
-				type:   'password',
-				secret: 'changeme'
-			}
+			identity: '[email protected]',
+			secret:   'changeme'
 		}
 		}
-	}).then((data) => {
-		// Check the swagger schema:
-		cy.task('validateSwaggerSchema', {
-			endpoint:       '/users',
-			method:         'post',
-			statusCode:     201,
-			responseSchema: data,
-			verbose:        true
-		}).should('equal', null);
-
-		expect(data.result).to.have.property('id');
-		expect(data.result.id).to.be.greaterThan(0);
-		cy.wrap(data.result);
+	}).then(res => {
+		cy.wrap(res.token);
 	});
 	});
 });
 });

+ 9 - 0
test/multi-reporter.json

@@ -0,0 +1,9 @@
+{
+	"reporterEnabled": "spec, mocha-junit-reporter",
+	"mochaJunitReporterReporterOptions": {
+		"jenkinsMode": true,
+		"rootSuiteTitle": "Cypress.npm",
+		"jenkinsClassnamePrefix": "Cypress.npm.",
+		"mochaFile": "results/junit/cypress.npm.[hash].xml"
+	}
+}

+ 12 - 10
test/package.json

@@ -4,21 +4,23 @@
 	"description": "",
 	"description": "",
 	"main": "index.js",
 	"main": "index.js",
 	"dependencies": {
 	"dependencies": {
-		"@jc21/cypress-swagger-validation": "^0.0.5",
+		"@jc21/cypress-swagger-validation": "^0.0.9",
 		"@jc21/restler": "^3.4.0",
 		"@jc21/restler": "^3.4.0",
-		"chalk": "^3.0.0",
-		"cypress": "^4.6.0",
+		"chalk": "^4.1.0",
+		"cypress": "^4.12.1",
+		"cypress-multi-reporters": "^1.4.0",
 		"cypress-plugin-retries": "^1.5.2",
 		"cypress-plugin-retries": "^1.5.2",
-		"eslint": "^6.7.2",
+		"eslint": "^7.6.0",
 		"eslint-plugin-align-assignments": "^1.1.2",
 		"eslint-plugin-align-assignments": "^1.1.2",
-		"eslint-plugin-chai-friendly": "^0.5.0",
-		"eslint-plugin-cypress": "^2.8.0",
-		"lodash": "^4.17.15",
-		"mocha": "^6.2.2",
-		"mocha-junit-reporter": "^1.23.1"
+		"eslint-plugin-chai-friendly": "^0.6.0",
+		"eslint-plugin-cypress": "^2.11.1",
+		"lodash": "^4.17.19",
+		"mocha": "^8.1.1",
+		"mocha-junit-reporter": "^2.0.0"
 	},
 	},
 	"scripts": {
 	"scripts": {
-		"cypress": "cypress open --config-file=cypress/config/dev.json --config baseUrl=http://127.0.0.1:3081"
+		"cypress": "cypress open --config-file=cypress/config/dev.json --config baseUrl=${BASE_URL:-http://127.0.0.1:3081}",
+		"cypress:headless": "cypress run --config-file=cypress/config/dev.json --config baseUrl=${BASE_URL:-http://127.0.0.1:3081}"
 	},
 	},
 	"author": "",
 	"author": "",
 	"license": "ISC"
 	"license": "ISC"

File diff suppressed because it is too large
+ 332 - 293
test/yarn.lock


Some files were not shown because too many files changed in this diff