Răsfoiți Sursa

Merge pull request #543 from jc21/develop

v2.4.0
jc21 5 ani în urmă
părinte
comite
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 {
 				// 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
 				sh 'rm -rf test/results'
-				sh 'docker-compose up cypress'
+				sh 'docker-compose up cypress-sqlite'
 				// 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 {
 				always {
 					// Dumps to analyze later
 					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'
 					// Cypress videos and screenshot artifacts
 					dir(path: 'test/results') {
@@ -136,8 +162,9 @@ pipeline {
 			}
 			steps {
 				withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
+					// Docker Login
 					sh "docker login -u '${duser}' -p '${dpass}'"
-					// Buildx with push
+					// Buildx with push from cache
 					sh "./scripts/buildx --push ${BUILDX_PUSH_TAGS}"
 				}
 			}

+ 13 - 3
README.md

@@ -1,7 +1,7 @@
 <p align="center">
 	<img src="https://nginxproxymanager.com/github.png">
 	<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">
 		<img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge">
 	</a>
@@ -155,14 +155,24 @@ Special thanks to the following contributors:
 				<br /><sub><b>OhHeyAlan</b></sub>
 			</a>
 		</td>
-	</tr>
-	<tr>
 		<td align="center">
 			<a href="https://github.com/dogmatic69">
 				<img src="https://avatars2.githubusercontent.com/u/94674?s=460&u=ca7647de53145c6283b6373ade5dc94ba99347db&v=4" width="80px;" alt=""/>
 				<br /><sub><b>Carl Sutton</b></sub>
 			</a>
 		</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>
 </table>
 <!-- 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');
 }
 
-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') {
 	data.version = config.database.version;

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

@@ -22,22 +22,6 @@ exports.up = function (knex/*, Promise*/) {
 	})
 		.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');
 		});
 };
 

+ 4 - 3
backend/models/access_list.js

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

+ 4 - 3
backend/models/access_list_auth.js

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

+ 4 - 3
backend/models/access_list_client.js

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

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

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

+ 4 - 3
backend/models/auth.js

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

+ 5 - 4
backend/models/certificate.js

@@ -4,17 +4,18 @@
 const db    = require('../db');
 const Model = require('objection').Model;
 const User  = require('./user');
+const now   = require('./now_helper');
 
 Model.knex(db);
 
 class Certificate extends Model {
 	$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
 		if (typeof this.expires_on === 'undefined') {
-			this.expires_on = Model.raw('NOW()');
+			this.expires_on = now();
 		}
 
 		// Default for domain_names
@@ -31,7 +32,7 @@ class Certificate extends Model {
 	}
 
 	$beforeUpdate () {
-		this.modified_on = Model.raw('NOW()');
+		this.modified_on = now();
 
 		// Sort domain_names
 		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 User        = require('./user');
 const Certificate = require('./certificate');
+const now         = require('./now_helper');
 
 Model.knex(db);
 
 class DeadHost extends Model {
 	$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
 		if (typeof this.domain_names === 'undefined') {
@@ -27,7 +28,7 @@ class DeadHost extends Model {
 	}
 
 	$beforeUpdate () {
-		this.modified_on = Model.raw('NOW()');
+		this.modified_on = now();
 
 		// Sort domain_names
 		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 AccessList  = require('./access_list');
 const Certificate = require('./certificate');
+const now         = require('./now_helper');
 
 Model.knex(db);
 
 class ProxyHost extends Model {
 	$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
 		if (typeof this.domain_names === 'undefined') {
@@ -28,7 +29,7 @@ class ProxyHost extends Model {
 	}
 
 	$beforeUpdate () {
-		this.modified_on = Model.raw('NOW()');
+		this.modified_on = now();
 
 		// Sort domain_names
 		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 User        = require('./user');
 const Certificate = require('./certificate');
+const now         = require('./now_helper');
 
 Model.knex(db);
 
 class RedirectionHost extends Model {
 	$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
 		if (typeof this.domain_names === 'undefined') {
@@ -27,7 +28,7 @@ class RedirectionHost extends Model {
 	}
 
 	$beforeUpdate () {
-		this.modified_on = Model.raw('NOW()');
+		this.modified_on = now();
 
 		// Sort domain_names
 		if (typeof this.domain_names !== 'undefined') {

+ 4 - 3
backend/models/stream.js

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

+ 4 - 3
backend/models/user.js

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

+ 4 - 3
backend/models/user_permission.js

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

+ 4 - 2
backend/package.json

@@ -12,22 +12,24 @@
 		"config": "^3.3.1",
 		"diskdb": "^0.1.17",
 		"express": "^4.17.1",
-		"express-fileupload": "^1.1.6",
+		"express-fileupload": "^1.1.9",
 		"gravatar": "^1.8.0",
 		"html-entities": "^1.2.1",
 		"json-schema-ref-parser": "^8.0.0",
 		"jsonwebtoken": "^8.5.1",
 		"knex": "^0.20.13",
 		"liquidjs": "^9.11.10",
-		"lodash": "^4.17.15",
+		"lodash": "^4.17.19",
 		"moment": "^2.24.0",
 		"mysql": "^2.18.1",
 		"node-rsa": "^1.0.8",
 		"nodemon": "^2.0.2",
 		"objection": "^2.1.3",
 		"path": "^0.12.7",
+		"pg": "^7.12.1",
 		"restler": "^3.4.0",
 		"signale": "^1.4.0",
+		"sqlite3": "^4.1.1",
 		"temp-write": "^4.0.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 userPermissionModel = require('./models/user_permission');
 const authModel           = require('./models/auth');
+const settingModel        = require('./models/setting');
 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) => {
 		// 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')) {
@@ -27,12 +33,12 @@ module.exports = function () {
 			}
 
 			// Now create the keys and save them in the config.
-			let key = new NodeRSA({b: 2048});
+			let key = new NodeRSA({ b: 2048 });
 			key.generateKeyPair();
 
 			config_data.jwt = {
 				key: key.exportKey('private').toString(),
-				pub: key.exportKey('public').toString()
+				pub: key.exportKey('public').toString(),
 			};
 
 			// Write config
@@ -47,7 +53,6 @@ module.exports = function () {
 					process.exit(0);
 				}
 			});
-
 		} else {
 			// JWT key pair exists
 			if (debug_mode) {
@@ -56,14 +61,20 @@ module.exports = function () {
 
 			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) => {
 			if (!row.count) {
 				// Create a new user and set password
@@ -75,7 +86,7 @@ module.exports = function () {
 					name:       'Administrator',
 					nickname:   'Admin',
 					avatar:     '',
-					roles:      ['admin']
+					roles:      ['admin'],
 				};
 
 				return userModel
@@ -88,28 +99,64 @@ module.exports = function () {
 								user_id: user.id,
 								type:    'password',
 								secret:  'changeme',
-								meta:    {}
+								meta:    {},
 							})
 							.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(() => {
-						logger.info('Initial setup completed');
+						logger.info('Initial admin setup completed');
 					});
 			} else if (debug_mode) {
 				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);
+};

Fișier diff suprimat deoarece este prea mare
+ 313 - 289
backend/yarn.lock


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

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

+ 154 - 154
docs/package.json

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

Fișier diff suprimat deoarece este prea mare
+ 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="form-group">
                     <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>
                 </div>
             </div>
@@ -61,4 +61,4 @@
             <i class="fa fa-trash"></i> <%- i18n('locations', 'delete') %>
         </a>
     </div>
-</div>    
+</div>    

Fișier diff suprimat deoarece este prea mare
+ 360 - 370
frontend/yarn.lock


+ 2 - 1
test/.gitignore

@@ -1,3 +1,4 @@
 .vscode
 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
 

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

@@ -1,15 +1,12 @@
 {
 	"requestTimeout": 30000,
 	"defaultCommandTimeout": 20000,
-	"reporter": "mocha-junit-reporter",
+	"reporter": "cypress-multi-reporters",
 	"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": {
 		"swaggerBase": "{{baseUrl}}/api/schema",
 		"RETRIES": 4

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

@@ -1,13 +1,14 @@
 {
 	"requestTimeout": 30000,
 	"defaultCommandTimeout": 20000,
-	"reporter": "junit",
+	"reporter": "cypress-multi-reporters",
 	"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": {
-		"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', () => {
 	it('Should return a valid health payload', function () {
-		cy.wait(2000);
 		cy.task('backendApiGet', {
 			path: '/api/',
 		}).then((data) => {
 			// Check the swagger schema:
-			cy.validateSwaggerSchema('get', '/', data);
+			cy.validateSwaggerSchema('get', 200, '/', data);
 		});
 	});
 
 	it('Should return a valid schema payload', function () {
-		cy.wait(2000);
 		cy.task('backendApiGet', {
 			path: '/api/schema',
 		}).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:
  *
  * @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 {*}       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', {
 		file:           Cypress.env('swaggerBase'),
 		endpoint:       path,
 		method:         method,
-		statusCode:     200,
+		statusCode:     statusCode,
 		responseSchema: data,
 		verbose:        true
 	}).should('equal', null);
 });
 
 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: {
-			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": "",
 	"main": "index.js",
 	"dependencies": {
-		"@jc21/cypress-swagger-validation": "^0.0.5",
+		"@jc21/cypress-swagger-validation": "^0.0.9",
 		"@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",
-		"eslint": "^6.7.2",
+		"eslint": "^7.6.0",
 		"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": {
-		"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": "",
 	"license": "ISC"

Fișier diff suprimat deoarece este prea mare
+ 332 - 293
test/yarn.lock


Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff