diff --git a/app/authorization/AuthProvider.scala b/app/authorization/AuthProvider.scala new file mode 100644 index 00000000..7607a411 --- /dev/null +++ b/app/authorization/AuthProvider.scala @@ -0,0 +1,46 @@ +// Copyright (C) 2018 The Delphi Team. +// See the LICENCE file distributed with this work for additional +// information regarding copyright ownership. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package authorization + + +import pdi.jwt.{Jwt, JwtAlgorithm, JwtClaim} +import play.api.Configuration + + object AuthProvider { + + var Token = "" // scalastyle:ignore + + /** This method generates JWT token for registering Delphi-Management at the Instance-Registry + * + * @param validFor + * @return + */ + + def generateJwt(validFor: Long = 1)(implicit configuration: Configuration): String = { + val jwtSecretKey = configuration.get[String]("play.http.secret.JWTkey") + if (Token == "" || !Jwt.isValid(Token, jwtSecretKey, Seq(JwtAlgorithm.HS256))) { + val claim = JwtClaim() + .issuedNow + .expiresIn(validFor * 300) + .startsNow + . +("user_id", configuration.get[String]("play.http.instance")) + . +("user_type", "Admin") + + Token = Jwt.encode(claim, jwtSecretKey, JwtAlgorithm.HS256) + } + Token + } + } diff --git a/app/controllers/ApiRouter.scala b/app/controllers/ApiRouter.scala index a55ab693..3e51482d 100644 --- a/app/controllers/ApiRouter.scala +++ b/app/controllers/ApiRouter.scala @@ -43,6 +43,6 @@ class ApiRouter @Inject()(irController: InstanceRegistryController, sysControlle case POST(p"/pauseInstance" ? q"instanceID=$instanceID") => irController.handleRequest(action="/pause", instanceID) case POST(p"/resumeInstance" ? q"instanceID=$instanceID") => irController.handleRequest(action="/resume", instanceID) case POST(p"/deleteInstance" ? q"instanceID=$instanceID") => irController.handleRequest(action="/delete", instanceID) - + case POST(p"/reconnectInstance" ? q"from=$from"& q"to=$to") => irController.reconnect(from.toInt, to.toInt) } } diff --git a/app/controllers/InstanceRegistryController.scala b/app/controllers/InstanceRegistryController.scala index cfa94c13..c56105c9 100644 --- a/app/controllers/InstanceRegistryController.scala +++ b/app/controllers/InstanceRegistryController.scala @@ -22,14 +22,15 @@ import akka.actor.{ActorRef, ActorSystem} import javax.inject.Inject import play.api.{Configuration, Logger} import play.api.libs.concurrent.CustomExecutionContext -import play.api.libs.json._ import play.api.libs.ws.WSClient import akka.stream.Materializer import play.api.libs.streams.ActorFlow import actors.{ClientSocketActor, PublishSocketMessageActor} import play.api.mvc._ - import scala.concurrent.ExecutionContext +import authorization.AuthProvider +import play.api.libs.json.Json + trait MyExecutionContext extends ExecutionContext @@ -63,9 +64,15 @@ class InstanceRegistryController @Inject()(implicit system: ActorSystem, mat: Ma val instanceRegistryUri = config.get[String]("app.instanceRegistryUri") val instanceRegistryBasePath = config.get[String]("app.instanceRegistryBasePath") + /**This method maps list of instances with specific componentType. + * + * @param componentType + * @return + */ def instances(componentType: String): Action[AnyContent] = Action.async { - - ws.url(instanceRegistryUri + "/instances").addQueryStringParameters("ComponentType" -> componentType).get().map { response => + ws.url(instanceRegistryUri).addQueryStringParameters("ComponentType" -> componentType) + .withHttpHeaders(("Authorization", s"Bearer ${AuthProvider.generateJwt()}")) + .get().map { response => // TODO: possible handling of parsing the data can be done here Ok(response.body) @@ -80,8 +87,15 @@ class InstanceRegistryController @Inject()(implicit system: ActorSystem, mat: Ma } } + /**Called to fetch network graph of current registry. Contains a list of all instances and all links + * currently registered. + * + * @return + */ + def getNetwork(): Action[AnyContent] = Action.async { - ws.url(instanceRegistryUri + "/network").get().map { response => + ws.url(instanceRegistryUri + "/instances/network").withHttpHeaders(("Authorization", s"Bearer ${AuthProvider.generateJwt()}")) + .get().map { response => // TODO: possible handling of parsing the data can be done here Logger.debug(response.body) if (response.status == 200) { @@ -92,10 +106,20 @@ class InstanceRegistryController @Inject()(implicit system: ActorSystem, mat: Ma }(myExecutionContext) } - def numberOfInstances(componentType: String) : Action[AnyContent] = Action.async { + /** + * Fetches the number of instances for the specified ComponentType. The ComponentType is an optional parameter which is passed as an query + * argument named 'ComponentType' + * + * @param componentType + * @return + */ + + def numberOfInstances(componentType: String): Action[AnyContent] = Action.async { // TODO: handle what should happen if the instance registry is not reachable. // TODO: create constants for the urls - ws.url(instanceRegistryUri + "/numberOfInstances").addQueryStringParameters("ComponentType" -> componentType).get().map { response => + ws.url(instanceRegistryUri + "/count").addQueryStringParameters("ComponentType" -> componentType) + .withHttpHeaders(("Authorization", s"Bearer ${AuthProvider.generateJwt()}")) + .get().map { response => // TODO: possible handling of parsing the data can be done here if (response.status == 200) { Ok(response.body) @@ -106,31 +130,51 @@ class InstanceRegistryController @Inject()(implicit system: ActorSystem, mat: Ma } /** - * This function is for handling all(start, stop, play, pause, resume) POST request. - * To control the instance State - * @param componentId - */ + * This function is for handling all(start, stop, play, pause, resume) POST request. + * To control the instance State (E.g. /instances/42/stop ) + * + * @param componentId + */ def handleRequest(action: String, instanceID: String): Action[AnyContent] = Action.async { request => - ws.url(instanceRegistryUri + action) - .addQueryStringParameters("Id" -> instanceID) + ws.url(instanceRegistryUri + "/instances/" + instanceID + action) + .withHttpHeaders(("Authorization", s"Bearer ${AuthProvider.generateJwt()}")) .post("") .map { response => new Status(response.status) }(myExecutionContext) } + def reconnect(from: Int, to: Int): Action[AnyContent] = Action.async { request => + + ws.url(instanceRegistryUri + "/instances/" + from + "/assignInstance" + ) + .withHttpHeaders(("Authorization", s"Bearer ${AuthProvider.generateJwt()}")) + .post(Json.obj("AssignedInstanceId" -> to)) + .map { response => + response.status match { + case 200 => + Ok(response.body) + case x => + new Status(x) + } + }(myExecutionContext) + } /** - * This function is for handling an POST request for adding an instance to the Scala web server - * - * @param componentType - * @param name - */ - def postInstance(compType: String, name: String): Action[AnyContent] = Action.async { request => - ws.url(instanceRegistryUri + "/deploy") - .addQueryStringParameters("ComponentType" -> compType, "InstanceName" -> name) - .post("") + * This function is for handling an POST request for adding an instance to the Scala web server + * (E.g. .../instances/deploy + * + * @param componentType + * @param name + */ + + def postInstance(compType: String, name: String): Action[AnyContent] = Action.async + { + request => + ws.url(instanceRegistryUri + "/instances/deploy") + .withHttpHeaders(("Authorization", s"Bearer ${AuthProvider.generateJwt()}")) + .post(Json.obj("ComponentType" -> compType, "InstanceName" -> name)) .map { response => response.status match { // scalastyle:off magic.number @@ -142,5 +186,4 @@ class InstanceRegistryController @Inject()(implicit system: ActorSystem, mat: Ma } }(myExecutionContext) } - } diff --git a/build.sbt b/build.sbt index b03a49dc..562c650a 100644 --- a/build.sbt +++ b/build.sbt @@ -4,7 +4,9 @@ name := "delphi-management" organization := "de.upb" -version := "0.8.0" + +version := "0.9.0" + scalaVersion := "2.12.4" @@ -58,3 +60,5 @@ libraryDependencies ++= Seq( "com.google.guava" % "guava" % "25.1-jre", "org.apache.commons" % "commons-compress" % "1.16" ) + +libraryDependencies += "com.pauldijou" %% "jwt-core" % "1.0.0" diff --git a/client/package-lock.json b/client/package-lock.json index f3e70ec2..b054b4ca 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -5,26 +5,37 @@ "requires": true, "dependencies": { "@angular-devkit/architect": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.12.2.tgz", - "integrity": "sha512-32Eim3PM/CJKGcCF1FJQ91ohuF2vBGMd4t1DILaoOMXHWmPLI9N4ILzWHfqFLRvb8QFgLn4VNG7CI9K7GcSBlQ==", + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.12.4.tgz", + "integrity": "sha512-19f3jbGyP+GzTSBgrHR4SWGK81SUgtTxhkAcyhmsIEDuXrMJM8kPu0HB9WivJ5p08+jzwz6xdF9mpNYSeD9uqw==", "dev": true, "requires": { - "@angular-devkit/core": "7.2.2", + "@angular-devkit/core": "7.2.4", "rxjs": "6.3.3" + }, + "dependencies": { + "rxjs": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + } } }, "@angular-devkit/build-angular": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.12.2.tgz", - "integrity": "sha512-4PDykCNDjjFo6Ximhq2efiufoUP6pj8KvhB8UI03mLbn/Os1W0y1lmiPJn+NjeBLwFWH9DqW9Vxk/pYek7MtEA==", + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.12.4.tgz", + "integrity": "sha512-zzjpM9GpCGEUtTPv/T04CALzFXkTgAAiNt1whY1Vmtu2YYUksXOm1ysA2RBLEhb81RodTEwVV2zFUj5v2xHYEw==", "dev": true, "requires": { - "@angular-devkit/architect": "0.12.2", - "@angular-devkit/build-optimizer": "0.12.2", - "@angular-devkit/build-webpack": "0.12.2", - "@angular-devkit/core": "7.2.2", - "@ngtools/webpack": "7.2.2", + "@angular-devkit/architect": "0.12.4", + "@angular-devkit/build-optimizer": "0.12.4", + "@angular-devkit/build-webpack": "0.12.4", + "@angular-devkit/core": "7.2.4", + "@ngtools/webpack": "7.2.4", "ajv": "6.6.2", "autoprefixer": "9.4.3", "circular-dependency-plugin": "5.0.2", @@ -42,11 +53,11 @@ "mini-css-extract-plugin": "0.4.4", "minimatch": "3.0.4", "node-sass": "4.10.0", - "opn": "5.3.0", + "opn": "5.4.0", "parse5": "4.0.0", "portfinder": "1.0.17", - "postcss": "7.0.11", - "postcss-import": "12.0.0", + "postcss": "7.0.13", + "postcss-import": "12.0.1", "postcss-loader": "3.0.0", "raw-loader": "0.5.1", "rxjs": "6.3.3", @@ -69,23 +80,26 @@ "webpack-subresource-integrity": "1.1.0-rc.6" }, "dependencies": { - "parse5": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", - "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", - "dev": true + "rxjs": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } } } }, "@angular-devkit/build-optimizer": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.12.2.tgz", - "integrity": "sha512-5SARSE18X5/churU0Qc0gOfDt5EwuwKsJmIA7hHBzi44iotQm5c8ea0q0acua4/U4K+jOsF6A4Faa08Vr2624A==", + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.12.4.tgz", + "integrity": "sha512-KraU+ZARX7JMtttPjOku9wVF+dnjMsIbiIVsQrNXhpFiGT1fSJhQTPxc98ONgEmUiGROFXXq2mHLilvMr2WdwQ==", "dev": true, "requires": { "loader-utils": "1.1.0", "source-map": "0.5.6", - "typescript": "3.2.2", + "typescript": "3.2.4", "webpack-sources": "1.2.0" }, "dependencies": { @@ -95,6 +109,12 @@ "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", "dev": true }, + "typescript": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.4.tgz", + "integrity": "sha512-0RNDbSdEokBeEAkgNbxJ+BLwSManFy9TeXz8uW+48j/xhEXv1ePME60olyzw2XzUqUBNAYFeJadIqAgNqIACwg==", + "dev": true + }, "webpack-sources": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.2.0.tgz", @@ -116,20 +136,31 @@ } }, "@angular-devkit/build-webpack": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.12.2.tgz", - "integrity": "sha512-Uv3f8XJc/5UTj2T7XjxFYDhuybFIIitLGxBpp/hEIc7eXI4MsJKB6CoDJy+2BQch7c/QjKH7W3dmTxzuSJ2j3g==", + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.12.4.tgz", + "integrity": "sha512-1+t2MUB+dq+1LbfTnvzZwj2QTWiugyMywXqYjsyt0rrh7VcriD1lQ+P5yN8kgFz/R7Ut4LgvS05yDX1JHi20qw==", "dev": true, "requires": { - "@angular-devkit/architect": "0.12.2", - "@angular-devkit/core": "7.2.2", + "@angular-devkit/architect": "0.12.4", + "@angular-devkit/core": "7.2.4", "rxjs": "6.3.3" + }, + "dependencies": { + "rxjs": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + } } }, "@angular-devkit/core": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.2.2.tgz", - "integrity": "sha512-gDF8iXiPN870WuBMl7bCQ5+Qz5SjGL/qMcvP4hli5hkn+kMAhgG38ligUK1bbhPQUJ+Z/nSOEmyv8gLHO09SZg==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.2.4.tgz", + "integrity": "sha512-XHF59tIHg2qEM1Wd415xhykBLjjfOK6yMB7CjNk1bToUMX2QDT87izJF4y1Vwa0lIw9G0jdgP/4/M/OqXcbYmA==", "dev": true, "requires": { "ajv": "6.6.2", @@ -137,72 +168,183 @@ "fast-json-stable-stringify": "2.0.0", "rxjs": "6.3.3", "source-map": "0.7.3" + }, + "dependencies": { + "rxjs": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + } } }, "@angular-devkit/schematics": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-7.2.2.tgz", - "integrity": "sha512-3qONTeqe+bUJ967PNDeITuD4F+3huTEs3u89zZLV+yvaxoK9XJlvaRoQXAkNAMUkij37BoFrGgBfGNijserd6A==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-7.3.1.tgz", + "integrity": "sha512-cd7usiasfSgw75INz72/VssrLr9tiVRYfo1TEdvr9ww0GuQbuQpB33xbV8W135eAV8+wzQ3Ce8ohaDHibvj6Yg==", "dev": true, "requires": { - "@angular-devkit/core": "7.2.2", + "@angular-devkit/core": "7.3.1", "rxjs": "6.3.3" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.1.tgz", + "integrity": "sha512-56XDWWfIzOAkEk69lBLgmCYybPUA4yjunhmMlCk7vVdb7gbQUyzNjFD04Uj0GjlejatAQ5F76tRwygD9C+3RXQ==", + "dev": true, + "requires": { + "ajv": "6.7.0", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + } + }, + "ajv": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.7.0.tgz", + "integrity": "sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "rxjs": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + } } }, "@angular/animations": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-7.2.1.tgz", - "integrity": "sha512-2AHc4HYz2cUVW3E0oYOTyUzBTnPJdtmVOx/Uo6+jnRqikvOGFOc5VXzFIYODe1Iiy+EYcSZ1lvQqwUbpZd6gwA==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-7.2.4.tgz", + "integrity": "sha512-Wx6cqU6koFOASlyl4aCygtbtROoehU6OKwV2EZTkfzHx6Eu/QyTiSa5kyoApVM5LMmCNeb8SxJMSAnKXztNl0A==", "requires": { "tslib": "^1.9.0" } }, "@angular/cdk": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-7.2.1.tgz", - "integrity": "sha512-oU1Pjq3JkDtkXquLxWK84A2jOCeYRf352dVGbQCxWoSOQ5KBtMAd42huGidPiOSHN6/f7xZwL3n4fq3fVIut8A==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-7.3.2.tgz", + "integrity": "sha512-jnthvY1Kt+DpJTrkgyKTiVuYgBdp4iG7QDeZJPBQm0e8mL2K0Pi9AqFbo01E4CGPqZpvtEggvqM0OJpR8J+amw==", "requires": { "parse5": "^5.0.0", "tslib": "^1.7.1" + }, + "dependencies": { + "parse5": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", + "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", + "optional": true + } } }, "@angular/cli": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-7.2.2.tgz", - "integrity": "sha512-fdj5Gtysde0mi902ZE67Zd1uhyryF+b50r+gmX3c+cFvM5hNXrdV7V82Ldjp7qle6ZF1fDSppSiPxGkt5lvemw==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-7.3.1.tgz", + "integrity": "sha512-8EvXYRhTqTaTk5PKv7VZxIWJiyG51R9RC9gtpRFx4bbnurqBHdEUxGMmaRsGT8QDbfvVsWnuakE0eeW1CrfZAQ==", "dev": true, "requires": { - "@angular-devkit/architect": "0.12.2", - "@angular-devkit/core": "7.2.2", - "@angular-devkit/schematics": "7.2.2", - "@schematics/angular": "7.2.2", - "@schematics/update": "0.12.2", + "@angular-devkit/architect": "0.13.1", + "@angular-devkit/core": "7.3.1", + "@angular-devkit/schematics": "7.3.1", + "@schematics/angular": "7.3.1", + "@schematics/update": "0.13.1", + "@yarnpkg/lockfile": "1.1.0", + "ini": "1.3.5", "inquirer": "6.2.1", - "opn": "5.3.0", - "semver": "5.5.1", + "npm-package-arg": "6.1.0", + "opn": "5.4.0", + "pacote": "9.4.0", + "semver": "5.6.0", "symbol-observable": "1.2.0" + }, + "dependencies": { + "@angular-devkit/architect": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.13.1.tgz", + "integrity": "sha512-QDmIbqde75ZZSEFbw6Q6kQWq4cY6C7D67yujXw6XTyubDNAs1tyXJyxTIB8vjSlEKwRizTTDd/B0ZXVcke3Mvw==", + "dev": true, + "requires": { + "@angular-devkit/core": "7.3.1", + "rxjs": "6.3.3" + } + }, + "@angular-devkit/core": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.1.tgz", + "integrity": "sha512-56XDWWfIzOAkEk69lBLgmCYybPUA4yjunhmMlCk7vVdb7gbQUyzNjFD04Uj0GjlejatAQ5F76tRwygD9C+3RXQ==", + "dev": true, + "requires": { + "ajv": "6.7.0", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + } + }, + "ajv": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.7.0.tgz", + "integrity": "sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "rxjs": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + } } }, "@angular/common": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-7.2.1.tgz", - "integrity": "sha512-lYf3MeVMz69EriS5ANFY5PerJK0i4xHp/Jy67reb8ydZ+sfW320PUMuFtx3bZvk9PD7NdL3QZvXmla/ogrltTQ==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-7.2.4.tgz", + "integrity": "sha512-3/i8RtnLTx/90gJHk5maE8zwsSiHgHvLItaa0qVfNlWiU0eCId/PL6TgDkut5vN9SQYL0oxhxFaVd35HmwsmuQ==", "requires": { "tslib": "^1.9.0" } }, "@angular/compiler": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-7.2.1.tgz", - "integrity": "sha512-wf9w882hNoRaTDRqkEvQxV7nGB3liTX/LWEMunmm/Yz0nWkvgErR9pIHv3Sm4Ox0hyG3GdMpcVBzQ8qPomGOag==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-7.2.4.tgz", + "integrity": "sha512-+zyMzPCL45ePEV9nrnYJvhAVgp2Y19bDaq0f0YdZAqAjgDqHzXGGR6wX8GueyJWmUYWx5vwK6Apla4HwDrYA1w==", "requires": { "tslib": "^1.9.0" } }, "@angular/compiler-cli": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-7.2.1.tgz", - "integrity": "sha512-ImmKTnBbAWIY7qrYSPFLJE83VYcDX7zK6Ig/vOl4e6dzvpZfJDYHMT8ELeWj7a2nkL9SjT8X3o9Mkbrb75Tepg==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-7.2.4.tgz", + "integrity": "sha512-UhLosSeuwFIfaGqGcYOh9WSOuzEpeuhIRAOt81MeqOQEqkoreUjfxrQq8XWNkdqsPZHtiptF5ZwXlMBxlj9jJg==", "dev": true, "requires": { "canonical-path": "1.0.0", @@ -537,63 +679,63 @@ } }, "@angular/core": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-7.2.1.tgz", - "integrity": "sha512-FYNAf4chxBoIVGCW2+fwR2MB2Ur5v1aG9L6zCcMXlZLbR64bu5j2m4e70RhXk/VptKvYWJ45od3xE5KfcaeEig==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-7.2.4.tgz", + "integrity": "sha512-kfAxhIxl89PmB7y81FR/RAv0yWRFcEYxEnTwV+o8jKGfemAXtQ0g/Vh+lJR0SD/TBgFilMxotN1mhwH4A8GShw==", "requires": { "tslib": "^1.9.0" } }, "@angular/forms": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-7.2.1.tgz", - "integrity": "sha512-MxinNUvl02UfpY9gJtbTU6Mdt9fjIJYOGskcpwm+5u9jiMeRvOnG94ySoNrygg3EWwScX+P0mM4KN6fJWau7gQ==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-7.2.4.tgz", + "integrity": "sha512-DAtOrdlTRsgvmZrsvczCAkY8dhTwZb5DXBmPuSXh0UR9lvEiCgNHGbwEiIiIkAHpw1wSeXZrq0qyy/oJRvf18g==", "requires": { "tslib": "^1.9.0" } }, "@angular/http": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/@angular/http/-/http-7.2.1.tgz", - "integrity": "sha512-3xfdN2bmCbzATwRGUEZQVkGn3IN6tMX/whLWGWgcEV3CENJqTUjfjn1+nSHASQLUnmOr5T7yTiWK5P7bDrHYzw==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@angular/http/-/http-7.2.4.tgz", + "integrity": "sha512-kazJREm7MtSCYbE+9zU/CcUXI5Csu53PooeQlAp80/TOHqry6fVKIMHCI892Db9ScY2ds0SzbyTmrxEQo7PP1A==", "requires": { "tslib": "^1.9.0" } }, "@angular/language-service": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-7.2.1.tgz", - "integrity": "sha512-LmeoO7KXBcPRDvQpBv+ttG9oalCE0z7+AxbJBJNrrwzypP624B3xX2XWai9XUNkxu+shqE00lAU2lC7Fxs5v7A==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-7.2.4.tgz", + "integrity": "sha512-A9Rud/27hHMSUUjpgn57nVeLsoYgdvFwJhtlZA/oCuSpmlD+LqqBsEpPhivwn++u44+DSrFXsic29jlFnsBotw==", "dev": true }, "@angular/material": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-7.2.1.tgz", - "integrity": "sha512-d2O+7dDjIl7W1qlOdTfsAgMSrz9NNmPV6zJKqsRpZtkywAwKSwqzOs29XN3GjN2wYxZCnCpQi99l4GgmzPxRzg==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-7.3.2.tgz", + "integrity": "sha512-x3it941KE8zuzDH1VLP4yfgmC3hwrqa1a2GHiekztF2KrbWPzEEGNvlyYOUV7nwiuwFw5twON73rW0HUKndwEA==", "requires": { "tslib": "^1.7.1" } }, "@angular/platform-browser": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-7.2.1.tgz", - "integrity": "sha512-/6uHdFLmRkrkeOo+TzScrLG2YydG8kBNyT6ZpSOBf+bmB5DHyIGd55gh/tQJteKrnyadxRhqWCLBTYAbVX9Pnw==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-7.2.4.tgz", + "integrity": "sha512-Klt8aKR5SP9bqfMfpSY5vQOY7AQEs8JGuZOk5Bfc2dUtYT2IEIvK2IqO8v2rcFRVO13HOPUxl328efyHqLgI7g==", "requires": { "tslib": "^1.9.0" } }, "@angular/platform-browser-dynamic": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-7.2.1.tgz", - "integrity": "sha512-hrSkI7aESEkqYnu628Z/LvYNlUNMqIqkXYAkT3urxFdCw7UwNeZKrDmd9sRwK3gK3sC1VeD9pXtqaKmGsnBjOA==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-7.2.4.tgz", + "integrity": "sha512-J/xWlmaYOPUoCHZ5TiIRiyYa4uRMtCz3aGdBfY8k/NWtNo8SCYaS3aut7Sk4RS5rK8aAVi+aYFlY5YOrlW+Hbg==", "requires": { "tslib": "^1.9.0" } }, "@angular/router": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-7.2.1.tgz", - "integrity": "sha512-3qMZnhFr6xx3dMy14rKwIw9ISTOZlsp9jAkthXVsfA2/thffScXHPBrH4SipkySLmOAtPmF5m5jscy8mx/1mJQ==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-7.2.4.tgz", + "integrity": "sha512-T8Uqf2H1SV1MQI38WwYJ4aa+4NNnvlp2Tp/rkfg6tKcp/cLkKqE6OOfiy9lmW+i/624v8tMgYoBMOUNBjAG23g==", "requires": { "tslib": "^1.9.0" } @@ -759,26 +901,35 @@ } }, "@ng-bootstrap/ng-bootstrap": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-4.0.1.tgz", - "integrity": "sha512-COQ6MgZ+HD27pGz2sVPB2ttCZozrjHPs0sayuZkleMvzTllYX/eQEPAOiS+yRsXNkDApi5/XGlIFVWBjxTtwoA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-4.0.3.tgz", + "integrity": "sha512-6lTwR9iaNvZLtMDnk9gerV96K8Nk8i2Gu2FeHw/aUK8VPsl6LakXBAn9eBwcWCtpPLQ7m6Amnsa57RufP4ZzSg==", "requires": { "tslib": "^1.9.0" } }, "@ngtools/webpack": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-7.2.2.tgz", - "integrity": "sha512-xjvQ8tlyyReE69q+whAubwX4fayPoy4NHSIDa429qdcUypkvhSScAtou003oVAKG519rznykDrUHAWtvFMVf4Q==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-7.2.4.tgz", + "integrity": "sha512-mXMDODmy53kk+Kb5jgLNQOsSrDBQQMf6C6KZNuGo8AdvUGdGaQeZDze4o7bcUz1KUjuaaP1Zh7pZtho8C4/T+Q==", "dev": true, "requires": { - "@angular-devkit/core": "7.2.2", + "@angular-devkit/core": "7.2.4", "enhanced-resolve": "4.1.0", "rxjs": "6.3.3", "tree-kill": "1.2.0", "webpack-sources": "1.2.0" }, "dependencies": { + "rxjs": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -798,36 +949,126 @@ } }, "@schematics/angular": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-7.2.2.tgz", - "integrity": "sha512-Yonddct1XBG1H5rTikagFTIT2/RhszJnNa2Iz+rvc26ffAl1mmYPB4sQb7gkOaZQSzK6SE7bT2QW32PVjYFoSQ==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-7.3.1.tgz", + "integrity": "sha512-0Ne8APPlTAjKg5CSZqluwCuW/5yPjr3ALCWzqwPxN0suE745usThtasBmqrjw0RMIt8nRqRgtg54Z7lCPO9ZFg==", "dev": true, "requires": { - "@angular-devkit/core": "7.2.2", - "@angular-devkit/schematics": "7.2.2", - "typescript": "3.2.2" + "@angular-devkit/core": "7.3.1", + "@angular-devkit/schematics": "7.3.1", + "typescript": "3.2.4" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.1.tgz", + "integrity": "sha512-56XDWWfIzOAkEk69lBLgmCYybPUA4yjunhmMlCk7vVdb7gbQUyzNjFD04Uj0GjlejatAQ5F76tRwygD9C+3RXQ==", + "dev": true, + "requires": { + "ajv": "6.7.0", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + } + }, + "ajv": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.7.0.tgz", + "integrity": "sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "rxjs": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "typescript": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.4.tgz", + "integrity": "sha512-0RNDbSdEokBeEAkgNbxJ+BLwSManFy9TeXz8uW+48j/xhEXv1ePME60olyzw2XzUqUBNAYFeJadIqAgNqIACwg==", + "dev": true + } } }, "@schematics/update": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.12.2.tgz", - "integrity": "sha512-+eU5O5gV1c0TZvXMUTMaEgKWeSAotbAv66KRnHV70hqDnXHE+hdL0jqlRF5Lz08C+tRbj4/Tlnb17X/jclfxnw==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.13.1.tgz", + "integrity": "sha512-EHOqolT/d/jRGuVTCUESLpk8JNpuaPlsVHfeK7Kdp/t0wSEnmtOelZX4+leS25lGXDaDUF3138ntjrZR4n6bGw==", "dev": true, "requires": { - "@angular-devkit/core": "7.2.2", - "@angular-devkit/schematics": "7.2.2", + "@angular-devkit/core": "7.3.1", + "@angular-devkit/schematics": "7.3.1", "@yarnpkg/lockfile": "1.1.0", "ini": "1.3.5", - "pacote": "9.1.1", + "pacote": "9.4.0", "rxjs": "6.3.3", - "semver": "5.5.1", + "semver": "5.6.0", "semver-intersect": "1.4.0" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.1.tgz", + "integrity": "sha512-56XDWWfIzOAkEk69lBLgmCYybPUA4yjunhmMlCk7vVdb7gbQUyzNjFD04Uj0GjlejatAQ5F76tRwygD9C+3RXQ==", + "dev": true, + "requires": { + "ajv": "6.7.0", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + } + }, + "ajv": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.7.0.tgz", + "integrity": "sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "rxjs": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + } } }, + "@types/cytoscape": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/@types/cytoscape/-/cytoscape-3.2.9.tgz", + "integrity": "sha512-ycpsM1JN7DdeL0tX4tXGPL5LCekfDpE736WDZKBUPlR1MlIaPrPK6APqjEtBuMRg0f1TM9rzaN+MghJLp0f04w==", + "dev": true + }, "@types/jasmine": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.3.5.tgz", - "integrity": "sha512-LJtc52O1PNUffMvH6Q3fS0BOhQWYlkh3SVu/Jc4GoPgJkUytk5Y6YPbw+6lZK2mWWvG62BtVyOFw0ih7r8STsw==", + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.3.9.tgz", + "integrity": "sha512-vw3VyFPa9mlba6NZPBZC3q2Zrnkgy5xuCVI43/tTLX6umdYrYvcFtQUKi2zH3PjFZQ9XCxNM/NMrM9uk8TPOzg==", "dev": true }, "@types/jasminewd2": { @@ -840,9 +1081,9 @@ } }, "@types/node": { - "version": "10.12.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz", - "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==", + "version": "10.12.26", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.26.tgz", + "integrity": "sha512-nMRqS+mL1TOnIJrL6LKJcNZPB8V3eTfRo9FQA2b5gDvrHurC8XbSA86KNe0dShlEL7ReWJv/OU9NL7Z0dnqWTg==", "dev": true }, "@types/q": { @@ -1162,9 +1403,9 @@ "dev": true }, "ajv-keywords": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", - "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.0.tgz", + "integrity": "sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==", "dev": true }, "amdefine": { @@ -1180,9 +1421,9 @@ "dev": true }, "ansi-escapes": { - "version": "3.1.0", - "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", - "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", "dev": true }, "ansi-html": { @@ -1242,7 +1483,6 @@ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "dev": true, - "optional": true, "requires": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -1783,9 +2023,9 @@ } }, "bootstrap": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.2.1.tgz", - "integrity": "sha512-tt/7vIv3Gm2mnd/WeDx36nfGGHleil0Wg8IeB7eMrVkY0fZ5iTaBisSh8oNANc2IBsCc6vCgCNTIM/IEN0+50Q==" + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.3.1.tgz", + "integrity": "sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag==" }, "brace-expansion": { "version": "1.1.11", @@ -1904,13 +2144,13 @@ } }, "browserslist": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.4.0.tgz", - "integrity": "sha512-tQkHS8VVxWbrjnNDXgt7/+SuPJ7qDvD0Y2e6bLtoQluR2SPvlmPUcfcU75L1KAalhqULlIFJlJ6BDfnYyJxJsw==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.4.1.tgz", + "integrity": "sha512-pEBxEXg7JwaakBXjATYw/D1YZh4QUSCX/Mnd/wnqSRPPSi1U39iDhDoKGoBUcraKdxDlrYqJxSI5nNvD+dWP2A==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30000928", - "electron-to-chromium": "^1.3.100", + "caniuse-lite": "^1.0.30000929", + "electron-to-chromium": "^1.3.103", "node-releases": "^1.1.3" } }, @@ -2061,9 +2301,9 @@ } }, "caniuse-lite": { - "version": "1.0.30000929", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000929.tgz", - "integrity": "sha512-n2w1gPQSsYyorSVYqPMqbSaz1w7o9ZC8VhOEGI9T5MfGDzp7sbopQxG6GaQmYsaq13Xfx/mkxJUWC1Dz3oZfzw==", + "version": "1.0.30000936", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000936.tgz", + "integrity": "sha512-orX4IdpbFhdNO7bTBhSbahp1EBpqzBc+qrvTRVUFfZgA4zta7TdM6PN5ZxkEUgDnz36m+PfWGcdX7AVfFWItJw==", "dev": true }, "canonical-path": { @@ -2458,8 +2698,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true, - "optional": true + "dev": true }, "constants-browserify": { "version": "1.0.0", @@ -2537,9 +2776,9 @@ } }, "core-js": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.2.tgz", - "integrity": "sha512-NdBPF/RVwPW6jr0NCILuyN9RiqLo2b1mddWHkUL+VnvcB7dzlnBJ1bXYntjpTGOgkZiiLWj2JxmOr7eGE3qK6g==" + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.4.tgz", + "integrity": "sha512-05qQ5hXShcqGkPZpXEFLIpxayZscVD2kuMBZewxiIPPEagukO4mqgPA9CWhUvFBJfy3ODdK2p9xyHh7FTU9/7A==" }, "core-util-is": { "version": "1.0.2", @@ -2692,6 +2931,24 @@ "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", "dev": true }, + "cytoscape": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.4.1.tgz", + "integrity": "sha512-oHbpo01yd4SB3TjOc/EU4C66TmauROo2+4tKtpLyFnk+/mu5R6OIlARt6OFSWrgoa1AB2f4wrcU0UnBRqhGNNw==", + "requires": { + "heap": "^0.2.6", + "lodash.debounce": "^4.0.8" + } + }, + "cytoscape-edgehandles": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/cytoscape-edgehandles/-/cytoscape-edgehandles-3.5.0.tgz", + "integrity": "sha512-PKuuCLKvgVPPiR0PLY51FbtHFS2w9mRGRjd5kwWJqARQrKqVm8yJqElbyjFHCs9DhuDMDlOgdZcCWm+mBvaCBA==", + "requires": { + "lodash.memoize": "^4.1.2", + "lodash.throttle": "^4.1.1" + } + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -2861,8 +3118,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "dev": true, - "optional": true + "dev": true }, "depd": { "version": "1.1.2", @@ -2931,9 +3187,9 @@ } }, "dir-glob": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.1.tgz", - "integrity": "sha512-UN6X6XwRjllabfRhBdkVSo63uurJ8nSvMGrwl94EYVz6g+exhTV+yVSYk5VC/xl3MBFBTtC0J20uFKce4Brrng==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", "dev": true, "requires": { "path-type": "^3.0.0" @@ -2983,9 +3239,9 @@ "dev": true }, "duplexify": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.1.tgz", - "integrity": "sha512-vM58DwdnKmty+FSPzT14K9JXb90H+j5emaR4KYbr2KTIz00WHGbWOe5ghQTx233ZCLZtrGDALzKwcjEtSt35mA==", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", "dev": true, "requires": { "end-of-stream": "^1.0.0", @@ -3011,9 +3267,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.103", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.103.tgz", - "integrity": "sha512-tObPqGmY9X8MUM8i3MEimYmbnLLf05/QV5gPlkR8MQ3Uj8G8B2govE1U4cQcBYtv3ymck9Y8cIOu4waoiykMZQ==", + "version": "1.3.113", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.113.tgz", + "integrity": "sha512-De+lPAxEcpxvqPTyZAXELNpRZXABRxf+uL/rSykstQhzj/B0l1150G/ExIIxKc16lI89Hgz81J0BHAcbTqK49g==", "dev": true }, "elliptic": { @@ -3288,9 +3544,9 @@ "dev": true }, "events": { - "version": "1.1.1", - "resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", + "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==", "dev": true }, "eventsource": { @@ -3789,13 +4045,13 @@ "dev": true }, "flush-write-stream": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", - "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", "dev": true, "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.4" + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" } }, "follow-redirects": { @@ -3942,8 +4198,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -3964,14 +4219,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3986,20 +4239,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -4116,8 +4366,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -4129,7 +4378,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4144,7 +4392,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -4152,14 +4399,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -4178,7 +4423,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -4259,8 +4503,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -4272,7 +4515,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -4358,8 +4600,7 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -4395,7 +4636,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -4415,7 +4655,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -4459,14 +4698,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -4475,7 +4712,6 @@ "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", "dev": true, - "optional": true, "requires": { "graceful-fs": "^4.1.2", "inherits": "~2.0.0", @@ -4488,7 +4724,6 @@ "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, - "optional": true, "requires": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", @@ -4526,8 +4761,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true, - "optional": true + "dev": true }, "get-stream": { "version": "3.0.0", @@ -4671,9 +4905,9 @@ "dev": true }, "handlebars": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.12.tgz", - "integrity": "sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.0.tgz", + "integrity": "sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==", "dev": true, "requires": { "async": "^2.5.0", @@ -4683,12 +4917,12 @@ }, "dependencies": { "async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", - "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", "dev": true, "requires": { - "lodash": "^4.17.10" + "lodash": "^4.17.11" } }, "source-map": { @@ -4771,8 +5005,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "dev": true, - "optional": true + "dev": true }, "has-value": { "version": "1.0.0", @@ -4826,6 +5059,11 @@ "minimalistic-assert": "^1.0.1" } }, + "heap": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.6.tgz", + "integrity": "sha1-CH4fELBGky/IWU3Z5tN4r8nR5aw=" + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -5333,15 +5571,6 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "dev": true, - "requires": { - "builtin-modules": "^1.0.0" - } - }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -5528,8 +5757,7 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true, - "optional": true + "dev": true }, "is-windows": { "version": "1.0.2", @@ -5873,9 +6101,9 @@ "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==" }, "js-base64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.0.tgz", - "integrity": "sha512-wlEBIZ5LP8usDylWbDNhKPEFVFdI5hCHpnVoT/Ysvoi/PRhJENm/Rlh9TvjYB38HFfKZN7OzEbRjmjvLkFw11g==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz", + "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==", "dev": true, "optional": true }, @@ -6234,7 +6462,6 @@ "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, - "optional": true, "requires": { "graceful-fs": "^4.1.2", "parse-json": "^2.2.0", @@ -6247,8 +6474,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true, - "optional": true + "dev": true } } }, @@ -6301,8 +6527,12 @@ "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", - "dev": true + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" }, "lodash.mergewith": { "version": "4.6.1", @@ -6317,6 +6547,11 @@ "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=", "dev": true }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" + }, "log4js": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/log4js/-/log4js-3.0.6.tgz", @@ -6384,12 +6619,12 @@ } }, "magic-string": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.1.tgz", - "integrity": "sha512-sCuTz6pYom8Rlt4ISPFn6wuFodbKMIHUMv4Qko9P17dpxb7s52KJTmRuZZqHdGmLCK9AOcDare039nRIcfdkEg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.2.tgz", + "integrity": "sha512-iLs9mPjh9IuTtRsqqhNGYcZXGei0Nh/A4xirrsqW7c+QhKVFL2vm7U09ru6cHRD22azaP/wMDgI+HCqbETMTtg==", "dev": true, "requires": { - "sourcemap-codec": "^1.4.1" + "sourcemap-codec": "^1.4.4" } }, "make-dir": { @@ -6523,8 +6758,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true, - "optional": true + "dev": true }, "map-visit": { "version": "1.0.0", @@ -6564,14 +6798,14 @@ "dev": true }, "mem": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz", - "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz", + "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==", "dev": true, "requires": { "map-age-cleaner": "^0.1.1", "mimic-fn": "^1.0.0", - "p-is-promise": "^1.1.0" + "p-is-promise": "^2.0.0" } }, "memory-fs": { @@ -6948,9 +7182,9 @@ } }, "node-libs-browser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", - "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.0.tgz", + "integrity": "sha512-5MQunG/oyOaBdttrL40dA7bUfPORLRWMUJLQtMg7nluxUvk5XwnLdL9twQHFAjRx/y7mIMkLKT9++qPbbk6BZA==", "dev": true, "requires": { "assert": "^1.1.1", @@ -6960,7 +7194,7 @@ "constants-browserify": "^1.0.0", "crypto-browserify": "^3.11.0", "domain-browser": "^1.1.1", - "events": "^1.0.0", + "events": "^3.0.0", "https-browserify": "^1.0.0", "os-browserify": "^0.3.0", "path-browserify": "0.0.0", @@ -6974,7 +7208,7 @@ "timers-browserify": "^2.0.4", "tty-browserify": "0.0.0", "url": "^0.11.0", - "util": "^0.10.3", + "util": "^0.11.0", "vm-browserify": "0.0.4" }, "dependencies": { @@ -6987,9 +7221,9 @@ } }, "node-releases": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.3.tgz", - "integrity": "sha512-6VrvH7z6jqqNFY200kdB6HdzkgM96Oaj9v3dqGfgp6mF+cHmU4wyQKZ2/WPDRVoR0Jz9KqbamaBN0ZhdUaysUQ==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.7.tgz", + "integrity": "sha512-bKdrwaqJUPHqlCzDD7so/R+Nk0jGv9a11ZhLrD9f6i947qGLrGAhU3OxRENa19QQmwzGy/g6zCDEuLGDO8HPvA==", "dev": true, "requires": { "semver": "^5.3.0" @@ -7063,15 +7297,26 @@ } }, "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { "hosted-git-info": "^2.1.4", - "is-builtin-module": "^1.0.0", + "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "resolve": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } } }, "normalize-path": { @@ -7090,9 +7335,9 @@ "dev": true }, "npm-bundled": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.5.tgz", - "integrity": "sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", "dev": true }, "npm-package-arg": { @@ -7108,9 +7353,9 @@ } }, "npm-packlist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.2.0.tgz", - "integrity": "sha512-7Mni4Z8Xkx0/oegoqlcao/JpPCPEMtUvsmB0q7mgvlMinykJLSRTYuFqoQLYgGY8biuxIeiHO+QNJKbCfljewQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.3.0.tgz", + "integrity": "sha512-qPBc6CnxEzpOcc4bjoIBJbYdy0D/LFFPUdxvfwor4/w3vxeE0h6TiOVurCEPpQ6trjN77u/ShyfeJGsbAfB3dA==", "dev": true, "requires": { "ignore-walk": "^3.0.1", @@ -7129,9 +7374,9 @@ } }, "npm-registry-fetch": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-3.8.0.tgz", - "integrity": "sha512-hrw8UMD+Nob3Kl3h8Z/YjmKamb1gf7D1ZZch2otrIXM3uFLB5vjEY6DhMlq80z/zZet6eETLbOXcuQudCB3Zpw==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-3.9.0.tgz", + "integrity": "sha512-srwmt8YhNajAoSAaDWndmZgx89lJwIZ1GWxOuckH4Coek4uHv5S+o/l9FLQe/awA+JwTnj4FJHldxhlXdZEBmw==", "dev": true, "requires": { "JSONStream": "^1.3.4", @@ -7156,7 +7401,6 @@ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, - "optional": true, "requires": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", @@ -7310,9 +7554,9 @@ } }, "opn": { - "version": "5.3.0", - "resolved": "http://registry.npmjs.org/opn/-/opn-5.3.0.tgz", - "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.4.0.tgz", + "integrity": "sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw==", "dev": true, "requires": { "is-wsl": "^1.1.0" @@ -7410,9 +7654,9 @@ "dev": true }, "p-is-promise": { - "version": "1.1.0", - "resolved": "http://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", - "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.0.0.tgz", + "integrity": "sha512-pzQPhYMCAgLAKPWD2jC3Se9fEfrD9npNos0y150EeqZll7akhEgGhTW/slB6lHku8AvYGiJ+YJ5hfHKePPgFWg==", "dev": true }, "p-limit": { @@ -7446,17 +7690,17 @@ "dev": true }, "pacote": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-9.1.1.tgz", - "integrity": "sha512-f28Rq5ozzKAA9YwIKw61/ipwAatUZseYmVssDbHHaexF0wRIVotapVEZPAjOT7Eu3LYVqEp0NVpNizoAnYBUaA==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-9.4.0.tgz", + "integrity": "sha512-WQ1KL/phGMkedYEQx9ODsjj7xvwLSpdFJJdEXrLyw5SILMxcTNt5DTxT2Z93fXuLFYJBlZJdnwdalrQdB/rX5w==", "dev": true, "requires": { - "bluebird": "^3.5.2", - "cacache": "^11.2.0", + "bluebird": "^3.5.3", + "cacache": "^11.3.2", "figgy-pudding": "^3.5.1", "get-stream": "^4.1.0", "glob": "^7.1.3", - "lru-cache": "^4.1.3", + "lru-cache": "^5.1.1", "make-fetch-happen": "^4.0.1", "minimatch": "^3.0.4", "minipass": "^2.3.5", @@ -7465,7 +7709,7 @@ "normalize-package-data": "^2.4.0", "npm-package-arg": "^6.1.0", "npm-packlist": "^1.1.12", - "npm-pick-manifest": "^2.1.0", + "npm-pick-manifest": "^2.2.3", "npm-registry-fetch": "^3.8.0", "osenv": "^0.1.5", "promise-inflight": "^1.0.1", @@ -7475,7 +7719,7 @@ "safe-buffer": "^5.1.2", "semver": "^5.6.0", "ssri": "^6.0.1", - "tar": "^4.4.6", + "tar": "^4.4.8", "unique-filename": "^1.1.1", "which": "^1.3.1" }, @@ -7500,17 +7744,6 @@ "ssri": "^6.0.1", "unique-filename": "^1.1.1", "y18n": "^4.0.0" - }, - "dependencies": { - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - } } }, "get-stream": { @@ -7522,6 +7755,15 @@ "pump": "^3.0.0" } }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, "mississippi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", @@ -7606,16 +7848,17 @@ } }, "parse-asn1": { - "version": "5.1.1", - "resolved": "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", - "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.3.tgz", + "integrity": "sha512-VrPoetlz7B/FqjBLD2f5wBVZvsZVLnRUrxVLfRYhGXCODa/NWE4p3Wp+6+aV3ZPL3KM7/OZmxDIwwijD7yuucg==", "dev": true, "requires": { "asn1.js": "^4.0.0", "browserify-aes": "^1.0.0", "create-hash": "^1.1.0", "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3" + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" } }, "parse-glob": { @@ -7657,10 +7900,10 @@ } }, "parse5": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", - "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", - "optional": true + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true }, "parseqs": { "version": "0.0.5", @@ -7799,9 +8042,9 @@ } }, "popper.js": { - "version": "1.14.6", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.6.tgz", - "integrity": "sha512-AGwHGQBKumlk/MDfrSOf0JHhJCImdDMcGNoqKmKkU+68GFazv3CQ6q9r7Ja1sKDZmYWTckY/uLyEznheTDycnA==" + "version": "1.14.7", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.7.tgz", + "integrity": "sha512-4q1hNvoUre/8srWsH7hnoSJ5xVmIL4qgz+s4qf2TnJIMyZFUFMGH+9vE7mXynAlHSZ/NdTmmow86muD0myUkVQ==" }, "portfinder": { "version": "1.0.17", @@ -7821,9 +8064,9 @@ "dev": true }, "postcss": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.11.tgz", - "integrity": "sha512-9AXb//5UcjeOEof9T+yPw3XTa5SL207ZOIC/lHYP4mbUTEh4M0rDAQekQpVANCZdwQwKhBtFZCk3i3h3h2hdWg==", + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.13.tgz", + "integrity": "sha512-h8SY6kQTd1wISHWjz+E6cswdhMuyBZRb16pSTv3W4zYZ3/YbyWeJdNUeOXB5IdZqE1U76OUEjjjqsC3z2f3hVg==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -7871,9 +8114,9 @@ } }, "postcss-import": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-12.0.0.tgz", - "integrity": "sha512-3KqKRZcaZAvxbY8DVLdd81tG5uKzbUQuiWIvy0o0fzEC42bKacqPYFWbfCQyw6L4LWUaqPz/idvIdbhpgQ32eQ==", + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-12.0.1.tgz", + "integrity": "sha512-3Gti33dmCjyKBgimqGxL3vcV8w9+bsHwO5UrBawp796+jdardbcFl4RP5w/76BwNL7aGzpKstIfF9I+kdE8pTw==", "dev": true, "requires": { "postcss": "^7.0.1", @@ -8283,7 +8526,6 @@ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", "dev": true, - "optional": true, "requires": { "load-json-file": "^1.0.0", "normalize-package-data": "^2.3.2", @@ -8295,7 +8537,6 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "dev": true, - "optional": true, "requires": { "graceful-fs": "^4.1.2", "pify": "^2.0.0", @@ -8306,8 +8547,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true, - "optional": true + "dev": true } } }, @@ -8316,7 +8556,6 @@ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", "dev": true, - "optional": true, "requires": { "find-up": "^1.0.0", "read-pkg": "^1.0.0" @@ -8327,7 +8566,6 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "dev": true, - "optional": true, "requires": { "path-exists": "^2.0.0", "pinkie-promise": "^2.0.0" @@ -8338,7 +8576,6 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "dev": true, - "optional": true, "requires": { "pinkie-promise": "^2.0.0" } @@ -8634,9 +8871,9 @@ } }, "rxjs": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", - "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", "requires": { "tslib": "^1.9.0" } @@ -8971,9 +9208,9 @@ "dev": true }, "smart-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.0.1.tgz", - "integrity": "sha512-RFqinRVJVcCAL9Uh1oVqE6FZkqsyLiVOYEZ20TqIOjuX7iFVJ+zsbs4RIghnw/pTs7mZvt8ZHhvm1ZUrR4fykg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.0.2.tgz", + "integrity": "sha512-JDhEpTKzXusOqXZ0BUIdH+CjFdO/CR3tLlf5CN34IypI+xMmXW1uB16OOY8z3cICbJlDAVJzNbwBhNO0wt9OAw==", "dev": true }, "snapdragon": { @@ -9232,13 +9469,13 @@ } }, "socks": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.2.2.tgz", - "integrity": "sha512-g6wjBnnMOZpE0ym6e0uHSddz9p3a+WsBaaYQaBaSCJYvrC4IXykQR9MNGjLQf38e9iIIhp3b1/Zk8YZI3KGJ0Q==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.2.3.tgz", + "integrity": "sha512-+2r83WaRT3PXYoO/1z+RDEBE7Z2f9YcdQnJ0K/ncXXbV5gJ6wYfNAebYFYiiUjM6E4JyXnPY8cimwyvFYHVUUA==", "dev": true, "requires": { "ip": "^1.1.5", - "smart-buffer": "^4.0.1" + "smart-buffer": "4.0.2" } }, "socks-proxy-agent": { @@ -9274,12 +9511,12 @@ }, "dependencies": { "async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", - "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", "dev": true, "requires": { - "lodash": "^4.17.10" + "lodash": "^4.17.11" } } } @@ -9528,9 +9765,9 @@ } }, "stream-browserify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", - "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", "dev": true, "requires": { "inherits": "~2.0.1", @@ -9629,7 +9866,6 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, - "optional": true, "requires": { "is-utf8": "^0.2.0" } @@ -9744,14 +9980,14 @@ } }, "terser": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-3.14.1.tgz", - "integrity": "sha512-NSo3E99QDbYSMeJaEk9YW2lTg3qS9V0aKGlb+PlOrei1X02r1wSBHCNX/O+yeTRFSWPKPIGj6MqvvdqV4rnVGw==", + "version": "3.16.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-3.16.1.tgz", + "integrity": "sha512-JDJjgleBROeek2iBcSNzOHLKsB/MdDf+E/BOAJ0Tk9r7p9/fVobfv7LMJ/g/k3v9SXdmjZnIlFd5nfn/Rt0Xow==", "dev": true, "requires": { "commander": "~2.17.1", "source-map": "~0.6.1", - "source-map-support": "~0.5.6" + "source-map-support": "~0.5.9" }, "dependencies": { "source-map": { @@ -10186,9 +10422,9 @@ "dev": true }, "typescript": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz", - "integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.4.tgz", + "integrity": "sha512-0RNDbSdEokBeEAkgNbxJ+BLwSManFy9TeXz8uW+48j/xhEXv1ePME60olyzw2XzUqUBNAYFeJadIqAgNqIACwg==", "dev": true }, "uglify-js": { @@ -10382,9 +10618,9 @@ } }, "util": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", - "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", "dev": true, "requires": { "inherits": "2.0.3" @@ -10951,7 +11187,6 @@ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "dev": true, - "optional": true, "requires": { "string-width": "^1.0.2 || 2" } @@ -11122,9 +11357,9 @@ "dev": true }, "zone.js": { - "version": "0.8.28", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.28.tgz", - "integrity": "sha512-MjwlvV0wr65IQiT0WSHedo/zUhAqtypMdTUjqroV81RohGj1XANwHuC37dwYxphTRbZBYidk0gNS0dQrU2Q3Pw==" + "version": "0.8.29", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.29.tgz", + "integrity": "sha512-mla2acNCMkWXBD+c+yeUrBUrzOxYMNFdQ6FGfigGGtEVBPJx07BQeJekjt9DmH1FtZek4E9rE1eRR9qQpxACOQ==" } } } diff --git a/client/package.json b/client/package.json index 913788db..b7cb934a 100644 --- a/client/package.json +++ b/client/package.json @@ -10,36 +10,39 @@ }, "private": true, "dependencies": { - "@angular/animations": "^7.2.1", - "@angular/cdk": "^7.2.1", - "@angular/common": "^7.2.1", - "@angular/compiler": "^7.2.1", - "@angular/core": "^7.2.1", - "@angular/forms": "^7.2.1", - "@angular/http": "^7.2.1", - "@angular/material": "^7.2.1", - "@angular/platform-browser": "^7.2.1", - "@angular/platform-browser-dynamic": "^7.2.1", - "@angular/router": "^7.2.1", - "@ng-bootstrap/ng-bootstrap": "^4.0.1", - "bootstrap": "^4.1.3", - "core-js": "^2.6.2", + "@angular/animations": "^7.2.4", + "@angular/cdk": "^7.3.2", + "@angular/common": "^7.2.4", + "@angular/compiler": "^7.2.4", + "@angular/core": "^7.2.4", + "@angular/forms": "^7.2.4", + "@angular/http": "^7.2.4", + "@angular/material": "^7.3.2", + "@angular/platform-browser": "^7.2.4", + "@angular/platform-browser-dynamic": "^7.2.4", + "@angular/router": "^7.2.4", + "@ng-bootstrap/ng-bootstrap": "^4.0.3", + "bootstrap": "^4.3.1", + "core-js": "^2.6.4", + "cytoscape": "^3.4.1", + "cytoscape-edgehandles": "^3.5.0", "font-awesome": "^4.7.0", "hammerjs": "^2.0.8", "jquery": "^3.3.1", "material-design-icons": "^3.0.1", - "popper.js": "^1.14.6", - "rxjs": "^6.3.3", - "zone.js": "^0.8.28" + "popper.js": "^1.14.7", + "rxjs": "^6.4.0", + "zone.js": "^0.8.29" }, "devDependencies": { - "@angular-devkit/build-angular": "^0.12.2", - "@angular/cli": "^7.2.2", - "@angular/compiler-cli": "^7.2.1", - "@angular/language-service": "^7.2.1", - "@types/jasmine": "^3.3.5", + "@angular-devkit/build-angular": "^0.12.4", + "@angular/cli": "^7.3.1", + "@angular/compiler-cli": "^7.2.4", + "@angular/language-service": "^7.2.4", + "@types/cytoscape": "^3.2.9", + "@types/jasmine": "^3.3.9", "@types/jasminewd2": "^2.0.6", - "@types/node": "^10.12.18", + "@types/node": "^10.12.26", "codelyzer": "^4.5.0", "jasmine-core": "^3.3.0", "jasmine-spec-reporter": "^4.2.1", @@ -53,6 +56,6 @@ "protractor": "^5.4.1", "ts-node": "^7.0.1", "tslint": "^5.12.1", - "typescript": "^3.2.2" + "typescript": "^3.2.4" } } diff --git a/client/src/app/api/api/api.service.ts b/client/src/app/api/api/api.service.ts index 80eb0fb6..919c7e51 100644 --- a/client/src/app/api/api/api.service.ts +++ b/client/src/app/api/api/api.service.ts @@ -21,7 +21,7 @@ import {CustomHttpUrlEncodingCodec} from '../encoder'; import {Instance} from '../../model/models/instance'; import {SysInfo} from '../../model/models/sysInfo'; import { Inject, Injectable, Optional } from '@angular/core'; -import { HttpClient, HttpHeaders, HttpParams, HttpEvent } from '@angular/common/http'; +import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; import { Observable } from 'rxjs'; import { Configuration } from '../configuration'; import { @@ -35,7 +35,8 @@ import { PAUSE_INSTANCE, RESUME_INSTANCE, DELETE_INSTANCE, - INSTANCE_NETWORK + INSTANCE_NETWORK, + RECONNECT } from '../variables'; @@ -69,6 +70,19 @@ export class ApiService { return this.get>(INSTANCE_NETWORK); } + public postReconnect(from: number, to: number) { + + let queryParam = new HttpParams({ encoder: new CustomHttpUrlEncodingCodec() }); + + if (from === null || to === undefined) { + throw new Error('Parameter to or from not given'); + } else { + queryParam = queryParam.set('from', from); + queryParam = queryParam.set('to', to); + } + + return this.commonConf(RECONNECT, queryParam); + } /** * Find number of running instances * How many instances per type are running diff --git a/client/src/app/api/variables.ts b/client/src/app/api/variables.ts index a618d11f..b328d20c 100644 --- a/client/src/app/api/variables.ts +++ b/client/src/app/api/variables.ts @@ -29,6 +29,7 @@ export const STOP_INSTANCE = 'api/stopInstance'; export const PAUSE_INSTANCE = 'api/pauseInstance'; export const RESUME_INSTANCE = 'api/resumeInstance'; export const DELETE_INSTANCE = 'api/deleteInstance'; +export const RECONNECT = 'api/reconnectInstance'; export const COLLECTION_FORMATS = { 'csv': ',', 'tsv': ' ', diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index daf5b82e..2e3b6b78 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts @@ -26,11 +26,12 @@ import { HttpClientModule } from '@angular/common/http'; import { HelpComponent } from './help/help.component'; + @NgModule({ declarations: [ AppComponent, LandingPageComponent, - HelpComponent, + HelpComponent ], imports: [ DashboardModule, diff --git a/client/src/app/dashboard/add-dialog/add-dialog.component.html b/client/src/app/dashboard/add-dialog/add-dialog.component.html index 2c92b594..0ee36054 100644 --- a/client/src/app/dashboard/add-dialog/add-dialog.component.html +++ b/client/src/app/dashboard/add-dialog/add-dialog.component.html @@ -12,5 +12,5 @@

Add Instance

- + diff --git a/client/src/app/dashboard/add-dialog/add-dialog.component.spec.ts b/client/src/app/dashboard/add-dialog/add-dialog.component.spec.ts index a9014064..9cc47157 100644 --- a/client/src/app/dashboard/add-dialog/add-dialog.component.spec.ts +++ b/client/src/app/dashboard/add-dialog/add-dialog.component.spec.ts @@ -46,7 +46,7 @@ describe('AddDialogComponent', () => { }); it('should check for button inside the Add dialog', () => { - component.onCloseCancle(); + component.onCloseCancel(); expect(mockDialogRef.close).toHaveBeenCalled(); }); }); diff --git a/client/src/app/dashboard/add-dialog/add-dialog.component.ts b/client/src/app/dashboard/add-dialog/add-dialog.component.ts index e8932455..4ed4c4da 100644 --- a/client/src/app/dashboard/add-dialog/add-dialog.component.ts +++ b/client/src/app/dashboard/add-dialog/add-dialog.component.ts @@ -33,8 +33,8 @@ export class AddDialogComponent implements OnInit { }); } - onCloseCancle() { - this.thisDialogRef.close('CancleAdd'); + onCloseCancel() { + this.thisDialogRef.close('CancelAdd'); } diff --git a/client/src/app/dashboard/crawler/crawler.component.html b/client/src/app/dashboard/crawler/crawler.component.html index cbc456cd..fc6e7590 100644 --- a/client/src/app/dashboard/crawler/crawler.component.html +++ b/client/src/app/dashboard/crawler/crawler.component.html @@ -16,9 +16,7 @@ ~ limitations under the License. --> -

Crawler Details

+

Crawler Details

-
- -
+ diff --git a/client/src/app/dashboard/crawler/crawler.component.spec.ts b/client/src/app/dashboard/crawler/crawler.component.spec.ts index decc9a53..6f85c9cf 100644 --- a/client/src/app/dashboard/crawler/crawler.component.spec.ts +++ b/client/src/app/dashboard/crawler/crawler.component.spec.ts @@ -17,38 +17,28 @@ */ import { async, ComponentFixture, TestBed, inject } from '@angular/core/testing'; -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; -import { HttpClientModule } from '@angular/common/http'; import { BrowserAnimationsModule} from '@angular/platform-browser/animations'; -import { MatTableModule, MatInputModule, MatPaginatorModule} from '@angular/material'; -import { MatFormFieldModule} from '@angular/material/form-field'; -import { MatCheckboxModule} from '@angular/material/checkbox'; -import { MatIconModule} from '@angular/material/icon'; -import { MatDialogModule} from '@angular/material/dialog'; -import { TableAllComponent } from '../table-all/table-all.component'; import { CrawlerComponent } from './crawler.component'; -import { ModelService } from 'src/app/model/model.service'; - describe('CrawlerComponent', () => { let fixture: ComponentFixture; + let component: CrawlerComponent; beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ CrawlerComponent, TableAllComponent], - imports: [HttpClientTestingModule, HttpClientModule, BrowserAnimationsModule, - MatTableModule, MatInputModule, MatFormFieldModule, MatCheckboxModule, MatPaginatorModule, MatIconModule, MatDialogModule] + declarations: [ CrawlerComponent], + imports: [BrowserAnimationsModule] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(CrawlerComponent); + component = fixture.componentInstance; fixture.detectChanges(); }); - it(`should create`, async(inject([HttpTestingController, ModelService], - (modelService: ModelService) => { - expect(modelService).toBeTruthy(); - }))); + it('should create', () => { + expect(component).toBeTruthy(); + }); }); diff --git a/client/src/app/dashboard/crawler/crawler.component.ts b/client/src/app/dashboard/crawler/crawler.component.ts index feac89b9..5bd6c2b9 100644 --- a/client/src/app/dashboard/crawler/crawler.component.ts +++ b/client/src/app/dashboard/crawler/crawler.component.ts @@ -17,8 +17,6 @@ */ import {Component, OnInit} from '@angular/core'; -import { Instance, ComponentType, ComponentTypeEnum} from '../../model/models/instance'; -import { ModelService } from 'src/app/model/model.service'; @Component({ @@ -28,18 +26,12 @@ import { ModelService } from 'src/app/model/model.service'; }) export class CrawlerComponent implements OnInit { - // this array is inserted into the table all component in the html code - tableData: Instance[]; - compType: ComponentType; + constructor() { - constructor(private modelService: ModelService) { - this.tableData = []; } ngOnInit() { - this.modelService.getObservableForInstances().subscribe(() => { - this.tableData = this.modelService.getComponentsByType(ComponentTypeEnum.Crawler); - }); + } } diff --git a/client/src/app/dashboard/dashboard-overview/dashboard-overview.component.css b/client/src/app/dashboard/dashboard-overview/dashboard-overview.component.css index a2712055..6d844934 100644 --- a/client/src/app/dashboard/dashboard-overview/dashboard-overview.component.css +++ b/client/src/app/dashboard/dashboard-overview/dashboard-overview.component.css @@ -16,3 +16,18 @@ * limitations under the License. */ + .alignHeading{ + text-align: center; + } + + .mat-36 { + font-size: 36px ; + height: 36px ; + width: 36px ; + } + +.topSpace{ + margin-top:20px; +} + + diff --git a/client/src/app/dashboard/dashboard-overview/dashboard-overview.component.html b/client/src/app/dashboard/dashboard-overview/dashboard-overview.component.html index 3cdf236a..3ca94f48 100644 --- a/client/src/app/dashboard/dashboard-overview/dashboard-overview.component.html +++ b/client/src/app/dashboard/dashboard-overview/dashboard-overview.component.html @@ -17,20 +17,34 @@ -->
-
-
- +
+
+

System Overview

+
+
+ + +
-
- +
+
+
+ +
+
+
+
+
+ +
+
+ +
+
+
-
- -
-
-
-
- -
-
-
+
\ No newline at end of file diff --git a/client/src/app/dashboard/dashboard-overview/dashboard-overview.component.spec.ts b/client/src/app/dashboard/dashboard-overview/dashboard-overview.component.spec.ts index b02cd145..3fab2555 100644 --- a/client/src/app/dashboard/dashboard-overview/dashboard-overview.component.spec.ts +++ b/client/src/app/dashboard/dashboard-overview/dashboard-overview.component.spec.ts @@ -22,6 +22,9 @@ import { RouterTestingModule } from '@angular/router/testing'; import { StatusCardComponent } from '../status-card/status-card.component'; import { DashboardCardComponent } from '../dashboard-card/dashboard-card.component'; import { RouterModule } from '@angular/router'; +import { MaterialModule } from '../../material-module/material.module'; +import { InforCenterComponent } from '../infor-center/infor-center.component'; +import { GraphViewModule} from '../graph-view/graph-view.module'; import { DashboardOverviewComponent } from './dashboard-overview.component'; describe('DashboardOverviewComponent', () => { @@ -30,8 +33,8 @@ describe('DashboardOverviewComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ DashboardOverviewComponent, DashboardCardComponent, StatusCardComponent ], - imports: [RouterModule, HttpClientModule, RouterTestingModule] + declarations: [ DashboardOverviewComponent, DashboardCardComponent, StatusCardComponent, InforCenterComponent], + imports: [RouterModule, HttpClientModule, RouterTestingModule, MaterialModule, GraphViewModule] }) .compileComponents(); })); diff --git a/client/src/app/dashboard/dashboard-routing.module.ts b/client/src/app/dashboard/dashboard-routing.module.ts index 9b4e88de..eecf35c4 100644 --- a/client/src/app/dashboard/dashboard-routing.module.ts +++ b/client/src/app/dashboard/dashboard-routing.module.ts @@ -26,6 +26,10 @@ import {UserManagementComponent} from './user-management/user-management.compone import {WebApiComponent} from './webapi/web-api.component'; import {UserProfileComponent} from './user-profile/user-profile.component'; import {HelpComponent} from '../help/help.component'; +import { GraphViewComponent } from './graph-view/graph-view/graph-view.component'; +import { InstanceDetailsComponent } from './instance-details/instance-details.component'; +import { TableAllComponent } from './table-all/table-all.component'; +import { TableOverviewComponent } from './table-overview/table-overview.component'; const dashboardRoutes: Routes = [ @@ -49,6 +53,10 @@ const dashboardRoutes: Routes = [ path: 'webApi', component: WebApiComponent }, + { + path: 'tableOverview', + component: TableOverviewComponent + }, { path: 'userManagement', component: UserManagementComponent @@ -60,6 +68,14 @@ const dashboardRoutes: Routes = [ { path: 'help', component: HelpComponent + }, + { + path: 'graphView', + component: GraphViewComponent + }, + { + path: 'instanceDetails', + component: InstanceDetailsComponent } ] }]; diff --git a/client/src/app/dashboard/dashboard.component.css b/client/src/app/dashboard/dashboard.component.css index ec9d9cbd..e9ddfee6 100644 --- a/client/src/app/dashboard/dashboard.component.css +++ b/client/src/app/dashboard/dashboard.component.css @@ -16,11 +16,4 @@ * limitations under the License. */ -/*.custom_title{ - text-align: center; -} -.custom_img{ - display: block; - margin-left: auto; - margin-right: auto; -}*/ + diff --git a/client/src/app/dashboard/dashboard.module.ts b/client/src/app/dashboard/dashboard.module.ts index ffaaa202..b19a6076 100644 --- a/client/src/app/dashboard/dashboard.module.ts +++ b/client/src/app/dashboard/dashboard.module.ts @@ -38,6 +38,10 @@ import { AddDialogComponent } from './add-dialog/add-dialog.component'; import {ApiModule} from '../api/api.module'; import {ModelModule} from '../model/model.module'; import { MaterialModule } from '../material-module/material.module'; +import { GraphViewModule } from './graph-view/graph-view.module'; +import { InforCenterComponent } from './infor-center/infor-center.component'; +import { InstanceDetailsComponent } from './instance-details/instance-details.component'; +import { TableOverviewComponent } from './table-overview/table-overview.component'; @NgModule({ @@ -51,7 +55,8 @@ import { MaterialModule } from '../material-module/material.module'; DashboardRoutingModule, DashboardRoutingModule, ApiModule, - ModelModule + ModelModule, + GraphViewModule ], declarations: [ DashboardCardComponent, @@ -66,7 +71,10 @@ import { MaterialModule } from '../material-module/material.module'; UserProfileComponent, UserManagementComponent, DeleteDialogComponent, - AddDialogComponent + AddDialogComponent, + InforCenterComponent, + InstanceDetailsComponent, + TableOverviewComponent ], entryComponents: [ DeleteDialogComponent, AddDialogComponent diff --git a/client/src/app/dashboard/delete-dialog/delete-dialog.component.html b/client/src/app/dashboard/delete-dialog/delete-dialog.component.html index baa62f4b..958530e5 100644 --- a/client/src/app/dashboard/delete-dialog/delete-dialog.component.html +++ b/client/src/app/dashboard/delete-dialog/delete-dialog.component.html @@ -21,5 +21,5 @@

Delete Instance

Are you sure you want to delete "{{data.name}}" instance? - + diff --git a/client/src/app/dashboard/delete-dialog/delete-dialog.component.spec.ts b/client/src/app/dashboard/delete-dialog/delete-dialog.component.spec.ts index f9a765c8..cf246c3c 100644 --- a/client/src/app/dashboard/delete-dialog/delete-dialog.component.spec.ts +++ b/client/src/app/dashboard/delete-dialog/delete-dialog.component.spec.ts @@ -51,8 +51,8 @@ describe('DeleteDialogComponent', () => { expect(mockDialogRef.close).toHaveBeenCalled(); }); - it('should check for cancle button inside the Delete dialog', () => { - component.onCloseCancle(); + it('should check for cancel button inside the Delete dialog', () => { + component.onCloseCancel(); expect(mockDialogRef.close).toHaveBeenCalled(); }); }); diff --git a/client/src/app/dashboard/delete-dialog/delete-dialog.component.ts b/client/src/app/dashboard/delete-dialog/delete-dialog.component.ts index afad4dc0..40e4c7fb 100644 --- a/client/src/app/dashboard/delete-dialog/delete-dialog.component.ts +++ b/client/src/app/dashboard/delete-dialog/delete-dialog.component.ts @@ -19,8 +19,8 @@ export class DeleteDialogComponent implements OnInit { this.thisDialogRef.close('Confirm'); } - onCloseCancle() { - this.thisDialogRef.close('Cancle'); + onCloseCancel() { + this.thisDialogRef.close('Cancel'); } diff --git a/client/src/app/dashboard/graph-view/GraphConfig.ts b/client/src/app/dashboard/graph-view/GraphConfig.ts new file mode 100644 index 00000000..91b0f2cf --- /dev/null +++ b/client/src/app/dashboard/graph-view/GraphConfig.ts @@ -0,0 +1,106 @@ +export class GraphConfig { + + + public readonly layout = { + name: 'circle', + + fit: true, // whether to fit the viewport to the graph + padding: 30, // the padding on fit + boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } + avoidOverlap: true, // prevents node overlap, may overflow boundingBox and radius if not enough space + nodeDimensionsIncludeLabels: false, // Excludes the label when calculating node bounding boxes for the layout algorithm + spacingFactor: undefined, // Applies a multiplicative factor (>0) to expand or compress the overall area that the nodes take up + radius: undefined, // the radius of the circle + startAngle: 3 / 2 * Math.PI, // where nodes start in radians + sweep: undefined, // how many radians should be between the first and last node (defaults to full circle) + clockwise: true, // whether the layout should go clockwise (true) or counterclockwise/anticlockwise (false) + sort: undefined, // a sorting function to order the nodes; e.g. function(a, b){ return a.data('weight') - b.data('weight') } + animate: false, // whether to transition the node positions + animationDuration: 500, // duration of animation in ms if enabled + animationEasing: undefined, // easing of animation if enabled + animateFilter: function ( node, i ) { return true; }, + ready: undefined, // callback on layoutready + stop: undefined, // callback on layoutstop + transform: function (node, position ) { return position; } + }; + + public readonly cytoscapeConfig = { + container: null, // container to render in + elements: [ // list of graph elements to start with + ], + layout: this.layout, + style: [{ + selector: 'node[name][image]', + style: { + label: 'data(name)', + 'background-image': 'data(image)', + 'width': '70%', + 'height': '70%', + 'background-opacity': 0, + 'background-fit': 'contain', + 'background-clip': 'none', + } + }, { + selector: '.eh-handle', + style: { + 'background-image': '../../../assets/images/EdgeConnector.png', + 'width': '60%', + 'height': '60%', + 'background-opacity': 0, + 'border-width': 12, // makes the handle easier to hit + 'background-fit': 'contain', + 'background-clip': 'none', + 'border-opacity': 0 + } + }, + { + selector: '.eh-hover', + style: { + 'background-color': 'red' + } + }] + }; + + public readonly edgeDragConfig = { + preview: true, // whether to show added edges preview before releasing selection + hoverDelay: 150, // time spent hovering over a target node before it is considered selected + handleNodes: 'node[type != "ElasticSearch"]', // selector/filter function for whether edges can be made from a given node + snap: false, // when enabled, the edge can be drawn by just moving close to a target node (can be confusing on compound graphs) + snapThreshold: 50, // the target node must be less than or equal to this many pixels away from the cursor/finger + snapFrequency: 15, // the number of times per second (Hz) that snap checks done (lower is less expensive) + noEdgeEventsInDraw: false, // set events:no to edges during draws, prevents mouseouts on compounds + disableBrowserGestures: true, + handlePosition: function( node ) { + return 'middle top'; // sets the position of the handle in the format of "X-AXIS Y-AXIS" such as "left top", "middle top" + }, + handleInDrawMode: false, // whether to show the handle in draw mode + edgeType: function( sourceNode, targetNode ) { + // can return 'flat' for flat edges between nodes or 'node' for intermediate node between them + // returning null/undefined means an edge can't be added between the two nodes + return 'flat'; + }, + loopAllowed: function( node ) { + // for the specified node, return whether edges from itself to itself are allowed + return false; + }, + nodeLoopOffset: -50, // offset for edgeType: 'node' loops + nodeParams: function( sourceNode, targetNode ) { + // for edges between the specified source and target + // return element object to be passed to cy.add() for intermediary node + return {}; + }, + edgeParams: function( sourceNode, targetNode, i ) { + // for edges between the specified source and target + // return element object to be passed to cy.add() for edge + // NB: i indicates edge index in case of edgeType: 'node' + return {}; + }, + ghostEdgeParams: function() { + // return element object to be passed to cy.add() for the ghost edge + // (default classes are always added for you) + return {}; + } + }; + + constructor() {} +} diff --git a/client/src/app/dashboard/graph-view/connect-dialog/connect-dialog.component.css b/client/src/app/dashboard/graph-view/connect-dialog/connect-dialog.component.css new file mode 100644 index 00000000..e69de29b diff --git a/client/src/app/dashboard/graph-view/connect-dialog/connect-dialog.component.html b/client/src/app/dashboard/graph-view/connect-dialog/connect-dialog.component.html new file mode 100644 index 00000000..fa78d476 --- /dev/null +++ b/client/src/app/dashboard/graph-view/connect-dialog/connect-dialog.component.html @@ -0,0 +1,8 @@ +

Reconnect Components

+
+

Do you really want to disconnect {{data.nameOne}} from {{data.nameTwo}} and connect it to {{data.nameThree}}?

+
+
+ + +
\ No newline at end of file diff --git a/client/src/app/dashboard/graph-view/connect-dialog/connect-dialog.component.spec.ts b/client/src/app/dashboard/graph-view/connect-dialog/connect-dialog.component.spec.ts new file mode 100644 index 00000000..a34a2571 --- /dev/null +++ b/client/src/app/dashboard/graph-view/connect-dialog/connect-dialog.component.spec.ts @@ -0,0 +1,32 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ConnectDialogComponent } from './connect-dialog.component'; +import { MaterialModule } from 'src/app/material-module/material.module'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; + +describe('ConnectDialogComponent', () => { + let component: ConnectDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ConnectDialogComponent ], + imports: [MaterialModule], + providers: [ { + provide: MAT_DIALOG_DATA, + useValue: {} + }] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ConnectDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/client/src/app/dashboard/graph-view/connect-dialog/connect-dialog.component.ts b/client/src/app/dashboard/graph-view/connect-dialog/connect-dialog.component.ts new file mode 100644 index 00000000..26959f47 --- /dev/null +++ b/client/src/app/dashboard/graph-view/connect-dialog/connect-dialog.component.ts @@ -0,0 +1,16 @@ +import { Component, OnInit, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA } from '@angular/material'; + +@Component({ + selector: 'app-connect-dialog', + templateUrl: './connect-dialog.component.html', + styleUrls: ['./connect-dialog.component.css'] +}) +export class ConnectDialogComponent implements OnInit { + + constructor(@Inject(MAT_DIALOG_DATA) public data: {nameOne: string, nameTwo: string, nameThree: string}) { } + + ngOnInit() { + } + +} diff --git a/client/src/app/dashboard/graph-view/graph-view.module.ts b/client/src/app/dashboard/graph-view/graph-view.module.ts new file mode 100755 index 00000000..59e8e455 --- /dev/null +++ b/client/src/app/dashboard/graph-view/graph-view.module.ts @@ -0,0 +1,23 @@ +import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {GraphViewComponent} from './graph-view/graph-view.component'; +import {GraphViewService} from './graph-view.service'; +import {ModelModule} from '../../model/model.module'; +import { ConnectDialogComponent } from './connect-dialog/connect-dialog.component'; +import { MaterialModule } from 'src/app/material-module/material.module'; + +@NgModule({ + declarations: [GraphViewComponent, ConnectDialogComponent], + imports: [ + CommonModule, + MaterialModule, + ModelModule + ], + exports: [GraphViewComponent], + providers: [GraphViewService], + entryComponents: [ + ConnectDialogComponent + ] +}) +export class GraphViewModule { +} diff --git a/client/src/app/dashboard/graph-view/graph-view.service.spec.ts b/client/src/app/dashboard/graph-view/graph-view.service.spec.ts new file mode 100755 index 00000000..8b2a7c44 --- /dev/null +++ b/client/src/app/dashboard/graph-view/graph-view.service.spec.ts @@ -0,0 +1,15 @@ +import {TestBed} from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import {GraphViewService} from './graph-view.service'; + +describe('GraphViewService', () => { + beforeEach(() => TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [GraphViewService] + })); + + it('should be created', () => { + const service: GraphViewService = TestBed.get(GraphViewService); + expect(service).toBeTruthy(); + }); +}); diff --git a/client/src/app/dashboard/graph-view/graph-view.service.ts b/client/src/app/dashboard/graph-view/graph-view.service.ts new file mode 100755 index 00000000..b643647e --- /dev/null +++ b/client/src/app/dashboard/graph-view/graph-view.service.ts @@ -0,0 +1,148 @@ +import {Injectable} from '@angular/core'; +import {ModelService, InstanceChange} from '../../model/model.service'; +import { Instance } from 'src/app/model/models/instance'; +import { BehaviorSubject, Observable, Subject} from 'rxjs'; +import * as cytoscape from 'cytoscape'; +import { InstanceLink } from 'src/app/model/models/instanceLink'; +import { Actions, StoreService } from 'src/app/model/store.service'; +import { GraphConfig } from './GraphConfig'; +import { ApiService } from 'src/app/api/api/api.service'; + +interface NodeEdgeMap { + nodes: Array; + edges: Array; +} +export interface ElementUpdate { + type: Actions; + elements: Array; +} +// 'Running' | 'Failed' | 'Stopped' | 'Paused' | 'NotReachable'; +const TYPE_TO_IMG = { + 'Crawler': { + 'Running': '../../../assets/images/crawler-running.png', + 'Failed' : '../../../assets/images/crawler-failed.png', + 'Stopped' : '../../../assets/images/crawler-stopped.png', + 'Paused' : '../../../assets/images/crawler-paused.png', + 'NotReachable' : '../../../assets/images/crawler-failed.png', + 'Unknown' : '../../../assets/images/crawler.png', + }, + 'WebApp': { + 'Running': '../../../assets/images/webapp-running.png', + 'Failed' : '../../../assets/images/webapp-failed.png', + 'Stopped' : '../../../assets/images/webapp-stopped.png', + 'Paused' : '../../../assets/images/webapp-paused.png', + 'NotReachable' : '../../../assets/images/webapp-failed.png', + 'Unknown' : '../../../assets/images/webapp.png', }, + 'WebApi': { + 'Running': '../../../assets/images/webapi-running.png', + 'Failed' : '../../../assets/images/webapi-failed.png', + 'Stopped' : '../../../assets/images/webapi-stopped.png', + 'Paused' : '../../../assets/images/webapi-paused.png', + 'NotReachable' : '../../../assets/images/webapi-failed.png', + 'Unknown' : '../../../assets/images/webapi.png', }, + 'ElasticSearch': { + 'Running': '../../../assets/images/elasticsearch-running.png', + 'Failed' : '../../../assets/images/elasticsearch-failed.png', + 'Stopped' : '../../../assets/images/elasticsearch-stopped.png', + 'Paused' : '../../../assets/images/elasticsearch-paused.png', + 'NotReachable' : '../../../assets/images/elasticsearch-failed.png', + 'Unknown' : '../../../assets/images/elasticsearch.png'} , +}; + + +@Injectable({ + providedIn: 'root' +}) +export class GraphViewService { + private elementProvider: BehaviorSubject; + private elementRemover: Subject>; + + constructor(private modelService: ModelService, private apiService: ApiService, private storeService: StoreService) { + this.elementProvider = new BehaviorSubject({type: Actions.NONE, elements: []}); + this.elementRemover = new BehaviorSubject>([]); + + this.modelService.getObservableForInstances().subscribe((change: InstanceChange) => { + console.log('received notification in graph view service', change); + if (change.elements !== undefined) { + if (change.type === Actions.DELETE) { + this.removeElements(change.elements); + } else { + this.handleElements(change.type, change.elements); + } + } + }); + } + + public reconnect(from: string, to: string) { + console.log('trying to reconnect', from, to); + this.apiService.postReconnect(Number(from), Number(to)).subscribe((res) => { + console.log('reconnect returned with result', res); + }); + } + + private removeElements(instances: Array) { + const ids = instances.map((value: Instance) => '' + value.id); + this.elementRemover.next(ids); + } + + private handleElements(type: Actions, instances: Array) { + console.log('received new instance in graph view service', instances); + const eles: Array = this.createCytoscapeElements(instances); + console.log('parsed instance to eles', eles); + this.elementProvider.next({type: type, elements: eles}); + } + + private createCytoscapeElements(instances: Array): Array { + const newElements = instances.reduce( + ( accumulator: NodeEdgeMap, value: Instance) => { + let img = TYPE_TO_IMG[value.componentType][value.instanceState]; + if (img === undefined) { + img = TYPE_TO_IMG[value.componentType]['Unknown']; + } + accumulator.nodes.push({ + group: 'nodes', + data: { + id: '' + value.id, + type: value.componentType, + name: value.name, + image: img, + status: value.instanceState + } + }); + + const outEdges: Array = this.mapLinksToEdges(value.linksFrom); + const inEdges: Array = this.mapLinksToEdges(value.linksTo); + accumulator.edges = accumulator.edges.concat(inEdges.concat(outEdges)); + + return accumulator; + }, {nodes: [], edges: []} + ); + return newElements.nodes.concat(newElements.edges); + } + + private mapLinksToEdges(links: Array): Array { + const edges: Array = links.map((edgeVal) => { + return {data: {id: edgeVal.idFrom + '_' + edgeVal.idTo, source: edgeVal.idFrom, target: edgeVal.idTo, status: edgeVal.linkState}}; + }); + return edges; + } + + public getGraphConfig() { + return new GraphConfig(); + } + + public getElementObservable(): Observable { + return new Observable((observer) => { + // calculate init value + const allInstances = Object.values(this.storeService.getState().instances); + console.log('all instances', allInstances); + const cyElements = this.createCytoscapeElements(allInstances); + observer.next({type: Actions.ADD, elements: cyElements}); + this.elementProvider.subscribe(observer); + }); + } + + public getElementRemoveObservable(): Observable> { + return this.elementRemover.asObservable(); + } +} diff --git a/client/src/app/dashboard/graph-view/graph-view/graph-view.component.css b/client/src/app/dashboard/graph-view/graph-view/graph-view.component.css new file mode 100755 index 00000000..6b4f4967 --- /dev/null +++ b/client/src/app/dashboard/graph-view/graph-view/graph-view.component.css @@ -0,0 +1,6 @@ +#cy { + height: 100%; + width: 100%; + display: block; + position: absolute; +} diff --git a/client/src/app/dashboard/graph-view/graph-view/graph-view.component.html b/client/src/app/dashboard/graph-view/graph-view/graph-view.component.html new file mode 100755 index 00000000..30cbf77e --- /dev/null +++ b/client/src/app/dashboard/graph-view/graph-view/graph-view.component.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/client/src/app/dashboard/graph-view/graph-view/graph-view.component.spec.ts b/client/src/app/dashboard/graph-view/graph-view/graph-view.component.spec.ts new file mode 100755 index 00000000..faf40d1f --- /dev/null +++ b/client/src/app/dashboard/graph-view/graph-view/graph-view.component.spec.ts @@ -0,0 +1,38 @@ +import {async, ComponentFixture, TestBed} from '@angular/core/testing'; + +import {GraphViewComponent} from './graph-view.component'; +import { ApiModule } from 'src/app/api/api.module'; +import { HttpClientModule } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ConnectDialogComponent } from '../connect-dialog/connect-dialog.component'; +import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing'; +import { MaterialModule } from 'src/app/material-module/material.module'; + +describe('GraphViewComponent', () => { + let component: GraphViewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [GraphViewComponent], + imports: [HttpClientTestingModule, HttpClientModule, ApiModule, MaterialModule] + }) + .compileComponents(); + + TestBed.overrideModule(BrowserDynamicTestingModule, { + set: { + entryComponents: [ConnectDialogComponent], + } + }); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(GraphViewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/client/src/app/dashboard/graph-view/graph-view/graph-view.component.ts b/client/src/app/dashboard/graph-view/graph-view/graph-view.component.ts new file mode 100755 index 00000000..ab0966b1 --- /dev/null +++ b/client/src/app/dashboard/graph-view/graph-view/graph-view.component.ts @@ -0,0 +1,195 @@ +import {Component, ElementRef, OnInit, ViewChild, OnDestroy} from '@angular/core'; +import * as cytoscape from 'cytoscape'; +import edgehandles from 'cytoscape-edgehandles'; +import {GraphViewService, ElementUpdate} from '../graph-view.service'; +import { Actions } from 'src/app/model/store.service'; +import { LinkStateEnum } from 'src/app/model/models/instanceLink'; +import { MatDialog } from '@angular/material'; +import { ConnectDialogComponent } from '../connect-dialog/connect-dialog.component'; +import { ComponentTypeEnum, ComponentType } from 'src/app/model/models/instance'; +import { GraphConfig } from '../GraphConfig'; +import { Subscription } from 'rxjs'; + +@Component({ + selector: 'app-graph-view', + templateUrl: './graph-view.component.html', + styleUrls: ['./graph-view.component.css'] +}) +export class GraphViewComponent implements OnInit, OnDestroy { + @ViewChild('cy') cyDiv: ElementRef; + private cy: cytoscape.Core; + private config: GraphConfig; + private elementSubscription: Subscription; + private elementRemoveSubscription: Subscription; + + constructor(private graphViewService: GraphViewService, public dialog: MatDialog) { + + } + + + ngOnInit() { + + this.configureCytoscape(); + this.addEdgeDragListener(); + this.addChangeListener(); + } + + ngOnDestroy() { + this.cy.destroy(); + this.elementSubscription.unsubscribe(); + this.elementRemoveSubscription.unsubscribe(); + } + + /** + * Adds listeners to the graphViewService and describe how the + * view should be updated if it receives any changes / updates. + */ + private addChangeListener() { + this.elementSubscription = this.graphViewService.getElementObservable().subscribe((update: ElementUpdate) => { + if (update.elements) { + if (update.type === Actions.ADD) { + this.cy.add(update.elements); + } else { + if (update.type === Actions.CHANGE) { + this.updateElements(update.elements); + } + } + this.cy.edges().style('line-color', function (ele: cytoscape.EdgeSingular) { + const status = ele.data('status'); + switch (status) { + case LinkStateEnum.Assigned: return 'green'; + case LinkStateEnum.Failed: return 'red'; + case LinkStateEnum.Outdated: return 'grey'; + default: return 'orange'; + } + }); + const layout = this.cy.layout(this.config.layout); + layout.run(); + } + }); + + this.elementRemoveSubscription = this.graphViewService.getElementRemoveObservable().subscribe((ids: Array) => { + if (ids) { + for (let i = 0; i < ids.length; i++) { + this.cy.remove(this.cy.getElementById(ids[i])); + } + } + }); + } + + /** + * Configures the components behavior when an edge is draged from one + * node to another. + */ + private addEdgeDragListener() { + let removedElements: cytoscape.CollectionReturnValue; + + (this.cy as any).on('ehstop', (event: any, sourceNode: cytoscape.NodeSingular) => { + this.cy.add(removedElements); + }); + + (this.cy as any).on('ehcomplete', + (event: any, sourceNode: cytoscape.NodeSingular, targetNode: cytoscape.NodeSingular, addedEles: any) => { + + const edgesSource = sourceNode.connectedEdges(); + + const nodeToDisconnect = this.getNodeToDisconnect(edgesSource, sourceNode, targetNode); + const nodeName = nodeToDisconnect.reduce((prevVal, ele) => { + return ele.data('name'); + }, ''); + const dialogRef = this.dialog.open(ConnectDialogComponent, { data: { + nameOne: sourceNode.data('name'), + nameTwo: nodeName, + nameThree: targetNode.data('name')}}); + dialogRef.afterClosed().subscribe((reconnect: boolean) => { + if (reconnect) { + this.graphViewService.reconnect(sourceNode.data('id'), targetNode.data('id')); + } + this.cy.remove(addedEles); + }); + }); + + (this.cy as any).on('ehstart', (event: any, sourceNode: cytoscape.NodeSingular) => { + + const allElesToHide = this.getCorrespondingEles(sourceNode); + console.log('all eles to hide', allElesToHide); + // we want to show the source node. + if (allElesToHide.length > 0) { + const elesToHide = allElesToHide.symmetricDifference(sourceNode); + console.log('eles to actually hide', elesToHide); + removedElements = elesToHide.remove(); + } + + }); + } + + private getNodeToDisconnect(edgeList: cytoscape.EdgeCollection, sourceNode: cytoscape.NodeSingular, + targetNode: cytoscape.NodeSingular ): cytoscape.NodeSingular { + console.log('edge list', edgeList); + const nodes = edgeList.connectedNodes(); + console.log('connected nodes', nodes); + const correspondingEles = this.getCorrespondingEles(sourceNode, nodes); + + const actualElement = nodes.symmetricDifference(correspondingEles).symmetricDifference(targetNode); + if (actualElement.length > 1) { + console.log('invalid element:', actualElement); + throw new Error('Invalid node collection returned'); + } + + return actualElement; + } + + private getCorrespondingEles(node: cytoscape.NodeSingular, eles?: cytoscape.NodeCollection): cytoscape.NodeCollection { + const type = node.data('type'); + let result: cytoscape.NodeCollection = this.cy.collection(); + switch (type) { + case ComponentTypeEnum.WebApi: + result = this.getElementsWithDifferentType(ComponentTypeEnum.ElasticSearch, eles); + break; + case ComponentTypeEnum.WebApp: + result = this.getElementsWithDifferentType(ComponentTypeEnum.WebApi, eles); + break; + case ComponentTypeEnum.Crawler: + result = this.getElementsWithDifferentType(ComponentTypeEnum.ElasticSearch, eles); + break; + } + return result; + } + /** + * Returns all elements to hide except those + * of the given @param type. + */ + private getElementsWithDifferentType(type: ComponentType, eles?: cytoscape.NodeCollection): cytoscape.NodeCollection { + console.log('type', type); + console.log('eles', eles); + return eles ? eles.nodes('node[type !="' + type + '"]') : this.cy.nodes('node[type !="' + type + '"]'); + } + + private updateElements(elements: Array) { + for (const element of elements) { + // if element with id is not in cytoscape just add it + const cyElement = this.cy.getElementById(element.data.id); + if (cyElement.length === 0) { + this.cy.add(element); + } else { // else get the element and udpate it's data field + cyElement.data(element.data); + } + } + } + + + /** + * Initializes cytoscape and registers the edge drag and drop extension. + */ + private configureCytoscape() { + this.config = this.graphViewService.getGraphConfig(); + this.config.cytoscapeConfig.container = this.cyDiv.nativeElement; + + this.cy = cytoscape(this.config.cytoscapeConfig); + if (!Object.getPrototypeOf(this.cy).edgehandles) { + cytoscape.use(edgehandles); + } + + (this.cy as any).edgehandles(this.config.edgeDragConfig); + } +} diff --git a/client/src/app/dashboard/header/header.component.html b/client/src/app/dashboard/header/header.component.html index 8884bbee..21709605 100644 --- a/client/src/app/dashboard/header/header.component.html +++ b/client/src/app/dashboard/header/header.component.html @@ -21,17 +21,16 @@ diff --git a/client/src/app/dashboard/infor-center/infor-center.component.css b/client/src/app/dashboard/infor-center/infor-center.component.css new file mode 100644 index 00000000..00b168b9 --- /dev/null +++ b/client/src/app/dashboard/infor-center/infor-center.component.css @@ -0,0 +1,3 @@ +.infoCenter{ + border: 1px solid black; +} \ No newline at end of file diff --git a/client/src/app/dashboard/infor-center/infor-center.component.html b/client/src/app/dashboard/infor-center/infor-center.component.html new file mode 100644 index 00000000..50c27773 --- /dev/null +++ b/client/src/app/dashboard/infor-center/infor-center.component.html @@ -0,0 +1,20 @@ + + +

Information Center

+
In a future version a notification center will be shown here.
\ No newline at end of file diff --git a/client/src/app/dashboard/infor-center/infor-center.component.spec.ts b/client/src/app/dashboard/infor-center/infor-center.component.spec.ts new file mode 100644 index 00000000..f7f2d18c --- /dev/null +++ b/client/src/app/dashboard/infor-center/infor-center.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InforCenterComponent } from './infor-center.component'; + +describe('InforCenterComponent', () => { + let component: InforCenterComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ InforCenterComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(InforCenterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/client/src/app/dashboard/dashboard-routing.module.spec.ts b/client/src/app/dashboard/infor-center/infor-center.component.ts similarity index 66% rename from client/src/app/dashboard/dashboard-routing.module.spec.ts rename to client/src/app/dashboard/infor-center/infor-center.component.ts index 124ed561..765d953a 100644 --- a/client/src/app/dashboard/dashboard-routing.module.spec.ts +++ b/client/src/app/dashboard/infor-center/infor-center.component.ts @@ -15,17 +15,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { Component, OnInit, Input, ViewChild } from '@angular/core'; +import { MatTableDataSource } from '@angular/material'; -import { DashboardRoutingModule } from './dashboard-routing.module'; +@Component({ + selector: 'app-infor-center', + templateUrl: './infor-center.component.html', + styleUrls: ['./infor-center.component.css'] +}) -describe('DashboardRoutingModule', () => { - let dashboardRoutingModule: DashboardRoutingModule; +export class InforCenterComponent implements OnInit { - beforeEach(() => { - dashboardRoutingModule = new DashboardRoutingModule(); - }); + constructor() { + } - it('should create an instance', () => { - expect(dashboardRoutingModule).toBeTruthy(); - }); -}); + ngOnInit() { + } + +} diff --git a/client/src/app/dashboard/instance-details/instance-details.component.css b/client/src/app/dashboard/instance-details/instance-details.component.css new file mode 100644 index 00000000..835361eb --- /dev/null +++ b/client/src/app/dashboard/instance-details/instance-details.component.css @@ -0,0 +1,25 @@ + +.bgOperation{ + border-style: solid; + border-width:1px 1px 1px 1px; + width: 100%; + height: 80%; + overflow: auto; + padding:5px; +} +.detailsFormat{ + border-style: solid; + border-width:1px 1px 1px 1px; +} +.alignTitle{ + text-align: center; +} +.rowAlign{ + margin-left: 10px; +} +.titleAlign{ + margin-left:10px; +} +.detailAlign{ + margin-top:15px; +} \ No newline at end of file diff --git a/client/src/app/dashboard/instance-details/instance-details.component.html b/client/src/app/dashboard/instance-details/instance-details.component.html new file mode 100644 index 00000000..c7923a0d --- /dev/null +++ b/client/src/app/dashboard/instance-details/instance-details.component.html @@ -0,0 +1,77 @@ + + +
+
+

Title

+
+
+
+ +
+

Instance Details

+
+ +

Name:

+
+ +

Instance name goes here!!

+
+ +

Host:

+
+ +

Host name goes here!!

+
+ +

Port Number:

+
+ +

Port number goes here!!

+
+ +

DockerId:

+
+ +

Docker Id goes here!!

+
+ +

Labels:

+
+ +

Labels goes here!!

+
+
+
+
+

Operations

+
+

The console terminal data goes here!!

+
+
+
+
+ +
+

Notifications

+ +
+ +
+
\ No newline at end of file diff --git a/client/src/app/dashboard/instance-details/instance-details.component.spec.ts b/client/src/app/dashboard/instance-details/instance-details.component.spec.ts new file mode 100644 index 00000000..402840d4 --- /dev/null +++ b/client/src/app/dashboard/instance-details/instance-details.component.spec.ts @@ -0,0 +1,27 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { BrowserAnimationsModule} from '@angular/platform-browser/animations'; +import { MaterialModule } from '../../material-module/material.module'; +import { InstanceDetailsComponent } from './instance-details.component'; + +describe('InstanceDetailsComponent', () => { + let component: InstanceDetailsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ InstanceDetailsComponent ], + imports: [BrowserAnimationsModule, MaterialModule] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(InstanceDetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/client/src/app/dashboard/instance-details/instance-details.component.ts b/client/src/app/dashboard/instance-details/instance-details.component.ts new file mode 100644 index 00000000..29703dc0 --- /dev/null +++ b/client/src/app/dashboard/instance-details/instance-details.component.ts @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2018 The Delphi Team. + * See the LICENCE file distributed with this work for additional + * information regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Component, OnInit, Input, ViewChild } from '@angular/core'; +import { InstanceDetails } from '../../model/models/instance'; + + +@Component({ + selector: 'app-instance-details', + templateUrl: './instance-details.component.html', + styleUrls: ['./instance-details.component.css'] +}) +export class InstanceDetailsComponent implements OnInit { + + constructor() { } + + ngOnInit() { +} +} diff --git a/client/src/app/dashboard/table-all/table-all.component.css b/client/src/app/dashboard/table-all/table-all.component.css index b69bc1fb..6d1b7fd7 100644 --- a/client/src/app/dashboard/table-all/table-all.component.css +++ b/client/src/app/dashboard/table-all/table-all.component.css @@ -35,15 +35,15 @@ table { } .customWidthClass{ - width:350px; + width: 235px; } .mat-elevation-z8 { box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.2), 0px 8px 10px 1px rgba(0, 0, 0, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12); } -.customWidthClass_new{ - margin-left: 20px; -} +.customWidthClassNew{ + padding-right: 75px; + } .mat_cell{ border-bottom-width:0px; @@ -56,3 +56,34 @@ table { float:right; margin-top:15px; } + +.example-element-detail { + overflow: hidden; + display: flex; +} + +tr.example-detail-row { + height: 0; +} + +tr.example-element-row:not(.example-expanded-row):hover { + background: #f5f5f5; +} + +tr.example-element-row:not(.example-expanded-row):active { + background: #efefef; +} + +.example-element-row td { + border-bottom-width: 0; +} + +.customOutline { + outline: none; +} + +.tablepos{ + margin:30px; +} + + diff --git a/client/src/app/dashboard/table-all/table-all.component.html b/client/src/app/dashboard/table-all/table-all.component.html index c369d874..05039366 100644 --- a/client/src/app/dashboard/table-all/table-all.component.html +++ b/client/src/app/dashboard/table-all/table-all.component.html @@ -23,7 +23,7 @@ - + ID {{element.id}} @@ -49,36 +49,76 @@ {{element.instanceState}} - - Action - -
- Instance not hosted with docker. -
- - - - - - - -
+ + + + + + + + + Action + +
+ Instance not hosted with docker. +
+ + + + + + + +
- - - + +
+ + + + + + +
+
+ + + + + + + + + + + +
DockerID {{element.dockerId}} Labels {{element.labels}}
+ +
+
@@ -86,4 +126,4 @@
-
+ \ No newline at end of file diff --git a/client/src/app/dashboard/table-all/table-all.component.ts b/client/src/app/dashboard/table-all/table-all.component.ts index cd00ccec..9e35d17c 100644 --- a/client/src/app/dashboard/table-all/table-all.component.ts +++ b/client/src/app/dashboard/table-all/table-all.component.ts @@ -21,6 +21,7 @@ import { Component, OnInit, Input, ViewChild } from '@angular/core'; import { MatDialog, MatPaginator, MatTableDataSource } from '@angular/material'; import { DeleteDialogComponent } from '../delete-dialog/delete-dialog.component'; import { AddDialogComponent } from '../add-dialog/add-dialog.component'; +import { animate, state, style, transition, trigger } from '@angular/animations'; import { HttpEvent } from '@angular/common/http'; import { ModelService } from 'src/app/model/model.service'; import { ApiService } from 'src/app/api/api/api.service'; @@ -29,7 +30,14 @@ import { ApiService } from 'src/app/api/api/api.service'; @Component({ selector: 'app-table-all', templateUrl: './table-all.component.html', - styleUrls: ['./table-all.component.css'] + styleUrls: ['./table-all.component.css'], + animations: [ + trigger('detailExpand', [ + state('collapsed', style({ height: '0px', minHeight: '0', display: 'none' })), + state('expanded', style({ height: '*' })), + transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')), + ]), + ] }) export class TableAllComponent implements OnInit { @Input() type: Instance['componentType']; @@ -44,15 +52,17 @@ export class TableAllComponent implements OnInit { @Input() compType: string; - displayedColumns = ['ID', 'name', 'host', 'portNumber', 'instanceState', 'action']; + displayedColumns = ['ID', 'name', 'host', 'portNumber', 'instanceState', 'Details', 'action']; + columnsToDisplay: string[] = ['dockerId', 'labels']; dataSource: MatTableDataSource = new MatTableDataSource(this.dataArray); + data = new MatTableDataSource(); dialogResult: string; + expandedElement: Instance; + @ViewChild(MatPaginator) paginator: MatPaginator; constructor(public dialog: MatDialog, private apiService: ApiService, private modelService: ModelService) { } - @ViewChild(MatPaginator) paginator: MatPaginator; - ngOnInit() { this.dataSource.paginator = this.paginator; } @@ -76,7 +86,7 @@ export class TableAllComponent implements OnInit { console.log('data', this.dataSource.data); } else { - if (result !== 'Cancle') { + if (result !== 'Cancel') { this.apiService.deleteInstance(id).subscribe((deleteResult: HttpEvent) => { console.log('result', deleteResult); }, err => { @@ -109,7 +119,7 @@ export class TableAllComponent implements OnInit { }); dialogRef.afterClosed().subscribe(dialogResult => { - if (dialogResult === 'CancleAdd') { + if (dialogResult === 'CancelAdd') { dialogRef.close(); } else { console.log('dialogResult', dialogResult); @@ -171,7 +181,23 @@ export class TableAllComponent implements OnInit { }); } + /** + * Function used to expand table row and show details of Docker and Labels + */ + onRowClicked(row: Instance): Array<{dockerId: string, labels: string[]}> { + let filteredList: Array<{dockerId: string, labels: string[]}>; + const NoId = 'Id not available'; + const NoIdLabels = ['Labels not available']; + if (row.dockerId !== undefined && row.labels.length !== 0) { + filteredList = [{ dockerId: row.dockerId, labels: row.labels }]; + } else if (row.dockerId === undefined && row.labels.length !== 0) { + filteredList = [{ dockerId: NoId, labels: row.labels }]; + } else if (row.dockerId !== undefined && row.labels.length === 0) { + filteredList = [{ dockerId: row.dockerId, labels: NoIdLabels }]; + } else { + filteredList = [{ dockerId: NoId, labels: NoIdLabels }]; + } + return filteredList; } - - +} diff --git a/client/src/app/dashboard/table-overview/table-overview.component.css b/client/src/app/dashboard/table-overview/table-overview.component.css new file mode 100644 index 00000000..e69de29b diff --git a/client/src/app/dashboard/table-overview/table-overview.component.html b/client/src/app/dashboard/table-overview/table-overview.component.html new file mode 100644 index 00000000..85a8ef24 --- /dev/null +++ b/client/src/app/dashboard/table-overview/table-overview.component.html @@ -0,0 +1,32 @@ + + +
+

WebApi Details

+
+ +
+

Web Application Details

+
+ +
+

Crawler Details

+
+ +
+
diff --git a/client/src/app/dashboard/table-overview/table-overview.component.spec.ts b/client/src/app/dashboard/table-overview/table-overview.component.spec.ts new file mode 100644 index 00000000..cfd818b8 --- /dev/null +++ b/client/src/app/dashboard/table-overview/table-overview.component.spec.ts @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2018 The Delphi Team. + * See the LICENCE file distributed with this work for additional + * information regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { async, ComponentFixture, TestBed, inject } from '@angular/core/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { BrowserAnimationsModule} from '@angular/platform-browser/animations'; +import { TableOverviewComponent } from './table-overview.component'; +import { MaterialModule } from '../../material-module/material.module'; +import { TableAllComponent } from '../table-all/table-all.component'; +import { ModelService } from 'src/app/model/model.service'; + +describe('TableOverviewComponent', () => { + let component: TableOverviewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ TableOverviewComponent, TableAllComponent], + imports: [HttpClientTestingModule, HttpClientModule, BrowserAnimationsModule, + MaterialModule] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TableOverviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it(`should create`, async(inject([HttpTestingController, ModelService], + (modelService: ModelService) => { + expect(modelService).toBeTruthy(); + }))); +}); diff --git a/client/src/app/dashboard/table-overview/table-overview.component.ts b/client/src/app/dashboard/table-overview/table-overview.component.ts new file mode 100644 index 00000000..c2d109c6 --- /dev/null +++ b/client/src/app/dashboard/table-overview/table-overview.component.ts @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018 The Delphi Team. + * See the LICENCE file distributed with this work for additional + * information regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, OnInit, Input } from '@angular/core'; +import { ComponentTypeEnum, Instance} from '../../model/models/instance'; +import { ModelService } from 'src/app/model/model.service'; + +@Component({ + selector: 'app-table-overview', + templateUrl: './table-overview.component.html', + styleUrls: ['./table-overview.component.css'] +}) +export class TableOverviewComponent implements OnInit { + webapiDetails: Instance[]; + webappDetails: Instance[]; + crawlerDetails: Instance[]; +constructor(private modelService: ModelService) { + } + ngOnInit() { + /** * Function for getting all the instances of component type 'WebApi' using ModelService + * @param ComponentType */ + this.modelService.getObservableForInstances().subscribe(() => { + this.webapiDetails = this.modelService.getComponentsByType(ComponentTypeEnum.WebApi); + }); + + /** * Function for getting all the instances of component type 'WebApplication' using ModelService + * @param ComponentType */ + this.modelService.getObservableForInstances().subscribe(() => { + this.webappDetails = this.modelService.getComponentsByType(ComponentTypeEnum.WebApp); + }); + + /** * Function for getting all the instances of component type 'Crawler' using ModelService + * @param ComponentType */ + this.modelService.getObservableForInstances().subscribe(() => { + this.crawlerDetails = this.modelService.getComponentsByType(ComponentTypeEnum.Crawler); + }); + } +} diff --git a/client/src/app/dashboard/webapi/web-api.component.html b/client/src/app/dashboard/webapi/web-api.component.html index 1332fbd0..d7d2c4f8 100644 --- a/client/src/app/dashboard/webapi/web-api.component.html +++ b/client/src/app/dashboard/webapi/web-api.component.html @@ -1,6 +1,21 @@ -

Web API Details

+ + +

Web API Details

-
- -
-
\ No newline at end of file + diff --git a/client/src/app/dashboard/webapi/web-api.component.spec.ts b/client/src/app/dashboard/webapi/web-api.component.spec.ts index fee3b842..391f4df1 100644 --- a/client/src/app/dashboard/webapi/web-api.component.spec.ts +++ b/client/src/app/dashboard/webapi/web-api.component.spec.ts @@ -17,34 +17,29 @@ */ import { async, ComponentFixture, TestBed, inject } from '@angular/core/testing'; -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; -import { HttpClientModule } from '@angular/common/http'; import { BrowserAnimationsModule} from '@angular/platform-browser/animations'; -import { TableAllComponent } from '../table-all/table-all.component'; import { WebApiComponent } from './web-api.component'; -import { MaterialModule } from 'src/app/material-module/material.module'; -import { ModelService } from 'src/app/model/model.service'; - describe('WebApiComponent', () => { let fixture: ComponentFixture; + let component: WebApiComponent; beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ WebApiComponent, TableAllComponent ], - imports: [HttpClientTestingModule, HttpClientModule, BrowserAnimationsModule, - MaterialModule] + declarations: [ WebApiComponent ], + imports: [BrowserAnimationsModule, + ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(WebApiComponent); + component = fixture.componentInstance; fixture.detectChanges(); }); - it(`should create`, async(inject([HttpTestingController, ModelService], - (modelService: ModelService) => { - expect(modelService).toBeTruthy(); - }))); + it('should create', () => { + expect(component).toBeTruthy(); + }); }); diff --git a/client/src/app/dashboard/webapi/web-api.component.ts b/client/src/app/dashboard/webapi/web-api.component.ts index b7258944..18bf66f2 100644 --- a/client/src/app/dashboard/webapi/web-api.component.ts +++ b/client/src/app/dashboard/webapi/web-api.component.ts @@ -17,9 +17,6 @@ */ import {Component, OnInit} from '@angular/core'; -import {ComponentTypeEnum, Instance} from '../../model/models/instance'; -import { ModelService } from 'src/app/model/model.service'; - @Component({ selector: 'app-web-api', @@ -28,17 +25,10 @@ import { ModelService } from 'src/app/model/model.service'; }) export class WebApiComponent implements OnInit { - // this array is inserted into the table all component in the html code - tableData: Instance[]; - constructor(private modelService: ModelService) { } + constructor() { } ngOnInit() { - this.tableData = []; - - this.modelService.getObservableForInstances().subscribe(() => { - this.tableData = this.modelService.getComponentsByType(ComponentTypeEnum.WebApi); - }); } } diff --git a/client/src/app/dashboard/webapp/webapp.component.html b/client/src/app/dashboard/webapp/webapp.component.html index 3c389fad..0d3ed9d7 100644 --- a/client/src/app/dashboard/webapp/webapp.component.html +++ b/client/src/app/dashboard/webapp/webapp.component.html @@ -16,10 +16,7 @@ ~ limitations under the License. --> -

Web Application Details

+

Web Application Details

- -
- -
+ diff --git a/client/src/app/dashboard/webapp/webapp.component.spec.ts b/client/src/app/dashboard/webapp/webapp.component.spec.ts index 1c31ae77..6ea22636 100644 --- a/client/src/app/dashboard/webapp/webapp.component.spec.ts +++ b/client/src/app/dashboard/webapp/webapp.component.spec.ts @@ -17,34 +17,30 @@ */ import { async, ComponentFixture, TestBed, inject } from '@angular/core/testing'; -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; -import { HttpClientModule } from '@angular/common/http'; import { BrowserAnimationsModule} from '@angular/platform-browser/animations'; -import { TableAllComponent } from '../table-all/table-all.component'; import { WebappComponent } from './webapp.component'; -import { MaterialModule } from 'src/app/material-module/material.module'; -import { ModelService } from 'src/app/model/model.service'; - describe('WebappComponent', () => { let fixture: ComponentFixture; + let component: WebappComponent; + beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ WebappComponent, TableAllComponent], - imports: [HttpClientTestingModule, HttpClientModule, BrowserAnimationsModule, - MaterialModule] + declarations: [ WebappComponent], + imports: [BrowserAnimationsModule] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(WebappComponent); + component = fixture.componentInstance; fixture.detectChanges(); }); - it(`should create`, async(inject([HttpTestingController, ModelService], - (modelService: ModelService) => { - expect(modelService).toBeTruthy(); + it(`should create`, async(inject([], + () => { + expect(component).toBeTruthy(); }))); }); diff --git a/client/src/app/dashboard/webapp/webapp.component.ts b/client/src/app/dashboard/webapp/webapp.component.ts index 16857f19..d05af315 100644 --- a/client/src/app/dashboard/webapp/webapp.component.ts +++ b/client/src/app/dashboard/webapp/webapp.component.ts @@ -17,9 +17,6 @@ */ import {Component, OnInit} from '@angular/core'; -import {ComponentTypeEnum, Instance} from '../../model/models/instance'; -import { ModelService } from 'src/app/model/model.service'; - @Component({ selector: 'app-webapp', @@ -27,16 +24,8 @@ import { ModelService } from 'src/app/model/model.service'; styleUrls: ['./webapp.component.css'] }) export class WebappComponent implements OnInit { - // this array is inserted into the table all component in the html code - tableData: Instance[]; - constructor(private modelService: ModelService) { + constructor() { } - ngOnInit() { - this.tableData = []; - this.modelService.getObservableForInstances().subscribe(() => { - this.tableData = this.modelService.getComponentsByType(ComponentTypeEnum.WebApp); - }); } - } diff --git a/client/src/app/material-module/material.module.ts b/client/src/app/material-module/material.module.ts index 4875cb90..ae1f1691 100644 --- a/client/src/app/material-module/material.module.ts +++ b/client/src/app/material-module/material.module.ts @@ -10,6 +10,7 @@ import { MatInputModule, MatCheckboxModule, MatDialogModule, MatFormFieldModule} from '@angular/material'; +import {MatGridListModule} from '@angular/material/grid-list'; @NgModule({ declarations: [], @@ -24,7 +25,8 @@ import { MatInputModule, MatPaginatorModule, MatCheckboxModule, MatDialogModule, - MatFormFieldModule + MatFormFieldModule, + MatGridListModule ], exports: [ CommonModule, @@ -37,7 +39,8 @@ import { MatInputModule, MatPaginatorModule, MatCheckboxModule, MatDialogModule, - MatFormFieldModule + MatFormFieldModule, + MatGridListModule ] }) export class MaterialModule { } diff --git a/client/src/app/model/models/instance.ts b/client/src/app/model/models/instance.ts index 95d93335..d9364662 100644 --- a/client/src/app/model/models/instance.ts +++ b/client/src/app/model/models/instance.ts @@ -48,6 +48,12 @@ export interface Instance { labels?: Array; } +export interface InstanceDetails { + type: string; + time: string; + description: string; +} + export type StateEnum = 'Running' | 'Failed' | 'Stopped' | 'Paused' | 'NotReachable'; export const StateEnum = { Running: 'Running' as StateEnum, diff --git a/client/src/assets/images/EdgeConnector.png b/client/src/assets/images/EdgeConnector.png new file mode 100644 index 00000000..b4dc48a5 Binary files /dev/null and b/client/src/assets/images/EdgeConnector.png differ diff --git a/client/src/assets/images/crawler-failed.png b/client/src/assets/images/crawler-failed.png new file mode 100755 index 00000000..19a73b0b Binary files /dev/null and b/client/src/assets/images/crawler-failed.png differ diff --git a/client/src/assets/images/crawler-paused.png b/client/src/assets/images/crawler-paused.png new file mode 100755 index 00000000..662ad868 Binary files /dev/null and b/client/src/assets/images/crawler-paused.png differ diff --git a/client/src/assets/images/crawler-running.png b/client/src/assets/images/crawler-running.png new file mode 100755 index 00000000..91014a1c Binary files /dev/null and b/client/src/assets/images/crawler-running.png differ diff --git a/client/src/assets/images/crawler-stopped.png b/client/src/assets/images/crawler-stopped.png new file mode 100755 index 00000000..5326c78e Binary files /dev/null and b/client/src/assets/images/crawler-stopped.png differ diff --git a/client/src/assets/images/elasticsearch-failed.png b/client/src/assets/images/elasticsearch-failed.png new file mode 100755 index 00000000..bcc8bfbb Binary files /dev/null and b/client/src/assets/images/elasticsearch-failed.png differ diff --git a/client/src/assets/images/elasticsearch-paused.png b/client/src/assets/images/elasticsearch-paused.png new file mode 100755 index 00000000..454821e3 Binary files /dev/null and b/client/src/assets/images/elasticsearch-paused.png differ diff --git a/client/src/assets/images/elasticsearch-running.png b/client/src/assets/images/elasticsearch-running.png new file mode 100755 index 00000000..2b81d0cb Binary files /dev/null and b/client/src/assets/images/elasticsearch-running.png differ diff --git a/client/src/assets/images/elasticsearch-stopped.png b/client/src/assets/images/elasticsearch-stopped.png new file mode 100755 index 00000000..29f1c580 Binary files /dev/null and b/client/src/assets/images/elasticsearch-stopped.png differ diff --git a/client/src/assets/images/webapi-failed.png b/client/src/assets/images/webapi-failed.png new file mode 100755 index 00000000..9c722b48 Binary files /dev/null and b/client/src/assets/images/webapi-failed.png differ diff --git a/client/src/assets/images/webapi-paused.png b/client/src/assets/images/webapi-paused.png new file mode 100755 index 00000000..cc0bb4ec Binary files /dev/null and b/client/src/assets/images/webapi-paused.png differ diff --git a/client/src/assets/images/webapi-running.png b/client/src/assets/images/webapi-running.png new file mode 100755 index 00000000..ccbea6a6 Binary files /dev/null and b/client/src/assets/images/webapi-running.png differ diff --git a/client/src/assets/images/webapi-stopped.png b/client/src/assets/images/webapi-stopped.png new file mode 100755 index 00000000..7d658ba0 Binary files /dev/null and b/client/src/assets/images/webapi-stopped.png differ diff --git a/client/src/assets/images/webapp-failed.png b/client/src/assets/images/webapp-failed.png new file mode 100755 index 00000000..6bbfc441 Binary files /dev/null and b/client/src/assets/images/webapp-failed.png differ diff --git a/client/src/assets/images/webapp-paused.png b/client/src/assets/images/webapp-paused.png new file mode 100755 index 00000000..f5caafa8 Binary files /dev/null and b/client/src/assets/images/webapp-paused.png differ diff --git a/client/src/assets/images/webapp-running.png b/client/src/assets/images/webapp-running.png new file mode 100755 index 00000000..8d3a8cf9 Binary files /dev/null and b/client/src/assets/images/webapp-running.png differ diff --git a/client/src/assets/images/webapp-stopped.png b/client/src/assets/images/webapp-stopped.png new file mode 100755 index 00000000..c5b3fe5b Binary files /dev/null and b/client/src/assets/images/webapp-stopped.png differ diff --git a/conf/application.conf b/conf/application.conf index 4f66e17b..4d1a96d3 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -26,7 +26,7 @@ akka { # configuration at INFO level, including defaults and overrides, so it s worth # putting at the very top. # - # Put the following in your conf/logback.xml file: + # # # @@ -374,5 +374,10 @@ app.instanceRegistryUri = ${?INSTANCE_REGISTRY_URI} app.instanceRegistryBasePath = "localhost:8087" app.instanceRegistryBasePath = ${?INSTANCE_REGISTRY_BASE_PATH} +//JwtKey +play.http.secret.JWTkey="changeme" +play.http.instance="Management" + + include "silhouette.conf"