You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sb...@apache.org on 2017/12/15 14:10:06 UTC

[36/50] [abbrv] ignite git commit: IGNITE-7199 Web console: several minor improvements.

IGNITE-7199 Web console: several minor improvements.


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

Branch: refs/heads/ignite-zk-ce
Commit: e08d19cb785f2cba94c99d2dd4df14310ef2e8d6
Parents: 3325876
Author: Alexey Kuznetsov <ak...@apache.org>
Authored: Thu Dec 14 12:07:40 2017 +0700
Committer: Alexey Kuznetsov <ak...@apache.org>
Committed: Thu Dec 14 12:07:40 2017 +0700

----------------------------------------------------------------------
 modules/web-console/backend/.eslintrc           | 185 -------------------
 .../web-console/backend/app/agentsHandler.js    |  20 +-
 .../web-console/backend/app/browsersHandler.js  |   8 +-
 modules/web-console/backend/app/mongo.js        |   4 +
 .../backend/config/settings.json.sample         |   8 +-
 .../1508395969410-init-registered-date.js       |  33 ++++
 modules/web-console/backend/package.json        |   2 +
 modules/web-console/backend/services/Utils.js   |  51 +++++
 modules/web-console/backend/services/auth.js    |  26 +--
 modules/web-console/backend/services/caches.js  |   4 +
 .../web-console/backend/services/clusters.js    |   4 +
 modules/web-console/backend/services/domains.js |   4 +
 modules/web-console/backend/services/igfss.js   |   4 +
 modules/web-console/backend/services/mails.js   |  80 ++++----
 .../web-console/backend/services/notebooks.js   |   4 +
 modules/web-console/backend/services/users.js   |  23 +--
 modules/web-console/frontend/app/app.js         |   6 +
 .../app/components/bs-select-menu/style.scss    |  10 +-
 .../app/components/bs-select-menu/template.pug  |  15 +-
 .../app/components/grid-export/component.js     |  52 ++++++
 .../app/components/grid-export/index.js         |  24 +++
 .../app/components/grid-export/template.pug     |  18 ++
 .../app/components/grid-no-data/component.js    |  33 ++++
 .../app/components/grid-no-data/controller.js   |  50 +++++
 .../app/components/grid-no-data/index.js        |  24 +++
 .../app/components/grid-no-data/style.scss      |  31 ++++
 .../list-editable-cols/cols.template.pug        |   1 -
 .../list-editable-cols/row.directive.js         |   2 +-
 .../app/components/list-editable/style.scss     |  12 +-
 .../app/components/list-editable/template.pug   |   6 +-
 .../list-of-registered-users.tpl.pug            |   2 +-
 .../app/components/ui-grid-filters/directive.js |  62 +++++++
 .../app/components/ui-grid-filters/index.js     |  43 +++++
 .../app/components/ui-grid-filters/style.scss   |  36 ++++
 .../app/components/ui-grid-filters/template.pug |  47 +++++
 .../web-console-header-extension/component.js   |  22 +++
 .../web-console-header-extension/template.pug   |  15 ++
 .../app/components/web-console-header/index.js  |   4 +-
 .../components/web-console-header/style.scss    |   2 +-
 .../components/web-console-header/template.pug  |   6 +-
 .../app/modules/form/field/input/text.scss      |   1 +
 .../frontend/app/modules/user/permissions.js    |   4 +-
 .../frontend/app/primitives/btn/index.scss      |  17 ++
 .../app/primitives/form-field/index.scss        |   2 +-
 .../app/primitives/ui-grid-settings/index.scss  |  12 ++
 .../frontend/app/primitives/ui-grid/index.scss  |   2 +
 modules/web-console/frontend/package.json       |   3 +-
 .../frontend/public/images/checkbox-active.svg  |  25 +++
 .../frontend/public/images/checkbox.svg         |  22 +++
 .../frontend/public/images/icons/alert.svg      |   1 +
 .../frontend/public/images/icons/checkmark.svg  |   3 +
 .../frontend/public/images/icons/index.js       |   4 +-
 .../frontend/public/images/icons/sort.svg       |   2 +-
 .../frontend/views/includes/header-left.pug     |   8 +-
 .../frontend/views/includes/header-right.pug    |   4 +-
 .../frontend/views/settings/profile.tpl.pug     |   9 +-
 56 files changed, 790 insertions(+), 312 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/backend/.eslintrc
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/.eslintrc b/modules/web-console/backend/.eslintrc
deleted file mode 100644
index 1915518..0000000
--- a/modules/web-console/backend/.eslintrc
+++ /dev/null
@@ -1,185 +0,0 @@
-env:
-    es6: true
-    node: true
-    mocha: true
-
-parserOptions:
-    sourceType: module
-    ecmaFeatures:
-        arrowFunctions: true
-        blockBindings: true
-        classes: true
-        defaultParams: true
-        destructuring: true
-        module: true
-        objectLiteralComputedProperties: true
-        objectLiteralShorthandMethods: true
-        objectLiteralShorthandProperties: true
-        spread: true
-        templateStrings: true
-        experimentalObjectRestSpread: true
-
-globals:
-    _: true
-    io: true
-
-rules:
-    arrow-parens: [1, "always"]
-    arrow-spacing: [1, { "before": true, "after": true }]
-    accessor-pairs: 2
-    block-scoped-var: 2
-    brace-style: [0, "1tbs"]
-    comma-dangle: [2, "never"]
-    comma-spacing: [2, {"before": false, "after": true}]
-    comma-style: [2, "last"]
-    complexity: [1, 40]
-    computed-property-spacing: [2, "never"]
-    consistent-return: 0
-    consistent-this: [0, "that"]
-    constructor-super: 2
-    curly: [2, "multi-or-nest"]
-    default-case: 2
-    dot-location: 0
-    dot-notation: [2, { "allowKeywords": true }]
-    eol-last: 2
-    eqeqeq: 2
-    func-names: 0
-    func-style: [0, "declaration"]
-    generator-star-spacing: 0
-    guard-for-in: 1
-    handle-callback-err: 0
-    id-length: [2, {"min": 1, "max": 60}]
-    indent: [2, 4, {"SwitchCase": 1, "MemberExpression": "off", "CallExpression": {"arguments": "off"}}]
-    key-spacing: [2, { "beforeColon": false, "afterColon": true }]
-    lines-around-comment: 0
-    linebreak-style: [0, "unix"]
-    max-depth: [0, 4]
-    max-len: [0, 120, 4]
-    max-nested-callbacks: [1, 4]
-    max-params: [0, 3]
-    max-statements: [0, 10]
-    new-cap: 2
-    new-parens: 2
-    no-alert: 2
-    no-array-constructor: 2
-    no-bitwise: 0
-    no-caller: 2
-    no-catch-shadow: 2
-    no-cond-assign: 2
-    no-console: 0
-    no-constant-condition: 2
-    no-continue: 0
-    no-class-assign: 2
-    no-const-assign: 2
-    no-control-regex: 2
-    no-debugger: 2
-    no-delete-var: 2
-    no-div-regex: 0
-    no-dupe-keys: 2
-    no-dupe-args: 2
-    no-duplicate-case: 2
-    no-else-return: 2
-    no-empty: 2
-    no-empty-character-class: 2
-    no-eq-null: 2
-    no-eval: 2
-    no-ex-assign: 2
-    no-extend-native: 2
-    no-extra-bind: 2
-    no-extra-boolean-cast: 2
-    no-extra-parens: 0
-    no-extra-semi: 2
-    no-fallthrough: 2
-    no-floating-decimal: 1
-    no-func-assign: 2
-    no-implied-eval: 2
-    no-inline-comments: 0
-    no-inner-declarations: [2, "functions"]
-    no-invalid-regexp: 2
-    no-irregular-whitespace: 2
-    no-iterator: 2
-    no-label-var: 2
-    no-labels: 2
-    no-lone-blocks: 2
-    no-lonely-if: 2
-    no-implicit-coercion: [2, {"boolean": false, "number": true, "string": true}]
-    no-loop-func: 2
-    no-mixed-requires: [0, false]
-    no-mixed-spaces-and-tabs: [2, true]
-    no-multi-spaces: ["error", {"exceptions": { "VariableDeclarator": true }}]
-    no-multi-str: 2
-    no-multiple-empty-lines: [0, {"max": 2}]
-    no-native-reassign: 2
-    no-negated-in-lhs: 2
-    no-nested-ternary: 0
-    no-new: 2
-    no-new-func: 2
-    no-new-object: 2
-    no-new-require: 0
-    no-new-wrappers: 2
-    no-obj-calls: 2
-    no-octal: 2
-    no-octal-escape: 2
-    no-param-reassign: 0
-    no-path-concat: 0
-    no-plusplus: 0
-    no-process-env: 0
-    no-process-exit: 0
-    no-proto: 2
-    no-redeclare: 2
-    no-regex-spaces: 1
-    no-restricted-modules: 0
-    no-script-url: 0
-    no-self-compare: 2
-    no-sequences: 2
-    no-shadow: 0
-    no-shadow-restricted-names: 2
-    no-spaced-func: 2
-    no-sparse-arrays: 1
-    no-sync: 0
-    no-ternary: 0
-    no-trailing-spaces: 2
-    no-throw-literal: 0
-    no-this-before-super: 2
-    no-unexpected-multiline: 2
-    no-undef: 2
-    no-undef-init: 2
-    no-undefined: 2
-    no-unneeded-ternary: 2
-    no-unreachable: 2
-    no-unused-expressions: [2, { allowShortCircuit: true }]
-    no-unused-vars: [2, {"vars": "all", "args": "after-used"}]
-    no-use-before-define: 2
-    no-useless-call: 2
-    no-void: 0
-    no-var: 2
-    no-warning-comments: 0
-    no-with: 2
-    newline-after-var: 0
-    object-shorthand: [2, "always"]
-    one-var: [2, "never"]
-    operator-assignment: [2, "always"]
-    operator-linebreak: 0
-    padded-blocks: 0
-    prefer-const: 1
-    prefer-spread: 2
-    quote-props: [2, "as-needed"]
-    quotes: [2, "single", {"allowTemplateLiterals": true}]
-    radix: 1
-    semi: [2, "always"]
-    semi-spacing: [2, {"before": false, "after": true}]
-    sort-vars: 0
-    keyword-spacing: 2
-    space-before-blocks: [2, "always"]
-    space-before-function-paren: [2, "never"]
-    space-in-parens: 0
-    space-infix-ops: 2
-    space-unary-ops: [2, { "words": true, "nonwords": false }]
-    spaced-comment: [1, "always"]
-    use-isnan: 2
-    valid-jsdoc: 0
-    valid-typeof: 2
-    vars-on-top: 2
-    wrap-iife: 0
-    wrap-regex: 0
-    yoda: [2, "never"]

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/backend/app/agentsHandler.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/app/agentsHandler.js b/modules/web-console/backend/app/agentsHandler.js
index 9ee64ce..eb3a1e0 100644
--- a/modules/web-console/backend/app/agentsHandler.js
+++ b/modules/web-console/backend/app/agentsHandler.js
@@ -131,6 +131,7 @@ module.exports.factory = function(settings, mongo, AgentSocket) {
             this._agentSockets = new AgentSockets();
 
             this.clusters = [];
+            this.topLsnrs = [];
         }
 
         /**
@@ -212,6 +213,15 @@ module.exports.factory = function(settings, mongo, AgentSocket) {
         }
 
         /**
+         * Add topology listener.
+         *
+         * @param lsnr
+         */
+        addTopologyListener(lsnr) {
+            this.topLsnrs.push(lsnr);
+        }
+
+        /**
          * Link agent with browsers by account.
          *
          * @param {Socket} sock
@@ -234,7 +244,9 @@ module.exports.factory = function(settings, mongo, AgentSocket) {
             sock.on('cluster:topology', (top) => {
                 const cluster = this.getOrCreateCluster(top);
 
-                if (_.isNil(agentSocket.cluster)) {
+                _.forEach(this.topLsnrs, (lsnr) => lsnr(agentSocket, cluster, top));
+
+                if (agentSocket.cluster !== cluster) {
                     agentSocket.cluster = cluster;
 
                     _.forEach(tokens, (token) => {
@@ -254,11 +266,11 @@ module.exports.factory = function(settings, mongo, AgentSocket) {
                 }
             });
 
-            sock.on('cluster:collector', (top) => {
+            sock.on('cluster:disconnected', () => {
+                const newTop = _.assign({}, agentSocket.cluster, {nids: []});
 
-            });
+                _.forEach(this.topLsnrs, (lsnr) => lsnr(agentSocket, agentSocket.cluster, newTop));
 
-            sock.on('cluster:disconnected', () => {
                 agentSocket.cluster = null;
 
                 _.forEach(tokens, (token) => {

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/backend/app/browsersHandler.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/app/browsersHandler.js b/modules/web-console/backend/app/browsersHandler.js
index 198bc12..a8ca706 100644
--- a/modules/web-console/backend/app/browsersHandler.js
+++ b/modules/web-console/backend/app/browsersHandler.js
@@ -133,6 +133,10 @@ module.exports = {
                 _.forEach(socks, (sock) => sock.emit('cluster:changed', cluster));
             }
 
+            pushInitialData(sock) {
+                // Send initial data.
+            }
+
             emitNotification(sock) {
                 sock.emit('user:notifications', this.notification);
             }
@@ -185,7 +189,8 @@ module.exports = {
              * @return {Promise.<T>}
              */
             executeOnNode(agent, demo, params) {
-                return agent.then((agentSock) => agentSock.emitEvent('node:rest', {uri: 'ignite', demo, params}))
+                return agent
+                    .then((agentSock) => agentSock.emitEvent('node:rest', {uri: 'ignite', demo, params}))
                     .then((res) => {
                         if (res.status === 0) {
                             if (res.zipped)
@@ -310,6 +315,7 @@ module.exports = {
                             this.agentListeners(sock);
                             this.nodeListeners(sock);
 
+                            this.pushInitialData(sock);
                             this.agentStats(sock.request.user.token, [sock]);
                             this.emitNotification(sock);
                         });

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/backend/app/mongo.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/app/mongo.js b/modules/web-console/backend/app/mongo.js
index de91676..955cd47 100644
--- a/modules/web-console/backend/app/mongo.js
+++ b/modules/web-console/backend/app/mongo.js
@@ -41,8 +41,10 @@ const defineSchema = (mongoose) => {
         firstName: String,
         lastName: String,
         email: String,
+        phone: String,
         company: String,
         country: String,
+        registered: Date,
         lastLogin: Date,
         lastActivity: Date,
         admin: Boolean,
@@ -60,12 +62,14 @@ const defineSchema = (mongoose) => {
         return {
             _id: ret._id,
             email: ret.email,
+            phone: ret.phone,
             firstName: ret.firstName,
             lastName: ret.lastName,
             company: ret.company,
             country: ret.country,
             admin: ret.admin,
             token: ret.token,
+            registered: ret.registered,
             lastLogin: ret.lastLogin,
             lastActivity: ret.lastActivity
         };

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/backend/config/settings.json.sample
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/config/settings.json.sample b/modules/web-console/backend/config/settings.json.sample
index 71a64ea..aa93e07 100644
--- a/modules/web-console/backend/config/settings.json.sample
+++ b/modules/web-console/backend/config/settings.json.sample
@@ -12,11 +12,11 @@
     },
     "mail": {
         "service": "",
-        "sign": "Kind regards,<br>Apache Ignite Team",
-        "greeting": "Apache Ignite Web Console",
-        "from": "Apache Ignite Web Console <so...@somecompany.tld>",
+        "from": "Some Company Web Console <so...@some_company.com>",
+        "greeting": "Some Company Web Console",
+        "sign": "Kind regards,<br>Some Company Team",
         "auth": {
-            "user": "someusername@somecompany.tld",
+            "user": "some_username@some_company.com",
             "pass": ""
         }
     }

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/backend/migrations/1508395969410-init-registered-date.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/migrations/1508395969410-init-registered-date.js b/modules/web-console/backend/migrations/1508395969410-init-registered-date.js
new file mode 100644
index 0000000..b994cac
--- /dev/null
+++ b/modules/web-console/backend/migrations/1508395969410-init-registered-date.js
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+const _ = require('lodash');
+
+exports.up = function up(done) {
+    const accounts = this('Account');
+
+    accounts.find({}).lean().exec()
+        .then((data) => _.forEach(data, (acc) => accounts.update({_id: acc._id}, {$set: {registered: acc.lastLogin}}, {upsert: true}).exec()))
+        .then(() => done())
+        .catch(done);
+};
+
+exports.down = function down(done) {
+    this('Account').update({}, {$unset: {registered: 1}}, {multi: true}).exec()
+        .then(() => done())
+        .catch(done);
+};

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/backend/package.json
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/package.json b/modules/web-console/backend/package.json
index ba442f9..889c40d 100644
--- a/modules/web-console/backend/package.json
+++ b/modules/web-console/backend/package.json
@@ -49,6 +49,7 @@
   "dependencies": {
     "app-module-path": "2.2.0",
     "body-parser": "1.17.2",
+    "common-tags": "1.4.0",
     "connect-mongo": "1.3.2",
     "cookie-parser": "1.4.3",
     "express": "4.15.3",
@@ -62,6 +63,7 @@
     "mongodb-prebuilt": "6.3.3",
     "mongoose": "4.11.4",
     "morgan": "1.8.2",
+    "nexmo": "2.0.2",
     "nconf": "0.8.4",
     "nodemailer": "4.0.1",
     "passport": "0.3.2",

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/backend/services/Utils.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/services/Utils.js b/modules/web-console/backend/services/Utils.js
new file mode 100644
index 0000000..ec97e4e
--- /dev/null
+++ b/modules/web-console/backend/services/Utils.js
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+'use strict';
+
+// Fire me up!
+
+module.exports = {
+    implements: 'services/utils'
+};
+
+/**
+ * @returns {UtilsService}
+ */
+module.exports.factory = () => {
+    class UtilsService {
+        /**
+         * Generate token string.
+         *
+         * @param len length of string
+         * @returns {String} Random string.
+         */
+        static randomString(len) {
+            const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+            const possibleLen = possible.length;
+
+            let res = '';
+
+            for (let i = 0; i < len; i++)
+                res += possible.charAt(Math.floor(Math.random() * possibleLen));
+
+            return res;
+        }
+    }
+
+    return UtilsService;
+};

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/backend/services/auth.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/services/auth.js b/modules/web-console/backend/services/auth.js
index dde2460..986ed95 100644
--- a/modules/web-console/backend/services/auth.js
+++ b/modules/web-console/backend/services/auth.js
@@ -21,38 +21,20 @@
 
 module.exports = {
     implements: 'services/auth',
-    inject: ['mongo', 'settings', 'errors']
+    inject: ['mongo', 'settings', 'errors', 'services/utils']
 };
 
 /**
  * @param mongo
  * @param settings
  * @param errors
+ * @param {UtilsService} utilsService
  * @returns {AuthService}
  */
 
-module.exports.factory = (mongo, settings, errors) => {
+module.exports.factory = (mongo, settings, errors, utilsService) => {
     class AuthService {
         /**
-         * Generate token string.
-         *
-         * @param length - length of string
-         * @returns {string} - generated token
-         */
-        static generateResetToken(length) {
-            length = length || settings.tokenLength;
-            const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
-            const possibleLen = possible.length;
-
-            let res = '';
-
-            for (let i = 0; i < length; i++)
-                res += possible.charAt(Math.floor(Math.random() * possibleLen));
-
-            return res;
-        }
-
-        /**
          * Reset password reset token for user.
          *
          * @param email - user email
@@ -64,7 +46,7 @@ module.exports.factory = (mongo, settings, errors) => {
                     if (!user)
                         throw new errors.MissingResourceException('Account with that email address does not exists!');
 
-                    user.resetPasswordToken = AuthService.generateResetToken(settings.tokenLength);
+                    user.resetPasswordToken = utilsService.randomString(settings.tokenLength);
 
                     return user.save();
                 });

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/backend/services/caches.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/services/caches.js b/modules/web-console/backend/services/caches.js
index 5c96ccd..9cb65a1 100644
--- a/modules/web-console/backend/services/caches.js
+++ b/modules/web-console/backend/services/caches.js
@@ -58,6 +58,8 @@ module.exports.factory = (mongo, spaceService, errors) => {
             .catch((err) => {
                 if (err.code === mongo.errCodes.DUPLICATE_KEY_UPDATE_ERROR || err.code === mongo.errCodes.DUPLICATE_KEY_ERROR)
                     throw new errors.DuplicateKeyException('Cache with name: "' + cache.name + '" already exist.');
+                else
+                    throw err;
             });
     };
 
@@ -77,6 +79,8 @@ module.exports.factory = (mongo, spaceService, errors) => {
             .catch((err) => {
                 if (err.code === mongo.errCodes.DUPLICATE_KEY_ERROR)
                     throw new errors.DuplicateKeyException('Cache with name: "' + cache.name + '" already exist.');
+                else
+                    throw err;
             });
     };
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/backend/services/clusters.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/services/clusters.js b/modules/web-console/backend/services/clusters.js
index 9f50ede..49e6f09 100644
--- a/modules/web-console/backend/services/clusters.js
+++ b/modules/web-console/backend/services/clusters.js
@@ -58,6 +58,8 @@ module.exports.factory = (mongo, spacesService, errors) => {
             .catch((err) => {
                 if (err.code === mongo.errCodes.DUPLICATE_KEY_UPDATE_ERROR || err.code === mongo.errCodes.DUPLICATE_KEY_ERROR)
                     throw new errors.DuplicateKeyException('Cluster with name: "' + cluster.name + '" already exist.');
+                else
+                    throw err;
             });
     };
 
@@ -77,6 +79,8 @@ module.exports.factory = (mongo, spacesService, errors) => {
             .catch((err) => {
                 if (err.code === mongo.errCodes.DUPLICATE_KEY_ERROR)
                     throw new errors.DuplicateKeyException('Cluster with name: "' + cluster.name + '" already exist.');
+                else
+                    throw err;
             });
     };
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/backend/services/domains.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/services/domains.js b/modules/web-console/backend/services/domains.js
index e25d091..986991d 100644
--- a/modules/web-console/backend/services/domains.js
+++ b/modules/web-console/backend/services/domains.js
@@ -65,6 +65,8 @@ module.exports.factory = (mongo, spacesService, cachesService, errors) => {
             .catch((err) => {
                 if (err.code === mongo.errCodes.DUPLICATE_KEY_UPDATE_ERROR || err.code === mongo.errCodes.DUPLICATE_KEY_ERROR)
                     throw new errors.DuplicateKeyException('Domain model with value type: "' + domain.valueType + '" already exist.');
+                else
+                    throw err;
             });
     };
 
@@ -86,6 +88,8 @@ module.exports.factory = (mongo, spacesService, cachesService, errors) => {
             .catch((err) => {
                 if (err.code === mongo.errCodes.DUPLICATE_KEY_ERROR)
                     throw new errors.DuplicateKeyException('Domain model with value type: "' + domain.valueType + '" already exist.');
+                else
+                    throw err;
             });
     };
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/backend/services/igfss.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/services/igfss.js b/modules/web-console/backend/services/igfss.js
index e9ff38e..5296f16 100644
--- a/modules/web-console/backend/services/igfss.js
+++ b/modules/web-console/backend/services/igfss.js
@@ -56,6 +56,8 @@ module.exports.factory = (mongo, spacesService, errors) => {
             .catch((err) => {
                 if (err.code === mongo.errCodes.DUPLICATE_KEY_UPDATE_ERROR || err.code === mongo.errCodes.DUPLICATE_KEY_ERROR)
                     throw new errors.DuplicateKeyException('IGFS with name: "' + igfs.name + '" already exist.');
+                else
+                    throw err;
             });
     };
 
@@ -74,6 +76,8 @@ module.exports.factory = (mongo, spacesService, errors) => {
             .catch((err) => {
                 if (err.code === mongo.errCodes.DUPLICATE_KEY_ERROR)
                     throw new errors.DuplicateKeyException('IGFS with name: "' + igfs.name + '" already exist.');
+                else
+                    throw err;
             });
     };
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/backend/services/mails.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/services/mails.js b/modules/web-console/backend/services/mails.js
index 6a31e93..c16db3b 100644
--- a/modules/web-console/backend/services/mails.js
+++ b/modules/web-console/backend/services/mails.js
@@ -32,44 +32,44 @@ module.exports = {
  * @returns {MailsService}
  */
 module.exports.factory = (settings) => {
-    /**
-     * Send mail to user.
-     *
-     * @param {Account} user
-     * @param {String} subject
-     * @param {String} html
-     * @param {String} sendErr
-     * @throws {Error}
-     * @return {Promise}
-     */
-    const send = (user, subject, html, sendErr) => {
-        return new Promise((resolve, reject) => {
-            const transportConfig = settings.mail;
-
-            if (_.isEmpty(transportConfig.service) || _.isEmpty(transportConfig.auth.user) || _.isEmpty(transportConfig.auth.pass))
-                throw new Error('Failed to send email. SMTP server is not configured. Please ask webmaster to setup SMTP server!');
-
-            const mailer = nodemailer.createTransport(transportConfig);
-
-            const sign = settings.mail.sign ? `<br><br>--------------<br>${settings.mail.sign}<br>` : '';
-
-            const mail = {
-                from: settings.mail.from,
-                to: settings.mail.address(`${user.firstName} ${user.lastName}`, user.email),
-                subject,
-                html: html + sign
-            };
-
-            mailer.sendMail(mail, (err) => {
-                if (err)
-                    return reject(sendErr ? new Error(sendErr) : err);
-
-                resolve(user);
+    class MailsService {
+        /**
+         * Send mail to user.
+         *
+         * @param {Account} user
+         * @param {String} subject
+         * @param {String} html
+         * @param {String} sendErr
+         * @throws {Error}
+         * @return {Promise}
+         */
+        static send(user, subject, html, sendErr) {
+            return new Promise((resolve, reject) => {
+                const transportConfig = settings.mail;
+
+                if (_.isEmpty(transportConfig.service) || _.isEmpty(transportConfig.auth.user) || _.isEmpty(transportConfig.auth.pass))
+                    throw new Error('Failed to send email. SMTP server is not configured. Please ask webmaster to setup SMTP server!');
+
+                const mailer = nodemailer.createTransport(transportConfig);
+
+                const sign = settings.mail.sign ? `<br><br>--------------<br>${settings.mail.sign}<br>` : '';
+
+                const mail = {
+                    from: settings.mail.from,
+                    to: settings.mail.address(`${user.firstName} ${user.lastName}`, user.email),
+                    subject,
+                    html: html + sign
+                };
+
+                mailer.sendMail(mail, (err) => {
+                    if (err)
+                        return reject(sendErr ? new Error(sendErr) : err);
+
+                    resolve(user);
+                });
             });
-        });
-    };
+        }
 
-    class MailsService {
         /**
          * Send email to user for password reset.
          * @param host
@@ -78,7 +78,7 @@ module.exports.factory = (settings) => {
         static emailUserSignUp(host, user) {
             const resetLink = `${host}/password/reset?token=${user.resetPasswordToken}`;
 
-            return send(user, `Thanks for signing up for ${settings.mail.greeting}.`,
+            return MailsService.send(user, `Thanks for signing up for ${settings.mail.greeting}.`,
                 `Hello ${user.firstName} ${user.lastName}!<br><br>` +
                 `You are receiving this email because you have signed up to use <a href="${host}">${settings.mail.greeting}</a>.<br><br>` +
                 'If you have not done the sign up and do not know what this email is about, please ignore it.<br>' +
@@ -94,7 +94,7 @@ module.exports.factory = (settings) => {
         static emailUserResetLink(host, user) {
             const resetLink = `${host}/password/reset?token=${user.resetPasswordToken}`;
 
-            return send(user, 'Password Reset',
+            return MailsService.send(user, 'Password Reset',
                 `Hello ${user.firstName} ${user.lastName}!<br><br>` +
                 'You are receiving this because you (or someone else) have requested the reset of the password for your account.<br><br>' +
                 'Please click on the following link, or paste this into your browser to complete the process:<br><br>' +
@@ -109,7 +109,7 @@ module.exports.factory = (settings) => {
          * @param user
          */
         static emailPasswordChanged(host, user) {
-            return send(user, 'Your password has been changed',
+            return MailsService.send(user, 'Your password has been changed',
                 `Hello ${user.firstName} ${user.lastName}!<br><br>` +
                 `This is a confirmation that the password for your account on <a href="${host}">${settings.mail.greeting}</a> has just been changed.<br><br>`,
                 'Password was changed, but failed to send confirmation email!');
@@ -121,7 +121,7 @@ module.exports.factory = (settings) => {
          * @param user
          */
         static emailUserDeletion(host, user) {
-            return send(user, 'Your account was removed',
+            return MailsService.send(user, 'Your account was removed',
                 `Hello ${user.firstName} ${user.lastName}!<br><br>` +
                 `You are receiving this email because your account for <a href="${host}">${settings.mail.greeting}</a> was removed.`,
                 'Account was removed, but failed to send email notification to user!');

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/backend/services/notebooks.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/services/notebooks.js b/modules/web-console/backend/services/notebooks.js
index 5d8b57d..4ae89d2 100644
--- a/modules/web-console/backend/services/notebooks.js
+++ b/modules/web-console/backend/services/notebooks.js
@@ -51,6 +51,8 @@ module.exports.factory = (mongo, spacesService, errors) => {
             .catch((err) => {
                 if (err.code === mongo.errCodes.DUPLICATE_KEY_UPDATE_ERROR || err.code === mongo.errCodes.DUPLICATE_KEY_ERROR)
                     throw new errors.DuplicateKeyException('Notebook with name: "' + notebook.name + '" already exist.');
+                else
+                    throw err;
             });
     };
 
@@ -65,6 +67,8 @@ module.exports.factory = (mongo, spacesService, errors) => {
             .catch((err) => {
                 if (err.code === mongo.errCodes.DUPLICATE_KEY_ERROR)
                     throw new errors.DuplicateKeyException('Notebook with name: "' + notebook.name + '" already exist.');
+                else
+                    throw err;
             });
     };
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/backend/services/users.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/services/users.js b/modules/web-console/backend/services/users.js
index 620f7e9..f804840 100644
--- a/modules/web-console/backend/services/users.js
+++ b/modules/web-console/backend/services/users.js
@@ -23,7 +23,7 @@ const _ = require('lodash');
 
 module.exports = {
     implements: 'services/users',
-    inject: ['errors', 'settings', 'mongo', 'services/spaces', 'services/mails', 'services/activities', 'agents-handler']
+    inject: ['errors', 'settings', 'mongo', 'services/spaces', 'services/mails', 'services/activities', 'services/utils', 'agents-handler']
 };
 
 /**
@@ -33,22 +33,11 @@ module.exports = {
  * @param {SpacesService} spacesService
  * @param {MailsService} mailsService
  * @param {ActivitiesService} activitiesService
+ * @param {UtilsService} utilsService
  * @param {AgentsHandler} agentHnd
  * @returns {UsersService}
  */
-module.exports.factory = (errors, settings, mongo, spacesService, mailsService, activitiesService, agentHnd) => {
-    const _randomString = () => {
-        const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
-        const possibleLen = possible.length;
-
-        let res = '';
-
-        for (let i = 0; i < settings.tokenLength; i++)
-            res += possible.charAt(Math.floor(Math.random() * possibleLen));
-
-        return res;
-    };
-
+module.exports.factory = (errors, settings, mongo, spacesService, mailsService, activitiesService, utilsService, agentHnd) => {
     class UsersService {
         /**
          * Save profile information.
@@ -61,8 +50,8 @@ module.exports.factory = (errors, settings, mongo, spacesService, mailsService,
             return mongo.Account.count().exec()
                 .then((cnt) => {
                     user.admin = cnt === 0;
-
-                    user.token = _randomString();
+                    user.registered = new Date();
+                    user.token = utilsService.randomString(settings.tokenLength);
 
                     return new mongo.Account(user);
                 })
@@ -80,7 +69,7 @@ module.exports.factory = (errors, settings, mongo, spacesService, mailsService,
                     });
                 })
                 .then((registered) => {
-                    registered.resetPasswordToken = _randomString();
+                    registered.resetPasswordToken = utilsService.randomString(settings.tokenLength);
 
                     return registered.save()
                         .then(() => mongo.Space.create({name: 'Personal space', owner: registered._id}))

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/app.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/app.js b/modules/web-console/frontend/app/app.js
index 5a27ea2..a1cd6eb 100644
--- a/modules/web-console/frontend/app/app.js
+++ b/modules/web-console/frontend/app/app.js
@@ -124,9 +124,12 @@ import pageConfigureAdvanced from './components/page-configure-advanced';
 import pageQueries from './components/page-queries';
 import gridColumnSelector from './components/grid-column-selector';
 import gridItemSelected from './components/grid-item-selected';
+import gridNoData from './components/grid-no-data';
+import gridExport from './components/grid-export';
 import bsSelectMenu from './components/bs-select-menu';
 import protectFromBsSelectRender from './components/protect-from-bs-select-render';
 import uiGridHovering from './components/ui-grid-hovering';
+import uiGridFilters from './components/ui-grid-filters';
 import listEditable from './components/list-editable';
 import clusterSelector from './components/cluster-selector';
 import connectedClusters from './components/connected-clusters';
@@ -201,8 +204,11 @@ angular.module('ignite-console', [
     pageQueries.name,
     gridColumnSelector.name,
     gridItemSelected.name,
+    gridNoData.name,
+    gridExport.name,
     bsSelectMenu.name,
     uiGridHovering.name,
+    uiGridFilters.name,
     protectFromBsSelectRender.name,
     AngularStrapTooltip.name,
     AngularStrapSelect.name,

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/components/bs-select-menu/style.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/bs-select-menu/style.scss b/modules/web-console/frontend/app/components/bs-select-menu/style.scss
index ccf33a3..d82bf19 100644
--- a/modules/web-console/frontend/app/components/bs-select-menu/style.scss
+++ b/modules/web-console/frontend/app/components/bs-select-menu/style.scss
@@ -31,7 +31,6 @@
     overflow-y: auto;
     overflow-x: hidden;
     max-height: $max-visible-items * $item-height;
-    max-width: 280px;
     box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.3);
     border-radius: $ignite-button-border-radius;
     border: 1px solid #c5c5c5;
@@ -76,6 +75,15 @@
         }
     }
 
+    .bssm-click-overlay {
+        position: fixed;
+        top: 0;
+        right: 0;
+        bottom: 0;
+        left: 0;
+        z-index: -1;
+    }
+    
     &.bssm-multiple {
         .bssm-active-indicator {
             display: initial;

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/components/bs-select-menu/template.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/bs-select-menu/template.pug b/modules/web-console/frontend/app/components/bs-select-menu/template.pug
index a9c1c28..c8c6eaa 100644
--- a/modules/web-console/frontend/app/components/bs-select-menu/template.pug
+++ b/modules/web-console/frontend/app/components/bs-select-menu/template.pug
@@ -25,11 +25,8 @@ ul.bs-select-menu(
             ng-click='$ctrl.areAllSelected() ? $selectNone() : $selectAll()'
             type='button'
         )
-            span.bssm-active-indicator.icon.icon-left.fa(
-                ng-class=`{
-                    'fa-check-square bssm-active-indicator__active': $ctrl.areAllSelected(),
-                    'fa-square-o': !$ctrl.areAllSelected()
-                }`
+            img.bssm-active-indicator.icon-left(
+                ng-src='{{ $ctrl.areAllSelected() ? "/images/checkbox-active.svg" : "/images/checkbox.svg" }}'
             )
             | All
     li(role='presentation' ng-repeat='match in $matches')
@@ -42,10 +39,8 @@ ul.bs-select-menu(
             data-placement='right auto'
             title='{{ ::match.label }}'
         )
-            span.bssm-active-indicator.icon.icon-left.fa(
-                ng-class=`{
-                    'fa-check-square bssm-active-indicator__active': $isActive($index),
-                    'fa-square-o': !$isActive($index)
-                }`
+            img.bssm-active-indicator.icon-left(
+                ng-src='{{ $isActive($index) ? "/images/checkbox-active.svg" : "/images/checkbox.svg" }}'
             )
             span.bssm-item-text(ng-bind-html='match.label')
+    .bssm-click-overlay(ng-click='$hide()')

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/components/grid-export/component.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/grid-export/component.js b/modules/web-console/frontend/app/components/grid-export/component.js
new file mode 100644
index 0000000..9c239a6
--- /dev/null
+++ b/modules/web-console/frontend/app/components/grid-export/component.js
@@ -0,0 +1,52 @@
+/*
+ * 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 template from './template.pug';
+
+export default {
+    template,
+    controller: class {
+        static $inject = ['$scope', 'uiGridGroupingConstants', 'uiGridExporterService', 'uiGridExporterConstants'];
+
+        constructor($scope, uiGridGroupingConstants, uiGridExporterService, uiGridExporterConstants) {
+            Object.assign(this, { uiGridGroupingConstants, uiGridExporterService, uiGridExporterConstants });
+        }
+
+        export() {
+            const data = [];
+            const columnHeaders = this.uiGridExporterService.getColumnHeaders(this.gridApi.grid, this.uiGridExporterConstants.VISIBLE);
+
+            _.forEach(this.gridApi.grid.rows, (row) => {
+                if (!row.visible)
+                    return;
+
+                const values = [];
+                _.forEach(columnHeaders, ({ name }) => {
+                    values.push({ value: row.entity[name] });
+                });
+
+                data.push(values);
+            });
+
+            const csvContent = this.uiGridExporterService.formatAsCsv(columnHeaders, data, this.gridApi.grid.options.exporterCsvColumnSeparator);
+            this.uiGridExporterService.downloadFile(this.gridApi.grid.options.exporterCsvFilename, csvContent, this.gridApi.grid.options.exporterOlderExcelCompatibility);
+        }
+    },
+    bindings: {
+        gridApi: '<'
+    }
+};

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/components/grid-export/index.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/grid-export/index.js b/modules/web-console/frontend/app/components/grid-export/index.js
new file mode 100644
index 0000000..9fa8835
--- /dev/null
+++ b/modules/web-console/frontend/app/components/grid-export/index.js
@@ -0,0 +1,24 @@
+/*
+ * 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 component from './component';
+
+export default angular
+    .module('ignite-console.grid-export', [])
+    .component('gridExport', component);

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/components/grid-export/template.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/grid-export/template.pug b/modules/web-console/frontend/app/components/grid-export/template.pug
new file mode 100644
index 0000000..99780ee
--- /dev/null
+++ b/modules/web-console/frontend/app/components/grid-export/template.pug
@@ -0,0 +1,18 @@
+//-
+    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.
+
+button.btn-ignite.btn-ignite--primary-outline(ng-click='$ctrl.export()' bs-tooltip='' data-title='Export filtered rows to CSV' data-placement='top')
+    svg(ignite-icon='csv')

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/components/grid-no-data/component.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/grid-no-data/component.js b/modules/web-console/frontend/app/components/grid-no-data/component.js
new file mode 100644
index 0000000..aa219df
--- /dev/null
+++ b/modules/web-console/frontend/app/components/grid-no-data/component.js
@@ -0,0 +1,33 @@
+/*
+ * 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 './style.scss';
+import controller from './controller.js';
+
+export default {
+    template: `
+        <div ng-if='$ctrl.noData' ng-transclude></div>
+        <div ng-if='$ctrl.noDataFiltered' ng-transclude='noDataFiltered'></div>
+    `,
+    controller,
+    bindings: {
+        gridApi: '<'
+    },
+    transclude: {
+        noDataFiltered: '?gridNoDataFiltered'
+    }
+};

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/components/grid-no-data/controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/grid-no-data/controller.js b/modules/web-console/frontend/app/components/grid-no-data/controller.js
new file mode 100644
index 0000000..95e8a5e
--- /dev/null
+++ b/modules/web-console/frontend/app/components/grid-no-data/controller.js
@@ -0,0 +1,50 @@
+/*
+ * 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 filter from 'lodash/fp/filter';
+
+const rowsFiltered = filter(({ visible }) => visible);
+
+export default class {
+    static $inject = ['$scope', 'uiGridConstants'];
+
+    constructor($scope, uiGridConstants) {
+        Object.assign(this, {$scope, uiGridConstants});
+
+        this.noData = true;
+    }
+
+    $onChanges(changes) {
+        if (changes && 'gridApi' in changes && changes.gridApi.currentValue) {
+            this.gridApi.core.on.rowsVisibleChanged(this.$scope, () => {
+                this.applyValues();
+            });
+        }
+    }
+
+    applyValues() {
+        if (!this.gridApi.grid.rows.length) {
+            this.noData = true;
+            return;
+        }
+
+        this.noData = false;
+
+        const filtered = rowsFiltered(this.gridApi.grid.rows);
+        this.noDataFiltered = !filtered.length;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/components/grid-no-data/index.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/grid-no-data/index.js b/modules/web-console/frontend/app/components/grid-no-data/index.js
new file mode 100644
index 0000000..2acecf9
--- /dev/null
+++ b/modules/web-console/frontend/app/components/grid-no-data/index.js
@@ -0,0 +1,24 @@
+/*
+ * 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 component from './component';
+
+export default angular
+    .module('ignite-console.grid-no-data', [])
+    .component('gridNoData', component);

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/components/grid-no-data/style.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/grid-no-data/style.scss b/modules/web-console/frontend/app/components/grid-no-data/style.scss
new file mode 100644
index 0000000..0a51ac2
--- /dev/null
+++ b/modules/web-console/frontend/app/components/grid-no-data/style.scss
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+grid-no-data {
+    position: relative;
+    display: block;
+    padding: 0 51px;
+
+    border-radius: 0 0 4px 4px;
+    
+    font-style: italic;
+    line-height: 16px;
+
+    [ng-transclude] {
+    	padding: 16px 0;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/components/list-editable/components/list-editable-cols/cols.template.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/list-editable/components/list-editable-cols/cols.template.pug b/modules/web-console/frontend/app/components/list-editable/components/list-editable-cols/cols.template.pug
index 2331173..f160707 100644
--- a/modules/web-console/frontend/app/components/list-editable/components/list-editable-cols/cols.template.pug
+++ b/modules/web-console/frontend/app/components/list-editable/components/list-editable-cols/cols.template.pug
@@ -15,7 +15,6 @@
     limitations under the License.
 
 .list-editable-cols__header(
-    ng-show='$ctrl.ngModel.$viewValue.length'
     ng-class='::$ctrl.rowClass'
 )
     .list-editable-cols__header-cell(ng-repeat='col in ::$ctrl.colDefs' ng-class='::col.cellClass')

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/components/list-editable/components/list-editable-cols/row.directive.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/list-editable/components/list-editable-cols/row.directive.js b/modules/web-console/frontend/app/components/list-editable/components/list-editable-cols/row.directive.js
index e427ab5..32d75f9 100644
--- a/modules/web-console/frontend/app/components/list-editable/components/list-editable-cols/row.directive.js
+++ b/modules/web-console/frontend/app/components/list-editable/components/list-editable-cols/row.directive.js
@@ -33,7 +33,7 @@ export default function() {
                 el.addClass(ctrl.rowClass);
 
             ctrl.colDefs.forEach(({ cellClass }, index) => {
-                children[index].classList.add(...(Array.isArray(cellClass) ? cellClass : [cellClass]));
+                _.forEach((Array.isArray(cellClass) ? cellClass : [cellClass]), (item) => children[index].classList.add(item));
             });
         }
     };

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/components/list-editable/style.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/list-editable/style.scss b/modules/web-console/frontend/app/components/list-editable/style.scss
index 0f3f8ae..83ce0d4 100644
--- a/modules/web-console/frontend/app/components/list-editable/style.scss
+++ b/modules/web-console/frontend/app/components/list-editable/style.scss
@@ -33,7 +33,8 @@ list-editable {
         display: flex;
         align-items: center;
         min-height: $min-height;
-        padding: 5px 10px;
+        padding: 5px 20px;
+        margin: -6px 0;
 
         font-style: italic;
     }
@@ -76,6 +77,10 @@ list-editable {
             justify-content: center;
         }
 
+        &-sort {
+            display: none;
+        }
+
         &-cross {
             [ignite-icon] {
                 width: 12px;
@@ -105,11 +110,6 @@ list-editable {
         }
 
         &:not(.le-row--has-item-view) {
-            & > .le-row-index,
-            & > .le-row-cross {
-                margin-top: 18px;
-            }
-
             align-items: flex-start;
         }
     }

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/components/list-editable/template.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/list-editable/template.pug b/modules/web-console/frontend/app/components/list-editable/template.pug
index a713188..2900602 100644
--- a/modules/web-console/frontend/app/components/list-editable/template.pug
+++ b/modules/web-console/frontend/app/components/list-editable/template.pug
@@ -14,8 +14,9 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 
-.le-body(ng-if='$ctrl.ngModel.$viewValue.length')
+.le-body()
     .le-row(
+        ng-if='$ctrl.ngModel.$viewValue.length'
         ng-repeat='item in $ctrl.ngModel.$viewValue track by $ctrl.$index(item, $index)'
         ng-class=`{
             'le-row--editable': $ctrl.isEditView($index),
@@ -46,4 +47,5 @@
             button.btn-ignite.btn-ignite--link-dashed-secondary(type='button' ng-click='$ctrl.remove($index)')
                 svg(ignite-icon='cross')
 
-div(ng-transclude='noItems' ng-if='!$ctrl.ngModel.$viewValue.length')
+    .le-row(ng-if='!$ctrl.ngModel.$viewValue.length')
+        div(ng-transclude='noItems')

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.tpl.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.tpl.pug b/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.tpl.pug
index e311246..11ff7bc 100644
--- a/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.tpl.pug
+++ b/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.tpl.pug
@@ -45,7 +45,7 @@ include /app/helpers/jade/mixins
                     required: false,
                     options: '$ctrl.actionOptions'
                 })
-                button.btn-ignite.btn-ignite--primary-outline(ng-click='$ctrl.exportCsv()' bs-tooltip='' data-title='Export table to csv' data-placement='top')
+                button.btn-ignite.btn-ignite--primary-outline(ng-click='$ctrl.exportCsv()' bs-tooltip='' data-title='Export table to CSV' data-placement='top')
                     svg(ignite-icon='csv')
                 form.ui-grid-settings-dateperiod(name=form novalidate)
                     -var form = 'admin'

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/components/ui-grid-filters/directive.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/ui-grid-filters/directive.js b/modules/web-console/frontend/app/components/ui-grid-filters/directive.js
new file mode 100644
index 0000000..2e18933
--- /dev/null
+++ b/modules/web-console/frontend/app/components/ui-grid-filters/directive.js
@@ -0,0 +1,62 @@
+/*
+ * 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 template from './template.pug';
+import './style.scss';
+
+export default function uiGridFilters(uiGridConstants) {
+    return {
+        require: 'uiGrid',
+        link: {
+            pre(scope, el, attr, grid) {
+                if (!grid.grid.options.enableFiltering) return;
+                grid.grid.options.columnDefs.filter((cd) => cd.multiselectFilterOptions).forEach((cd) => {
+                    cd.headerCellTemplate = template;
+                    cd.filter = {
+                        type: uiGridConstants.filter.SELECT,
+                        term: cd.multiselectFilterOptions.map((t) => t.value),
+                        condition(searchTerm, cellValue) {
+                            return searchTerm.includes(cellValue);
+                        },
+                        selectOptions: cd.multiselectFilterOptions,
+                        $$selectOptionsMapping: cd.multiselectFilterOptions.reduce((a, v) => Object.assign(a, {[v.value]: v.label}), {}),
+                        $$multiselectFilterTooltip() {
+                            const prefix = 'Active filter';
+                            switch (this.term.length) {
+                                case 0:
+                                    return `${prefix}: show none`;
+                                default:
+                                    return `${prefix}: ${this.term.map((t) => this.$$selectOptionsMapping[t]).join(', ')}`;
+                                case this.selectOptions.length:
+                                    return `${prefix}: show all`;
+                            }
+                        }
+                    };
+                    if (!cd.cellTemplate) {
+                        cd.cellTemplate = `
+                            <div class="ui-grid-cell-contents">
+                                {{ col.colDef.filter.$$selectOptionsMapping[row.entity[col.field]] }}
+                            </div>
+                        `;
+                    }
+                });
+            }
+        }
+    };
+}
+
+uiGridFilters.$inject = ['uiGridConstants'];

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/components/ui-grid-filters/index.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/ui-grid-filters/index.js b/modules/web-console/frontend/app/components/ui-grid-filters/index.js
new file mode 100644
index 0000000..0f05b77
--- /dev/null
+++ b/modules/web-console/frontend/app/components/ui-grid-filters/index.js
@@ -0,0 +1,43 @@
+/*
+ * 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 directive from './directive';
+import flow from 'lodash/flow';
+
+export default angular
+    .module('ignite-console.ui-grid-filters', ['ui.grid'])
+    .decorator('$tooltip', ['$delegate', ($delegate) => {
+        return function(el, config) {
+            const instance = $delegate(el, config);
+            instance.$referenceElement = el;
+            instance.destroy = flow(instance.destroy, () => instance.$referenceElement = null);
+            instance.$applyPlacement = flow(instance.$applyPlacement, () => {
+                if (!instance.$element) return;
+                const refWidth = instance.$referenceElement[0].getBoundingClientRect().width;
+                const elWidth = instance.$element[0].getBoundingClientRect().width;
+                if (refWidth > elWidth) {
+                    instance.$element.css({
+                        width: refWidth,
+                        maxWidth: 'initial'
+                    });
+                }
+            });
+            return instance;
+        };
+    }])
+    .directive('uiGridFilters', directive);

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/components/ui-grid-filters/style.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/ui-grid-filters/style.scss b/modules/web-console/frontend/app/components/ui-grid-filters/style.scss
new file mode 100644
index 0000000..629cbad
--- /dev/null
+++ b/modules/web-console/frontend/app/components/ui-grid-filters/style.scss
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+.ui-grid-filters[role="columnheader"] {
+	display: flex;
+
+    // Decrease horizontal padding because multiselect button already has it
+    padding-left: 8px !important;
+    padding-right: 8px !important;
+
+    & > div:first-child {
+    	flex: auto !important;
+    }
+
+    .ui-grid-cell-contents[role="button"] {
+        flex: auto !important;
+        flex-basis: 100% !important;
+
+        padding: 0 !important;
+        overflow: visible !important;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/components/ui-grid-filters/template.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/ui-grid-filters/template.pug b/modules/web-console/frontend/app/components/ui-grid-filters/template.pug
new file mode 100644
index 0000000..c898078
--- /dev/null
+++ b/modules/web-console/frontend/app/components/ui-grid-filters/template.pug
@@ -0,0 +1,47 @@
+//-
+    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.
+
+.ui-grid-filter-container.ui-grid-filters(role='columnheader')
+    div(ng-style='col.extraStyle'
+        ng-repeat='colFilter in col.filters'
+        ng-class="{'ui-grid-filter-cancel-button-hidden' : colFilter.disableCancelFilterButton === true }"
+        ng-switch='colFilter.type')
+        div(ng-switch-when='select')
+            button.btn-ignite.btn-ignite--link-dashed-success(
+                ng-class=`{
+                    'bold': colFilter.term.length !== colFilter.selectOptions.length
+                }`
+                type='button'
+                title='{{ colFilter.$$multiselectFilterTooltip() }}'
+                ng-model='colFilter.term'
+                bs-select
+                bs-options='option.value as option.label for option in colFilter.selectOptions'
+                data-multiple='true'
+                data-trigger='click'
+                data-placement='bottom-left'
+                protect-from-bs-select-render
+            ) {{ col.displayName }}
+
+    .ui-grid-cell-contents(role='button')
+        button.btn-ignite.btn-ignite--link-dashed-success(
+            ui-grid-one-bind-id-grid="col.uid + '-sortdir-text'"
+            ui-grid-visible="col.sort.direction"
+            aria-label="Sort Descending")
+            i(ng-class="{\
+                'ui-grid-icon-up-dir': col.sort.direction == 'asc',\
+                'ui-grid-icon-down-dir': col.sort.direction == 'desc',\
+                'ui-grid-icon-blank': !col.sort.direction\
+            }" title="" aria-hidden="true")

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/components/web-console-header/components/web-console-header-extension/component.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/web-console-header/components/web-console-header-extension/component.js b/modules/web-console/frontend/app/components/web-console-header/components/web-console-header-extension/component.js
new file mode 100644
index 0000000..1621adc
--- /dev/null
+++ b/modules/web-console/frontend/app/components/web-console-header/components/web-console-header-extension/component.js
@@ -0,0 +1,22 @@
+/*
+ * 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 template from './template.pug';
+
+export default {
+    template
+};

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/components/web-console-header/components/web-console-header-extension/template.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/web-console-header/components/web-console-header-extension/template.pug b/modules/web-console/frontend/app/components/web-console-header/components/web-console-header-extension/template.pug
new file mode 100644
index 0000000..0545db1
--- /dev/null
+++ b/modules/web-console/frontend/app/components/web-console-header/components/web-console-header-extension/template.pug
@@ -0,0 +1,15 @@
+//-
+    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.

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/components/web-console-header/index.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/web-console-header/index.js b/modules/web-console/frontend/app/components/web-console-header/index.js
index e41e1cc..3fc5c66 100644
--- a/modules/web-console/frontend/app/components/web-console-header/index.js
+++ b/modules/web-console/frontend/app/components/web-console-header/index.js
@@ -17,7 +17,9 @@
 
 import angular from 'angular';
 import component from './component';
+import componentExtension from './components/web-console-header-extension/component';
 
 export default angular
     .module('ignite-console.web-console-header', [])
-    .component('webConsoleHeader', component);
+    .component('webConsoleHeader', component)
+    .component('webConsoleHeaderExtension', componentExtension);

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/components/web-console-header/style.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/web-console-header/style.scss b/modules/web-console/frontend/app/components/web-console-header/style.scss
index f221a00..db49c3e 100644
--- a/modules/web-console/frontend/app/components/web-console-header/style.scss
+++ b/modules/web-console/frontend/app/components/web-console-header/style.scss
@@ -18,7 +18,7 @@
 web-console-header {
     @import "./../../../public/stylesheets/variables.scss";
 
-    $nav-item-margin: 40px;
+    $nav-item-margin: 30px;
     $bottom-border-width: 4px;
 
     display: block;

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/components/web-console-header/template.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/web-console-header/template.pug b/modules/web-console/frontend/app/components/web-console-header/template.pug
index 41586b7..660874e 100644
--- a/modules/web-console/frontend/app/components/web-console-header/template.pug
+++ b/modules/web-console/frontend/app/components/web-console-header/template.pug
@@ -16,10 +16,10 @@
 
 .wch-notification(ng-show='$ctrl.UserNotifications.message' ng-bind-html='$ctrl.UserNotifications.message')
 
-.wch-notification(ng-show='$ctrl.$rootScope.user.becomeUsed')
-    | You are currently viewing user #[strong {{$ctrl.$rootScope.user.firstName}} {{$ctrl.$rootScope.user.lastName}}] as administrator. #[a(ng-click='$ctrl.$rootScope.revertIdentity()') Revert to your identity?]
+.wch-notification(ng-show='$root.user.becomeUsed')
+    | You are currently viewing user #[strong {{$root.user.firstName}} {{$root.user.lastName}}] as administrator. #[a(ng-click='$root.revertIdentity()') Revert to your identity?]
 
-.wch-notification.wch-notification--demo(ng-if='$ctrl.$rootScope.IgniteDemoMode')
+.wch-notification.wch-notification--demo(ng-if='$root.IgniteDemoMode')
     .container(ng-controller='demoController')
         | You are now in #[b Demo Mode]. #[a(ng-click='closeDemo();') Close Demo?]
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/modules/form/field/input/text.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/form/field/input/text.scss b/modules/web-console/frontend/app/modules/form/field/input/text.scss
index 6882469..658d740 100644
--- a/modules/web-console/frontend/app/modules/form/field/input/text.scss
+++ b/modules/web-console/frontend/app/modules/form/field/input/text.scss
@@ -17,6 +17,7 @@
 
 .checkbox label .input-tip {
 	position: initial;
+    overflow: visible;
 }
 
 .input-tip .fa-floppy-o {

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/modules/user/permissions.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/user/permissions.js b/modules/web-console/frontend/app/modules/user/permissions.js
index b6f7c3a..616226a 100644
--- a/modules/web-console/frontend/app/modules/user/permissions.js
+++ b/modules/web-console/frontend/app/modules/user/permissions.js
@@ -16,8 +16,8 @@
  */
 
 const guest = ['login'];
-const becomed = ['profile', 'configuration', 'query', 'demo'];
-const user = becomed.concat(['logout']);
+const becomed = ['profile', 'configuration'];
+const user = becomed.concat(['logout', 'query', 'demo']);
 const admin = user.concat(['admin_page']);
 
 export default {

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/primitives/btn/index.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/primitives/btn/index.scss b/modules/web-console/frontend/app/primitives/btn/index.scss
index 94b5bd2..2870cff 100644
--- a/modules/web-console/frontend/app/primitives/btn/index.scss
+++ b/modules/web-console/frontend/app/primitives/btn/index.scss
@@ -242,6 +242,14 @@ $btn-content-padding-with-border: 9px 11px;
     @include btn-ignite--link-dashed($color, $activeHover, $disabled);
 }
 
+.btn-ignite--link-dashed-primary {
+    $color: $ignite-brand-primary;
+    $activeHover: change-color($color, $lightness: 26%);
+    $disabled: change-color($color, $saturation: 57%, $lightness: 68%);
+
+    @include btn-ignite--link-dashed($color, $activeHover, $disabled);
+}
+
 .btn-ignite--link-dashed-secondary {
     $activeHover: change-color($ignite-brand-success, $lightness: 26%);
     @include btn-ignite--link-dashed($text-color, $activeHover, $gray-light);
@@ -319,3 +327,12 @@ $btn-content-padding-with-border: 9px 11px;
         $color-hover: change-color($ignite-brand-success, $lightness: 26%)
     );
 }
+
+.btn-ignite--link {
+    background: transparent;
+
+    @include ignite-link(
+        $color: $ignite-brand-success,
+        $color-hover: change-color($ignite-brand-success, $lightness: 26%)
+    );
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/primitives/form-field/index.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/primitives/form-field/index.scss b/modules/web-console/frontend/app/primitives/form-field/index.scss
index f6d8496..01dd941 100644
--- a/modules/web-console/frontend/app/primitives/form-field/index.scss
+++ b/modules/web-console/frontend/app/primitives/form-field/index.scss
@@ -26,7 +26,7 @@
             width: auto;
         }
 
-        .ignite-form-field__label {
+        &__label {
             float: left;
             width: 100%;
             margin: 0 10px 4px;

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/primitives/ui-grid-settings/index.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/primitives/ui-grid-settings/index.scss b/modules/web-console/frontend/app/primitives/ui-grid-settings/index.scss
index e0cf139..56bab22 100644
--- a/modules/web-console/frontend/app/primitives/ui-grid-settings/index.scss
+++ b/modules/web-console/frontend/app/primitives/ui-grid-settings/index.scss
@@ -92,6 +92,18 @@
 
     &--heading {
         cursor: default;
+
+        sub {
+            bottom: 0;
+            height: 12px;
+            margin-left: 22px;
+
+            font-family: Roboto;
+            font-size: 12px;
+            line-height: 1;
+            text-align: left;
+            color: $gray-light;
+        }
     }
 
     &--heading > span {

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/app/primitives/ui-grid/index.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/primitives/ui-grid/index.scss b/modules/web-console/frontend/app/primitives/ui-grid/index.scss
index 5caa57c..a83cb57 100644
--- a/modules/web-console/frontend/app/primitives/ui-grid/index.scss
+++ b/modules/web-console/frontend/app/primitives/ui-grid/index.scss
@@ -157,6 +157,7 @@
             .ui-grid-filter-container {
                 padding-left: 20px;
                 padding-right: 20px;
+                font-weight: normal;
             }
 
             .ng-hide + .ui-grid-header-cell-row .ui-grid-header-cell {
@@ -519,6 +520,7 @@
     }
 }
 
+// Obsoleted, use grid-no-data.
 .ui-grid--ignite.no-data {
     position: relative;
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/package.json
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/package.json b/modules/web-console/frontend/package.json
index 5b1734f..7f7671a 100644
--- a/modules/web-console/frontend/package.json
+++ b/modules/web-console/frontend/package.json
@@ -49,7 +49,7 @@
     "angular-strap": "2.3.12",
     "angular-translate": "2.16.0",
     "angular-tree-control": "0.2.28",
-    "angular-ui-grid": "4.0.7",
+    "angular-ui-grid": "4.0.11",
     "babel-core": "6.25.0",
     "babel-eslint": "7.2.3",
     "babel-loader": "7.1.1",
@@ -62,6 +62,7 @@
     "bootstrap-sass": "3.3.7",
     "brace": "0.10.0",
     "browser-update": "2.1.9",
+    "bson-objectid": "1.1.5",
     "copy-webpack-plugin": "4.0.1",
     "css-loader": "0.28.7",
     "eslint": "4.3.0",

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/public/images/checkbox-active.svg
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/public/images/checkbox-active.svg b/modules/web-console/frontend/public/images/checkbox-active.svg
new file mode 100644
index 0000000..82e59c6
--- /dev/null
+++ b/modules/web-console/frontend/public/images/checkbox-active.svg
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="12px" height="12px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: sketchtool 45.2 (43514) - http://www.bohemiancoding.com/sketch -->
+    <title>1F50951A-D0DF-4DE9-B464-5A57049A9426</title>
+    <desc>Created with sketchtool.</desc>
+    <defs>
+        <polygon id="path-1" points="4.0575 5.993 5.0835 7.0255 8.99916406 3.06347168 10.0556641 4.12097168 5.0885 9.1435 3 7.0505"></polygon>
+        <filter x="-28.3%" y="-16.4%" width="156.7%" height="165.8%" filterUnits="objectBoundingBox" id="filter-2">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0.029487408   0 0 0 0 0.138965552   0 0 0 0 0.210990646  0 0 0 0.5 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Checkbox" transform="translate(-51.000000, -254.000000)">
+            <g id="CheckboxActive" transform="translate(51.000000, 254.000000)">
+                <rect id="Rectangle-2-Copy" fill="#0065B6" x="0" y="0" width="12" height="12" rx="2"></rect>
+                <g id="Imported-Layers">
+                    <use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
+                    <use fill="#FFFFFF" fill-rule="evenodd" xlink:href="#path-1"></use>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/public/images/checkbox.svg
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/public/images/checkbox.svg b/modules/web-console/frontend/public/images/checkbox.svg
new file mode 100644
index 0000000..82264a9
--- /dev/null
+++ b/modules/web-console/frontend/public/images/checkbox.svg
@@ -0,0 +1,22 @@
+<svg version="1.1" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="linearGradient-1" x1="50%" x2="50%" y1="-18.787%" y2="68.083%">
+   <stop stop-color="#979797" offset="0"/>
+   <stop stop-color="#BEBEBE" offset="1"/>
+  </linearGradient>
+  <rect id="path-2" x="148" y="254" width="12" height="12" rx="2"/>
+  <filter id="filter-3" x="-12.5%" y="-12.5%" width="125%" height="125%">
+   <feGaussianBlur in="SourceAlpha" result="shadowBlurInner1" stdDeviation="1"/>
+   <feOffset dx="0" dy="1" in="shadowBlurInner1" result="shadowOffsetInner1"/>
+   <feComposite in="shadowOffsetInner1" in2="SourceAlpha" k2="-1" k3="1" operator="arithmetic" result="shadowInnerInner1"/>
+   <feColorMatrix in="shadowInnerInner1" values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.5 0"/>
+  </filter>
+ </defs>
+ <g fill="none" fill-rule="evenodd">
+  <g id="Checkbox" transform="translate(-148 -254)">
+   <use width="100%" height="100%" fill="#ffffff" xlink:href="#path-2"/>
+   <use fill="black" filter="url(#filter-3)" xlink:href="#path-2"/>
+   <rect x="148.5" y="254.5" width="11" height="11" rx="2" stroke="url(#linearGradient-1)"/>
+  </g>
+ </g>
+</svg>

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/public/images/icons/alert.svg
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/public/images/icons/alert.svg b/modules/web-console/frontend/public/images/icons/alert.svg
new file mode 100644
index 0000000..6d1b3e2
--- /dev/null
+++ b/modules/web-console/frontend/public/images/icons/alert.svg
@@ -0,0 +1 @@
+<svg id="alert-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 19"><path fill="currentColor" d="M8 18.7c1 0 1.8-.8 1.8-1.8H6.2c0 1 .8 1.8 1.8 1.8zM14.1 8c0-2.9-2-5.3-4.7-5.9v-.7C9.4.6 8.8 0 8 0S6.6.6 6.6 1.4V2c-2.7.7-4.7 3.1-4.7 6v5.2L0 15.1v.9h16v-.9l-1.9-1.9V8z"/></svg>

http://git-wip-us.apache.org/repos/asf/ignite/blob/e08d19cb/modules/web-console/frontend/public/images/icons/checkmark.svg
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/public/images/icons/checkmark.svg b/modules/web-console/frontend/public/images/icons/checkmark.svg
new file mode 100644
index 0000000..a7896c7
--- /dev/null
+++ b/modules/web-console/frontend/public/images/icons/checkmark.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" viewBox="0 0 14 11" xmlns="http://www.w3.org/2000/svg">
+<path fill="currentColor" d="m12.877 0.32617l-8.4277 8.4219-3.3184-3.3203-1.1309 1.123 4.4492 4.4492 9.5508-9.5508-1.123-1.123z"/>
+</svg>