You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by an...@apache.org on 2016/03/28 10:47:47 UTC

[07/50] [abbrv] ignite git commit: IGNITE-2597 Refactored to websockets.

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-web/src/main/js/controllers/sql-controller.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/sql-controller.js b/modules/control-center-web/src/main/js/controllers/sql-controller.js
index 8336afd..dd712f8 100644
--- a/modules/control-center-web/src/main/js/controllers/sql-controller.js
+++ b/modules/control-center-web/src/main/js/controllers/sql-controller.js
@@ -17,15 +17,18 @@
 
 // Controller for SQL notebook screen.
 consoleModule.controller('sqlController', [
-    '$scope', '$http', '$timeout', '$interval', '$animate', '$location', '$anchorScroll', '$state', '$modal', '$popover', '$loading', '$common', '$confirm', '$agentDownload', 'QueryNotebooks', 'uiGridExporterConstants',
-    function ($scope, $http, $timeout, $interval, $animate, $location, $anchorScroll, $state, $modal, $popover, $loading, $common, $confirm, $agentDownload, QueryNotebooks, uiGridExporterConstants) {
+    '$scope', '$http', '$timeout', '$interval', '$animate', '$location', '$anchorScroll', '$state', '$modal', '$popover', '$loading', '$common', '$confirm', 'IgniteAgentMonitor', 'IgniteChartColors', 'QueryNotebooks', 'uiGridExporterConstants',
+    function ($scope, $http, $timeout, $interval, $animate, $location, $anchorScroll, $state, $modal, $popover, $loading, $common, $confirm, IgniteAgentMonitor, IgniteChartColors, QueryNotebooks, uiGridExporterConstants) {
+
+        var stopTopology = null;
+
         $scope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
+            $interval.cancel(stopTopology);
+
             if ($scope.notebook && $scope.notebook.paragraphs)
                 $scope.notebook.paragraphs.forEach(function (paragraph) {
                     _tryStopRefresh(paragraph);
                 });
-
-            $agentDownload.stopAwaitAgent();
         });
 
         $scope.joinTip = $common.joinTip;
@@ -84,17 +87,12 @@ consoleModule.controller('sqlController', [
         // We need max 1800 items to hold history for 30 mins in case of refresh every second.
         var HISTORY_LENGTH = 1800;
 
-        var CHART_COLORS = [
-            '#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf',
-            '#ffbb78', '#98df8a', '#ff9896', '#c5b0d5', '#aec7e8', '#c49c94', '#f7b6d2', '#c7c7c7', '#dbdb8d', '#9edae5'
-        ];
-
-        var MAX_VAL_COLS = CHART_COLORS.length;
+        var MAX_VAL_COLS = IgniteChartColors.length;
 
         $anchorScroll.yOffset = 55;
 
         $scope.chartColor = function(index) {
-            return {"color": "white", "background-color": CHART_COLORS[index]};
+            return {"color": "white", "background-color": IgniteChartColors[index]};
         };
 
         $scope.chartRemoveKeyColumn = function (paragraph, index) {
@@ -256,6 +254,23 @@ consoleModule.controller('sqlController', [
                 });
         };
 
+        var _refreshFn = function() {
+            IgniteAgentMonitor.topology($scope.demo)
+                .then(function(clusters) {
+                    var caches = _.flattenDeep(clusters.map(function (cluster) { return cluster._caches; }));
+
+                    $scope.caches = _.sortBy(_.uniq(_.reject(caches, { mode: 'LOCAL' }), function (cache) {
+                        return _mask(cache.name);
+                    }), 'name');
+
+                    _setActiveCache();
+                })
+                .catch(_handleException)
+                .finally(function() {
+                    $loading.finish('loading');
+                });
+        };
+
         var loadNotebook = function (notebook) {
             $scope.notebook = notebook;
 
@@ -278,14 +293,28 @@ consoleModule.controller('sqlController', [
             else
                 $scope.rebuildScrollParagraphs();
 
-            $agentDownload.startTopologyListening(getTopology, _onConnect, $scope.demo);
-        };
+            IgniteAgentMonitor.startWatch({
+                    state: 'base.configuration.clusters',
+                    text: 'Back to Configuration',
+                    goal: 'execute sql statements'
+                }, function () {
+                    $state.go('base.sql.demo');
+                })
+                .then(function () {
+                    $loading.start('loading');
 
-        $loading.start('loading');
+                    _refreshFn();
+
+                    if ($scope.demo)
+                        _.forEach($scope.notebook.paragraphs, $scope.execute);
+
+                    stopTopology = $interval(_refreshFn, 5000, 0, false);
+                });
+        };
 
         QueryNotebooks.read($scope.demo, $state.params.noteId)
             .then(loadNotebook)
-            .catch(function(err) {
+            .catch(function() {
                 $scope.notebookLoadFailed = true;
 
                 $loading.finish('loading');
@@ -329,9 +358,7 @@ consoleModule.controller('sqlController', [
                     else
                         $state.go('base.configuration.clusters');
                 })
-                .catch(function (errMsg) {
-                    $common.showError(errMsg);
-                });
+                .catch(_handleException);
         };
 
         $scope.renameParagraph = function (paragraph, newName) {
@@ -441,27 +468,6 @@ consoleModule.controller('sqlController', [
             return panel_idx >= 0;
         };
 
-        function getTopology(clusters, onSuccess) {
-            onSuccess();
-
-            var caches = _.flattenDeep(clusters.map(function (cluster) { return cluster._caches; }));
-
-            $scope.caches = _.sortBy(_.uniq(_.reject(caches, { mode: 'LOCAL' }), function (cache) {
-                return _mask(cache.name);
-            }), 'name');
-
-            _setActiveCache();
-
-            $loading.finish('loading');
-        }
-
-        function _onConnect() {
-            if (!$scope.demo)
-                return;
-
-            _.forEach($scope.notebook.paragraphs, $scope.execute);
-        }
-
         var _columnFilter = function(paragraph) {
             return paragraph.disabledSystemColumns || paragraph.systemColumns ? _allColumn : _hideColumn;
         };
@@ -579,116 +585,113 @@ consoleModule.controller('sqlController', [
             return retainedCols;
         }
 
-        var _tryCloseQueryResult = function (queryId) {
-            if (queryId)
-                $http.post('/api/v1/agent/query/close', { demo: $scope.demo, queryId: queryId });
-        };
-
-        var _processQueryResult = function (paragraph, refreshMode) {
-            return function (res) {
-                var prevKeyCols = paragraph.chartKeyCols;
-                var prevValCols = paragraph.chartValCols;
+        var _processQueryResult = function (paragraph, res) {
+            var prevKeyCols = paragraph.chartKeyCols;
+            var prevValCols = paragraph.chartValCols;
 
-                if (res.meta && !refreshMode) {
-                    paragraph.meta = [];
+            if (!_.eq(paragraph.meta, res.meta)) {
+                paragraph.meta = [];
 
-                    paragraph.chartColumns = [];
+                paragraph.chartColumns = [];
 
-                    if (!$common.isDefined(paragraph.chartKeyCols))
-                        paragraph.chartKeyCols = [];
+                if (!$common.isDefined(paragraph.chartKeyCols))
+                    paragraph.chartKeyCols = [];
 
-                    if (!$common.isDefined(paragraph.chartValCols))
-                        paragraph.chartValCols = [];
+                if (!$common.isDefined(paragraph.chartValCols))
+                    paragraph.chartValCols = [];
 
-                    if (res.meta.length <= 2) {
-                        var _key = _.find(res.meta, {fieldName: '_KEY'});
-                        var _val = _.find(res.meta, {fieldName: '_VAL'});
+                if (res.meta.length <= 2) {
+                    var _key = _.find(res.meta, {fieldName: '_KEY'});
+                    var _val = _.find(res.meta, {fieldName: '_VAL'});
 
-                        paragraph.disabledSystemColumns = (res.meta.length == 2 && _key && _val) ||
-                            (res.meta.length == 1 && (_key || _val));
-                    }
+                    paragraph.disabledSystemColumns = (res.meta.length == 2 && _key && _val) ||
+                        (res.meta.length == 1 && (_key || _val));
+                }
 
-                    paragraph.columnFilter = _columnFilter(paragraph);
+                paragraph.columnFilter = _columnFilter(paragraph);
 
-                    paragraph.meta = res.meta;
+                paragraph.meta = res.meta;
 
-                    _rebuildColumns(paragraph);
-                }
+                _rebuildColumns(paragraph);
+            }
 
-                paragraph.page = 1;
+            paragraph.page = 1;
 
-                paragraph.total = 0;
+            paragraph.total = 0;
 
-                paragraph.queryId = res.queryId;
+            paragraph.queryId = res.queryId;
 
-                delete paragraph.errMsg;
+            delete paragraph.errMsg;
 
-                // Prepare explain results for display in table.
-                if (paragraph.queryArgs.type == "EXPLAIN" && res.rows) {
-                    paragraph.rows = [];
+            // Prepare explain results for display in table.
+            if (paragraph.queryArgs.type == "EXPLAIN" && res.rows) {
+                paragraph.rows = [];
 
-                    res.rows.forEach(function (row, i) {
-                        var line = res.rows.length - 1 == i ? row[0] : row[0] + '\n';
+                res.rows.forEach(function (row, i) {
+                    var line = res.rows.length - 1 == i ? row[0] : row[0] + '\n';
 
-                        line.replace(/\"/g, '').split('\n').forEach(function (line) {
-                            paragraph.rows.push([line]);
-                        });
+                    line.replace(/\"/g, '').split('\n').forEach(function (line) {
+                        paragraph.rows.push([line]);
                     });
-                }
-                else
-                    paragraph.rows = res.rows;
-
-                paragraph.gridOptions.setRows(paragraph.rows);
+                });
+            }
+            else
+                paragraph.rows = res.rows;
 
-                var chartHistory = paragraph.chartHistory;
+            paragraph.gridOptions.setRows(paragraph.rows);
 
-                // Clear history on query change.
-                var queryChanged = paragraph.prevQuery != paragraph.query;
+            var chartHistory = paragraph.chartHistory;
 
-                if (queryChanged) {
-                    paragraph.prevQuery = paragraph.query;
+            // Clear history on query change.
+            var queryChanged = paragraph.prevQuery != paragraph.query;
 
-                    chartHistory.length = 0;
+            if (queryChanged) {
+                paragraph.prevQuery = paragraph.query;
 
-                    _.forEach(paragraph.charts, function (chart) {
-                        chart.data.length = 0;
-                    })
-                }
+                chartHistory.length = 0;
 
-                // Add results to history.
-                chartHistory.push({tm: new Date(), rows: paragraph.rows});
+                _.forEach(paragraph.charts, function (chart) {
+                    chart.data.length = 0;
+                })
+            }
 
-                // Keep history size no more than max length.
-                while (chartHistory.length > HISTORY_LENGTH)
-                    chartHistory.shift();
+            // Add results to history.
+            chartHistory.push({tm: new Date(), rows: paragraph.rows});
 
-                _showLoading(paragraph, false);
+            // Keep history size no more than max length.
+            while (chartHistory.length > HISTORY_LENGTH)
+                chartHistory.shift();
 
-                if (paragraph.result == 'none' || paragraph.queryArgs.type != "QUERY")
-                    paragraph.result = 'table';
-                else if (paragraph.chart()) {
-                    var resetCharts = queryChanged;
+            _showLoading(paragraph, false);
 
-                    if (!resetCharts) {
-                        var curKeyCols = paragraph.chartKeyCols;
-                        var curValCols = paragraph.chartValCols;
+            if (paragraph.result == 'none' || paragraph.queryArgs.type != "QUERY")
+                paragraph.result = 'table';
+            else if (paragraph.chart()) {
+                var resetCharts = queryChanged;
 
-                        resetCharts = !prevKeyCols || !prevValCols ||
-                            prevKeyCols.length != curKeyCols.length ||
-                            prevValCols.length != curValCols.length;
-                    }
+                if (!resetCharts) {
+                    var curKeyCols = paragraph.chartKeyCols;
+                    var curValCols = paragraph.chartValCols;
 
-                    _chartApplySettings(paragraph, resetCharts);
+                    resetCharts = !prevKeyCols || !prevValCols ||
+                        prevKeyCols.length != curKeyCols.length ||
+                        prevValCols.length != curValCols.length;
                 }
+
+                _chartApplySettings(paragraph, resetCharts);
             }
         };
 
         var _executeRefresh = function (paragraph) {
-            _tryCloseQueryResult(paragraph.queryId);
-
-            $http.post('/api/v1/agent/query', paragraph.queryArgs)
-                .success(_processQueryResult(paragraph, true))
-                .error(function (errMsg) {
+            IgniteAgentMonitor.awaitAgent()
+                .then(function () {
+                    return IgniteAgentMonitor.queryClose(paragraph.queryArgs);
+                })
+                .then(function () {
+                    return IgniteAgentMonitor.query(paragraph.queryArgs);
+                })
+                .then(_processQueryResult.bind(this, paragraph))
+                .catch(function (errMsg) {
                     paragraph.errMsg = errMsg;
                 });
         };
@@ -705,31 +708,33 @@ consoleModule.controller('sqlController', [
 
             _showLoading(paragraph, true);
 
-            _tryCloseQueryResult(paragraph.queryId);
-
-            paragraph.queryArgs = {
-                demo: $scope.demo,
-                type: "QUERY",
-                query: paragraph.query,
-                pageSize: paragraph.pageSize,
-                cacheName: paragraph.cacheName || undefined
-            };
-
-            $http.post('/api/v1/agent/query', paragraph.queryArgs)
-                .success(function (res) {
-                    _processQueryResult(paragraph)(res);
+            IgniteAgentMonitor.queryClose(paragraph.queryArgs)
+                .then(function () {
+                    paragraph.queryArgs = {
+                        demo: $scope.demo,
+                        type: "QUERY",
+                        query: paragraph.query,
+                        pageSize: paragraph.pageSize,
+                        cacheName: paragraph.cacheName || undefined
+                    };
+
+                    return IgniteAgentMonitor.query(paragraph.queryArgs);
+                })
+                .then(function (res) {
+                    _processQueryResult(paragraph, res);
 
                     _tryStartRefresh(paragraph);
                 })
-                .error(function (errMsg) {
+                .catch(function (errMsg) {
                     paragraph.errMsg = errMsg;
 
-                    _showLoading(paragraph, false);
+                        _showLoading(paragraph, false);
 
                     $scope.stopRefresh(paragraph);
+                })
+                .finally(function () {
+                    paragraph.ace.focus();
                 });
-
-            paragraph.ace.focus();
         };
 
         $scope.queryExecuted = function(paragraph) {
@@ -744,25 +749,27 @@ consoleModule.controller('sqlController', [
 
             _showLoading(paragraph, true);
 
-            _tryCloseQueryResult(paragraph.queryId);
-
-            paragraph.queryArgs = {
-                demo: $scope.demo,
-                type: "EXPLAIN",
-                query: 'EXPLAIN ' + paragraph.query,
-                pageSize: paragraph.pageSize,
-                cacheName: paragraph.cacheName || undefined
-            };
-
-            $http.post('/api/v1/agent/query', paragraph.queryArgs)
-                .success(_processQueryResult(paragraph))
-                .error(function (errMsg) {
+            IgniteAgentMonitor.queryClose(paragraph.queryArgs)
+                .then(function () {
+                    paragraph.queryArgs = {
+                        demo: $scope.demo,
+                        type: "EXPLAIN",
+                        query: 'EXPLAIN ' + paragraph.query,
+                        pageSize: paragraph.pageSize,
+                        cacheName: paragraph.cacheName || undefined
+                    };
+
+                    return IgniteAgentMonitor.query(paragraph.queryArgs);
+                })
+                .then(_processQueryResult.bind(this, paragraph))
+                .catch(function (errMsg) {
                     paragraph.errMsg = errMsg;
 
                     _showLoading(paragraph, false);
+                })
+                .finally(function () {
+                    paragraph.ace.focus();
                 });
-
-            paragraph.ace.focus();
         };
 
         $scope.scan = function (paragraph) {
@@ -773,36 +780,40 @@ consoleModule.controller('sqlController', [
 
             _showLoading(paragraph, true);
 
-            _tryCloseQueryResult(paragraph.queryId);
-
-            paragraph.queryArgs = {
-                demo: $scope.demo,
-                type: "SCAN",
-                pageSize: paragraph.pageSize,
-                cacheName: paragraph.cacheName || undefined
-            };
-
-            $http.post('/api/v1/agent/scan', paragraph.queryArgs)
-                .success(_processQueryResult(paragraph))
-                .error(function (errMsg) {
+            IgniteAgentMonitor.queryClose(paragraph.queryArgs)
+                .then(function () {
+                    paragraph.queryArgs = {
+                        demo: $scope.demo,
+                        type: "SCAN",
+                        pageSize: paragraph.pageSize,
+                        cacheName: paragraph.cacheName || undefined
+                    };
+
+                    return IgniteAgentMonitor.query(paragraph.queryArgs);
+                })
+                .then(_processQueryResult.bind(this, paragraph))
+                .catch(function (errMsg) {
                     paragraph.errMsg = errMsg;
 
                     _showLoading(paragraph, false);
+                })
+                .finally(function () {
+                    paragraph.ace.focus();
                 });
-
-            paragraph.ace.focus();
         };
 
         $scope.nextPage = function(paragraph) {
             _showLoading(paragraph, true);
 
-            $http.post('/api/v1/agent/query/fetch', {
-                    demo: $scope.demo,
-                    queryId: paragraph.queryId,
-                    pageSize: paragraph.pageSize,
-                    cacheName: paragraph.queryArgs.cacheName
-                })
-                .success(function (res) {
+            paragraph.queryArgs = {
+                demo: $scope.demo,
+                queryId: paragraph.queryId,
+                pageSize: paragraph.pageSize,
+                cacheName: paragraph.queryArgs.cacheName
+            };
+
+            IgniteAgentMonitor.next(paragraph.queryArgs)
+                .then(function (res) {
                     paragraph.page++;
 
                     paragraph.total += paragraph.rows.length;
@@ -823,10 +834,13 @@ consoleModule.controller('sqlController', [
                     if (res.last)
                         delete paragraph.queryId;
                 })
-                .error(function (errMsg) {
+                .catch(function (errMsg) {
                     paragraph.errMsg = errMsg;
 
                     _showLoading(paragraph, false);
+                })
+                .finally(function () {
+                    paragraph.ace.focus();
                 });
         };
 
@@ -887,17 +901,18 @@ consoleModule.controller('sqlController', [
             paragraph.gridOptions.api.exporter.pdfExport(uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE);
         };
 
-        $scope.exportCsvAll = function(paragraph) {
-            $http.post('/api/v1/agent/query/getAll', {
+        $scope.exportCsvAll = function (paragraph) {
+            IgniteAgentMonitor.query({
                     demo: $scope.demo,
+                    type: paragraph.queryArgs.type,
                     query: paragraph.queryArgs.query,
                     cacheName: paragraph.queryArgs.cacheName
                 })
-                .success(function (response) {
-                    _export(paragraph.name + '-all.csv', response.meta, response.rows);
+                .then(function (res) {
+                    _export(paragraph.name + '-all.csv', res.meta, res.rows);
                 })
-                .error(function (errMsg) {
-                    $common.showError(errMsg);
+                .finally(function () {
+                    paragraph.ace.focus();
                 });
         };
 
@@ -1337,7 +1352,7 @@ consoleModule.controller('sqlController', [
                             axisLabel:  _yAxisLabel(paragraph),
                             tickFormat: _yAxisFormat
                         },
-                        color: CHART_COLORS,
+                        color: IgniteChartColors,
                         stacked: stacked,
                         showControls: true
                     }
@@ -1405,7 +1420,7 @@ consoleModule.controller('sqlController', [
                             axisLabel:  _yAxisLabel(paragraph),
                             tickFormat: _yAxisFormat
                         },
-                        color: CHART_COLORS,
+                        color: IgniteChartColors,
                         useInteractiveGuideline: true
                     }
                 };
@@ -1426,27 +1441,27 @@ consoleModule.controller('sqlController', [
                     ? paragraph.chartsOptions.areaChart.style
                     : 'stack';
 
-                var options = {
-                    chart: {
-                        type: 'stackedAreaChart',
-                        height: 400,
-                        margin: {left: 70},
-                        duration: 0,
-                        x: _xX,
-                        y: _yY,
-                        xAxis: {
-                            axisLabel: _xAxisLabel(paragraph),
-                            tickFormat: paragraph.chartTimeLineEnabled() ? _xAxisTimeFormat : _xAxisWithLabelFormat(paragraph),
-                            showMaxMin: false
-                        },
-                        yAxis: {
-                            axisLabel:  _yAxisLabel(paragraph),
-                            tickFormat: _yAxisFormat
-                        },
-                        color: CHART_COLORS,
-                        style: style
-                    }
-                };
+            var options = {
+                chart: {
+                    type: 'stackedAreaChart',
+                    height: 400,
+                    margin: {left: 70},
+                    duration: 0,
+                    x: _xX,
+                    y: _yY,
+                    xAxis: {
+                        axisLabel: _xAxisLabel(paragraph),
+                        tickFormat: paragraph.chartTimeLineEnabled() ? _xAxisTimeFormat : _xAxisWithLabelFormat(paragraph),
+                        showMaxMin: false
+                    },
+                    yAxis: {
+                        axisLabel:  _yAxisLabel(paragraph),
+                        tickFormat: _yAxisFormat
+                    },
+                    color: IgniteChartColors,
+                    style: style
+                }
+            };
 
                 paragraph.charts = [{options: options, data: datum}];
 
@@ -1487,10 +1502,10 @@ consoleModule.controller('sqlController', [
 
             $scope.metadata = [];
 
-            $http.post('/api/v1/agent/cache/metadata', {demo: $scope.demo})
-                .success(function (metadata) {
-                    $scope.metadata = _.sortBy(_.filter(metadata, function (meta) {
-                        var cache = _.find($scope.caches, { name: meta.cacheName });
+        IgniteAgentMonitor.metadata($scope.demo)
+            .then(function (metadata) {
+                $scope.metadata = _.sortBy(_.filter(metadata, function (meta) {
+                    var cache = _.find($scope.caches, { name: meta.cacheName });
 
                         if (cache) {
                             meta.name = (cache.sqlSchema ? cache.sqlSchema : '"' + meta.cacheName + '"') + '.' + meta.typeName;
@@ -1503,15 +1518,12 @@ consoleModule.controller('sqlController', [
                             meta.children.unshift({type: 'plain', name: 'mode: ' + cache.mode});
                         }
 
-                        return cache;
-                    }), 'name');
-                })
-                .error(function (errMsg) {
-                    $common.showError(errMsg);
-                })
-                .finally(function() {
-                    $loading.finish('loadingCacheMetadata');
-                });
+                    return cache;
+                }), 'name');
+            })
+            .finally(function () {
+                $loading.finish('loadingCacheMetadata');
+            });
         };
 
         $scope.showResultQuery = function (paragraph) {

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-web/src/main/js/gulpfile.js/tasks/bundle.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/gulpfile.js/tasks/bundle.js b/modules/control-center-web/src/main/js/gulpfile.js/tasks/bundle.js
index 8d0d2e9..ce15b34 100644
--- a/modules/control-center-web/src/main/js/gulpfile.js/tasks/bundle.js
+++ b/modules/control-center-web/src/main/js/gulpfile.js/tasks/bundle.js
@@ -39,19 +39,15 @@ var options = {
 	minify: true
 };
 
-gulp.task('bundle', function(cb) {
-	return sequence('bundle:ignite', 'bundle:legacy', cb);
-});
+gulp.task('bundle', ['eslint', 'bundle:ignite', 'bundle:legacy']);
 
 // Package all external dependencies and ignite-console.
-gulp.task('bundle:ignite', ['eslint'], function() {
-	if (util.env.debug) {
+gulp.task('bundle:ignite', function() {
+	if (util.env.debug)
 		delete options.minify;
-	}
 
-	if (util.env.debug || util.env.sourcemaps) {
+	if (util.env.debug || util.env.sourcemaps)
 		options.sourceMaps = true;
-	}
 
 	return jspm.bundleSFX('app/index', 'build/app.min.js', options);
 });

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-web/src/main/js/gulpfile.js/tasks/inject-plugins.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/gulpfile.js/tasks/inject-plugins.js b/modules/control-center-web/src/main/js/gulpfile.js/tasks/inject-plugins.js
index ce5b5cd..bdac24c 100644
--- a/modules/control-center-web/src/main/js/gulpfile.js/tasks/inject-plugins.js
+++ b/modules/control-center-web/src/main/js/gulpfile.js/tasks/inject-plugins.js
@@ -23,8 +23,7 @@ var common_options = {
 };
 
 var html_targets = [
-    './build/*.html',
-    './build/**/*.html'
+    './build/index.html'
 ];
 
 var js_targets = [

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-web/src/main/js/package.json
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/package.json b/modules/control-center-web/src/main/js/package.json
index 9766d61..02cdcd9 100644
--- a/modules/control-center-web/src/main/js/package.json
+++ b/modules/control-center-web/src/main/js/package.json
@@ -57,9 +57,10 @@
     "passport": "^0.3.2",
     "passport-local": "^1.0.0",
     "passport-local-mongoose": "3.1.0",
+    "passport.socketio": "^3.6.1",
     "require-dir": "^0.3.0",
     "serve-favicon": "~2.3.0",
-    "ws": "~0.8.0"
+    "socket.io": "^1.4.5"
   },
   "devDependencies": {
     "jspm": "^0.16.15",
@@ -77,10 +78,11 @@
       "angular-loading": "github:darthwade/angular-loading@^0.1.4",
       "angular-motion": "github:mgcrea/angular-motion@^0.4.3",
       "angular-nvd3": "github:krispo/angular-nvd3@^1.0.4",
-      "angular-retina": "github:jrief/angular-retina@^0.3.8",
+      "angular-retina": "github:jrief/angular-retina@0.3.8",
       "angular-sanitize": "github:angular/bower-angular-sanitize@^1.5.0",
       "angular-smart-table": "github:lorenzofox3/Smart-Table@^2.1.4",
-      "angular-strap": "github:akuznetsov-gridgain/angular-strap@fix-1852-2.3.6",
+      "angular-socket-io": "github:btford/angular-socket-io@^0.7.0",
+      "angular-strap": "github:mgcrea/angular-strap@2.3.7",
       "angular-tree-control": "github:wix/angular-tree-control@^0.2.23",
       "angular-ui-grid": "github:angular-ui/bower-ui-grid@^3.0.7",
       "angular-ui-router": "github:angular-ui/ui-router@^0.2.15",
@@ -97,6 +99,7 @@
       "lodash": "npm:lodash@^3.10.1",
       "pdfmake": "github:bpampuch/pdfmake@^0.1.20",
       "query-command-supported": "github:zenorocha/document.queryCommandSupported@^1.0.0",
+      "socket.io-client": "github:socketio/socket.io-client@1.4.5",
       "text": "github:systemjs/plugin-text@^0.0.7"
     },
     "devDependencies": {
@@ -119,6 +122,20 @@
           }
         }
       },
+      "github:btford/angular-socket-io@0.7.0": {
+        "dependencies": {
+          "socket.io-client": "github:socketio/socket.io-client@1.4.5"
+        }
+      },
+      "github:socketio/socket.io-client@1.4.5": {
+        "format": "global",
+        "main": "socket.io",
+        "shim": {
+          "socket.io": {
+            "exports": "io"
+          }
+        }
+      },
       "github:Stuk/jszip@2.5.0": {
         "format": "global",
         "main": "./dist/jszip"
@@ -141,14 +158,12 @@
       "github:krispo/angular-nvd3@1.0.4": {
         "main": "dist/angular-nvd3",
         "dependencies": {
-          "angular": "^1",
           "nvd3": "npm:nvd3@1.8.1",
           "d3": "npm:d3@3.5.14"
         },
         "shim": {
           "dist/angular-nvd3": {
             "deps": [
-              "angular",
               "d3",
               "nvd3"
             ]
@@ -218,18 +233,22 @@
           "pdfmake": "github:bpampuch/pdfmake@^0.1.20"
         }
       },
-      "github:akuznetsov-gridgain/angular-strap@fix-1852-2.3.6": {
-        "main": "dist/angular-strap.tpl",
+      "github:mgcrea/angular-strap@2.3.7": {
+        "directories": {
+          "lib": "dist"
+        },
+        "main": "angular-strap.tpl",
         "dependencies": {
-          "angular": "github:angular/bower-angular@^1.5.0",
+          "angular": "^1",
           "angular-animate": "github:angular/bower-angular-animate@^1.5.0",
           "angular-motion": "github:mgcrea/angular-motion@^0.4.3",
           "angular-sanitize": "github:angular/bower-angular-sanitize@^1.5.0"
         },
         "shim": {
-          "dist/angular-strap.tpl": {
+          "angular-strap.tpl": {
             "deps": [
-              "./angular-strap"
+              "./angular-strap",
+              "angular"
             ]
           }
         }

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-web/src/main/js/serve.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/serve.js b/modules/control-center-web/src/main/js/serve.js
index 59ea9e8..74342a9 100644
--- a/modules/control-center-web/src/main/js/serve.js
+++ b/modules/control-center-web/src/main/js/serve.js
@@ -66,29 +66,23 @@ const fireUp = require('fire-up').newInjector({
     ]
 });
 
-Promise.all([fireUp('settings'), fireUp('app'), fireUp('agent')])
+Promise.all([fireUp('settings'), fireUp('app'), fireUp('agent'), fireUp('io')])
     .then((values) => {
-        const settings = values[0], app = values[1], agent = values[2];
+        const settings = values[0];
+        const app = values[1];
+        const agent = values[2];
+        const io = values[3];
 
-        // Create HTTP server.
-        const server = http.createServer(app);
-
-        app.set('port', settings.server.port);
+        // Start rest server.
+        const server = settings.server.SSLOptions
+            ? https.createServer(settings.server.SSLOptions) : http.createServer();
 
         server.listen(settings.server.port);
         server.on('error', _onError.bind(null, settings.server.port));
         server.on('listening', _onListening.bind(null, server.address()));
 
-        // Create HTTPS server if needed.
-        if (settings.server.SSLOptions) {
-            const httpsServer = https.createServer(settings.server.SSLOptions, app);
-
-            const httpsPort = settings.server.SSLOptions.port;
-
-            httpsServer.listen(httpsPort);
-            httpsServer.on('error', _onError.bind(null, httpsPort));
-            httpsServer.on('listening', _onListening.bind(null, httpsServer.address()));
-        }
+        app.listen(server);
+        io.listen(server);
 
         // Start agent server.
         const agentServer = settings.agent.SSLOptions
@@ -104,7 +98,7 @@ Promise.all([fireUp('settings'), fireUp('app'), fireUp('agent')])
         if (process.send)
             process.send('running');
     }).catch((err) => {
-        console.error(err);
+        console.error(error);
 
         process.exit(1);
     });

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-web/src/main/js/serve/agent.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/serve/agent.js b/modules/control-center-web/src/main/js/serve/agent.js
index 6c54ae1..04d7ed6 100644
--- a/modules/control-center-web/src/main/js/serve/agent.js
+++ b/modules/control-center-web/src/main/js/serve/agent.js
@@ -21,10 +21,21 @@
 
 module.exports = {
     implements: 'agent',
-    inject: ['require(fs)', 'require(ws)', 'require(apache-ignite)', 'mongo']
+    inject: ['require(lodash)', 'require(fs)', 'require(path)', 'require(jszip)', 'require(socket.io)', 'require(apache-ignite)', 'settings', 'mongo']
 };
 
-module.exports.factory = function(fs, ws, apacheIgnite, mongo) {
+/**
+ * @param _
+ * @param fs
+ * @param path
+ * @param JSZip
+ * @param socketio
+ * @param apacheIgnite
+ * @param settings
+ * @param mongo
+ * @returns {AgentManager}
+ */
+module.exports.factory = function(_, fs, path, JSZip, socketio, apacheIgnite, settings, mongo) {
     /**
      * Creates an instance of server for Ignite.
      */
@@ -72,37 +83,39 @@ module.exports.factory = function(fs, ws, apacheIgnite, mongo) {
 
     class Agent {
         /**
+         * @param {socketIo.Socket} _wsSrv
          * @param {AgentManager} manager
-         * @param {WebSocket} _wsSrv
          */
         constructor(_wsSrv, manager) {
             const self = this;
 
             this._manager = manager;
 
-            this._wsSrv = _wsSrv;
+            /**
+             * @type {socketIo.Socket}
+             * @private
+             */
+            this._wsAgents = _wsSrv;
 
-            this._wsSrv.on('close', () => {
+            this._wsAgents.on('disconnect', () => {
                 if (self._user)
-                    self._manager._removeClient(self._user._id, self);
-            });
-
-            this._wsSrv.on('message', (msgStr) => {
-                const msg = JSON.parse(msgStr);
-
-                self['_rmt' + msg.type](msg);
+                    self._manager._removeAgent(self._user._id, self);
             });
 
-            this._reqCounter = 0;
-
-            this._cbMap = {};
+            this._wsAgents.on('agent:auth', (msg, cb) => self._processAuth(msg, cb));
         }
 
-        _runCommand(method, args) {
+        /**
+         *
+         * @param event
+         * @param data
+         * @returns {Promise}
+         */
+        _exec(event, data) {
             const self = this;
 
             return new Promise((resolve, reject) =>
-                self._invokeRmtMethod(method, args, (error, res) => {
+                self._emit(event, data, (error, res) => {
                     if (error)
                         return reject(error);
 
@@ -133,10 +146,7 @@ module.exports.factory = function(fs, ws, apacheIgnite, mongo) {
             if (headers && typeof (headers) !== 'object')
                 throw 'headers must be an object';
 
-            if (!method)
-                method = 'GET';
-            else
-                method = method.toUpperCase();
+            method = method ? method.toUpperCase() : 'GET';
 
             if (method !== 'GET' && method !== 'POST')
                 throw 'Unknown HTTP method: ' + method;
@@ -145,38 +155,32 @@ module.exports.factory = function(fs, ws, apacheIgnite, mongo) {
                 if (error)
                     return cb(error);
 
-                const restError = restResult.error;
-
-                if (restError)
-                    return cb(restError);
+                error = restResult.error;
 
-                const restCode = restResult.restCode;
+                const code = restResult.code;
 
-                if (restCode !== 200) {
-                    if (restCode === 401)
-                        return cb.call({code: restCode, message: 'Failed to authenticate on node.'});
+                if (code !== 200) {
+                    if (code === 401)
+                        return cb({code, message: 'Failed to authenticate on node.'});
 
-                    return cb.call({
-                        code: restCode,
-                        message: restError || 'Failed connect to node and execute REST command.'
-                    });
+                    return cb({code, message: error || 'Failed connect to node and execute REST command.'});
                 }
 
                 try {
-                    const nodeResponse = JSON.parse(restResult.response);
+                    const response = JSON.parse(restResult.data);
 
-                    if (nodeResponse.successStatus === 0)
-                        return cb(null, nodeResponse.response);
+                    if (response.successStatus === 0)
+                        return cb(null, response.response);
 
-                    switch (nodeResponse.successStatus) {
+                    switch (response.successStatus) {
                         case 1:
-                            return cb({code: 500, message: nodeResponse.error});
+                            return cb({code: 500, message: response.error});
                         case 2:
-                            return cb({code: 401, message: nodeResponse.error});
+                            return cb({code: 401, message: response.error});
                         case 3:
-                            return cb({code: 403, message: nodeResponse.error});
+                            return cb({code: 403, message: response.error});
                         default:
-                            return cb(nodeResponse.error);
+                            return cb(response.error);
                     }
                 }
                 catch (e) {
@@ -184,14 +188,7 @@ module.exports.factory = function(fs, ws, apacheIgnite, mongo) {
                 }
             };
 
-            this._invokeRmtMethod('executeRest', [uri, params, demo, method, headers, body], _cb);
-        }
-
-        /**
-         * @param {?String} error
-         */
-        authResult(error) {
-            return this._runCommand('authResult', [error]);
+            this._emit('node:rest', {uri, params, demo, method, headers, body}, _cb);
         }
 
         /**
@@ -202,7 +199,7 @@ module.exports.factory = function(fs, ws, apacheIgnite, mongo) {
          * @returns {Promise} Promise on list of tables (see org.apache.ignite.schema.parser.DbTable java class)
          */
         metadataSchemas(driverPath, driverClass, url, info) {
-            return this._runCommand('schemas', [driverPath, driverClass, url, info]);
+            return this._exec('schemaImport:schemas', {driverPath, driverClass, url, info});
         }
 
         /**
@@ -215,89 +212,74 @@ module.exports.factory = function(fs, ws, apacheIgnite, mongo) {
          * @returns {Promise} Promise on list of tables (see org.apache.ignite.schema.parser.DbTable java class)
          */
         metadataTables(driverPath, driverClass, url, info, schemas, tablesOnly) {
-            return this._runCommand('metadata', [driverPath, driverClass, url, info, schemas, tablesOnly]);
+            return this._exec('schemaImport:metadata', {driverPath, driverClass, url, info, schemas, tablesOnly});
         }
 
         /**
          * @returns {Promise} Promise on list of jars from driver folder.
          */
         availableDrivers() {
-            return this._runCommand('availableDrivers', []);
+            return this._exec('schemaImport:drivers');
         }
 
         /**
          * Run http request
          *
-         * @this {AgentServer}
-         * @param {String} method Command name.
-         * @param {Array} args Command params.
-         * @param {Function} callback on finish
+         * @this {Agent}
+         * @param {String} event Command name.
+         * @param {Object} data Command params.
+         * @param {Function} [callback] on finish
          */
-        _invokeRmtMethod(method, args, callback) {
-            if (this._wsSrv.readyState !== 1) {
+        _emit(event, data, callback) {
+            if (!this._wsAgents.connected) {
                 if (callback)
                     callback('org.apache.ignite.agent.AgentException: Connection is closed');
 
                 return;
             }
 
-            const msg = {method, args};
-
-            if (callback) {
-                const reqId = this._reqCounter++;
-
-                this._cbMap[reqId] = callback;
-
-                msg.reqId = reqId;
-            }
-
-            this._wsSrv.send(JSON.stringify(msg));
+            this._wsAgents.emit(event, data, callback);
         }
 
-        _rmtAuthMessage(msg) {
+        /**
+         * Process auth request.
+         *
+         * @param {Object} data
+         * @param {Function} cb
+         * @private
+         */
+        _processAuth(data, cb) {
             const self = this;
 
-            fs.stat('public/agent/ignite-web-agent-1.5.0.final.zip', (errFs, stats) => {
-                let relDate = 0;
-
-                if (!errFs)
-                    relDate = stats.birthtime.getTime();
+            if (!_.isEmpty(this._manager.supportedAgents)) {
+                const ver = data.ver;
+                const bt = data.bt;
 
-                if ((msg.relDate || 0) < relDate)
-                    self.authResult('You are using an older version of the agent. Please reload agent archive');
+                if (_.isEmpty(ver) || _.isEmpty(bt) || _.isEmpty(this._manager.supportedAgents[ver]) ||
+                    this._manager.supportedAgents[ver].buildTime > bt)
+                    return cb('You are using an older version of the agent. Please reload agent archive');
+            }
 
-                mongo.Account.findOne({token: msg.token}, (err, account) => {
-                    // TODO IGNITE-1379 send error to web master.
-                    if (err)
-                        self.authResult('Failed to authorize user');
-                    else if (!account)
-                        self.authResult('Invalid token, user not found');
-                    else {
-                        self.authResult(null);
+            mongo.Account.findOne({token: data.token}, (err, account) => {
+                // TODO IGNITE-1379 send error to web master.
+                if (err)
+                    cb('Failed to authorize user');
+                else if (!account)
+                    cb('Invalid token, user not found');
+                else {
+                    self._user = account;
 
-                        self._user = account;
+                    self._manager._addAgent(account._id, self);
 
-                        self._manager._addAgent(account._id, self);
+                    self._cluster = new apacheIgnite.Ignite(new AgentServer(self));
 
-                        self._cluster = new apacheIgnite.Ignite(new AgentServer(self));
+                    self._demo = new apacheIgnite.Ignite(new AgentServer(self, true));
 
-                        self._demo = new apacheIgnite.Ignite(new AgentServer(self, true));
-                    }
-                });
+                    cb();
+                }
             });
         }
 
-        _rmtCallRes(msg) {
-            const callback = this._cbMap[msg.reqId];
-
-            if (!callback)
-                return;
-
-            delete this._cbMap[msg.reqId];
-
-            callback(msg.error, msg.response);
-        }
-
         /**
          * @returns {Ignite}
          */
@@ -311,11 +293,79 @@ module.exports.factory = function(fs, ws, apacheIgnite, mongo) {
          * @constructor
          */
         constructor() {
+            /**
+             * @type {Object.<ObjectId, Array.<Agent>>}
+             */
             this._agents = {};
+
+            /**
+             * @type {Object.<ObjectId, Array.<Socket>>}
+             */
+            this._users = {};
+
+            const agentArchives = fs.readdirSync(settings.agent.dists)
+                .filter((file) => path.extname(file) === '.zip');
+
+            /**
+             * @type {Object.<String, String>}
+             */
+            this.supportedAgents = {};
+
+            const jarFilter = (file) => path.extname(file) === '.jar';
+
+            for (const archive of agentArchives) {
+                const filePath = path.join(settings.agent.dists, archive);
+
+                const zip = new JSZip(fs.readFileSync(filePath));
+
+                const jarPath = _.find(_.keys(zip.files), jarFilter);
+
+                const jar = new JSZip(zip.files[jarPath].asNodeBuffer());
+
+                const manifest = jar.files['META-INF/MANIFEST.MF']
+                    .asText()
+                    .trim()
+                    .split(/\s*\n+\s*/)
+                    .map((line, r) => {
+                        r = line.split(/\s*:\s*/);
+
+                        this[r[0]] = r[1];
+
+                        return this;
+                    }, {})[0];
+
+                const ver = manifest['Implementation-Version'];
+
+                if (ver) {
+                    this.supportedAgents[ver] = {
+                        fileName: archive,
+                        filePath,
+                        buildTime: manifest['Build-Time']
+                    };
+                }
+            }
+
+            const latest = _.first(Object.keys(this.supportedAgents).sort((a, b) => {
+                const aParts = a.split('.');
+                const bParts = b.split('.');
+
+                for (let i = 0; i < aParts.length; ++i) {
+                    if (bParts.length === i)
+                        return 1;
+
+                    if (aParts[i] === aParts[i])
+                        continue;
+
+                    return aParts[i] > bParts[i] ? 1 : -1;
+                }
+            }));
+
+            if (latest)
+                this.supportedAgents.latest = this.supportedAgents[latest];
         }
 
         /**
-         *
+         * @param {http.Server|https.Server} srv
          */
         listen(srv) {
             if (this._server)
@@ -323,58 +373,106 @@ module.exports.factory = function(fs, ws, apacheIgnite, mongo) {
 
             this._server = srv;
 
-            this._wsSrv = new ws.Server({server: this._server});
+            /**
+             * @type {WebSocketServer}
+             */
+            this._wsAgents = socketio(this._server);
 
             const self = this;
 
-            this._wsSrv.on('connection', (_wsSrv) => new Agent(_wsSrv, self));
+            this._wsAgents.on('connection', (_wsSrv) => new Agent(_wsSrv, self));
         }
 
         /**
-         * @param userId
-         * @param {Agent} client
+         * @param {ObjectId} userId
+         * @param {Socket} user
+         * @returns {int} connected agent count.
          */
-        _removeClient(userId, client) {
+        addAgentListener(userId, user) {
+            let users = this._users[userId];
+
+            if (!users)
+                this._users[userId] = users = [];
+
+            users.push(user);
+
             const agents = this._agents[userId];
 
-            if (agents) {
-                let idx;
+            return agents ? agents.length : 0;
+        }
 
-                while ((idx = agents.indexOf(client)) !== -1)
-                    agents.splice(idx, 1);
+        /**
+         * @param {ObjectId} userId
+         * @param {Socket} user
+         * @returns {int} connected agent count.
+         */
+        removeAgentListener(userId, user) {
+            const users = this._users[userId];
 
-                if (agents.length === 0)
-                    delete this._agents[userId];
-            }
+            _.remove(users, (_user) => _user === user);
         }
 
         /**
          * @param {ObjectId} userId
-         * @param {Agent} agent
+         * @returns {Promise.<Agent>}
          */
-        _addAgent(userId, agent) {
-            let agents = this._agents[userId];
+        findAgent(userId) {
+            if (!this._server)
+                return Promise.reject('Agent server not started yet!');
 
-            if (!agents) {
-                agents = [];
+            const agents = this._agents[userId];
 
-                this._agents[userId] = agents;
-            }
+            if (!agents || agents.length === 0)
+                return Promise.reject('Failed to connect to agent');
 
-            agents.push(agent);
+            return Promise.resolve(agents[0]);
         }
 
         /**
+         * Close connections for all user agents.
          * @param {ObjectId} userId
-         * @returns {Agent}
          */
-        findClient(userId) {
+        close(userId) {
+            if (!this._server)
+                throw 'Agent server not started yet!';
+
+            const agents = this._agents[userId];
+
+            this._agents[userId] = [];
+
+            for (const agent of agents)
+                agent._emit('agent:close', 'Security token was changed for user');
+        }
+
+        /**
+         * @param userId
+         * @param {Agent} agent
+         */
+        _removeAgent(userId, agent) {
             const agents = this._agents[userId];
 
-            if (!agents || agents.length === 0)
-                return null;
+            _.remove(agents, (_agent) => _agent === agent);
+
+            const users = this._users[userId];
+
+            _.forEach(users, (user) => user.emit('agent:count', {count: agents.length}));
+        }
+
+        /**
+         * @param {ObjectId} userId
+         * @param {Agent} agent
+         */
+        _addAgent(userId, agent) {
+            let agents = this._agents[userId];
+
+            if (!agents)
+                this._agents[userId] = agents = [];
+
+            agents.push(agent);
+
+            const users = this._users[userId];
 
-            return agents[0];
+            _.forEach(users, (user) => user.emit('agent:count', {count: agents.length}));
         }
     }
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-web/src/main/js/serve/agent_dists/README.txt
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/serve/agent_dists/README.txt b/modules/control-center-web/src/main/js/serve/agent_dists/README.txt
new file mode 100644
index 0000000..d51bdf9
--- /dev/null
+++ b/modules/control-center-web/src/main/js/serve/agent_dists/README.txt
@@ -0,0 +1,7 @@
+Ignite Web Console
+======================================
+
+This is default folder for agent distributives.
+
+Also, you could specify custom folder in `serve/config/settings.json`
+

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-web/src/main/js/serve/app.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/serve/app.js b/modules/control-center-web/src/main/js/serve/app.js
index 5f4e4fb..e4b5eca 100644
--- a/modules/control-center-web/src/main/js/serve/app.js
+++ b/modules/control-center-web/src/main/js/serve/app.js
@@ -21,15 +21,22 @@
 
 module.exports = {
     implements: 'app',
-    inject: ['require(express)', 'configure', 'routes']
+    inject: ['require(express)', 'configure', 'routes', 'io']
 };
 
 module.exports.factory = function(Express, configure, routes) {
-    const app = new Express();
+    return {
+        /**
+         * @param {Server} srv
+         */
+        listen: (srv) => {
+            const app = new Express();
 
-    configure(app);
+            configure.express(app);
 
-    routes.register(app);
+            routes.register(app);
 
-    return app;
+            srv.addListener('request', app);
+        }
+    };
 };

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-web/src/main/js/serve/config/default.json
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/serve/config/default.json b/modules/control-center-web/src/main/js/serve/config/default.json
deleted file mode 100644
index 3dcc5ca..0000000
--- a/modules/control-center-web/src/main/js/serve/config/default.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
-    "server": {
-        "port": 3000,
-        "https-port": 8443,
-        "ssl": false,
-        "key": "serve/keys/test.key",
-        "cert": "serve/keys/test.crt",
-        "keyPassphrase": "password"
-    },
-    "mongoDB": {
-        "url": "mongodb://localhost/web-control-center"
-    },
-    "agent-server": {
-        "port": 3001,
-        "ssl": true,
-        "key": "serve/keys/test.key",
-        "cert": "serve/keys/test.crt",
-        "keyPassphrase": "password"
-    },
-    "smtp": {
-        "service": "",
-        "username": "Apache Ignite Web Console",
-        "email": "",
-        "password": ""
-    }
-}

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-web/src/main/js/serve/config/settings.json.sample
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/serve/config/settings.json.sample b/modules/control-center-web/src/main/js/serve/config/settings.json.sample
new file mode 100644
index 0000000..94dd9f7
--- /dev/null
+++ b/modules/control-center-web/src/main/js/serve/config/settings.json.sample
@@ -0,0 +1,26 @@
+{
+    "server": {
+        "port": 3000,
+        "ssl": false,
+        "key": "serve/keys/test.key",
+        "cert": "serve/keys/test.crt",
+        "keyPassphrase": "password"
+    },
+    "mongoDB": {
+        "url": "mongodb://localhost/console"
+    },
+    "agent-server": {
+        "port": 3001,
+        "ssl": false,
+        "key": "serve/keys/test.key",
+        "cert": "serve/keys/test.crt",
+        "keyPassphrase": "password"
+    },
+    "smtp": {
+        "service": "",
+        "username": "Apache Ignite Web Console",
+        "sign": "Kind regards,<br>Apache Ignite Team",
+        "email": "",
+        "password": ""
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-web/src/main/js/serve/configure.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/serve/configure.js b/modules/control-center-web/src/main/js/serve/configure.js
index 9d8b1d3..ac27751 100644
--- a/modules/control-center-web/src/main/js/serve/configure.js
+++ b/modules/control-center-web/src/main/js/serve/configure.js
@@ -21,46 +21,60 @@
 
 module.exports = {
     implements: 'configure',
-    inject: ['require(morgan)', 'require(cookie-parser)', 'require(body-parser)', 'require(express-force-ssl)',
-        'require(express-session)', 'require(connect-mongo)', 'require(passport)', 'settings', 'mongo']
+    inject: ['require(morgan)', 'require(cookie-parser)', 'require(body-parser)',
+        'require(express-session)', 'require(connect-mongo)', 'require(passport)', 'require(passport.socketio)', 'settings', 'mongo']
 };
 
-module.exports.factory = function(logger, cookieParser, bodyParser, forceSSL, session, connectMongo, Passport, settings, mongo) {
-    return (app) => {
-        app.use(logger('dev', {
-            skip: (req, res) => res.statusCode < 400
-        }));
+module.exports.factory = function(logger, cookieParser, bodyParser, session, connectMongo, passport, passportSocketIo, settings, mongo) {
+    const _sessionStore = new (connectMongo(session))({mongooseConnection: mongo.connection});
 
-        app.use(cookieParser(settings.sessionSecret));
+    return {
+        express: (app) => {
+            app.use(logger('dev', {
+                skip: (req, res) => res.statusCode < 400
+            }));
 
-        app.use(bodyParser.json({limit: '50mb'}));
-        app.use(bodyParser.urlencoded({limit: '50mb', extended: true}));
+            app.use(cookieParser(settings.sessionSecret));
 
-        const MongoStore = connectMongo(session);
+            app.use(bodyParser.json({limit: '50mb'}));
+            app.use(bodyParser.urlencoded({limit: '50mb', extended: true}));
 
-        app.use(session({
-            secret: settings.sessionSecret,
-            resave: false,
-            saveUninitialized: true,
-            cookie: {
-                expires: new Date(Date.now() + settings.cookieTTL),
-                maxAge: settings.cookieTTL
-            },
-            store: new MongoStore({mongooseConnection: mongo.connection})
-        }));
+            app.use(session({
+                secret: settings.sessionSecret,
+                resave: false,
+                saveUninitialized: true,
+                cookie: {
+                    expires: new Date(Date.now() + settings.cookieTTL),
+                    maxAge: settings.cookieTTL
+                },
+                store: _sessionStore
+            }));
 
-        app.use(Passport.initialize());
-        app.use(Passport.session());
+            app.use(passport.initialize());
+            app.use(passport.session());
 
-        Passport.serializeUser(mongo.Account.serializeUser());
-        Passport.deserializeUser(mongo.Account.deserializeUser());
+            passport.serializeUser(mongo.Account.serializeUser());
+            passport.deserializeUser(mongo.Account.deserializeUser());
 
-        Passport.use(mongo.Account.createStrategy());
+            passport.use(mongo.Account.createStrategy());
+        },
+        socketio: (io) => {
+            const _onAuthorizeSuccess = (data, accept) => {
+                accept(null, true);
+            };
 
-        if (settings.SSLOptions) {
-            app.set('forceSSLOptions', settings.SSLOptions);
+            const _onAuthorizeFail = (data, message, error, accept) => {
+                accept(null, false);
+            };
 
-            app.use(forceSSL);
+            io.use(passportSocketIo.authorize({
+                cookieParser,
+                key: 'connect.sid', // the name of the cookie where express/connect stores its session_id
+                secret: settings.sessionSecret, // the session_secret to parse the cookie
+                store: _sessionStore, // we NEED to use a sessionstore. no memorystore please
+                success: _onAuthorizeSuccess, // *optional* callback on success - read more below
+                fail: _onAuthorizeFail // *optional* callback on fail/error - read more below
+            }));
         }
     };
 };

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-web/src/main/js/serve/io.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/serve/io.js b/modules/control-center-web/src/main/js/serve/io.js
new file mode 100644
index 0000000..ded00fa
--- /dev/null
+++ b/modules/control-center-web/src/main/js/serve/io.js
@@ -0,0 +1,242 @@
+/*
+ * 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: 'io',
+    inject: ['require(lodash)', 'require(socket.io)', 'require(apache-ignite)', 'agent', 'configure']
+};
+
+module.exports.factory = (_, socketio, apacheIgnite, agentMgr, configure) => {
+    const SqlFieldsQuery = apacheIgnite.SqlFieldsQuery;
+    const ScanQuery = apacheIgnite.ScanQuery;
+
+    return {
+        listen: (server) => {
+            const io = socketio(server);
+
+            configure.socketio(io);
+
+            io.sockets.on('connection', (socket) => {
+                const user = socket.client.request.user;
+
+                socket.on('schemaImport:drivers', (cb) => {
+                    agentMgr.findAgent(user._id)
+                        .then((agent) => agent.availableDrivers())
+                        .then((drivers) => cb(null, drivers))
+                        .catch((errMsg) => cb(errMsg));
+                });
+
+                socket.on('schemaImport:schemas', (preset, cb) => {
+                    agentMgr.findAgent(user._id)
+                        .then((agent) => {
+                            const jdbcInfo = {user: preset.user, password: preset.password};
+
+                            return agent.metadataSchemas(preset.jdbcDriverJar, preset.jdbcDriverClass, preset.jdbcUrl, jdbcInfo);
+                        })
+                        .then((schemas) => cb(null, schemas))
+                        .catch((errMsg) => cb(errMsg));
+                });
+
+                socket.on('schemaImport:tables', (preset, cb) => {
+                    agentMgr.findAgent(user._id)
+                        .then((agent) => {
+                            const jdbcInfo = {user: preset.user, password: preset.password};
+
+                            return agent.metadataTables(preset.jdbcDriverJar, preset.jdbcDriverClass, preset.jdbcUrl, jdbcInfo,
+                                preset.schemas, preset.tablesOnly);
+                        })
+                        .then((tables) => cb(null, tables))
+                        .catch((errMsg) => cb(errMsg));
+                });
+
+                socket.on('node:topology', (demo, attr, mtr, cb) => {
+                    agentMgr.findAgent(user._id)
+                        .then((agent) => agent.ignite(demo).cluster(attr, mtr))
+                        .then((clusters) => cb(null, clusters))
+                        .catch((errMsg) => cb(errMsg));
+                });
+
+                socket.on('node:query:close', (args, cb) => {
+                    agentMgr.findAgent(user._id)
+                        .then((agent) => {
+                            const cache = agent.ignite(args.demo).cache(args.cacheName);
+
+                            return cache.__createPromise(cache._createCommand('qrycls').addParam('qryId', args.queryId));
+                        })
+                        .then(() => cb())
+                        .catch((errMsg) => cb(errMsg));
+                });
+
+                socket.on('node:query', (args, cb) => {
+                    agentMgr.findAgent(user._id)
+                        .then((agent) => {
+                            // Create sql query.
+                            const qry = args.type === 'SCAN' ? new ScanQuery() : new SqlFieldsQuery(args.query);
+
+                            // Set page size for query.
+                            qry.setPageSize(args.pageSize);
+
+                            return agent.ignite(args.demo).cache(args.cacheName).query(qry).nextPage();
+                        })
+                        .then((cursor) => cb(null, {
+                            meta: cursor.fieldsMetadata(),
+                            rows: cursor.page(),
+                            queryId: cursor.queryId()
+                        }))
+                        .catch((errMsg) => cb(errMsg));
+                });
+
+                socket.on('node:query:fetch', (args, cb) => {
+                    agentMgr.findAgent(user._id)
+                        .then((agent) => {
+                            const cache = agent.ignite(args.demo).cache(args.cacheName);
+
+                            const cmd = cache._createCommand('qryfetch')
+                                .addParam('qryId', args.queryId)
+                                .addParam('pageSize', args.pageSize);
+
+                            return cache.__createPromise(cmd);
+                        })
+                        .then((page) => cb(null, {
+                            rows: page.items,
+                            last: page === null || page.last
+                        }))
+                        .catch((errMsg) => cb(errMsg));
+                });
+
+                socket.on('node:query:getAll', (args, cb) => {
+                    agentMgr.findAgent(user._id)
+                        .then((agent) => {
+                            // Create sql query.
+                            const qry = args.type === 'SCAN' ? new ScanQuery() : new SqlFieldsQuery(args.query);
+
+                            // Set page size for query.
+                            qry.setPageSize(args.pageSize);
+
+                            return agent.ignite(args.demo).cache(args.cacheName).query(qry).getAll();
+                        })
+                        .then((cursor) => cb(null, {
+                            meta: cursor.fieldsMetadata(),
+                            rows: cursor.page()
+                        }))
+                        .catch((errMsg) => cb(errMsg));
+                });
+
+                socket.on('node:cache:metadata', (args, cb) => {
+                    agentMgr.findAgent(user._id)
+                        .then((agent) => agent.ignite(args.demo).cache(args.cacheName).metadata())
+                        .then((caches) => {
+                            let types = [];
+
+                            const _compact = (className) => {
+                                return className.replace('java.lang.', '').replace('java.util.', '').replace('java.sql.', '');
+                            };
+
+                            const _typeMapper = (meta, typeName) => {
+                                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
+                                        });
+                                    }
+                                }
+
+                                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
+                                        });
+                                    }
+
+                                    if (fields.length > 0) {
+                                        indexes.push({
+                                            type: 'index',
+                                            name: index.name,
+                                            children: fields,
+                                            cacheName: meta.cacheName,
+                                            typeName
+                                        });
+                                    }
+                                }
+
+                                columns = _.sortBy(columns, 'name');
+
+                                if (!_.isEmpty(indexes)) {
+                                    columns = columns.concat({
+                                        type: 'indexes',
+                                        name: 'Indexes',
+                                        cacheName: meta.cacheName,
+                                        typeName,
+                                        children: indexes
+                                    });
+                                }
+
+                                return {
+                                    type: 'type',
+                                    cacheName: meta.cacheName || '',
+                                    typeName,
+                                    children: columns
+                                };
+                            };
+
+                            for (const meta of caches) {
+                                const cacheTypes = meta.types.map(_typeMapper.bind(null, meta));
+
+                                if (!_.isEmpty(cacheTypes))
+                                    types = types.concat(cacheTypes);
+                            }
+
+                            return cb(null, types);
+                        })
+                        .catch((errMsg) => cb(errMsg));
+                });
+
+                const count = agentMgr.addAgentListener(user._id, socket);
+
+                socket.emit('agent:count', {count});
+            });
+
+            io.sockets.on('disconnect', (socket) =>
+                agentMgr.removeAgentListener(socket.client.request.user._id, socket)
+            );
+        }
+    };
+};

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-web/src/main/js/serve/mail.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/serve/mail.js b/modules/control-center-web/src/main/js/serve/mail.js
index 8ab3e7e..36b4fa7 100644
--- a/modules/control-center-web/src/main/js/serve/mail.js
+++ b/modules/control-center-web/src/main/js/serve/mail.js
@@ -28,11 +28,12 @@ module.exports.factory = function(nodemailer, settings) {
     return {
         /**
          * Send mail to user.
+         *
          * @param {Account} user
          * @param {String} subject
          * @param {String} html
          * @param {String} sendErr
-         *
+         * @throws {Error}
          * @return {Promise}
          */
         send: (user, subject, html, sendErr) => {
@@ -45,7 +46,7 @@ module.exports.factory = function(nodemailer, settings) {
             };
 
             if (transporter.service === '' || transporter.auth.user === '' || transporter.auth.pass === '')
-                return Promise.reject('Failed to send email. SMTP server is not configured . Please ask webmaster to setup SMTP server!');
+                throw new Error('Failed to send email. SMTP server is not configured. Please ask webmaster to setup SMTP server!');
 
             const mailer = nodemailer.createTransport(transporter);
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-web/src/main/js/serve/routes/agent.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/serve/routes/agent.js b/modules/control-center-web/src/main/js/serve/routes/agent.js
index 7bb05e7..4e123bf 100644
--- a/modules/control-center-web/src/main/js/serve/routes/agent.js
+++ b/modules/control-center-web/src/main/js/serve/routes/agent.js
@@ -21,7 +21,7 @@
 
 module.exports = {
     implements: 'agent-routes',
-    inject: ['require(lodash)', 'require(express)', 'require(apache-ignite)', 'require(fs)', 'require(jszip)', 'settings', 'agent']
+    inject: ['require(lodash)', 'require(express)', 'require(fs)', 'require(jszip)', 'require(apache-ignite)', 'settings', 'agent']
 };
 
 /**
@@ -34,302 +34,47 @@ module.exports = {
  * @param {AgentManager} agentMgr
  * @returns {Promise}
  */
-module.exports.factory = function(_, express, apacheIgnite, fs, JSZip, settings, agentMgr) {
+module.exports.factory = function(_, express, fs, JSZip, apacheIgnite, settings, agentMgr) {
     return new Promise((resolveFactory) => {
         const router = new express.Router();
 
-        const SqlFieldsQuery = apacheIgnite.SqlFieldsQuery;
-        const ScanQuery = apacheIgnite.ScanQuery;
-
-        const _client = (userId) => {
-            return new Promise((resolve, reject) => {
-                const agent = agentMgr.findClient(userId);
-
-                if (agent)
-                    return resolve(agent);
-
-                reject({code: 503, message: 'Connection to Ignite Web Agent is not established'});
-            });
-        };
-
-        const _compact = (className) => {
-            return className.replace('java.lang.', '').replace('java.util.', '').replace('java.sql.', '');
-        };
-
-        const _handleException = (res) => {
-            return (error) => {
-                if (_.isObject(error))
-                    return res.status(error.code).send(error.message);
-
-                return res.status(500).send(error);
-            };
-        };
-
         /* Get grid topology. */
         router.get('/download/zip', (req, res) => {
-            const agentFld = settings.agent.file;
-            const agentZip = agentFld + '.zip';
-            const agentPathZip = 'public/agent/' + agentFld + '.zip';
-
-            fs.stat(agentPathZip, (err, stats) => {
-                if (err)
-                    return res.download(agentPathZip, agentZip);
-
-                // Read a zip file.
-                fs.readFile(agentPathZip, (errFs, data) => {
-                    if (errFs)
-                        return res.download(agentPathZip, agentZip);
-
-                    const zip = new JSZip(data);
-
-                    const prop = [];
-
-                    const host = req.hostname.match(/:/g) ? req.hostname.slice(0, req.hostname.indexOf(':')) : req.hostname;
-
-                    prop.push('token=' + req.user.token);
-                    prop.push('server-uri=wss://' + host + ':' + settings.agent.port);
-                    prop.push('#Uncomment following options if needed:');
-                    prop.push('#node-uri=http://localhost:8080');
-                    prop.push('#driver-folder=./jdbc-drivers');
-                    prop.push('');
-                    prop.push('#Note: Do not change this auto generated line');
-                    prop.push('rel-date=' + stats.birthtime.getTime());
-
-                    zip.file(agentFld + '/default.properties', prop.join('\n'));
-
-                    const buffer = zip.generate({type: 'nodebuffer', platform: 'UNIX'});
-
-                    // Set the archive name.
-                    res.attachment(agentZip);
-
-                    res.send(buffer);
-                });
-            });
-        });
-
-        /* Get grid topology. */
-        router.post('/topology', (req, res) => {
-            _client(req.currentUserId())
-                .then((client) => client.ignite(req.body.demo).cluster(req.body.attr, req.body.mtr))
-                .then((clusters) => res.json(clusters))
-                .catch(_handleException(res));
-        });
-
-        /* Execute query. */
-        router.post('/query', (req, res) => {
-            _client(req.currentUserId())
-                .then((client) => {
-                    // Create sql query.
-                    const qry = new SqlFieldsQuery(req.body.query);
-
-                    // Set page size for query.
-                    qry.setPageSize(req.body.pageSize);
-
-                    return client.ignite(req.body.demo).cache(req.body.cacheName).query(qry).nextPage();
-                })
-                .then((cursor) => res.json({
-                    meta: cursor.fieldsMetadata(),
-                    rows: cursor.page(),
-                    queryId: cursor.queryId()
-                }))
-                .catch(_handleException(res));
-        });
-
-        /* Execute query getAll. */
-        router.post('/query/getAll', (req, res) => {
-            _client(req.currentUserId())
-                .then((client) => {
-                    // Create sql query.
-                    const qry = req.body.query ? new SqlFieldsQuery(req.body.query) : new ScanQuery();
-
-                    // Set page size for query.
-                    qry.setPageSize(1024);
-
-                    // Get query cursor.
-                    const cursor = client.ignite(req.body.demo).cache(req.body.cacheName).query(qry);
+            const latest = agentMgr.supportedAgents.latest;
 
-                    return cursor.getAll()
-                        .then((rows) => res.json({meta: cursor.fieldsMetadata(), rows}));
-                })
-                .catch(_handleException(res));
-        });
-
-        /* Execute query. */
-        router.post('/scan', (req, res) => {
-            _client(req.currentUserId())
-                .then((client) => {
-                    // Create sql query.
-                    const qry = new ScanQuery();
-
-                    // Set page size for query.
-                    qry.setPageSize(req.body.pageSize);
-
-                    // Get query cursor.
-                    return client.ignite(req.body.demo).cache(req.body.cacheName).query(qry).nextPage();
-                })
-                .then((cursor) => res.json({
-                    meta: cursor.fieldsMetadata(),
-                    rows: cursor.page(),
-                    queryId: cursor.queryId()
-                }))
-                .catch(_handleException(res));
-        });
-
-        /* Get next query page. */
-        router.post('/query/fetch', (req, res) => {
-            _client(req.currentUserId())
-                .then((client) => {
-                    const cache = client.ignite(req.body.demo).cache(req.body.cacheName);
-
-                    const cmd = cache._createCommand('qryfetch')
-                        .addParam('qryId', req.body.queryId)
-                        .addParam('pageSize', req.body.pageSize);
-
-                    return cache.__createPromise(cmd);
-                })
-                .then((page) => res.json({rows: page.items, last: page === null || page.last}))
-                .catch(_handleException(res));
-        });
-
-        /* Close query cursor by id. */
-        router.post('/query/close', (req, res) => {
-            _client(req.currentUserId())
-                .then((client) => {
-                    const cache = client.ignite(req.body.demo).cache(req.body.cacheName);
-
-                    return cache.__createPromise(cache._createCommand('qrycls').addParam('qryId', req.body.queryId));
-                })
-                .then(() => res.sendStatus(200))
-                .catch(_handleException(res));
-        });
-
-        /* Get metadata for cache. */
-        router.post('/cache/metadata', (req, res) => {
-            _client(req.currentUserId())
-                .then((client) => client.ignite(req.body.demo).cache(req.body.cacheName).metadata())
-                .then((caches) => {
-                    let types = [];
-
-                    const _typeMapper = (meta, typeName) => {
-                        let fields = meta.fields[typeName];
-
-                        let columns = [];
-
-                        for (const fieldName in fields) {
-                            if (fields.hasOwnProperty(fieldName)) {
-                                const fieldClass = _compact(fields[fieldName]);
+            if (_.isEmpty(latest))
+                return res.status(500).send('Missing agent zip on server. Please ask webmaster to upload agent zip!');
 
-                                columns.push({
-                                    type: 'field',
-                                    name: fieldName,
-                                    clazz: fieldClass,
-                                    system: fieldName === '_KEY' || fieldName === '_VAL',
-                                    cacheName: meta.cacheName,
-                                    typeName
-                                });
-                            }
-                        }
+            const agentFld = latest.fileName.substr(0, latest.fileName.length - 4);
+            const agentZip = latest.fileName;
+            const agentPathZip = latest.filePath;
 
-                        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
-                                });
-                            }
-
-                            if (fields.length > 0) {
-                                indexes.push({
-                                    type: 'index',
-                                    name: index.name,
-                                    children: fields,
-                                    cacheName: meta.cacheName,
-                                    typeName
-                                });
-                            }
-                        }
-
-                        columns = _.sortBy(columns, 'name');
-
-                        if (!_.isEmpty(indexes)) {
-                            columns = columns.concat({
-                                type: 'indexes',
-                                name: 'Indexes',
-                                cacheName: meta.cacheName,
-                                typeName,
-                                children: indexes
-                            });
-                        }
-
-                        return {
-                            type: 'type',
-                            cacheName: meta.cacheName || '',
-                            typeName,
-                            children: columns
-                        };
-                    };
-
-                    for (const meta of caches) {
-                        const cacheTypes = meta.types.map(_typeMapper.bind(null, meta));
-
-                        if (!_.isEmpty(cacheTypes))
-                            types = types.concat(cacheTypes);
-                    }
+            // Read a zip file.
+            fs.readFile(agentPathZip, (errFs, data) => {
+                if (errFs)
+                    return res.download(agentPathZip, agentZip);
 
-                    res.json(types);
-                })
-                .catch(_handleException(res));
-        });
+                const zip = new JSZip(data);
 
-        /* Ping client. */
-        router.post('/ping', (req, res) => {
-            _client(req.currentUserId())
-                .then(() => res.sendStatus(200))
-                .catch(_handleException(res));
-        });
+                const prop = [];
 
-        /* Get JDBC drivers list. */
-        router.post('/drivers', (req, res) => {
-            _client(req.currentUserId())
-                .then((client) => client.availableDrivers())
-                .then((arr) => res.json(arr))
-                .catch(_handleException(res));
-        });
+                const host = req.hostname.match(/:/g) ? req.hostname.slice(0, req.hostname.indexOf(':')) : req.hostname;
 
-        /** Get database schemas. */
-        router.post('/schemas', (req, res) => {
-            _client(req.currentUserId())
-                .then((client) => {
-                    const args = req.body;
+                prop.push('token=' + req.user.token);
+                prop.push('server-uri=' + (settings.agent.SSLOptions ? 'https' : 'http') + '://' + host + ':' + settings.agent.port);
+                prop.push('#Uncomment following options if needed:');
+                prop.push('#node-uri=http://localhost:8080');
+                prop.push('#driver-folder=./jdbc-drivers');
 
-                    args.jdbcInfo = {user: args.user, password: args.password};
+                zip.file(agentFld + '/default.properties', prop.join('\n'));
 
-                    return client.metadataSchemas(args.jdbcDriverJar, args.jdbcDriverClass, args.jdbcUrl, args.jdbcInfo);
-                })
-                .then((arr) => res.json(arr))
-                .catch(_handleException(res));
-        });
-
-        /** Get database tables. */
-        router.post('/tables', (req, res) => {
-            _client(req.currentUserId())
-                .then((client) => {
-                    const args = req.body;
+                const buffer = zip.generate({type: 'nodebuffer', platform: 'UNIX'});
 
-                    args.jdbcInfo = {user: args.user, password: args.password};
+                // Set the archive name.
+                res.attachment(agentZip);
 
-                    return client.metadataTables(args.jdbcDriverJar, args.jdbcDriverClass, args.jdbcUrl, args.jdbcInfo, args.schemas, args.tablesOnly);
-                })
-                .then((arr) => res.json(arr))
-                .catch(_handleException(res));
+                res.send(buffer);
+            });
         });
 
         resolveFactory(router);

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-web/src/main/js/serve/routes/profile.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/serve/routes/profile.js b/modules/control-center-web/src/main/js/serve/routes/profile.js
index da6b599..9d24239 100644
--- a/modules/control-center-web/src/main/js/serve/routes/profile.js
+++ b/modules/control-center-web/src/main/js/serve/routes/profile.js
@@ -21,10 +21,18 @@
 
 module.exports = {
     implements: 'profile-routes',
-    inject: ['require(lodash)', 'require(express)', 'mongo']
+    inject: ['require(lodash)', 'require(express)', 'mongo', 'agent']
 };
 
-module.exports.factory = function(_, express, mongo) {
+/**
+ *
+ * @param _
+ * @param express
+ * @param mongo
+ * @param {AgentManager} agent
+ * @returns {Promise}
+ */
+module.exports.factory = function(_, express, mongo, agent) {
     return new Promise((resolveFactory) => {
         const router = new express.Router();
 
@@ -71,6 +79,9 @@ module.exports.factory = function(_, express, mongo) {
                     });
                 })
                 .then((user) => {
+                    if (!params.token || user.token !== params.token)
+                        agent.close(user._id);
+
                     for (const param in params) {
                         if (params.hasOwnProperty(param))
                             user[param] = params[param];