You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ag...@apache.org on 2017/04/18 10:37:28 UTC

[32/50] [abbrv] ignite git commit: IGNITE-4995 Multi-cluster support for Web Console.

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/app/modules/agent/AgentManager.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/agent/AgentManager.service.js b/modules/web-console/frontend/app/modules/agent/AgentManager.service.js
new file mode 100644
index 0000000..3b39463
--- /dev/null
+++ b/modules/web-console/frontend/app/modules/agent/AgentManager.service.js
@@ -0,0 +1,529 @@
+/*
+ * 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 io from 'socket.io-client'; // eslint-disable-line no-unused-vars
+
+const maskNull = (val) => _.isNil(val) ? 'null' : val;
+
+const State = {
+    INIT: 'INIT',
+    AGENT_DISCONNECTED: 'AGENT_DISCONNECTED',
+    CLUSTER_DISCONNECTED: 'CLUSTER_DISCONNECTED',
+    CONNECTED: 'CONNECTED'
+};
+
+export default class IgniteAgentManager {
+    static $inject = ['$rootScope', '$q', 'igniteSocketFactory', 'AgentModal'];
+
+    constructor($root, $q, socketFactory, AgentModal) {
+        this.$root = $root;
+        this.$q = $q;
+        this.socketFactory = socketFactory;
+
+        /**
+         * @type {AgentModal}
+         */
+        this.AgentModal = AgentModal;
+
+        this.clusters = [];
+
+        $root.$on('$stateChangeSuccess', _.bind(this.stopWatch, this));
+
+        /**
+         * Connection to backend.
+         * @type {Socket}
+         */
+        this.socket = null;
+
+        this.connectionState = State.INIT;
+
+        /**
+         * Has agent with enabled demo mode.
+         * @type {boolean}
+         */
+        this.hasDemo = false;
+
+        this.clusters = [];
+    }
+
+    connect() {
+        const self = this;
+
+        if (_.nonNil(self.socket))
+            return;
+
+        self.socket = self.socketFactory();
+
+        const onDisconnect = () => {
+            self.connected = false;
+        };
+
+        self.socket.on('connect_error', onDisconnect);
+        self.socket.on('disconnect', onDisconnect);
+
+        self.connected = null;
+
+        try {
+            self.cluster = JSON.parse(localStorage.cluster);
+
+            localStorage.removeItem('cluster');
+        }
+        catch (ignore) {
+            // No-op.
+        }
+
+        self.socket.on('agents:stat', ({count, hasDemo, clusters}) => {
+            self.hasDemo = hasDemo;
+
+            const removed = _.differenceBy(self.clusters, clusters, 'id');
+
+            if (_.nonEmpty(removed)) {
+                _.pullAll(self.clusters, removed);
+
+                if (self.cluster && _.find(removed, {id: self.cluster.id}))
+                    self.cluster.disconnect = true;
+            }
+
+            const added = _.differenceBy(clusters, self.clusters, 'id');
+
+            if (_.nonEmpty(added)) {
+                self.clusters.push(...added);
+
+                if (_.isNil(self.cluster))
+                    self.cluster = _.head(added);
+
+                if (self.cluster && _.find(added, {id: self.cluster.id}))
+                    self.cluster.disconnect = false;
+            }
+
+            if (count === 0)
+                self.connectionState = State.AGENT_DISCONNECTED;
+            else {
+                self.connectionState = self.$root.IgniteDemoMode || _.get(self.cluster, 'disconnect') === false ?
+                    State.CONNECTED : State.CLUSTER_DISCONNECTED;
+            }
+        });
+    }
+
+    saveToStorage(cluster = this.cluster) {
+        try {
+            localStorage.cluster = JSON.stringify(cluster);
+        } catch (ignore) {
+            // No-op.
+        }
+    }
+
+    /**
+     * @returns {Promise}
+     */
+    awaitAgent() {
+        this.latchAwaitAgent = this.$q.defer();
+
+        this.offAwaitAgent = this.$root.$watch(() => this.connectionState, (state) => {
+            if (state === State.CONNECTED) {
+                this.offAwaitAgent();
+
+                this.latchAwaitAgent.resolve();
+            }
+        });
+
+        return this.latchAwaitAgent.promise;
+    }
+
+    /**
+     * @param {String} backText
+     * @param {String} [backState]
+     * @returns {Promise}
+     */
+    startWatch(backText, backState) {
+        const self = this;
+
+        self.backText = backText;
+        self.backState = backState;
+
+        if (_.nonEmpty(self.clusters) && _.get(self.cluster, 'disconnect') === true) {
+            self.cluster = _.head(self.clusters);
+
+            self.connectionState = State.CONNECTED;
+        }
+
+        self.offStateWatch = this.$root.$watch(() => self.connectionState, (state) => {
+            switch (state) {
+                case State.AGENT_DISCONNECTED:
+                    this.AgentModal.agentDisconnected(self.backText, self.backState);
+
+                    break;
+
+                case State.CLUSTER_DISCONNECTED:
+                    self.AgentModal.clusterDisconnected(self.backText, self.backState);
+
+                    break;
+
+                case State.CONNECTED:
+                    this.AgentModal.hide();
+
+                    break;
+
+                default:
+                    // Connection to backend is not established yet.
+            }
+        });
+
+        return self.awaitAgent();
+    }
+
+    stopWatch() {
+        if (!_.isFunction(this.offStateWatch))
+            return;
+
+        this.offStateWatch();
+
+        this.AgentModal.hide();
+
+        if (this.latchAwaitAgent) {
+            this.offAwaitAgent();
+
+            this.latchAwaitAgent.reject('Agent watch stopped.');
+        }
+    }
+
+    /**
+     *
+     * @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} driverPath
+     * @param {Object} driverClass
+     * @param {Object} url
+     * @param {Object} user
+     * @param {Object} password
+     * @returns {Promise}
+     */
+    schemas({driverPath, driverClass, url, user, password}) {
+        const info = {user, password};
+
+        return this._emit('schemaImport:schemas', {driverPath, driverClass, url, info});
+    }
+
+    /**
+     * @param {Object} driverPath
+     * @param {Object} driverClass
+     * @param {Object} url
+     * @param {Object} user
+     * @param {Object} password
+     * @param {Object} schemas
+     * @param {Object} tablesOnly
+     * @returns {Promise} Promise on list of tables (see org.apache.ignite.schema.parser.DbTable java class)
+     */
+    tables({driverPath, driverClass, url, user, password, schemas, tablesOnly}) {
+        const info = {user, password};
+
+        return this._emit('schemaImport:tables', {driverPath, driverClass, url, info, schemas, tablesOnly});
+    }
+
+    /**
+     *
+     * @param {String} event
+     * @param {Object} [args]
+     * @returns {Promise}
+     * @private
+     */
+    _rest(event, ...args) {
+        return this._emit(event, _.get(this, 'cluster.id'), ...args);
+    }
+
+    /**
+     * @param {Boolean} [attr]
+     * @param {Boolean} [mtr]
+     * @returns {Promise}
+     */
+    topology(attr = false, mtr = false) {
+        return this._rest('node:rest', {cmd: 'top', attr, mtr});
+    }
+
+    /**
+     * @param {String} [cacheName] Cache name.
+     * @returns {Promise}
+     */
+    metadata(cacheName) {
+        return this._rest('node:rest', {cmd: 'metadata', cacheName: maskNull(cacheName)})
+            .then((caches) => {
+                let types = [];
+
+                const _compact = (className) => {
+                    return className.replace('java.lang.', '').replace('java.util.', '').replace('java.sql.', '');
+                };
+
+                const _typeMapper = (meta, typeName) => {
+                    const maskedName = _.isEmpty(meta.cacheName) ? '<default>' : meta.cacheName;
+
+                    let fields = meta.fields[typeName];
+
+                    let columns = [];
+
+                    for (const fieldName in fields) {
+                        if (fields.hasOwnProperty(fieldName)) {
+                            const fieldClass = _compact(fields[fieldName]);
+
+                            columns.push({
+                                type: 'field',
+                                name: fieldName,
+                                clazz: fieldClass,
+                                system: fieldName === '_KEY' || fieldName === '_VAL',
+                                cacheName: meta.cacheName,
+                                typeName,
+                                maskedName
+                            });
+                        }
+                    }
+
+                    const indexes = [];
+
+                    for (const index of meta.indexes[typeName]) {
+                        fields = [];
+
+                        for (const field of index.fields) {
+                            fields.push({
+                                type: 'index-field',
+                                name: field,
+                                order: index.descendings.indexOf(field) < 0,
+                                unique: index.unique,
+                                cacheName: meta.cacheName,
+                                typeName,
+                                maskedName
+                            });
+                        }
+
+                        if (fields.length > 0) {
+                            indexes.push({
+                                type: 'index',
+                                name: index.name,
+                                children: fields,
+                                cacheName: meta.cacheName,
+                                typeName,
+                                maskedName
+                            });
+                        }
+                    }
+
+                    columns = _.sortBy(columns, 'name');
+
+                    if (!_.isEmpty(indexes)) {
+                        columns = columns.concat({
+                            type: 'indexes',
+                            name: 'Indexes',
+                            cacheName: meta.cacheName,
+                            typeName,
+                            maskedName,
+                            children: indexes
+                        });
+                    }
+
+                    return {
+                        type: 'type',
+                        cacheName: meta.cacheName || '',
+                        typeName,
+                        maskedName,
+                        children: columns
+                    };
+                };
+
+                for (const meta of caches) {
+                    const cacheTypes = meta.types.map(_typeMapper.bind(null, meta));
+
+                    if (!_.isEmpty(cacheTypes))
+                        types = types.concat(cacheTypes);
+                }
+
+                return types;
+            });
+    }
+
+    /**
+     * @param {String} taskId
+     * @param {Array.<String>|String} nids
+     * @param {Array.<Object>} args
+     */
+    visorTask(taskId, nids, ...args) {
+        args = _.map(args, (arg) => maskNull(arg));
+
+        nids = _.isArray(nids) ? nids.join(';') : maskNull(nids);
+
+        return this._rest('node:visor', taskId, nids, ...args);
+    }
+
+    /**
+     * @param {String} nid Node id.
+     * @param {String} cacheName Cache name.
+     * @param {String} [query] Query if null then scan query.
+     * @param {Boolean} nonCollocatedJoins Flag whether to execute non collocated joins.
+     * @param {Boolean} enforceJoinOrder Flag whether enforce join order is enabled.
+     * @param {Boolean} replicatedOnly Flag whether query contains only replicated tables.
+     * @param {Boolean} local Flag whether to execute query locally.
+     * @param {int} pageSz
+     * @returns {Promise}
+     */
+    querySql(nid, cacheName, query, nonCollocatedJoins, enforceJoinOrder, replicatedOnly, local, pageSz) {
+        return this.visorTask('querySql', nid, cacheName, query, nonCollocatedJoins, enforceJoinOrder, replicatedOnly, local, pageSz)
+            .then(({error, result}) => {
+                if (_.isEmpty(error))
+                    return result;
+
+                return Promise.reject(error);
+            });
+    }
+
+    /**
+     * @param {String} nid Node id.
+     * @param {int} queryId
+     * @param {int} pageSize
+     * @returns {Promise}
+     */
+    queryNextPage(nid, queryId, pageSize) {
+        return this.visorTask('queryFetch', nid, queryId, pageSize);
+    }
+
+    /**
+     * @param {String} nid Node id.
+     * @param {String} cacheName Cache name.
+     * @param {String} [query] Query if null then scan query.
+     * @param {Boolean} nonCollocatedJoins Flag whether to execute non collocated joins.
+     * @param {Boolean} enforceJoinOrder Flag whether enforce join order is enabled.
+     * @param {Boolean} replicatedOnly Flag whether query contains only replicated tables.
+     * @param {Boolean} local Flag whether to execute query locally.
+     * @returns {Promise}
+     */
+    querySqlGetAll(nid, cacheName, query, nonCollocatedJoins, enforceJoinOrder, replicatedOnly, local) {
+        // Page size for query.
+        const pageSz = 1024;
+
+        const fetchResult = (acc) => {
+            if (!acc.hasMore)
+                return acc;
+
+            return this.queryNextPage(acc.responseNodeId, acc.queryId, pageSz)
+                .then((res) => {
+                    acc.rows = acc.rows.concat(res.rows);
+
+                    acc.hasMore = res.hasMore;
+
+                    return fetchResult(acc);
+                });
+        };
+
+        return this.querySql(nid, cacheName, query, nonCollocatedJoins, enforceJoinOrder, replicatedOnly, local, pageSz)
+            .then(fetchResult);
+    }
+
+    /**
+     * @param {String} nid Node id.
+     * @param {int} [queryId]
+     * @returns {Promise}
+     */
+    queryClose(nid, queryId) {
+        return this.visorTask('queryClose', nid, queryId);
+    }
+
+    /**
+     * @param {String} nid Node id.
+     * @param {String} cacheName Cache name.
+     * @param {String} filter Filter text.
+     * @param {Boolean} regEx Flag whether filter by regexp.
+     * @param {Boolean} caseSensitive Case sensitive filtration.
+     * @param {Boolean} near Scan near cache.
+     * @param {Boolean} local Flag whether to execute query locally.
+     * @param {int} pageSize Page size.
+     * @returns {Promise}
+     */
+    queryScan(nid, cacheName, filter, regEx, caseSensitive, near, local, pageSize) {
+        return this.visorTask('queryScan', nid, cacheName, filter, regEx, caseSensitive, near, local, pageSize)
+            .then(({error, result}) => {
+                if (_.isEmpty(error))
+                    return result;
+
+                return Promise.reject(error);
+            });
+    }
+
+    /**
+     /**
+     * @param {String} nid Node id.
+     * @param {String} cacheName Cache name.
+     * @param {String} filter Filter text.
+     * @param {Boolean} regEx Flag whether filter by regexp.
+     * @param {Boolean} caseSensitive Case sensitive filtration.
+     * @param {Boolean} near Scan near cache.
+     * @param {Boolean} local Flag whether to execute query locally.
+     * @returns {Promise}
+     */
+    queryScanGetAll(nid, cacheName, filter, regEx, caseSensitive, near, local) {
+        // Page size for query.
+        const pageSz = 1024;
+
+        const fetchResult = (acc) => {
+            if (!acc.hasMore)
+                return acc;
+
+            return this.queryNextPage(acc.responseNodeId, acc.queryId, pageSz)
+                .then((res) => {
+                    acc.rows = acc.rows.concat(res.rows);
+
+                    acc.hasMore = res.hasMore;
+
+                    return fetchResult(acc);
+                });
+        };
+
+        return this.queryScan(nid, cacheName, filter, regEx, caseSensitive, near, local, pageSz)
+            .then(fetchResult);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/app/modules/agent/AgentModal.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/agent/AgentModal.service.js b/modules/web-console/frontend/app/modules/agent/AgentModal.service.js
new file mode 100644
index 0000000..54f8e52
--- /dev/null
+++ b/modules/web-console/frontend/app/modules/agent/AgentModal.service.js
@@ -0,0 +1,89 @@
+/*
+ * 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 templateUrl from 'views/templates/agent-download.tpl.pug';
+
+export default class AgentModal {
+    static $inject = ['$rootScope', '$state', '$modal', 'IgniteMessages'];
+
+    constructor($root, $state, $modal, Messages) {
+        const self = this;
+
+        self.$state = $state;
+        self.Messages = Messages;
+
+        // Pre-fetch modal dialogs.
+        self.modal = $modal({
+            templateUrl,
+            show: false,
+            backdrop: 'static',
+            keyboard: false,
+            controller: () => self,
+            controllerAs: 'ctrl'
+        });
+
+        self.modal.$scope.$on('modal.hide.before', () => {
+            Messages.hideAlert();
+        });
+
+        $root.$on('user', (event, user) => self.user = user);
+    }
+
+    hide() {
+        this.modal.hide();
+    }
+
+    /**
+     * Close dialog and go by specified link.
+     */
+    back() {
+        this.hide();
+
+        if (this.backState)
+            this.$state.go(this.backState);
+    }
+
+    /**
+     * @param {String} backState
+     * @param {String} [backText]
+     */
+    agentDisconnected(backText, backState) {
+        const self = this;
+
+        self.backText = backText;
+        self.backState = backState;
+
+        self.status = 'agentMissing';
+
+        self.modal.$promise.then(self.modal.show);
+    }
+
+    /**
+     * @param {String} backState
+     * @param {String} [backText]
+     */
+    clusterDisconnected(backText, backState) {
+        const self = this;
+
+        self.backText = backText;
+        self.backState = backState;
+
+        self.status = 'nodeMissing';
+
+        self.modal.$promise.then(self.modal.show);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/app/modules/agent/agent.module.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/agent/agent.module.js b/modules/web-console/frontend/app/modules/agent/agent.module.js
index b20a5bc..63257c5 100644
--- a/modules/web-console/frontend/app/modules/agent/agent.module.js
+++ b/modules/web-console/frontend/app/modules/agent/agent.module.js
@@ -16,352 +16,13 @@
  */
 
 import angular from 'angular';
-import io from 'socket.io-client'; // eslint-disable-line no-unused-vars
 
-import templateUrl from 'views/templates/agent-download.tpl.pug';
-
-const maskNull = (val) => _.isEmpty(val) ? 'null' : val;
-
-class IgniteAgentMonitor {
-    constructor(socketFactory, $root, $q, $state, $modal, Messages) {
-        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,
-            show: false,
-            backdrop: 'static',
-            keyboard: false
-        });
-
-        const _modalHide = this._downloadAgentModal.hide;
-
-        /**
-         * Special dialog hide function.
-         */
-        this._downloadAgentModal.hide = () => {
-            Messages.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.hasAgents = null;
-        this._scope.showModal = false;
-
-        /**
-         * @type {Socket}
-         */
-        this._socket = null;
-
-        this._socketFactory = socketFactory;
-
-        this._$q = $q;
-
-        this.Messages = Messages;
-    }
-
-    /**
-     * @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() {
-        if (this._socket)
-            return;
-
-        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.Messages.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 {String} nid Node id.
-     * @param {int} [queryId]
-     * @returns {Promise}
-     */
-    queryClose(nid, queryId) {
-        return this._rest('node:query:close', nid, queryId);
-    }
-
-    /**
-     * @param {String} nid Node id.
-     * @param {String} cacheName Cache name.
-     * @param {String} [query] Query if null then scan query.
-     * @param {Boolean} nonCollocatedJoins Flag whether to execute non collocated joins.
-     * @param {Boolean} enforceJoinOrder Flag whether enforce join order is enabled.
-     * @param {Boolean} local Flag whether to execute query locally.
-     * @param {int} pageSize
-     * @returns {Promise}
-     */
-    query(nid, cacheName, query, nonCollocatedJoins, enforceJoinOrder, local, pageSize) {
-        return this._rest('node:query', nid, maskNull(cacheName), maskNull(query), nonCollocatedJoins, enforceJoinOrder, local, pageSize)
-            .then(({result}) => {
-                if (_.isEmpty(result.error))
-                    return result.result;
-
-                return Promise.reject(result.error);
-            });
-    }
-
-    /**
-     * @param {String} nid Node id.
-     * @param {String} cacheName Cache name.
-     * @param {String} [query] Query if null then scan query.
-     * @param {Boolean} nonCollocatedJoins Flag whether to execute non collocated joins.
-     * @param {Boolean} enforceJoinOrder Flag whether enforce join order is enabled.
-     * @param {Boolean} local Flag whether to execute query locally.
-     * @returns {Promise}
-     */
-    queryGetAll(nid, cacheName, query, nonCollocatedJoins, enforceJoinOrder, local) {
-        return this._rest('node:query:getAll', nid, maskNull(cacheName), maskNull(query), nonCollocatedJoins, enforceJoinOrder, local);
-    }
-
-    /**
-     * @param {String} nid Node id.
-     * @param {String} cacheName Cache name.
-     * @param {String} filter Optional filter for scan query.
-     * @param {Boolean} regEx Flag whether filter by regexp.
-     * @param {Boolean} caseSensitive Case sensitive filtration.
-     * @param {Boolean} near Scan near cache.
-     * @param {Boolean} local Flag whether to execute query locally.
-     * @param {int} pageSize
-     * @returns {Promise}
-     */
-    scan(nid, cacheName, filter, regEx, caseSensitive, near, local, pageSize) {
-        return this._rest('node:scan', nid, maskNull(cacheName), maskNull(filter), regEx, caseSensitive, near, local, pageSize)
-            .then(({result}) => {
-                if (_.isEmpty(result.error))
-                    return result.result;
-
-                return Promise.reject(result.error);
-            });
-    }
-
-    /**
-     * @param {String} nid Node id.
-     * @param {String} cacheName Cache name.
-     * @param {String} filter Optional filter for scan query.
-     * @param {Boolean} regEx Flag whether filter by regexp.
-     * @param {Boolean} caseSensitive Case sensitive filtration.
-     * @param {Boolean} near Scan near cache.
-     * @param {Boolean} local Flag whether to execute query locally.
-     * @returns {Promise}
-     */
-    scanGetAll(nid, cacheName, filter, regEx, caseSensitive, near, local) {
-        return this._rest('node:scan:getAll', nid, maskNull(cacheName), maskNull(filter), regEx, caseSensitive, near, local);
-    }
-
-    /**
-     * @param {String} nid Node id.
-     * @param {int} queryId
-     * @param {int} pageSize
-     * @returns {Promise}
-     */
-    next(nid, queryId, pageSize) {
-        return this._rest('node:query:fetch', nid, queryId, pageSize)
-            .then(({result}) => result);
-    }
-
-    /**
-     * @param {String} [cacheName] Cache name.
-     * @returns {Promise}
-     */
-    metadata(cacheName) {
-        return this._rest('node:cache:metadata', maskNull(cacheName));
-    }
-
-    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', 'IgniteMessages'];
+import AgentModal from './AgentModal.service';
+import AgentManager from './AgentManager.service';
 
 angular
     .module('ignite-console.agent', [
 
     ])
-    .service('IgniteAgentMonitor', IgniteAgentMonitor);
+    .service('AgentModal', AgentModal)
+    .service('AgentManager', AgentManager);

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/app/modules/cluster/Cache.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/cluster/Cache.js b/modules/web-console/frontend/app/modules/cluster/Cache.js
new file mode 100644
index 0000000..5007900
--- /dev/null
+++ b/modules/web-console/frontend/app/modules/cluster/Cache.js
@@ -0,0 +1,51 @@
+/*
+ *  Copyright (C) GridGain Systems. All Rights Reserved.
+ *  _________        _____ __________________        _____
+ *  __  ____/___________(_)______  /__  ____/______ ____(_)_______
+ *  _  / __  __  ___/__  / _  __  / _  / __  _  __ `/__  / __  __ \
+ *  / /_/ /  _  /    _  /  / /_/ /  / /_/ /  / /_/ / _  /  _  / / /
+ *  \____/   /_/     /_/   \_,__/   \____/   \__,_/  /_/   /_/ /_/
+ */
+
+export default class Cache {
+    constructor(cache) {
+        this.dynamicDeploymentId = cache.dynamicDeploymentId;
+
+        // Name.
+        this.name = cache.name;
+
+        // Mode.
+        this.mode = cache.mode;
+
+        // Memory Usage.
+        this.memorySize = cache.memorySize;
+
+        // Heap.
+        this.size = cache.size;
+        this.primarySize = cache.primarySize;
+        this.backupSize = cache.dhtSize - cache.primarySize;
+        this.nearSize = cache.nearSize;
+
+        // Off-heap.
+        this.offHeapAllocatedSize = cache.offHeapAllocatedSize;
+        this.offHeapSize = cache.offHeapEntriesCount;
+        this.offHeapPrimarySize = cache.offHeapPrimaryEntriesCount || 0;
+        this.offHeapBackupSize = cache.offHeapBackupEntriesCount || 0;
+
+        // Swap.
+        this.swapSize = cache.swapSize;
+        this.swapKeys = cache.swapKeys;
+
+        const m = cache.metrics;
+
+        // Read/write metrics.
+        this.hits = m.hits;
+        this.misses = m.misses;
+        this.reads = m.reads;
+        this.writes = m.writes;
+
+        // Transaction metrics.
+        this.commits = m.txCommits;
+        this.rollbacks = m.txRollbacks;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/app/modules/cluster/CacheMetrics.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/cluster/CacheMetrics.js b/modules/web-console/frontend/app/modules/cluster/CacheMetrics.js
new file mode 100644
index 0000000..ab1fd8c
--- /dev/null
+++ b/modules/web-console/frontend/app/modules/cluster/CacheMetrics.js
@@ -0,0 +1,51 @@
+/*
+ *  Copyright (C) GridGain Systems. All Rights Reserved.
+ *  _________        _____ __________________        _____
+ *  __  ____/___________(_)______  /__  ____/______ ____(_)_______
+ *  _  / __  __  ___/__  / _  __  / _  / __  _  __ `/__  / __  __ \
+ *  / /_/ /  _  /    _  /  / /_/ /  / /_/ /  / /_/ / _  /  _  / / /
+ *  \____/   /_/     /_/   \_,__/   \____/   \__,_/  /_/   /_/ /_/
+ */
+
+export default class CacheMetrics {
+    constructor(cache) {
+        this.dynamicDeploymentId = cache.dynamicDeploymentId;
+
+        // Name.
+        this.name = cache.name;
+
+        // Mode.
+        this.mode = cache.mode;
+
+        // Memory Usage.
+        this.memorySize = cache.memorySize;
+
+        // Heap.
+        this.size = cache.size;
+        this.primarySize = cache.primarySize;
+        this.backupSize = cache.dhtSize - cache.primarySize;
+        this.nearSize = cache.nearSize;
+
+        // Off-heap.
+        this.offHeapAllocatedSize = cache.offHeapAllocatedSize;
+        this.offHeapSize = cache.offHeapEntriesCount;
+        this.offHeapPrimarySize = cache.offHeapPrimaryEntriesCount || 0;
+        this.offHeapBackupSize = cache.offHeapBackupEntriesCount || 0;
+
+        // Swap.
+        this.swapSize = cache.swapSize;
+        this.swapKeys = cache.swapKeys;
+
+        const m = cache.metrics;
+
+        // Read/write metrics.
+        this.hits = m.hits;
+        this.misses = m.misses;
+        this.reads = m.reads;
+        this.writes = m.writes;
+
+        // Transaction metrics.
+        this.commits = m.txCommits;
+        this.rollbacks = m.txRollbacks;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/app/modules/cluster/Node.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/cluster/Node.js b/modules/web-console/frontend/app/modules/cluster/Node.js
new file mode 100644
index 0000000..dac7c70
--- /dev/null
+++ b/modules/web-console/frontend/app/modules/cluster/Node.js
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+export default class Node {
+    constructor(node) {
+        this.nid = node.nodeId.toUpperCase();
+
+        this.jvmPid = node.attributes['org.apache.ignite.jvm.pid'];
+        this.macs = node.attributes['org.apache.ignite.macs'];
+
+        this.ip = node.attributes['org.apache.ignite.ips'].split(',')[0];
+        this.igniteVersion = node.attributes['org.apache.ignite.build.ver'];
+        this.version = node.attributes['plugins.gg.build.ver'] || this.igniteVersion;
+        // this.hostName = data.attributes[];
+        this.clientMode = node.attributes['org.apache.ignite.cache.client'] ? 'CLIENT' : 'SERVER';
+        this.gridName = node.attributes['org.apache.ignite.ignite.name'];
+
+        this.startTime = node.metrics.startTime;
+        this.upTime = node.metrics.upTime;
+
+        this.cpus = node.metrics.totalCpus;
+
+        this.heapMemoryMaximum = parseInt(node.metrics.heapMemoryMaximum, 10);
+        this.heapMemoryUsed = parseInt(node.metrics.heapMemoryUsed, 10);
+        this.heapMemoryCommitted = parseInt(node.metrics.heapMemoryCommitted, 10);
+
+        this.busy = parseFloat(node.metrics.busyTimePercentage);
+
+        this.cpuLoad = parseFloat(node.metrics.currentCpuLoad);
+        this.gcLoad = parseFloat(node.metrics.currentGcCpuLoad);
+
+        this.heapMemoryFreePercent = (this.heapMemoryMaximum - this.heapMemoryUsed) / this.heapMemoryMaximum;
+
+        this.os = `${node.attributes['os.name']} ${node.attributes['os.arch']} ${node.attributes['os.version']}`;
+    }
+
+    static from(node) {
+        return new Node(node);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/app/modules/cluster/NodeMetrics.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/cluster/NodeMetrics.js b/modules/web-console/frontend/app/modules/cluster/NodeMetrics.js
new file mode 100644
index 0000000..400d480
--- /dev/null
+++ b/modules/web-console/frontend/app/modules/cluster/NodeMetrics.js
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+export default class NodeMetrics {
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/app/modules/demo/Demo.module.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/demo/Demo.module.js b/modules/web-console/frontend/app/modules/demo/Demo.module.js
index 740ab30..dc763b3 100644
--- a/modules/web-console/frontend/app/modules/demo/Demo.module.js
+++ b/modules/web-console/frontend/app/modules/demo/Demo.module.js
@@ -117,7 +117,7 @@ angular
         return items;
     }];
 }])
-.service('DemoInfo', ['$rootScope', '$modal', '$state', '$q', 'igniteDemoInfo', 'IgniteAgentMonitor', ($rootScope, $modal, $state, $q, igniteDemoInfo, agentMonitor) => {
+.service('DemoInfo', ['$rootScope', '$modal', '$state', '$q', 'igniteDemoInfo', 'AgentManager', ($rootScope, $modal, $state, $q, igniteDemoInfo, agentMonitor) => {
     const scope = $rootScope.$new();
 
     let closePromise = null;
@@ -132,7 +132,6 @@ angular
     const dialog = $modal({
         templateUrl,
         scope,
-        placement: 'center',
         show: false,
         backdrop: 'static'
     });
@@ -146,7 +145,7 @@ angular
     scope.downloadAgent = () => {
         const lnk = document.createElement('a');
 
-        lnk.setAttribute('href', '/api/v1/agent/download/zip');
+        lnk.setAttribute('href', '/api/v1/agent/downloads/agent');
         lnk.setAttribute('target', '_self');
         lnk.setAttribute('download', null);
         lnk.style.display = 'none';

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/app/modules/dialog/dialog.factory.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/dialog/dialog.factory.js b/modules/web-console/frontend/app/modules/dialog/dialog.factory.js
index 2ac8917..599433a 100644
--- a/modules/web-console/frontend/app/modules/dialog/dialog.factory.js
+++ b/modules/web-console/frontend/app/modules/dialog/dialog.factory.js
@@ -20,7 +20,6 @@ import templateUrl from './dialog.tpl.pug';
 export default ['IgniteDialog', ['$modal', ($modal) => {
     const defaults = {
         templateUrl,
-        placement: 'center',
         show: false
     };
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/app/modules/getting-started/GettingStarted.provider.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/getting-started/GettingStarted.provider.js b/modules/web-console/frontend/app/modules/getting-started/GettingStarted.provider.js
index f4c834c..2608bd2 100644
--- a/modules/web-console/frontend/app/modules/getting-started/GettingStarted.provider.js
+++ b/modules/web-console/frontend/app/modules/getting-started/GettingStarted.provider.js
@@ -78,7 +78,7 @@ angular
             _fillPage();
         };
 
-        const dialog = $modal({ templateUrl, scope, placement: 'center', show: false, backdrop: 'static'});
+        const dialog = $modal({ templateUrl, scope, show: false, backdrop: 'static'});
 
         scope.close = () => {
             try {

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/app/modules/navbar/userbar.directive.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/navbar/userbar.directive.js b/modules/web-console/frontend/app/modules/navbar/userbar.directive.js
index af70eb1..279314f 100644
--- a/modules/web-console/frontend/app/modules/navbar/userbar.directive.js
+++ b/modules/web-console/frontend/app/modules/navbar/userbar.directive.js
@@ -22,7 +22,7 @@ export default ['igniteUserbar', [function() {
             const ctrl = this;
 
             ctrl.items = [
-                {text: 'Profile', sref: 'settings.profile'},
+                {text: 'Profile', sref: 'base.settings.profile'},
                 {text: 'Getting started', click: 'gettingStarted.tryShow(true)'}
             ];
 
@@ -30,7 +30,7 @@ export default ['igniteUserbar', [function() {
                 ctrl.items.splice(2);
 
                 if (AclService.can('admin_page'))
-                    ctrl.items.push({text: 'Admin panel', sref: 'settings.admin'});
+                    ctrl.items.push({text: 'Admin panel', sref: 'base.settings.admin'});
 
                 ctrl.items.push(...IgniteUserbar);
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/app/modules/nodes/Nodes.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/nodes/Nodes.service.js b/modules/web-console/frontend/app/modules/nodes/Nodes.service.js
index 4ca1d45..e231753 100644
--- a/modules/web-console/frontend/app/modules/nodes/Nodes.service.js
+++ b/modules/web-console/frontend/app/modules/nodes/Nodes.service.js
@@ -47,7 +47,6 @@ class Nodes {
                 nodes: () => nodes || [],
                 options: () => options
             },
-            placement: 'center',
             controller: 'nodesDialogController',
             controllerAs: '$ctrl'
         });

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/app/modules/sql/notebook.controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/sql/notebook.controller.js b/modules/web-console/frontend/app/modules/sql/notebook.controller.js
index 252dee6..68d318a 100644
--- a/modules/web-console/frontend/app/modules/sql/notebook.controller.js
+++ b/modules/web-console/frontend/app/modules/sql/notebook.controller.js
@@ -38,7 +38,7 @@ export default ['$scope', '$modal', '$state', 'IgniteMessages', 'IgniteNotebook'
         Notebook.read()
             .then((notebooks) => {
                 scope.$watchCollection(() => notebooks, (changed) => {
-                    if (!changed.length)
+                    if (_.isEmpty(changed))
                         return scope.notebooks = [];
 
                     scope.notebooks = [

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/app/modules/sql/sql.controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/sql/sql.controller.js b/modules/web-console/frontend/app/modules/sql/sql.controller.js
index 11700dd..bb94b0c 100644
--- a/modules/web-console/frontend/app/modules/sql/sql.controller.js
+++ b/modules/web-console/frontend/app/modules/sql/sql.controller.js
@@ -210,8 +210,8 @@ class Paragraph {
 }
 
 // Controller for SQL notebook screen.
-export default ['$rootScope', '$scope', '$http', '$q', '$timeout', '$interval', '$animate', '$location', '$anchorScroll', '$state', '$filter', '$modal', '$popover', 'IgniteLoading', 'IgniteLegacyUtils', 'IgniteMessages', 'IgniteConfirm', 'IgniteAgentMonitor', 'IgniteChartColors', 'IgniteNotebook', 'IgniteNodes', 'uiGridExporterConstants', 'IgniteVersion', 'IgniteActivitiesData', 'JavaTypes',
-    function($root, $scope, $http, $q, $timeout, $interval, $animate, $location, $anchorScroll, $state, $filter, $modal, $popover, Loading, LegacyUtils, Messages, Confirm, agentMonitor, IgniteChartColors, Notebook, Nodes, uiGridExporterConstants, Version, ActivitiesData, JavaTypes) {
+export default ['$rootScope', '$scope', '$http', '$q', '$timeout', '$interval', '$animate', '$location', '$anchorScroll', '$state', '$filter', '$modal', '$popover', 'IgniteLoading', 'IgniteLegacyUtils', 'IgniteMessages', 'IgniteConfirm', 'AgentManager', 'IgniteChartColors', 'IgniteNotebook', 'IgniteNodes', 'uiGridExporterConstants', 'IgniteVersion', 'IgniteActivitiesData', 'JavaTypes',
+    function($root, $scope, $http, $q, $timeout, $interval, $animate, $location, $anchorScroll, $state, $filter, $modal, $popover, Loading, LegacyUtils, Messages, Confirm, agentMgr, IgniteChartColors, Notebook, Nodes, uiGridExporterConstants, Version, ActivitiesData, JavaTypes) {
         const $ctrl = this;
 
         // Define template urls.
@@ -846,7 +846,7 @@ export default ['$rootScope', '$scope', '$http', '$q', '$timeout', '$interval',
          * @private
          */
         const _refreshFn = () =>
-            agentMonitor.topology(true)
+            agentMgr.topology(true)
                 .then((nodes) => {
                     $scope.caches = _.sortBy(_.reduce(nodes, (cachesAcc, node) => {
                         _.forEach(node.caches, (cache) => {
@@ -884,20 +884,10 @@ export default ['$rootScope', '$scope', '$http', '$q', '$timeout', '$interval',
                             paragraph.cacheName = _.head(cacheNames);
                     });
                 })
-                .then(() => agentMonitor.checkModal())
-                .catch((err) => agentMonitor.showNodeError(err));
+                .catch((err) => Messages.showError(err));
 
         const _startWatch = () =>
-            agentMonitor.startWatch({
-                state: 'base.configuration.clusters',
-                text: 'Back to Configuration',
-                goal: 'execute sql statements',
-                onDisconnect: () => {
-                    _stopTopologyRefresh();
-
-                    _startWatch();
-                }
-            })
+            agentMgr.startWatch('Back to Configuration', 'base.configuration.clusters')
                 .then(() => Loading.start('sqlLoading'))
                 .then(_refreshFn)
                 .then(() => Loading.finish('sqlLoading'))
@@ -1314,7 +1304,7 @@ export default ['$rootScope', '$scope', '$http', '$q', '$timeout', '$interval',
             const nid = paragraph.resNodeId;
 
             if (paragraph.queryId && _.find($scope.caches, ({nodes}) => _.includes(nodes, nid)))
-                return agentMonitor.queryClose(nid, paragraph.queryId);
+                return agentMgr.queryClose(nid, paragraph.queryId);
 
             return $q.when();
         };
@@ -1346,11 +1336,11 @@ export default ['$rootScope', '$scope', '$http', '$q', '$timeout', '$interval',
         const _executeRefresh = (paragraph) => {
             const args = paragraph.queryArgs;
 
-            agentMonitor.awaitAgent()
+            agentMgr.awaitAgent()
                 .then(() => _closeOldQuery(paragraph))
                 .then(() => args.localNid || _chooseNode(args.cacheName, false))
-                .then((nid) => agentMonitor.query(nid, args.cacheName, args.query, args.nonCollocatedJoins,
-                    args.enforceJoinOrder, !!args.localNid, args.pageSize))
+                .then((nid) => agentMgr.querySql(nid, args.cacheName, args.query, args.nonCollocatedJoins,
+                    args.enforceJoinOrder, false, !!args.localNid, args.pageSize))
                 .then(_processQueryResult.bind(this, paragraph, false))
                 .catch((err) => paragraph.setError(err));
         };
@@ -1422,7 +1412,7 @@ export default ['$rootScope', '$scope', '$http', '$q', '$timeout', '$interval',
 
                             ActivitiesData.post({ action: '/queries/execute' });
 
-                            return agentMonitor.query(nid, args.cacheName, qry, nonCollocatedJoins, enforceJoinOrder, local, args.pageSize);
+                            return agentMgr.querySql(nid, args.cacheName, qry, nonCollocatedJoins, enforceJoinOrder, false, local, args.pageSize);
                         })
                         .then((res) => {
                             _processQueryResult(paragraph, true, res);
@@ -1475,7 +1465,7 @@ export default ['$rootScope', '$scope', '$http', '$q', '$timeout', '$interval',
 
                     ActivitiesData.post({ action: '/queries/explain' });
 
-                    return agentMonitor.query(nid, args.cacheName, args.query, false, !!paragraph.enforceJoinOrder, false, args.pageSize);
+                    return agentMgr.querySql(nid, args.cacheName, args.query, false, !!paragraph.enforceJoinOrder, false, false, args.pageSize);
                 })
                 .then(_processQueryResult.bind(this, paragraph, true))
                 .catch((err) => {
@@ -1516,7 +1506,7 @@ export default ['$rootScope', '$scope', '$http', '$q', '$timeout', '$interval',
 
                             ActivitiesData.post({ action: '/queries/scan' });
 
-                            return agentMonitor.scan(nid, cacheName, filter, false, caseSensitive, false, local, pageSize);
+                            return agentMgr.queryScan(nid, cacheName, filter, false, caseSensitive, false, local, pageSize);
                         })
                         .then((res) => _processQueryResult(paragraph, true, res))
                         .catch((err) => {
@@ -1549,7 +1539,7 @@ export default ['$rootScope', '$scope', '$http', '$q', '$timeout', '$interval',
 
             paragraph.queryArgs.pageSize = paragraph.pageSize;
 
-            agentMonitor.next(paragraph.resNodeId, paragraph.queryId, paragraph.pageSize)
+            agentMgr.queryNextPage(paragraph.resNodeId, paragraph.queryId, paragraph.pageSize)
                 .then((res) => {
                     paragraph.page++;
 
@@ -1637,10 +1627,9 @@ export default ['$rootScope', '$scope', '$http', '$q', '$timeout', '$interval',
             const args = paragraph.queryArgs;
 
             return Promise.resolve(args.localNid || _chooseNode(args.cacheName, false))
-                .then((nid) =>
-                    args.type === 'SCAN'
-                        ? agentMonitor.scanGetAll(nid, args.cacheName, args.filter, !!args.regEx, !!args.caseSensitive, !!args.near, !!args.localNid)
-                        : agentMonitor.queryGetAll(nid, args.cacheName, args.query, !!args.nonCollocatedJoins, !!args.enforceJoinOrder, !!args.localNid))
+                .then((nid) => args.type === 'SCAN'
+                    ? agentMgr.queryScanGetAll(nid, args.cacheName, args.filter, !!args.regEx, !!args.caseSensitive, !!args.near, !!args.localNid)
+                    : agentMgr.querySqlGetAll(nid, args.cacheName, args.query, !!args.nonCollocatedJoins, !!args.enforceJoinOrder, false, !!args.localNid))
                 .then((res) => _export(paragraph.name + '-all.csv', paragraph.gridOptions.columnDefs, res.columns, res.rows))
                 .catch(Messages.showError)
                 .then(() => paragraph.ace && paragraph.ace.focus());
@@ -1728,7 +1717,7 @@ export default ['$rootScope', '$scope', '$http', '$q', '$timeout', '$interval',
 
             $scope.metadata = [];
 
-            agentMonitor.metadata()
+            agentMgr.metadata()
                 .then((metadata) => {
                     $scope.metadata = _.sortBy(_.filter(metadata, (meta) => {
                         const cache = _.find($scope.caches, { name: meta.cacheName });
@@ -1774,7 +1763,7 @@ export default ['$rootScope', '$scope', '$http', '$q', '$timeout', '$interval',
                 }
 
                 // Show a basic modal from a controller
-                $modal({scope, templateUrl: messageTemplateUrl, placement: 'center', show: true});
+                $modal({scope, templateUrl: messageTemplateUrl, show: true});
             }
         };
 
@@ -1798,7 +1787,7 @@ export default ['$rootScope', '$scope', '$http', '$q', '$timeout', '$interval',
                 }
 
                 // Show a basic modal from a controller
-                $modal({scope, templateUrl: messageTemplateUrl, placement: 'center', show: true});
+                $modal({scope, templateUrl: messageTemplateUrl, show: true});
             }
         };
     }

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/app/modules/states/admin.state.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/states/admin.state.js b/modules/web-console/frontend/app/modules/states/admin.state.js
index ea9ba49..cbd43f5 100644
--- a/modules/web-console/frontend/app/modules/states/admin.state.js
+++ b/modules/web-console/frontend/app/modules/states/admin.state.js
@@ -27,13 +27,13 @@ angular
 .config(['$stateProvider', 'AclRouteProvider', function($stateProvider, AclRoute) {
     // set up the states
     $stateProvider
-    .state('settings.admin', {
+    .state('base.settings.admin', {
         url: '/admin',
         views: {
             '@': {
                 template
             },
-            '@settings.admin': {
+            '@base.settings.admin': {
                 templateUrl
             }
         },

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/app/modules/states/configuration.state.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/states/configuration.state.js b/modules/web-console/frontend/app/modules/states/configuration.state.js
index a609d3a..624cf5f 100644
--- a/modules/web-console/frontend/app/modules/states/configuration.state.js
+++ b/modules/web-console/frontend/app/modules/states/configuration.state.js
@@ -51,15 +51,15 @@ angular.module('ignite-console.states.configuration', ['ui.router'])
             .state('base.configuration', {
                 url: '/configuration',
                 templateUrl: sidebarTpl,
-                abstract: true
+                abstract: true,
+                params: {
+                    linkId: null
+                }
             })
             .state('base.configuration.clusters', {
                 url: '/clusters',
                 templateUrl: clustersTpl,
                 onEnter: AclRoute.checkAccess('configuration'),
-                params: {
-                    linkId: null
-                },
                 metaTags: {
                     title: 'Configure Clusters'
                 }
@@ -68,9 +68,6 @@ angular.module('ignite-console.states.configuration', ['ui.router'])
                 url: '/caches',
                 templateUrl: cachesTpl,
                 onEnter: AclRoute.checkAccess('configuration'),
-                params: {
-                    linkId: null
-                },
                 metaTags: {
                     title: 'Configure Caches'
                 }
@@ -79,9 +76,6 @@ angular.module('ignite-console.states.configuration', ['ui.router'])
                 url: '/domains',
                 templateUrl: domainsTpl,
                 onEnter: AclRoute.checkAccess('configuration'),
-                params: {
-                    linkId: null
-                },
                 metaTags: {
                     title: 'Configure Domain Model'
                 }
@@ -90,9 +84,6 @@ angular.module('ignite-console.states.configuration', ['ui.router'])
                 url: '/igfs',
                 templateUrl: igfsTpl,
                 onEnter: AclRoute.checkAccess('configuration'),
-                params: {
-                    linkId: null
-                },
                 metaTags: {
                     title: 'Configure IGFS'
                 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/app/modules/states/profile.state.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/states/profile.state.js b/modules/web-console/frontend/app/modules/states/profile.state.js
index 7c270ad..3298bdc 100644
--- a/modules/web-console/frontend/app/modules/states/profile.state.js
+++ b/modules/web-console/frontend/app/modules/states/profile.state.js
@@ -26,7 +26,7 @@ angular
 .config(['$stateProvider', 'AclRouteProvider', function($stateProvider, AclRoute) {
     // set up the states
     $stateProvider
-    .state('settings.profile', {
+    .state('base.settings.profile', {
         url: '/profile',
         templateUrl,
         onEnter: AclRoute.checkAccess('profile'),

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/app/modules/user/Auth.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/user/Auth.service.js b/modules/web-console/frontend/app/modules/user/Auth.service.js
index 95ff4c3..a2f4763 100644
--- a/modules/web-console/frontend/app/modules/user/Auth.service.js
+++ b/modules/web-console/frontend/app/modules/user/Auth.service.js
@@ -15,8 +15,8 @@
  * limitations under the License.
  */
 
-export default ['Auth', ['$http', '$rootScope', '$state', '$window', 'IgniteErrorPopover', 'IgniteMessages', 'gettingStarted', 'User', 'IgniteAgentMonitor',
-    ($http, $root, $state, $window, ErrorPopover, Messages, gettingStarted, User, agentMonitor) => {
+export default ['Auth', ['$http', '$rootScope', '$state', '$window', 'IgniteErrorPopover', 'IgniteMessages', 'gettingStarted', 'User',
+    ($http, $root, $state, $window, ErrorPopover, Messages, gettingStarted, User) => {
         return {
             forgotPassword(userInfo) {
                 $http.post('/api/v1/password/forgot', userInfo)
@@ -35,8 +35,6 @@ export default ['Auth', ['$http', '$rootScope', '$state', '$window', 'IgniteErro
 
                                 $state.go('base.configuration.clusters');
 
-                                agentMonitor.init();
-
                                 $root.gettingStarted.tryShow();
                             });
                     })

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/app/services/Confirm.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/services/Confirm.service.js b/modules/web-console/frontend/app/services/Confirm.service.js
index 0b5b9be..1638d7e 100644
--- a/modules/web-console/frontend/app/services/Confirm.service.js
+++ b/modules/web-console/frontend/app/services/Confirm.service.js
@@ -21,7 +21,7 @@ import templateUrl from 'views/templates/confirm.tpl.pug';
 export default ['IgniteConfirm', ['$rootScope', '$q', '$modal', '$animate', ($root, $q, $modal, $animate) => {
     const scope = $root.$new();
 
-    const modal = $modal({templateUrl, scope, placement: 'center', show: false, backdrop: true});
+    const modal = $modal({templateUrl, scope, show: false, backdrop: true});
 
     const _hide = () => {
         $animate.enabled(modal.$element, false);

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/app/services/ConfirmBatch.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/services/ConfirmBatch.service.js b/modules/web-console/frontend/app/services/ConfirmBatch.service.js
index 2fd55b7..8b473d8 100644
--- a/modules/web-console/frontend/app/services/ConfirmBatch.service.js
+++ b/modules/web-console/frontend/app/services/ConfirmBatch.service.js
@@ -24,7 +24,6 @@ export default ['IgniteConfirmBatch', ['$rootScope', '$q', '$modal', ($root, $q,
     scope.confirmModal = $modal({
         templateUrl,
         scope,
-        placement: 'center',
         show: false,
         backdrop: 'static',
         keyboard: false

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/controllers/caches-controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/controllers/caches-controller.js b/modules/web-console/frontend/controllers/caches-controller.js
index 176a634..bf97692 100644
--- a/modules/web-console/frontend/controllers/caches-controller.js
+++ b/modules/web-console/frontend/controllers/caches-controller.js
@@ -513,7 +513,7 @@ export default ['cachesController', [
                         ];
 
                         // Show a basic modal from a controller
-                        $modal({scope, templateUrl: infoMessageTemplateUrl, placement: 'center', show: true});
+                        $modal({scope, templateUrl: infoMessageTemplateUrl, show: true});
                     }
 
                     save(item);

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/controllers/domains-controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/controllers/domains-controller.js b/modules/web-console/frontend/controllers/domains-controller.js
index 806dd45..57050a0 100644
--- a/modules/web-console/frontend/controllers/domains-controller.js
+++ b/modules/web-console/frontend/controllers/domains-controller.js
@@ -19,8 +19,8 @@ import templateUrl from 'views/configuration/domains-import.tpl.pug';
 
 // Controller for Domain model screen.
 export default ['domainsController', [
-    '$rootScope', '$scope', '$http', '$state', '$filter', '$timeout', '$modal', 'IgniteLegacyUtils', 'IgniteMessages', 'IgniteFocus', 'IgniteConfirm', 'IgniteConfirmBatch', 'IgniteInput', 'IgniteLoading', 'IgniteModelNormalizer', 'IgniteUnsavedChangesGuard', 'IgniteAgentMonitor', 'IgniteLegacyTable', 'IgniteConfigurationResource', 'IgniteErrorPopover', 'IgniteFormUtils', 'JavaTypes', 'SqlTypes', 'IgniteActivitiesData',
-    function($root, $scope, $http, $state, $filter, $timeout, $modal, LegacyUtils, Messages, Focus, Confirm, ConfirmBatch, Input, Loading, ModelNormalizer, UnsavedChangesGuard, IgniteAgentMonitor, LegacyTable, Resource, ErrorPopover, FormUtils, JavaTypes, SqlTypes, ActivitiesData) {
+    '$rootScope', '$scope', '$http', '$state', '$filter', '$timeout', '$modal', 'IgniteLegacyUtils', 'IgniteMessages', 'IgniteFocus', 'IgniteConfirm', 'IgniteConfirmBatch', 'IgniteInput', 'IgniteLoading', 'IgniteModelNormalizer', 'IgniteUnsavedChangesGuard', 'AgentManager', 'IgniteLegacyTable', 'IgniteConfigurationResource', 'IgniteErrorPopover', 'IgniteFormUtils', 'JavaTypes', 'SqlTypes', 'IgniteActivitiesData',
+    function($root, $scope, $http, $state, $filter, $timeout, $modal, LegacyUtils, Messages, Focus, Confirm, ConfirmBatch, Input, Loading, ModelNormalizer, UnsavedChangesGuard, agentMgr, LegacyTable, Resource, ErrorPopover, FormUtils, JavaTypes, SqlTypes, ActivitiesData) {
         UnsavedChangesGuard.install($scope);
 
         const emptyDomain = {empty: true};
@@ -416,7 +416,7 @@ export default ['domainsController', [
         const hideImportDomain = importDomainModal.hide;
 
         importDomainModal.hide = function() {
-            IgniteAgentMonitor.stopWatch();
+            agentMgr.stopWatch();
 
             hideImportDomain();
         };
@@ -461,7 +461,7 @@ export default ['domainsController', [
 
                 $scope.importDomain.loadingOptions = LOADING_JDBC_DRIVERS;
 
-                IgniteAgentMonitor.startWatch({text: 'Back to Domain models', goal: 'import domain model from database'})
+                agentMgr.startWatch('Back to Domain models', 'import domain model from database')
                     .then(() => {
                         ActivitiesData.post({
                             group: 'configuration',
@@ -486,7 +486,7 @@ export default ['domainsController', [
                         $scope.jdbcDriverJars = [];
                         $scope.ui.selectedJdbcDriverJar = {};
 
-                        return IgniteAgentMonitor.drivers()
+                        return agentMgr.drivers()
                             .then((drivers) => {
                                 $scope.ui.packageName = $scope.ui.packageNameUserInput;
 
@@ -530,19 +530,19 @@ export default ['domainsController', [
          * Load list of database schemas.
          */
         function _loadSchemas() {
-            IgniteAgentMonitor.awaitAgent()
+            agentMgr.awaitAgent()
                 .then(function() {
                     $scope.importDomain.loadingOptions = LOADING_SCHEMAS;
                     Loading.start('importDomainFromDb');
 
                     if ($root.IgniteDemoMode)
-                        return IgniteAgentMonitor.schemas($scope.demoConnection);
+                        return agentMgr.schemas($scope.demoConnection);
 
                     const preset = $scope.selectedPreset;
 
                     _savePreset(preset);
 
-                    return IgniteAgentMonitor.schemas(preset);
+                    return agentMgr.schemas(preset);
                 })
                 .then(function(schemas) {
                     $scope.importDomain.schemas = _.map(schemas, function(schema) {
@@ -674,7 +674,7 @@ export default ['domainsController', [
          * Load list of database tables.
          */
         function _loadTables() {
-            IgniteAgentMonitor.awaitAgent()
+            agentMgr.awaitAgent()
                 .then(function() {
                     $scope.importDomain.loadingOptions = LOADING_TABLES;
                     Loading.start('importDomainFromDb');
@@ -690,7 +690,7 @@ export default ['domainsController', [
                             preset.schemas.push(schema.name);
                     });
 
-                    return IgniteAgentMonitor.tables(preset);
+                    return agentMgr.tables(preset);
                 })
                 .then(function(tables) {
                     _importCachesOrTemplates = [DFLT_PARTITIONED_CACHE, DFLT_REPLICATED_CACHE].concat($scope.caches);

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/package.json
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/package.json b/modules/web-console/frontend/package.json
index b11f690..fccc944 100644
--- a/modules/web-console/frontend/package.json
+++ b/modules/web-console/frontend/package.json
@@ -30,7 +30,7 @@
   ],
   "dependencies": {
     "angular": "1.5.11",
-    "angular-acl": "0.1.7",
+    "angular-acl": "0.1.8",
     "angular-animate": "1.5.11",
     "angular-aria": "1.5.11",
     "angular-cookies": "1.5.11",
@@ -44,17 +44,17 @@
     "angular-socket-io": "0.7.0",
     "angular-strap": "2.3.12",
     "angular-touch": "1.5.11",
-    "angular-translate": "2.14.0",
+    "angular-translate": "2.15.1",
     "angular-tree-control": "0.2.28",
-    "angular-ui-grid": "4.0.2",
+    "angular-ui-grid": "4.0.4",
     "angular-ui-router": "0.4.2",
     "bootstrap-sass": "3.3.7",
-    "brace": "0.8.0",
-    "es6-promise": "4.0.5",
+    "brace": "0.10.0",
+    "es6-promise": "4.1.0",
     "file-saver": "1.3.3",
     "font-awesome": "4.7.0",
     "glob": "7.1.1",
-    "jquery": "3.1.1",
+    "jquery": "3.2.1",
     "jszip": "3.1.3",
     "lodash": "4.17.4",
     "nvd3": "1.8.4",
@@ -65,26 +65,26 @@
   },
   "devDependencies": {
     "assets-webpack-plugin": "3.5.1",
-    "autoprefixer": "6.7.5",
-    "babel-core": "6.20.0",
-    "babel-eslint": "7.0.0",
-    "babel-loader": "6.2.10",
+    "autoprefixer": "6.7.7",
+    "babel-core": "6.24.1",
+    "babel-eslint": "7.2.1",
+    "babel-loader": "6.4.1",
     "babel-plugin-add-module-exports": "0.2.1",
     "babel-plugin-transform-builtin-extend": "1.1.2",
-    "babel-plugin-transform-runtime": "6.15.0",
-    "babel-polyfill": "6.20.0",
+    "babel-plugin-transform-runtime": "6.23.0",
+    "babel-polyfill": "6.23.0",
     "babel-preset-angular": "6.0.15",
-    "babel-preset-es2015": "6.18.0",
-    "babel-runtime": "6.20.0",
+    "babel-preset-es2015": "6.24.1",
+    "babel-runtime": "6.23.0",
     "chai": "3.5.0",
-    "cross-env": "3.1.4",
-    "css-loader": "0.26.2",
-    "eslint": "3.16.1",
+    "cross-env": "4.0.0",
+    "css-loader": "0.28.0",
+    "eslint": "3.19.0",
     "eslint-friendly-formatter": "2.0.7",
-    "eslint-loader": "1.6.3",
+    "eslint-loader": "1.7.1",
     "expose-loader": "0.7.3",
-    "extract-text-webpack-plugin": "2.0.0",
-    "file-loader": "0.10.1",
+    "extract-text-webpack-plugin": "2.1.0",
+    "file-loader": "0.11.1",
     "gulp": "3.9.1",
     "gulp-eslint": "3.0.1",
     "gulp-inject": "4.2.0",
@@ -95,31 +95,31 @@
     "html-webpack-plugin": "2.28.0",
     "jasmine-core": "2.5.2",
     "json-loader": "0.5.4",
-    "karma": "1.5.0",
+    "karma": "1.6.0",
     "karma-babel-preprocessor": "6.0.1",
     "karma-jasmine": "1.1.0",
     "karma-mocha": "1.3.0",
-    "karma-mocha-reporter": "2.2.2",
-    "karma-phantomjs-launcher": "1.0.2",
+    "karma-mocha-reporter": "2.2.3",
+    "karma-phantomjs-launcher": "1.0.4",
     "karma-teamcity-reporter": "1.0.0",
-    "karma-webpack": "2.0.2",
+    "karma-webpack": "2.0.3",
     "mocha": "3.2.0",
     "mocha-teamcity-reporter": "1.1.1",
     "ngtemplate-loader": "1.3.1",
-    "node-sass": "4.5.0",
+    "node-sass": "4.5.2",
     "phantomjs-prebuilt": "2.1.14",
-    "postcss-loader": "1.3.2",
+    "postcss-loader": "1.3.3",
     "progress-bar-webpack-plugin": "1.9.3",
     "pug-html-loader": "1.1.0",
     "pug-loader": "2.3.0",
     "require-dir": "0.3.1",
-    "resolve-url-loader": "2.0.0",
-    "sass-loader": "6.0.2",
-    "style-loader": "0.13.2",
+    "resolve-url-loader": "2.0.2",
+    "sass-loader": "6.0.3",
+    "style-loader": "0.16.1",
     "url": "0.11.0",
     "url-loader": "0.5.8",
-    "webpack": "2.2.1",
-    "webpack-dev-server": "2.4.1",
+    "webpack": "2.3.3",
+    "webpack-dev-server": "2.4.2",
     "webpack-stream": "3.2.0",
     "worker-loader": "0.8.0"
   }

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/public/stylesheets/_bootstrap-variables.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/public/stylesheets/_bootstrap-variables.scss b/modules/web-console/frontend/public/stylesheets/_bootstrap-variables.scss
index 07e8c51..e308de3 100644
--- a/modules/web-console/frontend/public/stylesheets/_bootstrap-variables.scss
+++ b/modules/web-console/frontend/public/stylesheets/_bootstrap-variables.scss
@@ -441,7 +441,7 @@ $navbar-inverse-toggle-border-color:        #333 !default;
 //##
 
 //=== Shared nav styles
-$nav-link-padding:                          10px 15px !default;
+$nav-link-padding:                          0 0 0 15px !default;
 $nav-link-hover-bg:                         transparent !default;
 
 $nav-disabled-link-color:                   $gray-light !default;

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/public/stylesheets/_font-awesome-custom.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/public/stylesheets/_font-awesome-custom.scss b/modules/web-console/frontend/public/stylesheets/_font-awesome-custom.scss
index 47555a7..16670a5 100644
--- a/modules/web-console/frontend/public/stylesheets/_font-awesome-custom.scss
+++ b/modules/web-console/frontend/public/stylesheets/_font-awesome-custom.scss
@@ -97,3 +97,8 @@ $fa-font-path: '~font-awesome/fonts';
   
   margin: 0;
 }
+
+.icon-cluster {
+  @extend .fa;
+  @extend .fa-sitemap;
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/public/stylesheets/style.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/public/stylesheets/style.scss b/modules/web-console/frontend/public/stylesheets/style.scss
index 882a363..f3dd3c9 100644
--- a/modules/web-console/frontend/public/stylesheets/style.scss
+++ b/modules/web-console/frontend/public/stylesheets/style.scss
@@ -274,10 +274,6 @@ ul.navbar-nav, .sidebar-nav {
     }
 }
 
-.header .nav.navbar-nav.pull-right > li > a {
-    padding-right: 0;
-}
-
 .header .title {
     margin: 20px 0 5px 0;
     padding: 0 15px;
@@ -285,26 +281,36 @@ ul.navbar-nav, .sidebar-nav {
     font-size: 1.4em;
 }
 
-.header .nav.navbar-nav .not-link {
-    padding: 15px;
-    display: inline-block;
-}
-
 .nav > li {
     > a {
-        color: $navbar-default-link-color
-    }
-    > a:hover {
-        color: $link-hover-color
+        color: $navbar-default-link-color;
+
+        &:hover,
+        &:focus {
+            color: $link-hover-color;
+        }
+
+        &.active {
+            color: $link-color;
+        }
     }
-    > a.active {
-        color: $link-color
+
+    &.disabled > a {
+        label:hover, label:focus, i:hover, i:focus {
+            cursor: default;
+        }
     }
 }
 
-.theme-line header .navbar-nav a {
-    line-height: 25px;
-    font-size: 18px;
+.theme-line header .navbar-nav {
+    a {
+        line-height: 25px;
+        font-size: 18px;
+    }
+
+    > li > a {
+        padding-right: 0;
+    }
 }
 
 .theme-line .section-right {
@@ -1621,6 +1627,19 @@ th[st-sort] {
     padding: 10px 10px 10px 20px;
 }
 
+// Hack to solve an issue where scrollbars aren't visible in Safari.
+// Safari seems to clip part of the scrollbar element. By giving the
+// element a background, we're telling Safari that it *really* needs to
+// paint the whole area. See https://github.com/ajaxorg/ace/issues/2872
+.ace_scrollbar-inner {
+	background-color: #FFF;
+	opacity: 0.01;
+
+    .ace_dark & {
+        background-color: #000;
+    }
+}
+
 .ace_content {
     padding-left: 5px;
 }
@@ -2357,4 +2376,4 @@ html,body,.splash-screen {
             height: 65px;
         }
     }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/views/includes/header.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/views/includes/header.pug b/modules/web-console/frontend/views/includes/header.pug
index 99bcea9..ff8692e 100644
--- a/modules/web-console/frontend/views/includes/header.pug
+++ b/modules/web-console/frontend/views/includes/header.pug
@@ -27,26 +27,31 @@ header#header.header
                         a.dropdown-toggle Configure
                             span.caret
 
-                ul.nav.navbar-nav(ng-controller='notebookController')
-                    li.sql-notebooks(ng-if='IgniteDemoMode' ng-class='{active: $state.includes("base.sql")}')
+                ul.nav.navbar-nav(ng-if='IgniteDemoMode' ng-controller='notebookController')
+                    li.sql-notebooks( ng-class='{active: $state.includes("base.sql")}')
                         a(ui-sref='base.sql.demo') Queries
 
-                    li.sql-notebooks(ng-if='!IgniteDemoMode && !notebooks.length' ng-class='{active: $state.includes("base.sql")}')
+                ul.nav.navbar-nav(ng-if='!IgniteDemoMode' ng-controller='notebookController')
+                    li.sql-notebooks(ng-if='!notebooks.length' ng-class='{active: $state.includes("base.sql")}')
                         a(ng-click='createNotebook()') Queries
 
-                    li.sql-notebooks(ng-if='!IgniteDemoMode && notebooks.length' ng-class='{active: $state.includes("base.sql")}' bs-dropdown='notebooks' data-placement='bottom-left' data-trigger='hover focus' data-container='self' data-animation='' ng-click='$event.stopPropagation()' aria-haspopup='true' aria-expanded='false')
+                    li.sql-notebooks(ng-if='notebooks.length' ng-class='{active: $state.includes("base.sql")}' bs-dropdown='notebooks' data-placement='bottom-left' data-trigger='hover focus' data-container='self' data-animation='' ng-click='$event.stopPropagation()' aria-haspopup='true' aria-expanded='false')
                         a.dropdown-toggle Queries
                             span.caret
 
+                ul.nav.navbar-nav
                     li(ui-sref-active='active' ng-repeat='item in navbar.items' ng-class='{active: $state.includes("base.monitoring")}' bs-dropdown='item.children' data-placement='bottom-left' data-trigger='hover focus' data-container='self' data-animation='' ng-click='$event.stopPropagation()' aria-haspopup='true' aria-expanded='false')
                         a.dropdown-toggle {{::item.text}}
                             span.caret
 
-                a(ng-controller='demoController')
+                a.padding-left-dflt(ng-controller='demoController')
                     button.btn.btn-info(ng-if='IgniteDemoMode' ng-click='closeDemo()') Close demo
                     button.btn.btn-info(ng-if='!IgniteDemoMode' ng-click='startDemo()') Start demo
 
-                ul.nav.navbar-nav.pull-right(ignite-userbar)
-                    li(bs-dropdown='userbar.items' data-placement='bottom-right' data-trigger='hover focus' data-container='self' data-animation='' ng-class='{active: $state.includes("settings")}' ng-click='$event.stopPropagation()')
-                        a.dropdown-toggle {{user.firstName}} {{user.lastName}}
-                            span.caret
+                .pull-right
+                    ignite-cluster-select
+
+                    ul.nav.navbar-nav.pull-right(ignite-userbar)
+                        li(bs-dropdown='userbar.items' data-placement='bottom-right' data-trigger='hover focus' data-container='self' data-animation='' ng-class='{active: $state.includes("settings")}' ng-click='$event.stopPropagation()')
+                            a.dropdown-toggle {{user.firstName}} {{user.lastName}}
+                                span.caret

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/views/templates/agent-download.tpl.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/views/templates/agent-download.tpl.pug b/modules/web-console/frontend/views/templates/agent-download.tpl.pug
index b913636..615abbc 100644
--- a/modules/web-console/frontend/views/templates/agent-download.tpl.pug
+++ b/modules/web-console/frontend/views/templates/agent-download.tpl.pug
@@ -15,29 +15,38 @@
     limitations under the License.
 
 .modal.center(tabindex='-1' role='dialog')
-    .modal-dialog
-        .modal-content
-            #errors-container.modal-header.header
+    .modal-dialog(ng-switch='ctrl.status')
+        .modal-content(ng-switch-when='agentMissing')
+            .modal-header.header
                 h4.modal-title
                     i.fa.fa-download
-                    span(ng-if='!hasAgents') Connection to Ignite Web Agent is not established
-                    span(ng-if='hasAgents') Connection to Ignite Node is not established
-            .agent-download(ng-if='!hasAgents')
-                p Please download and run #[a(href='/api/v1/agent/download/zip' target='_self') ignite-web-agent] in order to {{::agentGoal}}
+                    span Connection to Ignite Web Agent is not established
+            .modal-body.agent-download
+                p Please download and run #[a(href='/api/v1/downloads/agent' target='_self') ignite-web-agent] to use this functionality
                 p For run:
                 ul
-                    li Download and unzip #[a(href='/api/v1/agent/download/zip' target='_self') ignite-web-agent] archive
+                    li Download and unzip #[a(href='/api/v1/downloads/agent' target='_self') ignite-web-agent] archive
                     li Run shell file #[b ignite-web-agent.{sh|bat}]
                 p Refer to #[b README.txt] in agent folder for more information
                 .modal-advanced-options
-                    i.fa.fa-chevron-circle-down(ng-show='agentLoad.showToken' ng-click='agentLoad.showToken = ! agentLoad.showToken')
-                    i.fa.fa-chevron-circle-right(ng-show='!agentLoad.showToken' ng-click='agentLoad.showToken = ! agentLoad.showToken')
-                    a(ng-click='agentLoad.showToken = ! agentLoad.showToken') {{agentLoad.showToken ? 'Hide security token...' : 'Show security token...'}}
-                .details-row(ng-show='agentLoad.showToken')
+                    i.fa(ng-class='showToken ? "fa-chevron-circle-down" : "fa-chevron-circle-right"' ng-click='showToken = !showToken')
+                    a(ng-click='showToken = !showToken') {{showToken ? 'Hide security token...' : 'Show security token...'}}
+                .details-row(ng-show='showToken')
                     label.labelField Security token: {{user.becameToken || user.token}}
                     i.tipLabel.fa.fa-clipboard(ignite-copy-to-clipboard='{{user.becameToken || user.token}}' bs-tooltip='' data-title='Copy security token to clipboard')
                     i.tipLabel.icon-help(ng-if=lines bs-tooltip='' data-title='The security token is used for authorization of web agent')
-            .agent-download(ng-if='hasAgents')
+
+            .modal-footer
+                button.btn.btn-default(ng-click='ctrl.back()') {{::ctrl.backText}}
+                a.btn.btn-primary(href='/api/v1/downloads/agent' target='_self') Download agent
+
+        .modal-content(ng-switch-when='nodeMissing')
+            .modal-header.header
+                h4.modal-title
+                    i.fa.fa-download
+                    span Connection to Ignite Node is not established
+
+            .modal-body.agent-download
                 p Connection to Ignite Web Agent is established, but agent failed to connect to Ignite Node
                 p Please check the following:
                 ul
@@ -45,6 +54,6 @@
                     li In agent settings check URI for connect to Ignite REST server
                     li Check agent logs for errors
                     li Refer to #[b README.txt] in agent folder for more information
+
             .modal-footer
-                button.btn.btn-default(ng-click='back()') {{::backText}}
-                a.btn.btn-primary(ng-if='!hasAgents' href='/api/v1/agent/download/zip' target='_self') Download agent
+                button.btn.btn-default(ng-click='ctrl.back()') {{::ctrl.backText}}

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/views/templates/demo-info.tpl.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/views/templates/demo-info.tpl.pug b/modules/web-console/frontend/views/templates/demo-info.tpl.pug
index 44c091c..62642b3 100644
--- a/modules/web-console/frontend/views/templates/demo-info.tpl.pug
+++ b/modules/web-console/frontend/views/templates/demo-info.tpl.pug
@@ -44,4 +44,4 @@
                         li Close dialog and try Web Console
             .modal-footer
                 button.btn.btn-default(ng-class='hasAgents ? "btn-primary" : "btn-default"' ng-click='close()') Close
-                button.btn.btn-primary(ng-hide='hasAgents' ng-click='downloadAgent()') Download agent
+                a.btn.btn-primary(ng-hide='hasAgents' href='/api/v1/downloads/agent' target='_self') Download agent

http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/web-agent/pom.xml
----------------------------------------------------------------------
diff --git a/modules/web-console/web-agent/pom.xml b/modules/web-console/web-agent/pom.xml
index 530aef6..697e58f 100644
--- a/modules/web-console/web-agent/pom.xml
+++ b/modules/web-console/web-agent/pom.xml
@@ -43,7 +43,7 @@
         <dependency>
             <groupId>io.socket</groupId>
             <artifactId>socket.io-client</artifactId>
-            <version>0.7.0</version>
+            <version>0.8.3</version>
         </dependency>
 
         <dependency>
@@ -59,9 +59,9 @@
         </dependency>
 
         <dependency>
-            <groupId>org.apache.httpcomponents</groupId>
-            <artifactId>httpclient</artifactId>
-            <version>${httpclient.version}</version>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+            <version>3.6.0</version>
         </dependency>
 
         <dependency>
@@ -98,9 +98,15 @@
 
         <dependency>
             <groupId>org.apache.ignite</groupId>
-            <artifactId>ignite-log4j</artifactId>
+            <artifactId>ignite-slf4j</artifactId>
             <version>${project.version}</version>
         </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jul-to-slf4j</artifactId>
+            <version>${slf4j.version}</version>
+        </dependency>
     </dependencies>
 
     <build>