diff --git a/Deprecated/Deployment_Guidance_of_Station_Software.docx b/Deprecated/Deployment_Guidance_of_Station_Software.docx
new file mode 100644
index 0000000000000000000000000000000000000000..d38f00d2fe73b61131a27e159902fd4929ad3912
Binary files /dev/null and b/Deprecated/Deployment_Guidance_of_Station_Software.docx differ
diff --git a/Deprecated/Deployment_Guidance_of_Station_Software_For_Docker_Compose.docx b/Deprecated/Deployment_Guidance_of_Station_Software_For_Docker_Compose.docx
new file mode 100644
index 0000000000000000000000000000000000000000..b6856697d14bdc231fe4c93e147c167676585a3a
Binary files /dev/null and b/Deprecated/Deployment_Guidance_of_Station_Software_For_Docker_Compose.docx differ
diff --git a/Deprecated/StationEnvironmentFiles/.env-aachenbeeck b/Deprecated/StationEnvironmentFiles/.env-aachenbeeck
new file mode 100644
index 0000000000000000000000000000000000000000..e04e0d976b1f160661bda11bc5bf97a75e92776f
--- /dev/null
+++ b/Deprecated/StationEnvironmentFiles/.env-aachenbeeck
@@ -0,0 +1,25 @@
+STATION_ID=aachenbeeck
+MONGO_HOST=pht-mongo
+MONGO_PORT=27017
+MONGO_USER=admin
+MONGO_PASSWORD=admin
+MONGO_DB=pht
+DOCKER_HOST=pht-dind
+DOCKER_PORT=2376
+HARBOR_ADDRESS=menzel.informatik.rwth-aachen.de
+HARBOR_PORT=3007
+HARBOR_USER=aachenbeeck
+HARBOR_PASSWORD=Q4h+@Wq3@k#Q
+HARBOR_CLI=CU146e5g9EFNQxZwGec0UlgWDJox04Tq
+OTHER_HARBOR_CLI=6n757ho8teukkz07ewta9t8570sovcbl
+HARBOR_EMAIL=aachenbeeck@pht.de
+HARBOR_WEBHOOK_SECRET=secret
+CENTRALSERVICE_ADDRESS=menzel.informatik.rwth-aachen.de
+CENTRALSERVICE_PORT=3005
+AUTH_SERVER_ADDRESS=menzel.informatik.rwth-aachen.de
+AUTH_SERVER_PORT=3006
+JWT_SECRET=rwthi5-pht-jwt
+SESSION_SECRET=rwthi5-pht-session
+VAULT_HOST=pht-vault
+VAULT_PORT=8200
+METADATAPROVIDER_ENDPOINT=http://metadataservice:9988
\ No newline at end of file
diff --git a/Deprecated/StationEnvironmentFiles/.env-aachenbruegel b/Deprecated/StationEnvironmentFiles/.env-aachenbruegel
new file mode 100644
index 0000000000000000000000000000000000000000..94a60d9fd8195d19c8a48f616d604f6df3cd58d4
--- /dev/null
+++ b/Deprecated/StationEnvironmentFiles/.env-aachenbruegel
@@ -0,0 +1,24 @@
+STATION_ID=aachenbruegel
+MONGO_HOST=pht-mongo
+MONGO_PORT=27017
+MONGO_USER=admin
+MONGO_PASSWORD=admin
+MONGO_DB=pht
+DOCKER_HOST=pht-dind
+DOCKER_PORT=2376
+HARBOR_ADDRESS=menzel.informatik.rwth-aachen.de
+HARBOR_PORT=3007
+HARBOR_USER=aachenbruegel
+HARBOR_PASSWORD=Vjt9@KJHuvJ+
+HARBOR_CLI=H9SucgDFaI2Pe3lff11d0woHGz9zsWhs
+HARBOR_EMAIL=aachenbruegel@pht.de
+HARBOR_WEBHOOK_SECRET=secret
+CENTRALSERVICE_ADDRESS=menzel.informatik.rwth-aachen.de
+CENTRALSERVICE_PORT=3005
+AUTH_SERVER_ADDRESS=menzel.informatik.rwth-aachen.de
+AUTH_SERVER_PORT=3006
+JWT_SECRET=rwthi5-pht-jwt
+SESSION_SECRET=rwthi5-pht-session
+VAULT_HOST=pht-vault
+VAULT_PORT=8200
+METADATAPROVIDER_ENDPOINT=http://metadataservice:9988
\ No newline at end of file
diff --git a/Deprecated/StationEnvironmentFiles/.env-aachenjannik b/Deprecated/StationEnvironmentFiles/.env-aachenjannik
new file mode 100644
index 0000000000000000000000000000000000000000..79f723ff41a55534206d909b249d32f5926f996b
--- /dev/null
+++ b/Deprecated/StationEnvironmentFiles/.env-aachenjannik
@@ -0,0 +1,24 @@
+STATION_ID=aachenjannik
+MONGO_HOST=pht-mongo
+MONGO_PORT=27017
+MONGO_USER=admin
+MONGO_PASSWORD=admin
+MONGO_DB=pht
+DOCKER_HOST=pht-dind
+DOCKER_PORT=2376
+HARBOR_ADDRESS=menzel.informatik.rwth-aachen.de
+HARBOR_PORT=3007
+HARBOR_USER=aachenjannik
+HARBOR_PASSWORD=H5@+d8aZjfxX
+HARBOR_CLI=c2twGpRMSJMlh3MBYgNlmTRF0NdBYUAw
+HARBOR_EMAIL=aachenjannik@pht.de
+HARBOR_WEBHOOK_SECRET=secret
+CENTRALSERVICE_ADDRESS=menzel.informatik.rwth-aachen.de
+CENTRALSERVICE_PORT=3005
+AUTH_SERVER_ADDRESS=menzel.informatik.rwth-aachen.de
+AUTH_SERVER_PORT=3006
+JWT_SECRET=rwthi5-pht-jwt
+SESSION_SECRET=rwthi5-pht-session
+VAULT_HOST=pht-vault
+VAULT_PORT=8200
+METADATAPROVIDER_ENDPOINT=http://metadataservice:9988
\ No newline at end of file
diff --git a/Deprecated/StationEnvironmentFiles/.env-aachenmenzel b/Deprecated/StationEnvironmentFiles/.env-aachenmenzel
new file mode 100644
index 0000000000000000000000000000000000000000..4019e2422bb9e29e4151fc1cd9289c89444ffd31
--- /dev/null
+++ b/Deprecated/StationEnvironmentFiles/.env-aachenmenzel
@@ -0,0 +1,25 @@
+STATION_ID=aachenmenzel
+MONGO_HOST=pht-mongo
+MONGO_PORT=27017
+MONGO_USER=admin
+MONGO_PASSWORD=admin
+MONGO_DB=pht
+DOCKER_HOST=pht-dind
+DOCKER_PORT=2376
+HARBOR_ADDRESS=menzel.informatik.rwth-aachen.de
+HARBOR_PORT=3007
+HARBOR_USER=aachenmenzel
+HARBOR_PASSWORD=WtR2H+qxM5Lu
+HARBOR_CLI=580wcOykS
+HARBOR_EMAIL=aachenmenzel@pht.de
+HARBOR_WEBHOOK_SECRET=secret
+CENTRALSERVICE_ADDRESS=menzel.informatik.rwth-aachen.de
+CENTRALSERVICE_PORT=3005
+AUTH_SERVER_ADDRESS=menzel.informatik.rwth-aachen.de
+AUTH_SERVER_PORT=3006
+JWT_SECRET=rwthi5-pht-jwt
+SESSION_SECRET=rwthi5-pht-session
+VAULT_HOST=pht-vault
+VAULT_PORT=8200
+METADATAPROVIDER_ENDPOINT=http://metadataservice:9988
+VAULT_TOKEN=s.x0PoaaWNLojOoi7WQLD0NT4Z
\ No newline at end of file
diff --git a/Deprecated/StationEnvironmentFiles/.env-dic_koeln b/Deprecated/StationEnvironmentFiles/.env-dic_koeln
new file mode 100644
index 0000000000000000000000000000000000000000..2276aadb1b0a134f631da333b0b74a4337904470
--- /dev/null
+++ b/Deprecated/StationEnvironmentFiles/.env-dic_koeln
@@ -0,0 +1,24 @@
+STATION_ID=dic_koeln
+MONGO_HOST=pht-mongo
+MONGO_PORT=27017
+MONGO_USER=admin
+MONGO_PASSWORD=admin
+MONGO_DB=pht
+DOCKER_HOST=pht-dind
+DOCKER_PORT=2376
+HARBOR_ADDRESS=menzel.informatik.rwth-aachen.de
+HARBOR_PORT=3007
+HARBOR_USER=dic_koeln
+HARBOR_PASSWORD=E+f_#sNcW4UD
+HARBOR_CLI=k5hdq6aao70c889k5eeolrouxujs5xvm
+HARBOR_EMAIL=dic_koeln@pht.de
+HARBOR_WEBHOOK_SECRET=secret
+CENTRALSERVICE_ADDRESS=menzel.informatik.rwth-aachen.de
+CENTRALSERVICE_PORT=3005
+AUTH_SERVER_ADDRESS=menzel.informatik.rwth-aachen.de
+AUTH_SERVER_PORT=3006
+JWT_SECRET=rwthi5-pht-jwt
+SESSION_SECRET=rwthi5-pht-session
+VAULT_HOST=pht-vault
+VAULT_PORT=8200
+METADATAPROVIDER_ENDPOINT=http://metadataservice:9988
\ No newline at end of file
diff --git a/Deprecated/StationEnvironmentFiles/.env-goettingen b/Deprecated/StationEnvironmentFiles/.env-goettingen
new file mode 100644
index 0000000000000000000000000000000000000000..f2e464b2a7db5f3b3198d771a5d5ed797360f976
--- /dev/null
+++ b/Deprecated/StationEnvironmentFiles/.env-goettingen
@@ -0,0 +1,24 @@
+STATION_ID=goettingen
+MONGO_HOST=pht-mongo
+MONGO_PORT=27017
+MONGO_USER=admin
+MONGO_PASSWORD=admin
+MONGO_DB=pht
+DOCKER_HOST=pht-dind
+DOCKER_PORT=2376
+HARBOR_ADDRESS=menzel.informatik.rwth-aachen.de
+HARBOR_PORT=3007
+HARBOR_USER=goettingen
+HARBOR_PASSWORD=5d8@b+4M6NC!
+HARBOR_CLI=9yjah3qx0cbpqw821kdncdjq5wti02lt
+HARBOR_EMAIL=goettingen@pht.de
+HARBOR_WEBHOOK_SECRET=secret
+CENTRALSERVICE_ADDRESS=menzel.informatik.rwth-aachen.de
+CENTRALSERVICE_PORT=3005
+AUTH_SERVER_ADDRESS=menzel.informatik.rwth-aachen.de
+AUTH_SERVER_PORT=3006
+JWT_SECRET=rwthi5-pht-jwt
+SESSION_SECRET=rwthi5-pht-session
+VAULT_HOST=pht-vault
+VAULT_PORT=8200
+METADATAPROVIDER_ENDPOINT=http://metadataservice:9988
\ No newline at end of file
diff --git a/Deprecated/StationEnvironmentFiles/.env-leipzig b/Deprecated/StationEnvironmentFiles/.env-leipzig
new file mode 100644
index 0000000000000000000000000000000000000000..28d790ebf4ecba16af12daa78c4ccfa7f2fc0e63
--- /dev/null
+++ b/Deprecated/StationEnvironmentFiles/.env-leipzig
@@ -0,0 +1,24 @@
+STATION_ID=leipzig
+MONGO_HOST=pht-mongo
+MONGO_PORT=27017
+MONGO_USER=admin
+MONGO_PASSWORD=admin
+MONGO_DB=pht
+DOCKER_HOST=pht-dind
+DOCKER_PORT=2376
+HARBOR_ADDRESS=menzel.informatik.rwth-aachen.de
+HARBOR_PORT=3007
+HARBOR_USER=leipzig
+HARBOR_PASSWORD=qE@2kfLVzS+r
+HARBOR_CLI=wn1ms2u1ayla9e8xbvy65f05if2ouux2
+HARBOR_EMAIL=leipzig@pht.de
+HARBOR_WEBHOOK_SECRET=secret
+CENTRALSERVICE_ADDRESS=menzel.informatik.rwth-aachen.de
+CENTRALSERVICE_PORT=3005
+AUTH_SERVER_ADDRESS=menzel.informatik.rwth-aachen.de
+AUTH_SERVER_PORT=3006
+JWT_SECRET=rwthi5-pht-jwt
+SESSION_SECRET=rwthi5-pht-session
+VAULT_HOST=pht-vault
+VAULT_PORT=8200
+METADATAPROVIDER_ENDPOINT=http://metadataservice:9988
\ No newline at end of file
diff --git a/Deprecated/StationEnvironmentFiles/.env-mittweida b/Deprecated/StationEnvironmentFiles/.env-mittweida
new file mode 100644
index 0000000000000000000000000000000000000000..9056401395f2dc8a7f0460f0d319e52195c69a03
--- /dev/null
+++ b/Deprecated/StationEnvironmentFiles/.env-mittweida
@@ -0,0 +1,24 @@
+STATION_ID=mittweida
+MONGO_HOST=pht-mongo
+MONGO_PORT=27017
+MONGO_USER=admin
+MONGO_PASSWORD=admin
+MONGO_DB=pht
+DOCKER_HOST=pht-dind
+DOCKER_PORT=2376
+HARBOR_ADDRESS=menzel.informatik.rwth-aachen.de
+HARBOR_PORT=3007
+HARBOR_USER=mittweida
+HARBOR_PASSWORD=E!x9LYW44g+4
+HARBOR_CLI=ckupestp30g1zqj3zyrb33ddbw3o4mgj
+HARBOR_EMAIL=mittweida@pht.de
+HARBOR_WEBHOOK_SECRET=secret
+CENTRALSERVICE_ADDRESS=menzel.informatik.rwth-aachen.de
+CENTRALSERVICE_PORT=3005
+AUTH_SERVER_ADDRESS=menzel.informatik.rwth-aachen.de
+AUTH_SERVER_PORT=3006
+JWT_SECRET=rwthi5-pht-jwt
+SESSION_SECRET=rwthi5-pht-session
+VAULT_HOST=pht-vault
+VAULT_PORT=8200
+METADATAPROVIDER_ENDPOINT=http://metadataservice:9988
\ No newline at end of file
diff --git a/Deprecated/StationEnvironmentFiles/.env-ukaachen b/Deprecated/StationEnvironmentFiles/.env-ukaachen
new file mode 100644
index 0000000000000000000000000000000000000000..624e42366d2c80a7ccca3d891a3d90d6b12bde32
--- /dev/null
+++ b/Deprecated/StationEnvironmentFiles/.env-ukaachen
@@ -0,0 +1,24 @@
+STATION_ID=ukaachen
+MONGO_HOST=pht-mongo
+MONGO_PORT=27017
+MONGO_USER=admin
+MONGO_PASSWORD=admin
+MONGO_DB=pht
+DOCKER_HOST=pht-dind
+DOCKER_PORT=2376
+HARBOR_ADDRESS=menzel.informatik.rwth-aachen.de
+HARBOR_PORT=3007
+HARBOR_USER=ukaachen
+HARBOR_PASSWORD=kCtL@C+Me6vF
+HARBOR_CLI=fyx5fgw3gi57ldeq1h6j61drywwmuybu
+HARBOR_EMAIL=ukaachen@pht.de
+HARBOR_WEBHOOK_SECRET=secret
+CENTRALSERVICE_ADDRESS=menzel.informatik.rwth-aachen.de
+CENTRALSERVICE_PORT=3005
+AUTH_SERVER_ADDRESS=menzel.informatik.rwth-aachen.de
+AUTH_SERVER_PORT=3006
+JWT_SECRET=rwthi5-pht-jwt
+SESSION_SECRET=rwthi5-pht-session
+VAULT_HOST=pht-vault
+VAULT_PORT=8200
+METADATAPROVIDER_ENDPOINT=http://metadataservice:9988
\ No newline at end of file
diff --git a/Deprecated/StationEnvironmentFiles/.gitkeep b/Deprecated/StationEnvironmentFiles/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/Deprecated/StationEnvironmentFiles/station.json b/Deprecated/StationEnvironmentFiles/station.json
new file mode 100644
index 0000000000000000000000000000000000000000..ace3bbfeb6469707fbb720024eb8323613715da0
--- /dev/null
+++ b/Deprecated/StationEnvironmentFiles/station.json
@@ -0,0 +1,35 @@
+[
+    {
+        "station_registry_uuid":"78c53fbb-f6b4-4866-8191-99cc766450fb",
+        "username": "aachenbruegel",
+        "password":"Vjt9@KJHuvJ+",
+        "harbor_cli":"72f0g3u8vdspfqmbjek95ubpkjgqwtpe"
+    },
+    {
+        "station_registry_uuid":"36158f2c-2ad4-42e9-99e3-7713bf2883ee",
+        "username": "aachenmenzel",
+        "password":"WtR2H+qxM5Lu",
+        "harbor_cli":"djq2cwhp92pmxf4rsjndjn4i7hu0m4sq"
+    },
+    {
+        "station_registry_uuid":"e4dd9f96-28cf-4599-b855-50f771ac4ee8",
+        "username": "mittweida",
+        "password":"E!x9LYW44g+4",
+        "harbor_cli":"ckupestp30g1zqj3zyrb33ddbw3o4mgj"
+    },
+    {
+        "station_registry_uuid":"ca4c78fc-4728-4dbd-ae48-a5e37535f3ac",
+        "username": "leipzig",
+        "password":"qE@2kfLVzS+r",
+        "harbor_cli":"wn1ms2u1ayla9e8xbvy65f05if2ouux2"
+    },
+    {
+        "station_registry_uuid":"ca4c78fc-4728-4dbd-ae48-a5e37535f3ac",
+        "username": "goettingen",
+        "password":"5d8@b+4M6NC!",
+        "harbor_cli":"9yjah3qx0cbpqw821kdncdjq5wti02lt"
+    }
+]
+
+
+
diff --git a/Deprecated/windowsServer2019DeploymentStepbyStep.docx b/Deprecated/windowsServer2019DeploymentStepbyStep.docx
new file mode 100644
index 0000000000000000000000000000000000000000..c3cd4fd10774b70c0f063c015f2e2417f2ca5690
Binary files /dev/null and b/Deprecated/windowsServer2019DeploymentStepbyStep.docx differ
diff --git a/StationDeploymentFiles/.gitkeep b/StationDeploymentFiles/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/StationDeploymentFiles/docker-compose.yml b/StationDeploymentFiles/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bdd7f89b0d397210e7db0256c77b6190f308e0b1
--- /dev/null
+++ b/StationDeploymentFiles/docker-compose.yml
@@ -0,0 +1,126 @@
+version: '3.5'
+services:
+  mongo:
+    image: 'mongo'
+    container_name: 'pht-mongo'
+    volumes:
+      - ./init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js:ro
+      - "pht-mongo-data:/data/db"
+    ports: 
+      - 27017:27017
+    networks:
+      pht-net:
+        aliases:
+          - pht-mongo
+      metadata:
+        aliases:
+          - pht-mongo
+    restart: unless-stopped
+
+  dind:
+    image: 'smithpht/dind:stable-dind'
+    container_name: 'pht-dind'
+    environment:
+    - DOCKER_TLS_CERTDIR=/certs
+    - DOCKER_TLS_SAN=DNS:pht-dind
+    volumes:
+      - "pht-dind-certs-ca:/certs/ca"
+      - "pht-dind-certs-client:/certs/client"
+      - "pht-dind-data:/var/lib/docker"
+    networks:
+      pht-net:
+        aliases:
+          - pht-dind
+    privileged: true
+    restart: unless-stopped
+
+  pht-web:
+    #image: 'station-software:dev'
+    image: 'registry.git.rwth-aachen.de/sascha.welten/pht-architecture/station-software:latest'
+    container_name: 'pht-web'
+    ports:
+      - "3030:3030"
+    volumes:
+      - "pht-dind-certs-client:/usr/src/app/dind-certs-client/certs:ro"
+      - "pht-vault-certs-client:/usr/src/app/vault-certs-client/certs:ro"
+    env_file:
+      - ./env
+    networks:
+      - pht-net
+      - metadata
+    depends_on: 
+      - mongo
+      - dind
+      - vault
+    restart: unless-stopped
+  vault:
+    # image: 'vault'
+    build: 
+      context: ./pht-vault
+      cache_from:
+        - vault:latest
+    image: vault:pht
+    container_name: 'pht-vault'
+    ports:
+      - "8200:8200"
+    environment:
+      - VAULT_ADDR=https://127.0.0.1:8200
+      - VAULT_API_ADDR=https://127.0.0.1:8200
+      - TLS_SAN=DNS:pht-vault
+    volumes:
+      - pht-vault-logs:/vault/logs
+      - pht-vault-data:/vault/data
+      - ./vault.json:/vault/config/vault.json
+      - "pht-vault-certs-ca:/certs/ca"
+      - "pht-vault-certs-client:/certs/client"
+    networks:
+      pht-net:
+        aliases:
+          - pht-vault
+    cap_add:
+      - IPC_LOCK
+    entrypoint: generate_cert.sh vault server -config=/vault/config/vault.json
+    restart: unless-stopped
+
+  telegraf:
+    image: telegraf
+    networks:
+      - metadata
+      - pht-net
+    volumes:
+      - "./telegraf.conf:/etc/telegraf/telegraf.conf"
+      - "pht-dind-certs-client:/certs:ro"
+    depends_on:
+      - dind
+  metadataservice:
+    image: "registry.git.rwth-aachen.de/sascha.welten/pht-architecture/phtmetadataprovider:latest"
+    networks: 
+      metadata:
+        aliases: 
+          - metadataservice
+    ports: 
+     - "9988:9988"
+    depends_on: 
+      - mongo
+    environment:
+      - UPLINKADRESS=https://menzel.informatik.rwth-aachen.de:3005/metadata/store/sparql
+      # pls set the station identifier for your station, in future this will be done by the wizard
+      - STATIONIDENTIFIER=http://registry.menzel.rwth-aachen.de/aachenbeeck
+networks:
+  pht-net:
+    external: false
+    name: pht-net
+  metadata:
+    external: false
+    name: metadata-net
+
+volumes:
+  pht-dind-certs-ca:
+  pht-dind-certs-client:
+  pht-dind-data:
+  pht-mongo-data:
+  pht-vault-logs:
+  pht-dind-data:
+  pht-vault-data:   
+  pht-vault-certs-ca:
+  pht-vault-certs-client: 
diff --git a/StationDeploymentFiles/env b/StationDeploymentFiles/env
new file mode 100644
index 0000000000000000000000000000000000000000..22ee1f2182d7c019d7f24adcda947df45e3d17e9
--- /dev/null
+++ b/StationDeploymentFiles/env
@@ -0,0 +1,25 @@
+STATION_ID=aachenbeeck
+MONGO_HOST=pht-mongo
+MONGO_PORT=27017
+MONGO_USER=admin
+MONGO_PASSWORD=admin
+MONGO_DB=pht
+DOCKER_HOST=pht-dind
+DOCKER_PORT=2376
+HARBOR_ADDRESS=menzel.informatik.rwth-aachen.de
+HARBOR_PORT=3007
+HARBOR_USER=aachenbeeck
+HARBOR_PASSWORD=Q4h+@Wq3@k#Q
+HARBOR_CLI=CU146e5g9EFNQxZwGec0UlgWDJox04Tq
+OTHER_HARBOR_CLI=6n757ho8teukkz07ewta9t8570sovcbl
+HARBOR_EMAIL=aachenbeeck@pht.de
+HARBOR_WEBHOOK_SECRET=secret
+CENTRALSERVICE_ADDRESS=menzel.informatik.rwth-aachen.de
+CENTRALSERVICE_PORT=3005
+AUTH_SERVER_ADDRESS=menzel.informatik.rwth-aachen.de
+AUTH_SERVER_PORT=3006
+JWT_SECRET=rwthi5-pht-jwt
+SESSION_SECRET=rwthi5-pht-session
+VAULT_HOST=pht-vault
+VAULT_PORT=8200
+
diff --git a/StationDeploymentFiles/init-mongo.js b/StationDeploymentFiles/init-mongo.js
new file mode 100644
index 0000000000000000000000000000000000000000..551f9c3a8215b3d478028c9bbafae9838345df6c
--- /dev/null
+++ b/StationDeploymentFiles/init-mongo.js
@@ -0,0 +1,36 @@
+db = db.getSiblingDB('pht')
+db.createUser(
+	{
+		user: "admin",
+		pwd: "admin",
+		roles: [
+			{
+				role: "readWrite",
+				db: "pht"
+			},
+			{
+				role: "dbAdmin",
+				db: "pht"
+			}
+		],
+		mechanisms: ["SCRAM-SHA-1"]
+	}
+)
+db = db.getSiblingDB('metadatapersistence')
+db.createUser(
+	{
+		user: "admin",
+		pwd: "admin",
+		roles: [
+			{
+				role: "readWrite",
+				db: "pht"
+			},
+			{
+				role: "dbAdmin",
+				db: "pht"
+			}
+		],
+		mechanisms: ["SCRAM-SHA-1"]
+	}
+)
\ No newline at end of file
diff --git a/StationDeploymentFiles/pht-vault/Dockerfile b/StationDeploymentFiles/pht-vault/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..e0d4d7deae3f7836d59ed7b246298a04484fd074
--- /dev/null
+++ b/StationDeploymentFiles/pht-vault/Dockerfile
@@ -0,0 +1,11 @@
+FROM vault:latest
+
+# Install OpenSSL
+RUN apk update && \
+  apk add --no-cache openssl && \
+  rm -rf "/var/cache/apk/*"
+
+COPY generate_cert.sh /usr/local/bin/generate_cert.sh
+RUN ["chmod", "+x", "/usr/local/bin/generate_cert.sh"]
+
+ENTRYPOINT ["docker-entrypoint.sh"]
\ No newline at end of file
diff --git a/StationDeploymentFiles/pht-vault/generate_cert.sh b/StationDeploymentFiles/pht-vault/generate_cert.sh
new file mode 100644
index 0000000000000000000000000000000000000000..ccf6307b3af2752d5704e2254ebe1747e7f189a4
--- /dev/null
+++ b/StationDeploymentFiles/pht-vault/generate_cert.sh
@@ -0,0 +1,93 @@
+#!/bin/sh
+set -eu
+
+_tls_ensure_private() {
+	local f="$1"; shift
+	[ -s "$f" ] || openssl genrsa -out "$f" 4096
+}
+_tls_san() {
+	{
+		ip -oneline address | awk '{ gsub(/\/.+$/, "", $4); print "IP:" $4 }'
+		{
+			cat /etc/hostname
+			echo 'localhost'
+			hostname -f
+			hostname -s
+		} | sed 's/^/DNS:/'
+		[ -z "${TLS_SAN:-}" ] || echo "$TLS_SAN"
+	} | sort -u | xargs printf '%s,' | sed "s/,\$//"
+}
+_tls_generate_certs() {
+	local dir="$1"; shift
+
+	# if ca/key.pem || !ca/cert.pem, generate CA public if necessary
+	# if ca/key.pem, generate server public
+	# if ca/key.pem, generate client public
+	# (regenerating public certs every startup to account for SAN/IP changes and/or expiration)
+
+	# https://github.com/FiloSottile/mkcert/issues/174
+	local certValidDays='825'
+
+	if [ -s "$dir/ca/key.pem" ] || [ ! -s "$dir/ca/cert.pem" ]; then
+		# if we either have a CA private key or do *not* have a CA public key, then we should create/manage the CA
+		mkdir -p "$dir/ca"
+		_tls_ensure_private "$dir/ca/key.pem"
+		openssl req -new -key "$dir/ca/key.pem" \
+			-out "$dir/ca/cert.pem" \
+			-subj '/CN=vault:pht CA' -x509 -days "$certValidDays"
+	fi
+
+	if [ -s "$dir/ca/key.pem" ]; then
+		# if we have a CA private key, we should create/manage a server key
+		mkdir -p "$dir/server"
+		_tls_ensure_private "$dir/server/key.pem"
+		openssl req -new -key "$dir/server/key.pem" \
+			-out "$dir/server/csr.pem" \
+			-subj '/CN=vault:pht server'
+		cat > "$dir/server/openssl.cnf" <<-EOF
+			[ x509_exts ]
+			subjectAltName = $(_tls_san)
+		EOF
+		openssl x509 -req \
+				-in "$dir/server/csr.pem" \
+				-CA "$dir/ca/cert.pem" \
+				-CAkey "$dir/ca/key.pem" \
+				-CAcreateserial \
+				-out "$dir/server/cert.pem" \
+				-days "$certValidDays" \
+				-extfile "$dir/server/openssl.cnf" \
+				-extensions x509_exts
+		cp "$dir/ca/cert.pem" "$dir/server/ca.pem"
+		openssl verify -CAfile "$dir/server/ca.pem" "$dir/server/cert.pem"
+	fi
+
+	if [ -s "$dir/ca/key.pem" ]; then
+		# if we have a CA private key, we should create/manage a client key
+		mkdir -p "$dir/client"
+		_tls_ensure_private "$dir/client/key.pem"
+		chmod 0644 "$dir/client/key.pem" # openssl defaults to 0600 for the private key, but this one needs to be shared with arbitrary client contexts
+		openssl req -new \
+				-key "$dir/client/key.pem" \
+				-out "$dir/client/csr.pem" \
+				-subj '/CN=vault:pht client'
+		cat > "$dir/client/openssl.cnf" <<-'EOF'
+			[ x509_exts ]
+			extendedKeyUsage = clientAuth
+		EOF
+		openssl x509 -req \
+				-in "$dir/client/csr.pem" \
+				-CA "$dir/ca/cert.pem" \
+				-CAkey "$dir/ca/key.pem" \
+				-CAcreateserial \
+				-out "$dir/client/cert.pem" \
+				-days "$certValidDays" \
+				-extfile "$dir/client/openssl.cnf" \
+				-extensions x509_exts
+		cp "$dir/ca/cert.pem" "$dir/client/ca.pem"
+		openssl verify -CAfile "$dir/client/ca.pem" "$dir/client/cert.pem"
+	fi
+}
+
+_tls_generate_certs "/certs"
+
+exec "$@"
\ No newline at end of file
diff --git a/StationDeploymentFiles/telegraf.conf b/StationDeploymentFiles/telegraf.conf
new file mode 100644
index 0000000000000000000000000000000000000000..4a3a46516277f0f53fd62618d17ac59c141daed8
--- /dev/null
+++ b/StationDeploymentFiles/telegraf.conf
@@ -0,0 +1,258 @@
+# Telegraf Configuration
+#
+# Telegraf is entirely plugin driven. All metrics are gathered from the
+# declared inputs, and sent to the declared outputs.
+#
+# Plugins must be declared in here to be active.
+# To deactivate a plugin, comment out the name and any variables.
+#
+# Use 'telegraf -config telegraf.conf -test' to see what metrics a config
+# file would generate.
+#
+# Environment variables can be used anywhere in this config file, simply surround
+# them with ${}. For strings the variable must be within quotes (ie, "${STR_VAR}"),
+# for numbers and booleans they should be plain (ie, ${INT_VAR}, ${BOOL_VAR})
+
+
+# Global tags can be specified here in key="value" format.
+[global_tags]
+  # dc = "us-east-1" # will tag all metrics with dc=us-east-1
+  # rack = "1a"
+  ## Environment variables can be used as tags, and throughout the config file
+  # user = "$USER"
+
+
+# Configuration for telegraf agent
+[agent]
+  ## Default data collection interval for all inputs
+  interval = "10s"
+  ## Rounds collection interval to 'interval'
+  ## ie, if interval="10s" then always collect on :00, :10, :20, etc.
+  round_interval = true
+
+  ## Telegraf will send metrics to outputs in batches of at most
+  ## metric_batch_size metrics.
+  ## This controls the size of writes that Telegraf sends to output plugins.
+  metric_batch_size = 1000
+
+  ## Maximum number of unwritten metrics per output.  Increasing this value
+  ## allows for longer periods of output downtime without dropping metrics at the
+  ## cost of higher maximum memory usage.
+  metric_buffer_limit = 10000
+
+  ## Collection jitter is used to jitter the collection by a random amount.
+  ## Each plugin will sleep for a random time within jitter before collecting.
+  ## This can be used to avoid many plugins querying things like sysfs at the
+  ## same time, which can have a measurable effect on the system.
+  collection_jitter = "0s"
+
+  ## Default flushing interval for all outputs. Maximum flush_interval will be
+  ## flush_interval + flush_jitter
+  flush_interval = "10s"
+  ## Jitter the flush interval by a random amount. This is primarily to avoid
+  ## large write spikes for users running a large number of telegraf instances.
+  ## ie, a jitter of 5s and interval 10s means flushes will happen every 10-15s
+  flush_jitter = "0s"
+
+  ## By default or when set to "0s", precision will be set to the same
+  ## timestamp order as the collection interval, with the maximum being 1s.
+  ##   ie, when interval = "10s", precision will be "1s"
+  ##       when interval = "250ms", precision will be "1ms"
+  ## Precision will NOT be used for service inputs. It is up to each individual
+  ## service input to set the timestamp at the appropriate precision.
+  ## Valid time units are "ns", "us" (or "µs"), "ms", "s".
+  precision = ""
+
+  ## Log at debug level.
+  # debug = false
+  ## Log only error level messages.
+  # quiet = false
+
+  ## Log target controls the destination for logs and can be one of "file",
+  ## "stderr" or, on Windows, "eventlog".  When set to "file", the output file
+  ## is determined by the "logfile" setting.
+  # logtarget = "file"
+
+  ## Name of the file to be logged to when using the "file" logtarget.  If set to
+  ## the empty string then logs are written to stderr.
+  # logfile = ""
+
+  ## The logfile will be rotated after the time interval specified.  When set
+  ## to 0 no time based rotation is performed.  Logs are rotated only when
+  ## written to, if there is no log activity rotation may be delayed.
+  # logfile_rotation_interval = "0d"
+
+  ## The logfile will be rotated when it becomes larger than the specified
+  ## size.  When set to 0 no size based rotation is performed.
+  # logfile_rotation_max_size = "0MB"
+
+  ## Maximum number of rotated archives to keep, any older logs are deleted.
+  ## If set to -1, no archives are removed.
+  # logfile_rotation_max_archives = 5
+
+  ## Override default hostname, if empty use os.Hostname()
+  hostname = ""
+  ## If set to true, do no set the "host" tag in the telegraf agent.
+  omit_hostname = false
+
+
+###############################################################################
+#                            OUTPUT PLUGINS                                   #
+###############################################################################
+
+
+# Configuration for sending metrics to InfluxDB
+#[[outputs.influxdb]]
+  ## The full HTTP or UDP URL for your InfluxDB instance.
+  ##
+  ## Multiple URLs can be specified for a single cluster, only ONE of the
+  ## urls will be written to each interval.
+  # urls = ["unix:///var/run/influxdb.sock"]
+  # urls = ["udp://127.0.0.1:8089"]
+  #urls = ["http://influxdb:8086"]
+
+  ## The target database for metrics; will be created as needed.
+  ## For UDP url endpoint database needs to be configured on server side.
+  #database = "telegraf"
+
+  ## The value of this tag will be used to determine the database.  If this
+  ## tag is not set the 'database' option is used as the default.
+  # database_tag = ""
+
+  ## If true, the 'database_tag' will not be included in the written metric.
+  # exclude_database_tag = false
+
+  ## If true, no CREATE DATABASE queries will be sent.  Set to true when using
+  ## Telegraf with a user without permissions to create databases or when the
+  ## database already exists.
+  # skip_database_creation = false
+
+  ## Name of existing retention policy to write to.  Empty string writes to
+  ## the default retention policy.  Only takes effect when using HTTP.
+  # retention_policy = ""
+
+  ## The value of this tag will be used to determine the retention policy.  If this
+  ## tag is not set the 'retention_policy' option is used as the default.
+  # retention_policy_tag = ""
+
+  ## If true, the 'retention_policy_tag' will not be included in the written metric.
+  # exclude_retention_policy_tag = false
+
+  ## Write consistency (clusters only), can be: "any", "one", "quorum", "all".
+  ## Only takes effect when using HTTP.
+  # write_consistency = "any"
+
+  ## Timeout for HTTP messages.
+  # timeout = "5s"
+
+  ## HTTP Basic Auth
+  # username = "telegraf"
+  # password = "metricsmetricsmetricsmetrics"
+
+  ## HTTP User-Agent
+  # user_agent = "telegraf"
+
+  ## UDP payload size is the maximum packet size to send.
+  # udp_payload = "512B"
+
+  ## Optional TLS Config for use on HTTP connections.
+  # tls_ca = "/etc/telegraf/ca.pem"
+  # tls_cert = "/etc/telegraf/cert.pem"
+  # tls_key = "/etc/telegraf/key.pem"
+  ## Use TLS but skip chain & host verification
+  # insecure_skip_verify = false
+
+  ## HTTP Proxy override, if unset values the standard proxy environment
+  ## variables are consulted to determine which proxy, if any, should be used.
+  # http_proxy = "http://corporate.proxy:3128"
+
+  ## Additional HTTP headers
+  # http_headers = {"X-Special-Header" = "Special-Value"}
+
+  ## HTTP Content-Encoding for write request body, can be set to "gzip" to
+  ## compress body or "identity" to apply no encoding.
+  # content_encoding = "gzip"
+
+  ## When true, Telegraf will output unsigned integers as unsigned values,
+  ## i.e.: "42u".  You will need a version of InfluxDB supporting unsigned
+  ## integer values.  Enabling this option will result in field type errors if
+  ## existing data has been written.
+  # influx_uint_support = false
+#[[outputs.influxdb_v2]]	
+  ## The URLs of the InfluxDB cluster nodes.
+  ##
+  ## Multiple URLs can be specified for a single cluster, only ONE of the
+  ## urls will be written to each interval.
+  ## urls exp: http://127.0.0.1:9999
+  #urls = ["http://influxdb:8086"]
+
+  ## Token for authentication.
+  #token = "6gCn0d-K1B5g3OGMzqgTTqWIXDpN4b47VP1GyIFxfEN2raQMW51dGQzNGf33r8Uvug4_tvQmn3vsrlYKnVs21Q=="
+  #organization = "metadata"
+  #bucket = "telegraf"
+
+[[outputs.http]]
+  url = "http://metadataservice:9988/telegraf"
+  timeout = "5s"
+  method = "POST"
+  data_format = "json"
+  json_timestamp_units = "1s"
+  content_encoding = "identity"
+# # Read metrics about docker containers
+[[inputs.docker]]
+#   ## Docker Endpoint
+#   ##   To use TCP, set endpoint = "tcp://[ip]:[port]"
+#   ##   To use environment variables (ie, docker-machine), set endpoint = "ENV"
+  endpoint = "https://pht-dind:2376"
+#
+#   ## Set to true to collect Swarm metrics(desired_replicas, running_replicas)
+#   gather_services = false
+#
+#   ## Only collect metrics for these containers, collect all if empty
+  container_names = []
+#
+#   ## Set the source tag for the metrics to the container ID hostname, eg first 12 chars
+#   source_tag = false
+#
+#   ## Containers to include and exclude. Globs accepted.
+#   ## Note that an empty array for both will include all containers
+#   container_name_include = []
+#   container_name_exclude = []
+#
+#   ## Container states to include and exclude. Globs accepted.
+#   ## When empty only containers in the "running" state will be captured.
+#   ## example: container_state_include = ["created", "restarting", "running", "removing", "paused", "exited", "dead"]
+#   ## example: container_state_exclude = ["created", "restarting", "running", "removing", "paused", "exited", "dead"]
+  container_state_include = ["running"]
+#   # container_state_exclude = []
+#
+#   ## Timeout for docker list, info, and stats commands
+#   timeout = "5s"
+#
+#   ## Whether to report for each container per-device blkio (8:0, 8:1...) and
+#   ## network (eth0, eth1, ...) stats or not
+#   perdevice = true
+#
+#   ## Whether to report for each container total blkio and network stats or not
+  total = true
+#
+#   ## Which environment variables should we use as a tag
+#   ##tag_env = ["JAVA_
+#   ## Optional TLS Config
+  tls_ca = "/certs/ca.pem"
+  tls_cert = "/certs/cert.pem"
+  tls_key = "/certs/key.pem"
+
+  insecure_skip_verify = true
+
+[[inputs.docker_log]]
+  endpoint = "https://pht-dind:2376"
+  from_beginning = true
+
+  source_tag = false
+
+  tls_ca = "/certs/ca.pem"
+  tls_cert = "/certs/cert.pem"
+  tls_key = "/certs/key.pem"
+
+  insecure_skip_verify = true
\ No newline at end of file
diff --git a/StationDeploymentFiles/vault.json b/StationDeploymentFiles/vault.json
new file mode 100644
index 0000000000000000000000000000000000000000..ee2f4105f0a3f164edafeafdc4163b3e5220769d
--- /dev/null
+++ b/StationDeploymentFiles/vault.json
@@ -0,0 +1,16 @@
+{
+  "backend": {
+    "file": {
+      "path": "/vault/data"
+    }
+  },
+  "listener": {
+    "tcp":{
+      "address": "0.0.0.0:8200",
+      "tls_disable": "false",
+      "tls_cert_file": "/certs/server/cert.pem",
+      "tls_key_file": "/certs/server/key.pem"
+    }
+  },
+  "ui": false
+}
diff --git a/StationSoftware/.dockerignore b/StationSoftware/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..10ca057eaf99baf60df45f164465af5fe6c86b21
--- /dev/null
+++ b/StationSoftware/.dockerignore
@@ -0,0 +1,4 @@
+node_modules
+npm-debug.log
+.env
+Deployment_Guidance_of_Station_Software.docx
\ No newline at end of file
diff --git a/StationSoftware/.env b/StationSoftware/.env
new file mode 100644
index 0000000000000000000000000000000000000000..aba653ca0af53bc5625e3140ee0c5fba0bd6144e
--- /dev/null
+++ b/StationSoftware/.env
@@ -0,0 +1,23 @@
+STATION_ID=aachenbeeck
+MONGO_HOST=172.25.0.3
+MONGO_PORT=27017
+MONGO_USER=admin
+MONGO_PASSWORD=admin
+MONGO_DB=pht
+DOCKER_HOST=172.25.0.2
+DOCKER_PORT=2376
+HARBOR_ADDRESS=menzel.informatik.rwth-aachen.de
+HARBOR_PORT=3007
+HARBOR_USER=aachenbeeck
+HARBOR_PASSWORD=Q4h+@Wq3@k#Q
+HARBOR_CLI=di1nr5t1307m4yy7q7bf8xclhbp7bogh
+OTHER_HARBOR_CLI=6n757ho8teukkz07ewta9t8570sovcbl
+HARBOR_EMAIL=aachenbeeck@pht.de
+HARBOR_WEBHOOK_SECRET=secret
+CENTRALSERVICE_ADDRESS=menzel.informatik.rwth-aachen.de
+CENTRALSERVICE_PORT=3005
+AUTH_SERVER_ADDRESS=menzel.informatik.rwth-aachen.de
+AUTH_SERVER_PORT=3006
+JWT_SECRET=rwthi5-pht-jwt
+SESSION_SECRET=rwthi5-pht-session
+METADATAPROVIDER_ENDPOINT=metadataservice:9988
\ No newline at end of file
diff --git a/StationSoftware/.gitlab-ci.yml b/StationSoftware/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..eda0d4e399dd4202439b12a0e69c54e649bb5811
--- /dev/null
+++ b/StationSoftware/.gitlab-ci.yml
@@ -0,0 +1,80 @@
+stages:
+  - build
+  - test
+  - deploy
+
+build_station_software:
+  image: docker:latest
+  services:
+    - docker:dind
+  stage: build
+  variables:
+    CI_REGISTRY_IMAGE_STATION_SOFTWARE: $CI_REGISTRY_IMAGE/station-software
+  before_script:
+    - apk update && apk add git
+    - echo "$CI_BUILD_TOKEN" | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
+  script:
+    - echo "Build stationsoftware image!"
+    - docker build --pull -t $CI_REGISTRY_IMAGE_STATION_SOFTWARE:$CI_COMMIT_SHA ./Monitoring/MetadataInfrastructure
+    - docker push $CI_REGISTRY_IMAGE_STATION_SOFTWARE:$CI_COMMIT_SHA
+    - echo "build complete!"
+
+test_station_software:
+  image: docker:latest
+  services:
+    - docker:dind
+  stage: test
+  needs: [build_station_software]
+  variables:
+    CI_REGISTRY_IMAGE_STATION_SOFTWARE: $CI_REGISTRY_IMAGE/station-software
+  before_script:
+    - apk update && apk add git
+    - echo "$CI_BUILD_TOKEN" | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
+  script:
+    - echo "Test station software!"
+    - docker pull $CI_REGISTRY_IMAGE_STATION_SOFTWARE:$CI_COMMIT_SHA
+    - docker run --rm -i $CI_REGISTRY_IMAGE_STATION_SOFTWARE:$CI_COMMIT_SHA npm test
+
+
+deploy_station_software_branch:
+  image: docker:latest
+  services:
+    - docker:dind
+  stage: deploy
+  needs: [test_station_software]
+  variables:
+    CI_REGISTRY_IMAGE_STATION_SOFTWARE: $CI_REGISTRY_IMAGE/station-software
+  before_script:
+    - apk update && apk add git
+    - echo "$CI_BUILD_TOKEN" | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
+  script:
+    - docker pull $CI_REGISTRY_IMAGE_STATION_SOFTWARE:$CI_COMMIT_SHA
+    - docker tag $CI_REGISTRY_IMAGE_STATION_SOFTWARE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE_STATION_SOFTWARE:$CI_COMMIT_BRANCH
+    - docker push $CI_REGISTRY_IMAGE_STATION_SOFTWARE:$CI_COMMIT_BRANCH
+
+
+deploy_station_software_stable:
+  image: docker:latest
+  services:
+    - docker:dind
+  stage: deploy
+  needs: [test_station_software]
+  rules:
+    - if: '$CI_COMMIT_BRANCH==$CI_DEFAULT_BRANCH'
+  variables:
+    CI_REGISTRY_IMAGE_STATION_SOFTWARE: $CI_REGISTRY_IMAGE/station-software
+  before_script:
+    - apk update && apk add git
+    - echo "$CI_BUILD_TOKEN" | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
+  script:
+    - echo "commit was made to the master branch!"
+    - docker pull $CI_REGISTRY_IMAGE_STATION_SOFTWARE:$CI_COMMIT_SHA
+    - docker tag $CI_REGISTRY_IMAGE_STATION_SOFTWARE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE_STATION_SOFTWARE:stable
+    - docker push $CI_REGISTRY_IMAGE_STATION_SOFTWARE:stable
+    - echo "push to docker hub"
+    - echo "d5721cbc-65c4-471d-b129-4f9e94c74434" | docker login --username smithpht --password-stdin docker.io
+    - docker tag $CI_REGISTRY_IMAGE_STATION_SOFTWARE:stable docker.io/smithpht/station-software:latest
+    - docker push docker.io/smithpht/station-software:latest
+
+
+
diff --git a/StationSoftware/Dockerfile b/StationSoftware/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..cb785089a07c58413101ed59d5e4a62e0198b6f1
--- /dev/null
+++ b/StationSoftware/Dockerfile
@@ -0,0 +1,25 @@
+FROM node:latest as node_cache
+
+# Create app directory
+WORKDIR /usr/src/app
+
+# Install app dependencies
+# A wildcard is used to ensure both package.json AND package-lock.json are copied
+# where available (npm@5+)
+COPY package*.json ./
+
+RUN npm install
+# If you are building your code for production
+# RUN npm ci --only=production
+
+FROM node:latest
+
+WORKDIR /usr/src/app
+
+COPY --from=node_cache /usr/src/app/ .
+
+# Bundle app source
+COPY . .
+
+EXPOSE 3030
+CMD [ "node", "./bin/www" ]
diff --git a/StationSoftware/README.md b/StationSoftware/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..789674350c643c7256023ffe2c0e70b1a4f731f6
--- /dev/null
+++ b/StationSoftware/README.md
@@ -0,0 +1,33 @@
+# PHT Web (Station Software)
+This is sub-project of PHT and provides Frontend and RESTful APIs.
+
+## Usages
+
+Use the docker-compose files
+
+### Why would you need to enable TLS for Docker API?
+If you want to expose Docker API on a network and if you use shared network namespaces (as in Kubernetes pods), this is a potential security issue (which can lead to access to the host system, for example). It is recommended to enable TLS, then Docker API will be reachable through the network in a safe manner.
+
+### How to enable TLS for Docker API?
+You can follow the Docker Docs instructions:
+https://docs.docker.com/engine/security/https/
+
+We have also prepared a deployment guide and a script that could be helpful.
+The provided script generates a CA, server, and client keys with OpenSSL.
+
+Inside the directory that you specified, the script will create three directories:
+* ca: the certificate authority files (cert.pem, key.pem)
+* server: the dockerd (daemon) certificate files (cert.pem, ca.pem, key.pem)
+* client: the client certificate files (cert.pem, ca.pem, key.pem)
+
+
+**Note:** It is conventional to use port 2375 for un-encrypted communication with the daemon. Docker over TLS should run on TCP port 2376.
+
+**Note:** If you use the dind image, it will automatically generate TLS certificates in the directory specified by the DOCKER_TLS_CERTDIR environment variable.
+
+### What is Docker in Docker (dind)?
+Docker in Docker (dind) is a Docker container hosting Docker machine. In other words, dind allows the Docker engine to run as a Container inside Docker. This link is the official repository for dind.
+
+### Would you need dind?
+Basically, you do not force to use dind. But if you would like to isolate the docker environment, in which the trains will run, from your host docker environment, it could be a good approach. 
+Plus you may not want to change docker configs on the host environment (for example you use Kubernetes clusters or you don't want to enable TCP Socket for docker daemon), in this case, you may need to use Docker in Docker. 
diff --git a/StationSoftware/app.js b/StationSoftware/app.js
new file mode 100644
index 0000000000000000000000000000000000000000..8bc4ed6b26116a89616af663bf7e65e2043db615
--- /dev/null
+++ b/StationSoftware/app.js
@@ -0,0 +1,122 @@
+// Environment variable config
+require('dotenv').config();
+
+// var createError = require('http-errors');
+var express = require('express');
+var path = require('path');
+var cookieParser = require('cookie-parser');
+var logger = require('morgan');
+var bodyParser = require('body-parser');
+
+const expressLayouts = require('express-ejs-layouts');
+const passport = require('passport');
+const flash = require('connect-flash');
+const session = require('express-session');
+const mongoose = require('mongoose');
+const promiseRetry = require('promise-retry');
+
+const config = require('./config/keys');
+const initPassport = require('./validation/passport');
+
+var indexRouter = require('./routes/index');
+
+
+var app = express();
+
+// Passport Config
+initPassport(passport);
+
+// Connect to MongoDB - START
+const mongoURI = config.mongoURI;
+console.log(mongoURI);
+
+// https://jira.mongodb.org/browse/NODE-1643
+const mongooseConnectOptions = {
+  useNewUrlParser: true,
+  reconnectTries: 60,
+  reconnectInterval: 1000,
+  poolSize: 10,
+  bufferMaxEntries: 0
+}
+
+const promiseRetryOptions = {
+  retries: 60,
+  factor: 1.5,
+  minTimeout: 1000,
+  maxTimeout: 5000
+}
+
+const connectToMongoDB = (url) => {
+  return promiseRetry((retry, number) => {
+    console.log(`MongoClient connecting to ${url} - retry number: ${number}`)
+    return mongoose.connect(url, mongooseConnectOptions).catch(retry)
+  }, promiseRetryOptions)
+}
+
+connectToMongoDB(mongoURI);
+// Connect to MongoDB - END
+
+// view engine setup
+app.set('views', path.join(__dirname, './views'));
+app.set('view engine', 'ejs');
+app.use(expressLayouts);
+
+// Express body parser
+app.use(express.urlencoded({ extended: true }));
+
+// Express session
+app.use(
+  session({
+    secret: process.env.SESSION_SECRET,
+    resave: true,
+    saveUninitialized: true
+  })
+);
+
+// Passport middleware
+app.use(passport.initialize());
+app.use(passport.session());
+
+// Connect flash
+app.use(flash());
+
+app.use(logger('dev'));
+app.use(express.json());
+app.use(express.urlencoded({ extended: false }));
+app.use(cookieParser());
+app.use(express.static(path.join(__dirname, './public')));
+
+// parse application/json
+app.use(bodyParser.json())
+
+// catch 404 and forward to error handler
+app.use(function (req, res, next) {
+  res.locals.success_msg = req.flash('success_msg');
+  res.locals.error_msg = req.flash('error_msg');
+  res.locals.error = req.flash('error');
+  res.locals.logs = req.flash('logs');
+  next();
+});
+
+app.use((err, req, res, next) => {
+  res.status(err.status || 400).json({
+    success: false,
+    message: err.message || 'An error occured.',
+    errors: err.error || [],
+  });
+});
+
+app.use('/', indexRouter);
+
+// error handler
+// app.use(function(err, req, res, next) {
+//   // set locals, only providing error in development
+//   res.locals.message = err.message;
+//   res.locals.error = req.app.get('env') === 'development' ? err : {};
+
+//   // render the error page
+//   res.status(err.status || 500);
+//   res.render('error');
+// });
+
+module.exports = app;
diff --git a/StationSoftware/bin/www b/StationSoftware/bin/www
new file mode 100644
index 0000000000000000000000000000000000000000..10cfc3f82fa7be6a206cc8722dc4e5e51f954d61
--- /dev/null
+++ b/StationSoftware/bin/www
@@ -0,0 +1,93 @@
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var app = require('../app');
+var debug = require('debug')('pht-web:server');
+var http = require('http');
+
+//skip self-signed-cert
+// process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"
+
+/**
+ * Get port from environment and store in Express.
+ */
+
+var port = normalizePort(process.env.PORT || '3030');
+app.set('port', port);
+
+/**
+ * Create HTTP server.
+ */
+
+var server = http.createServer(app);
+
+/**
+ * Listen on provided port, on all network interfaces.
+ */
+
+server.listen(port);
+server.on('error', onError);
+server.on('listening', onListening);
+
+/**
+ * Normalize a port into a number, string, or false.
+ */
+
+function normalizePort(val) {
+  var port = parseInt(val, 10);
+
+  if (isNaN(port)) {
+    // named pipe
+    return val;
+  }
+
+  if (port >= 0) {
+    // port number
+    return port;
+  }
+
+  return false;
+}
+
+/**
+ * Event listener for HTTP server "error" event.
+ */
+
+function onError(error) {
+  if (error.syscall !== 'listen') {
+    throw error;
+  }
+
+  var bind = typeof port === 'string'
+    ? 'Pipe ' + port
+    : 'Port ' + port;
+
+  // handle specific listen errors with friendly messages
+  switch (error.code) {
+    case 'EACCES':
+      console.error(bind + ' requires elevated privileges');
+      process.exit(1);
+      break;
+    case 'EADDRINUSE':
+      console.error(bind + ' is already in use');
+      process.exit(1);
+      break;
+    default:
+      throw error;
+  }
+}
+
+/**
+ * Event listener for HTTP server "listening" event.
+ */
+
+function onListening() {
+  var addr = server.address();
+  var bind = typeof addr === 'string'
+    ? 'pipe ' + addr
+    : 'port ' + addr.port;
+  debug('Listening on ' + bind);
+}
\ No newline at end of file
diff --git a/StationSoftware/config/keys.js b/StationSoftware/config/keys.js
new file mode 100644
index 0000000000000000000000000000000000000000..9fa9526fecbc241fe08439868dc1b13e852cb84b
--- /dev/null
+++ b/StationSoftware/config/keys.js
@@ -0,0 +1,5 @@
+
+// Information of database (MongoDB)
+module.exports = {
+    mongoURI:'mongodb://'+process.env.MONGO_USER+':'+process.env.MONGO_PASSWORD+'@'+process.env.MONGO_HOST+':'+process.env.MONGO_PORT+'/'+process.env.MONGO_DB,
+};
\ No newline at end of file
diff --git a/StationSoftware/dind-certs-client/index.js b/StationSoftware/dind-certs-client/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..306e90c287047598cb07e3e54dac2ae6594c10c2
--- /dev/null
+++ b/StationSoftware/dind-certs-client/index.js
@@ -0,0 +1,17 @@
+const fs = require('fs')
+const certDirectory = '/usr/src/app/dind-certs-client/certs';
+const certs = {
+    ca: fs.readFileSync(`${certDirectory}/ca.pem`),
+    key: fs.readFileSync(`${certDirectory}/key.pem`),
+    cert: fs.readFileSync(`${certDirectory}/cert.pem`),
+};
+
+module.exports = {
+    getAgentOptions: () => {
+        return {
+            ca: certs.ca,
+            cert: certs.cert,
+            key: certs.key,
+        }
+    }
+};
\ No newline at end of file
diff --git a/StationSoftware/models/Train.js b/StationSoftware/models/Train.js
new file mode 100644
index 0000000000000000000000000000000000000000..05583db3f7fe67f67cdadaa95f82d2c4b7064bdf
--- /dev/null
+++ b/StationSoftware/models/Train.js
@@ -0,0 +1,31 @@
+const mongoose = require('mongoose');
+const TrainSchema = new mongoose.Schema({
+  jobid: {
+    type: String,
+    required: true
+  },
+  trainstoragelocation: {
+    type: String,
+    required: true
+  },
+  trainclassid:{
+    type: String,
+    required: true
+  },
+  currentstation: {
+    type: String,
+    required: true
+  },
+  nextstation:{
+      type: String,
+      required: true
+  },
+  date: {
+    type: Date,
+    default: Date.now
+  }
+});
+
+const Train = mongoose.model('Train', TrainSchema);
+
+module.exports = Train;
\ No newline at end of file
diff --git a/StationSoftware/models/TransactionLog.js b/StationSoftware/models/TransactionLog.js
new file mode 100644
index 0000000000000000000000000000000000000000..f8d6df41d8dbaee85d51c26bcf0a8e124dc157dc
--- /dev/null
+++ b/StationSoftware/models/TransactionLog.js
@@ -0,0 +1,20 @@
+const mongoose = require('mongoose');
+
+const TransActionLogSchema = new mongoose.Schema({
+  date: {
+    type: Date,
+    default: Date.now,
+    required: true
+  },
+  user: {
+    type: String,
+  },
+  logMessage: {
+    type: String,
+    required: true
+  }
+})
+
+const TransactionLog = mongoose.model('TransactionLog', TransActionLogSchema)
+
+module.exports = TransactionLog
\ No newline at end of file
diff --git a/StationSoftware/models/User.js b/StationSoftware/models/User.js
new file mode 100644
index 0000000000000000000000000000000000000000..513cc109e3a42d96f24d10b902a455c73673b5be
--- /dev/null
+++ b/StationSoftware/models/User.js
@@ -0,0 +1,32 @@
+const mongoose = require('mongoose');
+
+const UserSchema = new mongoose.Schema({
+  firstname: {
+    type: String,
+    required: true
+  },
+  lastname: {
+    type: String,
+    required: true
+  },
+  email: {
+    type: String,
+    required: true
+  },
+  password: {
+    type: String,
+    required: true
+  },
+  role:{
+      type: String,
+      default:'guest'
+  },
+  date: {
+    type: Date,
+    default: Date.now
+  }
+});
+
+const User = mongoose.model('User', UserSchema);
+
+module.exports = User;
\ No newline at end of file
diff --git a/StationSoftware/package-lock.json b/StationSoftware/package-lock.json
new file mode 100644
index 0000000000000000000000000000000000000000..a25565b51dc2b2839b269a42a68b225798fcbcc0
--- /dev/null
+++ b/StationSoftware/package-lock.json
@@ -0,0 +1,4444 @@
+{
+  "name": "pht-web",
+  "version": "0.0.0",
+  "lockfileVersion": 2,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "pht-web",
+      "version": "0.0.0",
+      "dependencies": {
+        "axios": "^0.21.1",
+        "bcryptjs": "^2.4.3",
+        "connect-flash": "^0.1.1",
+        "cookie-parser": "~1.4.4",
+        "crypto": "^1.0.1",
+        "debug": "~2.6.9",
+        "dotenv": "^8.2.0",
+        "ejs": "~2.6.1",
+        "express": "~4.16.1",
+        "express-ejs-layouts": "^2.5.0",
+        "express-session": "^1.15.6",
+        "http-errors": "~1.6.3",
+        "mongoose": "^5.7.5",
+        "morgan": "~1.9.1",
+        "passport": "^0.4.0",
+        "passport-jwt": "^4.0.0",
+        "passport-local": "^1.0.0",
+        "promise-retry": "2.0.1",
+        "request": "^2.88.2"
+      },
+      "devDependencies": {
+        "nodemon": "^2.0.2"
+      }
+    },
+    "node_modules/abbrev": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+      "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
+      "dev": true
+    },
+    "node_modules/accepts": {
+      "version": "1.3.7",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
+      "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
+      "dependencies": {
+        "mime-types": "~2.1.24",
+        "negotiator": "0.6.2"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/ajv": {
+      "version": "6.12.0",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz",
+      "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==",
+      "dependencies": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      }
+    },
+    "node_modules/ansi-align": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz",
+      "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=",
+      "dev": true,
+      "dependencies": {
+        "string-width": "^2.0.0"
+      }
+    },
+    "node_modules/ansi-regex": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+      "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/ansi-styles": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+      "dev": true,
+      "dependencies": {
+        "color-convert": "^1.9.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/anymatch": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
+      "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
+      "dev": true,
+      "dependencies": {
+        "normalize-path": "^3.0.0",
+        "picomatch": "^2.0.4"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
+    },
+    "node_modules/asn1": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
+      "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
+      "dependencies": {
+        "safer-buffer": "~2.1.0"
+      }
+    },
+    "node_modules/assert-plus": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+      "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
+    },
+    "node_modules/aws-sign2": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+      "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/aws4": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz",
+      "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug=="
+    },
+    "node_modules/axios": {
+      "version": "0.21.1",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
+      "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
+      "dependencies": {
+        "follow-redirects": "^1.10.0"
+      }
+    },
+    "node_modules/balanced-match": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+      "dev": true
+    },
+    "node_modules/basic-auth": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
+      "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
+      "dependencies": {
+        "safe-buffer": "5.1.2"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/bcrypt-pbkdf": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+      "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+      "dependencies": {
+        "tweetnacl": "^0.14.3"
+      }
+    },
+    "node_modules/bcryptjs": {
+      "version": "2.4.3",
+      "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
+      "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms="
+    },
+    "node_modules/binary-extensions": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz",
+      "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/bl": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.0.tgz",
+      "integrity": "sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==",
+      "dependencies": {
+        "readable-stream": "^2.3.5",
+        "safe-buffer": "^5.1.1"
+      }
+    },
+    "node_modules/bluebird": {
+      "version": "3.5.1",
+      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz",
+      "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA=="
+    },
+    "node_modules/body-parser": {
+      "version": "1.18.3",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz",
+      "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=",
+      "dependencies": {
+        "bytes": "3.0.0",
+        "content-type": "~1.0.4",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "http-errors": "~1.6.3",
+        "iconv-lite": "0.4.23",
+        "on-finished": "~2.3.0",
+        "qs": "6.5.2",
+        "raw-body": "2.3.3",
+        "type-is": "~1.6.16"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/boxen": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz",
+      "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==",
+      "dev": true,
+      "dependencies": {
+        "ansi-align": "^2.0.0",
+        "camelcase": "^4.0.0",
+        "chalk": "^2.0.1",
+        "cli-boxes": "^1.0.0",
+        "string-width": "^2.0.0",
+        "term-size": "^1.2.0",
+        "widest-line": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "dependencies": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "node_modules/braces": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+      "dev": true,
+      "dependencies": {
+        "fill-range": "^7.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/bson": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.3.tgz",
+      "integrity": "sha512-TdiJxMVnodVS7r0BdL42y/pqC9cL2iKynVwA0Ho3qbsQYr428veL3l7BQyuqiw+Q5SqqoT0m4srSY/BlZ9AxXg==",
+      "engines": {
+        "node": ">=0.6.19"
+      }
+    },
+    "node_modules/buffer-equal-constant-time": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+      "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
+    },
+    "node_modules/bytes": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
+      "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/camelcase": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
+      "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/capture-stack-trace": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz",
+      "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/caseless": {
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+      "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
+    },
+    "node_modules/chalk": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+      "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^3.2.1",
+        "escape-string-regexp": "^1.0.5",
+        "supports-color": "^5.3.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/chokidar": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz",
+      "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==",
+      "dev": true,
+      "dependencies": {
+        "anymatch": "~3.1.1",
+        "braces": "~3.0.2",
+        "fsevents": "~2.1.2",
+        "glob-parent": "~5.1.0",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.3.0"
+      },
+      "engines": {
+        "node": ">= 8.10.0"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.1.2"
+      }
+    },
+    "node_modules/ci-info": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz",
+      "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==",
+      "dev": true
+    },
+    "node_modules/cli-boxes": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz",
+      "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/color-convert": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+      "dev": true,
+      "dependencies": {
+        "color-name": "1.1.3"
+      }
+    },
+    "node_modules/color-name": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+      "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+      "dev": true
+    },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+      "dev": true
+    },
+    "node_modules/configstore": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz",
+      "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==",
+      "dev": true,
+      "dependencies": {
+        "dot-prop": "^4.1.0",
+        "graceful-fs": "^4.1.2",
+        "make-dir": "^1.0.0",
+        "unique-string": "^1.0.0",
+        "write-file-atomic": "^2.0.0",
+        "xdg-basedir": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/connect-flash": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz",
+      "integrity": "sha1-2GMPJtlaf4UfmVax6MxnMvO2qjA=",
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
+    "node_modules/content-disposition": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
+      "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/content-type": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+      "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
+      "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie-parser": {
+      "version": "1.4.4",
+      "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.4.tgz",
+      "integrity": "sha512-lo13tqF3JEtFO7FyA49CqbhaFkskRJ0u/UAiINgrIXeRCY41c88/zxtrECl8AKH3B0hj9q10+h3Kt8I7KlW4tw==",
+      "dependencies": {
+        "cookie": "0.3.1",
+        "cookie-signature": "1.0.6"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/cookie-signature": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+      "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
+    },
+    "node_modules/core-util-is": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+    },
+    "node_modules/create-error-class": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz",
+      "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=",
+      "dev": true,
+      "dependencies": {
+        "capture-stack-trace": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/cross-spawn": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
+      "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
+      "dev": true,
+      "dependencies": {
+        "lru-cache": "^4.0.1",
+        "shebang-command": "^1.2.0",
+        "which": "^1.2.9"
+      }
+    },
+    "node_modules/crypto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
+      "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig=="
+    },
+    "node_modules/crypto-random-string": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz",
+      "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/dashdash": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+      "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+      "dependencies": {
+        "assert-plus": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
+    "node_modules/debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "dependencies": {
+        "ms": "2.0.0"
+      }
+    },
+    "node_modules/deep-extend": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+      "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+      "dev": true,
+      "engines": {
+        "node": ">=4.0.0"
+      }
+    },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/denque": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz",
+      "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==",
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
+    "node_modules/depd": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+      "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/destroy": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+      "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
+    },
+    "node_modules/dot-prop": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
+      "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==",
+      "dev": true,
+      "dependencies": {
+        "is-obj": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/dotenv": {
+      "version": "8.2.0",
+      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
+      "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/duplexer3": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
+      "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=",
+      "dev": true
+    },
+    "node_modules/ecc-jsbn": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+      "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+      "dependencies": {
+        "jsbn": "~0.1.0",
+        "safer-buffer": "^2.1.0"
+      }
+    },
+    "node_modules/ecdsa-sig-formatter": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+      "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+      "dependencies": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "node_modules/ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+    },
+    "node_modules/ejs": {
+      "version": "2.6.2",
+      "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.2.tgz",
+      "integrity": "sha512-PcW2a0tyTuPHz3tWyYqtK6r1fZ3gp+3Sop8Ph+ZYN81Ob5rwmbHEzaqs10N3BEsaGTkh/ooniXK+WwszGlc2+Q==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/err-code": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
+      "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="
+    },
+    "node_modules/escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
+    },
+    "node_modules/escape-string-regexp": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+      "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+      "dev": true,
+      "engines": {
+        "node": ">=0.8.0"
+      }
+    },
+    "node_modules/etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/execa": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
+      "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
+      "dev": true,
+      "dependencies": {
+        "cross-spawn": "^5.0.1",
+        "get-stream": "^3.0.0",
+        "is-stream": "^1.1.0",
+        "npm-run-path": "^2.0.0",
+        "p-finally": "^1.0.0",
+        "signal-exit": "^3.0.0",
+        "strip-eof": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/express": {
+      "version": "4.16.4",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz",
+      "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==",
+      "dependencies": {
+        "accepts": "~1.3.5",
+        "array-flatten": "1.1.1",
+        "body-parser": "1.18.3",
+        "content-disposition": "0.5.2",
+        "content-type": "~1.0.4",
+        "cookie": "0.3.1",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "finalhandler": "1.1.1",
+        "fresh": "0.5.2",
+        "merge-descriptors": "1.0.1",
+        "methods": "~1.1.2",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.2",
+        "path-to-regexp": "0.1.7",
+        "proxy-addr": "~2.0.4",
+        "qs": "6.5.2",
+        "range-parser": "~1.2.0",
+        "safe-buffer": "5.1.2",
+        "send": "0.16.2",
+        "serve-static": "1.13.2",
+        "setprototypeof": "1.1.0",
+        "statuses": "~1.4.0",
+        "type-is": "~1.6.16",
+        "utils-merge": "1.0.1",
+        "vary": "~1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.10.0"
+      }
+    },
+    "node_modules/express-ejs-layouts": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/express-ejs-layouts/-/express-ejs-layouts-2.5.0.tgz",
+      "integrity": "sha512-27Kza3FR8UqvQsq1ewaxC2IwpgrQttYDEFN5s8D74Fv1VPdzsXFWsiKhPMlNauG+DrgMAmh7FhQl5hKHffd+wQ=="
+    },
+    "node_modules/express-session": {
+      "version": "1.17.0",
+      "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.0.tgz",
+      "integrity": "sha512-t4oX2z7uoSqATbMfsxWMbNjAL0T5zpvcJCk3Z9wnPPN7ibddhnmDZXHfEcoBMG2ojKXZoCyPMc5FbtK+G7SoDg==",
+      "dependencies": {
+        "cookie": "0.4.0",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "~2.0.0",
+        "on-headers": "~1.0.2",
+        "parseurl": "~1.3.3",
+        "safe-buffer": "5.2.0",
+        "uid-safe": "~2.1.5"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/express-session/node_modules/cookie": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
+      "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/express-session/node_modules/depd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/express-session/node_modules/safe-buffer": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
+      "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
+    },
+    "node_modules/extend": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+    },
+    "node_modules/extsprintf": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+      "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
+      "engines": [
+        "node >=0.6.0"
+      ]
+    },
+    "node_modules/fast-deep-equal": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
+      "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA=="
+    },
+    "node_modules/fast-json-stable-stringify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
+    },
+    "node_modules/fill-range": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+      "dev": true,
+      "dependencies": {
+        "to-regex-range": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/finalhandler": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
+      "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
+      "dependencies": {
+        "debug": "2.6.9",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.2",
+        "statuses": "~1.4.0",
+        "unpipe": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/follow-redirects": {
+      "version": "1.13.2",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.2.tgz",
+      "integrity": "sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/RubenVerborgh"
+        }
+      ],
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/forever-agent": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+      "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/form-data": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+      "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.6",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 0.12"
+      }
+    },
+    "node_modules/forwarded": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
+      "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/fsevents": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz",
+      "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==",
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/get-stream": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
+      "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/getpass": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+      "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+      "dependencies": {
+        "assert-plus": "^1.0.0"
+      }
+    },
+    "node_modules/glob-parent": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz",
+      "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==",
+      "dev": true,
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/global-dirs": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz",
+      "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=",
+      "dev": true,
+      "dependencies": {
+        "ini": "^1.3.4"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/got": {
+      "version": "6.7.1",
+      "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz",
+      "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=",
+      "dev": true,
+      "dependencies": {
+        "create-error-class": "^3.0.0",
+        "duplexer3": "^0.1.4",
+        "get-stream": "^3.0.0",
+        "is-redirect": "^1.0.0",
+        "is-retry-allowed": "^1.0.0",
+        "is-stream": "^1.0.0",
+        "lowercase-keys": "^1.0.0",
+        "safe-buffer": "^5.0.1",
+        "timed-out": "^4.0.0",
+        "unzip-response": "^2.0.1",
+        "url-parse-lax": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/graceful-fs": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
+      "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==",
+      "dev": true
+    },
+    "node_modules/har-schema": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+      "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/har-validator": {
+      "version": "5.1.3",
+      "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
+      "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
+      "dependencies": {
+        "ajv": "^6.5.5",
+        "har-schema": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/has-flag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+      "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/http-errors": {
+      "version": "1.6.3",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
+      "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
+      "dependencies": {
+        "depd": "~1.1.2",
+        "inherits": "2.0.3",
+        "setprototypeof": "1.1.0",
+        "statuses": ">= 1.4.0 < 2"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/http-signature": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+      "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+      "dependencies": {
+        "assert-plus": "^1.0.0",
+        "jsprim": "^1.2.2",
+        "sshpk": "^1.7.0"
+      },
+      "engines": {
+        "node": ">=0.8",
+        "npm": ">=1.3.7"
+      }
+    },
+    "node_modules/iconv-lite": {
+      "version": "0.4.23",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
+      "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/ignore-by-default": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+      "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=",
+      "dev": true
+    },
+    "node_modules/import-lazy": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz",
+      "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/imurmurhash": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+      "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+      "dev": true,
+      "engines": {
+        "node": ">=0.8.19"
+      }
+    },
+    "node_modules/inherits": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+      "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+    },
+    "node_modules/ini": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
+      "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
+      "dev": true,
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/ipaddr.js": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
+      "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==",
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/is-binary-path": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "dev": true,
+      "dependencies": {
+        "binary-extensions": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/is-ci": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz",
+      "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==",
+      "dev": true,
+      "dependencies": {
+        "ci-info": "^1.5.0"
+      },
+      "bin": {
+        "is-ci": "bin.js"
+      }
+    },
+    "node_modules/is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-fullwidth-code-point": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+      "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/is-glob": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+      "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+      "dev": true,
+      "dependencies": {
+        "is-extglob": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-installed-globally": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz",
+      "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=",
+      "dev": true,
+      "dependencies": {
+        "global-dirs": "^0.1.0",
+        "is-path-inside": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/is-npm": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz",
+      "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-number": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.12.0"
+      }
+    },
+    "node_modules/is-obj": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
+      "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-path-inside": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz",
+      "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=",
+      "dev": true,
+      "dependencies": {
+        "path-is-inside": "^1.0.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-redirect": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz",
+      "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-retry-allowed": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz",
+      "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-stream": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+      "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-typedarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+      "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+    },
+    "node_modules/isarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+    },
+    "node_modules/isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+      "dev": true
+    },
+    "node_modules/isstream": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+      "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
+    },
+    "node_modules/jsbn": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+      "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
+    },
+    "node_modules/json-schema": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+      "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
+    },
+    "node_modules/json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
+    },
+    "node_modules/json-stringify-safe": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+      "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
+    },
+    "node_modules/jsonwebtoken": {
+      "version": "8.5.1",
+      "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
+      "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
+      "dependencies": {
+        "jws": "^3.2.2",
+        "lodash.includes": "^4.3.0",
+        "lodash.isboolean": "^3.0.3",
+        "lodash.isinteger": "^4.0.4",
+        "lodash.isnumber": "^3.0.3",
+        "lodash.isplainobject": "^4.0.6",
+        "lodash.isstring": "^4.0.1",
+        "lodash.once": "^4.0.0",
+        "ms": "^2.1.1",
+        "semver": "^5.6.0"
+      },
+      "engines": {
+        "node": ">=4",
+        "npm": ">=1.4.28"
+      }
+    },
+    "node_modules/jsonwebtoken/node_modules/ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+    },
+    "node_modules/jsprim": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
+      "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+      "engines": [
+        "node >=0.6.0"
+      ],
+      "dependencies": {
+        "assert-plus": "1.0.0",
+        "extsprintf": "1.3.0",
+        "json-schema": "0.2.3",
+        "verror": "1.10.0"
+      }
+    },
+    "node_modules/jwa": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+      "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+      "dependencies": {
+        "buffer-equal-constant-time": "1.0.1",
+        "ecdsa-sig-formatter": "1.0.11",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "node_modules/jws": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+      "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+      "dependencies": {
+        "jwa": "^1.4.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "node_modules/kareem": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz",
+      "integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw=="
+    },
+    "node_modules/latest-version": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz",
+      "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=",
+      "dev": true,
+      "dependencies": {
+        "package-json": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/lodash.includes": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+      "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8="
+    },
+    "node_modules/lodash.isboolean": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+      "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY="
+    },
+    "node_modules/lodash.isinteger": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+      "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M="
+    },
+    "node_modules/lodash.isnumber": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+      "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w="
+    },
+    "node_modules/lodash.isplainobject": {
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+      "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
+    },
+    "node_modules/lodash.isstring": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+      "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
+    },
+    "node_modules/lodash.once": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+      "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w="
+    },
+    "node_modules/lowercase-keys": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
+      "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/lru-cache": {
+      "version": "4.1.5",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
+      "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
+      "dev": true,
+      "dependencies": {
+        "pseudomap": "^1.0.2",
+        "yallist": "^2.1.2"
+      }
+    },
+    "node_modules/make-dir": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
+      "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==",
+      "dev": true,
+      "dependencies": {
+        "pify": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/memory-pager": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
+      "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
+      "optional": true
+    },
+    "node_modules/merge-descriptors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+      "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
+    },
+    "node_modules/methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
+      "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==",
+      "bin": {
+        "mime": "cli.js"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.43.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz",
+      "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.26",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz",
+      "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==",
+      "dependencies": {
+        "mime-db": "1.43.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/minimatch": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+      "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+      "dev": true,
+      "dependencies": {
+        "brace-expansion": "^1.1.7"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/minimist": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+      "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+      "dev": true
+    },
+    "node_modules/mongodb": {
+      "version": "3.5.3",
+      "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.5.3.tgz",
+      "integrity": "sha512-II7P7A3XUdPiXRgcN96qIoRa1oesM6qLNZkzfPluNZjVkgQk3jnQwOT6/uDk4USRDTTLjNFw2vwfmbRGTA7msg==",
+      "dependencies": {
+        "bl": "^2.2.0",
+        "bson": "^1.1.1",
+        "denque": "^1.4.1",
+        "require_optional": "^1.0.1",
+        "safe-buffer": "^5.1.2",
+        "saslprep": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      },
+      "optionalDependencies": {
+        "saslprep": "^1.0.0"
+      }
+    },
+    "node_modules/mongoose": {
+      "version": "5.9.2",
+      "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.9.2.tgz",
+      "integrity": "sha512-Sa1qfqBvUfAgsrXpZjbBoIx8PEDUJSKF5Ous8gnBFI7TPiueSgJjg6GRA7A0teU8AB/vd0h8rl1rD5RQNfWhIw==",
+      "dependencies": {
+        "bson": "~1.1.1",
+        "kareem": "2.3.1",
+        "mongodb": "3.5.3",
+        "mongoose-legacy-pluralize": "1.0.2",
+        "mpath": "0.6.0",
+        "mquery": "3.2.2",
+        "ms": "2.1.2",
+        "regexp-clone": "1.0.0",
+        "safe-buffer": "5.1.2",
+        "sift": "7.0.1",
+        "sliced": "1.0.1"
+      },
+      "engines": {
+        "node": ">=4.0.0"
+      }
+    },
+    "node_modules/mongoose-legacy-pluralize": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz",
+      "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ=="
+    },
+    "node_modules/mongoose/node_modules/ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+    },
+    "node_modules/morgan": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz",
+      "integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==",
+      "dependencies": {
+        "basic-auth": "~2.0.0",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "on-finished": "~2.3.0",
+        "on-headers": "~1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/mpath": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.6.0.tgz",
+      "integrity": "sha512-i75qh79MJ5Xo/sbhxrDrPSEG0H/mr1kcZXJ8dH6URU5jD/knFxCVqVC/gVSW7GIXL/9hHWlT9haLbCXWOll3qw==",
+      "engines": {
+        "node": ">=4.0.0"
+      }
+    },
+    "node_modules/mquery": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.2.tgz",
+      "integrity": "sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q==",
+      "dependencies": {
+        "bluebird": "3.5.1",
+        "debug": "3.1.0",
+        "regexp-clone": "^1.0.0",
+        "safe-buffer": "5.1.2",
+        "sliced": "1.0.1"
+      },
+      "engines": {
+        "node": ">=4.0.0"
+      }
+    },
+    "node_modules/mquery/node_modules/debug": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+      "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+      "dependencies": {
+        "ms": "2.0.0"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+    },
+    "node_modules/negotiator": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
+      "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/nodemon": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.2.tgz",
+      "integrity": "sha512-GWhYPMfde2+M0FsHnggIHXTqPDHXia32HRhh6H0d75Mt9FKUoCBvumNHr7LdrpPBTKxsWmIEOjoN+P4IU6Hcaw==",
+      "dev": true,
+      "dependencies": {
+        "chokidar": "^3.2.2",
+        "debug": "^3.2.6",
+        "ignore-by-default": "^1.0.1",
+        "minimatch": "^3.0.4",
+        "pstree.remy": "^1.1.7",
+        "semver": "^5.7.1",
+        "supports-color": "^5.5.0",
+        "touch": "^3.1.0",
+        "undefsafe": "^2.0.2",
+        "update-notifier": "^2.5.0"
+      },
+      "bin": {
+        "nodemon": "bin/nodemon.js"
+      },
+      "engines": {
+        "node": ">=8.10.0"
+      }
+    },
+    "node_modules/nodemon/node_modules/debug": {
+      "version": "3.2.6",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+      "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+      "dev": true,
+      "dependencies": {
+        "ms": "^2.1.1"
+      }
+    },
+    "node_modules/nodemon/node_modules/ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+      "dev": true
+    },
+    "node_modules/nopt": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
+      "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=",
+      "dev": true,
+      "dependencies": {
+        "abbrev": "1"
+      },
+      "bin": {
+        "nopt": "bin/nopt.js"
+      }
+    },
+    "node_modules/normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/npm-run-path": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
+      "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
+      "dev": true,
+      "dependencies": {
+        "path-key": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/oauth-sign": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+      "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/on-finished": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+      "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+      "dependencies": {
+        "ee-first": "1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/on-headers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
+      "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/p-finally": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+      "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/package-json": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz",
+      "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=",
+      "dev": true,
+      "dependencies": {
+        "got": "^6.7.1",
+        "registry-auth-token": "^3.0.1",
+        "registry-url": "^3.0.3",
+        "semver": "^5.1.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/passport": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/passport/-/passport-0.4.1.tgz",
+      "integrity": "sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg==",
+      "dependencies": {
+        "passport-strategy": "1.x.x",
+        "pause": "0.0.1"
+      },
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
+    "node_modules/passport-jwt": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.0.tgz",
+      "integrity": "sha512-BwC0n2GP/1hMVjR4QpnvqA61TxenUMlmfNjYNgK0ZAs0HK4SOQkHcSv4L328blNTLtHq7DbmvyNJiH+bn6C5Mg==",
+      "dependencies": {
+        "jsonwebtoken": "^8.2.0",
+        "passport-strategy": "^1.0.0"
+      }
+    },
+    "node_modules/passport-local": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz",
+      "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=",
+      "dependencies": {
+        "passport-strategy": "1.x.x"
+      },
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
+    "node_modules/passport-strategy": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
+      "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=",
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
+    "node_modules/path-is-inside": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+      "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
+      "dev": true
+    },
+    "node_modules/path-key": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+      "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/path-to-regexp": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+      "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
+    },
+    "node_modules/pause": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
+      "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10="
+    },
+    "node_modules/performance-now": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+      "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
+    },
+    "node_modules/picomatch": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz",
+      "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==",
+      "dev": true,
+      "engines": {
+        "node": ">=8.6"
+      }
+    },
+    "node_modules/pify": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+      "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/prepend-http": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
+      "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/process-nextick-args": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
+    },
+    "node_modules/promise-retry": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
+      "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
+      "dependencies": {
+        "err-code": "^2.0.2",
+        "retry": "^0.12.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/proxy-addr": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz",
+      "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==",
+      "dependencies": {
+        "forwarded": "~0.1.2",
+        "ipaddr.js": "1.9.0"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/pseudomap": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
+      "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
+      "dev": true
+    },
+    "node_modules/psl": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz",
+      "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ=="
+    },
+    "node_modules/pstree.remy": {
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.7.tgz",
+      "integrity": "sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A==",
+      "dev": true
+    },
+    "node_modules/punycode": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/qs": {
+      "version": "6.5.2",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+      "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
+      "engines": {
+        "node": ">=0.6"
+      }
+    },
+    "node_modules/random-bytes": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
+      "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/range-parser": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/raw-body": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz",
+      "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==",
+      "dependencies": {
+        "bytes": "3.0.0",
+        "http-errors": "1.6.3",
+        "iconv-lite": "0.4.23",
+        "unpipe": "1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/rc": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+      "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+      "dev": true,
+      "dependencies": {
+        "deep-extend": "^0.6.0",
+        "ini": "~1.3.0",
+        "minimist": "^1.2.0",
+        "strip-json-comments": "~2.0.1"
+      },
+      "bin": {
+        "rc": "cli.js"
+      }
+    },
+    "node_modules/readable-stream": {
+      "version": "2.3.7",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+      "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+      "dependencies": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.3",
+        "isarray": "~1.0.0",
+        "process-nextick-args": "~2.0.0",
+        "safe-buffer": "~5.1.1",
+        "string_decoder": "~1.1.1",
+        "util-deprecate": "~1.0.1"
+      }
+    },
+    "node_modules/readdirp": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz",
+      "integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==",
+      "dev": true,
+      "dependencies": {
+        "picomatch": "^2.0.7"
+      },
+      "engines": {
+        "node": ">=8.10.0"
+      }
+    },
+    "node_modules/regexp-clone": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz",
+      "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw=="
+    },
+    "node_modules/registry-auth-token": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz",
+      "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==",
+      "dev": true,
+      "dependencies": {
+        "rc": "^1.1.6",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "node_modules/registry-url": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz",
+      "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=",
+      "dev": true,
+      "dependencies": {
+        "rc": "^1.0.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/request": {
+      "version": "2.88.2",
+      "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
+      "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
+      "dependencies": {
+        "aws-sign2": "~0.7.0",
+        "aws4": "^1.8.0",
+        "caseless": "~0.12.0",
+        "combined-stream": "~1.0.6",
+        "extend": "~3.0.2",
+        "forever-agent": "~0.6.1",
+        "form-data": "~2.3.2",
+        "har-validator": "~5.1.3",
+        "http-signature": "~1.2.0",
+        "is-typedarray": "~1.0.0",
+        "isstream": "~0.1.2",
+        "json-stringify-safe": "~5.0.1",
+        "mime-types": "~2.1.19",
+        "oauth-sign": "~0.9.0",
+        "performance-now": "^2.1.0",
+        "qs": "~6.5.2",
+        "safe-buffer": "^5.1.2",
+        "tough-cookie": "~2.5.0",
+        "tunnel-agent": "^0.6.0",
+        "uuid": "^3.3.2"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/require_optional": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz",
+      "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==",
+      "dependencies": {
+        "resolve-from": "^2.0.0",
+        "semver": "^5.1.0"
+      }
+    },
+    "node_modules/resolve-from": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz",
+      "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/retry": {
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
+      "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=",
+      "engines": {
+        "node": ">= 4"
+      }
+    },
+    "node_modules/safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+    },
+    "node_modules/safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+    },
+    "node_modules/saslprep": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
+      "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
+      "optional": true,
+      "dependencies": {
+        "sparse-bitfield": "^3.0.3"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/semver": {
+      "version": "5.7.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+      "bin": {
+        "semver": "bin/semver"
+      }
+    },
+    "node_modules/semver-diff": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz",
+      "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=",
+      "dev": true,
+      "dependencies": {
+        "semver": "^5.0.3"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/send": {
+      "version": "0.16.2",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
+      "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==",
+      "dependencies": {
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "destroy": "~1.0.4",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "0.5.2",
+        "http-errors": "~1.6.2",
+        "mime": "1.4.1",
+        "ms": "2.0.0",
+        "on-finished": "~2.3.0",
+        "range-parser": "~1.2.0",
+        "statuses": "~1.4.0"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/serve-static": {
+      "version": "1.13.2",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz",
+      "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==",
+      "dependencies": {
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.2",
+        "send": "0.16.2"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/setprototypeof": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
+      "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
+    },
+    "node_modules/shebang-command": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+      "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+      "dev": true,
+      "dependencies": {
+        "shebang-regex": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/shebang-regex": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+      "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/sift": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/sift/-/sift-7.0.1.tgz",
+      "integrity": "sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g=="
+    },
+    "node_modules/signal-exit": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+      "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+      "dev": true
+    },
+    "node_modules/sliced": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz",
+      "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E="
+    },
+    "node_modules/sparse-bitfield": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
+      "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=",
+      "optional": true,
+      "dependencies": {
+        "memory-pager": "^1.0.2"
+      }
+    },
+    "node_modules/sshpk": {
+      "version": "1.16.1",
+      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
+      "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
+      "dependencies": {
+        "asn1": "~0.2.3",
+        "assert-plus": "^1.0.0",
+        "bcrypt-pbkdf": "^1.0.0",
+        "dashdash": "^1.12.0",
+        "ecc-jsbn": "~0.1.1",
+        "getpass": "^0.1.1",
+        "jsbn": "~0.1.0",
+        "safer-buffer": "^2.0.2",
+        "tweetnacl": "~0.14.0"
+      },
+      "bin": {
+        "sshpk-conv": "bin/sshpk-conv",
+        "sshpk-sign": "bin/sshpk-sign",
+        "sshpk-verify": "bin/sshpk-verify"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/statuses": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
+      "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/string_decoder": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "dependencies": {
+        "safe-buffer": "~5.1.0"
+      }
+    },
+    "node_modules/string-width": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+      "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+      "dev": true,
+      "dependencies": {
+        "is-fullwidth-code-point": "^2.0.0",
+        "strip-ansi": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/strip-ansi": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+      "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+      "dev": true,
+      "dependencies": {
+        "ansi-regex": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/strip-eof": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+      "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/strip-json-comments": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+      "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/supports-color": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+      "dev": true,
+      "dependencies": {
+        "has-flag": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/term-size": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz",
+      "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=",
+      "dev": true,
+      "dependencies": {
+        "execa": "^0.7.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/timed-out": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz",
+      "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "dev": true,
+      "dependencies": {
+        "is-number": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=8.0"
+      }
+    },
+    "node_modules/touch": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
+      "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
+      "dev": true,
+      "dependencies": {
+        "nopt": "~1.0.10"
+      },
+      "bin": {
+        "nodetouch": "bin/nodetouch.js"
+      }
+    },
+    "node_modules/tough-cookie": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
+      "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
+      "dependencies": {
+        "psl": "^1.1.28",
+        "punycode": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
+    "node_modules/tunnel-agent": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+      "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+      "dependencies": {
+        "safe-buffer": "^5.0.1"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/tweetnacl": {
+      "version": "0.14.5",
+      "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+      "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
+    },
+    "node_modules/type-is": {
+      "version": "1.6.18",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+      "dependencies": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/uid-safe": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
+      "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
+      "dependencies": {
+        "random-bytes": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/undefsafe": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz",
+      "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==",
+      "dev": true,
+      "dependencies": {
+        "debug": "^2.2.0"
+      }
+    },
+    "node_modules/unique-string": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz",
+      "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=",
+      "dev": true,
+      "dependencies": {
+        "crypto-random-string": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/unzip-response": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz",
+      "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/update-notifier": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz",
+      "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==",
+      "dev": true,
+      "dependencies": {
+        "boxen": "^1.2.1",
+        "chalk": "^2.0.1",
+        "configstore": "^3.0.0",
+        "import-lazy": "^2.1.0",
+        "is-ci": "^1.0.10",
+        "is-installed-globally": "^0.1.0",
+        "is-npm": "^1.0.0",
+        "latest-version": "^3.0.0",
+        "semver-diff": "^2.0.0",
+        "xdg-basedir": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/uri-js": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
+      "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
+      "dependencies": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "node_modules/url-parse-lax": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz",
+      "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=",
+      "dev": true,
+      "dependencies": {
+        "prepend-http": "^1.0.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+    },
+    "node_modules/utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
+    "node_modules/uuid": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+      "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
+      "bin": {
+        "uuid": "bin/uuid"
+      }
+    },
+    "node_modules/vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/verror": {
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+      "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+      "engines": [
+        "node >=0.6.0"
+      ],
+      "dependencies": {
+        "assert-plus": "^1.0.0",
+        "core-util-is": "1.0.2",
+        "extsprintf": "^1.2.0"
+      }
+    },
+    "node_modules/which": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+      "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+      "dev": true,
+      "dependencies": {
+        "isexe": "^2.0.0"
+      },
+      "bin": {
+        "which": "bin/which"
+      }
+    },
+    "node_modules/widest-line": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz",
+      "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==",
+      "dev": true,
+      "dependencies": {
+        "string-width": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/write-file-atomic": {
+      "version": "2.4.3",
+      "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz",
+      "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==",
+      "dev": true,
+      "dependencies": {
+        "graceful-fs": "^4.1.11",
+        "imurmurhash": "^0.1.4",
+        "signal-exit": "^3.0.2"
+      }
+    },
+    "node_modules/xdg-basedir": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz",
+      "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/yallist": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
+      "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
+      "dev": true
+    }
+  },
+  "dependencies": {
+    "abbrev": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+      "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
+      "dev": true
+    },
+    "accepts": {
+      "version": "1.3.7",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
+      "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
+      "requires": {
+        "mime-types": "~2.1.24",
+        "negotiator": "0.6.2"
+      }
+    },
+    "ajv": {
+      "version": "6.12.0",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz",
+      "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==",
+      "requires": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      }
+    },
+    "ansi-align": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz",
+      "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=",
+      "dev": true,
+      "requires": {
+        "string-width": "^2.0.0"
+      }
+    },
+    "ansi-regex": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+      "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+      "dev": true
+    },
+    "ansi-styles": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+      "dev": true,
+      "requires": {
+        "color-convert": "^1.9.0"
+      }
+    },
+    "anymatch": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
+      "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
+      "dev": true,
+      "requires": {
+        "normalize-path": "^3.0.0",
+        "picomatch": "^2.0.4"
+      }
+    },
+    "array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
+    },
+    "asn1": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
+      "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
+      "requires": {
+        "safer-buffer": "~2.1.0"
+      }
+    },
+    "assert-plus": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+      "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+    },
+    "asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
+    },
+    "aws-sign2": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+      "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
+    },
+    "aws4": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz",
+      "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug=="
+    },
+    "axios": {
+      "version": "0.21.1",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
+      "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
+      "requires": {
+        "follow-redirects": "^1.10.0"
+      }
+    },
+    "balanced-match": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+      "dev": true
+    },
+    "basic-auth": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
+      "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
+      "requires": {
+        "safe-buffer": "5.1.2"
+      }
+    },
+    "bcrypt-pbkdf": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+      "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+      "requires": {
+        "tweetnacl": "^0.14.3"
+      }
+    },
+    "bcryptjs": {
+      "version": "2.4.3",
+      "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
+      "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms="
+    },
+    "binary-extensions": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz",
+      "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
+      "dev": true
+    },
+    "bl": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.0.tgz",
+      "integrity": "sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==",
+      "requires": {
+        "readable-stream": "^2.3.5",
+        "safe-buffer": "^5.1.1"
+      }
+    },
+    "bluebird": {
+      "version": "3.5.1",
+      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz",
+      "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA=="
+    },
+    "body-parser": {
+      "version": "1.18.3",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz",
+      "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=",
+      "requires": {
+        "bytes": "3.0.0",
+        "content-type": "~1.0.4",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "http-errors": "~1.6.3",
+        "iconv-lite": "0.4.23",
+        "on-finished": "~2.3.0",
+        "qs": "6.5.2",
+        "raw-body": "2.3.3",
+        "type-is": "~1.6.16"
+      }
+    },
+    "boxen": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz",
+      "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==",
+      "dev": true,
+      "requires": {
+        "ansi-align": "^2.0.0",
+        "camelcase": "^4.0.0",
+        "chalk": "^2.0.1",
+        "cli-boxes": "^1.0.0",
+        "string-width": "^2.0.0",
+        "term-size": "^1.2.0",
+        "widest-line": "^2.0.0"
+      }
+    },
+    "brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "requires": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "braces": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+      "dev": true,
+      "requires": {
+        "fill-range": "^7.0.1"
+      }
+    },
+    "bson": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.3.tgz",
+      "integrity": "sha512-TdiJxMVnodVS7r0BdL42y/pqC9cL2iKynVwA0Ho3qbsQYr428veL3l7BQyuqiw+Q5SqqoT0m4srSY/BlZ9AxXg=="
+    },
+    "buffer-equal-constant-time": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+      "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
+    },
+    "bytes": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
+      "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
+    },
+    "camelcase": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
+      "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
+      "dev": true
+    },
+    "capture-stack-trace": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz",
+      "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==",
+      "dev": true
+    },
+    "caseless": {
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+      "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
+    },
+    "chalk": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+      "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+      "dev": true,
+      "requires": {
+        "ansi-styles": "^3.2.1",
+        "escape-string-regexp": "^1.0.5",
+        "supports-color": "^5.3.0"
+      }
+    },
+    "chokidar": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz",
+      "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==",
+      "dev": true,
+      "requires": {
+        "anymatch": "~3.1.1",
+        "braces": "~3.0.2",
+        "fsevents": "~2.1.2",
+        "glob-parent": "~5.1.0",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.3.0"
+      }
+    },
+    "ci-info": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz",
+      "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==",
+      "dev": true
+    },
+    "cli-boxes": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz",
+      "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=",
+      "dev": true
+    },
+    "color-convert": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+      "dev": true,
+      "requires": {
+        "color-name": "1.1.3"
+      }
+    },
+    "color-name": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+      "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+      "dev": true
+    },
+    "combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "requires": {
+        "delayed-stream": "~1.0.0"
+      }
+    },
+    "concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+      "dev": true
+    },
+    "configstore": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz",
+      "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==",
+      "dev": true,
+      "requires": {
+        "dot-prop": "^4.1.0",
+        "graceful-fs": "^4.1.2",
+        "make-dir": "^1.0.0",
+        "unique-string": "^1.0.0",
+        "write-file-atomic": "^2.0.0",
+        "xdg-basedir": "^3.0.0"
+      }
+    },
+    "connect-flash": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz",
+      "integrity": "sha1-2GMPJtlaf4UfmVax6MxnMvO2qjA="
+    },
+    "content-disposition": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
+      "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ="
+    },
+    "content-type": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+      "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
+    },
+    "cookie": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
+      "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
+    },
+    "cookie-parser": {
+      "version": "1.4.4",
+      "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.4.tgz",
+      "integrity": "sha512-lo13tqF3JEtFO7FyA49CqbhaFkskRJ0u/UAiINgrIXeRCY41c88/zxtrECl8AKH3B0hj9q10+h3Kt8I7KlW4tw==",
+      "requires": {
+        "cookie": "0.3.1",
+        "cookie-signature": "1.0.6"
+      }
+    },
+    "cookie-signature": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+      "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
+    },
+    "core-util-is": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+    },
+    "create-error-class": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz",
+      "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=",
+      "dev": true,
+      "requires": {
+        "capture-stack-trace": "^1.0.0"
+      }
+    },
+    "cross-spawn": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
+      "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
+      "dev": true,
+      "requires": {
+        "lru-cache": "^4.0.1",
+        "shebang-command": "^1.2.0",
+        "which": "^1.2.9"
+      }
+    },
+    "crypto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
+      "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig=="
+    },
+    "crypto-random-string": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz",
+      "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=",
+      "dev": true
+    },
+    "dashdash": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+      "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+      "requires": {
+        "assert-plus": "^1.0.0"
+      }
+    },
+    "debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "requires": {
+        "ms": "2.0.0"
+      }
+    },
+    "deep-extend": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+      "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+      "dev": true
+    },
+    "delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
+    },
+    "denque": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz",
+      "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ=="
+    },
+    "depd": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+      "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
+    },
+    "destroy": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+      "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
+    },
+    "dot-prop": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
+      "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==",
+      "dev": true,
+      "requires": {
+        "is-obj": "^1.0.0"
+      }
+    },
+    "dotenv": {
+      "version": "8.2.0",
+      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
+      "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw=="
+    },
+    "duplexer3": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
+      "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=",
+      "dev": true
+    },
+    "ecc-jsbn": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+      "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+      "requires": {
+        "jsbn": "~0.1.0",
+        "safer-buffer": "^2.1.0"
+      }
+    },
+    "ecdsa-sig-formatter": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+      "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+      "requires": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+    },
+    "ejs": {
+      "version": "2.6.2",
+      "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.2.tgz",
+      "integrity": "sha512-PcW2a0tyTuPHz3tWyYqtK6r1fZ3gp+3Sop8Ph+ZYN81Ob5rwmbHEzaqs10N3BEsaGTkh/ooniXK+WwszGlc2+Q=="
+    },
+    "encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
+    },
+    "err-code": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
+      "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="
+    },
+    "escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
+    },
+    "escape-string-regexp": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+      "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+      "dev": true
+    },
+    "etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
+    },
+    "execa": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
+      "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
+      "dev": true,
+      "requires": {
+        "cross-spawn": "^5.0.1",
+        "get-stream": "^3.0.0",
+        "is-stream": "^1.1.0",
+        "npm-run-path": "^2.0.0",
+        "p-finally": "^1.0.0",
+        "signal-exit": "^3.0.0",
+        "strip-eof": "^1.0.0"
+      }
+    },
+    "express": {
+      "version": "4.16.4",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz",
+      "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==",
+      "requires": {
+        "accepts": "~1.3.5",
+        "array-flatten": "1.1.1",
+        "body-parser": "1.18.3",
+        "content-disposition": "0.5.2",
+        "content-type": "~1.0.4",
+        "cookie": "0.3.1",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "finalhandler": "1.1.1",
+        "fresh": "0.5.2",
+        "merge-descriptors": "1.0.1",
+        "methods": "~1.1.2",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.2",
+        "path-to-regexp": "0.1.7",
+        "proxy-addr": "~2.0.4",
+        "qs": "6.5.2",
+        "range-parser": "~1.2.0",
+        "safe-buffer": "5.1.2",
+        "send": "0.16.2",
+        "serve-static": "1.13.2",
+        "setprototypeof": "1.1.0",
+        "statuses": "~1.4.0",
+        "type-is": "~1.6.16",
+        "utils-merge": "1.0.1",
+        "vary": "~1.1.2"
+      }
+    },
+    "express-ejs-layouts": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/express-ejs-layouts/-/express-ejs-layouts-2.5.0.tgz",
+      "integrity": "sha512-27Kza3FR8UqvQsq1ewaxC2IwpgrQttYDEFN5s8D74Fv1VPdzsXFWsiKhPMlNauG+DrgMAmh7FhQl5hKHffd+wQ=="
+    },
+    "express-session": {
+      "version": "1.17.0",
+      "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.0.tgz",
+      "integrity": "sha512-t4oX2z7uoSqATbMfsxWMbNjAL0T5zpvcJCk3Z9wnPPN7ibddhnmDZXHfEcoBMG2ojKXZoCyPMc5FbtK+G7SoDg==",
+      "requires": {
+        "cookie": "0.4.0",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "~2.0.0",
+        "on-headers": "~1.0.2",
+        "parseurl": "~1.3.3",
+        "safe-buffer": "5.2.0",
+        "uid-safe": "~2.1.5"
+      },
+      "dependencies": {
+        "cookie": {
+          "version": "0.4.0",
+          "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
+          "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
+        },
+        "depd": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+          "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
+        },
+        "safe-buffer": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
+          "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
+        }
+      }
+    },
+    "extend": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+    },
+    "extsprintf": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+      "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
+    },
+    "fast-deep-equal": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
+      "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA=="
+    },
+    "fast-json-stable-stringify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
+    },
+    "fill-range": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+      "dev": true,
+      "requires": {
+        "to-regex-range": "^5.0.1"
+      }
+    },
+    "finalhandler": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
+      "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
+      "requires": {
+        "debug": "2.6.9",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.2",
+        "statuses": "~1.4.0",
+        "unpipe": "~1.0.0"
+      }
+    },
+    "follow-redirects": {
+      "version": "1.13.2",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.2.tgz",
+      "integrity": "sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA=="
+    },
+    "forever-agent": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+      "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
+    },
+    "form-data": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+      "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+      "requires": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.6",
+        "mime-types": "^2.1.12"
+      }
+    },
+    "forwarded": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
+      "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
+    },
+    "fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
+    },
+    "fsevents": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz",
+      "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==",
+      "dev": true,
+      "optional": true
+    },
+    "get-stream": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
+      "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
+      "dev": true
+    },
+    "getpass": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+      "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+      "requires": {
+        "assert-plus": "^1.0.0"
+      }
+    },
+    "glob-parent": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz",
+      "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==",
+      "dev": true,
+      "requires": {
+        "is-glob": "^4.0.1"
+      }
+    },
+    "global-dirs": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz",
+      "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=",
+      "dev": true,
+      "requires": {
+        "ini": "^1.3.4"
+      }
+    },
+    "got": {
+      "version": "6.7.1",
+      "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz",
+      "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=",
+      "dev": true,
+      "requires": {
+        "create-error-class": "^3.0.0",
+        "duplexer3": "^0.1.4",
+        "get-stream": "^3.0.0",
+        "is-redirect": "^1.0.0",
+        "is-retry-allowed": "^1.0.0",
+        "is-stream": "^1.0.0",
+        "lowercase-keys": "^1.0.0",
+        "safe-buffer": "^5.0.1",
+        "timed-out": "^4.0.0",
+        "unzip-response": "^2.0.1",
+        "url-parse-lax": "^1.0.0"
+      }
+    },
+    "graceful-fs": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
+      "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==",
+      "dev": true
+    },
+    "har-schema": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+      "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
+    },
+    "har-validator": {
+      "version": "5.1.3",
+      "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
+      "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
+      "requires": {
+        "ajv": "^6.5.5",
+        "har-schema": "^2.0.0"
+      }
+    },
+    "has-flag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+      "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+      "dev": true
+    },
+    "http-errors": {
+      "version": "1.6.3",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
+      "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
+      "requires": {
+        "depd": "~1.1.2",
+        "inherits": "2.0.3",
+        "setprototypeof": "1.1.0",
+        "statuses": ">= 1.4.0 < 2"
+      }
+    },
+    "http-signature": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+      "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+      "requires": {
+        "assert-plus": "^1.0.0",
+        "jsprim": "^1.2.2",
+        "sshpk": "^1.7.0"
+      }
+    },
+    "iconv-lite": {
+      "version": "0.4.23",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
+      "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
+      "requires": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      }
+    },
+    "ignore-by-default": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+      "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=",
+      "dev": true
+    },
+    "import-lazy": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz",
+      "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=",
+      "dev": true
+    },
+    "imurmurhash": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+      "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+      "dev": true
+    },
+    "inherits": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+      "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+    },
+    "ini": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
+      "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
+      "dev": true
+    },
+    "ipaddr.js": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
+      "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA=="
+    },
+    "is-binary-path": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "dev": true,
+      "requires": {
+        "binary-extensions": "^2.0.0"
+      }
+    },
+    "is-ci": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz",
+      "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==",
+      "dev": true,
+      "requires": {
+        "ci-info": "^1.5.0"
+      }
+    },
+    "is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+      "dev": true
+    },
+    "is-fullwidth-code-point": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+      "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+      "dev": true
+    },
+    "is-glob": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+      "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+      "dev": true,
+      "requires": {
+        "is-extglob": "^2.1.1"
+      }
+    },
+    "is-installed-globally": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz",
+      "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=",
+      "dev": true,
+      "requires": {
+        "global-dirs": "^0.1.0",
+        "is-path-inside": "^1.0.0"
+      }
+    },
+    "is-npm": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz",
+      "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=",
+      "dev": true
+    },
+    "is-number": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "dev": true
+    },
+    "is-obj": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
+      "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
+      "dev": true
+    },
+    "is-path-inside": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz",
+      "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=",
+      "dev": true,
+      "requires": {
+        "path-is-inside": "^1.0.1"
+      }
+    },
+    "is-redirect": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz",
+      "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=",
+      "dev": true
+    },
+    "is-retry-allowed": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz",
+      "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==",
+      "dev": true
+    },
+    "is-stream": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+      "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+      "dev": true
+    },
+    "is-typedarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+      "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+    },
+    "isarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+    },
+    "isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+      "dev": true
+    },
+    "isstream": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+      "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
+    },
+    "jsbn": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+      "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
+    },
+    "json-schema": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+      "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
+    },
+    "json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
+    },
+    "json-stringify-safe": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+      "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
+    },
+    "jsonwebtoken": {
+      "version": "8.5.1",
+      "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
+      "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
+      "requires": {
+        "jws": "^3.2.2",
+        "lodash.includes": "^4.3.0",
+        "lodash.isboolean": "^3.0.3",
+        "lodash.isinteger": "^4.0.4",
+        "lodash.isnumber": "^3.0.3",
+        "lodash.isplainobject": "^4.0.6",
+        "lodash.isstring": "^4.0.1",
+        "lodash.once": "^4.0.0",
+        "ms": "^2.1.1",
+        "semver": "^5.6.0"
+      },
+      "dependencies": {
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        }
+      }
+    },
+    "jsprim": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
+      "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+      "requires": {
+        "assert-plus": "1.0.0",
+        "extsprintf": "1.3.0",
+        "json-schema": "0.2.3",
+        "verror": "1.10.0"
+      }
+    },
+    "jwa": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+      "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+      "requires": {
+        "buffer-equal-constant-time": "1.0.1",
+        "ecdsa-sig-formatter": "1.0.11",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "jws": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+      "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+      "requires": {
+        "jwa": "^1.4.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "kareem": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz",
+      "integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw=="
+    },
+    "latest-version": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz",
+      "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=",
+      "dev": true,
+      "requires": {
+        "package-json": "^4.0.0"
+      }
+    },
+    "lodash.includes": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+      "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8="
+    },
+    "lodash.isboolean": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+      "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY="
+    },
+    "lodash.isinteger": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+      "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M="
+    },
+    "lodash.isnumber": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+      "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w="
+    },
+    "lodash.isplainobject": {
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+      "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
+    },
+    "lodash.isstring": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+      "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
+    },
+    "lodash.once": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+      "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w="
+    },
+    "lowercase-keys": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
+      "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==",
+      "dev": true
+    },
+    "lru-cache": {
+      "version": "4.1.5",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
+      "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
+      "dev": true,
+      "requires": {
+        "pseudomap": "^1.0.2",
+        "yallist": "^2.1.2"
+      }
+    },
+    "make-dir": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
+      "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==",
+      "dev": true,
+      "requires": {
+        "pify": "^3.0.0"
+      }
+    },
+    "media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
+    },
+    "memory-pager": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
+      "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
+      "optional": true
+    },
+    "merge-descriptors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+      "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
+    },
+    "methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
+    },
+    "mime": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
+      "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ=="
+    },
+    "mime-db": {
+      "version": "1.43.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz",
+      "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ=="
+    },
+    "mime-types": {
+      "version": "2.1.26",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz",
+      "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==",
+      "requires": {
+        "mime-db": "1.43.0"
+      }
+    },
+    "minimatch": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+      "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+      "dev": true,
+      "requires": {
+        "brace-expansion": "^1.1.7"
+      }
+    },
+    "minimist": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+      "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+      "dev": true
+    },
+    "mongodb": {
+      "version": "3.5.3",
+      "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.5.3.tgz",
+      "integrity": "sha512-II7P7A3XUdPiXRgcN96qIoRa1oesM6qLNZkzfPluNZjVkgQk3jnQwOT6/uDk4USRDTTLjNFw2vwfmbRGTA7msg==",
+      "requires": {
+        "bl": "^2.2.0",
+        "bson": "^1.1.1",
+        "denque": "^1.4.1",
+        "require_optional": "^1.0.1",
+        "safe-buffer": "^5.1.2",
+        "saslprep": "^1.0.0"
+      }
+    },
+    "mongoose": {
+      "version": "5.9.2",
+      "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.9.2.tgz",
+      "integrity": "sha512-Sa1qfqBvUfAgsrXpZjbBoIx8PEDUJSKF5Ous8gnBFI7TPiueSgJjg6GRA7A0teU8AB/vd0h8rl1rD5RQNfWhIw==",
+      "requires": {
+        "bson": "~1.1.1",
+        "kareem": "2.3.1",
+        "mongodb": "3.5.3",
+        "mongoose-legacy-pluralize": "1.0.2",
+        "mpath": "0.6.0",
+        "mquery": "3.2.2",
+        "ms": "2.1.2",
+        "regexp-clone": "1.0.0",
+        "safe-buffer": "5.1.2",
+        "sift": "7.0.1",
+        "sliced": "1.0.1"
+      },
+      "dependencies": {
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        }
+      }
+    },
+    "mongoose-legacy-pluralize": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz",
+      "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ=="
+    },
+    "morgan": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz",
+      "integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==",
+      "requires": {
+        "basic-auth": "~2.0.0",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "on-finished": "~2.3.0",
+        "on-headers": "~1.0.1"
+      }
+    },
+    "mpath": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.6.0.tgz",
+      "integrity": "sha512-i75qh79MJ5Xo/sbhxrDrPSEG0H/mr1kcZXJ8dH6URU5jD/knFxCVqVC/gVSW7GIXL/9hHWlT9haLbCXWOll3qw=="
+    },
+    "mquery": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.2.tgz",
+      "integrity": "sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q==",
+      "requires": {
+        "bluebird": "3.5.1",
+        "debug": "3.1.0",
+        "regexp-clone": "^1.0.0",
+        "safe-buffer": "5.1.2",
+        "sliced": "1.0.1"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+          "requires": {
+            "ms": "2.0.0"
+          }
+        }
+      }
+    },
+    "ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+    },
+    "negotiator": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
+      "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
+    },
+    "nodemon": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.2.tgz",
+      "integrity": "sha512-GWhYPMfde2+M0FsHnggIHXTqPDHXia32HRhh6H0d75Mt9FKUoCBvumNHr7LdrpPBTKxsWmIEOjoN+P4IU6Hcaw==",
+      "dev": true,
+      "requires": {
+        "chokidar": "^3.2.2",
+        "debug": "^3.2.6",
+        "ignore-by-default": "^1.0.1",
+        "minimatch": "^3.0.4",
+        "pstree.remy": "^1.1.7",
+        "semver": "^5.7.1",
+        "supports-color": "^5.5.0",
+        "touch": "^3.1.0",
+        "undefsafe": "^2.0.2",
+        "update-notifier": "^2.5.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.2.6",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+          "dev": true
+        }
+      }
+    },
+    "nopt": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
+      "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=",
+      "dev": true,
+      "requires": {
+        "abbrev": "1"
+      }
+    },
+    "normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "dev": true
+    },
+    "npm-run-path": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
+      "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
+      "dev": true,
+      "requires": {
+        "path-key": "^2.0.0"
+      }
+    },
+    "oauth-sign": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+      "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
+    },
+    "on-finished": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+      "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+      "requires": {
+        "ee-first": "1.1.1"
+      }
+    },
+    "on-headers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
+      "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="
+    },
+    "p-finally": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+      "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
+      "dev": true
+    },
+    "package-json": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz",
+      "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=",
+      "dev": true,
+      "requires": {
+        "got": "^6.7.1",
+        "registry-auth-token": "^3.0.1",
+        "registry-url": "^3.0.3",
+        "semver": "^5.1.0"
+      }
+    },
+    "parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
+    },
+    "passport": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/passport/-/passport-0.4.1.tgz",
+      "integrity": "sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg==",
+      "requires": {
+        "passport-strategy": "1.x.x",
+        "pause": "0.0.1"
+      }
+    },
+    "passport-jwt": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.0.tgz",
+      "integrity": "sha512-BwC0n2GP/1hMVjR4QpnvqA61TxenUMlmfNjYNgK0ZAs0HK4SOQkHcSv4L328blNTLtHq7DbmvyNJiH+bn6C5Mg==",
+      "requires": {
+        "jsonwebtoken": "^8.2.0",
+        "passport-strategy": "^1.0.0"
+      }
+    },
+    "passport-local": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz",
+      "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=",
+      "requires": {
+        "passport-strategy": "1.x.x"
+      }
+    },
+    "passport-strategy": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
+      "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ="
+    },
+    "path-is-inside": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+      "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
+      "dev": true
+    },
+    "path-key": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+      "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+      "dev": true
+    },
+    "path-to-regexp": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+      "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
+    },
+    "pause": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
+      "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10="
+    },
+    "performance-now": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+      "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
+    },
+    "picomatch": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz",
+      "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==",
+      "dev": true
+    },
+    "pify": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+      "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+      "dev": true
+    },
+    "prepend-http": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
+      "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=",
+      "dev": true
+    },
+    "process-nextick-args": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
+    },
+    "promise-retry": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
+      "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
+      "requires": {
+        "err-code": "^2.0.2",
+        "retry": "^0.12.0"
+      }
+    },
+    "proxy-addr": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz",
+      "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==",
+      "requires": {
+        "forwarded": "~0.1.2",
+        "ipaddr.js": "1.9.0"
+      }
+    },
+    "pseudomap": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
+      "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
+      "dev": true
+    },
+    "psl": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz",
+      "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ=="
+    },
+    "pstree.remy": {
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.7.tgz",
+      "integrity": "sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A==",
+      "dev": true
+    },
+    "punycode": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
+    },
+    "qs": {
+      "version": "6.5.2",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+      "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
+    },
+    "random-bytes": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
+      "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs="
+    },
+    "range-parser": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
+    },
+    "raw-body": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz",
+      "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==",
+      "requires": {
+        "bytes": "3.0.0",
+        "http-errors": "1.6.3",
+        "iconv-lite": "0.4.23",
+        "unpipe": "1.0.0"
+      }
+    },
+    "rc": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+      "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+      "dev": true,
+      "requires": {
+        "deep-extend": "^0.6.0",
+        "ini": "~1.3.0",
+        "minimist": "^1.2.0",
+        "strip-json-comments": "~2.0.1"
+      }
+    },
+    "readable-stream": {
+      "version": "2.3.7",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+      "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+      "requires": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.3",
+        "isarray": "~1.0.0",
+        "process-nextick-args": "~2.0.0",
+        "safe-buffer": "~5.1.1",
+        "string_decoder": "~1.1.1",
+        "util-deprecate": "~1.0.1"
+      }
+    },
+    "readdirp": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz",
+      "integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==",
+      "dev": true,
+      "requires": {
+        "picomatch": "^2.0.7"
+      }
+    },
+    "regexp-clone": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz",
+      "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw=="
+    },
+    "registry-auth-token": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz",
+      "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==",
+      "dev": true,
+      "requires": {
+        "rc": "^1.1.6",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "registry-url": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz",
+      "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=",
+      "dev": true,
+      "requires": {
+        "rc": "^1.0.1"
+      }
+    },
+    "request": {
+      "version": "2.88.2",
+      "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
+      "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
+      "requires": {
+        "aws-sign2": "~0.7.0",
+        "aws4": "^1.8.0",
+        "caseless": "~0.12.0",
+        "combined-stream": "~1.0.6",
+        "extend": "~3.0.2",
+        "forever-agent": "~0.6.1",
+        "form-data": "~2.3.2",
+        "har-validator": "~5.1.3",
+        "http-signature": "~1.2.0",
+        "is-typedarray": "~1.0.0",
+        "isstream": "~0.1.2",
+        "json-stringify-safe": "~5.0.1",
+        "mime-types": "~2.1.19",
+        "oauth-sign": "~0.9.0",
+        "performance-now": "^2.1.0",
+        "qs": "~6.5.2",
+        "safe-buffer": "^5.1.2",
+        "tough-cookie": "~2.5.0",
+        "tunnel-agent": "^0.6.0",
+        "uuid": "^3.3.2"
+      }
+    },
+    "require_optional": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz",
+      "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==",
+      "requires": {
+        "resolve-from": "^2.0.0",
+        "semver": "^5.1.0"
+      }
+    },
+    "resolve-from": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz",
+      "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c="
+    },
+    "retry": {
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
+      "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs="
+    },
+    "safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+    },
+    "safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+    },
+    "saslprep": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
+      "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
+      "optional": true,
+      "requires": {
+        "sparse-bitfield": "^3.0.3"
+      }
+    },
+    "semver": {
+      "version": "5.7.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+    },
+    "semver-diff": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz",
+      "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=",
+      "dev": true,
+      "requires": {
+        "semver": "^5.0.3"
+      }
+    },
+    "send": {
+      "version": "0.16.2",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
+      "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==",
+      "requires": {
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "destroy": "~1.0.4",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "0.5.2",
+        "http-errors": "~1.6.2",
+        "mime": "1.4.1",
+        "ms": "2.0.0",
+        "on-finished": "~2.3.0",
+        "range-parser": "~1.2.0",
+        "statuses": "~1.4.0"
+      }
+    },
+    "serve-static": {
+      "version": "1.13.2",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz",
+      "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==",
+      "requires": {
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.2",
+        "send": "0.16.2"
+      }
+    },
+    "setprototypeof": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
+      "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
+    },
+    "shebang-command": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+      "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+      "dev": true,
+      "requires": {
+        "shebang-regex": "^1.0.0"
+      }
+    },
+    "shebang-regex": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+      "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+      "dev": true
+    },
+    "sift": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/sift/-/sift-7.0.1.tgz",
+      "integrity": "sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g=="
+    },
+    "signal-exit": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+      "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+      "dev": true
+    },
+    "sliced": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz",
+      "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E="
+    },
+    "sparse-bitfield": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
+      "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=",
+      "optional": true,
+      "requires": {
+        "memory-pager": "^1.0.2"
+      }
+    },
+    "sshpk": {
+      "version": "1.16.1",
+      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
+      "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
+      "requires": {
+        "asn1": "~0.2.3",
+        "assert-plus": "^1.0.0",
+        "bcrypt-pbkdf": "^1.0.0",
+        "dashdash": "^1.12.0",
+        "ecc-jsbn": "~0.1.1",
+        "getpass": "^0.1.1",
+        "jsbn": "~0.1.0",
+        "safer-buffer": "^2.0.2",
+        "tweetnacl": "~0.14.0"
+      }
+    },
+    "statuses": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
+      "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
+    },
+    "string_decoder": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "requires": {
+        "safe-buffer": "~5.1.0"
+      }
+    },
+    "string-width": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+      "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+      "dev": true,
+      "requires": {
+        "is-fullwidth-code-point": "^2.0.0",
+        "strip-ansi": "^4.0.0"
+      }
+    },
+    "strip-ansi": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+      "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+      "dev": true,
+      "requires": {
+        "ansi-regex": "^3.0.0"
+      }
+    },
+    "strip-eof": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+      "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
+      "dev": true
+    },
+    "strip-json-comments": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+      "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
+      "dev": true
+    },
+    "supports-color": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+      "dev": true,
+      "requires": {
+        "has-flag": "^3.0.0"
+      }
+    },
+    "term-size": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz",
+      "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=",
+      "dev": true,
+      "requires": {
+        "execa": "^0.7.0"
+      }
+    },
+    "timed-out": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz",
+      "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=",
+      "dev": true
+    },
+    "to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "dev": true,
+      "requires": {
+        "is-number": "^7.0.0"
+      }
+    },
+    "touch": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
+      "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
+      "dev": true,
+      "requires": {
+        "nopt": "~1.0.10"
+      }
+    },
+    "tough-cookie": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
+      "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
+      "requires": {
+        "psl": "^1.1.28",
+        "punycode": "^2.1.1"
+      }
+    },
+    "tunnel-agent": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+      "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+      "requires": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "tweetnacl": {
+      "version": "0.14.5",
+      "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+      "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
+    },
+    "type-is": {
+      "version": "1.6.18",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+      "requires": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
+      }
+    },
+    "uid-safe": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
+      "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
+      "requires": {
+        "random-bytes": "~1.0.0"
+      }
+    },
+    "undefsafe": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz",
+      "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==",
+      "dev": true,
+      "requires": {
+        "debug": "^2.2.0"
+      }
+    },
+    "unique-string": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz",
+      "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=",
+      "dev": true,
+      "requires": {
+        "crypto-random-string": "^1.0.0"
+      }
+    },
+    "unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
+    },
+    "unzip-response": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz",
+      "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=",
+      "dev": true
+    },
+    "update-notifier": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz",
+      "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==",
+      "dev": true,
+      "requires": {
+        "boxen": "^1.2.1",
+        "chalk": "^2.0.1",
+        "configstore": "^3.0.0",
+        "import-lazy": "^2.1.0",
+        "is-ci": "^1.0.10",
+        "is-installed-globally": "^0.1.0",
+        "is-npm": "^1.0.0",
+        "latest-version": "^3.0.0",
+        "semver-diff": "^2.0.0",
+        "xdg-basedir": "^3.0.0"
+      }
+    },
+    "uri-js": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
+      "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
+      "requires": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "url-parse-lax": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz",
+      "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=",
+      "dev": true,
+      "requires": {
+        "prepend-http": "^1.0.1"
+      }
+    },
+    "util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+    },
+    "utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
+    },
+    "uuid": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+      "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
+    },
+    "vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
+    },
+    "verror": {
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+      "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+      "requires": {
+        "assert-plus": "^1.0.0",
+        "core-util-is": "1.0.2",
+        "extsprintf": "^1.2.0"
+      }
+    },
+    "which": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+      "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+      "dev": true,
+      "requires": {
+        "isexe": "^2.0.0"
+      }
+    },
+    "widest-line": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz",
+      "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==",
+      "dev": true,
+      "requires": {
+        "string-width": "^2.1.1"
+      }
+    },
+    "write-file-atomic": {
+      "version": "2.4.3",
+      "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz",
+      "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.11",
+        "imurmurhash": "^0.1.4",
+        "signal-exit": "^3.0.2"
+      }
+    },
+    "xdg-basedir": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz",
+      "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=",
+      "dev": true
+    },
+    "yallist": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
+      "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
+      "dev": true
+    }
+  }
+}
diff --git a/StationSoftware/package.json b/StationSoftware/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..13c53066934618434649c282965a70cdf2b76009
--- /dev/null
+++ b/StationSoftware/package.json
@@ -0,0 +1,38 @@
+{
+  "name": "pht-web",
+  "version": "0.0.0",
+  "private": true,
+  "scripts": {
+    "start": "node ./bin/www",
+    "dev": "nodemon ./bin/www"
+  },
+  "dependencies": {
+    "axios": "^0.21.1",
+    "bcryptjs": "^2.4.3",
+    "connect-flash": "^0.1.1",
+    "cookie-parser": "~1.4.4",
+    "crypto": "^1.0.1",
+    "debug": "~2.6.9",
+    "diff2html": "^3.4.4",
+    "dotenv": "^8.2.0",
+    "ejs": "~2.6.1",
+    "express": "~4.16.1",
+    "express-ejs-layouts": "^2.5.0",
+    "express-session": "^1.15.6",
+    "http-errors": "~1.6.3",
+    "mongoose": "^5.7.5",
+    "morgan": "~1.9.1",
+    "node-vault": "^0.9.21",
+    "passport": "^0.4.0",
+    "passport-jwt": "^4.0.0",
+    "passport-local": "^1.0.0",
+    "promise-retry": "2.0.1",
+    "request": "^2.88.2",
+    "request-promise": "^4.2.6",
+    "tar": "^6.1.0",
+    "tar-stream": "^2.2.0"
+  },
+  "devDependencies": {
+    "nodemon": "^2.0.2"
+  }
+}
\ No newline at end of file
diff --git a/StationSoftware/public/icon.png b/StationSoftware/public/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..d096699d47fafe54fb93d42d7250e7e812044d59
Binary files /dev/null and b/StationSoftware/public/icon.png differ
diff --git a/StationSoftware/public/logo.png b/StationSoftware/public/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..7beded61ba5d6df9951d11ffb456d54b7c7156a6
Binary files /dev/null and b/StationSoftware/public/logo.png differ
diff --git a/StationSoftware/public/stylesheets/style.css b/StationSoftware/public/stylesheets/style.css
new file mode 100644
index 0000000000000000000000000000000000000000..4524eab06c4d951899a9d80956a8aeced060514d
--- /dev/null
+++ b/StationSoftware/public/stylesheets/style.css
@@ -0,0 +1,119 @@
+#sidebar {
+  overflow: hidden;
+  z-index: 3;
+}
+#sidebar .list-group {
+  min-width: 300px;
+  min-height: 100vh;
+}
+
+#sidebar .list-group-item {
+  border-radius: 0;
+  white-space: nowrap;
+}
+
+@media (max-width:768px) {
+  #sidebar {
+      min-width: 35px;
+      max-width: 40px;
+      overflow-y: auto;
+      overflow-x: visible;
+      transition: all 0.25s ease;
+      transform: translateX(-45px);
+      position: fixed;
+  }
+  
+  #sidebar.show {
+      transform: translateX(0);
+  }
+
+  #sidebar::-webkit-scrollbar{ width: 0px; }
+  
+  #sidebar, #sidebar .list-group {
+      min-width: 35px;
+      overflow: visible;
+  }
+  /* overlay sub levels on small screens */
+  #sidebar .list-group .collapse.show, #sidebar .list-group .collapsing {
+      position: relative;
+      z-index: 1;
+      width: 190px;
+      top: 0;
+  }
+  #sidebar .list-group > .list-group-item {
+      text-align: center;
+      padding: .75rem .5rem;
+  }
+  /* hide caret icons of top level when collapsed */
+  #sidebar .list-group > .list-group-item[aria-expanded="true"]::after,
+  #sidebar .list-group > .list-group-item[aria-expanded="false"]::after {
+      display:none;
+  }
+}
+
+.collapse.show {
+visibility: visible;
+}
+.collapsing {
+visibility: visible;
+height: 0;
+-webkit-transition-property: height, visibility;
+transition-property: height, visibility;
+-webkit-transition-timing-function: ease-out;
+transition-timing-function: ease-out;
+}
+.collapsing.width {
+-webkit-transition-property: width, visibility;
+transition-property: width, visibility;
+width: 0;
+height: 100%;
+-webkit-transition-timing-function: ease-out;
+transition-timing-function: ease-out;
+}
+
+.break-word {
+  word-wrap: break-word
+}
+.w-15vw {
+  max-width: 15vw;
+}
+.w-20vw {
+  max-width: 20vw;
+}
+.w-25vw {
+  max-width: 25vw;
+}
+.w-30vw {
+  max-width: 30vw;
+}
+.w-40vw {
+  max-width: 40vw;
+}
+.w-50vw {
+  max-width: 50vw;
+}
+.w-operations-btn {
+  width: 25px;
+}
+
+
+.success_flash {
+  animation: blinker 1s linear;
+  background-color: #d4edda !important;
+}
+@keyframes blinker {
+  50% {
+      opacity: 0.6;
+  }
+}
+
+
+.tree-view-container ul{
+	padding-left: 2em;
+	list-style-type: none;
+}
+
+.tree-view-container > ul:first-of-type{
+	padding: 0;
+}
+
diff --git a/StationSoftware/restartContainerTest.sh b/StationSoftware/restartContainerTest.sh
new file mode 100644
index 0000000000000000000000000000000000000000..fe11fbbdb5ad1c3f5e9297feb256424c2c31476a
--- /dev/null
+++ b/StationSoftware/restartContainerTest.sh
@@ -0,0 +1,13 @@
+docker stop pht-web-test
+docker rm pht-web-test
+docker rmi pht-web-test
+docker build -t pht-web-test .
+docker run --name pht-web-test -d -p 3031:3030 \
+ -v stationdeploymentfiles_pht-dind-certs-client:/usr/src/app/dind-certs-client/certs:ro \
+ -v stationdeploymentfiles_pht-vault-certs-client:/usr/src/app/vault-certs-client/certs:ro \
+ --dns 134.130.5.1 \
+ --dns 134.130.4.1 \
+ --env-file /home/jaberansary/pht-architecture/StationEnvironmentFiles/.env-aachenmenzel \
+  pht-web-test
+docker network connect pht-net pht-web-test
+docker logs -f pht-web-test
\ No newline at end of file
diff --git a/StationSoftware/routes/api/api.js b/StationSoftware/routes/api/api.js
new file mode 100644
index 0000000000000000000000000000000000000000..be88e041162f216470ffe3f7f71277d91a762539
--- /dev/null
+++ b/StationSoftware/routes/api/api.js
@@ -0,0 +1,40 @@
+const express = require('express');
+const router = express.Router();
+const jwt = require('jsonwebtoken');
+const passport = require('passport');
+const bcrypt = require('bcryptjs');
+// Load User model
+const User = require('../../models/User');
+
+router.post('/signin', (req, res) => {
+    const { email, password } = req.body;
+    // Match user
+    User.findOne({
+        email: email
+    }).then(user => {
+        if (!user) {
+            res.status(401).send({ success: false, msg: 'Authentication failed. User not found.' });
+        } else {
+            // Match password
+            bcrypt.compare(password, user.password, (err, isMatch) => {
+                if (isMatch && !err) {
+                    // if user is found and password is right create a token
+                    var token = jwt.sign(user.toJSON(), process.env.JWT_SECRET, {
+                        expiresIn: 604800 // 1 week
+                    });
+                    // return the information including token as JSON
+                    res.json({ success: true, token: 'Bearer '+token });
+                } else {
+                    res.status(401).send({ success: false, msg: 'Authentication failed. Wrong password.' });
+                }
+            });
+        }
+    });
+});
+
+router.get('/protected', passport.authenticate('jwt', { session: true }),
+    function(req, res) {
+        res.send("get protected resource...");
+    }
+);
+module.exports = router;
\ No newline at end of file
diff --git a/StationSoftware/routes/auth/auth.js b/StationSoftware/routes/auth/auth.js
new file mode 100644
index 0000000000000000000000000000000000000000..c646f7c9d5abbbc8fac8aed7a35e535559f5839c
--- /dev/null
+++ b/StationSoftware/routes/auth/auth.js
@@ -0,0 +1,98 @@
+const express = require('express');
+const router = express.Router();
+const bcrypt = require('bcryptjs');
+const passport = require('passport');
+// Load User model
+const User = require('../../models/User');
+const { forwardAuthenticated } = require('../../validation/auth');
+
+// Login Page
+router.get('/login', forwardAuthenticated, (req, res) => res.render('login'));
+
+// Register Page
+router.get('/register', forwardAuthenticated, (req, res) => res.render('register'));
+
+// Register
+router.post('/register', (req, res) => {
+  const { firstname, lastname, email, password, password2 } = req.body;
+  let errors = [];
+
+  if (!firstname || !lastname || !email || !password || !password2) {
+    errors.push({ msg: 'Please enter all fields' });
+  }
+
+  if (password != password2) {
+    errors.push({ msg: 'Passwords do not match' });
+  }
+
+  if (password.length < 6) {
+    errors.push({ msg: 'Password must be at least 6 characters' });
+  }
+
+  if (errors.length > 0) {
+    res.render('register', {
+      errors,
+      firstname,
+      lastname,
+      email,
+      password,
+      password2
+    });
+  } else {
+    User.findOne({ email: email }).then(user => {
+      if (user) {
+        errors.push({ msg: 'Email already exists' });
+        res.render('register', {
+          errors,
+          firstname,
+          lastname,
+          email,
+          password,
+          password2
+        });
+      } else {
+        const newUser = new User({
+          firstname,
+          lastname,
+          email,
+          password
+        });
+
+        bcrypt.genSalt(10, (err, salt) => {
+          bcrypt.hash(newUser.password, salt, (err, hash) => {
+            if (err) throw err;
+            newUser.password = hash;
+            newUser
+              .save()
+              .then(user => {
+                req.flash(
+                  'success_msg',
+                  'You are now registered and can log in'
+                );
+                res.redirect('/auth/login');
+              })
+              .catch(err => console.log(err));
+          });
+        });
+      }
+    });
+  }
+});
+
+// Login
+router.post('/login', (req, res, next) => {
+  passport.authenticate('local', {
+    successRedirect: '/dashboard',
+    failureRedirect: '/auth/login',
+    failureFlash: true
+  })(req, res, next);
+});
+
+// Logout
+router.get('/logout', (req, res) => {
+  req.logout();
+  req.flash('success_msg', 'You are logged out');
+  res.redirect('/auth/login');
+});
+
+module.exports = router;
diff --git a/StationSoftware/routes/dashboard/dashboard.js b/StationSoftware/routes/dashboard/dashboard.js
new file mode 100644
index 0000000000000000000000000000000000000000..f246e35d1d1cf87a9f23d2b7475982f6c321a3cc
--- /dev/null
+++ b/StationSoftware/routes/dashboard/dashboard.js
@@ -0,0 +1,369 @@
+const express = require('express');
+var request = require('request');
+const router = express.Router();
+const Train = require('../../models/Train');
+const { ensureAuthenticated } = require('../../validation/auth');
+const { getAgentOptions } = require('../../dind-certs-client');
+const axios = require('axios');
+const { response } = require('express');
+const { vault } = require('../../utils');
+
+
+// Dashboard
+// Get Docker Information
+function getAdminAuthRequestOptions() {
+    let authServer = new URL('/auth/realms/pht/protocol/openid-connect/token', `https://${process.env.AUTH_SERVER_ADDRESS}`);
+    authServer.port = process.env.AUTH_SERVER_PORT;
+    var options = {
+        url: authServer.toString(),
+        headers: {
+            'Content-Type': 'application/json',
+        },
+        form: {
+            grant_type: "password",
+            client_id: "central-service",
+            username: process.env.HARBOR_USER,
+            password: process.env.HARBOR_PASSWORD,
+            scope: "openid profile email offline_access",
+        }
+    };
+    return options;
+}
+
+router.get('/', ensureAuthenticated,
+    function (req, res, next) {
+        if (!req.harbor) {
+            req.harbor = { auth: {} };
+        }
+        let options = getAdminAuthRequestOptions();
+        request.post(options, (error, response) => {
+            if (error) {
+                console.log("error: ", error.message)
+                req.flash('error_msg', error.message);
+                res.redirect('/error');
+                return;
+            }
+
+            if (response.statusCode == 200 && typeof(response.body) != 'undefined' && response.body != null){
+                let body = JSON.parse(response.body);
+                req.harbor.auth.admin_access_token = body.access_token;
+                return next();
+            } else {
+                console.log("error: ", "Cannot Connect to the central authentication server.")
+                req.flash('error_msg', "Cannot Connect to the central authentication server.");
+                res.redirect('/error');
+                return;
+            }
+        });
+    },
+    function (req, res) {
+        let options = {
+            url: `https://${process.env.CENTRALSERVICE_ADDRESS}:${process.env.CENTRALSERVICE_PORT}/centralservice/api/jobinfo/${process.env.STATION_ID}`,
+            headers: {
+                'Content-Type': 'application/json',
+                'Authorization': `Bearer ${req.harbor.auth.admin_access_token}`,
+            },
+        };
+        request.get(options, function (error, response, body) {
+            if (error) {
+                console.log("error: ", error.message)
+                req.flash('error_msg', error.message);
+                res.redirect('/error');
+                return;
+            }
+
+            console.log(body);
+
+            try {
+                let trains = JSON.parse(body);
+                res.render('dashboard', { user: req.user, trains: trains });
+            } catch (error) {
+                console.error(error);
+                console.log("error: ", "Cannot Connect to the central service.")
+                req.flash('error_msg', "Cannot Connect to the central service.");
+                res.redirect('/error');
+                return;
+            }
+
+        });
+    });
+
+router.get('/images', ensureAuthenticated, (req, res) => {
+    Train.find({}, function (err, docs) {
+        if (err) {
+            req.flash('error_msg', err.message);
+            res.redirect('/dashboard');
+        } else if (docs.length > 0) {
+
+            let options = {
+                uri: `https://${process.env.DOCKER_HOST}:${process.env.DOCKER_PORT}/images/json?all=1`,
+                method: 'GET',
+                agentOptions: getAgentOptions()
+            };
+
+            request(options, function (error, response, body) {
+                if (error) {
+                    console.log(error);
+                    req.flash('error_msg', error.message);
+                    res.redirect('/dashboard');
+                } else {
+                    let data = JSON.parse(body);
+                    let pull_images = [];
+                    let push_images = [];
+                    for (var i = 0; i < data.length; i++) {
+                        if (data[i].RepoTags == null) {
+                            continue;
+                        }
+                        for (var j = 0; j < data[i].RepoTags.length; j++) {
+                            if (data[i].RepoTags[j] == 'none') {
+                                continue;
+                            }
+                            for (var k = 0; k < docs.length; k++) {
+                                if (((docs[k].trainstoragelocation + ':' + docs[k].currentstation) === data[i].RepoTags[j]) && (data[i].ParentId === '' || (data[i].Labels && data[i].Labels.decrypted))) {
+                                    pull_images.push({ Id: data[i].Id, RepoTag: data[i].RepoTags[j], JobId: docs[k].jobid, TrainClassId: docs[k].trainclassid, Labels: JSON.stringify(data[i].Labels) });
+                                    break;
+                                }
+                                if (((docs[k].trainstoragelocation + ':' + docs[k].nextstation) === data[i].RepoTags[j])
+                                    || (((docs[k].trainstoragelocation + ':' + docs[k].currentstation) === data[i].RepoTags[j]) && (data[i].ParentId !== ''))) {
+                                    push_images.push({ Id: data[i].Id, RepoTag: data[i].RepoTags[j], TrainClassId: docs[k].trainclassid });
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                    res.render('dashboard-images', { user: req.user, pull_images: pull_images, push_images: push_images });
+                }
+            });
+        }
+        else {
+            res.render('dashboard-images', { user: req.user, pull_images: [], push_images: [] });
+        }
+    });
+
+});
+
+router.get('/containers', ensureAuthenticated, (req, res) => {
+    Train.find({}, function (err, docs) {
+        if (err) {
+            req.flash('error_msg', err.message);
+            res.redirect('/dashboard');
+        } else if (docs.length > 0) {
+            
+            let options = {
+                uri: `https://${process.env.DOCKER_HOST}:${process.env.DOCKER_PORT}/containers/json?all=1`,
+                method: 'GET',
+                agentOptions: getAgentOptions()
+            };
+
+            request(options, function (error, response, body) {
+                if (error) {
+                    req.flash('error_msg', error.message);
+                    res.redirect('/dashboard');
+                } else {
+                    let data = JSON.parse(body);
+                    let containers = [];
+                    for (let i = 0; i < data.length; i++) {
+                        for (let j = 0; j < data[i].Names.length; j++) {
+                            console.log(data[i].Names[j]);
+                            for (var k = 0; k < docs.length; k++) {
+                                if ('/' + docs[k].jobid === data[i].Names[j]) {
+                                    containers.push({ Id: data[i].Id, Name: data[i].Names[j], JobId: docs[k].jobid, Image: data[i].Image, State: data[i].State, Status: data[i].Status, NextTag: docs[k].nextstation, Repo: docs[k].trainstoragelocation, TrainClassId: docs[k].trainclassid });
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                    res.render('dashboard-containers', { user: req.user, containers: containers});
+                }
+            });
+        } else {
+            res.render('dashboard-containers', { user: req.user, containers: []});
+        }
+    });
+});
+
+router.get('/reject', ensureAuthenticated, (req, res) => {
+    const { jobid } = req.query;
+    res.render('dashboard-reject', { user: req.user, jobid: jobid })
+});
+
+router.get('/create/container', ensureAuthenticated, (req, res) => {
+
+    const { jobId, image, labels } = req.query;
+
+    let envVariableList = [];
+
+    // console.log(labels);
+    if (labels) {
+        let parsedLabels = JSON.parse(labels);
+        // console.log(parsedLabels);
+        if (parsedLabels && parsedLabels.envs)
+            envVariableList = JSON.parse(parsedLabels.envs);
+        // console.log(envVariableList);
+    }
+
+    // Validate envs structure
+    const input_type_list = ["number", "password", "text", "url"];
+    let index = envVariableList.length
+    while (index--) {
+        const element = envVariableList[index];
+        if (!element.name) {
+            envVariableList.splice(index, 1);
+            continue;
+        }
+
+        if (!input_type_list.includes(element.type)) {
+            envVariableList[index].type = "text";
+        }
+
+        if (!element.required) {
+            envVariableList[index].required = false;
+        }
+    }
+
+    const blank_default = { name: "", type: "text", required: false };
+    if (envVariableList.length > 0)
+        envVariableList = envVariableList.concat([blank_default, blank_default, blank_default]);
+    else
+        envVariableList = envVariableList.concat([blank_default, blank_default, blank_default, blank_default, blank_default, blank_default]);
+
+
+    // key-value version 1 - START
+    let kv1Promise = new Promise((resolve) => {
+        //Get key-value engines (v1)
+        vault.getKeyValueEngines(version = 1).then(result => {
+            let promises = [];
+            let kvEnginesVersion1 = result;
+            kvEnginesVersion1.forEach(kvEngine => {
+
+                promises.push(new Promise((resolve) => {
+
+                    //Traverse all existing paths
+                    vault.traverse([kvEngine], (paths) => {
+                        resolve(paths);
+                    })
+
+                }));
+
+            });
+
+            //When all results received
+            Promise.all(promises).then(results => {
+
+                let paths = results.flat();
+                //Read keys corresponding to each path
+                let pathPromises = paths.map(path => vault.read(path));
+                Promise.all(pathPromises).then(results => {
+
+                    let pathList = [];
+
+                    for (let index = 0; index < results.length; index++) {
+                        if (!results[index])
+                            continue;
+                        const element = results[index];
+                        //key-value pairs
+                        const data = element.data;
+                        pathList.push(Object.keys(data).map(key => { return { path: paths[index], key: key } }));
+                    }
+
+                    pathList = pathList.flat();
+                    resolve(pathList);
+
+                })
+            });
+        });
+    });
+    // key-value version 1 - END
+
+    // key-value version 2 - START
+    let kv2Promise = new Promise((resolve) => {
+        //Get key-value engines (v2)
+        vault.getKeyValueEngines(version = 2).then(result => {
+
+            let promises = [];
+            let kvEnginesVersion2 = result;
+            kvEnginesVersion2.forEach(kvEngine => {
+
+                promises.push(new Promise((resolve) => {
+
+                    //Traverse all existing paths (add 'metadata' for api calls)
+                    vault.traverse([`${kvEngine}metadata/`], (paths) => {
+                        resolve(paths);
+                    })
+
+                }));
+
+            });
+
+            //When all results received
+            Promise.all(promises).then(results => {
+
+                let paths = results.flat();
+
+                //Replace 'metadata' with 'data' for api calls
+                paths = paths.map(path => {
+                    path = path.split("/");
+                    path.splice(1, 1, "data");
+                    path = path.join("/");
+                    return path;
+                });
+                // console.log(paths);
+                //Read keys corresponding to each path
+                let pathPromises = paths.map(path => vault.read(path));
+                Promise.all(pathPromises).then(results => {
+
+                    let pathList = [];
+
+                    for (let index = 0; index < results.length; index++) {
+                        if (!results[index])
+                            continue;
+                        const element = results[index];
+                        //key-value pairs
+                        const data = element.data.data;
+                        pathList.push(Object.keys(data).map(key => { return { path: paths[index], key: `data.${key}` } }));
+                    }
+
+                    pathList = pathList.flat();
+                    resolve(pathList);
+
+                })
+            });
+
+        });
+    });
+    // key-value version 2 - END
+
+    Promise.all([kv1Promise, kv2Promise]).then(results => {
+        let pathList = results.flat();
+        res.render('dashboard-create-container', { user: req.user, image: image, jobId: jobId, paths: pathList, envVariableList: envVariableList });
+    });
+
+});
+
+router.get('/notifications', ensureAuthenticated, (req, res) => {
+    res.render('dashboard-notifications', { user: req.user });
+});
+
+router.get('/metadata', ensureAuthenticated, async (req, res) => {
+    const host = process.env.METADATAPROVIDER_ENDPOINT;
+    var descriptionList = [];
+    var list, useAllowList;  
+    const t1 = axios.get(host + '/descriptionList').then(response => {
+        descriptionList = response.data['descriptionList'];
+    }).catch(err => {
+        res.send(err.message);
+    })
+    const t2 = axios.get(host + '/filter').then(response => {
+        list = response.data['list'];
+        useAllowList = response.data['useAllowList'];
+        console.log("USE ALLOW LIST RESPONSE: " + String(useAllowList))
+    }).catch(err => {
+        res.send(err.message);
+
+    })
+    await t1;
+    await t2;
+    res.render('metadata-list', { descriptionList, list: list.join('\n') , useAllowList: Boolean(useAllowList), user: req.user });
+})
+
+module.exports = router;
\ No newline at end of file
diff --git a/StationSoftware/routes/docker/docker.js b/StationSoftware/routes/docker/docker.js
new file mode 100644
index 0000000000000000000000000000000000000000..7018d7236ffd56e65d10ca8c6df864f8d839a868
--- /dev/null
+++ b/StationSoftware/routes/docker/docker.js
@@ -0,0 +1,1506 @@
+const express = require('express');
+const router = express.Router();
+const request = require('request');
+const Train = require('../../models/Train');
+const TransactionLog = require('../../models/TransactionLog')
+const { getAgentOptions } = require('../../dind-certs-client');
+const MetadataNotificationHelper = require('../helpers/MetadataNotifier')
+const { utility, vault, trainConfigUtil, cryptoUtil } = require('../../utils');
+const dockerContainerChangesKind = { 0: "Modified", 1: "Added", 2: "Deleted" };
+const fs = require('fs');
+const path = require('path');
+const requestPromise = require("request-promise");
+const tar = require("tar");
+const tarStream = require("tar-stream");
+const { Readable } = require("stream");
+const { exec } = require("child_process");
+const diff2Html = require('diff2html');
+const os = require('os');
+
+const getTempDir = (dirPath, empty) => {
+
+    // empty : if it already exists, remove all old files
+
+    const tempDirPath = path.join(os.tmpdir(), dirPath);
+
+    try {
+
+        fs.mkdirSync(tempDirPath, { recursive: true });
+        return tempDirPath;
+
+    } catch (error) {
+
+        if (error.code === 'EEXIST') {
+            console.log(`already exists`);
+
+            if (empty) {
+
+                try {
+
+                    fs.rmdirSync(tempDirPath, { recursive: true });
+                    fs.mkdirSync(tempDirPath, { recursive: true });
+
+                } catch (error) {
+                    console.log(error);
+                    return null
+                }
+            }
+            
+            return tempDirPath;
+        }
+        else {
+            console.log(error);
+            return null
+        }
+    }
+}
+
+const removeJobTempDir = (jobId) => {
+
+    const tempDirPath = getTempDir(jobId);
+    fs.rmdirSync(tempDirPath, { recursive: true });
+
+}
+
+const removeContainerTempDir = (jobId, container) => {
+
+    const tempDirPath = getTempDir(path.join(jobId, container));
+    fs.rmdirSync(tempDirPath, { recursive: true });
+
+}
+
+const getJobTempDir = (jobId, empty) => {
+
+    const tempDirPath = getTempDir(jobId, empty);
+    return tempDirPath;
+
+}
+
+const getTempContainerTarArchiveFileName = () => {
+
+    const tempContainerTarArchiveFileName = "container.tar";
+    return tempContainerTarArchiveFileName;
+
+}
+
+const getTempContainerTarArchiveSubPath = (jobId, container, beforeOrAfterExecution) => {
+
+    console.log(jobId, container, beforeOrAfterExecution)
+
+    var executionStateDirName = null;
+    if (beforeOrAfterExecution === "before") {
+        executionStateDirName = "before"
+    }
+    else if (beforeOrAfterExecution === "after") {
+        executionStateDirName = "after"
+    }
+
+    const tempContainerTarArchiveFilePath = path.join(jobId, container, executionStateDirName);
+    return tempContainerTarArchiveFilePath;
+
+}
+
+const getEncryptedTarArchiveFileName = () => {
+
+    const encryptedTarArchiveFileName = "file.enc";
+    return encryptedTarArchiveFileName;
+
+}
+
+const getEncryptedTarArchiveFilePathInContainer = () => {
+
+    const encryptedTarArchiveFileName = getEncryptedTarArchiveFileName();
+    const encryptedTarArchiveFilePathInContainer = path.join("/", encryptedTarArchiveFileName);
+    return encryptedTarArchiveFilePathInContainer;
+    
+}
+
+const getPrevChangesFileName = () => {
+
+    const prevChangesFileName = "changes";
+    return prevChangesFileName;
+
+}
+
+router.get('/image/pull', async function (req, res) {
+    const { jobid, trainstoragelocation, trainclassid, currentstation, nextstation } = req.query;
+    var auth = {
+        username: process.env.HARBOR_USER,
+        password: process.env.HARBOR_CLI,
+        email: process.env.HARBOR_EMAIL
+    }
+    var authInfo = Buffer.from(JSON.stringify(auth)).toString('base64');
+    var options = {
+        uri: 'https://' + process.env.DOCKER_HOST + ':' + process.env.DOCKER_PORT + '/images/create?fromImage=' + trainstoragelocation + '&tag=' + currentstation,
+        method: 'POST',
+        headers: {
+            "Content-Type": "application/json",
+            'X-Registry-Auth': authInfo
+        },
+        json: {},
+        agentOptions: getAgentOptions()
+    };
+
+    let body = Buffer.from("");
+    request(options).on("error", async (err) => {
+        console.log('error: ', err);
+        req.flash('error_msg', err);
+        res.status(500).send();
+    }).on("data", async (data) => {
+        body = Buffer.concat([body, data])
+        res.write(data);
+    }).on("complete", async (response) => {
+        console.log("complete");
+        if (response.statusCode == 200) {
+
+            try {
+
+                // save pulled job information on local db
+                const train = {
+                    "jobid": jobid,
+                    "trainstoragelocation": trainstoragelocation,
+                    "trainclassid": trainclassid,
+                    "currentstation": currentstation,
+                    "nextstation": nextstation
+                };
+                // Insert or Update - If a job with the provided jobid exists, it will update - update: When a job visit the station more than once
+                await Train.findOneAndUpdate(
+                    { "jobid": jobid },
+                    train,
+                    {
+                        upsert: true // Make this update into an upsert
+                    });
+
+                // log
+                TransactionLog.create({ user: req.user.email, logMessage: "Image " + String(trainstoragelocation) + ":" + String(currentstation) + "pulled" });
+
+                // metadata service
+                MetadataNotificationHelper.notifyMetadataProviderImageFinishedDownloading(jobid, new Date());
+
+
+                // create an empty temp directory - later is used for inspect changes, encryption, and decryption
+                const tempDirPath = getJobTempDir(jobid, true);
+
+                req.flash('success_msg', 'Image ' + trainstoragelocation + ':' + currentstation + ' has been pulled.');
+                res.end();
+
+            } catch (err) {
+                console.log('error: ', err);
+                req.flash('error_msg', err);
+                res.end();
+            }
+
+        } else {
+            req.flash('error_msg', (body.toString("utf-8")) || "Internal Server Error");
+            console.log(body.toString("utf-8"));
+            res.end()
+        }
+    });
+    MetadataNotificationHelper.notifyMetadataProviderImageStartedDownloading(jobid, trainstoragelocation + ':' + currentstation, new Date());
+});
+
+
+// Reject Train requests
+router.post('/train/reject', async function(req, res) {
+    const { jobid, reason, comment} = req.body;
+    let errors = [];
+    if (reason === "0") {
+        errors.push({ msg: 'Please select a reject reason.' });
+    }
+    if (reason === "3" && comment === "") {
+        errors.push({ msg: 'Please enter the text of reject reason.' });
+    }
+    if (errors.length > 0) {
+        res.render('dashboard-reject', {user: req.user, jobid: jobid, errors: errors});
+    } else {
+        let rejectMessage = comment;
+        if (reason === "1"){
+            rejectMessage = "No anonymity"
+        }
+        if (reason === "2"){
+            rejectMessage = "No access right";
+        }
+        let options = {
+            url: `https://${process.env.CENTRALSERVICE_ADDRESS}:${process.env.CENTRALSERVICE_PORT}/centralservice/hook`,
+            headers: {
+                'Content-Type': 'application/json',
+                'authorization': process.env.HARBOR_WEBHOOK_SECRET,
+            },
+            body: JSON.stringify({
+                type:"reject",
+                jobId: jobid,
+                rejectMessage: rejectMessage
+            }),
+        };
+        request.post(options, function (error, response, body) {
+            if (error) {
+                req.flash('error_msg', error.message);
+                res.redirect('/dashboard/reject');
+            } 
+            console.log(response.statusCode);
+            console.log(response.body);
+            TransactionLog.create({user:req.user.email,logMessage:"Train rejected with jobID:" + String(jobid)})
+            MetadataNotificationHelper.notifyMetadataProviderImageRejected(jobid, rejectMessage, new Date());
+            req.flash('success_msg', "The job "+jobid+" has been rejected.");
+            res.redirect('/dashboard');
+            
+        });
+    }
+    
+});
+
+
+// Create Container
+router.post('/container/create', utility.asyncHandler(async (req, res, next) => {
+
+    const { jobId, image, envs } = req.body;
+    
+    // console.log(envs)
+
+    let env_array = [];
+    let env_array_manual = [];
+    let env_array_vault = [];
+    let env_array_vault_temp = [];
+
+    //Separate manual type env variables from valut type env variables
+    envs.forEach(item => {
+
+        item.name = item.name.trim();
+
+        if (item.type === "manual") {
+            if (item.name.length < 1)
+                return;
+                
+            item.value = item.value.trim();                                
+            env_array_manual.push(`${item.name}=${item.value}`);
+        }
+        else if (item.type === "vault") {
+            let temp = {
+                path: JSON.parse(item.path).path,
+                env: item.name,
+                key: JSON.parse(item.path).key
+            };
+            env_array_vault_temp.push(temp);
+        }
+    });
+
+    //Group the vault envs by their path
+    env_array_vault_temp = utility.groupBy(env_array_vault_temp, 'path');
+
+    //Generate config JSON - below is a small structural sample
+    // [
+    //     {
+    //         "path": "PATH_TO_SECRET",
+    //         "kv": {
+    //             "TRAIN_ENV_VARIABLE": "RESPONSE_WRAPPING_TOKEN"
+    //         }
+    //     }
+    // ]
+
+    // WRAPPED RESULT
+    let uniquePaths = Object.keys(env_array_vault_temp);
+    for (let index = 0; index < uniquePaths.length; index++) {
+        const path = uniquePaths[index];
+        // Get Response-Wrapping Token on path
+        const response = await vault.read(path, { headers: { "X-Vault-Wrap-TTL": "1h" } });
+        if (response.isError) {
+            res.render('error', { user: req.user, error_msg: (JSON.stringify(response.data) || 'error') });
+            return;
+        }
+        let temp = { path: response.wrap_info.token, kv: {} };
+        env_array_vault_temp[path].forEach(x => {
+            temp.kv[x.env] = x.key
+        })
+        env_array_vault.push(temp);
+    }
+
+    //Vault configs
+    if (env_array_vault.length > 0) {
+
+        let tokenConfig = {
+            num_uses: env_array_vault.length
+        };
+
+        // console.log("GetTrainToken");
+        const trainToken = await vault.getTrainToken(tokenConfig);
+        // console.log("Token:", trainToken);
+        if (trainToken.isError) {
+            res.render('error', { user: req.user, error_msg: (JSON.stringify(trainToken.data) || 'error') });
+            return;
+        }
+
+        env_array.push(`VAULT_ADDR=${await vault.getVaultApiEndpoint()}/sys/wrapping/unwrap`);
+        env_array.push(`VAULT_TOKEN=${trainToken.auth.client_token}`);
+        env_array.push(`VAULT_SECRET_CONFIG=${JSON.stringify(env_array_vault)}`);
+    }
+
+    env_array = env_array.concat(env_array_manual);
+    // console.log(JSON.stringify(env_array));
+
+    let options = {
+        uri: `https://${process.env.DOCKER_HOST}:${process.env.DOCKER_PORT}/containers/create?name=${jobId}`,
+        method: 'POST',
+        headers: {
+            "Content-Type": "application/json",
+        },
+        json: {
+            "Image": image
+        },
+        agentOptions: getAgentOptions()
+    };
+
+    if (env_array.length) {
+        options.json["Env"] = env_array;
+    }
+
+    // Bind Vault clinet certs to train
+    // if (true) {
+    //     options.json["HostConfig"] = {
+    //         "Binds": ["/pht-vault-certs-client:/certs:ro"]
+    //     }
+    // }
+
+    request(options, (error, response, body) => {
+        if (error) {
+            console.error(error);
+            req.flash('error_msg', error.message);
+            res.redirect('/dashboard/images');
+        }
+
+        if (response.statusCode == 201) {
+
+            // make a backup from the container before its execution - path: <os_temp_dir>/<job_id>/<container_id>/before/container.tar
+            try {
+
+                const container = body.Id.substring(0, 13);
+                const tempContainerTarArchiveFileName = getTempContainerTarArchiveFileName();
+                const tempContainerTarArchiveSubPath = getTempContainerTarArchiveSubPath(jobId, container, "before");
+                const tempContainerTarArchiveFilePath = path.join(getTempDir(tempContainerTarArchiveSubPath, true), tempContainerTarArchiveFileName);
+
+                let options = {
+                    uri: `https://${process.env.DOCKER_HOST}:${process.env.DOCKER_PORT}/containers/${container}/export`,
+                    method: 'GET',
+                    agentOptions: getAgentOptions()
+                };
+
+                request(options).pipe(fs.createWriteStream(tempContainerTarArchiveFilePath)).on('finish', () => {
+
+                    // console.log(body);
+                    req.flash('success_msg', 'Container ' + container + ' has been created.');
+                    TransactionLog.create({ user: req.user.email, logMessage: 'Container ' + container + ' created.' });
+                    res.redirect('/dashboard/containers');
+
+                });
+
+            } catch (error) {
+
+                console.log(error);
+                req.flash('error_msg', JSON.stringify(error));
+                res.redirect('/dashboard/images');
+
+            }
+
+        } else {
+            console.log(body);
+            req.flash('error_msg', JSON.stringify(body));
+            res.redirect('/dashboard/images');
+        }
+
+    });
+}));
+
+
+router.get('/image/remove', function(req, res) {
+    const { jobId, image } = req.query;
+
+    var options = {
+        uri: 'https://' + process.env.DOCKER_HOST + ':' + process.env.DOCKER_PORT + '/images/' + image + '?force=true',
+        method: 'DELETE',
+        agentOptions: getAgentOptions()
+    };
+    request(options, (error, response, body) => {
+        if (error) {
+            console.error(error);
+            req.flash('error_msg', error.message);
+            res.redirect('/dashboard/images');
+        }
+        if (response.statusCode == 200) {  
+            console.log(body);
+            req.flash('success_msg', 'Image ' + image + ' has been removed.');
+            TransactionLog.create({user:req.user.email, logMessage:'Image ' + image + ' removed'});
+
+            // remove temp direcotry
+            removeJobTempDir(jobId);
+
+            res.redirect('/dashboard/images');
+        } else {
+            console.error(body);
+            req.flash('error_msg', body);
+            res.redirect('/dashboard/images');
+        }
+    });
+});
+
+
+router.get('/image/push', function(req, res) {
+    const { image } = req.query;
+    var auth = {
+        username: process.env.HARBOR_USER,
+        password: process.env.HARBOR_CLI,
+        email: process.env.HARBOR_EMAIL
+    }
+    var authInfo = Buffer.from(JSON.stringify(auth)).toString('base64');
+    var options = {
+        uri: 'https://' + process.env.DOCKER_HOST + ':' + process.env.DOCKER_PORT + '/images/'+image+'/push',
+        method: 'POST',
+        headers: {
+                "Content-Type": "application/json",
+                'X-Registry-Auth': authInfo
+        },
+        json: {},
+        agentOptions: getAgentOptions()
+    };
+    request(options, async (error, response, body) => {
+        if (error) {
+            console.error(error);
+            req.flash('error_msg', error.message);
+            res.redirect('/dashboard/images');
+        }
+        if (response.statusCode == 200) {  
+            console.log("step1 - push image: ", body);
+
+            // DELETE image after push (to Harbor)
+            var options = {
+                uri: 'https://' + process.env.DOCKER_HOST + ':' + process.env.DOCKER_PORT + '/images/' + image + '?force=true',
+                method: 'DELETE',
+                agentOptions: getAgentOptions()
+            };
+            request(options, (error, response, body) => {
+                if (error) {
+                    console.error(error);
+                    req.flash('error_msg', error.message);
+                    res.redirect('/dashboard/containers');
+                }
+                if (response.statusCode == 200) {  
+                    console.log("step2 - remove image: ", body);
+                    req.flash('success_msg', 'Image ' + image + ' has been pushed.');
+                    TransactionLog.create({user:req.user.email, logMessage:'Image ' + image + ' pushed'});
+                    res.redirect('/dashboard/images');
+       
+                } else {
+                    console.error(body);
+                    req.flash('error_msg', body);
+                    res.redirect('/dashboard/containers');
+                }
+            });
+
+
+        } else {
+            console.error(body);
+            req.flash('error_msg', body);
+            res.redirect('/dashboard/images');
+        }
+    });
+});
+
+
+//Start Container
+router.get('/container/start', function(req, res) {
+    const { container, jobId } = req.query;
+    // jobId may contain a leading /, dunno why
+    // therefore, assumed that the jobId is just the pattern XXXXXX-XXXXX-XXXX-XXX
+    const cleanedJobId = jobId.replace('/','')
+    const options = {
+        uri: 'https://'+process.env.DOCKER_HOST+':'+process.env.DOCKER_PORT+'/containers/' + container + '/start',
+        method: 'POST',
+        agentOptions: getAgentOptions() 
+    };
+    // The options for the wait request
+    const waiting_options = {
+        uri: 'https://' + process.env.DOCKER_HOST + ':' + process.env.DOCKER_PORT + '/containers/' + container + '/wait',
+        method: 'POST',
+        agentOptions: getAgentOptions()
+    }
+    request(options, (error, response, body) => {
+        if (error) {
+            console.error(error);
+            req.flash('error_msg', error.message);
+            res.redirect('/dashboard/images');
+        }
+        if (response.statusCode == 204) {  
+            console.log(body);
+            req.flash('success_msg', 'Container ' + container + ' has been started.');
+            console.log('Container ' + container + ' started');
+            MetadataNotificationHelper.notifyMetadataProviderImageStartedRunning(cleanedJobId, new Date());
+            TransactionLog.create({user:req.user.email, logMessage:'Train started:' + String(cleanedJobId)})
+            // waiting on the container and notify when it is stopeed:
+            request(waiting_options, (error, response, body) => {
+                if ( error ) {
+                    console.error(error);
+                    req.flash('error_msg', error.message);
+                    return;
+                }
+                if(body.StatusCode == 0) {
+                    MetadataNotificationHelper.notifyMetadataProviderImageFinished(cleanedJobId, true, new Date());
+                } else {
+                    MetadataNotificationHelper.notifyMetadataProviderImageFinished(cleanedJobId, false, new Date());
+                }
+            });
+            res.redirect('/dashboard/containers');
+        }else{
+            console.error(body);
+            req.flash('error_msg', body);
+            res.redirect('/dashboard/images');
+        }
+    });
+});
+
+// // Commit Container - OLD - WITHOUT ENCRYPTION
+// router.get('/container/commit', function (req, res) {
+//     const { container, repo, tag, image } = req.query;
+//     var options = {
+//         uri: 'https://' + process.env.DOCKER_HOST + ':' + process.env.DOCKER_PORT + '/commit?container=' + container + '&repo=' + repo + '&tag=' + tag,
+//         method: 'POST',
+//         headers: {
+//             "Content-Type": "application/json",
+//         },
+//         json: {},
+//         agentOptions: getAgentOptions()
+//     };
+//     request(options, (error, response, body) => {
+//         if (error) {
+//             console.error(error);
+//             req.flash('error_msg', error.message);
+//             res.redirect('/dashboard/containers');
+//         }
+//         if (response.statusCode == 201) {
+//             console.log("step1 - commit container: ", body);
+
+//             // DELETE container after commit
+//             var options = {
+//                 uri: 'https://' + process.env.DOCKER_HOST + ':' + process.env.DOCKER_PORT + '/containers/' + container,
+//                 method: 'DELETE',
+//                 agentOptions: getAgentOptions()
+//             };
+//             request(options, (error, response, body) => {
+//                 if (error) {
+//                     console.error(error);
+//                     req.flash('error_msg', error.message);
+//                     res.redirect('/dashboard/containers');
+//                 }
+//                 if (response.statusCode == 204) {
+//                     console.log("step2 - remove container: ", body);
+
+//                     if(process.env.STATION_ID === tag ){
+//                         req.flash('success_msg', 'Container ' + container + ' has been committed.');
+//                         res.redirect('/dashboard/images');
+//                         return;
+//                     }
+
+//                     // DELETE image
+//                     var options = {
+//                         uri: 'https://' + process.env.DOCKER_HOST + ':' + process.env.DOCKER_PORT + '/images/' + image + '?force=true',
+//                         method: 'DELETE',
+//                         agentOptions: getAgentOptions()
+//                     };
+//                     request(options, (error, response, body) => {
+//                         if (error) {
+//                             console.error(error);
+//                             req.flash('error_msg', error.message);
+//                             res.redirect('/dashboard/containers');
+//                         }
+//                         if (response.statusCode == 200) {
+//                             console.log("step3 - remove image: ", body);
+//                             req.flash('success_msg', 'Container ' + container + ' has been committed.');
+//                             TransactionLog.create({user:req.user.email, logMessage:'Container ' + container + ' committed'});
+//                             res.redirect('/dashboard/images');
+
+//                         } else {
+//                             console.error(body);
+//                             req.flash('error_msg', body);
+//                             res.redirect('/dashboard/containers');
+//                         }
+//                     });
+
+//                 } else {
+//                     console.error(body);
+//                     req.flash('error_msg', body);
+//                     res.redirect('/dashboard/containers');
+//                 }
+//             });
+
+//         } else {
+//             console.error(body);
+//             req.flash('error_msg', body);
+//             res.redirect('/dashboard/containers');
+//         }
+//     });
+// });
+
+router.get('/container/remove', function (req, res) {
+    const { jobId, container } = req.query;
+    var options = {
+        uri: 'https://' + process.env.DOCKER_HOST + ':' + process.env.DOCKER_PORT + '/containers/' + container,
+        method: 'DELETE',
+        agentOptions: getAgentOptions()
+    };
+    request(options, (error, response, body) => {
+        if (error) {
+            console.error(error);
+            req.flash('error_msg', error.message);
+            res.redirect('/dashboard/containers');
+        }
+        if (response.statusCode == 204) {
+            console.log(body);
+            req.flash('success_msg', 'Container ' + container + ' has been removed.');
+            TransactionLog.create({ user: req.user.email, logMessage: 'Container ' + container + ' removed' });
+
+            // remove temp direcotry
+            removeContainerTempDir(jobId, container);
+            
+            res.redirect('/dashboard/containers');
+        } else {
+            console.error(body);
+            req.flash('error_msg', body);
+            res.redirect('/dashboard/containers');
+        }
+    });
+});
+
+router.get('/container/logs', function (req, res) {
+    //container_id
+    const { container } = req.query;
+    var options = {
+        uri: `https://${process.env.DOCKER_HOST}:${process.env.DOCKER_PORT}/containers/${container}/logs?stdout=true&stderr=true`,
+        method: 'GET',
+        agentOptions: getAgentOptions()
+    };
+
+    let body = Buffer.from("");
+    request(options).on("error", async (err) => {
+        console.log('error: ', err);
+        req.flash('error_msg', err);
+        res.status(500).send();
+    }).on("data", async (data) => {
+        // Remove header
+        if (data[1] == 00 && data[2] == 00 && data[3] == 00)
+            data = data.slice(8, data.length);
+        // console.log("data", data.toString("utf-8"));
+        body = Buffer.concat([body, data]);
+    }).on("complete", async (response) => {
+        // console.log("complete");
+        // console.log(body.toString("utf-8"));
+        res.json(JSON.stringify(body.toString("utf-8")));
+    });
+});
+
+router.get('/container/changes', function (req, res) {
+    const { container } = req.query;
+    var options = {
+        uri: `https://${process.env.DOCKER_HOST}:${process.env.DOCKER_PORT}/containers/${container}/changes`,
+        method: 'GET',
+        agentOptions: getAgentOptions()
+    };
+    request(options, (error, response, body) => {
+        if (error) {
+            console.error(`error: ${error}`);
+            res.status(500).send(error);
+        }
+        if (response.statusCode == 200) {
+            try {
+                let changes = JSON.parse(body);
+
+                let tree = [];
+                let level = { tree };
+
+                if (changes) {
+                    changes = changes.map(x => { x.KindName = dockerContainerChangesKind[x.Kind]; return x });
+
+                    changes.forEach(item => {
+                        let path = item.Path;
+                        path.split('/').reduce((r, name, i, a) => {
+                            if (!r[name]) {
+                                r[name] = { tree: [] };
+                                r.tree.push({ name, children: r[name].tree, path: (name ? path : ""), kindCode: item.Kind, kindName: item.KindName, containerId: container });
+                            }
+
+                            return r[name];
+                        }, level)
+                    });
+                }
+
+                res.json(JSON.stringify(tree));
+
+            } catch (error) {
+                console.error(error);
+                req.flash('error_msg', error);
+                res.redirect('/dashboard/containers');
+            }
+        } else {
+            console.error(`error: ${body}`);
+            res.status(500).send(body);
+        }
+    });
+});
+
+router.get('/container/compare', utility.asyncHandler(async (req, res, next) => {
+    const { jobId, container } = req.query;
+
+    const tempContainerTarArchiveFileName = getTempContainerTarArchiveFileName();
+
+    const tempContainerTarArchiveSubPath_beforeExecution = getTempContainerTarArchiveSubPath(jobId, container, "before");
+    const tempContainerTarArchiveSubPath_afterExecution = getTempContainerTarArchiveSubPath(jobId, container, "after");
+
+    const tempContainerTarArchiveDirPath_beforeExecution = getTempDir(tempContainerTarArchiveSubPath_beforeExecution);
+    const tempContainerTarArchiveDirPath_afterExecution = getTempDir(tempContainerTarArchiveSubPath_afterExecution);
+
+    const tempContainerTarArchiveFilePath_beforeExecution = path.join(tempContainerTarArchiveDirPath_beforeExecution, tempContainerTarArchiveFileName);
+    const tempContainerTarArchiveFilePath_afterExecution = path.join(tempContainerTarArchiveDirPath_afterExecution, tempContainerTarArchiveFileName);
+
+    try {
+
+        // GET CONTAINER CHANGES
+        let containerChangesOptions = {
+            uri: `https://${process.env.DOCKER_HOST}:${process.env.DOCKER_PORT}/containers/${container}/changes`,
+            method: 'GET',
+            agentOptions: getAgentOptions()
+        };        
+        const containerChangesResult = await requestPromise(containerChangesOptions);
+        const containerChangesResultJson = JSON.parse(containerChangesResult);
+
+        // Exclude sub directories - KEEP FILES ONLY
+        let fileList = [];
+        let filePathList = [];
+        containerChangesResultJson.forEach(file => {
+            let index = fileList.findIndex(x => { return file.Path.includes(x.Path) });
+            if (index < 0)
+                fileList.push(file)
+            else
+                fileList.splice(index, 1, file)
+        });
+        filePathList = fileList.map(x => x.Path.substring(1));
+        // console.log(filePathList);
+
+        // Extract changes from before execution backup
+        if (fs.existsSync(tempContainerTarArchiveFilePath_beforeExecution)) {
+            // Extract only changed files - before execution version
+            tar.x({
+                cwd: tempContainerTarArchiveDirPath_beforeExecution,
+                file: tempContainerTarArchiveFilePath_beforeExecution,
+                sync: true
+            }, filePathList);
+        }
+        else {
+            throw new Error('Could not find backup file (before execution).');
+        }
+
+        // Download and extract changed files from the executed container
+        if (!fs.existsSync(tempContainerTarArchiveFilePath_afterExecution)) {
+
+            let options = {
+                uri: `https://${process.env.DOCKER_HOST}:${process.env.DOCKER_PORT}/containers/${container}/export`,
+                method: 'GET',
+                agentOptions: getAgentOptions()
+            };
+            await new Promise(resolve => {
+                request(options).pipe(fs.createWriteStream(tempContainerTarArchiveFilePath_afterExecution))
+                .on('finish', resolve)
+                .on('error', (error) => {
+                    console.log(error);
+                    reject(error);
+                });
+            });
+        }
+        // Extract changed files - after execution version
+        tar.x({
+            cwd: tempContainerTarArchiveDirPath_afterExecution,
+            file: tempContainerTarArchiveFilePath_afterExecution,
+            sync: true,
+        }, filePathList);
+        
+        const binaryFiles = /^Binary files (.*) and (.*) differ/;
+        const binaryHead = ['--- ','+++ ','@@ -0 +0 @@'].join('\n');
+
+        let promises = [];
+        filePathList.forEach(file => {
+
+            const path_to_file_in_before_execution_dir = path.join(tempContainerTarArchiveDirPath_beforeExecution, file);
+            const path_to_file_in_after_execution_dir = path.join(tempContainerTarArchiveDirPath_afterExecution, file);
+
+            // Execute 'diff' command 
+            const cmd = `diff --new-file -u ${path_to_file_in_before_execution_dir} ${path_to_file_in_after_execution_dir}`;
+
+            promises.push(
+                new Promise((resolve, reject) => {
+                    exec(cmd, (error, stdout, stderr) => {
+
+                        if (binaryFiles.test(stdout))
+                            stdout = binaryHead.concat('\n', stdout);
+
+                        // Exit status is 0 if inputs are the same, 1 if different, 2 if trouble.
+                        if (error) {
+                            if (error.code === 1) {
+                                resolve(stdout);
+                            }
+                            else {
+                                console.log(`error: ${error.message}`);
+                                if (stderr) {
+                                    console.log(`stderr: ${stderr}`);
+                                }
+                                reject();
+                            }
+                            return;
+                        }
+                        console.log(`stdout: ${stdout}`);
+                        resolve(stdout);
+                    })
+                })
+            );
+            
+        });
+
+        let promisesResults = await Promise.all(promises);
+        // console.log(promisesResults);
+
+        const diffUnifiedString = promisesResults.join('\n');
+        // console.log(diffUnifiedString);
+
+        const diffJson = diff2Html.parse(diffUnifiedString);
+        // console.log(JSON.stringify(diffJson))
+
+        // Hide the line according to its type (Only new added lines ('insert') are displayed, the rest ('delete', 'context') are replaced with '***confidential***') 
+        diffJson.forEach(diff => {
+            // strip base path from file name
+            diff.oldName = diff.oldName.replace(tempContainerTarArchiveDirPath_beforeExecution, '');
+            diff.newName = diff.oldName;
+
+            let index = fileList.findIndex(x => diff.newName.includes(x.Path));
+            // 0: "Modified", 1: "Added", 2: "Deleted"
+            if (fileList[index].Kind === 1)
+                diff.isNew = true
+            else if (fileList[index].Kind === 2)
+                diff.isDeleted = true
+
+            diff.blocks.forEach(block => {
+                block.lines.forEach(line => {
+
+                    // LineType {
+                    //     'insert',
+                    //     'delete',
+                    //     'context',
+                    // }
+
+                    if (line.type === "insert") {
+                        return
+                    }
+                    else {
+                        line.content = `${line.content[0]}***confidential***`
+                    }
+                })
+            })
+        });
+
+        const diffHtml = diff2Html.html(diffJson);
+        res.end(diffHtml);
+
+    } catch (error) {
+        console.error(`error: ${error}`);
+        res.status(500).send(error);
+    }
+}));
+
+router.get('/container/archive', utility.asyncHandler(async (req, res, next) => {
+    const { container, path } = req.query;
+ 
+    // ***** TO-DO : path should be authorized - Only added files can be downloaded *****
+
+    let options = {
+        uri: `https://${process.env.DOCKER_HOST}:${process.env.DOCKER_PORT}/containers/${container}/archive?path=${path}`,
+        method: 'GET',
+        agentOptions: getAgentOptions()
+    };
+
+    request(options).on("error", async (err) => {
+        console.log('error: ', err);
+        res.status(500).send(err);
+    }).on("response", async (response) => {
+
+        if (response.statusCode == 200) {
+            let fileName = `${container}_${path.replaceAll("/", "_")}.tar`;
+            res.set('Content-disposition', 'attachment; filename=' + fileName);
+            res.set('Content-Type', 'application/x-tar');
+        } else {
+            let fileName = "error.json";
+            res.set('Content-disposition', 'attachment; filename=' + fileName);
+            res.set('Content-Type', 'application/json');
+        }
+    }).on("data", async (data) => {
+        res.write(data);
+    }).on("complete", async (response) => {
+        res.end();
+    });
+
+}));
+
+// Commit Container - NEW - WITH ENCRYPTION
+router.get('/container/commit', utility.asyncHandler(async (req, res, next) => {
+
+    const { jobId, image, container, repo, tag,  } = req.query;
+
+    const tempDirPath = getJobTempDir(jobId);
+    const encryptedTarArchiveFileName = getEncryptedTarArchiveFileName();
+    const encryptedTarArchiveFilePathInContainer = getEncryptedTarArchiveFilePathInContainer();
+
+    try {
+
+        // This includes a list of files that have been modified by previous stations - They are decrypted and must be encrypted again before the train leaves the station
+        let prevChanges = [];
+        const prevChangesFileName = getPrevChangesFileName();
+        const prevChangesFilePath = path.join(tempDirPath, prevChangesFileName);
+
+        if (fs.existsSync(prevChangesFilePath)) {
+            console.log("prev_changes");
+            const fileContent = fs.readFileSync(prevChangesFilePath);
+            prevChanges = JSON.parse(fileContent);
+        }
+        else {
+            throw new Error('Could not find backup file.');
+        }
+
+        // GET a list of changes made by this run
+        let containerChangesOptions = {
+            uri: `https://${process.env.DOCKER_HOST}:${process.env.DOCKER_PORT}/containers/${container}/changes`,
+            method: 'GET',
+            agentOptions: getAgentOptions()
+        };    
+        const containerChangesResult = await requestPromise(containerChangesOptions);
+        const containerChangesResultJson = JSON.parse(containerChangesResult);
+
+        // exclude subdirectories (sub changes) from the list of changes - KEEP ONLY FILES
+        let fileList = [];
+        let deletedFileList = [];
+        containerChangesResultJson.forEach(file => {
+            // 0: "Modified", 1: "Added", 2: "Deleted"
+            // ignore deleted files - deleted files are lated deleted from the base image
+            if (file.Kind === 2) {
+                
+                let index = prevChanges.findIndex(x => {
+                    // if needed, consider "/" in comparison
+                    if (file.Path.startsWith("/")) {
+                        x = `/${x}`;
+                    }
+                    return x.startsWith(file.Path);
+                });
+
+                // two types of deletion: 1.deleted file is part of the previous changes 2.deleted file is part of the base image
+                if (index > -1) {
+                    // remove deleted file from prev changes
+                    prevChanges.splice(index, 1);
+                }
+                else {
+                    deletedFileList.push(file.Path);
+                }
+                return;
+            }
+
+            let index = fileList.findIndex(x => { return file.Path.includes(x.Path) });
+            if (index < 0)
+                fileList.push(file)
+            else
+                fileList.splice(index, 1, file)
+        });
+
+        // strip "/" from paths - first character
+        let fileListStripped = [];
+        fileListStripped = fileList.map(x => {
+            if (x.Path.startsWith("/"))
+                return x.Path.substring(1);
+        });
+        console.log(fileListStripped);
+
+        // append prev changes to current changes
+        fileListStripped = prevChanges.concat(fileListStripped);
+
+        // keep unique paths
+        fileListStripped = [ ...new Set(fileListStripped)];  
+
+
+        const tempDirSubPathAfterContainerExecution = getTempContainerTarArchiveSubPath(jobId, container, "after");
+        const tempDirPathAfterContainerExecution = getTempDir(tempDirSubPathAfterContainerExecution);
+        const containerTarArchiveFileName = getTempContainerTarArchiveFileName();
+        const containerTarArchiveFilePath = path.join(tempDirPathAfterContainerExecution, containerTarArchiveFileName);
+        
+
+        // extract changed files from the container
+        if (!fs.existsSync(containerTarArchiveFilePath)) {
+
+            let options = {
+                uri: `https://${process.env.DOCKER_HOST}:${process.env.DOCKER_PORT}/containers/${container}/export`,
+                method: 'GET',
+                agentOptions: getAgentOptions()
+            };
+
+            await new Promise(resolve => {
+                request(options).pipe(fs.createWriteStream(containerTarArchiveFilePath))
+                .on('finish', resolve)
+                .on('error', (error) => {
+                    console.log(error);
+                    reject(error);
+                });
+            });
+
+        }
+        tar.extract({
+            cwd: tempDirPathAfterContainerExecution,
+            file: containerTarArchiveFilePath,
+            sync: true,
+        }, fileListStripped);
+    
+        // create a new tar archive contains all changes
+        const tarArchiveFileName = "file.tar";
+        const tarArchiveFilePath = path.join(tempDirPathAfterContainerExecution, tarArchiveFileName);
+        tar.create({
+            cwd: tempDirPathAfterContainerExecution,
+            file: tarArchiveFilePath,
+            sync: true,
+        }, fileListStripped);
+
+        // encode tar file to base64 - needed for vault encryption
+        let tarFileBase64Encoded = Buffer.from(fs.readFileSync(tarArchiveFilePath)).toString('base64');
+
+        // generate a symmetric key
+        const symmetricKeyName = jobId;
+        let generateSymmetricKeyResult = await vault.command.write(`transit/keys/${symmetricKeyName}`, {
+            exportable: true,
+            allow_plaintext_backup: true,
+            type: "aes256-gcm96"
+        });
+
+        // encrypt changes with the symmetric key
+        let encryptionResult = await vault.command.write(`transit/encrypt/${symmetricKeyName}`, { plaintext: tarFileBase64Encoded });
+
+        // write ciphertext in a file
+        const encryptedTarArchiveFilePath = path.join(tempDirPathAfterContainerExecution, encryptedTarArchiveFileName);
+        fs.writeFileSync(encryptedTarArchiveFilePath, encryptionResult.data.ciphertext);
+
+        // export the symmetric key
+        let exportSymmetricKeyResult = await vault.command.read(`/transit/export/encryption-key/${symmetricKeyName}/1`);
+        const symmetricKey = exportSymmetricKeyResult.data.keys['1'];
+
+        // FIND base Image Id
+        // 1. inspect container for its image
+        let inspectContainerOptions = {
+            uri: `https://${process.env.DOCKER_HOST}:${process.env.DOCKER_PORT}/containers/${container}/json`,
+            method: 'GET',
+            agentOptions: getAgentOptions()
+        };
+        const inspectContainerResult = await requestPromise(inspectContainerOptions);
+        const inspectContainerResultJson = JSON.parse(inspectContainerResult);
+
+        // 2. inspect history of image for its base image
+        const imageId = inspectContainerResultJson.Image;
+        let imageHistoryOptions = {
+            uri: `https://${process.env.DOCKER_HOST}:${process.env.DOCKER_PORT}/images/${imageId}/history`,
+            method: 'GET',
+            agentOptions: getAgentOptions()
+        };
+        const imageHistoryResult = await requestPromise(imageHistoryOptions);
+        const imageHistoryResultJson = JSON.parse(imageHistoryResult);
+        const baseImageId = imageHistoryResultJson.reverse().find(item => item.Id !== "<missing>").Id;        
+
+        // Dockerfile
+        dockerFileLines = [
+            `FROM ${baseImageId}`
+        ];
+
+        // COPY file.enc
+        dockerFileLines = dockerFileLines.concat([`COPY ${encryptedTarArchiveFileName} ${encryptedTarArchiveFilePathInContainer}`]);
+
+        // update train_config file
+        let trainConfig = trainConfigUtil.getTrainConfigJsonBaseModel();
+        const trainConfigFileName = trainConfigUtil.getTrainConfigFileName();
+        const trainConfigFilePath = path.join(tempDirPath, trainConfigFileName);
+        if (fs.existsSync(trainConfigFilePath)) {
+            console.log("train_config");
+            const fileContent = fs.readFileSync(trainConfigFilePath);
+            trainConfig = JSON.parse(fileContent);
+        }
+        trainConfig = trainConfigUtil.updateTrainConfigJson(trainConfig, symmetricKey);
+        const updatedTrainConfigFilePath = path.join(tempDirPathAfterContainerExecution, trainConfigFileName);
+        fs.writeFileSync(updatedTrainConfigFilePath, JSON.stringify(trainConfig));
+
+        const trainConfigFilePathInContainer = trainConfigUtil.getTrainConfigFilePathInContainer();
+        // COPY train_config.json 
+        dockerFileLines = dockerFileLines.concat([`COPY ${trainConfigFileName} ${trainConfigFilePathInContainer}`]);
+
+        // dockerFileLines.push(`LABEL train_config=${JSON.stringify(JSON.stringify(trainConfig))}`);
+        
+        // REMOVE files - If a file is removed in this execution, remove it from base image
+        dockerFileLines = dockerFileLines.concat(deletedFileList.map( filePath => `RUN rm -rf ${filePath}`));
+
+        // save Dockerfile
+        const dockerFileName = "Dockerfile";
+        const dockerFilePath = path.join(tempDirPathAfterContainerExecution, dockerFileName);
+        fs.writeFileSync(dockerFilePath, dockerFileLines.join('\n'));
+
+        // create tar archive file to build a new image
+        const encImageTarArchiveFileName = "encImage.tar";
+        const encImageTarArchiveFilePath = path.join(tempDirPathAfterContainerExecution, encImageTarArchiveFileName);
+        tar.create({
+            cwd: tempDirPathAfterContainerExecution,
+            file: encImageTarArchiveFilePath,
+            sync: true,
+        }, [encryptedTarArchiveFileName, dockerFileName, trainConfigFileName]);
+    
+        // build a new image (contains updated train_config.json and file.enc)
+        const t = `${repo}:${tag}`
+        console.log(t);
+        let buildOptions = {
+            uri: `https://${process.env.DOCKER_HOST}:${process.env.DOCKER_PORT}/build?t=${t}`,
+            method: 'POST',
+            agentOptions: getAgentOptions()
+        };
+
+        buildOptions.body = fs.createReadStream(encImageTarArchiveFilePath);
+        const buildResult = await requestPromise(buildOptions);
+        console.log(buildResult);
+
+        // DELETE container after encryption and building a new image
+        let deleteContainerOptions = {
+            uri: `https://${process.env.DOCKER_HOST}:${process.env.DOCKER_PORT}/containers/${container}`,
+            method: 'DELETE',
+            agentOptions: getAgentOptions()
+        };
+        let deleteContainerResult = await requestPromise(deleteContainerOptions);
+
+        // DELETE IMAGE
+        let deleteImageOptions = {
+            uri: `https://${process.env.DOCKER_HOST}:${process.env.DOCKER_PORT}/images/${imageId}?force=true`,
+            method: 'DELETE',
+            agentOptions: getAgentOptions()
+        };
+        let deleteImageResult = await requestPromise(deleteImageOptions);
+
+        TransactionLog.create({user:req.user.email, logMessage:'Container ' + container + ' committed'});
+
+        // remove temp directory
+        removeJobTempDir(jobId);
+
+        req.flash('success_msg', 'Container ' + container + ' has been encrypted and committed.');
+        res.redirect('/dashboard/images');
+
+    } catch (error) {
+        console.log(error);
+        req.flash('error_msg', error);
+        res.redirect('/dashboard/containers');
+    }
+
+}));
+
+router.get('/image/decrypt', utility.asyncHandler(async (req, res, next) => {
+
+    const { jobId, image } = req.query;
+
+    const tempDirPath = getJobTempDir(jobId);
+    const encryptedTarArchiveFileName = getEncryptedTarArchiveFileName();
+    const encryptedTarArchiveFilePathInContainer = getEncryptedTarArchiveFilePathInContainer();
+
+    let tempContainerId = null;
+    let extractPath = null;
+
+    // list of files added to the base image on decryption process
+    let changes = [];
+    // train_config
+    let trainConfig = trainConfigUtil.getTrainConfigJsonBaseModel();
+
+    try {
+        console.log("try1");
+
+        // create a temp container
+        let createTempContainerOptions = {
+            uri: `https://${process.env.DOCKER_HOST}:${process.env.DOCKER_PORT}/containers/create`,
+            method: 'POST',
+            json: {
+                "Image": image
+            },
+            agentOptions: getAgentOptions()
+        };
+        let createTempContainerResult = await requestPromise(createTempContainerOptions);
+        tempContainerId = createTempContainerResult.Id;
+
+        extractPath = getTempDir(path.join(jobId, tempContainerId), true);
+
+        try {
+
+            console.log("try2");
+
+            // HEAD - check the existence of the train_config file
+            let trainConfigFilePathInContainer = trainConfigUtil.getTrainConfigFilePathInContainer();
+            let trainConfigFileHeadArchiveOptions = {
+                uri: `https://${process.env.DOCKER_HOST}:${process.env.DOCKER_PORT}/containers/${tempContainerId}/archive?path=${trainConfigFilePathInContainer}`,
+                method: 'HEAD',
+                agentOptions: getAgentOptions()
+            };
+            let trainConfigFileHeadArchiveResult = await requestPromise(trainConfigFileHeadArchiveOptions);
+
+            // GET - download and extract train_config file from the temp container (docker api provides tar archive)
+            let trainConfigFileGetArchiveOptions = {
+                uri: `https://${process.env.DOCKER_HOST}:${process.env.DOCKER_PORT}/containers/${tempContainerId}/archive?path=${trainConfigFilePathInContainer}`,
+                method: 'GET',
+                agentOptions: getAgentOptions(),
+                transform: function (body) {
+                    return Readable.from([body]);
+                }
+            };
+            let trainConfigFileGetArchiveResult = await requestPromise(trainConfigFileGetArchiveOptions);
+
+            // untar train_config
+            trainConfig = await trainConfigUtil.unTarTrainConfigJson(trainConfigFileGetArchiveResult);
+
+            if (trainConfig.hasOwnProperty(trainConfigUtil.train_config_constant["symmetric_key"]) && trainConfig[trainConfigUtil.train_config_constant["symmetric_key"]]) {
+
+                const encryptedSymmetricKey = trainConfig[trainConfigUtil.train_config_constant["symmetric_key"]];
+                const decryptedSymmetricKey = trainConfigUtil.decryptSymmetricKey(encryptedSymmetricKey);
+                const symmetricKeyBackup = cryptoUtil.getVaultSymmetricKeyBackupModel(jobId, decryptedSymmetricKey, null, { isBase64: true });
+
+                // restore symmetric key to vault
+                const symmetricKeyName = jobId;
+                let restoreSymmetricKeyResult = await vault.command.write(`transit/restore/${jobId}`, {
+                    backup: symmetricKeyBackup,
+                    name: symmetricKeyName,
+                    // force the restore to proceed even if a key by this name already exists.
+                    force: true
+                });
+
+                // HEAD - check the existence of the encrypted file
+                let headArchiveOptions = {
+                    uri: `https://${process.env.DOCKER_HOST}:${process.env.DOCKER_PORT}/containers/${tempContainerId}/archive?path=${encryptedTarArchiveFilePathInContainer}`,
+                    method: 'HEAD',
+                    agentOptions: getAgentOptions()
+                };
+                let headArchiveResult = await requestPromise(headArchiveOptions);
+
+                // GET - download and extract encrypted file from the temp container (docker api provides tar archive)
+                await new Promise((resolve, reject) => {
+
+                    let options = {
+                        uri: `https://${process.env.DOCKER_HOST}:${process.env.DOCKER_PORT}/containers/${tempContainerId}/archive?path=${encryptedTarArchiveFilePathInContainer}`,
+                        method: 'GET',
+                        agentOptions: getAgentOptions()
+                    };
+
+                    let stream = request(options)
+                        .pipe(
+                            tar.extract({
+                                cwd: extractPath,
+                                sync: true,
+                            })
+                        )
+                        .on('finish', () => {
+                            resolve();
+                        })
+                        .on('error', (error) => {
+                            console.log(error);
+                            reject(error);
+                        })
+                }).catch(error => {
+                    console.log(error);
+                    reject(error);
+                });
+
+                // read ciphertext
+                const encryptedTarArchiveFilePath = path.join(extractPath, encryptedTarArchiveFileName);
+                const ciphertext = fs.readFileSync(encryptedTarArchiveFilePath, "utf8");
+
+                // decrypt
+                const decryptionResult = await vault.write(`transit/decrypt/${symmetricKeyName}`, { ciphertext: ciphertext });
+                const plaintextEncoded = decryptionResult.data.plaintext;
+
+                // base64 decode - a tar archive file
+                const plaintext = Buffer.from(plaintextEncoded, 'base64');
+
+                // List the contents of a tar archive
+                const plaintextStream = Readable.from(plaintext);
+                let extractListOfContents = tarStream.extract();
+                extractListOfContents.on('entry', function (header, stream, next) {
+
+                    // header is the tar header
+                    // stream is the content body (might be an empty stream)
+                    // call next when you are done with this entry
+
+                    // clear stream - just file name is enough
+                    if (header.type == "file") {
+                        changes.push(header.name);
+                        stream = Readable.from("");
+                    }
+
+                    stream.on('end', function () {
+                        next() // ready for next entry
+                    })
+
+                    stream.resume(); // just auto drain the stream
+           
+                }).on('finish', function () {
+                    // all entries read
+                    
+                });
+                plaintextStream.pipe(extractListOfContents);
+
+                // put decrypted file into the container
+                let putArchiveOptions = {
+                    uri: `https://${process.env.DOCKER_HOST}:${process.env.DOCKER_PORT}/containers/${tempContainerId}/archive?path=/`,
+                    method: 'PUT',
+                    agentOptions: getAgentOptions(),
+                    body: plaintext
+                };
+                let putArchiveResult = await requestPromise(putArchiveOptions);
+
+            }
+
+        } catch (error) {
+            console.log("catch2");            
+            console.log("Decryption requirements are not complete.");
+
+            console.log(error);
+            req.flash('error_msg', error);
+            res.redirect('/dashboard/images');
+            return;
+        }
+
+        // COMMIT temp container
+        // set repo and tag from image name
+        const lastIndexOfColon = image.lastIndexOf(":");
+        const repo = image.substring(0, lastIndexOfColon);
+        const tag = image.substring(lastIndexOfColon + 1);
+
+        let commitTempContainerOptions = {
+            uri: `https://${process.env.DOCKER_HOST}:${process.env.DOCKER_PORT}/commit?container=${tempContainerId}&repo=${repo}&tag=${tag}`,
+            method: 'POST',
+            headers: {
+                "Content-Type": "application/json",
+            },
+            json: {
+                "Labels": {
+                    "decrypted": "true",
+                },
+            },
+            agentOptions: getAgentOptions()
+        };
+        let commitTempContainerResult = await requestPromise(commitTempContainerOptions);
+
+        req.flash('success_msg', 'Image ' + image + ' has been decrypted.');
+        res.redirect('/dashboard/images');
+
+    } catch (error) {
+        console.log("catch1");
+
+        console.log(error);
+        req.flash('error_msg', error);
+        res.redirect('/dashboard/images');
+
+    } finally {
+
+        console.log("finally");
+
+        if (tempContainerId) {
+            // DELETE temp container
+            var deleteTempContainerOptions = {
+                uri: `https://${process.env.DOCKER_HOST}:${process.env.DOCKER_PORT}/containers/${tempContainerId}`,
+                method: 'DELETE',
+                agentOptions: getAgentOptions()
+            };
+            request(deleteTempContainerOptions, (error, response, body) => {
+                if (error) {
+                    console.error(error);
+                }
+                if (body) {
+                    console.error(body);
+                }
+            });
+        }
+
+        if (extractPath) {
+            // DELETE extraction directory
+            fs.rm(extractPath, { recursive: true, force: true }, (err) => {
+                if (err)
+                    console.log(err)
+            });
+        }
+
+        // store list of decrypted files
+        const prevChangesFileName = getPrevChangesFileName();
+        const prevChangesFilePath = path.join(tempDirPath, prevChangesFileName);
+        fs.writeFileSync(prevChangesFilePath, JSON.stringify(changes));
+
+        const trainConfigFileName = trainConfigUtil.getTrainConfigFileName();
+        const trainConfigFilePath = path.join(tempDirPath, trainConfigFileName);
+        fs.writeFileSync(trainConfigFilePath, JSON.stringify(trainConfig));
+
+    }
+}));
+
+
+// ***** TEMP CODE - DOWNLOAD IMAGE AS A TAR ARCHIVE FILE - START *****
+router.get('/image/export', utility.asyncHandler(async (req, res, next) => {
+    const { image } = req.query;
+    let options = {
+        uri: `https://${process.env.DOCKER_HOST}:${process.env.DOCKER_PORT}/images/${image}/get`,
+        method: 'GET',
+        agentOptions: getAgentOptions()
+    };
+
+    request(options).on("error", async (err) => {
+        console.log('error: ', err);
+        res.status(500).send(err);
+    }).on("response", async (response) => {
+
+        if (response.statusCode == 200) {
+            let fileName = `${image}.tar`;
+            res.set('Content-disposition', 'attachment; filename=' + fileName);
+            res.set('Content-Type', 'application/x-tar');
+        } else {
+            let fileName = "error.json";
+            res.set('Content-disposition', 'attachment; filename=' + fileName);
+            res.set('Content-Type', 'application/json');
+        }
+    }).on("data", async (data) => {
+        res.write(data);
+    }).on("complete", async (response) => {
+        res.end();
+    });
+}));
+// ***** TEMP CODE - DOWNLOAD IMAGE AS A TAR ARCHIVE FILE - END *****
+
+
+// ***** TEMP CODE - DOWNLOAD CONTANIER AS A TAR ARCHIVE FILE - START *****
+router.get('/container/export', utility.asyncHandler(async (req, res, next) => {
+    const { container } = req.query;
+    let options = {
+        uri: `https://${process.env.DOCKER_HOST}:${process.env.DOCKER_PORT}/containers/${container}/export`,
+        method: 'GET',
+        agentOptions: getAgentOptions()
+    };
+
+    request(options).on("error", async (err) => {
+        console.log('error: ', err);
+        res.status(500).send(err);
+    }).on("response", async (response) => {
+
+        if (response.statusCode == 200) {
+            let fileName = `${container}.tar`;
+            res.set('Content-disposition', 'attachment; filename=' + fileName);
+            res.set('Content-Type', 'application/x-tar');
+        } else {
+            let fileName = "error.json";
+            res.set('Content-disposition', 'attachment; filename=' + fileName);
+            res.set('Content-Type', 'application/json');
+        }
+    }).on("data", async (data) => {
+        res.write(data);
+    }).on("complete", async (response) => {
+        res.end();
+    });
+}));
+// ***** TEMP CODE - DOWNLOAD CONTANIER AS A TAR ARCHIVE FILE - END *****
+
+module.exports = router;
\ No newline at end of file
diff --git a/StationSoftware/routes/error/error.js b/StationSoftware/routes/error/error.js
new file mode 100644
index 0000000000000000000000000000000000000000..dc8bc83c246939dc842dfdb2952039b61f049f3c
--- /dev/null
+++ b/StationSoftware/routes/error/error.js
@@ -0,0 +1,7 @@
+const express = require('express');
+const router = express.Router();
+
+// Error Page
+router.get('/', (req, res) => res.render('error', { user: req.user }));
+
+module.exports = router;
\ No newline at end of file
diff --git a/StationSoftware/routes/helpers/MetadataNotifier.js b/StationSoftware/routes/helpers/MetadataNotifier.js
new file mode 100644
index 0000000000000000000000000000000000000000..ec6e919aa3389b3466cc71895d5d4da60c5d659c
--- /dev/null
+++ b/StationSoftware/routes/helpers/MetadataNotifier.js
@@ -0,0 +1,90 @@
+const axios = require('axios')
+const host = process.env.METADATAPROVIDER_ENDPOINT
+const imageIdentifyingCache = []
+async function notifyMetadataProviderImageStartedDownloading(jobId, fullImageIdentifier, timestamp) {
+    console.log("USING HOST FOR METADATA NOTIFICATIONS:" + host)
+    console.log("Sending StartedDownloading Notification for:" + jobId)
+    if (!timestamp instanceof Date) {
+        throw TypeError("timestamp is not date compatible type")
+    }
+    if (!jobId instanceof String) {
+        throw TypeError("jobId is no string compatible type")
+    }
+    data = {
+        jobId: jobId,
+        image: fullImageIdentifier,
+        timestamp: timestamp.toISOString()
+    }
+    await axios.post(host + '/execution/startedDownloading', JSON.stringify(data)).then(res => { console.log('Done with res:'+String(res.statusCode))}).catch(err => console.log(err.message))
+}
+async function notifyMetadataProviderImageFinishedDownloading(jobId, timestamp) {
+    console.log("Sending FinishDownloading Notification for:" + jobId)
+    if (!timestamp instanceof Date) {
+        throw TypeError("timestamp is not date compatible type")
+    }
+    if (!jobId instanceof String) {
+        throw TypeError("jobId is no string compatible type")
+    }
+    data = {
+        jobId: jobId,
+        timestamp: timestamp.toISOString()
+    }
+    return axios.post(host + '/execution/finishedDownloading', JSON.stringify(data)).then(res => { console.log('Done with res:'+String(res.statusCode))}).catch(err => console.log(err.message))
+}
+async function notifyMetadataProviderImageStartedRunning(jobId, timestamp) {
+    console.log("Sending StartedRunning Notification for:" + jobId)
+    if (!timestamp instanceof Date) {
+        throw TypeError("timestamp is not date compatible type")
+    }
+    if (!jobId instanceof String) {
+        throw TypeError("jobId is no string compatible type")
+    }
+    data = {
+        jobId: jobId,
+        timestamp: timestamp.toISOString()
+    }
+    return axios.post(host + '/execution/startedRunning', JSON.stringify(data)).then(res => { console.log('Done with res:'+String(res.statusCode))}).catch(err => console.log(err.message))
+}
+async function notifyMetadataProviderImageFinished(jobId, successful, timestamp) {
+    console.log("Sending finish Notification for:" + jobId)
+    if (!timestamp instanceof Date) {
+        throw TypeError("timestamp is not date compatible type")
+    }
+    if (!jobId instanceof String) {
+        throw TypeError("jobId is no string compatible type")
+    }
+    if (!jobId instanceof Boolean) {
+        throw TypeError("successful is no string compatible type")
+    }
+    data = {
+        jobId: jobId,
+        timestamp: timestamp.toISOString(),
+        successful: successful
+    }
+    return axios.post(host + '/execution/finished', JSON.stringify(data)).then(res => { console.log('Done with res:'+String(res.statusCode))}).catch(err => console.log(err.message))
+}
+async function notifyMetadataProviderImageRejected(jobId, message, timestamp) {
+    console.log("Sending reject Notification for:" + jobId)
+    if (!timestamp instanceof Date) {
+        throw TypeError("timestamp is not date compatible type")
+    }
+    if (!jobId instanceof String) {
+        throw TypeError("jobId is no string compatible type")
+    }
+
+    data = {
+        jobId: jobId,
+        timestamp: timestamp.toISOString(),
+        message: message
+    }
+    return axios.post(host + '/execution/rejected', JSON.stringify(data)).then(res => { console.log('Done with res:'+String(res.statusCode))}).catch(err => console.log(err.message))
+}
+
+module.exports= {
+    notifyMetadataProviderImageFinished,
+    notifyMetadataProviderImageFinishedDownloading,
+    notifyMetadataProviderImageRejected,
+    notifyMetadataProviderImageStartedDownloading,
+    notifyMetadataProviderImageStartedRunning,
+    imageIdentifyingCache
+}
\ No newline at end of file
diff --git a/StationSoftware/routes/index.js b/StationSoftware/routes/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..75081f0911719c7d38d72bd30c43e5b5322677bc
--- /dev/null
+++ b/StationSoftware/routes/index.js
@@ -0,0 +1,39 @@
+const express = require('express');
+const router = express.Router();
+const dashboardRouter = require('./dashboard/dashboard');
+const authRouter = require('./auth/auth');
+const apiRouter = require('./api/api');
+const dockerRouter = require('./docker/docker');
+const metadataRouter = require('./metadata/metadata')
+const errorRouter = require('./error/error');
+const vaultRouter = require('./vault/vault');
+
+
+const {forwardAuthenticated, ensureAuthenticated} = require('../validation/auth');
+// Welcome Page
+router.get('/', forwardAuthenticated, (req, res) => res.render('welcome'));
+
+// RESTful API router
+router.use('/api', apiRouter);
+
+// Dashboard
+router.use('/dashboard', dashboardRouter);
+
+// Authenfication
+router.use('/auth', authRouter);
+
+// Docker API
+router.use('/docker', ensureAuthenticated, dockerRouter);
+
+
+// Metadata service API
+router.use('/metadata', ensureAuthenticated, metadataRouter)
+
+// Vault
+router.use('/vault', ensureAuthenticated, vaultRouter);
+
+//Error
+router.use('/error', errorRouter);
+
+
+module.exports = router;
\ No newline at end of file
diff --git a/StationSoftware/routes/metadata/metadata.js b/StationSoftware/routes/metadata/metadata.js
new file mode 100644
index 0000000000000000000000000000000000000000..b8b980da2f0a47707d4994b2aa50b788bfeddae8
--- /dev/null
+++ b/StationSoftware/routes/metadata/metadata.js
@@ -0,0 +1,27 @@
+const express = require('express');
+const router = express.Router();
+const axios = require('axios')
+
+router.post('/updateList', async function (req, res) {
+    const { list, useAllowList } = req.body
+    if (useAllowList === "true") {
+        useAllowListBool = true
+    } else {
+        useAllowListBool = false
+    }
+    console.log("UseALlowList"+ String(useAllowListBool))
+    // the metadata provider needs an list instead of an array
+    listArray = list.split("\n")
+    // post to the server, if result is 200, flash a message to the user
+    axios.post(process.env.METADATAPROVIDER_ENDPOINT + "/filter", JSON.stringify({ list: listArray, useAllowList: useAllowListBool})).then(respond => {
+        if(respond.statusCode === 200) {
+            req.flash('success_msg', 'MetadataProvider service updated!');
+            res.redirect('/dashboard/metadata')
+        } else {
+            req.flash('ERROR');
+            res.redirect('/dashboard/metadata')
+        }
+    })
+})
+
+module.exports = router
\ No newline at end of file
diff --git a/StationSoftware/routes/vault/vault.js b/StationSoftware/routes/vault/vault.js
new file mode 100644
index 0000000000000000000000000000000000000000..21d2f9b8daa59ceb39d04bd0ae6e979e0d5df9d7
--- /dev/null
+++ b/StationSoftware/routes/vault/vault.js
@@ -0,0 +1,368 @@
+const { response } = require('express');
+const express = require('express');
+const router = express.Router();
+const { utility, vault } = require('../../utils');
+
+//List All KV Engines - View
+router.get('/', utility.asyncHandler(async (req, res, next) => {
+
+    let vaultHealthStatus = await vault.command.health();
+    // console.log(vaultHealthStatus);
+    if (vaultHealthStatus.isError) {
+        res.render('error', { user: req.user, error_msg: (JSON.stringify(vaultHealthStatus.data.error) || 'error')});
+        return;
+    }
+
+    if (!vaultHealthStatus.initialized) {
+        res.redirect('/vault/init');
+        return;
+    }
+
+    if (vaultHealthStatus.sealed) {
+        res.redirect('/vault/unseal');
+        return;
+    }
+
+    let vaultIsAuthenticated = await vault.isAuthenticated();
+    if (!vaultIsAuthenticated) {
+        res.redirect('/vault/set-token');
+        return;
+    }
+
+    let kvEngines = await vault.getKeyValueEngines();
+    // console.log(kvEngines);
+    res.render('./vault/vault-kv-engine-list', { user: req.user, kvEngines: kvEngines});
+}));
+
+router.get('/init', utility.asyncHandler(async (req, res, next) => {
+
+    let initStatus = await vault.command.initialized();
+    if (initStatus.initialized) {
+        res.redirect('/vault');
+        return;
+    }
+    res.render('./vault/vault-init', { user: req.user });
+}));
+
+router.post('/init', utility.asyncHandler(async (req, res, next) => {
+
+    const { keyShares, keyThreshold } = req.body;
+    let initOption = {
+        secret_shares : parseInt(keyShares),
+        secret_threshold : parseInt(keyThreshold)
+    }
+    initResult = await vault.command.init(initOption);
+    console.log(initResult);
+    
+    res.render('./vault/vault-init-download-keys', { user: req.user, vaultKeys: initResult, vaultkeyThreshold : keyThreshold });
+}));
+
+router.get('/unseal', utility.asyncHandler(async (req, res, next) => {
+
+    let sealStatus = await vault.command.status();
+    if (!sealStatus.initialized) {
+        res.redirect('/vault');
+        return;
+    }
+    if (!sealStatus.sealed) {
+        res.redirect('/vault');
+        return;
+    }
+    
+    sealStatus.progressPercentage = ((sealStatus.progress/sealStatus.t) * 100).toFixed(2);
+    res.render('./vault/vault-unseal', { user: req.user, vaultSealStatus: sealStatus });
+}));
+
+router.post('/unseal', utility.asyncHandler(async (req, res, next) => {
+
+    const { key } = req.body;
+    let unsealOption = {
+        key : key
+    }
+    unsealResult = await vault.command.unseal(unsealOption);
+    console.log("unsealResult",unsealResult);
+    
+    if (unsealResult.sealed) {
+        res.redirect('/vault/unseal');
+        return;
+    }
+
+    res.redirect('/vault');
+}));
+
+router.get('/set-token', utility.asyncHandler(async (req, res, next) => {
+
+    let sealStatus = await vault.command.status();
+    if (!sealStatus.initialized) {
+        res.redirect('/vault');
+        return;
+    }
+    if (sealStatus.sealed) {
+        res.redirect('/vault');
+        return;
+    }
+
+    let vaultIsAuthenticated = await vault.isAuthenticated();
+    if (vaultIsAuthenticated) {
+        res.redirect('/vault');
+        return;
+    }
+
+    res.render('./vault/vault-set-token', { user: req.user});
+}));
+
+router.post('/set-token', (req, res, next) => {
+
+    const { token } = req.body;
+    vault.setToken(token);
+
+    res.redirect('/vault');
+});
+
+
+//Enable KV Engine - View
+router.get('/kv/enable', async (req, res, next) => {
+    res.render('./vault/vault-kv-engine-enable', { user: req.user });
+});
+
+//Enable KV Engine
+router.post('/kv/enable', utility.asyncHandler(async (req, res, next) => {
+
+    const { path, version } = req.body;
+
+    let vaultReqBody = { "path": path, "type": "kv", "config": {}, "options": { "version": version }, "generate_signing_key": true }
+    let result = await vault.write(`sys/mounts/${path}`, vaultReqBody);
+    // console.log(result);
+    if (!result.isError) {
+        res.redirect(`/vault/kv/secret/read/${encodeURIComponent(path + '/')}`);
+    }
+    else {
+        req.flash('error_msg', JSON.stringify(result) || 'error');
+        res.redirect('/vault/kv/enable');
+    }
+}));
+
+//Disable KV Engine
+router.get('/kv/disable/:kvEngine', utility.asyncHandler(async (req, res, next) => {
+
+    let kvEngine = req.params.kvEngine;
+    // console.log('kvEngine', kvEngine)
+
+    let result = await vault.delete(`sys/mounts/${kvEngine}`);
+    // console.log(result);
+    if (result.isError) {
+        req.flash('error_msg', JSON.stringify(result) || 'error');
+    }
+    else {
+        req.flash('success_msg', `${kvEngine} successfully disabled.`);
+    }
+    res.redirect('/vault');
+}));
+
+//KV Engine Configuration - View
+router.get('/kv/configuration/:kvEngine', utility.asyncHandler(async (req, res, next) => {
+
+    let kvEngine = req.params.kvEngine;
+    // console.log('kvEngine', kvEngine)
+
+    let result = await vault.read(`sys/internal/ui/mounts/${kvEngine}`);
+    // console.log(result);
+    if (result.isError) {
+        req.flash('error_msg', JSON.stringify(result) || 'error');
+        res.redirect('/vault');
+        return;
+    }
+    res.render('./vault/vault-kv-engine-config-show', { user: req.user, kvEngineConfig: result.data });
+
+}));
+
+router.get('/kv/secret/read/:vaultPath', utility.asyncHandler(async (req, res, next) => {
+
+    let vaultPath = req.params.vaultPath;
+    let vaultApiPath = vaultPath;
+    // console.log('vaultPath', vaultPath);
+
+    let kvEnginePath = vaultPath.split('/')[0];
+    let kvEngineConfig = await vault.read(`sys/internal/ui/mounts/${kvEnginePath}`);
+
+    if (kvEngineConfig.isError) {
+        req.flash('error_msg', JSON.stringify(kvEngineConfig) || 'error');
+        res.redirect('/vault');
+        return;
+    }
+
+    //List sub path secrets
+    if (vaultPath.endsWith('/')) {
+
+        if (kvEngineConfig.data.options.version == 2) {
+            let temp = vaultPath.split('/');
+            temp.splice(1, 0, 'metadata');
+            vaultApiPath = temp.join('/')
+        }
+        let result = await vault.list(vaultApiPath);
+        // console.log('result', result);
+        let paths = [];
+        if (!result.isError) {
+            paths = result.data.keys;
+        }
+
+        let vaultPathBreadcrumb = vaultPath.split('/').slice(0, -1).map(function (o, i) {
+                return { "showName": o, "href": (vaultPath.split('/').slice(0, i + 1).concat('').join('/')) };
+        });
+        // console.log(vaultPathBreadcrumb);
+
+        res.render('./vault/vault-kv-secret-list', { user: req.user, vaultPath: vaultPath, paths: paths, vaultPathBreadcrumb: vaultPathBreadcrumb });
+    }
+    //Show secret
+    else {
+        if (kvEngineConfig.data.options.version == 2) {
+            let temp = vaultPath.split('/');
+            temp.splice(1, 0, 'data');
+            vaultApiPath = temp.join('/')
+        }
+        let result = await vault.read(vaultApiPath);
+        // console.log('result', result);
+        if (!result.isError) {
+            let data = {};
+            let metadata = null;
+            if (kvEngineConfig.data.options.version == 2) {
+                data = result.data.data;
+                metadata = result.data.metadata;
+            }
+            else
+                data = result.data;
+            res.render('./vault/vault-kv-secret-show', { user: req.user, vaultPath: vaultPath, secretData: data, secretMetaData: metadata });
+        }
+        else {
+            req.flash('error_msg', JSON.stringify(result) || 'error');
+            res.redirect('/vault');
+        }
+    }
+
+}));
+
+router.get('/kv/secret/create/:vaultPath', async (req, res, next) => {
+
+    let vaultPath = req.params.vaultPath;
+    let vaultApiPath = vaultPath;
+    // console.log('vaultPath', vaultPath);
+
+    res.render('./vault/vault-kv-secret-create', { user: req.user, vaultPath: vaultPath });
+});
+
+router.post('/kv/secret/create', utility.asyncHandler(async (req, res, next) => {
+
+    const { vaultPath, secretPath, secretData } = req.body;
+
+    let vaultReqBody = {};
+
+    secretData.forEach(secret => {
+        if (secret.key)
+            vaultReqBody[secret.key] = secret.value;
+    });
+
+    let vaultApiPath = `${vaultPath}${secretPath}`;
+    // console.log(vaultReqBody);
+    let result = await vault.write(vaultApiPath, vaultReqBody);
+    // console.log(result);
+    if (!result.isError) {
+        res.redirect(`/vault/kv/secret/read/${encodeURIComponent(vaultApiPath)}`);
+    }
+    else {
+        req.flash('error_msg', JSON.stringify(result) || 'error');
+        res.redirect(`/vault/kv/secret/create/${encodeURIComponent(vaultPath)}`);
+    }
+
+}));
+
+router.get('/kv/secret/delete/:vaultPath', utility.asyncHandler(async (req, res, next) => {
+
+    let vaultPath = req.params.vaultPath;
+    // console.log('vaultPath', vaultPath);
+
+    let result = await vault.delete(`${vaultPath}`);
+    // console.log(result);
+    redirectPath = vaultPath.split('/');
+    redirectPath.splice(-1,1);
+    redirectPath = redirectPath.join('/');
+    redirectPath = `${redirectPath}/`;
+    if (result.isError) {
+        req.flash('error_msg', JSON.stringify(result) || 'error');
+    }
+    res.redirect(`/vault/kv/secret/read/${encodeURIComponent(redirectPath)}`);
+}));
+
+//Edit Secret - View
+router.get('/kv/secret/edit/:vaultPath', utility.asyncHandler(async (req, res, next) => {
+
+    let vaultPath = req.params.vaultPath;
+    //Vault API path may differ from actual Vault path (KV V2: Writing and reading are prefixed with the data/ path)
+    let vaultApiPath = vaultPath;
+    // console.log('vaultPath', vaultPath);
+
+    let kvEnginePath = vaultPath.split('/')[0];
+    let kvEngineConfig = await vault.read(`sys/internal/ui/mounts/${kvEnginePath}`);
+
+    if (kvEngineConfig.isError) {
+        req.flash('error_msg', JSON.stringify(kvEngineConfig) || 'error');
+        res.redirect('/vault');
+        return;
+    }
+
+    if (kvEngineConfig.data.options.version == 2) {
+        let temp = vaultPath.split('/');
+        temp.splice(1, 0, 'data');
+        vaultApiPath = temp.join('/')
+    }
+    let result = await vault.read(vaultApiPath);
+    // console.log('result', result);
+    if (!result.isError) {
+        let data = {};
+        let metadata = null;
+        if (kvEngineConfig.data.options.version == 2) {
+            data = result.data.data;
+            metadata = result.data.metadata;
+        }
+        else
+            data = result.data;
+        res.render('./vault/vault-kv-secret-edit', { user: req.user, vaultPath: vaultPath, secretData: data, secretMetaData: metadata });
+    }
+    else {
+        req.flash('error_msg', JSON.stringify(result) || 'error');
+        res.redirect('/vault');
+    }
+
+}));
+
+//Edit Secret
+router.post('/kv/secret/edit', utility.asyncHandler(async (req, res, next) => {
+
+    const { vaultPath, secretData } = req.body;
+
+    let vaultReqBody = {};
+
+    secretData.forEach(secret => {
+        if (secret.key)
+            vaultReqBody[secret.key] = secret.value;
+    });
+
+    let vaultApiPath = `${vaultPath}`;
+    // console.log(vaultReqBody);
+    let result = await vault.write(vaultApiPath, vaultReqBody);
+    // console.log(result);
+    if (!result.isError) {
+        res.redirect(`/vault/kv/secret/read/${encodeURIComponent(vaultPath)}`);
+    }
+    else {
+        req.flash('error_msg', JSON.stringify(result) || 'error');
+        res.redirect(`/vault/kv/secret/edit/${encodeURIComponent(vaultPath)}`);
+    }
+
+}));
+
+module.exports = router;
+
+
+//http://menzel.informatik.rwth-aachen.de:8200/v1/sys/mounts/test1
+// {"path":"test1","type":"kv","config":{},"options":{"version":2},"generate_signing_key":true}
+//{"path":"test2","type":"kv","config":{},"options":{"version":1},"generate_signing_key":true}
\ No newline at end of file
diff --git a/StationSoftware/tls_generate_certs.sh b/StationSoftware/tls_generate_certs.sh
new file mode 100644
index 0000000000000000000000000000000000000000..c9b9dd9be6382e4f6cb4eebc7ce70c4d40720b4b
--- /dev/null
+++ b/StationSoftware/tls_generate_certs.sh
@@ -0,0 +1,87 @@
+_tls_ensure_private() {
+	local f="$1"; shift
+	[ -s "$f" ] || openssl genrsa -out "$f" 4096
+}
+_tls_san() {
+	{
+		ip -oneline address | awk '{ gsub(/\/.+$/, "", $4); print "IP:" $4 }'
+		{
+			cat /etc/hostname
+			echo 'docker'
+			echo 'localhost'
+			hostname -f
+			hostname -s
+		} | sed 's/^/DNS:/'
+		[ -z "${DOCKER_TLS_SAN:-}" ] || echo "$DOCKER_TLS_SAN"
+	} | sort -u | xargs printf '%s,' | sed "s/,\$//"
+}
+_tls_generate_certs() {
+	local dir="$1"; shift
+
+	# if ca/key.pem || !ca/cert.pem, generate CA public if necessary
+	# if ca/key.pem, generate server public
+	# if ca/key.pem, generate client public
+	# (regenerating public certs every startup to account for SAN/IP changes and/or expiration)
+
+	# https://github.com/FiloSottile/mkcert/issues/174
+	local certValidDays='825'
+
+	if [ -s "$dir/ca/key.pem" ] || [ ! -s "$dir/ca/cert.pem" ]; then
+		# if we either have a CA private key or do *not* have a CA public key, then we should create/manage the CA
+		mkdir -p "$dir/ca"
+		_tls_ensure_private "$dir/ca/key.pem"
+		openssl req -new -key "$dir/ca/key.pem" \
+			-out "$dir/ca/cert.pem" \
+			-subj '/CN=docker CA' -x509 -days "$certValidDays"
+	fi
+
+	if [ -s "$dir/ca/key.pem" ]; then
+		# if we have a CA private key, we should create/manage a server key
+		mkdir -p "$dir/server"
+		_tls_ensure_private "$dir/server/key.pem"
+		openssl req -new -key "$dir/server/key.pem" \
+			-out "$dir/server/csr.pem" \
+			-subj '/CN=docker server'
+		cat > "$dir/server/openssl.cnf" <<-EOF
+			[ x509_exts ]
+			subjectAltName = $(_tls_san)
+		EOF
+		openssl x509 -req \
+				-in "$dir/server/csr.pem" \
+				-CA "$dir/ca/cert.pem" \
+				-CAkey "$dir/ca/key.pem" \
+				-CAcreateserial \
+				-out "$dir/server/cert.pem" \
+				-days "$certValidDays" \
+				-extfile "$dir/server/openssl.cnf" \
+				-extensions x509_exts
+		cp "$dir/ca/cert.pem" "$dir/server/ca.pem"
+		openssl verify -CAfile "$dir/server/ca.pem" "$dir/server/cert.pem"
+	fi
+
+	if [ -s "$dir/ca/key.pem" ]; then
+		# if we have a CA private key, we should create/manage a client key
+		mkdir -p "$dir/client"
+		_tls_ensure_private "$dir/client/key.pem"
+		chmod 0644 "$dir/client/key.pem" # openssl defaults to 0600 for the private key, but this one needs to be shared with arbitrary client contexts
+		openssl req -new \
+				-key "$dir/client/key.pem" \
+				-out "$dir/client/csr.pem" \
+				-subj '/CN=docker client'
+		cat > "$dir/client/openssl.cnf" <<-'EOF'
+			[ x509_exts ]
+			extendedKeyUsage = clientAuth
+		EOF
+		openssl x509 -req \
+				-in "$dir/client/csr.pem" \
+				-CA "$dir/ca/cert.pem" \
+				-CAkey "$dir/ca/key.pem" \
+				-CAcreateserial \
+				-out "$dir/client/cert.pem" \
+				-days "$certValidDays" \
+				-extfile "$dir/client/openssl.cnf" \
+				-extensions x509_exts
+		cp "$dir/ca/cert.pem" "$dir/client/ca.pem"
+		openssl verify -CAfile "$dir/client/ca.pem" "$dir/client/cert.pem"
+	fi
+}
\ No newline at end of file
diff --git a/StationSoftware/utils/crypto/index.js b/StationSoftware/utils/crypto/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..02e2248851b989dce1c769acf872e1d69cc12a8b
--- /dev/null
+++ b/StationSoftware/utils/crypto/index.js
@@ -0,0 +1,140 @@
+const crypto = require('crypto');
+
+const PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv3UBcs2zeiRjaj4u1EEE\nxGfrsAqqnaXuGFiU+De4dL7Tm8SEf+lnjT7BSQnTasM3K7spsLvGrN0eoHye+VDZ\nUZTLIZ4nLONx0ZH9bXbKado+hUheNoAg8ut/immsZZurOU+ymDdauN3yCZ7/Dwo/\n3Rd1fUtMOlrLmNZ78XjiUy60rf69PZanzvcILeknu2ATDLh6c894UyfAN3EWfitI\nnRj0+d46fmSCjeRo21UyFfISyyAhoAGpWONUhwUSmuY3k6b1OV1d8fFqsuwmeXld\nZ+s0fzW/+Adl45l8dM5YXoK44tMv3G8nzPu0RvEk8i0bA/WwPM9oWp1v2utCLo1z\nswIDAQAB\n-----END PUBLIC KEY-----\n`
+const PRIVATE_KEY = `-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAv3UBcs2zeiRjaj4u1EEExGfrsAqqnaXuGFiU+De4dL7Tm8SE\nf+lnjT7BSQnTasM3K7spsLvGrN0eoHye+VDZUZTLIZ4nLONx0ZH9bXbKado+hUhe\nNoAg8ut/immsZZurOU+ymDdauN3yCZ7/Dwo/3Rd1fUtMOlrLmNZ78XjiUy60rf69\nPZanzvcILeknu2ATDLh6c894UyfAN3EWfitInRj0+d46fmSCjeRo21UyFfISyyAh\noAGpWONUhwUSmuY3k6b1OV1d8fFqsuwmeXldZ+s0fzW/+Adl45l8dM5YXoK44tMv\n3G8nzPu0RvEk8i0bA/WwPM9oWp1v2utCLo1zswIDAQABAoIBABp4bePWhtr7Tvdw\nc38LIkKZb5+eSeqT93BMRwRuh8U0YQNnTz50IcoIhCa6Ag3/wY/9R41U2OorhEGC\nGWS1BebeoJscR6RQKftkqNpKkz/BWeWJlOUKIAQWBUM4ywodVLYOy8150cQ/g2hL\nIE6PBzdlN+xDzd0/kmimNuqy4O/JBuAFELwJX0pfR+dADsD1PEX4BaWz9VBfD2Ef\n+MZKeCxQ4DnuWyyzCIgxT5h1Gwc7zwqSlVy+BWRMmufWstsX0ib2DNJwm2VASi+d\ndqWB2I/4NMuhMOmC96h0rgCJgDWM4Ds+niFLsVMres1VBkyyXclbvL3J6UnFWlfA\nuuLJQmkCgYEA8ldsL7PHQvXwCYq3wEdDEpGEm3Gl7khDiNsCiFokRvBoMIb/rYYT\n3a8kNTSf9SurvmYC9VSxhApe56bG7i7WIfvzDXZR6L3abBeDIWCVJ9mvKdW+0VaX\niMFz5z4EZusKAFDJUhhgMDosoHqJlOHONT04zHG80e8zkKD+k+JOxf0CgYEAyj9o\nH7dZ0vwoaP6tWYZAgmHvpq4JPuFgl8FBuHwHRqVmXK5ahYmy5rirlUWN898lzYEu\nA3zboHPIc97BqxwduT1mjguAX4mXlvMeB3insH1KD88DXL2jBm1f+AIq6hqV2x7T\nx4+9L31tS28Pyo4wxmxev4ekgwwKrz/wuzHhd28CgYAdHNV5UY6Rg7wHWWvDpIvx\nMhwNFHULkBDU4wKF4NZU01Kg6cbTULUYP48I+T5yFIH4SIb4c+kzZI+MIqPpPyUo\nRf0n09v5Kr2PmK9/Ffw1IliBnRTkTxO7MQo8cF3VA01bRlk5DIaZpJNx3+ahRRMh\noC4vmUZGrgayzDRpDZnK/QKBgFaEJ0OiCG/D5Hl9sKQiVQgxYvY3bscSXGKujjGg\nBPDIonA1OY30aK5gAy5Y0a+oHqC5iPh++ei6ft5qRQiwf1qVlIBhFSpJTqqJF6h0\nia9q+TqoALU0fj+qnCoYq0j31HEmz8uHhpOBITbqrKOmjeDjzOg72zkf9pYfURiS\n7vNLAoGAHCygRRFngrakTAbxnM0Cy3YStiWrJRC7LIhBqH16cwJmhWWQvBa7KuPc\n71+FnAHt/uXSM541YsMC9O0VeawXelGBJuFsXOfzCtsKpvenWTexYeC1BGhmtpfJ\nLuIzXbimBjt+8CQNs4shIeJEVWNdUvG6eH+vqvx3VkwsV+4a1Fg=\n-----END RSA PRIVATE KEY-----\n`
+
+const getVaultSymmetricKeyBackupModel = (name, key, hmacKey, opts = {}) => {
+
+    //opts.isBase64
+    const isBase64 = opts.isBase64;
+
+    const currDate = new Date();
+
+    let backupModel = {
+        "policy": {
+            "name": `${name}`,
+            "keys": {
+                "1": {
+                    "key": `${key}`,
+                    "hmac_key": `${hmacKey}`,
+                    "time": `${currDate.toISOString()}`,
+                    "ec_x": null,
+                    "ec_y": null,
+                    "ec_d": null,
+                    "rsa_key": null,
+                    "public_key": "",
+                    "convergent_version": 0,
+                    "creation_time": currDate.valueOf()
+                }
+            },
+            "derived": false,
+            "kdf": 0,
+            "convergent_encryption": false,
+            "exportable": true,
+            "min_decryption_version": 1,
+            "min_encryption_version": 0,
+            "latest_version": 1,
+            "archive_version": 1,
+            "archive_min_version": 0,
+            "min_available_version": 0,
+            "deletion_allowed": false,
+            "convergent_version": 0,
+            "type": 0,
+            "backup_info": {
+                "time": `${currDate.toISOString()}`,
+                "version": 1
+            },
+            "restore_info": null,
+            "allow_plaintext_backup": true,
+            "version_template": "",
+            "storage_prefix": ""
+        },
+        "archived_keys": {
+            "keys": [
+                {
+                    "key": null,
+                    "hmac_key": null,
+                    "time": "0001-01-01T00:00:00Z",
+                    "ec_x": null,
+                    "ec_y": null,
+                    "ec_d": null,
+                    "rsa_key": null,
+                    "public_key": "",
+                    "convergent_version": 0,
+                    "creation_time": 0
+                },
+                {
+                    "key": `${key}`,
+                    "hmac_key": `${hmacKey}`,
+                    "time": `${currDate.toISOString()}`,
+                    "ec_x": null,
+                    "ec_y": null,
+                    "ec_d": null,
+                    "rsa_key": null,
+                    "public_key": "",
+                    "convergent_version": 0,
+                    "creation_time": currDate.valueOf()
+                }
+            ]
+        }
+    };
+
+    // base64 encoding
+    if (isBase64) {
+        let buff = Buffer.from(JSON.stringify(backupModel));
+        backupModel = buff.toString('base64');
+    }
+
+    return backupModel;
+}
+
+const getRsaPublickey = (ownerId, opts) => {
+    // opts.isBase64
+    var isBase64 = false;
+    if (opts) {
+        isBase64 = opts.isBase64;
+    }
+
+    let publicKey = null;
+
+    if (ownerId) {
+        publicKey = PUBLIC_KEYS[ownerId]
+    }
+    else {
+        publicKey = PUBLIC_KEY;
+    }
+
+    //  ***** TEMP START ****** if owner is train requester
+    if (!publicKey)
+        publicKey = PUBLIC_KEY;
+    //  ***** TEMP END ******
+
+    // base64 encoding
+    if (isBase64) {
+        let buff = Buffer.from(publicKey);
+        publicKey = buff.toString('base64');
+    }
+
+    return publicKey;
+}
+
+const encryptWithRsaPublicKey = (toEncrypt, publicKey) => {
+    let buffer = Buffer.from(toEncrypt);
+    let encrypted = crypto.publicEncrypt(publicKey, buffer);
+    return encrypted.toString("base64");
+};
+
+const decryptWithRsaPrivateKey = (toDecrypt) => {
+    const privateKey = PRIVATE_KEY;
+    var buffer = Buffer.from(toDecrypt, "base64");
+    var decrypted = crypto.privateDecrypt(privateKey, buffer);
+    return decrypted.toString("utf8");
+};
+
+
+module.exports = {
+    getRsaPublickey,
+    encryptWithRsaPublicKey,
+    decryptWithRsaPrivateKey,
+    getVaultSymmetricKeyBackupModel
+}
\ No newline at end of file
diff --git a/StationSoftware/utils/index.js b/StationSoftware/utils/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..28b90dff14e5be4040ef555b89cde615efec031d
--- /dev/null
+++ b/StationSoftware/utils/index.js
@@ -0,0 +1,11 @@
+const utility = require('./utility');
+const vault = require('./vault');
+const trainConfigUtil = require('./train-config');
+const cryptoUtil = require('./crypto');
+
+module.exports = {
+    utility,
+    vault,
+    trainConfigUtil,
+    cryptoUtil
+};
diff --git a/StationSoftware/utils/train-config.js b/StationSoftware/utils/train-config.js
new file mode 100644
index 0000000000000000000000000000000000000000..d6e27439c32aa0ab3673edb40e54df31cb2f4e70
--- /dev/null
+++ b/StationSoftware/utils/train-config.js
@@ -0,0 +1,117 @@
+const path = require('path');
+const tarStream = require("tar-stream");
+const cryptoUtil = require('./crypto');
+
+const train_config_constant = {
+    "rsa_public_key": "rsa_public_key",
+    "symmetric_key": "symmetric_key"
+}
+
+const getTrainConfigJsonBaseModel = () => {
+    const train_config = {};
+    return train_config
+}
+
+const getTrainConfigFileName = () => {
+    const trainConfigFileName = 'train_config.json';
+    // const trainConfigFileName = 'initTrain.py';
+    return trainConfigFileName;
+}
+
+const getTrainConfigFileAbsolutePath = () => {
+    const trainConfigFileAbsolutePath = '/';
+    return trainConfigFileAbsolutePath;
+}
+
+const getTrainConfigFilePathInContainer = () => {
+
+    const trainConfigFileName = getTrainConfigFileName();
+    const trainConfigFileAbsolutePath = getTrainConfigFileAbsolutePath();
+    const trainConfigFilePathInContainer = path.join(trainConfigFileAbsolutePath, trainConfigFileName);
+    return trainConfigFilePathInContainer;
+}
+
+const streamToString = (stream) => {
+    const chunks = [];
+    return new Promise((resolve, reject) => {
+        stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
+        stream.on('error', (err) => reject(err));
+        stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
+    })
+}
+
+const unTarTrainConfigJson = (getArchiveResult) => {
+
+    let train_config = {};
+
+    return new Promise((resolve, reject) => {
+
+        let extract = tarStream.extract();
+        extract.on('entry', async (header, stream, next) => {
+
+            try {
+
+                if (header.type == "file" && header.name == getTrainConfigFileName()) {
+                    train_config = await streamToString(stream);
+                    train_config = JSON.parse(train_config);
+                    resolve(train_config);
+                    return;
+                }
+                next();
+
+            } catch (error) {
+                reject(error);
+            }
+
+        });
+
+        extract.on('finish', () => {
+            resolve(train_config);
+        });
+
+        getArchiveResult.pipe(extract);
+
+    });
+}
+
+const tarTrainConfigJson = (train_config) => {
+
+    let pack = tarStream.pack();
+
+    pack.entry({ name: getTrainConfigFileName() }, JSON.stringify(train_config));
+    pack.finalize();
+    return pack;
+
+}
+
+const decryptSymmetricKey = (encryptedSymmetricKey) => {
+    const decryptedSymmetricKey = cryptoUtil.decryptWithRsaPrivateKey(encryptedSymmetricKey);
+    return decryptedSymmetricKey;
+}
+
+const updateTrainConfigJson = (train_config, symmetricKey, opts = {}) => {
+
+    // get dest repository public key for encryption
+    const destPublicKey = train_config[train_config_constant['rsa_public_key']];
+    // (re)encrypt symmetric key with the next repo public key
+    const encryptedSymmetricKey = cryptoUtil.encryptWithRsaPublicKey(symmetricKey, destPublicKey);
+    // update train_config
+    train_config[train_config_constant['symmetric_key']] = encryptedSymmetricKey;
+
+    return train_config;
+}
+
+
+module.exports = {
+    
+    getTrainConfigJsonBaseModel,
+    getTrainConfigFileName,
+    getTrainConfigFileAbsolutePath,
+    getTrainConfigFilePathInContainer,
+    unTarTrainConfigJson,
+    updateTrainConfigJson,
+    tarTrainConfigJson,
+    decryptSymmetricKey,
+    train_config_constant
+
+}
\ No newline at end of file
diff --git a/StationSoftware/utils/utility.js b/StationSoftware/utils/utility.js
new file mode 100644
index 0000000000000000000000000000000000000000..82308296f0c299a441db521cc25c0fab25ecf9f3
--- /dev/null
+++ b/StationSoftware/utils/utility.js
@@ -0,0 +1,17 @@
+const asyncHandler = fn => (req, res, next) =>
+  Promise
+    .resolve(fn(req, res, next))
+    .catch(next);
+
+// https://stackoverflow.com/questions/14446511/most-efficient-method-to-groupby-on-an-array-of-objects
+const groupBy = function (xs, key) {
+  return xs.reduce(function (rv, x) {
+    (rv[x[key]] = rv[x[key]] || []).push(x);
+    return rv;
+  }, {});
+};    
+
+module.exports = {
+    asyncHandler,
+    groupBy
+}
\ No newline at end of file
diff --git a/StationSoftware/utils/vault.js b/StationSoftware/utils/vault.js
new file mode 100644
index 0000000000000000000000000000000000000000..83069a467691e1aa2f4d04ecaa121c7033038458
--- /dev/null
+++ b/StationSoftware/utils/vault.js
@@ -0,0 +1,146 @@
+const { error } = require('console');
+const dns = require('dns');
+const { getAgentOptions } = require('../vault-certs-client');
+
+const options = {
+    apiVersion: process.env.VAULT_API_VERSION || 'v1',
+    endpoint: `https://${process.env.VAULT_HOST}:${process.env.VAULT_PORT}`,
+    token: process.env.VAULT_TOKEN || 'client_token',
+    requestOptions : {
+        agentOptions: getAgentOptions()
+    }
+};
+
+const vault = require("node-vault")(options);
+
+module.exports = {
+
+    command: vault,
+
+    isAuthenticated: async function () {
+        let isAuthenticated = false
+        try {
+            let lookupResult = await vault.tokenLookupSelf();
+            console.log(lookupResult);
+            if (lookupResult.data) {
+                isAuthenticated = true;
+            }
+        } catch (error) {
+            // console.log(JSON.stringify(error));
+        }
+        return isAuthenticated;
+    },
+
+    setToken: (token) => {
+        process.env.VAULT_TOKEN = token;
+        vault.token = token;
+    },
+
+    getVaultApiEndpoint: async () => {
+
+        return new Promise((resolve, reject) => {
+            let vaultApiEndpoint = `${vault.endpoint}/${vault.apiVersion}`;
+            dns.lookup(process.env.VAULT_HOST, (err, address) => {
+                if (err)
+                    resolve(vaultApiEndpoint);
+                vaultApiEndpoint = vaultApiEndpoint.replace(process.env.VAULT_HOST, address);
+                resolve(vaultApiEndpoint);
+            });
+        });
+    },
+
+    getKeyValueEngines: async function (version) {
+
+        let result = await this.read("sys/internal/ui/mounts");
+        let kv_engines = [];
+        if (!result.isError) {
+            let data = result.data;
+            let engines = Object.keys(data.secret);
+            kv_engines = engines.filter(x => data.secret[x].type == "kv");
+            if (version)
+                kv_engines = kv_engines.filter(x => data.secret[x].options.version == version)
+        }
+        return kv_engines;
+    },
+
+    //Recursively iterates through all sub-paths
+    traverse: function (paths, callback) {
+
+        let pathIndex = paths.findIndex(x => x.endsWith("/"));
+        if (pathIndex < 0) {
+            // There is no more sub-path - call callback function and return
+            if (callback)
+                callback(paths);
+            return paths;
+        }
+
+        let path = paths[pathIndex];
+        paths.splice(pathIndex, 1);
+
+        this.list(path).then(result => {
+            if (!result.isError) {
+                let keys = result.data.keys;
+                paths = paths.concat(keys.map(key => path + key));
+            }
+            this.traverse(paths, callback);
+        });
+    },
+
+    getTrainToken: function (config = {}) {
+
+        let payload = {};
+
+        // payload["policies"] = ["train"];
+        // payload["no_default_policy"] = true;
+        payload["policies"] = ["default"];
+        payload["num_uses"] = config.num_uses || 1;
+
+        // return this.write("auth/token/create/train", payload);
+        return this.write("auth/token/create", payload);
+    },
+
+    // GET
+    read: async (path, requestOptions) => {
+        try {
+            let result = await vault.read(path, requestOptions);
+            return result;
+        } catch (error) {
+            console.log(JSON.stringify(error));
+            return { "isError": true, "data": error };
+        }
+    },
+
+    // POST
+    write: async (path, data, requestOptions) => {
+        try {
+            let result = await vault.write(path, data, requestOptions);
+            return result || { "isError": false };
+        } catch (error) {
+            console.log(JSON.stringify(error));
+            return { "isError": true, "data": error };
+        }
+    },
+
+    // LIST
+    list: async (path, requestOptions) => {
+        try {
+            let result = await vault.list(path, requestOptions);
+            return result;
+        } catch (error) {
+            console.log(JSON.stringify(error));
+            return { "isError": true, "data": error };
+        }
+    },
+
+    // DELETE
+    delete: async (path, requestOptions) => {
+        try {
+            let result = await vault.delete(path, requestOptions);
+            return result || { "isError": false };
+        } catch (error) {
+            console.log(JSON.stringify(error));
+            return { "isError": true, "data": error };
+        }
+    }
+
+};
\ No newline at end of file
diff --git a/StationSoftware/validation/auth.js b/StationSoftware/validation/auth.js
new file mode 100644
index 0000000000000000000000000000000000000000..cd286cc758ff3d6fe94f9d3716b9f619ef8b3e00
--- /dev/null
+++ b/StationSoftware/validation/auth.js
@@ -0,0 +1,60 @@
+const fs = require('fs')
+const certDirectory = '/usr/src/app/dind-certs-client/certs';
+const certs = {
+  ca: fs.readFileSync(`${certDirectory}/ca.pem`),
+  key: fs.readFileSync(`${certDirectory}/key.pem`),
+  cert: fs.readFileSync(`${certDirectory}/cert.pem`),
+};
+module.exports = {
+  ensureAuthenticated: function (req, res, next) {
+    if (req.isAuthenticated()) {
+      return next();
+    }
+    req.flash('error_msg', 'Please log in to view that resource');
+    res.redirect('/auth/login');
+  },
+
+  forwardAuthenticated: function (req, res, next) {
+    if (!req.isAuthenticated()) {
+      return next();
+    }
+    res.redirect('/dashboard');
+  },
+
+  keyloakAuthenticated: function (req, res, next) {
+    if (!req.harbor) {
+      req.harbor = { auth: {} };
+    }
+    let authServer = new URL('/auth/realms/pht/protocol/openid-connect/token', 'https://' + process.env.AUTH_SERVER_ADDRESS);
+    authServer.port = process.env.AUTH_SERVER_PORT;
+    var options = {
+      'url': authServer.toString(),
+      'headers': {
+        'Content-Type': 'application/json',
+      },
+      form: {
+        grant_type: "password",
+        client_id: "central-service",
+        username: process.env.HARBOR_USER,
+        password: process.env.HARBOR_PASSWORD,
+        scope: "openid profile email offline_access",
+      }
+    };
+    request.post(options, (error, response) => {
+      if (error) {
+        return res.status(400).send(error)
+      }
+      let body = JSON.parse(response.body);
+      req.harbor.auth.admin_access_token = body.access_token;
+      return next();
+    });
+  },
+
+  getAgentOptions: () => {
+    return {
+      ca: certs.ca,
+      cert: certs.cert,
+      key: certs.key,
+    }
+  }
+};
\ No newline at end of file
diff --git a/StationSoftware/validation/passport.js b/StationSoftware/validation/passport.js
new file mode 100644
index 0000000000000000000000000000000000000000..853d8bdb570a19083be570e7814bda547cc3eb86
--- /dev/null
+++ b/StationSoftware/validation/passport.js
@@ -0,0 +1,64 @@
+const LocalStrategy = require('passport-local').Strategy;
+const JwtStrategy = require('passport-jwt').Strategy;
+const ExtractJwt = require('passport-jwt').ExtractJwt;
+const bcrypt = require('bcryptjs');
+
+// Load User model
+const User = require('../models/User');
+
+module.exports = function (passport) {
+  passport.use(
+    new LocalStrategy({ usernameField: 'email' }, (email, password, done) => {
+      // Match user
+      User.findOne({
+        email: email
+      }).then(user => {
+        if (!user) {
+          return done(null, false, { message: 'That email is not registered' });
+        }
+        // Match password
+        bcrypt.compare(password, user.password, (err, isMatch) => {
+          if (err) throw err;
+          if (isMatch) {
+            return done(null, user);
+          } else {
+            return done(null, false, { message: 'Password incorrect' });
+          }
+        });
+      });
+    })
+  );
+
+  //JWT Passport options
+  var opts = {
+    jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
+    secretOrKey: process.env.JWT_SECRET,
+  };
+  passport.use(
+    new JwtStrategy(opts, (payload, done) => {
+      User.findOne({
+        email: payload.email
+      }).then(user => {
+        if (!user) {
+          return done(null, false, { message: 'That email is not registered' });
+        }
+        // Match password
+        if (payload.password === user.password) {
+          return done(null, user);
+        } else {
+          return done(null, false, { message: 'Password incorrect' });
+        }
+      });
+    })
+  );
+
+  passport.serializeUser(function (user, done) {
+    done(null, user.id);
+  });
+
+  passport.deserializeUser(function (id, done) {
+    User.findById(id, function (err, user) {
+      done(err, user);
+    });
+  });
+};
\ No newline at end of file
diff --git a/StationSoftware/vault-certs-client/index.js b/StationSoftware/vault-certs-client/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..6f38cdaad61c263f2b52db0621167953c649a5f2
--- /dev/null
+++ b/StationSoftware/vault-certs-client/index.js
@@ -0,0 +1,17 @@
+const fs = require('fs')
+const certDirectory = '/usr/src/app/vault-certs-client/certs';
+const certs = {
+    ca: fs.readFileSync(`${certDirectory}/ca.pem`),
+    key: fs.readFileSync(`${certDirectory}/key.pem`),
+    cert: fs.readFileSync(`${certDirectory}/cert.pem`),
+};
+
+module.exports = {
+    getAgentOptions: () => {
+        return {
+            ca: certs.ca,
+            cert: certs.cert,
+            key: certs.key,
+        }
+    }
+};
\ No newline at end of file
diff --git a/StationSoftware/views/dashboard-containers.ejs b/StationSoftware/views/dashboard-containers.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..b7317742ca696c0d06d64408b056491c371fc8b3
--- /dev/null
+++ b/StationSoftware/views/dashboard-containers.ejs
@@ -0,0 +1,250 @@
+<!-- Nav -->
+<%- include('partials/nav', {active: "containers"}); %>
+
+<div class="container-fluid">
+    <div class="row d-flex d-md-block flex-nowrap wrapper">
+        <main class="col-12 float-left col px-5 px-md-3 py-3 main">
+            <h1 class="display-3 hidden-xs-down">
+                Dashboard
+            </h1>
+            <% include ./partials/messages %>
+            <div class="my-3 p-3 bg-white rounded shadow-sm">
+                <h6 class="border-bottom border-gray pb-2 mb-0">Containers</h6>
+                <div class="table-responsive">
+                    <table class="table table-striped table-hover">
+                        <thead class="thead-dark">
+                            <tr>
+                                <th scope="col">#</th>
+                                <th scope="col">Id</th>
+                                <th scope="col">Train ID</th>
+                                <th scope="col">Name</th>
+                                <th scope="col">Image</th>
+                                <th scope="col">State</th>
+                                <th scope="col">Status</th>
+                                <th scope="col" class="w-operations-btn">Operations</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            <% for(var i=0; i < containers.length; i++) { %>
+                            <tr>
+                                <th scope="row"><%= i+1 %></th>
+                                <td><%= containers[i].Id.substring(0, 13) %></td>
+                                <td class="break-word w-40vw font-weight-bold"><%= containers[i].TrainClassId %></td>
+                                <td class="break-word w-15vw"><%= containers[i].Name %></td>
+                                <td class="break-word w-20vw"><%= containers[i].Image %></td>
+                                <td><%= containers[i].State %></td>
+                                <td><%= containers[i].Status %></td>
+                                <td>
+                                    <% if(containers[i].State === "exited"){ %>
+                                        <a href="<%='/docker/container/commit?container=' + containers[i].Id.substring(0, 13) + '&repo=' + containers[i].Repo + '&tag=' +containers[i].NextTag + '&image=' + containers[i].Image + '&jobId=' + containers[i].JobId %>"
+                                        class="btn btn-primary my-2 my-sm-0 btn-block" >Commit</a>
+                                    <a href="<%='/docker/container/remove?container=' + containers[i].Id.substring(0, 13) + '&jobId=' + containers[i].JobId %>"
+                                        class="btn btn-danger my-2 my-sm-0 btn-block">Remove</a>
+                                    <button onclick="showLogsModal('<%= containers[i].Id.substring(0, 13) %>')" 
+                                        class="btn btn-secondary my-2 my-sm-0 btn-block">Logs</button>
+                                    <button onclick="showContainerChangesModal('<%= containers[i].Id.substring(0, 13) %>', '<%= containers[i].JobId %>')" 
+                                        class="btn btn-info my-2 my-sm-0 btn-block">Changes</button>
+                                    <% } else if (containers[i].State === "running"){ %>
+                                    <% } else{ %> 
+                                    <a href="<%='/docker/container/start?container=' + containers[i].Id.substring(0, 13) + '&jobId=' + containers[i].Name%>"
+                                        class="btn btn-primary my-2 my-sm-0 btn-block">Start</a>
+                                        <a href="<%='/docker/container/remove?container=' + containers[i].Id.substring(0, 13) + '&jobId=' + containers[i].JobId %>"
+                                        class="btn btn-danger my-2 my-sm-0 btn-block">Remove</a>
+                                    <% } %>
+                                </td>
+                            </tr>
+                            <% } %>
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+
+        </main>
+    </div>
+</div>
+
+<div class="modal fade" id="logsModal" tabindex="-1" role="dialog" aria-labelledby="logsModalLabel" aria-hidden="true"
+    data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title" id="logsModalLabel">Container Logs</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <pre id="logsContent" style="max-height:60vh;">
+                    <!--LOGS-->
+                </pre>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
+            </div>
+        </div>
+    </div>
+</div>
+
+<div class="modal fade" id="containerChangesModal" tabindex="-1" role="dialog" aria-labelledby="containerChangesModalLabel" aria-hidden="true"
+    data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title" id="containerChangesModalLabel">Container Changes</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-header border-bottom-0">
+                <div class="row">
+                    <div class="col">
+                        <kbd class="bg-warning">Modified</kbd>
+                    </div>
+                    <div class="col">
+                        <kbd class="bg-success">Added</kbd>
+                    </div>
+                    <div class="col">
+                        <kbd class="bg-danger">Deleted</kbd>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-body" style="max-height:60vh; overflow-y: auto;">
+
+                <nav>
+                    <div class="nav nav-tabs" id="nav-tab" role="tablist">
+                      <a class="nav-item nav-link active" id="nav-list-tab" data-toggle="tab" href="#nav-change-list" role="tab" aria-controls="nav-change-list" aria-selected="true">List</a>
+                      <a class="nav-item nav-link" id="nav-compare-tab" data-toggle="tab" href="#nav-change-compare" role="tab" aria-controls="nav-change-compare" aria-selected="false">Compare</a>
+                    </div>
+                </nav>
+                <div class="tab-content" id="nav-tabContent">
+                    <div class="tab-pane fade show active" id="nav-change-list" role="tabpanel" aria-labelledby="nav-change-list-tab">
+                        <div id="treeView" class="container tree-view-container mt-2"></div>
+                    </div>
+                    <div class="tab-pane fade" id="nav-change-compare" role="tabpanel" aria-labelledby="nav-change-compare-tab">
+                        <div id="compareView" class="container mt-2">
+                            <h2 id="compareViewLoading">Analyzing <i class="fas fa-spinner fa-pulse"></i></h2>
+                            <h1 id="compareViewFail" class="d-none">Something went wrong <i class="fas fa-times"></i></h1>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script>
+    function showLogsModal(containerId) {
+        $.get(`/docker/container/logs?container=${containerId}`, function (data) {
+            try {
+                data = JSON.parse(data);
+            } catch (e) {
+                alert("error");
+                location.reload();
+                return;
+            }
+            $('#logsModal').find('#logsContent').html(data);
+            $('#logsModal').modal('show')
+        });
+    }
+
+    function showContainerChangesModal(containerId, jobId) {
+
+        // TREE VIEW TAB
+        $.get(`/docker/container/changes?container=${containerId}`, function (data) {
+
+            try {
+                data = JSON.parse(data);
+            } catch (e) {
+                alert("error");
+                location.reload();
+                return;
+            }
+
+            let rootUl = document.createElement('ul');
+
+            if (data && data.length)
+                rootUl.appendChild(traversePathTree(data[0]));
+            console.log(rootUl);
+
+            $('#containerChangesModal').find('#treeView').html(rootUl);
+            $('#containerChangesModal').modal('show')
+        }).fail(function (error) {
+            console.log(error);
+            alert("An error occurred while loading the changes.");
+        });
+
+        // COMPARE TAB
+        $.get(`/docker/container/compare?container=${containerId}&jobId=${jobId}`, function (diffHtml) {
+
+            $('#containerChangesModal').find('#compareView').html(diffHtml);
+
+        }).fail(function (error) {
+            console.log(error);
+            alert("An error occurred in the comparison.");
+
+            $('#containerChangesModal').find('#compareViewLoading').addClass("d-none");
+            $('#containerChangesModal').find('#compareViewFail').removeClass("d-none");
+        });
+
+        $("#containerChangesModal").on("hidden.bs.modal", function(){
+            $('#containerChangesModal').find('#treeView').html("");
+            $('#containerChangesModal').find('#compareView').html(`<h2 id="compareViewLoading">Analyzing <i class="fas fa-spinner fa-pulse"></i></h2><h1 id="compareViewFail" class="d-none">Something went wrong <i class="fas fa-times"></i></h1>`);
+
+        });
+    }
+
+    function traversePathTree(node) {
+        //process current node here
+        let li = document.createElement('li');
+        let a = document.createElement('a');
+        a.href = `/docker/container/archive?container=${node.containerId}&path=${node.path}`;
+        // a.download = `${node.containerId}_${node.path.replaceAll("/","_")}.tar`
+        a.append(node.name);
+
+        //visit children of current
+        if (node.children.length) {
+
+            let span = document.createElement('span');
+            span.innerHTML = `<i class="fas fa-angle-down"></i> <i class="fas fa-folder text-${getKindColor(node.kindCode)}"></i> `;
+            span.append(a);
+            li.appendChild(span);
+
+            let ul = document.createElement('ul');
+            for (var index in node.children) {
+                let childItem = traversePathTree(node.children[index]);
+                ul.appendChild(childItem);
+            }
+
+            li.appendChild(ul);
+        }
+        else {
+            let span = document.createElement('span');
+            span.innerHTML = `<i class="fas fa-file text-${getKindColor(node.kindCode)}"></i> `;
+            span.append(a);
+            li.appendChild(span);
+
+        }
+
+        return li;
+    }
+
+    function getKindColor(kind) {
+        switch (kind) {
+            //Modified
+            case 0:
+                return 'warning'
+                break;
+            //Added
+            case 1:
+                return 'success'
+                break;
+            //Deleted
+            case 2:
+                return 'danger'
+                break;
+        }
+    }
+</script>
\ No newline at end of file
diff --git a/StationSoftware/views/dashboard-create-container.ejs b/StationSoftware/views/dashboard-create-container.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..1981bb5e84a3b3622a477bbdae9c9ca967ce417e
--- /dev/null
+++ b/StationSoftware/views/dashboard-create-container.ejs
@@ -0,0 +1,91 @@
+<!-- Nav -->
+<%- include('partials/nav', {active: "images"}); %>
+
+<div class="container-fluid">
+    <div class="row d-flex d-md-block flex-nowrap wrapper">
+        <main class="col-md-10 float-left col px-5 px-md-3 py-3 main">
+            <h1 class="display-3 hidden-xs-down">
+                Create a container
+            </h1>
+            <% include ./partials/messages %>
+            <form action="/docker/container/create" method="POST" class="was-validated">
+
+                <div class="form-group">
+                    <label for="image" class="col-form-label"><%=image%></label>
+                    <input type="hidden" id="image" name="image" value="<%=image%>">
+                </div>
+                <div class="form-group">
+                    <label for="jobId" class="col-form-label"><%=jobId%></label>
+                    <input type="hidden" id="jobId" name="jobId" value="<%=jobId%>">
+                </div>
+                <div class="form-group">
+                    <% for (var i in envVariableList){%>
+                        <div class="form-row mb-2">
+                            <div class="col">
+                                <% if (i==0) { %>
+                                    <label for="envName<%=i%>">Environment Variable</label>
+                                    <% } %>
+                                        <input id="envName<%=i%>" type="text" class="form-control" placeholder="Name"
+                                            name="envs[<%=i%>][name]" value="<%=envVariableList[i].name%>" <%-envVariableList[i].name.length > 0 ? 'readonly tabindex="-1"' : ''%> />
+                            </div>
+
+                            <div class="col">
+                                <% if (i==0) { %>
+                                <label for="envType<%=i%>">Type</label>
+                                <% } %>
+                                <select id="envType<%=i%>" class="form-control" name="envs[<%=i%>][type]" onchange="typeToggleHandler(this)" data-row-index="<%=i%>">
+                                    <option value="" disabled>Select a type</option>
+                                    <option value="manual" selected>Manual</option>
+                                    <option value="vault">Vault</option>
+                                </select>
+                            </div>
+
+                            <div class="col">
+                                <% if (i==0) { %>
+                                    <label>Value</label>
+                                <% } %>
+                                    <input id="envValue<%=i%>" type="<%=envVariableList[i].type%>" class="form-control" placeholder="Value"
+                                        name="envs[<%=i%>][value]" <%-envVariableList[i].required ? 'required pattern="\\S(.*\\S)?"' : '' %> />
+                        
+                                    <!-- At least one non-whitespace character and no whitespace at the beginning or end of the input pattern="\S(.*\S)?" -->
+                                    <!-- At least one non-whitespace character, but also allows whitespace characters (spaces, tabs, carriage returns, etc.) at the beginning or end pattern=".*\S+.*" -->
+                        
+                                    <select id="envPath<%=i%>" class="form-control" style="display: none;" name="envs[<%=i%>][path]" disabled
+                                        <%-envVariableList[i].required ? 'required' : '' %>>
+                                        <option value="" disabled selected>Select a path</option>
+                                        <% for (var index in paths){%>
+                                            <option value="<%=JSON.stringify(paths[index])%>">
+                                                <%=`${paths[index].path}:${paths[index].key}`%>
+                                            </option>
+                                        <%} %>
+                                    </select>
+                            
+                            </div>
+                        </div>
+                        <%} %>
+                </div>
+                <button type="submit" class="btn btn-primary">Create</button>
+                <a class="btn btn-secondary" href="/dashboard/images">Back</a>
+            </form>
+
+        </main>
+    </div>
+</div>
+
+<script>
+    function typeToggleHandler(element) {
+        console.log(element)
+        if (element.value == 'manual') {
+            document.getElementById(`envPath${element.dataset.rowIndex}`).style.display = 'none';
+            document.getElementById(`envPath${element.dataset.rowIndex}`).disabled = true;
+            document.getElementById(`envValue${element.dataset.rowIndex}`).style.display = 'block';
+            document.getElementById(`envValue${element.dataset.rowIndex}`).disabled = false;
+        }
+        else {
+            document.getElementById(`envPath${element.dataset.rowIndex}`).style.display = 'block';
+            document.getElementById(`envPath${element.dataset.rowIndex}`).disabled = false;
+            document.getElementById(`envValue${element.dataset.rowIndex}`).style.display = 'none';
+            document.getElementById(`envValue${element.dataset.rowIndex}`).disabled = true;
+        }
+    }
+</script>
diff --git a/StationSoftware/views/dashboard-images.ejs b/StationSoftware/views/dashboard-images.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..97feac303dc7be56431f260079ab53aec41335cb
--- /dev/null
+++ b/StationSoftware/views/dashboard-images.ejs
@@ -0,0 +1,87 @@
+<!-- Nav -->
+<%- include('partials/nav', {active: "images"}); %>
+
+<div class="container-fluid">
+    <div class="row d-flex d-md-block flex-nowrap wrapper">
+        <main class="col-12 float-left col px-5 px-md-3 py-3 main">
+            <h1 class="display-3 hidden-xs-down">
+                Dashboard
+            </h1>
+            <% include ./partials/messages %>
+            <div class="my-3 p-3 bg-white rounded shadow-sm">
+                <h6 class="border-bottom border-gray pb-2 mb-0">Pulled Images</h6>
+                <div class="table-responsive">
+                    <table class="table table-striped table-hover">
+                        <thead class="thead-dark">
+                            <tr>
+                                <th scope="col">#</th>
+                                <th scope="col">Id</th>
+                                <th scope="col">Train ID</th>
+                                <th scope="col">Image Tag</th>
+                                <th scope="col" class="w-operations-btn">Operations</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            <% for(var i=0; i < pull_images.length; i++) { %>
+                            <tr>
+                                <th scope="row"><%= i+1 %></th>
+                                <td class="break-word w-20vw"><%= pull_images[i].Id %></td>
+                                <td class="break-word w-50vw font-weight-bold"><%= pull_images[i].TrainClassId %></td>
+                                <td class="break-word w-20vw"><%= pull_images[i].RepoTag %>
+                                <% if(pull_images[i].Labels.includes('"decrypted":"true"')) { %>  
+                                    <strong>Decrypted</strong>
+                                <% } %>
+                                </td>
+                                <td>
+                                    <a href="<%='/docker/image/decrypt?image=' + pull_images[i].RepoTag + '&jobId=' + pull_images[i].JobId %>"
+                                        class="btn btn-warning my-2 my-sm-0 btn-block">Decrypt</a>
+                                    <a href="<%='/dashboard/create/container?image=' + pull_images[i].RepoTag + '&jobId=' + pull_images[i].JobId + '&labels=' + pull_images[i].Labels %>"
+                                        class="btn btn-primary my-2 my-sm-0 btn-block">Create
+                                        Container</a>
+                                    <a href="<%='/docker/image/remove?image=' + pull_images[i].RepoTag + '&jobId=' + pull_images[i].JobId %>"
+                                        class="btn btn-danger my-2 my-sm-0 btn-block">Delete
+                                        Image</a>
+                                </td>
+                            </tr>
+                            <% } %>
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+
+            <div class="my-3 p-3 bg-white rounded shadow-sm">
+                <h6 class="border-bottom border-gray pb-2 mb-0">Images waiting to push</h6>
+                <div class="table-responsive">
+                    <table class="table table-striped table-hover">
+                        <thead class="thead-dark">
+                            <tr>
+                                <th scope="col">#</th>
+                                <th scope="col">Id</th>
+                                <th scope="col">Train ID</th>
+                                <th scope="col">Image Tag</th>
+                                <th scope="col" class="w-operations-btn">Operations</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            <% for(var i=0; i < push_images.length; i++) { %>
+                            <tr>
+                                <th scope="row"><%= i+1 %></th>
+                                <td class="break-word w-20vw"><%= push_images[i].Id %></td>
+                                <td class="break-word w-50vw font-weight-bold"><%= push_images[i].TrainClassId %></td>
+                                <td class="break-word w-20vw"><%= push_images[i].RepoTag %></td>
+                                <td>
+                                    <a href="<%='/docker/image/push?image='+push_images[i].RepoTag%>"
+                                        class="btn btn-primary my-2 my-sm-0 btn-block">Push Image</a>
+                                    <a href="<%='/docker/image/remove?image='+push_images[i].RepoTag + '&jobId=' + push_images[i].JobId %>"
+                                        class="btn btn-danger my-2 my-sm-0 btn-block">Delete
+                                        Image</a>
+                                </td>
+                            </tr>
+                            <% } %>
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+        </main>
+    </div>
+</div>
\ No newline at end of file
diff --git a/StationSoftware/views/dashboard-notifications.ejs b/StationSoftware/views/dashboard-notifications.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..979b2872fdb3ddbf5277d01abc451fe90d138d3e
--- /dev/null
+++ b/StationSoftware/views/dashboard-notifications.ejs
@@ -0,0 +1,13 @@
+<!-- Nav -->
+<%- include('partials/nav', {active: "notifications"}); %>
+
+<div class="container-fluid">
+    <div class="row d-flex d-md-block flex-nowrap wrapper">
+        <main class="col-md-10 float-left col px-5 px-md-3 py-3 main">
+            <h1 class="display-3 hidden-xs-down">
+                Dashboard
+            </h1>
+
+        </main>
+    </div>
+</div>
\ No newline at end of file
diff --git a/StationSoftware/views/dashboard-reject.ejs b/StationSoftware/views/dashboard-reject.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..51c5b50b9b52d6a93c08971468d98f047f0f9031
--- /dev/null
+++ b/StationSoftware/views/dashboard-reject.ejs
@@ -0,0 +1,38 @@
+<!-- Nav -->
+<%- include('partials/nav', {active: "dashboard"}); %>
+
+<div class="container-fluid">
+    <div class="row d-flex d-md-block flex-nowrap wrapper">
+        <main class="col-md-10 float-left col px-5 px-md-3 py-3 main">
+            <h1 class="display-3 hidden-xs-down">
+                Dashboard
+            </h1>
+            <% include ./partials/messages %>
+            <form action="/docker/train/reject" method="POST">
+
+                <div class="form-group">
+                    <label for="jobid" class="col-form-label"><%=jobid%></label>
+                    <input type="hidden" id="jobid" name="jobid" value="<%=jobid%>">
+                </div>
+
+                <div class="form-group">
+                    <label for="reason" class="col-form-label">Reason:</label>
+                    <select name="reason" class="custom-select">
+                        <option value="0" hidden>Display but don't show in list</option>
+                        <option value="1">No anonymity</option>
+                        <option value="2">No access right</option>
+                        <option value="3">Other reason</option>
+                    </select>
+                </div>
+
+                <div class="form-group">
+                    <label for="comment">Other reasons:</label>
+                    <textarea class="form-control" rows="5" name="comment" id="comment"></textarea>
+                </div>
+                <button type="submit" class="btn btn-primary">Reject</button>
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
+            </form>
+
+        </main>
+    </div>
+</div>
\ No newline at end of file
diff --git a/StationSoftware/views/dashboard.ejs b/StationSoftware/views/dashboard.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..4f9d33dec4f253284105d2c5d9f9af64ef3ba1a1
--- /dev/null
+++ b/StationSoftware/views/dashboard.ejs
@@ -0,0 +1,158 @@
+<!-- Nav -->
+<%- include('partials/nav', {active: "dashboard"}); %>
+
+<div class="container-fluid">
+    <div class="row d-flex d-md-block flex-nowrap wrapper">
+        <main class="col-12 float-left col px-5 px-md-3 py-3 main">
+            <h1 class="display-3 hidden-xs-down">
+                Dashboard
+            </h1>
+            <% include ./partials/messages %>
+            <div class="my-3 p-3 bg-white rounded shadow-sm">
+                <h6 class="border-bottom border-gray pb-2 mb-0">Recent Train Request</h6>
+                <div class="table-responsive">
+                    <table class="table table-striped table-hover">
+                        <thead class="thead-dark">
+                            <tr>
+                                <th scope="col">#</th>
+                                <th scope="col">Train ID</th>
+                                <th scope="col">Location</th>
+                                <th scope="col">Current</th>
+                                <th scope="col">Next</th>
+                                <th scope="col" class="w-operations-btn">Operation</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            <% for(var i=0; i < trains.length; i++) { %>
+                            <tr id="train-<%= trains[i].jobid %>">
+                                <th scope="row"><%= i+1 %></th>
+                                <td class="break-word w-50vw font-weight-bold"><%= trains[i].trainclassid %></td>
+                                <td class="break-word w-30vw"><%= trains[i].trainstoragelocation %></td>
+                                <td><%= trains[i].currentstation %></td>
+                                <td><%= trains[i].nextstation %></td>
+                                <td id="buttons-td-<%= trains[i].jobid %>">
+                                    <a href="<%='/dashboard/reject?jobid='+trains[i].jobid%>" class="btn btn-danger float-right my-2 my-sm-0 btn-block">Rejection</a>
+                                    
+                                    <button onclick="pullTrain('<%= JSON.stringify(trains[i]) %>')" 
+                                        class="btn btn-primary float-right my-2 my-sm-0 btn-block">Pull Image</button>    
+                                </td>
+                                <td id="pulling-status-td-<%= trains[i].jobid %>" class="d-none">
+
+                                    <div class="fa-3x text-center">
+                                        <i class="fas fa-spinner fa-pulse" id="pulse-icon-<%= trains[i].jobid %>"></i>
+                                        <i class="fas fa-check d-none" style="color:green" id="check-icon-<%= trains[i].jobid %>"></i>
+                                    </div>
+
+                                </td>
+                            </tr>
+                            <tr id="pulling-logs-tr-<%= trains[i].jobid %>" class="d-none">
+                                <td colspan="6">
+                                    <pre id="pulling-logs-pre-<%= trains[i].jobid %>" style="font-size: 0.85vw; max-height: 100px;overflow-y: auto;"></pre>
+                                </td>
+                            </tr>
+                            <% } %>
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+
+        </main>
+    </div>
+</div>
+
+<script>
+
+    function modeToggle(mode, train) {
+
+        //0 init 1 pulling 2 complete
+
+        if (mode === 0) {//init
+
+            //hide logs
+            const trElm = document.getElementById(`pulling-logs-tr-${train.jobid}`);
+            trElm.classList.add("d-none");
+            //unhide buttons
+            const buttonsTdElm = document.getElementById(`buttons-td-${train.jobid}`);
+            buttonsTdElm.classList.remove("d-none");
+            //hide status icon
+            const statusTdElm = document.getElementById(`pulling-status-td-${train.jobid}`);
+            statusTdElm.classList.add("d-none");
+        }
+
+        else if (mode === 1) {//pulling
+
+            //unhide logs
+            const trElm = document.getElementById(`pulling-logs-tr-${train.jobid}`);
+            trElm.classList.remove("d-none");
+            //hide buttons
+            const buttonsTdElm = document.getElementById(`buttons-td-${train.jobid}`);
+            buttonsTdElm.classList.add("d-none");
+            //unhide status
+            const statusTdElm = document.getElementById(`pulling-status-td-${train.jobid}`);
+            statusTdElm.classList.remove("d-none");
+
+        }
+        else if (mode === 2) {//complete
+
+            //show check icon
+            const pulseIconElm = document.getElementById(`pulse-icon-${train.jobid}`);
+            pulseIconElm.classList.add("d-none");
+            const checkIconElm = document.getElementById(`check-icon-${train.jobid}`);
+            checkIconElm.classList.remove("d-none");
+            //flash train row
+            const trElm = document.getElementById(`train-${train.jobid}`);
+            trElm.classList.add("success_flash");
+        }
+
+    }
+
+    function pullTrain(train) {
+
+        train = JSON.parse(train);
+        console.log(train);
+
+        //pulling
+        modeToggle(1, train)
+
+        const url = `/docker/image/pull?jobid=${train.jobid}&trainstoragelocation=${train.trainstoragelocation}&trainclassid=${train.trainclassid}&currentstation=${train.currentstation}&nextstation=${train.nextstation}`;
+        const regex = /\\u([\d\w]{4})/gi;
+
+        var xhr = new XMLHttpRequest()
+        xhr.open("GET", url, true)
+
+        xhr.onerror = function (e) {
+            console.log(e);
+            alert("Error");
+        }
+        xhr.onprogress = function (e) {
+            console.log(e);
+            const element = document.getElementById(`pulling-logs-pre-${train.jobid}`);
+            const logs = xhr.responseText.replace(regex, function (match, grp) {
+                return String.fromCharCode(parseInt(grp, 16));
+            });
+            element.innerText += logs;
+            element.scrollTop = element.scrollHeight;
+            console.log(logs);
+        }
+        xhr.onloadend = function (e) {
+            console.log(e);
+
+            if (e.target.status === 200) {
+
+                //complete
+                modeToggle(2, train);
+                window.location.replace("/dashboard/images");
+            }
+            else {
+
+                if (e.target.statusText)
+                    alert(e.target.statusText);
+
+                //init    
+                modeToggle(0, train)
+            }
+        }
+        xhr.send();
+    }
+
+</script>
\ No newline at end of file
diff --git a/StationSoftware/views/error.ejs b/StationSoftware/views/error.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..5fc56b8eb9e6cd95ba934c35977a9aad8e3eb393
--- /dev/null
+++ b/StationSoftware/views/error.ejs
@@ -0,0 +1,13 @@
+<!-- Nav -->
+<%- include('partials/nav', {active: ""}); %>
+
+<div class="container-fluid">
+    <div class="row d-flex d-md-block flex-nowrap wrapper">
+        <main class="col-12 float-left col px-5 px-md-3 py-3 main">
+            <h1 class="display-3 hidden-xs-down">
+                Error
+            </h1>
+            <% include ./partials/messages %>
+        </main>
+    </div>
+</div>
\ No newline at end of file
diff --git a/StationSoftware/views/layout.ejs b/StationSoftware/views/layout.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..b6eaaa683b927375fc0fbf2e03d0176364e87b22
--- /dev/null
+++ b/StationSoftware/views/layout.ejs
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <meta http-equiv="X-UA-Compatible" content="ie=edge" />
+  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.3/css/all.css"
+    integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/" crossorigin="anonymous">
+
+  <!-- Bootstrap CSS -->
+  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
+    integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
+
+  <link rel='stylesheet' href='/stylesheets/style.css' />
+
+  <!-- diff2html Stylesheet -->
+  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/styles/github.min.css" />
+  <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css" />
+
+  <!-- diff2html Javascripts -->
+  <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html-ui.min.js"></script>
+
+  <title>PHT Station Software</title>
+</head>
+
+<body>
+  <%- body %>
+
+  <!-- Optional JavaScript -->
+  <!-- jQuery first, then Popper.js, then Bootstrap JS -->
+  <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
+  <!-- <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
+    integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n"
+    crossorigin="anonymous"></script> -->
+  <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
+    integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
+    crossorigin="anonymous"></script>
+  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
+    integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
+    crossorigin="anonymous"></script>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/StationSoftware/views/login.ejs b/StationSoftware/views/login.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..6df909320f43c42ccbe81fb32cbbc1d686d21905
--- /dev/null
+++ b/StationSoftware/views/login.ejs
@@ -0,0 +1,34 @@
+<div class="row mt-5">
+  <div class="col-md-6 m-auto">
+    <div class="card card-body">
+      <h1 class="text-center mb-3"><i class="fas fa-sign-in-alt"></i>Login</h1>
+      <% include ./partials/messages %>
+      <form action="/auth/login" method="POST">
+        <div class="form-group">
+          <label for="email">Email</label>
+          <input
+            type="email"
+            id="email"
+            name="email"
+            class="form-control"
+            placeholder="Enter Email"
+          />
+        </div>
+        <div class="form-group">
+          <label for="password">Password</label>
+          <input
+            type="password"
+            id="password"
+            name="password"
+            class="form-control"
+            placeholder="Enter Password"
+          />
+        </div>
+        <button type="submit" class="btn btn-primary btn-block">Login</button>
+      </form>
+      <p class="lead mt-4">
+        No Account? <a href="/auth/register">Register</a>
+      </p>
+    </div>
+  </div>
+</div>
diff --git a/StationSoftware/views/metadata-list.ejs b/StationSoftware/views/metadata-list.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..df8fc21992234b563d2a045d6cd8410802efa4ee
--- /dev/null
+++ b/StationSoftware/views/metadata-list.ejs
@@ -0,0 +1,66 @@
+<%- include('partials/nav', {active: "metadata"}); %>
+<script>
+    function addToList(uri) {
+        if (String(document.getElementById.value).substr(String(document.getElementById('list').value).length -1) != '\n') {
+            document.getElementById('list').value += '\n';
+        }
+        document.getElementById('list').value += uri;
+    }
+</script>
+<div class="container-fluid">
+    <div class="row d-flex d-md-block flex-nowrap wrapper">
+        <main class="col-12 float-left col px-5 px-md-3 py-3 main">
+            <h1 class="display-3 hidden-xs-down">
+                Dashboard
+            </h1>
+            <% include ./partials/messages %>
+        </main>
+                <h2 class="accordion-header">
+                    <button class="btn btn-primary" type="button" data-toggle="collapse" data-target="#schemaDescriptionContainer" aria-expanded="true" aria-controls="schemaDescriptionContainer">Metadata schema definitions</button>
+                    
+                </h2>
+                <div id="schemaDescriptionContainer" class="collapse" data-bs-parent="schemaAccordion">
+                    <div class="card card-body">
+                    <table class="table table-bordered">
+                        <thead>
+                        <tr>
+                            <th>URI</th>
+                            <th>Name</th>
+                        </tr>
+                        </thead>
+                        <tbody>
+                        <% for (desc of descriptionList) { %>
+                            <tr>
+                                <td><%= desc[0]%></td>
+                                <td><%= desc[1]%></td>
+                                <td><button class="btn btn-primary" onclick="addToList('<%= desc[0] %>')">Add</button></td>
+                            </tr>
+                        <% } %>
+                        </tbody>
+                    </table>
+                    </div>
+                </div>
+        <form action="/metadata/updateList" method="POST">
+            <div class="form-group">
+                <textarea rows="5" cols="60" id="list" name="list"><%= list %></textarea>
+                <p><label class="display-5" for="list">The ressource which should be allowed/blocked.</label></p>
+            </div>
+            <div class="form-group">
+                
+                <% if (useAllowList===true) { %>
+                    <p><input type="radio" checked=true id="useAllowListYes" name="useAllowList" value="true">
+                    <label for="useAllowListYes">Allow only the URIs in this list and block the rest</label></p>
+                    <p><input type="radio" id="useAllowListNo" name="useAllowList" value="false">
+                    <label for="useAllowListNo">Allow all URIs and block only those in the list</label></p>
+                <% } else { %>
+                    <p><input type="radio" id="useAllowListYes" name="useAllowList" value="true">
+                    <label for="useAllowListYes">Allow only the URIs in this list and block the rest</label></p>
+                    <p><input type="radio" checked=true id="useAllowListNo" name="useAllowList" value="false">
+                    <label for="useAllowListNo">Block only the URIs in this list and allow the rest</label></p>
+                <% } %>
+            </div>
+            <button type="submit" class="btn btn-primary">Update</button>
+        </form>
+
+    </div>
+</div>
\ No newline at end of file
diff --git a/StationSoftware/views/partials/messages.ejs b/StationSoftware/views/partials/messages.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..713cb89a3f5050205c74b4210ad1cbfa1d8d1c98
--- /dev/null
+++ b/StationSoftware/views/partials/messages.ejs
@@ -0,0 +1,36 @@
+<% if(typeof errors != 'undefined'){ %> <% errors.forEach(function(error) { %>
+<div class="alert alert-warning alert-dismissible fade show" role="alert">
+  <%= error.msg %>
+  <button type="button" class="close" data-dismiss="alert" aria-label="Close">
+    <span aria-hidden="true">&times;</span>
+  </button>
+</div>
+<% }); %> <% } %> <% if(success_msg != ''){ %>
+<div class="alert alert-success alert-dismissible fade show" role="alert">
+  <%= success_msg %>
+  <button type="button" class="close" data-dismiss="alert" aria-label="Close">
+    <span aria-hidden="true">&times;</span>
+  </button>
+</div>
+<% } %> <% if(error_msg != ''){ %>
+<div class="alert alert-danger alert-dismissible fade show" role="alert">
+  <%= error_msg %>
+  <button type="button" class="close" data-dismiss="alert" aria-label="Close">
+    <span aria-hidden="true">&times;</span>
+  </button>
+</div>
+<% } %> <% if(error != ''){ %>
+<div class="alert alert-danger alert-dismissible fade show" role="alert">
+  <%= error %>
+  <button type="button" class="close" data-dismiss="alert" aria-label="Close">
+    <span aria-hidden="true">&times;</span>
+  </button>
+</div>
+<% } %> <% if(logs != ''){ %>
+  <div class="alert alert-secondary alert-dismissible fade show" role="alert">
+    <%= logs %>
+    <button type="button" class="close" data-dismiss="alert" aria-label="Close">
+      <span aria-hidden="true">&times;</span>
+    </button>
+  </div>
+<% } %>
diff --git a/StationSoftware/views/partials/nav.ejs b/StationSoftware/views/partials/nav.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..9e6cfc74c90811f7ceecdb4cc0ea1f17468c7c03
--- /dev/null
+++ b/StationSoftware/views/partials/nav.ejs
@@ -0,0 +1,45 @@
+<!-- Nav -->
+<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top" style="position: sticky;">
+    <!-- <a class="navbar-brand" href="#"><img src="/logo.png"></a> -->
+    <a class="navbar-brand" href="#">
+        <i class="fas fa-hospital"></i> PHT - <%=process.env.STATION_ID.toUpperCase()%>
+    </a>
+    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
+        aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
+        <span class="navbar-toggler-icon"></span>
+    </button>
+    <div class="collapse navbar-collapse" id="navbarSupportedContent">
+        <ul class="navbar-nav mr-auto">
+            <li class="nav-item <%if(active=='dashboard'){%>active<%}%>">
+                <a class="nav-link" href="/dashboard">Dashboard</span></a>
+            </li>
+            <li class="nav-item <%if(active=='images'){%>active<%}%>">
+                <a class="nav-link" href="/dashboard/images">Images</a>
+            </li>
+            <li class="nav-item <%if(active=='containers'){%>active<%}%>">
+                <a class="nav-link" href="/dashboard/containers">Containers</a>
+            </li>
+            <li class="nav-item <%if(active=='notifications'){%>active<%}%>">
+                <a class="nav-link" href="/dashboard/notifications">Notifications</a>
+            </li>
+            <li class="nav-item <%if(active=='metadata'){%>active<%}%>">
+                <a class="nav-link" href="/dashboard/metadata">Metadata</a>
+            </li>
+            <li class="nav-item">
+                <a class="nav-link" href="#">Profile</a>
+            </li>
+            <li class="nav-item <%if(active=='vault'){%>active<%}%>">
+                <a class="nav-link" href="/vault">Vault</a>
+            </li>
+        </ul>
+
+        <%if (locals.user){ %>
+            <form class="form-inline my-2 my-lg-0">
+                <label class="text-white bg-dark mr-sm-2 p-3"><i class="far fa-user"></i><%= user.firstname %></label>
+                <a href="/auth/logout" class="btn btn-outline-danger my-2 my-sm-0 p-3"><i
+                        class="fas fa-sign-out-alt"></i>Logout</a>
+            </form>
+        <% } %>
+    </div>
+</nav>
+
diff --git a/StationSoftware/views/register.ejs b/StationSoftware/views/register.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..cd38646b25a2c3ae952d61cc2470200f58cf78b3
--- /dev/null
+++ b/StationSoftware/views/register.ejs
@@ -0,0 +1,41 @@
+<div class="row mt-5">
+  <div class="col-md-6 m-auto">
+    <div class="card card-body">
+      <h1 class="text-center mb-3">
+        <i class="fas fa-user-plus"></i> Register
+      </h1>
+      <% include ./partials/messages %>
+      <form action="/auth/register" method="POST">
+        <div class="form-group">
+          <label for="firstname">First Name</label>
+          <input type="name" id="firstname" name="firstname" class="form-control" placeholder="Enter Name"
+            value="<%= typeof name != 'undefined' ? name : '' %>" />
+        </div>
+        <div class="form-group">
+          <label for="lastname">Last Name</label>
+          <input type="name" id="lastname" name="lastname" class="form-control" placeholder="Enter Name"
+            value="<%= typeof name != 'undefined' ? name : '' %>" />
+        </div>
+        <div class="form-group">
+          <label for="email">Email</label>
+          <input type="email" id="email" name="email" class="form-control" placeholder="Enter Email"
+            value="<%= typeof email != 'undefined' ? email : '' %>" />
+        </div>
+        <div class="form-group">
+          <label for="password">Password</label>
+          <input type="password" id="password" name="password" class="form-control" placeholder="Create Password"
+            value="<%= typeof password != 'undefined' ? password : '' %>" />
+        </div>
+        <div class="form-group">
+          <label for="password2">Confirm Password</label>
+          <input type="password" id="password2" name="password2" class="form-control" placeholder="Confirm Password"
+            value="<%= typeof password2 != 'undefined' ? password2 : '' %>" />
+        </div>
+        <button type="submit" class="btn btn-primary btn-block">
+          Register
+        </button>
+      </form>
+      <p class="lead mt-4">Have An Account? <a href="/auth/login">Login</a></p>
+    </div>
+  </div>
+</div>
\ No newline at end of file
diff --git a/StationSoftware/views/vault/vault-init-download-keys.ejs b/StationSoftware/views/vault/vault-init-download-keys.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..71e4f3fbf95b187c75c99327d3d20c9cd892547b
--- /dev/null
+++ b/StationSoftware/views/vault/vault-init-download-keys.ejs
@@ -0,0 +1,62 @@
+<!-- Nav -->
+<%- include('../partials/nav', {active: "vault" }); %>
+
+<div class="container-fluid">
+    <div class="row d-flex d-md-block flex-nowrap wrapper">
+        <main class="col-12 float-left col px-5 px-md-3 py-3 main">
+            <h1>
+                Vault Initialization
+            </h1>
+            <% include ../partials/messages %>
+            <div class="my-3 p-3 bg-white rounded shadow-sm">
+
+                <div class="card">
+                    <div class="card-body">
+                        <h5 class="card-title">Vault has been initialized! Here is your key.</h5>
+                        <p class="card-text">Please securely distribute the keys below. When the Vault is
+                            re-sealed, restarted, or stopped, you must provide at least <strong class="text-danger">
+                                <%=vaultkeyThreshold%>
+                            </strong> of these keys to
+                            unseal it again. Vault does not store the master key. Without at least <strong class="text-danger">
+                                <%=vaultkeyThreshold%>
+                            </strong> keys, your
+                            Vault will remain permanently sealed.</p>
+                        <button class="btn btn-success"
+                            onclick="downloadKeys('<%=JSON.stringify(vaultKeys)%>', '<%= (new Date()).toISOString() %>');displayUnsealBtn()">Download
+                            keys</button>
+                        <a id="unsealNavBtn" href="/vault/unseal" class="btn btn-primary d-none">Continue to Unseal</a>
+                    </div>
+                </div>
+
+            </div>
+        </main>
+    </div>
+</div>
+
+
+<script>
+
+    function downloadKeys(keys, datetimeString) {
+
+        //Beautify JSON file
+        keys = JSON.stringify(JSON.parse(keys), null, 2);
+        let blob = new Blob([keys], { type: "application/json" });
+        let url = window.URL.createObjectURL(blob);
+        let filename = `vault-${datetimeString.replaceAll(':', '_')}.json`;
+
+        let element = document.createElement('a');
+        element.setAttribute('href', url);
+        element.setAttribute('download', filename);
+        element.style.display = 'none';
+        document.body.appendChild(element);
+        element.click();
+        document.body.removeChild(element);
+    }
+
+
+    function displayUnsealBtn() {
+        const btnElm = document.getElementById('unsealNavBtn');
+        btnElm.classList.remove("d-none");
+    }
+
+</script>
\ No newline at end of file
diff --git a/StationSoftware/views/vault/vault-init.ejs b/StationSoftware/views/vault/vault-init.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..2951a47699f0aed774153e13c86a6a6b916d0d4b
--- /dev/null
+++ b/StationSoftware/views/vault/vault-init.ejs
@@ -0,0 +1,40 @@
+<!-- Nav -->
+<%- include('../partials/nav', {active: "vault"}); %>
+
+<div class="container-fluid">
+    <div class="row d-flex d-md-block flex-nowrap wrapper">
+        <main class="col-12 float-left col px-5 px-md-3 py-3 main">
+            <h1>
+                Vault Initialization
+            </h1>
+            <% include ../partials/messages %>
+            <div class="my-3 p-3 bg-white rounded shadow-sm">
+                <form action="/vault/init" method="POST" enctype="application/json">
+    
+                    <div class="form-group">
+    
+                        <div class="form-row mb-2">
+                            <div class="col">
+                                <label for="keyShares">Key shares</label>
+                                <input id="keyShares" type="number" class="form-control" placeholder="Key shares" name="keyShares" value="5" required>
+                                <small id="keySharesHelp" class="form-text text-muted">The number of key shares to split the master key into</small>
+                            </div>
+                        </div>
+
+                        <div class="form-row mb-2">
+                            <div class="col">
+                                <label for="keyThreshold">Key threshold</label>
+                                <input id="keyThreshold" type="number" class="form-control" placeholder="Key threshold" name="keyThreshold" value="3" required>
+                                <small id="keySharesHelp" class="form-text text-muted">The number of key shares required to reconstruct the master key</small>
+                            </div>
+                        </div>
+    
+                    </div>
+                    <div>
+                        <button type="submit" class="btn btn-primary">Initialize</button>
+                    </div>
+                </form>
+            </div>
+        </main>
+    </div>
+</div>
\ No newline at end of file
diff --git a/StationSoftware/views/vault/vault-kv-engine-config-show.ejs b/StationSoftware/views/vault/vault-kv-engine-config-show.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..24bb2e72a3713c6c7d05bb186078f13107edf282
--- /dev/null
+++ b/StationSoftware/views/vault/vault-kv-engine-config-show.ejs
@@ -0,0 +1,128 @@
+<!-- Nav -->
+<%- include('../partials/nav', {active: "vault"}); %>
+
+<div class="container-fluid">
+    <div class="row d-flex d-md-block flex-nowrap wrapper">
+        <main class="col-12 float-left col px-5 px-md-3 py-3 main">
+            <h1>
+                Configuration: <%=kvEngineConfig.path%>
+            </h1>
+            <% include ../partials/messages %>
+            <div class="my-3 p-3 bg-white rounded shadow-sm">
+                <div class="table-responsive">
+                    <table class="table table-striped table-hover table-sm">
+                        <tbody>
+                            <tr>
+                                <td class="break-word font-weight-bold">
+                                    Secret engine type
+                                </td>
+                                <td>
+                                    <%=kvEngineConfig.type%>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td class="break-word font-weight-bold">
+                                    Path
+                                </td>
+                                <td>
+                                    <%=kvEngineConfig.path%>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td class="break-word font-weight-bold">
+                                    Description
+                                </td>
+                                <td>
+                                    <%=kvEngineConfig.description%>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td class="break-word font-weight-bold">
+                                    Accessor
+                                </td>
+                                <td>
+                                    <%=kvEngineConfig.accessor%>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td class="break-word font-weight-bold">
+                                    Local
+                                </td>
+                                <td>
+                                    <% if(kvEngineConfig.local){ %>
+                                        <i class="fas fa-check-circle text-success"></i>
+                                      <% } else{ %>  
+                                        <i class="fas fa-times-circle"></i>
+                                     <% } %>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td class="break-word font-weight-bold">
+                                    Seal wrap
+                                </td>
+                                <td>
+                                    <% if(kvEngineConfig.seal_wrap){ %>
+                                        <i class="fas fa-check-circle text-success"></i>
+                                      <% } else{ %>  
+                                        <i class="fas fa-times-circle"></i>
+                                     <% } %>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td class="break-word font-weight-bold">
+                                    Default Lease TTL
+                                </td>
+                                <td>
+                                    <%=kvEngineConfig.config.default_lease_ttl%>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td class="break-word font-weight-bold">
+                                    Max Lease TTL
+                                </td>
+                                <td>
+                                    <%=kvEngineConfig.config.max_lease_ttl%>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td class="break-word font-weight-bold">
+                                    Request keys excluded from HMACing in audit
+                                </td>
+                                <td>
+                                    <%=kvEngineConfig.config.audit_non_hmac_request_keys ? kvEngineConfig.config.audit_non_hmac_request_keys.toString() : ''%>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td class="break-word font-weight-bold">
+                                    Response keys excluded from HMACing in audit
+                                </td>
+                                <td>
+                                    <%=kvEngineConfig.config.audit_non_hmac_response_keys ? kvEngineConfig.config.audit_non_hmac_response_keys.toString() : ''%>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td class="break-word font-weight-bold">
+                                    Allowed passthrough request headers
+                                </td>
+                                <td>
+                                    <%=kvEngineConfig.config.passthrough_request_headers ? kvEngineConfig.config.passthrough_request_headers.toString() : ''%>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td class="break-word font-weight-bold">
+                                    Version
+                                </td>
+                                <td>
+                                    <%=kvEngineConfig.options.version%>
+                                </td>
+                            </tr>
+                        </tbody>
+                    </table>
+                </div>
+                <div>
+                    <a class="btn btn-secondary" href="/vault">Back</a>
+                </div>
+            </div>
+        </main>
+    </div>
+</div>
\ No newline at end of file
diff --git a/StationSoftware/views/vault/vault-kv-engine-enable.ejs b/StationSoftware/views/vault/vault-kv-engine-enable.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..5d6f77badd49b465acf98209eb8cd2ee5a62dfd5
--- /dev/null
+++ b/StationSoftware/views/vault/vault-kv-engine-enable.ejs
@@ -0,0 +1,34 @@
+<!-- Nav -->
+<%- include('../partials/nav', {active: "vault"}); %>
+
+<div class="container-fluid">
+    <div class="row d-flex d-md-block flex-nowrap wrapper">
+        <main class="col-12 float-left col px-5 px-md-3 py-3 main">
+            <h1>
+                Enable New KV Engine
+            </h1>
+            <% include ../partials/messages %>
+            <form action="/vault/kv/enable" method="POST">
+                <div class="form-group">
+
+                    <div class="form-row mb-2">
+                        <div class="col">
+                            <label for="pathInput">Path</label>
+                            <input id="pathInput" type="text" class="form-control" placeholder="Path" name="path" required>
+                        </div>
+                        <div class="col">
+                            <label for="versionInput">Version <i class="fas fa-info-circle" data-toggle="tooltip" title="The KV Secrets Engine can operate in different modes. Version 1 is the original generic Secrets Engine the allows for storing of static key/value pairs. Version 2 added more features including data versioning, TTLs, and check and set."></i></label>
+                            <select id="versionInput" class="form-control" name="version" required readonly>
+                                <option value="1" selected>1</option>
+                                <option value="2">2</option>
+                            </select>
+                        </div>
+                    </div>
+
+                </div>
+                <button type="submit" class="btn btn-primary">Enable Engine</button>
+                <a class="btn btn-secondary" href="/vault">Back</a>
+            </form>
+        </main>
+    </div>
+</div>
\ No newline at end of file
diff --git a/StationSoftware/views/vault/vault-kv-engine-list.ejs b/StationSoftware/views/vault/vault-kv-engine-list.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..a02fc1b9beb4198ecb820132d27feea18226cb77
--- /dev/null
+++ b/StationSoftware/views/vault/vault-kv-engine-list.ejs
@@ -0,0 +1,45 @@
+<!-- Nav -->
+<%- include('../partials/nav', {active: "vault"}); %>
+
+<div class="container-fluid">
+    <div class="row d-flex d-md-block flex-nowrap wrapper">
+        <main class="col-12 float-left col px-5 px-md-3 py-3 main">
+            <h1>
+                Key-Value Engines
+            </h1>
+            <% include ../partials/messages %>
+            <div class="my-3 p-3 bg-white rounded shadow-sm">
+                <div class="table-responsive">
+                    <table class="table table-striped table-hover table-sm">
+                        <thead class="thead-dark">
+                            <tr>
+                                <th colspan="2">
+                                    <div class="col p-0 text-right">
+                                        <a href="/vault/kv/enable" class="btn btn-primary">Enable New KV Engine</a>
+                                    </div>
+                                </th>
+                            </tr>
+                            <tr>
+                                <th scope="col">Path</th>
+                                <th scope="col" class="w-operations-btn">Operation</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            <% for(var i=0; i < kvEngines.length; i++) { %> 
+                            <tr>
+                                <td class="break-word font-weight-bold">
+                                    <a href="/vault/kv/secret/read/<%= encodeURIComponent(kvEngines[i]) %>"><%= kvEngines[i] %></a>
+                                </td>
+                                <td>
+                                    <a href="/vault/kv/configuration/<%= kvEngines[i] %>" class="btn btn-primary float-right my-2 my-sm-0 btn-block">View Configuration</a>
+                                    <a href="/vault/kv/disable/<%= kvEngines[i] %>" class="btn btn-danger float-right my-2 my-sm-0 btn-block" onclick="return confirm('Disable engine? Any data in this engine will be permanently deleted.')">Disable</a>
+                                </td>
+                            </tr>
+                            <% } %>
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+        </main>
+    </div>
+</div>
\ No newline at end of file
diff --git a/StationSoftware/views/vault/vault-kv-secret-create.ejs b/StationSoftware/views/vault/vault-kv-secret-create.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..6d79e09ff363e08fc0f058564216778d875bc82d
--- /dev/null
+++ b/StationSoftware/views/vault/vault-kv-secret-create.ejs
@@ -0,0 +1,63 @@
+<!-- Nav -->
+<%- include('../partials/nav', {active: "vault"}); %>
+
+<div class="container-fluid">
+    <div class="row d-flex d-md-block flex-nowrap wrapper">
+        <main class="col-12 float-left col px-5 px-md-3 py-3 main">
+            <h1>
+                Create Secret
+            </h1>
+            <% include ../partials/messages %>
+            <form action="/vault/kv/secret/create" method="POST" enctype="application/json">
+
+                <div class="form-group">
+                    <label for="vaultPath" class="col-form-label font-weight-bold"><%=vaultPath%></label>
+                    <input type="hidden" id="vaultPath" name="vaultPath" value="<%=vaultPath%>">
+                </div>
+
+                <div class="form-group">
+
+                    <div class="form-row mb-2">
+                        <div class="col">
+                            <label for="pathInput">Path</label>
+                            <input id="pathInput" type="text" class="form-control" placeholder="Path" name="secretPath" required>
+                        </div>
+                    </div>
+
+                    <hr/>
+
+                    <div class="form-row mb-2">
+                        <table class="table table-striped table-sm">
+                            <thead>
+                                <tr>
+                                    <th colspan="2">
+                                        Secret Data
+                                    </th>
+                                </tr>
+                            </thead>
+                            <tbody>
+                                <% for (var i in [1,2,3,4,5]){%>
+                                    <tr>
+                                        <td>
+                                            <input id="secretKey<%=i%>" type="text" class="form-control" placeholder="key"
+                                                name="secretData[<%=i%>][key]">
+                                        </td>
+                                        <td>
+                                            <input id="secretValue<%=i%>" type="password" class="form-control" placeholder="value"
+                                                name="secretData[<%=i%>][value]">
+                                        </td>
+                                    </tr>
+                                <%} %>
+                            </tbody>
+                        </table>
+                    </div>
+
+                </div>
+                <div>
+                    <button type="submit" class="btn btn-primary">Save</button>
+                    <a class="btn btn-secondary" href="/vault/kv/secret/read/<%= encodeURIComponent(vaultPath) %>">Back</a>
+                </div>
+            </form>
+        </main>
+    </div>
+</div>
\ No newline at end of file
diff --git a/StationSoftware/views/vault/vault-kv-secret-edit.ejs b/StationSoftware/views/vault/vault-kv-secret-edit.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..dd9541b786073d17f0b7d78f180bf899dd5db536
--- /dev/null
+++ b/StationSoftware/views/vault/vault-kv-secret-edit.ejs
@@ -0,0 +1,104 @@
+<!-- Nav -->
+<%- include('../partials/nav', {active: "vault"}); %>
+
+<div class="container-fluid">
+    <div class="row d-flex d-md-block flex-nowrap wrapper">
+        <main class="col-12 float-left col px-5 px-md-3 py-3 main">
+            <h1>
+                Edit Secret
+            </h1>
+            <% include ../partials/messages %>
+            <form action="/vault/kv/secret/edit" method="POST" enctype="application/json">
+
+                <div class="form-group">
+
+                    <div class="form-row mb-2">
+                        <div class="col">
+                            <label for="pathInput" class="font-weight-bold">Path</label>
+                            <input id="pathInput" type="text" class="form-control" placeholder="Path" name="vaultPath" value="<%=vaultPath%>" required readonly>
+                        </div>
+                    </div>
+
+                    <hr/>
+
+                    <div class="form-row mb-2">
+                        <table class="table table-striped table-sm">
+                            <thead>
+                                <tr>
+                                    <th colspan="3">
+                                        Secret data
+                                    </th>
+                                </tr>
+                            </thead>
+                            <tbody>
+                                <% Object.keys(secretData).concat([null,null,null,null]).forEach((key,i) => { %>
+                                    <tr id="tableRow<%=i%>">
+                                        <td>
+                                            <input id="secretKey<%=i%>" type="text" class="form-control" placeholder="key"
+                                                name="secretData[<%=i%>][key]"
+                                                value="<%=key%>">
+                                        </td>
+                                        <td>
+                                            <div class="input-group">
+                                                <input id="secretValue<%=i%>" type="password" class="form-control" placeholder="value"
+                                                    name="secretData[<%=i%>][value]" value="<%=secretData[key]%>"
+                                                    onfocus="togglePasswordInputVisibility('<%=i%>')"
+                                                    onblur="togglePasswordInputVisibility('<%=i%>')">
+                                            
+                                                <% if (key) { %>
+                                                    <div class="input-group-append">
+                                                        <button id="secretValue<%=i%>_toggleBtn" class="btn btn-outline-secondary" type="button"
+                                                            onclick="togglePasswordInputVisibility('<%=i%>')"><i class="fas fa-eye"></i></button>
+                                                    </div>
+                                                <%} %>
+                                            </div>
+                                        </td>
+                                        <td style="width: 50px;">
+                                            <% if (key) { %>
+                                                <button id="secret<%=i%>_clearBtn" class="btn btn-outline-secondary" type="button" onclick="clearInput('<%=i%>')"><i
+                                                        class="fas fa-trash-alt"></i></button>
+                                            <%} %>
+                                        </td>
+                                    </tr>
+                                <%}) %>
+                            </tbody>
+                        </table>
+                    </div>
+
+                </div>
+                <div>
+                    <button type="submit" class="btn btn-primary">Save</button>
+                    <a class="btn btn-secondary" href="/vault/kv/secret/read/<%=encodeURIComponent(vaultPath)%>">Back</a>
+                </div>
+            </form>
+        </main>
+    </div>
+</div>
+
+<script>
+
+    function togglePasswordInputVisibility(rowIndex) {
+        const inputElement = document.getElementById(`secretValue${rowIndex}`);
+        const toggleBtnElement = document.getElementById(`secretValue${rowIndex}_toggleBtn`);
+        console.log(inputElement);
+        console.log(toggleBtnElement);
+        if (inputElement.type === "password") {
+            inputElement.type = "text";
+            toggleBtnElement.innerHTML = '<i class="fas fa-eye-slash"></i>';
+        }
+        else if (inputElement.type === "text") {
+            inputElement.type = "password";
+            toggleBtnElement.innerHTML = '<i class="fas fa-eye"></i>';
+        }
+    }
+
+    function clearInput(rowIndex) {
+        const secretKeyInputElement = document.getElementById(`secretKey${rowIndex}`);
+        const secretValueInputElement = document.getElementById(`secretValue${rowIndex}`);
+        const tableRowElement = document.getElementById(`tableRow${rowIndex}`);
+        secretKeyInputElement.value = null;
+        secretValueInputElement.value = null;
+        tableRowElement.outerHTML = null;
+    }
+
+</script>
\ No newline at end of file
diff --git a/StationSoftware/views/vault/vault-kv-secret-list.ejs b/StationSoftware/views/vault/vault-kv-secret-list.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..647046e4ba0fb2a7120e533df812ba00d0af4d68
--- /dev/null
+++ b/StationSoftware/views/vault/vault-kv-secret-list.ejs
@@ -0,0 +1,82 @@
+<!-- Nav -->
+<%- include('../partials/nav', {active: "vault"}); %>
+
+<div class="container-fluid">
+    <div class="row d-flex d-md-block flex-nowrap wrapper">
+        <main class="col-12 float-left col px-5 px-md-3 py-3 main">
+            <nav aria-label="breadcrumb">
+                <ol class="breadcrumb">
+
+                    <li class="breadcrumb-item"><a href="/vault">
+                            kv engines
+                        </a>
+                    </li>
+
+                    <% for(var i=0; i < vaultPathBreadcrumb.length; i++) { %>
+            
+                        <% if(i===vaultPathBreadcrumb.length - 1) { %>
+                            <li class="breadcrumb-item active" aria-current="page">
+                                <%=vaultPathBreadcrumb[i].showName%>
+                            </li>
+                        <% } else { %>
+                            <li class="breadcrumb-item"><a href="/vault/kv/secret/read/<%= encodeURIComponent(vaultPathBreadcrumb[i].href) %>">
+                                    <%=vaultPathBreadcrumb[i].showName%>
+                                </a>
+                            </li>
+                        <% } %>
+            
+                    <% } %>
+            
+                </ol>
+            </nav>
+
+            <% include ../partials/messages %>
+            <div class="my-3 p-3 bg-white rounded shadow-sm">
+                <div class="table-responsive">
+                    <table class="table table-striped table-hover table-sm">
+                        <thead class="thead-dark">
+                            <tr>
+                                <th colspan="2">
+                                    <div class="col p-0 text-right">
+                                        <a href="/vault/kv/secret/create/<%= encodeURIComponent(vaultPath) %>" class="btn btn-primary">Create Secret</a>
+                                    </div>
+                                </th>
+                            </tr>
+                            <tr>
+                                <th scope="col">Path</th>
+                                <th scope="col" class="w-operations-btn">Operation</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            <% for(var i=0; i < paths.length; i++) { %>
+                            <tr>
+                                <td class="break-word font-weight-bold">
+                                    <a onclick="buildUrl(this, '<%= paths[i] %>' )" href=""><%= paths[i] %></a>
+                                </td>
+                                <td>
+                                    <% if (!paths[i].endsWith("/")) { %>
+                                        <a href="/vault/kv/secret/edit/<%= encodeURIComponent(vaultPath + paths[i]) %>" class="btn btn-primary float-right my-2 my-sm-0 btn-block">Edit</a>
+                                        <a href="/vault/kv/secret/delete/<%= encodeURIComponent(vaultPath + paths[i]) %>"
+                                            class="btn btn-danger float-right my-2 my-sm-0 btn-block"
+                                            onclick="return confirm('Delete this? This will permanently delete this secret and all its versions.')">Delete</a>
+                                    <% } %>
+                                </td>
+                            </tr>
+                            <% } %>
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+        </main>
+    </div>
+</div>
+
+<script>
+    //build relative url
+    function buildUrl(item, subPath) {
+        console.log(subPath);
+        item.href = window.location.href + encodeURIComponent(subPath);
+        console.log(item.href)
+        return true;
+    }
+</script>
\ No newline at end of file
diff --git a/StationSoftware/views/vault/vault-kv-secret-show.ejs b/StationSoftware/views/vault/vault-kv-secret-show.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..3d0905810843a18ba8459851bec53cf0ba03d2c5
--- /dev/null
+++ b/StationSoftware/views/vault/vault-kv-secret-show.ejs
@@ -0,0 +1,77 @@
+<!-- Nav -->
+<%- include('../partials/nav', {active: "vault"}); %>
+
+<div class="container-fluid">
+    <div class="row d-flex d-md-block flex-nowrap wrapper">
+        <main class="col-12 float-left col px-5 px-md-3 py-3 main">
+            <h1>
+                <%= vaultPath %>
+            </h1>
+            <% include ../partials/messages %>
+            <div class="my-3 p-3 bg-white rounded shadow-sm">
+                <div class="table-responsive">
+                    <table class="table table-striped table-hover table-sm">
+                        <thead class="thead-dark">
+                            <tr>
+                                <th colspan="2">
+                                    <div class="col p-0 text-right">
+                                        <a href="/vault/kv/secret/edit/<%= encodeURIComponent(vaultPath) %>" class="btn btn-primary">Edit</a>
+                                        <a href="/vault/kv/secret/delete/<%= encodeURIComponent(vaultPath) %>"
+                                            class="btn btn-danger"
+                                            onclick="return confirm('Delete this? This will permanently delete this secret and all its versions.')">Delete</a>
+                                    </div>
+                                </th>
+                            </tr>
+                            <tr>
+                                <th scope="col">Key</th>
+                                <th scope="col">Value</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            <% Object.keys(secretData).forEach((key,i) => { %>
+                            <tr>
+                                <td class="break-word font-weight-bold">
+                                    <%= key %>
+                                </td>
+                                <td>
+                                    <div class="input-group">
+                                        <input id="secretValue<%=i%>" type="password" class="form-control" name="secretData[<%=i%>][value]"
+                                            value="<%=secretData[key]%>" readonly>
+                                    
+                                        <div class="input-group-append">
+                                            <button id="secretValue<%=i%>_toggleBtn" class="btn btn-outline-secondary" type="button"
+                                                onclick="togglePasswordInputVisibility('<%=i%>')"><i class="fas fa-eye"></i></button>
+                                        </div>
+                                    </div>
+                                </td>
+                            </tr>
+                            <% }) %>
+                        </tbody>
+                    </table>
+                </div>
+                <div>
+                    <a class="btn btn-secondary" href="/vault/kv/secret/read/<%=encodeURIComponent(vaultPath.split('/').slice(0,-1).concat(['']).join('/'))%>">Back</a>
+                </div>
+            </div>
+        </main>
+    </div>
+</div>
+
+<script>
+
+    function togglePasswordInputVisibility(rowIndex) {
+        const inputElement = document.getElementById(`secretValue${rowIndex}`);
+        const toggleBtnElement = document.getElementById(`secretValue${rowIndex}_toggleBtn`);
+        console.log(inputElement);
+        console.log(toggleBtnElement);
+        if (inputElement.type === "password") {
+            inputElement.type = "text";
+            toggleBtnElement.innerHTML = '<i class="fas fa-eye-slash"></i>';
+        }
+        else if (inputElement.type === "text") {
+            inputElement.type = "password";
+            toggleBtnElement.innerHTML = '<i class="fas fa-eye"></i>';
+        }
+    }
+
+</script>
\ No newline at end of file
diff --git a/StationSoftware/views/vault/vault-set-token.ejs b/StationSoftware/views/vault/vault-set-token.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..aa3c563f0fe51b70c8da0b406d0141713c431a13
--- /dev/null
+++ b/StationSoftware/views/vault/vault-set-token.ejs
@@ -0,0 +1,31 @@
+<!-- Nav -->
+<%- include('../partials/nav', {active: "vault"}); %>
+
+<div class="container-fluid">
+    <div class="row d-flex d-md-block flex-nowrap wrapper">
+        <main class="col-12 float-left col px-5 px-md-3 py-3 main">
+            <h1>
+                Sign in to Vault
+            </h1>
+            <% include ../partials/messages %>
+            <div class="my-3 p-3 bg-white rounded shadow-sm">
+                <form action="/vault/set-token" method="POST" enctype="application/json">
+    
+                    <div class="form-group">
+    
+                        <div class="form-row mb-2">
+                            <div class="col">
+                                <label for="token">Token</label>
+                                <input id="token" type="password" class="form-control" name="token" required>
+                            </div>
+                        </div>
+    
+                    </div>
+                    <div>
+                        <button type="submit" class="btn btn-primary">Sign In</button>
+                    </div>
+                </form>
+            </div>
+        </main>
+    </div>
+</div>
\ No newline at end of file
diff --git a/StationSoftware/views/vault/vault-unseal.ejs b/StationSoftware/views/vault/vault-unseal.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..8ef9331879089df914d2be9a09c1a59d9fb9ddb6
--- /dev/null
+++ b/StationSoftware/views/vault/vault-unseal.ejs
@@ -0,0 +1,50 @@
+<!-- Nav -->
+<%- include('../partials/nav', {active: "vault"}); %>
+
+<div class="container-fluid">
+    <div class="row d-flex d-md-block flex-nowrap wrapper">
+        <main class="col-12 float-left col px-5 px-md-3 py-3 main">
+            <h1>
+                Unseal Vault
+            </h1>
+            <% include ../partials/messages %>
+            <div class="my-3 p-3 bg-white rounded shadow-sm">
+                <div class="alert alert-warning" role="alert">
+                    <h4 class="alert-heading">Vault is sealed</h4>
+                    <p>You can unseal the vault by entering a portion of the master key. Once all portions are entered, the vault
+                        will be unsealed.</p>
+                </div>
+                <form action="/vault/unseal" method="POST" enctype="application/json">
+    
+                    <div class="form-group">
+    
+                        <div class="form-row mb-2">
+                            <div class="col">
+                                <label for="key">Master Key Portion</label>
+                                <input id="key" type="password" class="form-control" name="key" required>
+                            </div>
+                        </div>
+
+                        <% if (vaultSealStatus.progress> 0){%>
+                            <div class="form-row mb-2">
+                                <div class="col">
+                                    <div class="progress">
+                                        <div class="progress-bar bg-success" role="progressbar"
+                                            style="width:<%=vaultSealStatus.progressPercentage%>%" aria-valuenow="<%=vaultSealStatus.progress%>"
+                                            aria-valuemin="0" aria-valuemax="<%=vaultSealStatus.t%>">
+                                            <%=vaultSealStatus.progress%>/<%=vaultSealStatus.t%> keys provided
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        <%} %>
+    
+                    </div>
+                    <div>
+                        <button type="submit" class="btn btn-primary">Unseal</button>
+                    </div>
+                </form>
+            </div>
+        </main>
+    </div>
+</div>
\ No newline at end of file
diff --git a/StationSoftware/views/welcome.ejs b/StationSoftware/views/welcome.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..e3b237de756c0437cc9beb3d232ad8adaf8b6273
--- /dev/null
+++ b/StationSoftware/views/welcome.ejs
@@ -0,0 +1,25 @@
+<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top" style="position: sticky;">
+  <!-- <a class="navbar-brand" href="#"><img src="/logo.png"></a> -->
+  <a class="navbar-brand" href="#">
+    RWTH i5 PHT <i class="fas fa-hospital"></i> <%=process.env.STATION_ID.toUpperCase()%>
+  </a>
+</nav>
+<div class="row mt-5">
+  <div class="col-md-6 m-auto">
+    <div class="card card-body text-center">
+      <h1>
+        <p>Create an account or login</p>
+      </h1>
+      <a href="/auth/register" class="btn btn-primary btn-block mb-2">Register</a>
+      <a href="/auth/login" class="btn btn-secondary btn-block"></i>Login</a>
+    </div>
+  </div>
+</div>
+
+
+
+
+
+
+
+