You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ak...@apache.org on 2016/06/27 03:23:04 UTC

[20/22] ignite git commit: Ignite Web Console beta2.

Ignite Web Console beta2.


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/541e17d0
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/541e17d0
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/541e17d0

Branch: refs/heads/master
Commit: 541e17d07520e5dac7daec1b188abe6bd12d6aad
Parents: e7ebe0a
Author: Alexey Kuznetsov <ak...@apache.org>
Authored: Mon Jun 27 10:17:45 2016 +0700
Committer: Alexey Kuznetsov <ak...@apache.org>
Committed: Mon Jun 27 10:17:46 2016 +0700

----------------------------------------------------------------------
 .../visor/compute/VisorGatewayTask.java         |    3 +-
 modules/schema-import/README.txt                |   12 +-
 modules/web-agent/README.txt                    |   25 +-
 .../web-agent/assembly/release-web-agent.xml    |    2 -
 modules/web-agent/bin/ignite-web-agent.bat      |    2 +-
 modules/web-agent/bin/ignite-web-agent.sh       |    2 +-
 modules/web-agent/pom.xml                       |   22 +-
 .../console/agent/AgentConfiguration.java       |   53 +-
 .../ignite/console/agent/AgentLauncher.java     |   21 +-
 .../ignite/console/demo/AgentClusterDemo.java   |   82 +-
 modules/web-console/README.txt                  |    4 +-
 modules/web-console/src/main/js/.eslintrc       |    7 +-
 .../src/main/js/app/data/getting-started.json   |    2 +-
 .../src/main/js/app/data/pom-dependencies.json  |   20 +
 .../directives/ui-ace-docker/ui-ace-docker.jade |    4 +-
 .../ui-ace-java/ui-ace-java.directive.js        |    4 +-
 .../directives/ui-ace-pojos/ui-ace-pojos.jade   |    4 +-
 .../ui-ace-xml/ui-ace-xml.directive.js          |    4 +-
 .../src/main/js/app/helpers/jade/mixins.jade    |   17 +-
 modules/web-console/src/main/js/app/index.js    |   18 +-
 .../src/main/js/app/modules/Demo/Demo.module.js |   14 +-
 .../QueryNotebooks/QueryNotebooks.provider.js   |  115 -
 .../main/js/app/modules/agent/agent.module.js   |  323 +++
 .../configuration/generator/Pom.service.js      |   46 +-
 .../form/field/input/datalist.directive.js      |    6 +-
 .../modules/form/field/input/text.directive.js  |    9 +-
 .../js/app/modules/form/field/input/text.jade   |    2 +-
 .../app/modules/form/group/group.directive.js   |    6 +-
 .../form/validator/java-keywords.directive.js   |    9 +-
 .../src/main/js/app/modules/loading/loading.css |    2 +-
 .../query-notebooks/query-notebooks.module.js   |  115 +
 .../app/modules/states/configuration.state.js   |   32 +-
 .../states/configuration/caches/general.jade    |    2 +-
 .../states/configuration/caches/store.jade      |   37 +-
 .../clusters/attributes.directive.js            |   27 +
 .../configuration/clusters/attributes.jade      |   58 +
 .../states/configuration/clusters/binary.jade   |   12 +-
 .../clusters/collision.directive.js             |   27 +
 .../configuration/clusters/collision.jade       |   60 +
 .../clusters/collision/custom.directive.js      |   27 +
 .../clusters/collision/custom.jade              |   24 +
 .../clusters/collision/fifo-queue.directive.js  |   27 +
 .../clusters/collision/fifo-queue.jade          |   28 +
 .../collision/job-stealing.directive.js         |   27 +
 .../clusters/collision/job-stealing.jade        |   64 +
 .../collision/priority-queue.directive.js       |   27 +
 .../clusters/collision/priority-queue.jade      |   43 +
 .../configuration/clusters/communication.jade   |   30 +-
 .../configuration/clusters/deployment.jade      |   31 +-
 .../configuration/clusters/discovery.jade       |    4 +-
 .../clusters/failover.directive.js              |   27 +
 .../states/configuration/clusters/failover.jade |   82 +
 .../states/configuration/clusters/general.jade  |   20 +-
 .../clusters/general/discovery/cloud.jade       |    6 +-
 .../clusters/general/discovery/google.jade      |   11 +-
 .../clusters/general/discovery/s3.jade          |    4 +-
 .../clusters/general/discovery/zookeeper.jade   |   21 +-
 .../bounded-exponential-backoff.jade            |    6 +-
 .../discovery/zookeeper/retrypolicy/custom.jade |    6 +-
 .../retrypolicy/exponential-backoff.jade        |    6 +-
 .../zookeeper/retrypolicy/forever.jade          |    2 +-
 .../zookeeper/retrypolicy/n-times.jade          |    2 +-
 .../zookeeper/retrypolicy/one-time.jade         |    2 +-
 .../zookeeper/retrypolicy/until-elapsed.jade    |    4 +-
 .../states/configuration/clusters/igfs.jade     |    4 +-
 .../configuration/clusters/logger.directive.js  |   27 +
 .../states/configuration/clusters/logger.jade   |   65 +
 .../clusters/logger/custom.directive.js         |   27 +
 .../configuration/clusters/logger/custom.jade   |   24 +
 .../clusters/logger/log4j.directive.js          |   27 +
 .../configuration/clusters/logger/log4j.jade    |   49 +
 .../clusters/logger/log4j2.directive.js         |   27 +
 .../configuration/clusters/logger/log4j2.jade   |   38 +
 .../states/configuration/domains/query.jade     |    4 +-
 .../states/configuration/domains/store.jade     |    2 +-
 .../modules/states/configuration/igfs/misc.jade |    2 +-
 .../main/js/app/modules/user/Auth.service.js    |   19 +-
 .../js/app/services/AgentMonitor.service.js     |  337 ---
 .../src/main/js/app/services/cleanup.service.js |    4 +-
 .../src/main/js/build/system.config.js          |   55 +-
 .../src/main/js/controllers/admin-controller.js |   14 +-
 .../main/js/controllers/caches-controller.js    |  336 ++-
 .../main/js/controllers/clusters-controller.js  |  537 +++--
 .../src/main/js/controllers/common-module.js    | 1052 +++++----
 .../main/js/controllers/domains-controller.js   |  847 ++++---
 .../src/main/js/controllers/igfs-controller.js  |  212 +-
 .../main/js/controllers/profile-controller.js   |   60 +-
 .../src/main/js/controllers/sql-controller.js   | 2173 +++++++++---------
 .../src/main/js/generator/generator-common.js   |  151 +-
 .../src/main/js/generator/generator-java.js     |  998 ++++----
 .../src/main/js/generator/generator-optional.js |    2 +-
 .../main/js/generator/generator-properties.js   |   25 +-
 .../src/main/js/generator/generator-readme.js   |    6 +-
 .../src/main/js/generator/generator-xml.js      |  430 +++-
 .../main/js/gulpfile.babel.js/tasks/eslint.js   |    3 +
 .../src/main/js/public/images/cluster.png       |  Bin 29670 -> 29376 bytes
 .../src/main/js/public/images/query-table.png   |  Bin 42253 -> 29189 bytes
 .../src/main/js/public/stylesheets/style.scss   |   28 +
 modules/web-console/src/main/js/serve/agent.js  |  222 +-
 .../web-console/src/main/js/serve/browser.js    |  120 +-
 .../web-console/src/main/js/serve/configure.js  |    1 +
 modules/web-console/src/main/js/serve/mongo.js  |   58 +-
 .../src/main/js/serve/routes/agent.js           |    3 +-
 .../src/main/js/serve/routes/caches.js          |    4 +-
 .../src/main/js/serve/routes/clusters.js        |    8 +-
 .../src/main/js/serve/routes/igfs.js            |    2 +-
 .../src/main/js/serve/routes/profile.js         |   11 +-
 .../src/main/js/serve/routes/public.js          |   35 +-
 .../main/js/views/configuration/clusters.jade   |    4 +
 .../js/views/configuration/domains-import.jade  |  325 +--
 .../src/main/js/views/settings/profile.jade     |   14 +-
 .../web-console/src/main/js/views/signin.jade   |   26 +-
 .../src/main/js/views/sql/cache-metadata.jade   |    2 +-
 .../web-console/src/main/js/views/sql/sql.jade  |  205 +-
 114 files changed, 6087 insertions(+), 4279 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/core/src/main/java/org/apache/ignite/internal/visor/compute/VisorGatewayTask.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/compute/VisorGatewayTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/compute/VisorGatewayTask.java
index c59d299..f1b22ff 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/visor/compute/VisorGatewayTask.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/compute/VisorGatewayTask.java
@@ -355,7 +355,8 @@ public class VisorGatewayTask implements ComputeTask<Object[], Object> {
                 }
             }
 
-            return ignite.compute().execute(taskName, new VisorTaskArgument<>(nids, jobArgs, false));
+            return ignite.compute(ignite.cluster().forNodeIds(nids))
+                .execute(taskName, new VisorTaskArgument<>(nids, jobArgs, false));
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/schema-import/README.txt
----------------------------------------------------------------------
diff --git a/modules/schema-import/README.txt b/modules/schema-import/README.txt
index d4f2dbf..31a8ff0 100644
--- a/modules/schema-import/README.txt
+++ b/modules/schema-import/README.txt
@@ -19,12 +19,12 @@ For example you may use the following script for create sample type 'Person' in
 
 create table PERSON(id integer not null PRIMARY KEY, first_name varchar(50), last_name varchar(50), salary double);
 
-insert into PERSON(id, first_name, first_name, salary) values(1, 'Johannes', 'Kepler', 1000);
-insert into PERSON(id, first_name, first_name, salary) values(2, 'Galileo', 'Galilei', 1200);
-insert into PERSON(id, first_name, first_name, salary) values(3, 'Henry', 'More', 1150);
-insert into PERSON(id, first_name, first_name, salary) values(4, 'Polish', 'Brethren', 2000);
-insert into PERSON(id, first_name, first_name, salary) values(5, 'Robert', 'Boyle', 2500);
-insert into PERSON(id, first_name, first_name, salary) values(6, 'Isaac', 'Newton', 1300);
+insert into PERSON(id, first_name, last_name, salary) values(1, 'Johannes', 'Kepler', 1000);
+insert into PERSON(id, first_name, last_name, salary) values(2, 'Galileo', 'Galilei', 1200);
+insert into PERSON(id, first_name, last_name, salary) values(3, 'Henry', 'More', 1150);
+insert into PERSON(id, first_name, last_name, salary) values(4, 'Polish', 'Brethren', 2000);
+insert into PERSON(id, first_name, last_name, salary) values(5, 'Robert', 'Boyle', 2500);
+insert into PERSON(id, first_name, last_name, salary) values(6, 'Isaac', 'Newton', 1300);
 
 The Ignite Schema Import utility generates the following artifacts:
  # Java POJO key and value classes (enter "org.apache.ignite.schema" package name before generation).

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-agent/README.txt
----------------------------------------------------------------------
diff --git a/modules/web-agent/README.txt b/modules/web-agent/README.txt
index 0d8d4ac..c6e625b 100644
--- a/modules/web-agent/README.txt
+++ b/modules/web-agent/README.txt
@@ -17,22 +17,23 @@ Configuration file:
   Should be a file with simple line-oriented format as described here: http://docs.oracle.com/javase/7/docs/api/java/util/Properties.html#load(java.io.Reader)
 
   Available entries names:
-    token
+    tokens
     server-uri
     node-uri
     driver-folder
 
   Example configuration file:
-    token=1a2b3c4d5f
-    serverURI=http://console.example.com:3001
+    tokens=1a2b3c4d5f,2j1s134d12
+    serverURI=https://console.example.com:3001
 
-Security token:
-  1) By default token will be included into downloaded agent zip.
-  2) You can get/change token in your profile.
+Security tokens:
+  1) By default security token of current user will be included into "default.properties" inside downloaded "ignite-web-agent-x.x.x.zip".
+  2) One can get/reset token in Web Console profile (https://<your_console_address>/settings/profile).
+  3) One may specify several comma separated tokens using configuration file or command line arguments of web agent.
 
 Ignite Web agent requirements:
-  1) Ignite node should be started with REST server (move ignite-rest-http folder from lib/optional/ to lib/).
-  2) Pass Ignite node REST server URI to agent.
+  1) In order to communicate with web agent Ignite node should be started with REST server (move ignite-rest-http folder from lib/optional/ to lib/).
+  2) Configure web agent serverURI property by Ignite node REST server URI.
 
 Options:
   -h, --help
@@ -47,15 +48,15 @@ Options:
   -s, --server-uri
      URI for connect to Ignite Web Console via web-socket protocol, default
      value: http://localhost:3001
-  -t, --token
-     User's security token
+  -t, --tokens
+     User's security tokens
 
 How to build:
   To build from sources run following command in Ignite project root folder:
   mvn clean package -pl :ignite-web-agent -am -P web-console -DskipTests=true
 
 Demo of Ignite Web Agent:
- In order to simplify evaluation demo mode was implemented. To start demo, you need to to click button "Start demo".
+ In order to simplify evaluation demo mode was implemented. To start demo, you need to click button "Start demo".
  New tab will be open with prepared demo data.
 
  1) Demo for import domain model from database.
@@ -73,7 +74,7 @@ Demo of Ignite Web Agent:
      In this mode internal Ignite node will be started. Cache created and populated with data.
        2.1) Click "SQL" in Ignite Web Console top menu.
        2.2) "Demo" notebook with preconfigured queries will be opened.
-       2.3) You can also execute any SQL queries for tables: "Country, Department, Employee", "Parking, Car".
+       2.3) You can also execute any SQL queries for tables: "Country, Department, Employee, Parking, Car".
 
  For example:
    2.4) Enter SQL statement:

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-agent/assembly/release-web-agent.xml
----------------------------------------------------------------------
diff --git a/modules/web-agent/assembly/release-web-agent.xml b/modules/web-agent/assembly/release-web-agent.xml
index eb7da95..aa85b59 100644
--- a/modules/web-agent/assembly/release-web-agent.xml
+++ b/modules/web-agent/assembly/release-web-agent.xml
@@ -42,7 +42,6 @@
         <fileSet>
             <directory>${basedir}/bin</directory>
             <outputDirectory>/</outputDirectory>
-            <filtered>true</filtered>
             <includes>
                 <include>**/*.bat</include>
             </includes>
@@ -50,7 +49,6 @@
         <fileSet>
             <directory>${basedir}/bin</directory>
             <outputDirectory>/</outputDirectory>
-            <filtered>true</filtered>
             <fileMode>0755</fileMode>
             <includes>
                 <include>**/*.sh</include>

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-agent/bin/ignite-web-agent.bat
----------------------------------------------------------------------
diff --git a/modules/web-agent/bin/ignite-web-agent.bat b/modules/web-agent/bin/ignite-web-agent.bat
index 5b3f24c..f16eb35 100644
--- a/modules/web-agent/bin/ignite-web-agent.bat
+++ b/modules/web-agent/bin/ignite-web-agent.bat
@@ -55,7 +55,7 @@ goto error_finish
 ::
 if "%JVM_OPTS%" == "" set JVM_OPTS=-Xms1g -Xmx1g -server -XX:+AggressiveOpts -XX:MaxPermSize=256m
 
-"%JAVA_HOME%\bin\java.exe" %JVM_OPTS% -cp ignite-web-agent-${version}.jar org.apache.ignite.console.agent.AgentLauncher  %*
+"%JAVA_HOME%\bin\java.exe" %JVM_OPTS% -cp "*" org.apache.ignite.console.agent.AgentLauncher  %*
 
 set JAVA_ERRORLEVEL=%ERRORLEVEL%
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-agent/bin/ignite-web-agent.sh
----------------------------------------------------------------------
diff --git a/modules/web-agent/bin/ignite-web-agent.sh b/modules/web-agent/bin/ignite-web-agent.sh
index 6d0a1b5..3f2c2bc 100644
--- a/modules/web-agent/bin/ignite-web-agent.sh
+++ b/modules/web-agent/bin/ignite-web-agent.sh
@@ -84,4 +84,4 @@ if [ -z "$JVM_OPTS" ] ; then
     JVM_OPTS="-Xms1g -Xmx1g -server -XX:+AggressiveOpts -XX:MaxPermSize=256m"
 fi
 
-"$JAVA" ${JVM_OPTS} -cp ignite-web-agent-${version}.jar org.apache.ignite.console.agent.AgentLauncher "$@"
+"$JAVA" ${JVM_OPTS} -cp "*" org.apache.ignite.console.agent.AgentLauncher "$@"

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-agent/pom.xml
----------------------------------------------------------------------
diff --git a/modules/web-agent/pom.xml b/modules/web-agent/pom.xml
index cb55319..d87084f 100644
--- a/modules/web-agent/pom.xml
+++ b/modules/web-agent/pom.xml
@@ -47,7 +47,7 @@
         <dependency>
             <groupId>com.fasterxml.jackson.datatype</groupId>
             <artifactId>jackson-datatype-json-org</artifactId>
-            <version>2.7.1</version>
+            <version>${jackson2.version}</version>
         </dependency>
 
         <dependency>
@@ -88,6 +88,26 @@
 
         <dependency>
             <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-spring</artifactId>
+            <version>${project.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework</groupId>
+                    <artifactId>spring-aop</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.springframework</groupId>
+                    <artifactId>spring-tx</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.springframework</groupId>
+                    <artifactId>spring-jdbc</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.ignite</groupId>
             <artifactId>ignite-log4j</artifactId>
             <version>${project.version}</version>
         </dependency>

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java
----------------------------------------------------------------------
diff --git a/modules/web-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java b/modules/web-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java
index ffd30a5..d4787cc 100644
--- a/modules/web-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java
+++ b/modules/web-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java
@@ -23,6 +23,8 @@ import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.Reader;
 import java.net.URL;
+import java.util.Arrays;
+import java.util.List;
 import java.util.Properties;
 
 /**
@@ -45,11 +47,13 @@ public class AgentConfiguration {
     private static final String DFLT_NODE_URI = "http://localhost:8080";
 
     /** */
-    @Parameter(names = {"-t", "--token"}, description = "User's security token used to establish connection to Ignite Console.")
-    private String tok;
+    @Parameter(names = {"-t", "--tokens"},
+        description = "User's tokens separated by comma used to connect to Ignite Console.")
+    private List<String> tokens;
 
     /** */
-    @Parameter(names = {"-s", "--server-uri"}, description = "URI for connect to Ignite Console via web-socket protocol" +
+    @Parameter(names = {"-s", "--server-uri"},
+        description = "URI for connect to Ignite Console via web-socket protocol" +
         "           " +
         "      Default value: " + DFLT_SERVER_URI)
     private String srvUri;
@@ -80,17 +84,17 @@ public class AgentConfiguration {
     private Boolean help;
 
     /**
-     * @return Token.
+     * @return Tokens.
      */
-    public String token() {
-        return tok;
+    public List<String> tokens() {
+        return tokens;
     }
 
     /**
-     * @param tok Token.
+     * @param tokens Tokens.
      */
-    public void token(String tok) {
-        this.tok = tok;
+    public void tokens(List<String> tokens) {
+        this.tokens = tokens;
     }
 
     /**
@@ -173,10 +177,10 @@ public class AgentConfiguration {
             props.load(reader);
         }
 
-        String val = (String)props.remove("token");
+        String val = (String)props.remove("tokens");
 
         if (val != null)
-            token(val);
+            tokens(Arrays.asList(val.split(",")));
 
         val = (String)props.remove("server-uri");
 
@@ -198,8 +202,8 @@ public class AgentConfiguration {
      * @param cmd Command.
      */
     public void merge(AgentConfiguration cmd) {
-        if (tok == null)
-            token(cmd.token());
+        if (tokens == null)
+            tokens(cmd.tokens());
 
         if (srvUri == null)
             serverUri(cmd.serverUri());
@@ -221,16 +225,25 @@ public class AgentConfiguration {
     @Override public String toString() {
         StringBuilder sb = new StringBuilder();
 
-        if (tok != null && tok.length() > 0) {
-            sb.append("User's security token         : ");
+        if (tokens != null && tokens.size() > 0) {
+            sb.append("User's security tokens        : ");
 
-            if (tok.length() > 4) {
-                sb.append(new String(new char[tok.length() - 4]).replace('\0', '*'));
+            boolean first = true;
 
-                sb.append(tok.substring(tok.length() - 4));
+            for (String tok : tokens) {
+                if (first)
+                    first = false;
+                else
+                    sb.append(",");
+
+                if (tok.length() > 4) {
+                    sb.append(new String(new char[tok.length() - 4]).replace('\0', '*'));
+
+                    sb.append(tok.substring(tok.length() - 4));
+                }
+                else
+                    sb.append(new String(new char[tok.length()]).replace('\0', '*'));
             }
-            else
-                sb.append(new String(new char[tok.length()]).replace('\0', '*'));
 
             sb.append('\n');
         }

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java
----------------------------------------------------------------------
diff --git a/modules/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java b/modules/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java
index 4b99a92..810fad4 100644
--- a/modules/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java
+++ b/modules/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java
@@ -26,7 +26,6 @@ import io.socket.emitter.Emitter;
 import java.io.File;
 import java.io.IOException;
 import java.net.ConnectException;
-import java.net.SocketException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
@@ -73,6 +72,9 @@ public class AgentLauncher {
     private static final String EVENT_SCHEMA_IMPORT_METADATA = "schemaImport:metadata";
 
     /** */
+    private static final String EVENT_AGENT_WARNING = "agent:warning";
+
+    /** */
     private static final String EVENT_AGENT_CLOSE = "agent:close";
 
     /** */
@@ -138,7 +140,7 @@ public class AgentLauncher {
      */
     private static final Emitter.Listener onDisconnect = new Emitter.Listener() {
         @Override public void call(Object... args) {
-            log.error(String.format("Connection closed: %s.", args[0]));
+            log.error(String.format("Connection closed: %s.", args));
         }
     };
 
@@ -198,7 +200,7 @@ public class AgentLauncher {
         System.out.println(cfg);
         System.out.println();
 
-        if (cfg.token() == null) {
+        if (cfg.tokens() == null) {
             String webHost;
 
             try {
@@ -213,9 +215,9 @@ public class AgentLauncher {
             System.out.println("Security token is required to establish connection to the web console.");
             System.out.println(String.format("It is available on the Profile page: https://%s/profile", webHost));
 
-            System.out.print("Enter security token: ");
+            System.out.print("Enter security tokens separated by comma: ");
 
-            cfg.token(System.console().readLine().trim());
+            cfg.tokens(Arrays.asList(System.console().readLine().trim().split(",")));
         }
 
         final RestHandler restHnd = new RestHandler(cfg);
@@ -258,7 +260,7 @@ public class AgentLauncher {
                         JSONObject authMsg = new JSONObject();
 
                         try {
-                            authMsg.put("token", cfg.token());
+                            authMsg.put("tokens", cfg.tokens());
 
                             String clsName = AgentLauncher.class.getSimpleName() + ".class";
 
@@ -280,7 +282,7 @@ public class AgentLauncher {
                                 @Override public void call(Object... args) {
                                     // Authentication failed if response contains args.
                                     if (args != null && args.length > 0) {
-                                        onDisconnect.call("Authentication failed: " + args[0]);
+                                        onDisconnect.call(args);
 
                                         System.exit(1);
                                     }
@@ -312,6 +314,11 @@ public class AgentLauncher {
                     .on(EVENT_SCHEMA_IMPORT_METADATA, dbHnd.metadataListener())
                     .on(EVENT_ERROR, onError)
                     .on(EVENT_DISCONNECT, onDisconnect)
+                    .on(EVENT_AGENT_WARNING, new Emitter.Listener() {
+                        @Override public void call(Object... args) {
+                            log.warn(args[0]);
+                        }
+                    })
                     .on(EVENT_AGENT_CLOSE, new Emitter.Listener() {
                         @Override public void call(Object... args) {
                             onDisconnect.call(args);

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-agent/src/main/java/org/apache/ignite/console/demo/AgentClusterDemo.java
----------------------------------------------------------------------
diff --git a/modules/web-agent/src/main/java/org/apache/ignite/console/demo/AgentClusterDemo.java b/modules/web-agent/src/main/java/org/apache/ignite/console/demo/AgentClusterDemo.java
index d09e5c7..bf0903a 100644
--- a/modules/web-agent/src/main/java/org/apache/ignite/console/demo/AgentClusterDemo.java
+++ b/modules/web-agent/src/main/java/org/apache/ignite/console/demo/AgentClusterDemo.java
@@ -18,10 +18,13 @@
 package org.apache.ignite.console.demo;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.Random;
+import java.util.Set;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
@@ -55,6 +58,8 @@ import org.apache.ignite.transactions.Transaction;
 import org.apache.log4j.Logger;
 
 import static org.apache.ignite.IgniteSystemProperties.IGNITE_ATOMIC_CACHE_DELETE_HISTORY_SIZE;
+import static org.apache.ignite.IgniteSystemProperties.IGNITE_PERFORMANCE_SUGGESTIONS_DISABLED;
+import static org.apache.ignite.IgniteSystemProperties.IGNITE_UPDATE_NOTIFIER;
 import static org.apache.ignite.IgniteSystemProperties.IGNITE_JETTY_PORT;
 import static org.apache.ignite.IgniteSystemProperties.IGNITE_NO_ASCII;
 import static org.apache.ignite.events.EventType.EVTS_DISCOVERY;
@@ -80,16 +85,24 @@ public class AgentClusterDemo {
 
     /** */
     private static final String COUNTRY_CACHE_NAME = "CountryCache";
+
     /** */
     private static final String DEPARTMENT_CACHE_NAME = "DepartmentCache";
+
     /** */
     private static final String EMPLOYEE_CACHE_NAME = "EmployeeCache";
+
     /** */
     private static final String PARKING_CACHE_NAME = "ParkingCache";
+
     /** */
     private static final String CAR_CACHE_NAME = "CarCache";
 
     /** */
+    private static final Set<String> DEMO_CACHES = new HashSet<>(Arrays.asList(COUNTRY_CACHE_NAME,
+        DEPARTMENT_CACHE_NAME, EMPLOYEE_CACHE_NAME, PARKING_CACHE_NAME, CAR_CACHE_NAME));
+
+    /** */
     private static final Random rnd = new Random();
 
     /** Countries count. */
@@ -320,9 +333,7 @@ public class AgentClusterDemo {
         IgniteConfiguration cfg = new IgniteConfiguration();
 
         cfg.setGridName((client ? "demo-server-" : "demo-client-") + gridIdx);
-
         cfg.setLocalHost("127.0.0.1");
-
         cfg.setIncludeEventTypes(EVTS_DISCOVERY);
 
         TcpDiscoveryVmIpFinder ipFinder = new TcpDiscoveryVmIpFinder();
@@ -333,7 +344,6 @@ public class AgentClusterDemo {
         TcpDiscoverySpi discoSpi = new TcpDiscoverySpi();
 
         discoSpi.setLocalPort(60900);
-
         discoSpi.setIpFinder(ipFinder);
 
         cfg.setDiscoverySpi(discoSpi);
@@ -341,21 +351,17 @@ public class AgentClusterDemo {
         TcpCommunicationSpi commSpi = new TcpCommunicationSpi();
 
         commSpi.setSharedMemoryPort(-1);
-
         commSpi.setLocalPort(60800);
 
         cfg.setCommunicationSpi(commSpi);
-
         cfg.setGridLogger(new Log4JLogger(log));
-
         cfg.setMetricsLogFrequency(0);
-
         cfg.getConnectorConfiguration().setPort(60700);
 
         if (client)
             cfg.setClientMode(true);
-        else
-            cfg.setCacheConfiguration(cacheCountry(), cacheDepartment(), cacheEmployee(), cacheParking(), cacheCar());
+
+        cfg.setCacheConfiguration(cacheCountry(), cacheDepartment(), cacheEmployee(), cacheParking(), cacheCar());
 
         return cfg;
     }
@@ -477,7 +483,6 @@ public class AgentClusterDemo {
         final long diff = new java.util.Date().getTime();
 
         populateCacheEmployee(ignite, diff);
-
         populateCacheCar(ignite);
 
         ScheduledExecutorService cachePool = newScheduledThreadPool(2, "demo-sql-load-cache-tasks");
@@ -485,32 +490,49 @@ public class AgentClusterDemo {
         cachePool.scheduleWithFixedDelay(new Runnable() {
             @Override public void run() {
                 try {
+                    for (String cacheName : ignite.cacheNames()) {
+                        if (!DEMO_CACHES.contains(cacheName)) {
+                            IgniteCache<Integer, String> otherCache = ignite.cache(cacheName);
+
+                            if (otherCache != null) {
+                                for (int i = 0, n = 1; i < cnt; i++, n++) {
+                                    Integer key = rnd.nextInt(1000);
+
+                                    String val = otherCache.get(key);
+
+                                    if (val == null)
+                                        otherCache.put(key, "other-" + key);
+                                    else if (rnd.nextInt(100) < 30)
+                                        otherCache.remove(key);
+                                }
+                            }
+                        }
+                    }
+
                     IgniteCache<Integer, Employee> cacheEmployee = ignite.cache(EMPLOYEE_CACHE_NAME);
 
-                    if (cacheEmployee == null)
-                        return;
+                    if (cacheEmployee != null)
+                        try(Transaction tx = ignite.transactions().txStart(PESSIMISTIC, REPEATABLE_READ)) {
+                            for (int i = 0, n = 1; i < cnt; i++, n++) {
+                                Integer id = rnd.nextInt(EMPL_CNT);
 
-                    try(Transaction tx = ignite.transactions().txStart(PESSIMISTIC, REPEATABLE_READ)) {
-                        for (int i = 0, n = 1; i < cnt; i++, n++) {
-                            Integer id = rnd.nextInt(EMPL_CNT);
+                                Integer depId = rnd.nextInt(DEP_CNT);
 
-                            Integer depId = rnd.nextInt(DEP_CNT);
+                                double r = rnd.nextDouble();
 
-                            double r = rnd.nextDouble();
+                                cacheEmployee.put(id, new Employee(id, depId, depId, "First name employee #" + n,
+                                    "Last name employee #" + n, "Email employee #" + n, "Phone number employee #" + n,
+                                    new java.sql.Date((long)(r * diff)), "Job employee #" + n, 500 + round(r * 2000, 2)));
 
-                            cacheEmployee.put(id, new Employee(id, depId, depId, "First name employee #" + n,
-                                "Last name employee #" + n, "Email employee #" + n, "Phone number employee #" + n,
-                                new java.sql.Date((long)(r * diff)), "Job employee #" + n, 500 + round(r * 2000, 2)));
+                                if (rnd.nextBoolean())
+                                    cacheEmployee.remove(rnd.nextInt(EMPL_CNT));
 
-                            if (rnd.nextBoolean())
-                                cacheEmployee.remove(rnd.nextInt(EMPL_CNT));
+                                cacheEmployee.get(rnd.nextInt(EMPL_CNT));
+                            }
 
-                            cacheEmployee.get(rnd.nextInt(EMPL_CNT));
+                            if (rnd.nextInt(100) > 20)
+                                tx.commit();
                         }
-
-                        if (rnd.nextInt(100) > 20)
-                            tx.commit();
-                    }
                 }
                 catch (Throwable e) {
                     if (!e.getMessage().contains("cache is stopped"))
@@ -553,6 +575,8 @@ public class AgentClusterDemo {
             log.info("DEMO: Starting embedded nodes for demo...");
 
             System.setProperty(IGNITE_ATOMIC_CACHE_DELETE_HISTORY_SIZE, "1");
+            System.setProperty(IGNITE_PERFORMANCE_SUGGESTIONS_DISABLED, "true");
+            System.setProperty(IGNITE_UPDATE_NOTIFIER, "false");
 
             System.setProperty(IGNITE_JETTY_PORT, "60800");
             System.setProperty(IGNITE_NO_ASCII, "true");
@@ -598,12 +622,12 @@ public class AgentClusterDemo {
 
                 acfg.demoNodeUri(String.format("http://%s:%d", host, port));
 
-                log.info("DEMO: Embedded nodes for sql test-drive successfully started");
+                log.info("DEMO: Embedded nodes for sql and monitoring demo successfully started");
 
                 startLoad(ignite, 20);
             }
             catch (Exception e) {
-                log.error("DEMO: Failed to start embedded node for sql test-drive!", e);
+                log.error("DEMO: Failed to start embedded node for sql and monitoring demo!", e);
 
                 return false;
             }

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-console/README.txt
----------------------------------------------------------------------
diff --git a/modules/web-console/README.txt b/modules/web-console/README.txt
index ae509d6..286082c 100644
--- a/modules/web-console/README.txt
+++ b/modules/web-console/README.txt
@@ -7,7 +7,7 @@ The Apache Ignite Web Console includes an interactive configuration wizard which
  on your in-memory cache as well as view execution plans, in-memory schema, and streaming charts.
 
 In order to simplify evaluation of Web Console demo mode was implemented.
- To start demo, you need to to click button "Start demo". New tab will be open with prepared demo data on each screen.
+ To start demo, you need to click button "Start demo". New tab will be open with prepared demo data on each screen.
 
  Demo for import domain model from database.
   In this mode an in-memory H2 database will be started.
@@ -24,7 +24,7 @@ In order to simplify evaluation of Web Console demo mode was implemented.
     In this mode internal Ignite node will be started. Cache created and populated with data.
      1) Click "SQL" in Ignite Web Console top menu.
      2) "Demo" notebook with preconfigured queries will be opened.
-     3) You can also execute any SQL queries for tables: "Country, Department, Employee", "Parking, Car".
+     3) You can also execute any SQL queries for tables: "Country, Department, Employee, Parking, Car".
 
  For example:
   1) Enter SQL statement:

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-console/src/main/js/.eslintrc
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/.eslintrc b/modules/web-console/src/main/js/.eslintrc
index 00fa0ad..1882b44 100644
--- a/modules/web-console/src/main/js/.eslintrc
+++ b/modules/web-console/src/main/js/.eslintrc
@@ -20,6 +20,8 @@ ecmaFeatures:
 
 globals:
     _: true
+    $: true
+    d3: true
     io: true
     window: true
     global: true
@@ -123,7 +125,7 @@ rules:
     no-multiple-empty-lines: [0, {"max": 2}]
     no-native-reassign: 2
     no-negated-in-lhs: 2
-    no-nested-ternary: 2
+    no-nested-ternary: 0
     no-new: 2
     no-new-func: 2
     no-new-object: 2
@@ -195,3 +197,6 @@ rules:
     wrap-iife: 0
     wrap-regex: 0
     yoda: [2, "never"]
+
+parserOptions:
+    sourceType: module

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-console/src/main/js/app/data/getting-started.json
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/data/getting-started.json b/modules/web-console/src/main/js/app/data/getting-started.json
index 7aff0b4..1b435ab 100644
--- a/modules/web-console/src/main/js/app/data/getting-started.json
+++ b/modules/web-console/src/main/js/app/data/getting-started.json
@@ -81,7 +81,7 @@
           "</div>",
           "<div class='col-xs-5'>",
           " <ul>",
-          "  <li>Preview XML Configuration</li>",
+          "  <li>Preview XML configuration</li>",
           "  <li>Preview code configuration</li>",
           "  <li>Preview Docker file</li>",
           "  <li>Preview POM dependencies</li>",

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-console/src/main/js/app/data/pom-dependencies.json
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/data/pom-dependencies.json b/modules/web-console/src/main/js/app/data/pom-dependencies.json
new file mode 100644
index 0000000..7ab6c1b
--- /dev/null
+++ b/modules/web-console/src/main/js/app/data/pom-dependencies.json
@@ -0,0 +1,20 @@
+{
+    "Cloud": {"artifactId": "ignite-cloud"},
+    "S3": {"artifactId": "ignite-aws"},
+    "GoogleStorage": {"artifactId": "ignite-gce"},
+    "ZooKeeper": {"artifactId": "ignite-zookeeper"},
+
+    "Log4j": {"artifactId": "ignite-log4j"},
+    "Log4j2": {"artifactId": "ignite-log4j2"},
+    "JCL": {"artifactId": "ignite-jcl"},
+    "HadoopIgfsJcl": {"artifactId": "ignite-hadoop"},
+    "SLF4J": {"artifactId": "ignite-slf4j"},
+
+    "Generic": {"groupId": "com.mchange", "artifactId": "c3p0", "version": "0.9.5.1"},
+    "MySQL": {"groupId": "mysql", "artifactId": "mysql-connector-java", "version": "5.1.37"},
+    "PostgreSQL": {"groupId": "org.postgresql", "artifactId": "postgresql", "version": "9.4-1204-jdbc42"},
+    "H2": {"groupId": "com.h2database", "artifactId": "h2", "version": "1.3.175"},
+    "Oracle": {"groupId": "oracle", "artifactId": "jdbc", "version": "11.2", "jar": "ojdbc6.jar"},
+    "DB2": {"groupId": "ibm", "artifactId": "jdbc", "version": "4.19.26", "jar": "db2jcc4.jar"},
+    "SQLServer": {"groupId": "microsoft", "artifactId": "jdbc", "version": "4.1", "jar": "sqljdbc41.jar"}
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-console/src/main/js/app/directives/ui-ace-docker/ui-ace-docker.jade
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/directives/ui-ace-docker/ui-ace-docker.jade b/modules/web-console/src/main/js/app/directives/ui-ace-docker/ui-ace-docker.jade
index f634aed..3b0e7b8 100644
--- a/modules/web-console/src/main/js/app/directives/ui-ace-docker/ui-ace-docker.jade
+++ b/modules/web-console/src/main/js/app/directives/ui-ace-docker/ui-ace-docker.jade
@@ -17,7 +17,7 @@
 mixin hard-link(ref, txt)
     a(style='color:#ec1c24' href=ref target='_blank') #{txt}
 
-div
+.panel-details-noborder
     .details-row
         p
             +hard-link('https://docs.docker.com/reference/builder', 'Docker')
@@ -28,4 +28,4 @@ div
             | . For more information about using Ignite with Docker please read&nbsp;
             +hard-link('http://apacheignite.readme.io/docs/docker-deployment', 'documentation')
             |.
-    div(ng-if='ctrl.data' ignite-ace='{onLoad: onLoad, mode: "dockerfile"}' ng-model='ctrl.data')
+    .details-row(ng-if='ctrl.data' ignite-ace='{onLoad: onLoad, mode: "dockerfile"}' ng-model='ctrl.data')

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-console/src/main/js/app/directives/ui-ace-java/ui-ace-java.directive.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/directives/ui-ace-java/ui-ace-java.directive.js b/modules/web-console/src/main/js/app/directives/ui-ace-java/ui-ace-java.directive.js
index 948f82c..cdce0fc 100644
--- a/modules/web-console/src/main/js/app/directives/ui-ace-java/ui-ace-java.directive.js
+++ b/modules/web-console/src/main/js/app/directives/ui-ace-java/ui-ace-java.directive.js
@@ -95,9 +95,9 @@ export default ['igniteUiAceJava', ['GeneratorJava', (generator) => {
             }
         }
 
-        if (typeof attrs.clusterCfg !== 'undefined') {
+        if (!_.isUndefined(attrs.clusterCfg)) {
             scope.$watch('cfg', (cfg) => {
-                if (typeof cfg !== 'undefined')
+                if (!_.isUndefined(cfg))
                     return;
 
                 scope.cfg = {};

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-console/src/main/js/app/directives/ui-ace-pojos/ui-ace-pojos.jade
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/directives/ui-ace-pojos/ui-ace-pojos.jade b/modules/web-console/src/main/js/app/directives/ui-ace-pojos/ui-ace-pojos.jade
index 3e82f12..ed1432b 100644
--- a/modules/web-console/src/main/js/app/directives/ui-ace-pojos/ui-ace-pojos.jade
+++ b/modules/web-console/src/main/js/app/directives/ui-ace-pojos/ui-ace-pojos.jade
@@ -17,7 +17,7 @@
 mixin check-tooltip(message)
     i.tipLabel.fa.fa-question-circle(bs-tooltip='"#{message}"')
 
-div
+.panel-details-noborder
     .details-row
         .col-xs-2.col-sm-2.col-md-2
             label POJO class:
@@ -37,4 +37,4 @@ div
                 input(type='checkbox' ng-model='ctrl.includeKeyFields')
                 | Include key fields
             +check-tooltip("Generate key fields in POJO value class")
-    div(ng-if='ctrl.data' ignite-ace='{onLoad: onLoad, mode: "java"}' ng-model='ctrl.data')
+    .details-row(ng-if='ctrl.data' ignite-ace='{onLoad: onLoad, mode: "java"}' ng-model='ctrl.data')

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-console/src/main/js/app/directives/ui-ace-xml/ui-ace-xml.directive.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/directives/ui-ace-xml/ui-ace-xml.directive.js b/modules/web-console/src/main/js/app/directives/ui-ace-xml/ui-ace-xml.directive.js
index 79787a2..1e913fb 100644
--- a/modules/web-console/src/main/js/app/directives/ui-ace-xml/ui-ace-xml.directive.js
+++ b/modules/web-console/src/main/js/app/directives/ui-ace-xml/ui-ace-xml.directive.js
@@ -95,9 +95,9 @@ export default ['igniteUiAceXml', ['GeneratorXml', (generator) => {
             }
         }
 
-        if (typeof attrs.clusterCfg !== 'undefined') {
+        if (!_.isUndefined(attrs.clusterCfg)) {
             scope.$watch('cfg', (cfg) => {
-                if (typeof cfg !== 'undefined')
+                if (!_.isUndefined(cfg))
                     return;
 
                 scope.cfg = {};

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-console/src/main/js/app/helpers/jade/mixins.jade
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/helpers/jade/mixins.jade b/modules/web-console/src/main/js/app/helpers/jade/mixins.jade
index 341d351..2b8b282 100644
--- a/modules/web-console/src/main/js/app/helpers/jade/mixins.jade
+++ b/modules/web-console/src/main/js/app/helpers/jade/mixins.jade
@@ -353,7 +353,7 @@ mixin table-text-field(field, items, valid, save, placeholder, newItem)
     -var onBlur = valid + ' && ( ' + save + '); ' + resetOnBlur + ';'
 
     ignite-form-field-input-text(
-        data-name=field
+        data-name='#{field}{{ $index || "" }}'
         data-ng-model=field
         data-ng-required='true'
         data-placeholder=placeholder
@@ -376,7 +376,7 @@ mixin table-java-class-field(lbl, field, items, valid, save, newItem)
     -var onBlur = valid + ' && ( ' + save + '); ' + resetOnBlur + ';'
 
     ignite-form-field-input-text(
-        data-name=field
+        data-name='#{field}{{ $index || "" }}'
         data-ng-model=field
         data-ng-required='true'
         data-ignite-unique=items
@@ -411,7 +411,7 @@ mixin table-java-package-field(field, items, valid, save, newItem)
     -var onBlur = valid + ' && ( ' + save + '); ' + resetOnBlur + ';'
 
     ignite-form-field-input-text(
-        data-name=field
+        data-name='#{field}{{ $index || "" }}'
         data-ng-model=field
         data-ng-required='true'
         data-placeholder='Enter package name'
@@ -436,7 +436,7 @@ mixin table-address-field(field, items, valid, save, newItem, portRange)
     -var onBlur = valid + ' && ( ' + save + '); ' + resetOnBlur + ';'
 
     ignite-form-field-input-text(
-        data-name=field
+        data-name='#{field}{{ $index || "" }}'
         data-ng-model=field
         data-ng-required='true'
         data-placeholder='IP address:port'
@@ -466,8 +466,9 @@ mixin table-save-button(valid, save, newItem)
 //- Mixin for table remove button.
 mixin table-remove-conditional-button(items, show, tip)
     i.tipField.fa.fa-remove(
-        ng-show='#{show} && !field.edit'
-        bs-tooltip='"#{tip}"'
+        ng-hide='!#{show} || field.edit'
+        bs-tooltip
+        data-title=tip
         ng-click='#{items}.splice(#{items}.indexOf(model), 1)'
     )
 
@@ -519,12 +520,12 @@ mixin evictionPolicy(model, name, enabled, required, tip)
 
 //- Mixin for clusters dropdown.
 mixin clusters(model, tip)
-    +dropdown-multiple('<span>Clusters:</span>' + '<a ui-sref="base.configuration.clusters({id: ' + model + '._id})"> (add)</a>',
+    +dropdown-multiple('<span>Clusters:</span>' + '<a ui-sref="base.configuration.clusters({linkId: linkId()})"> (add)</a>',
         model + '.clusters', 'clusters', 'true', 'Choose clusters', 'No clusters configured', 'clusters', tip)
 
 //- Mixin for caches dropdown.
 mixin caches(model, tip)
-    +dropdown-multiple('<span>Caches:</span>' + '<a ui-sref="base.configuration.caches({id: ' + model + '._id})"> (add)</a>',
+    +dropdown-multiple('<span>Caches:</span>' + '<a ui-sref="base.configuration.caches({linkId: linkId()})"> (add)</a>',
         model + '.caches', 'caches', 'true', 'Choose caches', 'No caches configured', 'caches', tip)
 
 //- Mixin for XML and Java preview.

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-console/src/main/js/app/index.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/index.js b/modules/web-console/src/main/js/app/index.js
index a8bd741..397d25f 100644
--- a/modules/web-console/src/main/js/app/index.js
+++ b/modules/web-console/src/main/js/app/index.js
@@ -56,10 +56,11 @@ import 'angular-motion/dist/angular-motion.css!';
 import './decorator/select';
 import './decorator/tooltip';
 
-import './modules/Demo/Demo.module';
-import './modules/form/form.module';
 import './services/JavaTypes.service.js';
-import './modules/QueryNotebooks/QueryNotebooks.provider';
+import './modules/form/form.module';
+import './modules/agent/agent.module.js';
+import './modules/query-notebooks/query-notebooks.module';
+import './modules/Demo/Demo.module.js';
 
 import './modules/states/signin.state';
 import './modules/states/logout.state';
@@ -100,7 +101,6 @@ import confirm from './services/confirm.service';
 import IgniteInetAddress from './services/InetAddress.service';
 import IgniteCountries from './services/Countries.service';
 import IgniteChartColors from './services/ChartColors.service';
-import IgniteAgentMonitor from './services/AgentMonitor.service';
 import JavaTypes from './services/JavaTypes.service';
 
 // Providers.
@@ -149,13 +149,14 @@ angular
     'ui.router',
     'gridster',
     // Base modules.
+    'ignite-console.ace',
+    'ignite-console.Form',
     'ignite-console.user',
     'ignite-console.branding',
-    'ignite-console.Form',
-    'ignite-console.QueryNotebooks',
-    'ignite-console.ace',
-    'ignite-console.demo',
     'ignite-console.socket',
+    'ignite-console.agent',
+    'ignite-console.query-notebooks',
+    'ignite-console.demo',
     // States.
     'ignite-console.states.login',
     'ignite-console.states.logout',
@@ -193,7 +194,6 @@ angular
 .service(...IgniteInetAddress)
 .service(...IgniteCountries)
 .service(...IgniteChartColors)
-.service(...IgniteAgentMonitor)
 .service(...JavaTypes)
 // Providers.
 // Filters.

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-console/src/main/js/app/modules/Demo/Demo.module.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/modules/Demo/Demo.module.js b/modules/web-console/src/main/js/app/modules/Demo/Demo.module.js
index adf5a82..f08d84c 100644
--- a/modules/web-console/src/main/js/app/modules/Demo/Demo.module.js
+++ b/modules/web-console/src/main/js/app/modules/Demo/Demo.module.js
@@ -111,9 +111,11 @@ angular
         return items;
     }];
 }])
-.service('DemoInfo', ['$rootScope', '$modal', '$state', 'igniteDemoInfo', 'IgniteAgentMonitor', ($rootScope, $modal, $state, igniteDemoInfo, agentMonitor) => {
+.service('DemoInfo', ['$rootScope', '$modal', '$state', '$q', 'igniteDemoInfo', 'IgniteAgentMonitor', ($rootScope, $modal, $state, $q, igniteDemoInfo, agentMonitor) => {
     const scope = $rootScope.$new();
 
+    let closePromise = null;
+
     function _fillPage() {
         const model = igniteDemoInfo;
 
@@ -131,10 +133,8 @@ angular
 
     scope.close = () => {
         dialog.hide();
-    };
 
-    scope.gotoConfiguration = () => {
-        scope.$$postDigest(() => $state.go('base.configuration.clusters'));
+        closePromise && closePromise.resolve();
     };
 
     scope.downloadAgent = () => {
@@ -154,11 +154,13 @@ angular
 
     return {
         show: () => {
+            closePromise = $q.defer();
+
             _fillPage();
 
-            dialog.$promise
+            return dialog.$promise
                 .then(dialog.show)
-                .then(() => agentMonitor.awaitAgent())
+                .then(() => Promise.race([agentMonitor.awaitAgent(), closePromise.promise]))
                 .then(() => scope.hasAgents = true);
         }
     };

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-console/src/main/js/app/modules/QueryNotebooks/QueryNotebooks.provider.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/modules/QueryNotebooks/QueryNotebooks.provider.js b/modules/web-console/src/main/js/app/modules/QueryNotebooks/QueryNotebooks.provider.js
deleted file mode 100644
index 7e4e523..0000000
--- a/modules/web-console/src/main/js/app/modules/QueryNotebooks/QueryNotebooks.provider.js
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 angular from 'angular';
-
-angular
-    .module('ignite-console.QueryNotebooks', [
-
-    ])
-    .provider('QueryNotebooks', function() {
-        const _demoNotebook = {
-            name: 'SQL demo',
-            paragraphs: [
-                {
-                    name: 'Query with refresh rate',
-                    cacheName: 'CarCache',
-                    pageSize: 50,
-                    query: 'SELECT count(*)\nFROM "CarCache".Car',
-                    result: 'bar',
-                    timeLineSpan: '1',
-                    rate: {
-                        value: 3,
-                        unit: 1000,
-                        installed: true
-                    }
-                },
-                {
-                    name: 'Simple query',
-                    cacheName: 'CarCache',
-                    pageSize: 50,
-                    query: 'SELECT * FROM "CarCache".Car',
-                    result: 'table',
-                    timeLineSpan: '1',
-                    rate: {
-                        value: 30,
-                        unit: 1000,
-                        installed: false
-                    }
-                },
-                {
-                    name: 'Query with aggregates',
-                    cacheName: 'CarCache',
-                    pageSize: 50,
-                    query: 'SELECT p.name, count(*) AS cnt\nFROM "ParkingCache".Parking p\nINNER JOIN "CarCache".Car c\n  ON (p.id) = (c.parkingId)\nGROUP BY P.NAME',
-                    result: 'table',
-                    timeLineSpan: '1',
-                    rate: {
-                        value: 30,
-                        unit: 1000,
-                        installed: false
-                    }
-                }
-            ],
-            expandedParagraphs: [0, 1, 2]
-        };
-
-        this.$get = ['$q', '$http', '$rootScope', ($q, $http, $root) => {
-            return {
-                read(noteId) {
-                    if ($root.IgniteDemoMode)
-                        return $q.when(angular.copy(_demoNotebook));
-
-                    return $http.post('/api/v1/notebooks/get', {noteId})
-                        .then(({data}) => data)
-                        .catch(({data}) => $q.reject(data));
-                },
-                save(notebook) {
-                    if ($root.IgniteDemoMode)
-                        return $q.when();
-
-                    return $http.post('/api/v1/notebooks/save', notebook)
-                        .then(({data}) => data)
-                        .catch(({data}) => $q.reject(data));
-                },
-                remove(notebook) {
-                    if ($root.IgniteDemoMode)
-                        return $q.reject(`Removing "${notebook.name}" notebook is not supported.`);
-
-                    return $http.post('/api/v1/notebooks/remove', {_id: notebook._id})
-                        .then(() => {
-                            const idx = _.findIndex($root.notebooks, (item) => {
-                                return item._id === notebook._id;
-                            });
-
-                            if (idx >= 0) {
-                                $root.notebooks.splice(idx, 1);
-
-                                $root.rebuildDropdown();
-
-                                if (idx < $root.notebooks.length)
-                                    return $root.notebooks[idx];
-                            }
-
-                            if ($root.notebooks.length > 0)
-                                return $root.notebooks[$root.notebooks.length - 1];
-                        })
-                        .catch(({data}) => $q.reject(data));
-                }
-            };
-        }];
-    });

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-console/src/main/js/app/modules/agent/agent.module.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/modules/agent/agent.module.js b/modules/web-console/src/main/js/app/modules/agent/agent.module.js
new file mode 100644
index 0000000..4e3f7ef
--- /dev/null
+++ b/modules/web-console/src/main/js/app/modules/agent/agent.module.js
@@ -0,0 +1,323 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 angular from 'angular';
+import io from 'socket.io-client'; // eslint-disable-line no-unused-vars
+
+class IgniteAgentMonitor {
+    constructor(socketFactory, $root, $q, $state, $modal, $common) {
+        this._scope = $root.$new();
+
+        $root.$watch('user', () => {
+            this._scope.user = $root.user;
+        });
+
+        $root.$on('$stateChangeStart', () => {
+            this.stopWatch();
+        });
+
+        // Pre-fetch modal dialogs.
+        this._downloadAgentModal = $modal({
+            scope: this._scope,
+            templateUrl: '/templates/agent-download.html',
+            show: false,
+            backdrop: 'static',
+            keyboard: false
+        });
+
+        const _modalHide = this._downloadAgentModal.hide;
+
+        /**
+         * Special dialog hide function.
+         */
+        this._downloadAgentModal.hide = () => {
+            $common.hideAlert();
+
+            _modalHide();
+        };
+
+        /**
+         * Close dialog and go by specified link.
+         */
+        this._scope.back = () => {
+            this.stopWatch();
+
+            if (this._scope.backState)
+                this._scope.$$postDigest(() => $state.go(this._scope.backState));
+        };
+
+        this._scope.downloadAgent = () => {
+            const lnk = document.createElement('a');
+
+            lnk.setAttribute('href', '/api/v1/agent/download/zip');
+            lnk.setAttribute('target', '_self');
+            lnk.setAttribute('download', null);
+            lnk.style.display = 'none';
+
+            document.body.appendChild(lnk);
+
+            lnk.click();
+
+            document.body.removeChild(lnk);
+        };
+
+        this._scope.hasAgents = null;
+        this._scope.showModal = false;
+
+        /**
+         * @type {Socket}
+         */
+        this._socket = null;
+
+        this._socketFactory = socketFactory;
+
+        this._$q = $q;
+
+        this._$common = $common;
+    }
+
+    /**
+     * @private
+     */
+    checkModal() {
+        if (this._scope.showModal && !this._scope.hasAgents)
+            this._downloadAgentModal.$promise.then(this._downloadAgentModal.show);
+        else if ((this._scope.hasAgents || !this._scope.showModal) && this._downloadAgentModal.$isShown)
+            this._downloadAgentModal.hide();
+    }
+
+    /**
+     * @returns {Promise}
+     */
+    awaitAgent() {
+        if (this._scope.hasAgents)
+            return this._$q.when();
+
+        const latch = this._$q.defer();
+
+        const offConnected = this._scope.$on('agent:watch', (event, state) => {
+            if (state !== 'DISCONNECTED')
+                offConnected();
+
+            if (state === 'CONNECTED')
+                return latch.resolve();
+
+            if (state === 'STOPPED')
+                return latch.reject('Agent watch stopped.');
+        });
+
+        return latch.promise;
+    }
+
+    init() {
+        this._socket = this._socketFactory();
+
+        const disconnectFn = () => {
+            this._scope.hasAgents = false;
+
+            this.checkModal();
+
+            this._scope.$broadcast('agent:watch', 'DISCONNECTED');
+        };
+
+        this._socket.on('connect_error', disconnectFn);
+        this._socket.on('disconnect', disconnectFn);
+
+        this._socket.on('agent:count', ({count}) => {
+            this._scope.hasAgents = count > 0;
+
+            this.checkModal();
+
+            this._scope.$broadcast('agent:watch', this._scope.hasAgents ? 'CONNECTED' : 'DISCONNECTED');
+        });
+    }
+
+    /**
+     * @param {Object} back
+     * @returns {Promise}
+     */
+    startWatch(back) {
+        this._scope.backState = back.state;
+        this._scope.backText = back.text;
+
+        this._scope.agentGoal = back.goal;
+
+        if (back.onDisconnect) {
+            this._scope.offDisconnect = this._scope.$on('agent:watch', (e, state) =>
+                state === 'DISCONNECTED' && back.onDisconnect());
+        }
+
+        this._scope.showModal = true;
+
+        // Remove blinking on init.
+        if (this._scope.hasAgents !== null)
+            this.checkModal();
+
+        return this.awaitAgent();
+    }
+
+    /**
+     *
+     * @param {String} event
+     * @param {Object} [args]
+     * @returns {Promise}
+     * @private
+     */
+    _emit(event, ...args) {
+        if (!this._socket)
+            return this._$q.reject('Failed to connect to server');
+
+        const latch = this._$q.defer();
+
+        const onDisconnect = () => {
+            this._socket.removeListener('disconnect', onDisconnect);
+
+            latch.reject('Connection to server was closed');
+        };
+
+        this._socket.on('disconnect', onDisconnect);
+
+        args.push((err, res) => {
+            this._socket.removeListener('disconnect', onDisconnect);
+
+            if (err)
+                latch.reject(err);
+
+            latch.resolve(res);
+        });
+
+        this._socket.emit(event, ...args);
+
+        return latch.promise;
+    }
+
+    drivers() {
+        return this._emit('schemaImport:drivers');
+    }
+
+    /**
+     *
+     * @param {Object} preset
+     * @returns {Promise}
+     */
+    schemas(preset) {
+        return this._emit('schemaImport:schemas', preset);
+    }
+
+    /**
+     *
+     * @param {Object} preset
+     * @returns {Promise}
+     */
+    tables(preset) {
+        return this._emit('schemaImport:tables', preset);
+    }
+
+    /**
+     * @param {Object} err
+     */
+    showNodeError(err) {
+        if (this._scope.showModal) {
+            this._downloadAgentModal.$promise.then(this._downloadAgentModal.show);
+
+            this._$common.showError(err);
+        }
+    }
+
+    /**
+     *
+     * @param {String} event
+     * @param {Object} [args]
+     * @returns {Promise}
+     * @private
+     */
+    _rest(event, ...args) {
+        return this._downloadAgentModal.$promise
+            .then(() => this._emit(event, ...args));
+    }
+
+    /**
+     * @param {Boolean} [attr]
+     * @param {Boolean} [mtr]
+     * @returns {Promise}
+     */
+    topology(attr, mtr) {
+        return this._rest('node:topology', !!attr, !!mtr);
+    }
+
+    /**
+     * @param {int} [queryId]
+     * @returns {Promise}
+     */
+    queryClose(queryId) {
+        return this._rest('node:query:close', queryId);
+    }
+
+    /**
+     * @param {String} cacheName Cache name.
+     * @param {int} pageSize
+     * @param {String} [query] Query if null then scan query.
+     * @returns {Promise}
+     */
+    query(cacheName, pageSize, query) {
+        return this._rest('node:query', _.isEmpty(cacheName) ? null : cacheName, pageSize, query);
+    }
+
+    /**
+     * @param {String} cacheName Cache name.
+     * @param {String} [query] Query if null then scan query.
+     * @returns {Promise}
+     */
+    queryGetAll(cacheName, query) {
+        return this._rest('node:query:getAll', _.isEmpty(cacheName) ? null : cacheName, query);
+    }
+
+    /**
+     * @param {String} [cacheName] Cache name.
+     * @returns {Promise}
+     */
+    metadata(cacheName) {
+        return this._rest('node:cache:metadata', _.isEmpty(cacheName) ? null : cacheName);
+    }
+
+    /**
+     * @param {int} queryId
+     * @param {int} pageSize
+     * @returns {Promise}
+     */
+    next(queryId, pageSize) {
+        return this._rest('node:query:fetch', queryId, pageSize);
+    }
+
+    stopWatch() {
+        this._scope.showModal = false;
+
+        this.checkModal();
+
+        this._scope.offDisconnect && this._scope.offDisconnect();
+
+        this._scope.$broadcast('agent:watch', 'STOPPED');
+    }
+}
+
+IgniteAgentMonitor.$inject = ['igniteSocketFactory', '$rootScope', '$q', '$state', '$modal', '$common'];
+
+angular
+    .module('ignite-console.agent', [
+
+    ])
+    .service('IgniteAgentMonitor', IgniteAgentMonitor);

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-console/src/main/js/app/modules/configuration/generator/Pom.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/modules/configuration/generator/Pom.service.js b/modules/web-console/src/main/js/app/modules/configuration/generator/Pom.service.js
index cf266e1..3508e59 100644
--- a/modules/web-console/src/main/js/app/modules/configuration/generator/Pom.service.js
+++ b/modules/web-console/src/main/js/app/modules/configuration/generator/Pom.service.js
@@ -15,6 +15,9 @@
  * limitations under the License.
  */
 
+// Java built-in class names.
+import POM_DEPENDENCIES from 'app/data/pom-dependencies.json!';
+
 /**
  * Pom file generation entry point.
  */
@@ -31,7 +34,8 @@ class GeneratorPom {
     }
 
     addDependency(deps, groupId, artifactId, version, jar) {
-        deps.push({groupId, artifactId, version, jar});
+        if (!_.find(deps, (dep) => dep.groupId === groupId && dep.artifactId === artifactId))
+            deps.push({groupId, artifactId, version, jar});
     }
 
     addResource(res, dir, exclude) {
@@ -132,8 +136,8 @@ class GeneratorPom {
      */
     generate(cluster, igniteVersion, res) {
         const caches = cluster.caches;
-        const dialect = {};
         const deps = [];
+        const storeDeps = [];
         const excludeGroupIds = ['org.apache.ignite'];
 
         const blobStoreFactory = {cacheStoreFactory: {kind: 'CacheHibernateBlobStoreFactory'}};
@@ -145,8 +149,11 @@ class GeneratorPom {
             if (cache.cacheStoreFactory && cache.cacheStoreFactory.kind) {
                 const storeFactory = cache.cacheStoreFactory[cache.cacheStoreFactory.kind];
 
-                if (storeFactory.dialect && (!storeFactory.connectVia || storeFactory.connectVia === 'DataSource'))
-                    dialect[storeFactory.dialect] = true;
+                if (storeFactory.dialect && (!storeFactory.connectVia || storeFactory.connectVia === 'DataSource')) {
+                    const dep = POM_DEPENDENCIES[storeFactory.dialect];
+
+                    this.addDependency(storeDeps, dep.groupId, dep.artifactId, dep.version, dep.jar);
+                }
             }
         });
 
@@ -172,14 +179,10 @@ class GeneratorPom {
         this.addDependency(deps, 'org.apache.ignite', 'ignite-indexing', igniteVersion);
         this.addDependency(deps, 'org.apache.ignite', 'ignite-rest-http', igniteVersion);
 
-        if (cluster.discovery.kind === 'Cloud')
-            this.addDependency(deps, 'org.apache.ignite', 'ignite-cloud', igniteVersion);
-        else if (cluster.discovery.kind === 'S3')
-            this.addDependency(deps, 'org.apache.ignite', 'ignite-aws', igniteVersion);
-        else if (cluster.discovery.kind === 'GoogleStorage')
-            this.addDependency(deps, 'org.apache.ignite', 'ignite-gce', igniteVersion);
-        else if (cluster.discovery.kind === 'ZooKeeper')
-            this.addDependency(deps, 'org.apache.ignite', 'ignite-zookeeper', igniteVersion);
+        let dep = POM_DEPENDENCIES[cluster.discovery.kind];
+
+        if (dep)
+            this.addDependency(deps, 'org.apache.ignite', dep.artifactId, igniteVersion);
 
         if (_.find(cluster.igfss, (igfs) => igfs.secondaryFileSystemEnabled))
             this.addDependency(deps, 'org.apache.ignite', 'ignite-hadoop', igniteVersion);
@@ -187,21 +190,14 @@ class GeneratorPom {
         if (_.find(caches, blobStoreFactory))
             this.addDependency(deps, 'org.apache.ignite', 'ignite-hibernate', igniteVersion);
 
-        dialect.Generic && this.addDependency(deps, 'com.mchange', 'c3p0', '0.9.5.1');
-
-        dialect.MySQL && this.addDependency(deps, 'mysql', 'mysql-connector-java', '5.1.37');
-
-        dialect.PostgreSQL && this.addDependency(deps, 'org.postgresql', 'postgresql', '9.4-1204-jdbc42');
+        if (cluster.logger && cluster.logger.kind) {
+            dep = POM_DEPENDENCIES[cluster.logger.kind];
 
-        dialect.H2 && this.addDependency(deps, 'com.h2database', 'h2', '1.3.175');
-
-        dialect.Oracle && this.addDependency(deps, 'oracle', 'jdbc', '11.2', 'ojdbc6.jar');
-
-        dialect.DB2 && this.addDependency(deps, 'ibm', 'jdbc', '4.19.26', 'db2jcc4.jar');
-
-        dialect.SQLServer && this.addDependency(deps, 'microsoft', 'jdbc', '4.1', 'sqljdbc41.jar');
+            if (dep)
+                this.addDependency(deps, 'org.apache.ignite', dep.artifactId, igniteVersion);
+        }
 
-        this.dependencies(res, cluster, deps);
+        this.dependencies(res, cluster, deps.concat(storeDeps));
 
         res.needEmptyLine = true;
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-console/src/main/js/app/modules/form/field/input/datalist.directive.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/modules/form/field/input/datalist.directive.js b/modules/web-console/src/main/js/app/modules/form/field/input/datalist.directive.js
index 0cd9d69..ce67897 100644
--- a/modules/web-console/src/main/js/app/modules/form/field/input/datalist.directive.js
+++ b/modules/web-console/src/main/js/app/modules/form/field/input/datalist.directive.js
@@ -74,10 +74,10 @@ export default ['igniteFormFieldInputDatalist', ['IgniteFormGUID', '$table', (gu
         scope.ngChange = () => {
             ngModel.$setViewValue(scope.value);
 
-            if (JSON.stringify(scope.value) !== JSON.stringify(form.$defaults[name]))
-                ngModel.$setDirty();
-            else
+            if (_.isEqual(scope.value, form.$defaults[name]))
                 ngModel.$setPristine();
+            else
+                ngModel.$setDirty();
 
             setTimeout(checkValid, 100); // Use setTimeout() workaround of problem of two controllers.
         };

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-console/src/main/js/app/modules/form/field/input/text.directive.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/modules/form/field/input/text.directive.js b/modules/web-console/src/main/js/app/modules/form/field/input/text.directive.js
index ba4407f..adcc179 100644
--- a/modules/web-console/src/main/js/app/modules/form/field/input/text.directive.js
+++ b/modules/web-console/src/main/js/app/modules/form/field/input/text.directive.js
@@ -37,11 +37,14 @@ export default ['igniteFormFieldInputText', ['IgniteFormGUID', '$table', (guid,
             label.for = scope.id;
 
             scope.label = label;
+            scope.labelName = label.name;
 
             scope.$watch('required', (required) => {
                 label.required = required || false;
             });
         }
+        else
+            scope.labelName = attrs.igniteLabelName || 'Value';
 
         form.$defaults = form.$defaults || {};
 
@@ -75,10 +78,10 @@ export default ['igniteFormFieldInputText', ['IgniteFormGUID', '$table', (guid,
         scope.ngChange = () => {
             ngModel.$setViewValue(scope.value);
 
-            if (JSON.stringify(scope.value) !== JSON.stringify(form.$defaults[name]))
-                ngModel.$setDirty();
-            else
+            if (_.isEqual(scope.value, form.$defaults[name]))
                 ngModel.$setPristine();
+            else
+                ngModel.$setDirty();
 
             setTimeout(checkValid, 100); // Use setTimeout() workaround of problem of two controllers.
         };

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-console/src/main/js/app/modules/form/field/input/text.jade
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/modules/form/field/input/text.jade b/modules/web-console/src/main/js/app/modules/form/field/input/text.jade
index 40aef79..8a1dfc2 100644
--- a/modules/web-console/src/main/js/app/modules/form/field/input/text.jade
+++ b/modules/web-console/src/main/js/app/modules/form/field/input/text.jade
@@ -43,6 +43,6 @@ mixin feedback(isCheckPristine, error, errorMessage)
         data-ng-focus='tableReset()'
     )
 
-    +feedback(true, 'required', '{{ label.name }} could not be empty!')
+    +feedback(true, 'required', '{{ labelName }} could not be empty!')
 
     span.transclude-here

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-console/src/main/js/app/modules/form/group/group.directive.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/modules/form/group/group.directive.js b/modules/web-console/src/main/js/app/modules/form/group/group.directive.js
index 5039d6f..0971d44 100644
--- a/modules/web-console/src/main/js/app/modules/form/group/group.directive.js
+++ b/modules/web-console/src/main/js/app/modules/form/group/group.directive.js
@@ -50,10 +50,10 @@ export default ['igniteFormGroup', [() => {
         };
 
         const setAsDirty = () => {
-            if (JSON.stringify(scope.ngModel) !== JSON.stringify(parentFormCtrl.$defaults[name]))
-                ngModelCtrl.$setDirty();
-            else
+            if (_.isEqual(scope.ngModel, parentFormCtrl.$defaults[name]))
                 ngModelCtrl.$setPristine();
+            else
+                ngModelCtrl.$setDirty();
         };
 
         scope.$watch(() => parentFormCtrl.$pristine, setAsDefault);

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-console/src/main/js/app/modules/form/validator/java-keywords.directive.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/modules/form/validator/java-keywords.directive.js b/modules/web-console/src/main/js/app/modules/form/validator/java-keywords.directive.js
index 841c48b..d97e59a 100644
--- a/modules/web-console/src/main/js/app/modules/form/validator/java-keywords.directive.js
+++ b/modules/web-console/src/main/js/app/modules/form/validator/java-keywords.directive.js
@@ -20,15 +20,14 @@ export default ['javaKeywords', ['JavaTypes', (JavaTypes) => {
         if (_.isUndefined(attrs.javaKeywords) || !attrs.javaKeywords)
             return;
 
+        const packageOnly = attrs.javaPackageName === 'package-only';
+
         ngModel.$validators.javaKeywords = (value) => {
             if (value) {
-                if (!JavaTypes.validIdentifier(value) || !JavaTypes.packageSpecified(value))
+                if (!JavaTypes.validIdentifier(value) || (!packageOnly && !JavaTypes.packageSpecified(value)))
                     return true;
 
-                for (const item of value.split('.')) {
-                    if (JavaTypes.isKeywords(item))
-                        return false;
-                }
+                return _.findIndex(value.split('.'), JavaTypes.isKeywords) < 0;
             }
 
             return true;

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-console/src/main/js/app/modules/loading/loading.css
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/modules/loading/loading.css b/modules/web-console/src/main/js/app/modules/loading/loading.css
index fac7517..87bbc6a 100644
--- a/modules/web-console/src/main/js/app/modules/loading/loading.css
+++ b/modules/web-console/src/main/js/app/modules/loading/loading.css
@@ -25,7 +25,7 @@
     left: 0;
     right: 0;
     top: 0;
-    z-index: 1002;
+    z-index: 1001;
     opacity: 0;
     visibility: hidden;
     background-color: white;

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-console/src/main/js/app/modules/query-notebooks/query-notebooks.module.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/modules/query-notebooks/query-notebooks.module.js b/modules/web-console/src/main/js/app/modules/query-notebooks/query-notebooks.module.js
new file mode 100644
index 0000000..5999948
--- /dev/null
+++ b/modules/web-console/src/main/js/app/modules/query-notebooks/query-notebooks.module.js
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 angular from 'angular';
+
+angular
+    .module('ignite-console.query-notebooks', [
+
+    ])
+    .provider('QueryNotebooks', function() {
+        const _demoNotebook = {
+            name: 'SQL demo',
+            paragraphs: [
+                {
+                    name: 'Query with refresh rate',
+                    cacheName: 'CarCache',
+                    pageSize: 50,
+                    query: 'SELECT count(*)\nFROM "CarCache".Car',
+                    result: 'bar',
+                    timeLineSpan: '1',
+                    rate: {
+                        value: 3,
+                        unit: 1000,
+                        installed: true
+                    }
+                },
+                {
+                    name: 'Simple query',
+                    cacheName: 'CarCache',
+                    pageSize: 50,
+                    query: 'SELECT * FROM "CarCache".Car',
+                    result: 'table',
+                    timeLineSpan: '1',
+                    rate: {
+                        value: 30,
+                        unit: 1000,
+                        installed: false
+                    }
+                },
+                {
+                    name: 'Query with aggregates',
+                    cacheName: 'CarCache',
+                    pageSize: 50,
+                    query: 'SELECT p.name, count(*) AS cnt\nFROM "ParkingCache".Parking p\nINNER JOIN "CarCache".Car c\n  ON (p.id) = (c.parkingId)\nGROUP BY P.NAME',
+                    result: 'table',
+                    timeLineSpan: '1',
+                    rate: {
+                        value: 30,
+                        unit: 1000,
+                        installed: false
+                    }
+                }
+            ],
+            expandedParagraphs: [0, 1, 2]
+        };
+
+        this.$get = ['$q', '$http', '$rootScope', ($q, $http, $root) => {
+            return {
+                read(noteId) {
+                    if ($root.IgniteDemoMode)
+                        return $q.when(angular.copy(_demoNotebook));
+
+                    return $http.post('/api/v1/notebooks/get', {noteId})
+                        .then(({data}) => data)
+                        .catch(({data}) => $q.reject(data));
+                },
+                save(notebook) {
+                    if ($root.IgniteDemoMode)
+                        return $q.when();
+
+                    return $http.post('/api/v1/notebooks/save', notebook)
+                        .then(({data}) => data)
+                        .catch(({data}) => $q.reject(data));
+                },
+                remove(notebook) {
+                    if ($root.IgniteDemoMode)
+                        return $q.reject(`Removing "${notebook.name}" notebook is not supported.`);
+
+                    return $http.post('/api/v1/notebooks/remove', {_id: notebook._id})
+                        .then(() => {
+                            const idx = _.findIndex($root.notebooks, (item) => {
+                                return item._id === notebook._id;
+                            });
+
+                            if (idx >= 0) {
+                                $root.notebooks.splice(idx, 1);
+
+                                $root.rebuildDropdown();
+
+                                if (idx < $root.notebooks.length)
+                                    return $root.notebooks[idx];
+                            }
+
+                            if ($root.notebooks.length > 0)
+                                return $root.notebooks[$root.notebooks.length - 1];
+                        })
+                        .catch(({data}) => $q.reject(data));
+                }
+            };
+        }];
+    });

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-console/src/main/js/app/modules/states/configuration.state.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/modules/states/configuration.state.js b/modules/web-console/src/main/js/app/modules/states/configuration.state.js
index 4a0578c..7e5e95e 100644
--- a/modules/web-console/src/main/js/app/modules/states/configuration.state.js
+++ b/modules/web-console/src/main/js/app/modules/states/configuration.state.js
@@ -55,6 +55,19 @@ import clustersSwap from './configuration/clusters/swap.directive';
 import clustersTime from './configuration/clusters/time.directive';
 import clustersThread from './configuration/clusters/thread.directive';
 import clustersTransactions from './configuration/clusters/transactions.directive';
+import clustersUserAttributes from './configuration/clusters/attributes.directive';
+import clustersCollision from './configuration/clusters/collision.directive';
+import clustersFailover from './configuration/clusters/failover.directive';
+import clustersLogger from './configuration/clusters/logger.directive';
+
+import clustersCollisionJobStealing from './configuration/clusters/collision/job-stealing.directive';
+import clustersCollisionFifoQueue from './configuration/clusters/collision/fifo-queue.directive';
+import clustersCollisionPriorityQueue from './configuration/clusters/collision/priority-queue.directive';
+import clustersCollisionCustom from './configuration/clusters/collision/custom.directive';
+
+import clustersLoggerLog4j2 from './configuration/clusters/logger/log4j2.directive';
+import clustersLoggerLog4j from './configuration/clusters/logger/log4j.directive';
+import clustersLoggerCustom from './configuration/clusters/logger/custom.directive';
 
 // Domains screen.
 import domainsGeneral from './configuration/domains/general.directive';
@@ -87,6 +100,17 @@ import summaryTabs from './configuration/summary/summary-tabs.directive';
 angular.module('ignite-console.states.configuration', ['ui.router'])
     // Clusters screen.
     .directive(...previewPanel)
+    .directive(...clustersLoggerCustom)
+    .directive(...clustersLoggerLog4j)
+    .directive(...clustersLoggerLog4j2)
+    .directive(...clustersLogger)
+    .directive(...clustersFailover)
+    .directive(...clustersCollisionCustom)
+    .directive(...clustersCollisionPriorityQueue)
+    .directive(...clustersCollisionFifoQueue)
+    .directive(...clustersCollisionJobStealing)
+    .directive(...clustersCollision)
+    .directive(...clustersUserAttributes)
     .directive(...clustersTransactions)
     .directive(...clustersThread)
     .directive(...clustersTime)
@@ -154,7 +178,7 @@ angular.module('ignite-console.states.configuration', ['ui.router'])
                 url: '/clusters',
                 templateUrl: '/configuration/clusters.html',
                 params: {
-                    id: null
+                    linkId: null
                 },
                 metaTags: {
                     title: 'Configure Clusters'
@@ -164,7 +188,7 @@ angular.module('ignite-console.states.configuration', ['ui.router'])
                 url: '/caches',
                 templateUrl: '/configuration/caches.html',
                 params: {
-                    id: null
+                    linkId: null
                 },
                 metaTags: {
                     title: 'Configure Caches'
@@ -174,7 +198,7 @@ angular.module('ignite-console.states.configuration', ['ui.router'])
                 url: '/domains',
                 templateUrl: '/configuration/domains.html',
                 params: {
-                    id: null
+                    linkId: null
                 },
                 metaTags: {
                     title: 'Configure Domain Model'
@@ -184,7 +208,7 @@ angular.module('ignite-console.states.configuration', ['ui.router'])
                 url: '/igfs',
                 templateUrl: '/configuration/igfs.html',
                 params: {
-                    id: null
+                    linkId: null
                 },
                 metaTags: {
                     title: 'Configure IGFS'

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-console/src/main/js/app/modules/states/configuration/caches/general.jade
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/modules/states/configuration/caches/general.jade b/modules/web-console/src/main/js/app/modules/states/configuration/caches/general.jade
index 2285825..b5e0797 100644
--- a/modules/web-console/src/main/js/app/modules/states/configuration/caches/general.jade
+++ b/modules/web-console/src/main/js/app/modules/states/configuration/caches/general.jade
@@ -31,7 +31,7 @@ form.panel.panel-default(name='general' novalidate)
                 .settings-row
                     +clusters(model, 'Associate clusters with the current cache')
                 .settings-row
-                    +dropdown-multiple('<span>Domain models:</span><a ui-sref="base.configuration.domains({id: ' + model + '._id})"> (add)</a>',
+                    +dropdown-multiple('<span>Domain models:</span><a ui-sref="base.configuration.domains({linkId: linkId()})"> (add)</a>',
                         model + '.domains', 'domains', 'true', 'Choose domain models', 'No domain models configured', 'domains',
                         'Select domain models to describe types in cache')
                 .settings-row