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 2017/02/08 04:43:57 UTC

[1/2] ignite git commit: IGNITE-4472 Added user activities in Web Console.

Repository: ignite
Updated Branches:
  refs/heads/master e6ea938d1 -> 26ee9c286


http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/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
deleted file mode 100644
index a3700ca..0000000
--- a/modules/web-console/frontend/app/modules/Demo/Demo.module.js
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import angular from 'angular';
-
-import DEMO_INFO from 'app/data/demo-info.json';
-
-angular
-.module('ignite-console.demo', [
-    'ignite-console.socket'
-])
-.config(['$stateProvider', ($stateProvider) => {
-    $stateProvider
-        .state('demo', {
-            abstract: true,
-            template: '<ui-view></ui-view>'
-        })
-        .state('demo.resume', {
-            url: '/demo',
-            controller: ['$state', ($state) => {
-                $state.go('base.configuration.clusters');
-            }],
-            metaTags: {
-            }
-        })
-        .state('demo.reset', {
-            url: '/demo/reset',
-            controller: ['$state', '$http', 'IgniteMessages', ($state, $http, Messages) => {
-                $http.post('/api/v1/demo/reset')
-                    .then(() => $state.go('base.configuration.clusters'))
-                    .catch((res) => {
-                        $state.go('base.configuration.clusters');
-
-                        Messages.showError(res);
-                    });
-            }],
-            metaTags: {}
-        });
-}])
-.provider('Demo', ['$stateProvider', '$httpProvider', 'igniteSocketFactoryProvider', function($state, $http, socketFactory) {
-    if (/(\/demo.*)/ig.test(location.pathname))
-        sessionStorage.setItem('IgniteDemoMode', 'true');
-
-    const enabled = sessionStorage.getItem('IgniteDemoMode') === 'true';
-
-    if (enabled) {
-        socketFactory.set({query: 'IgniteDemoMode=true'});
-
-        $http.interceptors.push('demoInterceptor');
-    }
-
-    this.$get = ['$rootScope', ($root) => {
-        $root.IgniteDemoMode = enabled;
-
-        return {enabled};
-    }];
-}])
-.factory('demoInterceptor', ['Demo', (Demo) => {
-    const isApiRequest = (url) => /\/api\/v1/ig.test(url);
-
-    return {
-        request(cfg) {
-            if (Demo.enabled && isApiRequest(cfg.url))
-                cfg.headers.IgniteDemoMode = true;
-
-            return cfg;
-        }
-    };
-}])
-.controller('demoController', ['$scope', '$state', '$window', 'IgniteConfirm', ($scope, $state, $window, Confirm) => {
-    const _openTab = (stateName) => $window.open($state.href(stateName), '_blank');
-
-    $scope.startDemo = () => {
-        if (!$scope.user.demoCreated)
-            return _openTab('demo.reset');
-
-        Confirm.confirm('Would you like to continue with previous demo session?', true, false)
-            .then((resume) => {
-                if (resume)
-                    return _openTab('demo.resume');
-
-                _openTab('demo.reset');
-            });
-    };
-
-    $scope.closeDemo = () => {
-        $window.close();
-    };
-}])
-.provider('igniteDemoInfo', [function() {
-    const items = DEMO_INFO;
-
-    this.update = (data) => items[0] = data;
-
-    this.$get = [() => {
-        return items;
-    }];
-}])
-.service('DemoInfo', ['$rootScope', '$modal', '$state', '$q', 'igniteDemoInfo', 'IgniteAgentMonitor', ($rootScope, $modal, $state, $q, igniteDemoInfo, agentMonitor) => {
-    const scope = $rootScope.$new();
-
-    let closePromise = null;
-
-    function _fillPage() {
-        const model = igniteDemoInfo;
-
-        scope.title = model[0].title;
-        scope.message = model[0].message.join(' ');
-    }
-
-    const dialog = $modal({
-        templateUrl: '/templates/demo-info.html',
-        scope,
-        placement: 'center',
-        show: false,
-        backdrop: 'static'
-    });
-
-    scope.close = () => {
-        dialog.hide();
-
-        closePromise && closePromise.resolve();
-    };
-
-    scope.downloadAgent = () => {
-        const lnk = document.createElement('a');
-
-        lnk.setAttribute('href', '/api/v1/agent/download/zip');
-        lnk.setAttribute('target', '_self');
-        lnk.setAttribute('download', null);
-        lnk.style.display = 'none';
-
-        document.body.appendChild(lnk);
-
-        lnk.click();
-
-        document.body.removeChild(lnk);
-    };
-
-    return {
-        show: () => {
-            closePromise = $q.defer();
-
-            _fillPage();
-
-            return dialog.$promise
-                .then(dialog.show)
-                .then(() => Promise.race([agentMonitor.awaitAgent(), closePromise.promise]))
-                .then(() => scope.hasAgents = true);
-        }
-    };
-}]);

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/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
new file mode 100644
index 0000000..bd759df
--- /dev/null
+++ b/modules/web-console/frontend/app/modules/demo/Demo.module.js
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import angular from 'angular';
+
+import DEMO_INFO from 'app/data/demo-info.json';
+
+angular
+.module('ignite-console.demo', [
+    'ignite-console.socket'
+])
+.config(['$stateProvider', 'AclRouteProvider', ($stateProvider, AclRoute) => {
+    $stateProvider
+        .state('demo', {
+            abstract: true,
+            url: '/demo',
+            template: '<ui-view></ui-view>'
+        })
+        .state('demo.resume', {
+            url: '/resume',
+            onEnter: AclRoute.checkAccess('demo'),
+            controller: ['$state', ($state) => {
+                $state.go('base.configuration.clusters');
+            }],
+            metaTags: {
+                title: 'Demo resume'
+            }
+        })
+        .state('demo.reset', {
+            url: '/reset',
+            onEnter: AclRoute.checkAccess('demo'),
+            controller: ['$state', '$http', 'IgniteMessages', ($state, $http, Messages) => {
+                $http.post('/api/v1/demo/reset')
+                    .then(() => $state.go('base.configuration.clusters'))
+                    .catch((res) => {
+                        $state.go('base.configuration.clusters');
+
+                        Messages.showError(res);
+                    });
+            }],
+            metaTags: {
+                title: 'Demo reset'
+            }
+        });
+}])
+.provider('Demo', ['$stateProvider', '$httpProvider', 'igniteSocketFactoryProvider', function($state, $http, socketFactory) {
+    if (/(\/demo.*)/ig.test(location.pathname))
+        sessionStorage.setItem('IgniteDemoMode', 'true');
+
+    const enabled = sessionStorage.getItem('IgniteDemoMode') === 'true';
+
+    if (enabled) {
+        socketFactory.set({query: 'IgniteDemoMode=true'});
+
+        $http.interceptors.push('demoInterceptor');
+    }
+
+    this.$get = ['$rootScope', ($root) => {
+        $root.IgniteDemoMode = enabled;
+
+        return {enabled};
+    }];
+}])
+.factory('demoInterceptor', ['Demo', (Demo) => {
+    const isApiRequest = (url) => /\/api\/v1/ig.test(url);
+
+    return {
+        request(cfg) {
+            if (Demo.enabled && isApiRequest(cfg.url))
+                cfg.headers.IgniteDemoMode = true;
+
+            return cfg;
+        }
+    };
+}])
+.controller('demoController', ['$scope', '$state', '$window', 'IgniteConfirm', ($scope, $state, $window, Confirm) => {
+    const _openTab = (stateName) => $window.open($state.href(stateName), '_blank');
+
+    $scope.startDemo = () => {
+        if (!$scope.user.demoCreated)
+            return _openTab('demo.reset');
+
+        Confirm.confirm('Would you like to continue with previous demo session?', true, false)
+            .then((resume) => {
+                if (resume)
+                    return _openTab('demo.resume');
+
+                _openTab('demo.reset');
+            });
+    };
+
+    $scope.closeDemo = () => {
+        $window.close();
+    };
+}])
+.provider('igniteDemoInfo', [function() {
+    const items = DEMO_INFO;
+
+    this.update = (data) => items[0] = data;
+
+    this.$get = [() => {
+        return items;
+    }];
+}])
+.service('DemoInfo', ['$rootScope', '$modal', '$state', '$q', 'igniteDemoInfo', 'IgniteAgentMonitor', ($rootScope, $modal, $state, $q, igniteDemoInfo, agentMonitor) => {
+    const scope = $rootScope.$new();
+
+    let closePromise = null;
+
+    function _fillPage() {
+        const model = igniteDemoInfo;
+
+        scope.title = model[0].title;
+        scope.message = model[0].message.join(' ');
+    }
+
+    const dialog = $modal({
+        templateUrl: '/templates/demo-info.html',
+        scope,
+        placement: 'center',
+        show: false,
+        backdrop: 'static'
+    });
+
+    scope.close = () => {
+        dialog.hide();
+
+        closePromise && closePromise.resolve();
+    };
+
+    scope.downloadAgent = () => {
+        const lnk = document.createElement('a');
+
+        lnk.setAttribute('href', '/api/v1/agent/download/zip');
+        lnk.setAttribute('target', '_self');
+        lnk.setAttribute('download', null);
+        lnk.style.display = 'none';
+
+        document.body.appendChild(lnk);
+
+        lnk.click();
+
+        document.body.removeChild(lnk);
+    };
+
+    return {
+        show: () => {
+            closePromise = $q.defer();
+
+            _fillPage();
+
+            return dialog.$promise
+                .then(dialog.show)
+                .then(() => Promise.race([agentMonitor.awaitAgent(), closePromise.promise]))
+                .then(() => scope.hasAgents = true);
+        }
+    };
+}]);

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/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 0d0b171..4e972ef 100644
--- a/modules/web-console/frontend/app/modules/sql/sql.controller.js
+++ b/modules/web-console/frontend/app/modules/sql/sql.controller.js
@@ -186,8 +186,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',
-    function($root, $scope, $http, $q, $timeout, $interval, $animate, $location, $anchorScroll, $state, $filter, $modal, $popover, Loading, LegacyUtils, Messages, Confirm, agentMonitor, IgniteChartColors, Notebook, Nodes, uiGridExporterConstants, Version) {
+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',
+    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) {
         let stopTopology = null;
 
         const _tryStopRefresh = function(paragraph) {
@@ -965,6 +965,8 @@ export default ['$rootScope', '$scope', '$http', '$q', '$timeout', '$interval',
         $scope.addQuery = function() {
             const sz = $scope.notebook.paragraphs.length;
 
+            ActivitiesData.post({ action: '/queries/add/query' });
+
             const paragraph = new Paragraph($animate, $timeout, {
                 name: 'Query' + (sz === 0 ? '' : sz),
                 query: '',
@@ -991,6 +993,8 @@ export default ['$rootScope', '$scope', '$http', '$q', '$timeout', '$interval',
         $scope.addScan = function() {
             const sz = $scope.notebook.paragraphs.length;
 
+            ActivitiesData.post({ action: '/queries/add/scan' });
+
             const paragraph = new Paragraph($animate, $timeout, {
                 name: 'Scan' + (sz === 0 ? '' : sz),
                 query: '',
@@ -1379,6 +1383,8 @@ export default ['$rootScope', '$scope', '$http', '$q', '$timeout', '$interval',
 
                             const qry = args.maxPages ? addLimit(args.query, args.pageSize * args.maxPages) : paragraph.query;
 
+                            ActivitiesData.post({ action: '/queries/execute' });
+
                             return agentMonitor.query(nid, args.cacheName, qry, nonCollocatedJoins, local, args.pageSize);
                         })
                         .then((res) => {
@@ -1430,6 +1436,8 @@ export default ['$rootScope', '$scope', '$http', '$q', '$timeout', '$interval',
                         pageSize: paragraph.pageSize
                     };
 
+                    ActivitiesData.post({ action: '/queries/explain' });
+
                     return agentMonitor.query(nid, args.cacheName, args.query, false, false, args.pageSize);
                 })
                 .then(_processQueryResult.bind(this, paragraph, true))
@@ -1466,6 +1474,8 @@ export default ['$rootScope', '$scope', '$http', '$q', '$timeout', '$interval',
                                 localNid: local ? nid : null
                             };
 
+                            ActivitiesData.post({ action: '/queries/scan' });
+
                             return agentMonitor.query(nid, args.cacheName, query, false, local, args.pageSize);
                         })
                         .then((res) => _processQueryResult(paragraph, true, res))

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/modules/sql/sql.module.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/sql/sql.module.js b/modules/web-console/frontend/app/modules/sql/sql.module.js
index a1ffde9..5875961 100644
--- a/modules/web-console/frontend/app/modules/sql/sql.module.js
+++ b/modules/web-console/frontend/app/modules/sql/sql.module.js
@@ -30,7 +30,7 @@ angular.module('ignite-console.sql', [
             // set up the states
             $stateProvider
                 .state('base.sql', {
-                    url: '/sql',
+                    url: '/queries',
                     abstract: true,
                     template: '<ui-view></ui-view>'
                 })

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/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 c3151e1..35c6fbb 100644
--- a/modules/web-console/frontend/app/modules/states/admin.state.js
+++ b/modules/web-console/frontend/app/modules/states/admin.state.js
@@ -29,7 +29,7 @@ angular
         templateUrl: '/settings/admin.html',
         onEnter: AclRoute.checkAccess('admin_page'),
         metaTags: {
-            title: 'List of registered users'
+            title: 'Admin panel'
         }
     });
 }]);

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/modules/states/configuration/summary/summary.controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/states/configuration/summary/summary.controller.js b/modules/web-console/frontend/app/modules/states/configuration/summary/summary.controller.js
index cfc6df9..16d2fae 100644
--- a/modules/web-console/frontend/app/modules/states/configuration/summary/summary.controller.js
+++ b/modules/web-console/frontend/app/modules/states/configuration/summary/summary.controller.js
@@ -21,8 +21,8 @@ import saver from 'file-saver';
 const escapeFileName = (name) => name.replace(/[\\\/*\"\[\],\.:;|=<>?]/g, '-').replace(/ /g, '_');
 
 export default [
-    '$rootScope', '$scope', '$http', 'IgniteLegacyUtils', 'IgniteMessages', 'IgniteLoading', '$filter', 'IgniteConfigurationResource', 'JavaTypes', 'IgniteVersion', 'IgniteConfigurationGenerator', 'SpringTransformer', 'JavaTransformer', 'IgniteDockerGenerator', 'IgniteMavenGenerator', 'IgnitePropertiesGenerator', 'IgniteReadmeGenerator', 'IgniteFormUtils', 'IgniteSummaryZipper',
-    function($root, $scope, $http, LegacyUtils, Messages, Loading, $filter, Resource, JavaTypes, Version, generator, spring, java, docker, pom, propsGenerator, readme, FormUtils, SummaryZipper) {
+    '$rootScope', '$scope', '$http', 'IgniteLegacyUtils', 'IgniteMessages', 'IgniteLoading', '$filter', 'IgniteConfigurationResource', 'JavaTypes', 'IgniteVersion', 'IgniteConfigurationGenerator', 'SpringTransformer', 'JavaTransformer', 'IgniteDockerGenerator', 'IgniteMavenGenerator', 'IgnitePropertiesGenerator', 'IgniteReadmeGenerator', 'IgniteFormUtils', 'IgniteSummaryZipper', 'IgniteActivitiesData',
+    function($root, $scope, $http, LegacyUtils, Messages, Loading, $filter, Resource, JavaTypes, Version, generator, spring, java, docker, pom, propsGenerator, readme, FormUtils, SummaryZipper, ActivitiesData) {
         const ctrl = this;
 
         $scope.ui = {
@@ -304,6 +304,8 @@ export default [
 
             $scope.isPrepareDownloading = true;
 
+            ActivitiesData.post({ action: '/configuration/download' });
+
             return new SummaryZipper({ cluster, data: ctrl.data || {}, IgniteDemoMode: $root.IgniteDemoMode })
                 .then((data) => {
                     saver.saveAs(data, escapeFileName(cluster.name) + '-project.zip');

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/modules/user/AclRoute.provider.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/user/AclRoute.provider.js b/modules/web-console/frontend/app/modules/user/AclRoute.provider.js
index 40abea5..4225bc4 100644
--- a/modules/web-console/frontend/app/modules/user/AclRoute.provider.js
+++ b/modules/web-console/frontend/app/modules/user/AclRoute.provider.js
@@ -17,31 +17,36 @@
 
 export default [() => {
     class AclRoute {
-        static checkAccess = (permissions, failState) => {
+        static checkAccess(permissions, failState) {
             failState = failState || '403';
 
-            return ['$state', 'AclService', 'User', ($state, AclService, User) => {
-                User.read()
-                    .then(() => {
-                        if (AclService.can(permissions))
-                            return;
+            return ['$q', '$state', 'AclService', 'User', 'IgniteActivitiesData', function($q, $state, AclService, User, Activities) {
+                const action = this.name ? $state.href(this.name) : null;
 
-                        return $state.go(failState);
-                    })
+                return User.read()
                     .catch(() => {
                         User.clean();
 
                         if ($state.current.name !== 'signin')
                             $state.go('signin');
+
+                        return $q.reject('Failed to detect user');
+                    })
+                    .then(() => {
+                        if (AclService.can(permissions))
+                            return Activities.post({ action });
+
+                        $state.go(failState);
+
+                        return $q.reject('User are not authorized');
                     });
             }];
         }
-    }
 
-    return {
-        checkAccess: AclRoute.checkAccess,
-        $get: () => {
+        static $get() {
             return AclRoute;
         }
-    };
+    }
+
+    return AclRoute;
 }];

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/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 e0f905d..95ff4c3 100644
--- a/modules/web-console/frontend/app/modules/user/Auth.service.js
+++ b/modules/web-console/frontend/app/modules/user/Auth.service.js
@@ -21,7 +21,7 @@ export default ['Auth', ['$http', '$rootScope', '$state', '$window', 'IgniteErro
             forgotPassword(userInfo) {
                 $http.post('/api/v1/password/forgot', userInfo)
                     .then(() => $state.go('password.send'))
-                    .cacth(({data}) => ErrorPopover.show('forgot_email', Messages.errorMessage(null, data)));
+                    .catch(({data}) => ErrorPopover.show('forgot_email', Messages.errorMessage(null, data)));
             },
             auth(action, userInfo) {
                 $http.post('/api/v1/' + action, userInfo)

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

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/modules/user/user.module.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/user/user.module.js b/modules/web-console/frontend/app/modules/user/user.module.js
index 11798d0..b86a62e 100644
--- a/modules/web-console/frontend/app/modules/user/user.module.js
+++ b/modules/web-console/frontend/app/modules/user/user.module.js
@@ -22,10 +22,10 @@ import Auth from './Auth.service';
 import User from './User.service';
 import AclRouteProvider from './AclRoute.provider';
 
-angular
-.module('ignite-console.user', [
+angular.module('ignite-console.user', [
     'mm.acl',
-    'ignite-console.config'
+    'ignite-console.config',
+    'ignite-console.core'
 ])
 .factory('sessionRecoverer', ['$injector', '$q', ($injector, $q) => {
     return {

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/vendor.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/vendor.js b/modules/web-console/frontend/app/vendor.js
index a9e8844..3bbb322 100644
--- a/modules/web-console/frontend/app/vendor.js
+++ b/modules/web-console/frontend/app/vendor.js
@@ -25,6 +25,7 @@ import 'angular-strap/dist/angular-strap.tpl';
 import 'angular-socket-io';
 import 'angular-retina';
 import 'angular-ui-router';
+import 'angular-translate';
 import 'ui-router-metatags/dist/ui-router-metatags';
 import 'angular-smart-table';
 import 'angular-ui-grid/ui-grid';

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/controllers/admin-controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/controllers/admin-controller.js b/modules/web-console/frontend/controllers/admin-controller.js
deleted file mode 100644
index cf7fd71..0000000
--- a/modules/web-console/frontend/controllers/admin-controller.js
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-const ICON_SORT = '<span ui-grid-one-bind-id-grid="col.uid + \'-sortdir-text\'" ui-grid-visible="col.sort.direction" aria-label="Sort Descending"><i ng-class="{ \'ui-grid-icon-up-dir\': col.sort.direction == asc, \'ui-grid-icon-down-dir\': col.sort.direction == desc, \'ui-grid-icon-blank\': !col.sort.direction }" title="" aria-hidden="true"></i></span>';
-
-const CLUSTER_HEADER_TEMPLATE = `<div class='ui-grid-cell-contents' bs-tooltip data-title='{{ col.headerTooltip(col) }}' data-placement='top'><i class='fa fa-sitemap'></i>${ICON_SORT}</div>`;
-const MODEL_HEADER_TEMPLATE = `<div class='ui-grid-cell-contents' bs-tooltip data-title='{{ col.headerTooltip(col) }}' data-placement='top'><i class='fa fa-object-group'></i>${ICON_SORT}</div>`;
-const CACHE_HEADER_TEMPLATE = `<div class='ui-grid-cell-contents' bs-tooltip data-title='{{ col.headerTooltip(col) }}' data-placement='top'><i class='fa fa-database'></i>${ICON_SORT}</div>`;
-const IGFS_HEADER_TEMPLATE = `<div class='ui-grid-cell-contents' bs-tooltip data-title='{{ col.headerTooltip(col) }}' data-placement='top'><i class='fa fa-folder-o'></i>${ICON_SORT}</div>`;
-
-const ACTIONS_TEMPLATE = `
-<div class='text-center ui-grid-cell-actions'>
-    <a class='btn btn-default dropdown-toggle' bs-dropdown='' ng-show='row.entity._id != $root.user._id' data-placement='bottom-right' data-container='.panel'>
-        <i class='fa fa-gear'></i>&nbsp;
-        <span class='caret'></span>
-    </a>
-    <ul class='dropdown-menu' role='menu'>
-        <li>
-            <a ng-click='grid.api.becomeUser(row.entity)'>Become this user</a>
-        </li>
-        <li>
-            <a ng-click='grid.api.toggleAdmin(row.entity)' ng-if='row.entity.admin && row.entity._id !== $root.user._id'>Revoke admin</a>
-            <a ng-click='grid.api.toggleAdmin(row.entity)' ng-if='!row.entity.admin && row.entity._id !== $root.user._id'>Grant admin</a>
-        </li>
-        <li>
-            <a ng-click='grid.api.removeUser(row.entity)'>Remove user</a>
-        </li>
-</div>`;
-
-const EMAIL_TEMPLATE = '<div class="ui-grid-cell-contents"><a ng-href="mailto:{{ COL_FIELD }}">{{ COL_FIELD }}</a></div>';
-
-// Controller for Admin screen.
-export default ['adminController', [
-    '$rootScope', '$scope', '$http', '$q', '$state', '$filter', 'uiGridConstants', 'IgniteMessages', 'IgniteConfirm', 'User', 'IgniteNotebookData', 'IgniteCountries',
-    ($rootScope, $scope, $http, $q, $state, $filter, uiGridConstants, Messages, Confirm, User, Notebook, Countries) => {
-        $scope.users = null;
-
-        const companySelectOptions = [];
-        const countrySelectOptions = [];
-
-        const COLUMNS_DEFS = [
-            {displayName: 'Actions', cellTemplate: ACTIONS_TEMPLATE, field: 'test', minWidth: 80, width: 80, enableFiltering: false, enableSorting: false},
-            {displayName: 'User', field: 'userName', minWidth: 65, enableFiltering: true, filter: { placeholder: 'Filter by name...' }},
-            {displayName: 'Email', field: 'email', cellTemplate: EMAIL_TEMPLATE, minWidth: 160, enableFiltering: true, filter: { placeholder: 'Filter by email...' }},
-            {displayName: 'Company', field: 'company', minWidth: 160, filter: {
-                selectOptions: companySelectOptions, type: uiGridConstants.filter.SELECT, condition: uiGridConstants.filter.EXACT }
-            },
-            {displayName: 'Country', field: 'countryCode', minWidth: 80, filter: {
-                selectOptions: countrySelectOptions, type: uiGridConstants.filter.SELECT, condition: uiGridConstants.filter.EXACT }
-            },
-            {displayName: 'Last login', field: 'lastLogin', cellFilter: 'date:"medium"', minWidth: 175, width: 175, enableFiltering: false, sort: { direction: 'desc', priority: 0 }},
-            {displayName: 'Clusters count', headerCellTemplate: CLUSTER_HEADER_TEMPLATE, field: '_clusters', type: 'number', headerTooltip: 'Clusters count', minWidth: 50, width: 50, enableFiltering: false},
-            {displayName: 'Models count', headerCellTemplate: MODEL_HEADER_TEMPLATE, field: '_models', type: 'number', headerTooltip: 'Models count', minWidth: 50, width: 50, enableFiltering: false},
-            {displayName: 'Caches count', headerCellTemplate: CACHE_HEADER_TEMPLATE, field: '_caches', type: 'number', headerTooltip: 'Caches count', minWidth: 50, width: 50, enableFiltering: false},
-            {displayName: 'IGFS count', headerCellTemplate: IGFS_HEADER_TEMPLATE, field: '_igfs', type: 'number', headerTooltip: 'IGFS count', minWidth: 50, width: 50, enableFiltering: false}
-        ];
-
-        const ctrl = $scope.ctrl = {};
-
-        const becomeUser = function(user) {
-            $http.get('/api/v1/admin/become', { params: {viewedUserId: user._id}})
-                .then(() => User.load())
-                .then(() => $state.go('base.configuration.clusters'))
-                .then(() => Notebook.load())
-                .catch(Messages.showError);
-        };
-
-        const removeUser = (user) => {
-            Confirm.confirm(`Are you sure you want to remove user: "${user.userName}"?`)
-                .then(() => {
-                    $http.post('/api/v1/admin/remove', {userId: user._id})
-                        .then(() => {
-                            const i = _.findIndex($scope.users, (u) => u._id === user._id);
-
-                            if (i >= 0)
-                                $scope.users.splice(i, 1);
-
-                            Messages.showInfo(`User has been removed: "${user.userName}"`);
-                        })
-                        .catch(({data, status}) => {
-                            if (status === 503)
-                                Messages.showInfo(data);
-                            else
-                                Messages.showError('Failed to remove user: ', data);
-                        });
-                });
-        };
-
-        const toggleAdmin = (user) => {
-            if (user.adminChanging)
-                return;
-
-            user.adminChanging = true;
-
-            $http.post('/api/v1/admin/save', {userId: user._id, adminFlag: !user.admin})
-                .then(() => {
-                    user.admin = !user.admin;
-
-                    Messages.showInfo(`Admin right was successfully toggled for user: "${user.userName}"`);
-                })
-                .catch((res) => {
-                    Messages.showError('Failed to toggle admin right for user: ', res);
-                })
-                .finally(() => user.adminChanging = false);
-        };
-
-
-        ctrl.gridOptions = {
-            data: [],
-            columnVirtualizationThreshold: 30,
-            columnDefs: COLUMNS_DEFS,
-            categories: [
-                {name: 'Actions', visible: true, selectable: true},
-                {name: 'User', visible: true, selectable: true},
-                {name: 'Email', visible: true, selectable: true},
-                {name: 'Company', visible: true, selectable: true},
-                {name: 'Country', visible: true, selectable: true},
-                {name: 'Last login', visible: true, selectable: true},
-
-                {name: 'Clusters count', visible: true, selectable: true},
-                {name: 'Models count', visible: true, selectable: true},
-                {name: 'Caches count', visible: true, selectable: true},
-                {name: 'IGFS count', visible: true, selectable: true}
-            ],
-            enableFiltering: true,
-            enableRowSelection: false,
-            enableRowHeaderSelection: false,
-            enableColumnMenus: false,
-            multiSelect: false,
-            modifierKeysToMultiSelect: true,
-            noUnselect: true,
-            flatEntityAccess: true,
-            fastWatch: true,
-            onRegisterApi: (api) => {
-                ctrl.gridApi = api;
-
-                api.becomeUser = becomeUser;
-                api.removeUser = removeUser;
-                api.toggleAdmin = toggleAdmin;
-            }
-        };
-
-        /**
-         * Set grid height.
-         *
-         * @param {Number} rows Rows count.
-         * @private
-         */
-        const adjustHeight = (rows) => {
-            const height = Math.min(rows, 20) * 30 + 75;
-
-            // Remove header height.
-            ctrl.gridApi.grid.element.css('height', height + 'px');
-
-            ctrl.gridApi.core.handleWindowResize();
-        };
-
-        const usersToFilterOptions = (column) => {
-            return _.sortBy(
-                _.map(
-                    _.groupBy($scope.users, (usr) => {
-                        const fld = usr[column];
-
-                        return _.isNil(fld) ? fld : fld.toUpperCase();
-                    }),
-                    (arr, value) => ({label: `${_.head(arr)[column] || 'Not set'} (${arr.length})`, value})
-                ),
-                'value');
-        };
-
-        const _reloadUsers = () => {
-            $http.post('/api/v1/admin/list')
-                .then(({ data }) => {
-                    $scope.users = data;
-
-                    companySelectOptions.length = 0;
-                    countrySelectOptions.length = 0;
-
-                    _.forEach($scope.users, (user) => {
-                        user.userName = user.firstName + ' ' + user.lastName;
-                        user.countryCode = Countries.getByName(user.country).code;
-
-                        user._clusters = user.counters.clusters;
-                        user._models = user.counters.models;
-                        user._caches = user.counters.caches;
-                        user._igfs = user.counters.igfs;
-                    });
-
-                    companySelectOptions.push(...usersToFilterOptions('company'));
-                    countrySelectOptions.push(...usersToFilterOptions('countryCode'));
-
-                    $scope.ctrl.gridOptions.data = data;
-
-                    adjustHeight(data.length);
-                })
-                .catch(Messages.showError);
-        };
-
-        _reloadUsers();
-
-        const _enableColumns = (categories, visible) => {
-            _.forEach(categories, (cat) => {
-                cat.visible = visible;
-
-                _.forEach(ctrl.gridOptions.columnDefs, (col) => {
-                    if (col.displayName === cat.name)
-                        col.visible = visible;
-                });
-            });
-
-            ctrl.gridApi.grid.refresh();
-        };
-
-        const _selectableColumns = () => _.filter(ctrl.gridOptions.categories, (cat) => cat.selectable);
-
-        ctrl.toggleColumns = (category, visible) => _enableColumns([category], visible);
-        ctrl.selectAllColumns = () => _enableColumns(_selectableColumns(), true);
-        ctrl.clearAllColumns = () => _enableColumns(_selectableColumns(), false);
-    }
-]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/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 303110e..bfffe92 100644
--- a/modules/web-console/frontend/controllers/domains-controller.js
+++ b/modules/web-console/frontend/controllers/domains-controller.js
@@ -17,8 +17,8 @@
 
 // Controller for Domain model screen.
 export default ['domainsController', [
-    '$rootScope', '$scope', '$http', '$state', '$filter', '$timeout', '$modal', 'IgniteLegacyUtils', 'IgniteMessages', 'IgniteFocus', 'IgniteConfirm', 'IgniteConfirmBatch', 'IgniteClone', 'IgniteLoading', 'IgniteModelNormalizer', 'IgniteUnsavedChangesGuard', 'IgniteAgentMonitor', 'IgniteLegacyTable', 'IgniteConfigurationResource', 'IgniteErrorPopover', 'IgniteFormUtils', 'JavaTypes', 'SqlTypes',
-    function($root, $scope, $http, $state, $filter, $timeout, $modal, LegacyUtils, Messages, Focus, Confirm, ConfirmBatch, Clone, Loading, ModelNormalizer, UnsavedChangesGuard, IgniteAgentMonitor, LegacyTable, Resource, ErrorPopover, FormUtils, JavaTypes, SqlTypes) {
+    '$rootScope', '$scope', '$http', '$state', '$filter', '$timeout', '$modal', 'IgniteLegacyUtils', 'IgniteMessages', 'IgniteFocus', 'IgniteConfirm', 'IgniteConfirmBatch', 'IgniteClone', 'IgniteLoading', 'IgniteModelNormalizer', 'IgniteUnsavedChangesGuard', 'IgniteAgentMonitor', 'IgniteLegacyTable', 'IgniteConfigurationResource', 'IgniteErrorPopover', 'IgniteFormUtils', 'JavaTypes', 'SqlTypes', 'IgniteActivitiesData',
+    function($root, $scope, $http, $state, $filter, $timeout, $modal, LegacyUtils, Messages, Focus, Confirm, ConfirmBatch, Clone, Loading, ModelNormalizer, UnsavedChangesGuard, IgniteAgentMonitor, LegacyTable, Resource, ErrorPopover, FormUtils, JavaTypes, SqlTypes, ActivitiesData) {
         UnsavedChangesGuard.install($scope);
 
         const emptyDomain = {empty: true};
@@ -460,6 +460,14 @@ export default ['domainsController', [
                 $scope.importDomain.loadingOptions = LOADING_JDBC_DRIVERS;
 
                 IgniteAgentMonitor.startWatch({text: 'Back to Domain models', goal: 'import domain model from database'})
+                    .then(() => {
+                        ActivitiesData.post({
+                            group: 'configuration',
+                            action: 'configuration/import/model'
+                        });
+
+                        return true;
+                    })
                     .then(importDomainModal.$promise)
                     .then(importDomainModal.show)
                     .then(() => {

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/package.json
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/package.json b/modules/web-console/frontend/package.json
index fd50d5b..ff52ba4 100644
--- a/modules/web-console/frontend/package.json
+++ b/modules/web-console/frontend/package.json
@@ -44,6 +44,7 @@
     "angular-socket-io": "~0.7.0",
     "angular-strap": "~2.3.8",
     "angular-touch": "~1.5.9",
+    "angular-translate": "~2.13.1",
     "angular-tree-control": "~0.2.26",
     "angular-ui-grid": "~3.2.9",
     "angular-ui-router": "~0.3.1",

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/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 bfa6c6c..47555a7 100644
--- a/modules/web-console/frontend/public/stylesheets/_font-awesome-custom.scss
+++ b/modules/web-console/frontend/public/stylesheets/_font-awesome-custom.scss
@@ -69,3 +69,31 @@ $fa-font-path: '~font-awesome/fonts';
 
   cursor: default;
 }
+
+.icon-user {
+  @extend .fa;
+  @extend .fa-user-o;
+
+  cursor: default;
+}
+
+.icon-admin {
+  @extend .fa;
+  @extend .fa-user-secret;
+
+  cursor: default;
+}
+
+.icon-datepicker-left {
+  @extend .fa;
+  @extend .fa-chevron-left;
+
+  margin: 0;
+}
+
+.icon-datepicker-right {
+  @extend .fa;
+  @extend .fa-chevron-right;
+  
+  margin: 0;
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/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 4318fc2..67cfed1 100644
--- a/modules/web-console/frontend/public/stylesheets/style.scss
+++ b/modules/web-console/frontend/public/stylesheets/style.scss
@@ -2302,12 +2302,13 @@ html,body,.splash-screen {
         cursor: default;
 
         i {
+            margin-top: 2px;
             margin-right: 10px;
         }
 
         label {
             cursor: default;
-            line-height: 24px;
+            line-height: 28px;
         }
 
         sub {
@@ -2326,4 +2327,40 @@ html,body,.splash-screen {
     .ui-grid-filter-select {
         width: calc(100% - 10px);
     }
+
+    .ui-grid-cell-contents > i {
+        line-height: $line-height-base;
+    }
+
+    .ui-grid-row:nth-child(odd):hover .ui-grid-cell {
+        background: $ignite-row-hover;
+    }
+    
+    .ui-grid-row:nth-child(even):hover .ui-grid-cell {
+        background: $ignite-row-hover;
+    }
 }
+
+.datepicker.dropdown-menu {
+    width: 250px;
+    height: 270px;
+
+    button {
+        outline: none;
+        border: 0;
+    }
+
+    tbody {
+        height: 180px;
+    }
+
+    tbody button {
+        padding: 6px;
+    }
+
+    &.datepicker-mode-1, &.datepicker-mode-2 {
+        tbody button {
+            height: 65px;
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/public/stylesheets/variables.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/public/stylesheets/variables.scss b/modules/web-console/frontend/public/stylesheets/variables.scss
index 8500eac..e30bbdd 100644
--- a/modules/web-console/frontend/public/stylesheets/variables.scss
+++ b/modules/web-console/frontend/public/stylesheets/variables.scss
@@ -26,3 +26,4 @@ $ignite-border-bottom-color: $brand-primary;
 $ignite-background-color: #fff;
 $ignite-header-color: #555;
 $ignite-invalid-color: $brand-primary;
+$ignite-row-hover: #c9dde1;

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/views/settings/admin.jade
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/views/settings/admin.jade b/modules/web-console/frontend/views/settings/admin.jade
index c985826..a09fda9 100644
--- a/modules/web-console/frontend/views/settings/admin.jade
+++ b/modules/web-console/frontend/views/settings/admin.jade
@@ -14,38 +14,12 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 
-mixin grid-settings()
-    i.fa.fa-bars(data-animation='am-flip-x' bs-dropdown='' aria-haspopup='true' aria-expanded='expanded' data-auto-close='1' data-trigger='click')
-    ul.select.dropdown-menu(role='menu')
-        li(ng-repeat='item in ctrl.gridOptions.categories|filter:{selectable:true}')
-            a(ng-click='ctrl.toggleColumns(item, !item.visible)')
-                i.fa.fa-check-square-o.pull-left(ng-if='item.visible')
-                i.fa.fa-square-o.pull-left(ng-if='!item.visible')
-                span {{::item.name}}
-        li.divider
-        li
-            a(ng-click='ctrl.selectAllColumns()') Select all
-        li
-            a(ng-click='ctrl.clearAllColumns()') Clear all
-        li.divider
-        li
-            a(ng-click='$hide()') Close
-
-.admin-page.row(ng-controller='adminController')
+.admin-page.row
     .docs-content.greedy
         .docs-header
-            h1 List of registered users
+            h1 Admin panel
             hr
         .docs-body
             .row
                 .col-xs-12
-                    .panel.panel-default
-                        .panel-heading.ui-grid-settings
-                            +grid-settings
-                            label Total users: 
-                                strong {{ users.length }}&nbsp;&nbsp;&nbsp;
-                            label Showing users: 
-                                strong {{ ctrl.gridApi.grid.getVisibleRows().length }}
-                                sub(ng-show='users.length === ctrl.gridApi.grid.getVisibleRows().length') all
-                        .panel-collapse
-                            .grid(ui-grid='ctrl.gridOptions' ui-grid-resize-columns ui-grid-selection ui-grid-pinning)
+                    ignite-list-of-registered-users(data-options='ctrl.data')

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/views/sql/sql.jade
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/views/sql/sql.jade b/modules/web-console/frontend/views/sql/sql.jade
index 03015e8..61d5b30 100644
--- a/modules/web-console/frontend/views/sql/sql.jade
+++ b/modules/web-console/frontend/views/sql/sql.jade
@@ -15,7 +15,7 @@
     limitations under the License.
 
 include /app/helpers/jade/mixins.jade
-include /app/directives/ui-grid-settings/ui-grid-settings.jade
+include /app/components/ui-grid-settings/ui-grid-settings.jade
 
 mixin btn-toolbar(btn, click, tip, focusId)
     i.btn.btn-default.fa(class=btn ng-click=click bs-tooltip='' data-title=tip ignite-on-click-focus=focusId data-trigger='hover' data-placement='bottom')
@@ -195,7 +195,7 @@ mixin paragraph-scan
                 +table-result-body
             .footer.clearfix()
                 .pull-left
-                    | Showing results for scan of #[b{{ paragraph.queryArgs.cacheName | defaultName }}]
+                    | Showing results for scan of #[b {{ paragraph.queryArgs.cacheName | defaultName }}]
                     span(ng-if='paragraph.queryArgs.filter') &nbsp; with filter: #[b {{ paragraph.queryArgs.filter }}]
                     span(ng-if='paragraph.queryArgs.localNid') &nbsp; on node: #[b {{ paragraph.queryArgs.localNid | limitTo:8 }}]
 


[2/2] ignite git commit: IGNITE-4472 Added user activities in Web Console.

Posted by an...@apache.org.
IGNITE-4472 Added user activities in Web Console.


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

Branch: refs/heads/master
Commit: 26ee9c2865648118da97ee8ef84df990359edb96
Parents: e6ea938
Author: Andrey Novikov <an...@gridgain.com>
Authored: Wed Feb 8 11:43:22 2017 +0700
Committer: Andrey Novikov <an...@gridgain.com>
Committed: Wed Feb 8 11:43:22 2017 +0700

----------------------------------------------------------------------
 modules/web-console/backend/app/agent.js        |  10 +-
 modules/web-console/backend/app/mongo.js        |  49 ++--
 modules/web-console/backend/app/routes.js       |   5 +-
 .../web-console/backend/routes/activities.js    |  52 +++++
 modules/web-console/backend/routes/admin.js     |   2 +-
 modules/web-console/backend/routes/agent.js     |  10 +-
 modules/web-console/backend/routes/public.js    |   1 -
 .../web-console/backend/services/activities.js  | 136 +++++++++++
 modules/web-console/backend/services/users.js   |  13 +-
 modules/web-console/frontend/app/app.config.js  |   9 +
 modules/web-console/frontend/app/app.js         |  29 ++-
 .../activities-user-dialog.controller.js        |  60 +++++
 .../activities-user-dialog.jade                 |  36 +++
 .../components/activities-user-dialog/index.js  |  36 +++
 .../form-field-datepicker.jade                  |  55 +++++
 .../form-field-datepicker.scss                  |  20 ++
 .../list-of-registered-users/index.js           |  28 +++
 .../list-of-registered-users.categories.js      |  30 +++
 .../list-of-registered-users.column-defs.js     |  80 +++++++
 .../list-of-registered-users.controller.js      | 207 ++++++++++++++++
 .../list-of-registered-users.jade               |  54 +++++
 .../ui-grid-header/ui-grid-header.jade          |  27 +++
 .../ui-grid-header/ui-grid-header.scss          |  84 +++++++
 .../ui-grid-settings/ui-grid-settings.jade      |  33 +++
 .../ui-grid-settings/ui-grid-settings.scss      |  70 ++++++
 .../app/core/activities/Activities.data.js      |  39 ++++
 .../frontend/app/core/admin/Admin.data.js       |  77 ++++++
 modules/web-console/frontend/app/core/index.js  |  25 ++
 modules/web-console/frontend/app/data/i18n.js   |  38 +++
 .../ui-grid-settings/ui-grid-settings.jade      |  33 ---
 .../ui-grid-settings/ui-grid-settings.scss      |  38 ---
 .../app/filters/uiGridSubcategories.filter.js   |  24 ++
 .../frontend/app/modules/Demo/Demo.module.js    | 166 -------------
 .../frontend/app/modules/demo/Demo.module.js    | 172 ++++++++++++++
 .../frontend/app/modules/sql/sql.controller.js  |  14 +-
 .../frontend/app/modules/sql/sql.module.js      |   2 +-
 .../frontend/app/modules/states/admin.state.js  |   2 +-
 .../configuration/summary/summary.controller.js |   6 +-
 .../app/modules/user/AclRoute.provider.js       |  31 +--
 .../frontend/app/modules/user/Auth.service.js   |   2 +-
 .../frontend/app/modules/user/permissions.js    |   2 +-
 .../frontend/app/modules/user/user.module.js    |   6 +-
 modules/web-console/frontend/app/vendor.js      |   1 +
 .../frontend/controllers/admin-controller.js    | 234 -------------------
 .../frontend/controllers/domains-controller.js  |  12 +-
 modules/web-console/frontend/package.json       |   1 +
 .../stylesheets/_font-awesome-custom.scss       |  28 +++
 .../frontend/public/stylesheets/style.scss      |  39 +++-
 .../frontend/public/stylesheets/variables.scss  |   1 +
 .../frontend/views/settings/admin.jade          |  32 +--
 modules/web-console/frontend/views/sql/sql.jade |   4 +-
 51 files changed, 1603 insertions(+), 562 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/backend/app/agent.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/app/agent.js b/modules/web-console/backend/app/agent.js
index 961253f..8170280 100644
--- a/modules/web-console/backend/app/agent.js
+++ b/modules/web-console/backend/app/agent.js
@@ -24,7 +24,7 @@
  */
 module.exports = {
     implements: 'agent-manager',
-    inject: ['require(lodash)', 'require(fs)', 'require(path)', 'require(jszip)', 'require(socket.io)', 'settings', 'mongo']
+    inject: ['require(lodash)', 'require(fs)', 'require(path)', 'require(jszip)', 'require(socket.io)', 'settings', 'mongo', 'services/activities']
 };
 
 /**
@@ -35,9 +35,10 @@ module.exports = {
  * @param socketio
  * @param settings
  * @param mongo
+ * @param {ActivitiesService} activitiesService
  * @returns {AgentManager}
  */
-module.exports.factory = function(_, fs, path, JSZip, socketio, settings, mongo) {
+module.exports.factory = function(_, fs, path, JSZip, socketio, settings, mongo, activitiesService) {
     /**
      *
      */
@@ -823,6 +824,11 @@ module.exports.factory = function(_, fs, path, JSZip, socketio, settings, mongo)
                 const sockets = this._browsers[accountId];
 
                 _.forEach(sockets, (socket) => socket.emit('agent:count', {count: agents.length}));
+
+                activitiesService.merge(accountId, {
+                    group: 'agent',
+                    action: '/agent/start'
+                });
             });
         }
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/backend/app/mongo.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/app/mongo.js b/modules/web-console/backend/app/mongo.js
index dd71f3a..2d252b9 100644
--- a/modules/web-console/backend/app/mongo.js
+++ b/modules/web-console/backend/app/mongo.js
@@ -48,6 +48,7 @@ module.exports.factory = function(passportMongo, settings, pluginMongo, mongoose
         company: String,
         country: String,
         lastLogin: Date,
+        lastActivity: Date,
         admin: Boolean,
         token: String,
         resetPasswordToken: String
@@ -59,22 +60,26 @@ module.exports.factory = function(passportMongo, settings, pluginMongo, mongoose
         usernameLowerCase: true
     });
 
+    const transform = (doc, ret) => {
+        return {
+            _id: ret._id,
+            email: ret.email,
+            firstName: ret.firstName,
+            lastName: ret.lastName,
+            company: ret.company,
+            country: ret.country,
+            admin: ret.admin,
+            token: ret.token,
+            lastLogin: ret.lastLogin,
+            lastActivity: ret.lastActivity
+        };
+    };
+
     // Configure transformation to JSON.
-    AccountSchema.set('toJSON', {
-        transform: (doc, ret) => {
-            return {
-                _id: ret._id,
-                email: ret.email,
-                firstName: ret.firstName,
-                lastName: ret.lastName,
-                company: ret.company,
-                country: ret.country,
-                admin: ret.admin,
-                token: ret.token,
-                lastLogin: ret.lastLogin
-            };
-        }
-    });
+    AccountSchema.set('toJSON', { transform });
+
+    // Configure transformation to JSON.
+    AccountSchema.set('toObject', { transform });
 
     result.errCodes = {
         DUPLICATE_KEY_ERROR: 11000,
@@ -902,6 +907,20 @@ module.exports.factory = function(passportMongo, settings, pluginMongo, mongoose
         res.status(err.code || 500).send(err.message);
     };
 
+    // Define Activities schema.
+    const ActivitiesSchema = new Schema({
+        owner: {type: ObjectId, ref: 'Account'},
+        date: Date,
+        group: String,
+        action: String,
+        amount: { type: Number, default: 1 }
+    });
+
+    ActivitiesSchema.index({ owner: 1, group: 1, action: 1, date: 1}, { unique: true });
+
+    // Define Activities model.
+    result.Activities = mongoose.model('Activities', ActivitiesSchema);
+
     // Registering the routes of all plugin modules
     for (const name in pluginMongo) {
         if (pluginMongo.hasOwnProperty(name))

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/backend/app/routes.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/app/routes.js b/modules/web-console/backend/app/routes.js
index 6961173..6b5d052 100644
--- a/modules/web-console/backend/app/routes.js
+++ b/modules/web-console/backend/app/routes.js
@@ -22,11 +22,11 @@
 module.exports = {
     implements: 'routes',
     inject: ['routes/public', 'routes/admin', 'routes/profiles', 'routes/demo', 'routes/clusters', 'routes/domains',
-        'routes/caches', 'routes/igfss', 'routes/notebooks', 'routes/agents', 'routes/configurations']
+        'routes/caches', 'routes/igfss', 'routes/notebooks', 'routes/agents', 'routes/configurations', 'routes/activities']
 };
 
 module.exports.factory = function(publicRoute, adminRoute, profilesRoute, demoRoute,
-    clustersRoute, domainsRoute, cachesRoute, igfssRoute, notebooksRoute, agentsRoute, configurationsRoute) {
+    clustersRoute, domainsRoute, cachesRoute, igfssRoute, notebooksRoute, agentsRoute, configurationsRoute, activitiesRoute) {
     return {
         register: (app) => {
             const _mustAuthenticated = (req, res, next) => {
@@ -59,6 +59,7 @@ module.exports.factory = function(publicRoute, adminRoute, profilesRoute, demoRo
 
             app.use('/notebooks', _mustAuthenticated, notebooksRoute);
             app.use('/agent', _mustAuthenticated, agentsRoute);
+            app.use('/activities', _mustAuthenticated, activitiesRoute);
         }
     };
 };

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/backend/routes/activities.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/routes/activities.js b/modules/web-console/backend/routes/activities.js
new file mode 100644
index 0000000..08c27cf
--- /dev/null
+++ b/modules/web-console/backend/routes/activities.js
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+// Fire me up!
+
+module.exports = {
+    implements: 'routes/activities',
+    inject: ['require(express)', 'services/activities']
+};
+
+/**
+ * @param express
+ * @param {ActivitiesService} activitiesService
+ * @returns {Promise}
+ */
+module.exports.factory = function(express, activitiesService) {
+    return new Promise((factoryResolve) => {
+        const router = new express.Router();
+
+        // Get user activities.
+        router.get('/user/:userId', (req, res) => {
+            activitiesService.listByUser(req.params.userId, req.query)
+                .then(res.api.ok)
+                .catch(res.api.error);
+        });
+
+        // Post user activities to page.
+        router.post('/page', (req, res) => {
+            activitiesService.merge(req.user._id, req.body)
+                .then(res.api.ok)
+                .catch(res.api.error);
+        });
+
+        factoryResolve(router);
+    });
+};

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/backend/routes/admin.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/routes/admin.js b/modules/web-console/backend/routes/admin.js
index 70736d0..5b0896a 100644
--- a/modules/web-console/backend/routes/admin.js
+++ b/modules/web-console/backend/routes/admin.js
@@ -43,7 +43,7 @@ module.exports.factory = function(_, express, settings, mongo, spacesService, ma
          * Get list of user accounts.
          */
         router.post('/list', (req, res) => {
-            usersService.list()
+            usersService.list(req.body)
                 .then(res.api.ok)
                 .catch(res.api.error);
         });

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/backend/routes/agent.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/routes/agent.js b/modules/web-console/backend/routes/agent.js
index 477363f..5ae807b 100644
--- a/modules/web-console/backend/routes/agent.js
+++ b/modules/web-console/backend/routes/agent.js
@@ -21,21 +21,27 @@
 
 module.exports = {
     implements: 'routes/agents',
-    inject: ['require(lodash)', 'require(express)', 'services/agents']
+    inject: ['require(lodash)', 'require(express)', 'services/agents', 'services/activities']
 };
 
 /**
  * @param _
  * @param express
  * @param {AgentsService} agentsService
+ * @param {ActivitiesService} activitiesService
  * @returns {Promise}
  */
-module.exports.factory = function(_, express, agentsService) {
+module.exports.factory = function(_, express, agentsService, activitiesService) {
     return new Promise((resolveFactory) => {
         const router = new express.Router();
 
         /* Get grid topology. */
         router.get('/download/zip', (req, res) => {
+            activitiesService.merge(req.user._id, {
+                group: 'agent',
+                action: '/agent/download'
+            });
+
             agentsService.getArchive(req.origin(), req.user.token)
                 .then(({fileName, buffer}) => {
                     // Set the archive name.

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/backend/routes/public.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/routes/public.js b/modules/web-console/backend/routes/public.js
index 590d395..860e267 100644
--- a/modules/web-console/backend/routes/public.js
+++ b/modules/web-console/backend/routes/public.js
@@ -25,7 +25,6 @@ module.exports = {
 };
 
 /**
- *
  * @param express
  * @param passport
  * @param mongo

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/backend/services/activities.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/services/activities.js b/modules/web-console/backend/services/activities.js
new file mode 100644
index 0000000..7f3a777
--- /dev/null
+++ b/modules/web-console/backend/services/activities.js
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+// Fire me up!
+
+module.exports = {
+    implements: 'services/activities',
+    inject: ['require(lodash)', 'mongo']
+};
+
+/**
+ * @param _
+ * @param mongo
+ * @returns {ActivitiesService}
+ */
+module.exports.factory = (_, mongo) => {
+    class ActivitiesService {
+        /**
+         * Update page activities.
+         *
+         * @param {String} owner - User ID
+         * @param {Object} page - The page
+         * @returns {Promise.<mongo.ObjectId>} that resolve activity
+         */
+        static merge(owner, {action, group}) {
+            mongo.Account.findById(owner)
+                .then((user) => {
+                    user.lastActivity = new Date();
+
+                    return user.save();
+                });
+
+            const date = new Date();
+
+            date.setDate(1);
+            date.setHours(0, 0, 0, 0);
+
+            return mongo.Activities.findOne({owner, action, date}).exec()
+                .then((activity) => {
+                    if (activity) {
+                        activity.amount++;
+
+                        return activity.save();
+                    }
+
+                    return mongo.Activities.create({owner, action, group, date});
+                });
+        }
+
+        /**
+         * Get user activities
+         * @param {String} owner - User ID
+         * @returns {Promise.<mongo.ObjectId>} that resolve activities
+         */
+        static listByUser(owner, {startDate, endDate}) {
+            const $match = {owner};
+
+            if (startDate)
+                $match.date = {$gte: new Date(startDate)};
+
+            if (endDate) {
+                $match.date = $match.date || {};
+                $match.date.$lt = new Date(endDate);
+            }
+
+            return mongo.Activities.find($match);
+        }
+
+        static total({startDate, endDate}) {
+            const $match = {};
+
+            if (startDate)
+                $match.date = {$gte: new Date(startDate)};
+
+            if (endDate) {
+                $match.date = $match.date || {};
+                $match.date.$lt = new Date(endDate);
+            }
+
+            return mongo.Activities.aggregate([
+                {$match},
+                {$group: {
+                    _id: {owner: '$owner', group: '$group'},
+                    amount: {$sum: '$amount'}
+                }}
+            ]).exec().then((data) => {
+                return _.reduce(data, (acc, { _id, amount }) => {
+                    const {owner, group} = _id;
+                    acc[owner] = _.merge(acc[owner] || {}, { [group]: amount });
+                    return acc;
+                }, {});
+            });
+        }
+
+        static detail({startDate, endDate}) {
+            const $match = { };
+
+            if (startDate)
+                $match.date = {$gte: new Date(startDate)};
+
+            if (endDate) {
+                $match.date = $match.date || {};
+                $match.date.$lt = new Date(endDate);
+            }
+
+            return mongo.Activities.aggregate([
+                {$match},
+                {$group: {_id: {owner: '$owner', action: '$action'}, total: {$sum: '$amount'}}}
+            ]).exec().then((data) => {
+                return _.reduce(data, (acc, { _id, total }) => {
+                    const {owner, action} = _id;
+                    acc[owner] = _.merge(acc[owner] || {}, { [action]: total });
+                    return acc;
+                }, {});
+            });
+        }
+    }
+
+    return ActivitiesService;
+};

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/backend/services/users.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/services/users.js b/modules/web-console/backend/services/users.js
index 8058b25..2dd603f 100644
--- a/modules/web-console/backend/services/users.js
+++ b/modules/web-console/backend/services/users.js
@@ -21,7 +21,7 @@
 
 module.exports = {
     implements: 'services/users',
-    inject: ['require(lodash)', 'mongo', 'settings', 'services/spaces', 'services/mails', 'agent-manager', 'errors']
+    inject: ['require(lodash)', 'mongo', 'settings', 'services/spaces', 'services/mails', 'services/activities', 'agent-manager', 'errors']
 };
 
 /**
@@ -30,11 +30,12 @@ module.exports = {
  * @param settings
  * @param {SpacesService} spacesService
  * @param {MailsService} mailsService
+ * @param {ActivitiesService} activitiesService
  * @param agentMgr
  * @param errors
  * @returns {UsersService}
  */
-module.exports.factory = (_, mongo, settings, spacesService, mailsService, agentMgr, errors) => {
+module.exports.factory = (_, mongo, settings, spacesService, mailsService, activitiesService, agentMgr, errors) => {
     const _randomString = () => {
         const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
         const possibleLen = possible.length;
@@ -143,7 +144,7 @@ module.exports.factory = (_, mongo, settings, spacesService, mailsService, agent
          * Get list of user accounts and summary information.
          * @returns {mongo.Account[]} - returns all accounts with counters object
          */
-        static list() {
+        static list(params) {
             return Promise.all([
                 mongo.Space.aggregate([
                     {$match: {demo: false}},
@@ -161,13 +162,17 @@ module.exports.factory = (_, mongo, settings, spacesService, mailsService, agent
                         }
                     }
                 ]).exec(),
+                activitiesService.total(params),
+                activitiesService.detail(params),
                 mongo.Account.find({}).sort('firstName lastName').lean().exec()
             ])
-                .then(([counters, users]) => {
+                .then(([counters, activitiesTotal, activitiesDetail, users]) => {
                     const countersMap = _.keyBy(counters, 'owner');
 
                     _.forEach(users, (user) => {
                         user.counters = _.omit(countersMap[user._id], '_id', 'owner');
+                        user.activitiesTotal = activitiesTotal[user._id];
+                        user.activitiesDetail = activitiesDetail[user._id];
                     });
 
                     return users;

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/app.config.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/app.config.js b/modules/web-console/frontend/app/app.config.js
index 7416ce9..0e85711 100644
--- a/modules/web-console/frontend/app/app.config.js
+++ b/modules/web-console/frontend/app/app.config.js
@@ -94,3 +94,12 @@ igniteConsoleCfg.config(['$dropdownProvider', ($dropdownProvider) => {
         templateUrl: 'templates/dropdown.html'
     });
 }]);
+
+// AngularStrap dropdowns () configuration.
+igniteConsoleCfg.config(['$datepickerProvider', ($datepickerProvider) => {
+    angular.extend($datepickerProvider.defaults, {
+        autoclose: true,
+        iconLeft: 'icon-datepicker-left',
+        iconRight: 'icon-datepicker-right'
+    });
+}]);

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/app.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/app.js b/modules/web-console/frontend/app/app.js
index 4ecd9b5..9958cb5 100644
--- a/modules/web-console/frontend/app/app.js
+++ b/modules/web-console/frontend/app/app.js
@@ -16,7 +16,9 @@
  */
 
 import '../public/stylesheets/style.scss';
-import '../app/directives/ui-grid-settings/ui-grid-settings.scss';
+import '../app/components/ui-grid-header/ui-grid-header.scss';
+import '../app/components/ui-grid-settings/ui-grid-settings.scss';
+import '../app/components/form-field-datepicker/form-field-datepicker.scss';
 import './helpers/jade/mixins.jade';
 
 import './app.config';
@@ -25,10 +27,10 @@ import './decorator/select';
 import './decorator/tooltip';
 
 import './modules/form/form.module';
-import './modules/agent/agent.module.js';
+import './modules/agent/agent.module';
 import './modules/sql/sql.module';
 import './modules/nodes/nodes.module';
-import './modules/Demo/Demo.module.js';
+import './modules/demo/Demo.module';
 
 import './modules/states/signin.state';
 import './modules/states/logout.state';
@@ -39,6 +41,7 @@ import './modules/states/admin.state';
 import './modules/states/errors.state';
 
 // ignite:modules
+import './core';
 import './modules/user/user.module';
 import './modules/branding/branding.module';
 import './modules/navbar/navbar.module';
@@ -50,6 +53,9 @@ import './modules/socket.module';
 import './modules/loading/loading.module';
 // endignite
 
+// Data
+import i18n from './data/i18n';
+
 // Directives.
 import igniteAutoFocus from './directives/auto-focus.directive.js';
 import igniteBsAffixUpdate from './directives/bs-affix-update.directive';
@@ -98,9 +104,9 @@ import defaultName from './filters/default-name.filter';
 import domainsValidation from './filters/domainsValidation.filter';
 import duration from './filters/duration.filter';
 import hasPojo from './filters/hasPojo.filter';
+import uiGridSubcategories from './filters/uiGridSubcategories.filter';
 
 // Controllers
-import admin from 'controllers/admin-controller';
 import caches from 'controllers/caches-controller';
 import clusters from 'controllers/clusters-controller';
 import domains from 'controllers/domains-controller';
@@ -109,6 +115,10 @@ import profile from 'controllers/profile-controller';
 import auth from './controllers/auth.controller';
 import resetPassword from './controllers/reset-password.controller';
 
+// Components
+import igniteListOfRegisteredUsers from './components/list-of-registered-users';
+import IgniteActivitiesUserDialog from './components/activities-user-dialog';
+
 // Inject external modules.
 import 'ignite_modules_temp/index';
 
@@ -129,6 +139,7 @@ angular
     'nvd3',
     'smart-table',
     'treeControl',
+    'pascalprecht.translate',
     'ui.grid',
     'ui.grid.saveState',
     'ui.grid.selection',
@@ -136,6 +147,7 @@ angular
     'ui.grid.autoResize',
     'ui.grid.exporter',
     // Base modules.
+    'ignite-console.core',
     'ignite-console.ace',
     'ignite-console.Form',
     'ignite-console.user',
@@ -186,6 +198,7 @@ angular
 .directive(...igniteRetainSelection)
 .directive('igniteOnFocusOut', igniteOnFocusOut)
 .directive('igniteRestoreInputFocus', igniteRestoreInputFocus)
+.directive('igniteListOfRegisteredUsers', igniteListOfRegisteredUsers)
 // Services.
 .service('IgniteErrorPopover', ErrorPopover)
 .service('JavaTypes', JavaTypes)
@@ -204,8 +217,8 @@ angular
 .service(...FormUtils)
 .service(...LegacyUtils)
 .service(...UnsavedChangesGuard)
+.service('IgniteActivitiesUserDialog', IgniteActivitiesUserDialog)
 // Controllers.
-.controller(...admin)
 .controller(...auth)
 .controller(...resetPassword)
 .controller(...caches)
@@ -219,7 +232,11 @@ angular
 .filter(...domainsValidation)
 .filter(...duration)
 .filter(...hasPojo)
-.config(['$stateProvider', '$locationProvider', '$urlRouterProvider', ($stateProvider, $locationProvider, $urlRouterProvider) => {
+.filter('uiGridSubcategories', uiGridSubcategories)
+.config(['$translateProvider', '$stateProvider', '$locationProvider', '$urlRouterProvider', ($translateProvider, $stateProvider, $locationProvider, $urlRouterProvider) => {
+    $translateProvider.translations('en', i18n);
+    $translateProvider.preferredLanguage('en');
+
     // Set up the states.
     $stateProvider
         .state('base', {

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/components/activities-user-dialog/activities-user-dialog.controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/activities-user-dialog/activities-user-dialog.controller.js b/modules/web-console/frontend/app/components/activities-user-dialog/activities-user-dialog.controller.js
new file mode 100644
index 0000000..46853b2
--- /dev/null
+++ b/modules/web-console/frontend/app/components/activities-user-dialog/activities-user-dialog.controller.js
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const COLUMNS_DEFS = [
+    {displayName: 'Action', field: 'action', minWidth: 65 },
+    {displayName: 'Description', field: 'title', minWidth: 65 },
+    {displayName: 'Visited', field: 'amount', minWidth: 65 }
+];
+
+export default class ActivitiesCtrl {
+    static $inject = ['$state', 'user', 'params', 'IgniteActivitiesData'];
+
+    constructor($state, user, params, ActivitiesData) {
+        const $ctrl = this;
+        const userId = user._id;
+
+        $ctrl.user = user;
+
+        $ctrl.gridOptions = {
+            data: [],
+            columnVirtualizationThreshold: 30,
+            columnDefs: COLUMNS_DEFS,
+            categories: [
+                {name: 'Action', visible: true, selectable: true},
+                {name: 'Description', visible: true, selectable: true},
+                {name: 'Visited', visible: true, selectable: true}
+            ],
+            enableRowSelection: false,
+            enableRowHeaderSelection: false,
+            enableColumnMenus: false,
+            multiSelect: false,
+            modifierKeysToMultiSelect: true,
+            noUnselect: true,
+            flatEntityAccess: true,
+            fastWatch: true,
+            onRegisterApi: (api) => {
+                $ctrl.gridApi = api;
+            }
+        };
+
+        ActivitiesData.listByUser(userId, params)
+            .then((data) => {
+                $ctrl.data = data;
+            });
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/components/activities-user-dialog/activities-user-dialog.jade
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/activities-user-dialog/activities-user-dialog.jade b/modules/web-console/frontend/app/components/activities-user-dialog/activities-user-dialog.jade
new file mode 100644
index 0000000..2c55ebd
--- /dev/null
+++ b/modules/web-console/frontend/app/components/activities-user-dialog/activities-user-dialog.jade
@@ -0,0 +1,36 @@
+//-
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+.modal(tabindex='-1' role='dialog')
+    .modal-dialog
+        .modal-content
+            .modal-header
+                h4.modal-title 
+                    i.fa.fa-info-circle
+                    | Activities: {{ ctrl.user.userName }}
+            .modal-body.modal-body-with-scroll(id='activities-user-dialog')
+                table.table.table-striped.table-bordered.table-hover(scrollable-container='#activities-user-dialog' st-table='displayedRows' st-safe-src='ctrl.data')
+                    thead
+                        th.text-center(st-sort='title') Description
+                        th.text-center(st-sort='action') Action
+                        th.text-center(st-sort='amount') Visited
+                    tbody
+                        tr(ng-repeat='row in displayedRows')
+                            td.text-left {{ row.action | translate }}
+                            td.text-left {{ row.action }}
+                            td.text-left {{ row.amount }}
+            .modal-footer
+                button.btn.btn-primary(id='confirm-btn-confirm' ng-click='$hide()') Close

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/components/activities-user-dialog/index.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/activities-user-dialog/index.js b/modules/web-console/frontend/app/components/activities-user-dialog/index.js
new file mode 100644
index 0000000..03d3585
--- /dev/null
+++ b/modules/web-console/frontend/app/components/activities-user-dialog/index.js
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ import controller from './activities-user-dialog.controller';
+ import templateUrl from './activities-user-dialog.jade';
+
+ export default ['$modal', ($modal) => ({ show = true, user, params }) => {
+     const ActivitiesUserDialog = $modal({
+         templateUrl,
+         show,
+         resolve: {
+             user: () => user,
+             params: () => params
+         },
+         placement: 'center',
+         controller,
+         controllerAs: 'ctrl'
+     });
+
+     return ActivitiesUserDialog.$promise
+         .then(() => ActivitiesUserDialog);
+ }];

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/components/form-field-datepicker/form-field-datepicker.jade
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/form-field-datepicker/form-field-datepicker.jade b/modules/web-console/frontend/app/components/form-field-datepicker/form-field-datepicker.jade
new file mode 100644
index 0000000..6792977
--- /dev/null
+++ b/modules/web-console/frontend/app/components/form-field-datepicker/form-field-datepicker.jade
@@ -0,0 +1,55 @@
+//-
+    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.
+
+mixin ignite-form-field-datepicker(label, model, name, disabled, required, placeholder, tip)
+    mixin form-field-input()
+        input.form-control(
+            id='{{ #{name} }}Input'
+            name='{{ #{name} }}'
+
+            placeholder=placeholder
+            
+            data-ng-model=model
+
+            data-ng-required=required && '#{required}'
+            data-ng-disabled=disabled && '#{disabled}'
+
+            bs-datepicker
+            data-date-format='MMM yyyy' 
+            data-start-view='1' 
+            data-min-view='1' 
+            data-max-date='today'
+
+            data-container='body > .wrapper'
+
+            tabindex='0'
+
+            onkeydown="return false"
+
+            data-ignite-form-panel-field=''
+        )&attributes(attributes.attributes)
+
+    .ignite-form-field
+        +ignite-form-field__label(label, name, required)
+        .ignite-form-field__control
+            if tip
+                i.tipField.icon-help(bs-tooltip='' data-title=tip)
+
+            if block
+                block
+
+            .input-tip
+                +form-field-input(attributes=attributes)

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/components/form-field-datepicker/form-field-datepicker.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/form-field-datepicker/form-field-datepicker.scss b/modules/web-console/frontend/app/components/form-field-datepicker/form-field-datepicker.scss
new file mode 100644
index 0000000..0f6fe6e
--- /dev/null
+++ b/modules/web-console/frontend/app/components/form-field-datepicker/form-field-datepicker.scss
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+.datepicker.dropdown-menu tbody button {
+    height: 100%;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/components/list-of-registered-users/index.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/list-of-registered-users/index.js b/modules/web-console/frontend/app/components/list-of-registered-users/index.js
new file mode 100644
index 0000000..32a34f4
--- /dev/null
+++ b/modules/web-console/frontend/app/components/list-of-registered-users/index.js
@@ -0,0 +1,28 @@
+/*
+ * 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 './list-of-registered-users.jade';
+import controller from './list-of-registered-users.controller';
+
+export default [() => {
+    return {
+        scope: true,
+        templateUrl,
+        controller,
+        controllerAs: '$ctrl'
+    };
+}];

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.categories.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.categories.js b/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.categories.js
new file mode 100644
index 0000000..95edf8b
--- /dev/null
+++ b/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.categories.js
@@ -0,0 +1,30 @@
+/*
+ * 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 [
+    {name: 'Actions', visible: true, selectable: true},
+    {name: 'User', visible: true, selectable: true},
+    {name: 'Email', visible: true, selectable: true},
+    {name: 'Company', visible: true, selectable: true},
+    {name: 'Country', visible: true, selectable: true},
+    {name: 'Last login', visible: false, selectable: true},
+    {name: 'Last activity', visible: true, selectable: true},
+    {name: 'Configurations', visible: false, selectable: true},
+    {name: 'Total activities', visible: true, selectable: true},
+    {name: 'Configuration\'s activities', visible: false, selectable: true},
+    {name: 'Queries\' activities', visible: false, selectable: true}
+];

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.column-defs.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.column-defs.js b/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.column-defs.js
new file mode 100644
index 0000000..61e1bd8
--- /dev/null
+++ b/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.column-defs.js
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const ICON_SORT = '<span ui-grid-one-bind-id-grid="col.uid + \'-sortdir-text\'" ui-grid-visible="col.sort.direction" aria-label="Sort Descending"><i ng-class="{ \'ui-grid-icon-up-dir\': col.sort.direction == asc, \'ui-grid-icon-down-dir\': col.sort.direction == desc, \'ui-grid-icon-blank\': !col.sort.direction }" title="" aria-hidden="true"></i></span>';
+
+const USER_TEMPLATE = '<div class="ui-grid-cell-contents"><i class="pull-left" ng-class="row.entity.admin ? \'icon-admin\' : \'icon-user\'"></i>{{ COL_FIELD }}</div>';
+
+const CLUSTER_HEADER_TEMPLATE = `<div class='ui-grid-cell-contents' bs-tooltip data-title='{{ col.headerTooltip(col) }}' data-placement='top'><i class='fa fa-sitemap'></i>${ICON_SORT}</div>`;
+const MODEL_HEADER_TEMPLATE = `<div class='ui-grid-cell-contents' bs-tooltip data-title='{{ col.headerTooltip(col) }}' data-placement='top'><i class='fa fa-object-group'></i>${ICON_SORT}</div>`;
+const CACHE_HEADER_TEMPLATE = `<div class='ui-grid-cell-contents' bs-tooltip data-title='{{ col.headerTooltip(col) }}' data-placement='top'><i class='fa fa-database'></i>${ICON_SORT}</div>`;
+const IGFS_HEADER_TEMPLATE = `<div class='ui-grid-cell-contents' bs-tooltip data-title='{{ col.headerTooltip(col) }}' data-placement='top'><i class='fa fa-folder-o'></i>${ICON_SORT}</div>`;
+
+const ACTIONS_TEMPLATE = `
+<div class='text-center ui-grid-cell-actions'>
+    <a class='btn btn-default dropdown-toggle' bs-dropdown='' data-placement='bottom-right' data-container='.panel'>
+        <i class='fa fa-gear'></i>&nbsp;
+        <span class='caret'></span>
+    </a>
+    <ul class='dropdown-menu' role='menu'>
+        <li ng-show='row.entity._id != $root.user._id'>
+            <a ng-click='grid.api.becomeUser(row.entity)'>Become this user</a>
+        </li>
+        <li ng-show='row.entity._id != $root.user._id'>
+            <a ng-click='grid.api.toggleAdmin(row.entity)' ng-if='row.entity.admin && row.entity._id !== $root.user._id'>Revoke admin</a>
+            <a ng-click='grid.api.toggleAdmin(row.entity)' ng-if='!row.entity.admin && row.entity._id !== $root.user._id'>Grant admin</a>
+        </li>
+        <li ng-show='row.entity._id != $root.user._id'>
+            <a ng-click='grid.api.removeUser(row.entity)'>Remove user</a>
+        </li>
+        <li>
+            <a ng-click='grid.api.showActivities(row.entity)'>Activity detail</a>
+        </li>
+</div>`;
+
+const EMAIL_TEMPLATE = '<div class="ui-grid-cell-contents"><a ng-href="mailto:{{ COL_FIELD }}">{{ COL_FIELD }}</a></div>';
+
+export default [
+    {displayName: 'Actions', categoryDisplayName: 'Actions', cellTemplate: ACTIONS_TEMPLATE, field: 'test', minWidth: 70, width: 70, enableFiltering: false, enableSorting: false, pinnedLeft: true},
+    {displayName: 'User', categoryDisplayName: 'User', field: 'userName', cellTemplate: USER_TEMPLATE, minWidth: 160, enableFiltering: true, filter: { placeholder: 'Filter by name...' }, pinnedLeft: true},
+    {displayName: 'Email', categoryDisplayName: 'Email', field: 'email', cellTemplate: EMAIL_TEMPLATE, minWidth: 160, enableFiltering: true, filter: { placeholder: 'Filter by email...' }},
+    {displayName: 'Company', categoryDisplayName: 'Company', field: 'company', minWidth: 160, enableFiltering: true},
+    {displayName: 'Country', categoryDisplayName: 'Country', field: 'countryCode', minWidth: 80, enableFiltering: true},
+    {displayName: 'Last login', categoryDisplayName: 'Last login', field: 'lastLogin', cellFilter: 'date:"M/d/yy HH:mm"', minWidth: 105, width: 105, enableFiltering: false, visible: false},
+    {displayName: 'Last activity', categoryDisplayName: 'Last activity', field: 'lastActivity', cellFilter: 'date:"M/d/yy HH:mm"', minWidth: 105, width: 105, enableFiltering: false, visible: true, sort: { direction: 'desc', priority: 0 }},
+    // Configurations
+    {displayName: 'Clusters count', categoryDisplayName: 'Configurations', headerCellTemplate: CLUSTER_HEADER_TEMPLATE, field: 'counters.clusters', type: 'number', headerTooltip: 'Clusters count', minWidth: 50, width: 50, enableFiltering: false, visible: false},
+    {displayName: 'Models count', categoryDisplayName: 'Configurations', headerCellTemplate: MODEL_HEADER_TEMPLATE, field: 'counters.models', type: 'number', headerTooltip: 'Models count', minWidth: 50, width: 50, enableFiltering: false, visible: false},
+    {displayName: 'Caches count', categoryDisplayName: 'Configurations', headerCellTemplate: CACHE_HEADER_TEMPLATE, field: 'counters.caches', type: 'number', headerTooltip: 'Caches count', minWidth: 50, width: 50, enableFiltering: false, visible: false},
+    {displayName: 'IGFS count', categoryDisplayName: 'Configurations', headerCellTemplate: IGFS_HEADER_TEMPLATE, field: 'counters.igfs', type: 'number', headerTooltip: 'IGFS count', minWidth: 50, width: 50, enableFiltering: false, visible: false},
+    // Activities Total
+    {displayName: 'Cfg', categoryDisplayName: 'Total activities', field: 'activitiesTotal["configuration"] || 0', type: 'number', headerTooltip: 'Configuration', minWidth: 50, width: 50, enableFiltering: false},
+    {displayName: 'Qry', categoryDisplayName: 'Total activities', field: 'activitiesTotal["queries"] || 0', type: 'number', headerTooltip: 'Queries', minWidth: 50, width: 50, enableFiltering: false},
+    {displayName: 'Demo', categoryDisplayName: 'Total activities', field: 'activitiesTotal["demo"] || 0', type: 'number', headerTooltip: 'Demo', minWidth: 50, width: 50, enableFiltering: false},
+    {displayName: 'AD', categoryDisplayName: 'Total activities', field: 'activitiesDetail["/agent/download"] || 0', type: 'number', headerTooltip: 'Agent Download', minWidth: 50, width: 50, enableFiltering: false},
+    {displayName: 'AS', categoryDisplayName: 'Total activities', field: 'activitiesDetail["/agent/start"] || 0', type: 'number', headerTooltip: 'Agent Start', minWidth: 50, width: 50, enableFiltering: false},
+    // Activities Configuration
+    {displayName: 'Clusters', categoryDisplayName: 'Configuration\'s activities', field: 'activitiesDetail["/configuration/clusters"] || 0', type: 'number', headerTooltip: 'Configuration Clusters', minWidth: 50, width: 80, enableFiltering: false, visible: false},
+    {displayName: 'Model', categoryDisplayName: 'Configuration\'s activities', field: 'activitiesDetail["/configuration/domains"] || 0', type: 'number', headerTooltip: 'Configuration Model', minWidth: 50, width: 80, enableFiltering: false, visible: false},
+    {displayName: 'Caches', categoryDisplayName: 'Configuration\'s activities', field: 'activitiesDetail["/configuration/caches"] || 0', type: 'number', headerTooltip: 'Configuration Caches', minWidth: 50, width: 80, enableFiltering: false, visible: false},
+    {displayName: 'IGFS', categoryDisplayName: 'Configuration\'s activities', field: 'activitiesDetail["/configuration/igfs"] || 0', type: 'number', headerTooltip: 'Configuration IGFS', minWidth: 50, width: 80, enableFiltering: false, visible: false},
+    {displayName: 'Summary', categoryDisplayName: 'Configuration\'s activities', field: 'activitiesDetail["/configuration/summary"] || 0', type: 'number', headerTooltip: 'Configuration Summary', minWidth: 50, width: 80, enableFiltering: false, visible: false},
+    // Activities Queries
+    {displayName: 'Execute', categoryDisplayName: 'Queries\' activities', field: 'activitiesDetail["/queries/execute"] || 0', type: 'number', headerTooltip: 'Query execute', minWidth: 50, width: 80, enableFiltering: false, visible: false},
+    {displayName: 'Explain', categoryDisplayName: 'Queries\' activities', field: 'activitiesDetail["/queries/explain"] || 0', type: 'number', headerTooltip: 'Query explain', minWidth: 50, width: 80, enableFiltering: false, visible: false},
+    {displayName: 'Scan', categoryDisplayName: 'Queries\' activities', field: 'activitiesDetail["/queries/scan"] || 0', type: 'number', headerTooltip: 'Scan', minWidth: 50, width: 80, enableFiltering: false, visible: false}
+];

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.controller.js b/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.controller.js
new file mode 100644
index 0000000..19f7921
--- /dev/null
+++ b/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.controller.js
@@ -0,0 +1,207 @@
+/*
+ * 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 headerTemplate from 'app/components/ui-grid-header/ui-grid-header.jade';
+
+import columnDefs from './list-of-registered-users.column-defs';
+import categories from './list-of-registered-users.categories';
+
+export default class IgniteListOfRegisteredUsersCtrl {
+    static $inject = ['$scope', '$state', '$templateCache', 'User', 'uiGridConstants', 'IgniteAdminData', 'IgniteNotebookData', 'IgniteConfirm', 'IgniteActivitiesUserDialog'];
+
+    constructor($scope, $state, $templateCache, User, uiGridConstants, AdminData, NotebookData, Confirm, ActivitiesUserDialog) {
+        const $ctrl = this;
+
+        const companySelectOptions = [];
+        const countrySelectOptions = [];
+
+        $ctrl.params = {
+            startDate: new Date()
+        };
+
+        $ctrl.params.startDate.setDate(1);
+        $ctrl.params.startDate.setHours(0, 0, 0, 0);
+
+        const columnCompany = _.find(columnDefs, { displayName: 'Company' });
+        const columnCountry = _.find(columnDefs, { displayName: 'Country' });
+
+        columnCompany.filter = {
+            selectOptions: companySelectOptions,
+            type: uiGridConstants.filter.SELECT,
+            condition: uiGridConstants.filter.EXACT
+        };
+
+        columnCountry.filter = {
+            selectOptions: countrySelectOptions,
+            type: uiGridConstants.filter.SELECT,
+            condition: uiGridConstants.filter.EXACT
+        };
+
+        const becomeUser = (user) => {
+            AdminData.becomeUser(user._id)
+                .then(() => User.load())
+                .then(() => $state.go('base.configuration.clusters'))
+                .then(() => NotebookData.load());
+        };
+
+        const removeUser = (user) => {
+            Confirm.confirm(`Are you sure you want to remove user: "${user.userName}"?`)
+                .then(() => AdminData.removeUser(user))
+                .then(() => {
+                    const i = _.findIndex($ctrl.gridOptions.data, (u) => u._id === user._id);
+
+                    if (i >= 0)
+                        $ctrl.gridOptions.data.splice(i, 1);
+                })
+                .then(() => $ctrl.adjustHeight($ctrl.gridOptions.data.length));
+        };
+
+        const toggleAdmin = (user) => {
+            if (user.adminChanging)
+                return;
+
+            user.adminChanging = true;
+
+            AdminData.toggleAdmin(user)
+                .then(() => user.admin = !user.admin)
+                .finally(() => user.adminChanging = false);
+        };
+
+        const showActivities = (user) => {
+            return new ActivitiesUserDialog({ user, params: $ctrl.params });
+        };
+
+        $ctrl.gridOptions = {
+            data: [],
+            columnVirtualizationThreshold: 30,
+            columnDefs,
+            categories,
+            headerTemplate: $templateCache.get(headerTemplate),
+            enableFiltering: true,
+            enableRowSelection: false,
+            enableRowHeaderSelection: false,
+            enableColumnMenus: false,
+            multiSelect: false,
+            modifierKeysToMultiSelect: true,
+            noUnselect: true,
+            fastWatch: true,
+            onRegisterApi: (api) => {
+                $ctrl.gridApi = api;
+
+                api.becomeUser = becomeUser;
+                api.removeUser = removeUser;
+                api.toggleAdmin = toggleAdmin;
+                api.showActivities = showActivities;
+            }
+        };
+
+        const usersToFilterOptions = (column) => {
+            return _.sortBy(
+                _.map(
+                    _.groupBy($ctrl.gridOptions.data, (usr) => {
+                        const fld = usr[column];
+
+                        return _.isNil(fld) ? fld : fld.toUpperCase();
+                    }),
+                    (arr, value) => ({label: `${_.head(arr)[column] || 'Not set'} (${arr.length})`, value})
+                ),
+                'value');
+        };
+
+        /**
+         * @param {{startDate: Date, endDate: Date}} params
+         */
+        const reloadUsers = (params) => {
+            AdminData.loadUsers(params)
+                .then((data) => $ctrl.gridOptions.data = data)
+                .then((data) => {
+                    companySelectOptions.push(...usersToFilterOptions('company'));
+                    countrySelectOptions.push(...usersToFilterOptions('countryCode'));
+
+                    this.gridApi.grid.refresh();
+
+                    return data;
+                })
+                .then((data) => $ctrl.adjustHeight(data.length));
+        };
+
+        $scope.$watch(() => $ctrl.params.startDate, () => {
+            const endDate = new Date($ctrl.params.startDate);
+
+            endDate.setMonth(endDate.getMonth() + 1);
+
+            $ctrl.params.endDate = endDate;
+
+            reloadUsers($ctrl.params);
+        });
+    }
+
+    adjustHeight(rows) {
+        const height = Math.min(rows, 20) * 30 + 75;
+
+        // Remove header height.
+        this.gridApi.grid.element.css('height', height + 'px');
+
+        this.gridApi.core.handleWindowResize();
+    }
+
+    _enableColumns(_categories, visible) {
+        _.forEach(_categories, (cat) => {
+            cat.visible = visible;
+
+            _.forEach(this.gridOptions.columnDefs, (col) => {
+                if (col.categoryDisplayName === cat.name)
+                    col.visible = visible;
+            });
+        });
+
+        // Workaround for this.gridApi.grid.refresh() didn't return promise.
+        this.gridApi.grid.processColumnsProcessors(this.gridApi.grid.columns)
+            .then((renderableColumns) => this.gridApi.grid.setVisibleColumns(renderableColumns))
+            .then(() => this.gridApi.grid.redrawInPlace())
+            .then(() => this.gridApi.grid.refreshCanvas(true))
+            .then(() => {
+                if (visible) {
+                    const categoryDisplayName = _.last(_categories).name;
+
+                    const col = _.findLast(this.gridOptions.columnDefs, {categoryDisplayName});
+
+                    this.gridApi.grid.scrollTo(null, col);
+                }
+            });
+    }
+
+    _selectableColumns() {
+        return _.filter(this.gridOptions.categories, (cat) => cat.selectable);
+    }
+
+    toggleColumns(category, visible) {
+        this._enableColumns([category], visible);
+    }
+
+    selectAllColumns() {
+        this._enableColumns(this._selectableColumns(), true);
+    }
+
+    clearAllColumns() {
+        this._enableColumns(this._selectableColumns(), false);
+    }
+
+    exportCsv() {
+        this.gridApi.exporter.csvExport('all', 'visible');
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.jade
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.jade b/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.jade
new file mode 100644
index 0000000..efed9c0
--- /dev/null
+++ b/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.jade
@@ -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.
+
+include /app/helpers/jade/mixins.jade
+include /app/components/form-field-datepicker/form-field-datepicker.jade
+
+mixin grid-settings()
+    i.fa.fa-bars(data-animation='am-flip-x' bs-dropdown='' aria-haspopup='true' aria-expanded='expanded' data-auto-close='1' data-trigger='click')
+    ul.select.dropdown-menu(role='menu')
+        li(ng-repeat='item in $ctrl.gridOptions.categories|filter:{selectable:true}')
+            a(ng-click='$ctrl.toggleColumns(item, !item.visible)')
+                i.fa.fa-check-square-o.pull-left(ng-if='item.visible')
+                i.fa.fa-square-o.pull-left(ng-if='!item.visible')
+                span {{::item.name}}
+        li.divider
+        li
+            a(ng-click='$ctrl.selectAllColumns()') Select all
+        li
+            a(ng-click='$ctrl.clearAllColumns()') Clear all
+        li.divider
+        li
+            a(ng-click='$hide()') Close
+
+.panel.panel-default
+    .panel-heading.ui-grid-settings
+        +grid-settings
+        label Total users: 
+            strong {{ $ctrl.gridOptions.data.length }}&nbsp;&nbsp;&nbsp;
+        label Showing users: 
+            strong {{ $ctrl.gridApi.grid.getVisibleRows().length }}
+            sub(ng-show='users.length === $ctrl.gridApi.grid.getVisibleRows().length') all
+        div.ui-grid-settings-dateperiod
+            form(ng-form=form novalidate)
+                -var form = 'admin'
+
+                +ignite-form-field-datepicker('Period:', '$ctrl.params.startDate', '"period"')
+                
+                button.btn.btn-primary(ng-click='$ctrl.exportCsv()' bs-tooltip data-title='Export table to csv') Export
+
+    .panel-collapse
+        .grid.ui-grid--ignite(ui-grid='$ctrl.gridOptions' ui-grid-resize-columns ui-grid-selection ui-grid-exporter ui-grid-pinning)

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/components/ui-grid-header/ui-grid-header.jade
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/ui-grid-header/ui-grid-header.jade b/modules/web-console/frontend/app/components/ui-grid-header/ui-grid-header.jade
new file mode 100644
index 0000000..7e44d94
--- /dev/null
+++ b/modules/web-console/frontend/app/components/ui-grid-header/ui-grid-header.jade
@@ -0,0 +1,27 @@
+//-
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+.ui-grid-header.ui-grid-header--subcategories(role='rowgroup')
+    .ui-grid-top-panel
+        .ui-grid-header-viewport
+            .ui-grid-header-canvas
+                .ui-grid-header-cell-wrapper(ng-style='colContainer.headerCellWrapperStyle()')
+                    .ui-grid-header-cell-row(role='row')
+                        .ui-grid-header-span.ui-grid-header-cell.ui-grid-clearfix(ng-repeat='cat in grid.options.categories')
+                            div(ng-show='(colContainer.renderedColumns|uiGridSubcategories:cat.name).length > 1')
+                                .ui-grid-cell-contents {{ cat.name }}
+                            .ui-grid-header-cell-row
+                                .ui-grid-header-cell.ui-grid-clearfix(ng-repeat='col in (colContainer.renderedColumns|uiGridSubcategories:cat.name) track by col.uid' ui-grid-header-cell='' col='col' render-index='$index')

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/components/ui-grid-header/ui-grid-header.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/ui-grid-header/ui-grid-header.scss b/modules/web-console/frontend/app/components/ui-grid-header/ui-grid-header.scss
new file mode 100644
index 0000000..c390504
--- /dev/null
+++ b/modules/web-console/frontend/app/components/ui-grid-header/ui-grid-header.scss
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.ui-grid-header--subcategories {
+    .ui-grid-row:nth-child(even) .ui-grid-cell.cell-total {
+        background-color: rgba(102,175,233,.6);
+    }
+
+    .ui-grid-row:nth-child(odd) .ui-grid-cell.cell-total {
+        background-color: rgba(102,175,233,.3);
+    }
+
+    .ui-grid-header-cell-row {
+        height: 30px;
+    }
+
+    .ui-grid-header-cell [role="columnheader"] {
+        display: flex;
+        
+        flex-wrap: wrap;
+        align-items: center;
+        justify-content: center;
+
+        height: 100%;
+
+        & > div {
+            flex: 1 100%;
+            height: auto;
+        }
+
+        & > div[ui-grid-filter] {
+            flex: auto;
+        }
+    }
+
+    .ui-grid-header-span {
+        position: relative;
+        border-right: 0;
+
+        .ng-hide + .ui-grid-header-cell-row .ui-grid-header-cell {
+            height: 58px;
+        }
+
+        .ng-hide + .ui-grid-header-cell-row .ui-grid-cell-contents {
+            padding: 5px 5px;
+        }
+
+        .ui-grid-column-resizer.right {
+            top: -100px;
+        }
+        .ng-hide + .ui-grid-header-cell-row .ui-grid-column-resizer.right {
+            bottom: -100px;
+        }
+
+        &.ui-grid-header-cell .ui-grid-header-cell .ui-grid-column-resizer.right {
+            border-right-width: 0;
+        }
+        &.ui-grid-header-cell .ui-grid-header-cell:last-child .ui-grid-column-resizer.right {
+            border-right-width: 1px;
+        }
+
+        & > div > .ui-grid-cell-contents {
+            border-bottom: 1px solid #d4d4d4;
+        }
+    }
+
+    input {
+        line-height: 21px;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/components/ui-grid-settings/ui-grid-settings.jade
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/ui-grid-settings/ui-grid-settings.jade b/modules/web-console/frontend/app/components/ui-grid-settings/ui-grid-settings.jade
new file mode 100644
index 0000000..8f1487e
--- /dev/null
+++ b/modules/web-console/frontend/app/components/ui-grid-settings/ui-grid-settings.jade
@@ -0,0 +1,33 @@
+//-
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+mixin ui-grid-settings()
+    .ui-grid-settings
+        i.fa.fa-bars(data-animation='am-flip-x' bs-dropdown='' aria-haspopup='true' aria-expanded='expanded' data-auto-close='1' data-trigger='click')
+        ul.select.dropdown-menu(role='menu')
+            li(ng-repeat='item in paragraph.gridOptions.categories|filter:{selectable:true}')
+                a(ng-click='paragraph.toggleColumns(item, !item.visible)')
+                    i.fa.fa-check-square-o.pull-left(ng-if='item.visible')
+                    i.fa.fa-square-o.pull-left(ng-if='!item.visible')
+                    span {{::item.name}}
+            li.divider
+            li
+                a(ng-click='paragraph.selectAllColumns()') Select all
+            li
+                a(ng-click='paragraph.clearAllColumns()') Clear all
+            li.divider
+            li
+                a(ng-click='$hide()') Close

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/components/ui-grid-settings/ui-grid-settings.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/ui-grid-settings/ui-grid-settings.scss b/modules/web-console/frontend/app/components/ui-grid-settings/ui-grid-settings.scss
new file mode 100644
index 0000000..3016488
--- /dev/null
+++ b/modules/web-console/frontend/app/components/ui-grid-settings/ui-grid-settings.scss
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.ui-grid-settings {
+    ul.select.dropdown-menu > li > a {
+        padding-top: 0;
+        padding-bottom: 0;
+    }
+
+    ul.select.dropdown-menu > li > a > i {
+        position: relative;
+        line-height: 26px;
+        width: 14px;
+        margin-left: 0;
+        color: inherit;
+    }
+
+    ul.select.dropdown-menu > li > a > span {
+        line-height: 26px;
+        padding-left: 5px;
+        padding-right: 8px;
+        cursor: pointer;
+    }
+
+    &-dateperiod {
+        float: right;
+
+        .ignite-form-field {
+            width: 160px;
+            margin-right: 10px;
+
+            &__label {
+            }
+
+            &__control {
+            }
+
+            &:nth-child(1) {
+                float: left;
+
+                .ignite-form-field__label {
+                    width: 40%;
+                }
+
+                .ignite-form-field__control {
+                    width: 60%;
+                }
+            }
+        }
+
+        .btn {
+            line-height: 20px;
+            margin-right: 0;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/core/activities/Activities.data.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/core/activities/Activities.data.js b/modules/web-console/frontend/app/core/activities/Activities.data.js
new file mode 100644
index 0000000..8a67a97
--- /dev/null
+++ b/modules/web-console/frontend/app/core/activities/Activities.data.js
@@ -0,0 +1,39 @@
+/*
+ * 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 ActivitiesData {
+    static $inject = ['$http', '$state'];
+
+    constructor($http, $state) {
+        this.$http = $http;
+        this.$state = $state;
+    }
+
+    post(options = {}) {
+        let { group, action } = options;
+
+        action = action || this.$state.$current.url.source;
+        group = group || action.match(/^\/([^/]+)/)[1];
+
+        return this.$http.post('/api/v1/activities/page', { group, action });
+    }
+
+    listByUser(userId, params) {
+        return this.$http.get(`/api/v1/activities/user/${userId}`, { params })
+            .then(({ data }) => data);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/core/admin/Admin.data.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/core/admin/Admin.data.js b/modules/web-console/frontend/app/core/admin/Admin.data.js
new file mode 100644
index 0000000..66d82f0
--- /dev/null
+++ b/modules/web-console/frontend/app/core/admin/Admin.data.js
@@ -0,0 +1,77 @@
+/*
+ * 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 IgniteAdminData {
+    static $inject = ['$http', 'IgniteMessages', 'IgniteCountries'];
+
+    constructor($http, Messages, Countries) {
+        this.$http = $http;
+        this.Messages = Messages;
+        this.Countries = Countries;
+    }
+
+    becomeUser(viewedUserId) {
+        return this.$http.get('/api/v1/admin/become', {
+            params: {viewedUserId}
+        })
+        .catch(this.Messages.showError);
+    }
+
+    removeUser(user) {
+        return this.$http.post('/api/v1/admin/remove', {
+            userId: user._id
+        })
+        .then(() => {
+            this.Messages.showInfo(`User has been removed: "${user.userName}"`);
+        })
+        .catch(({data, status}) => {
+            if (status === 503)
+                this.Messages.showInfo(data);
+            else
+                this.Messages.showError('Failed to remove user: ', data);
+        });
+    }
+
+    toggleAdmin(user) {
+        return this.$http.post('/api/v1/admin/save', {
+            userId: user._id,
+            adminFlag: !user.admin
+        })
+        .then(() => {
+            this.Messages.showInfo(`Admin right was successfully toggled for user: "${user.userName}"`);
+        })
+        .catch((res) => {
+            this.Messages.showError('Failed to toggle admin right for user: ', res);
+        });
+    }
+
+    prepareUsers(user) {
+        const { Countries } = this;
+
+        user.userName = user.firstName + ' ' + user.lastName;
+        user.countryCode = Countries.getByName(user.country).code;
+
+        return user;
+    }
+
+    loadUsers(params) {
+        return this.$http.post('/api/v1/admin/list', params)
+            .then(({ data }) => data)
+            .then((users) => _.map(users, this.prepareUsers.bind(this)))
+            .catch(this.Messages.showError);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/core/index.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/core/index.js b/modules/web-console/frontend/app/core/index.js
new file mode 100644
index 0000000..7f72ee3
--- /dev/null
+++ b/modules/web-console/frontend/app/core/index.js
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import angular from 'angular';
+
+import IgniteAdminData from './admin/Admin.data';
+import IgniteActivitiesData from './activities/Activities.data';
+
+angular.module('ignite-console.core', [])
+    .service('IgniteAdminData', IgniteAdminData)
+    .service('IgniteActivitiesData', IgniteActivitiesData);

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/data/i18n.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/data/i18n.js b/modules/web-console/frontend/app/data/i18n.js
new file mode 100644
index 0000000..bc8c700
--- /dev/null
+++ b/modules/web-console/frontend/app/data/i18n.js
@@ -0,0 +1,38 @@
+/*
+ * 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 {
+    '/agent/start': 'Agent start',
+    '/agent/download': 'Agent download',
+    '/configuration/clusters': 'Configure clusters',
+    '/configuration/caches': 'Configure caches',
+    '/configuration/domains': 'Configure domain model',
+    '/configuration/igfs': 'Configure IGFS',
+    '/configuration/summary': 'Configurations summary',
+    '/demo/resume': 'Demo resume',
+    '/demo/reset': 'Demo reset',
+    '/queries/execute': 'Query execute',
+    '/queries/explain': 'Query explain',
+    '/queries/scan': 'Scan',
+    '/queries/add/query': 'Add query',
+    '/queries/add/scan': 'Add scan',
+    '/queries/demo': 'SQL demo',
+    '/queries/notebook/': 'Query notebook',
+    '/settings/profile': 'User profile',
+    '/settings/admin': 'Admin panel',
+    '/logout': 'Logout'
+};

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/directives/ui-grid-settings/ui-grid-settings.jade
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/directives/ui-grid-settings/ui-grid-settings.jade b/modules/web-console/frontend/app/directives/ui-grid-settings/ui-grid-settings.jade
deleted file mode 100644
index 8f1487e..0000000
--- a/modules/web-console/frontend/app/directives/ui-grid-settings/ui-grid-settings.jade
+++ /dev/null
@@ -1,33 +0,0 @@
-//-
-    Licensed to the Apache Software Foundation (ASF) under one or more
-    contributor license agreements.  See the NOTICE file distributed with
-    this work for additional information regarding copyright ownership.
-    The ASF licenses this file to You under the Apache License, Version 2.0
-    (the "License"); you may not use this file except in compliance with
-    the License.  You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
-
-mixin ui-grid-settings()
-    .ui-grid-settings
-        i.fa.fa-bars(data-animation='am-flip-x' bs-dropdown='' aria-haspopup='true' aria-expanded='expanded' data-auto-close='1' data-trigger='click')
-        ul.select.dropdown-menu(role='menu')
-            li(ng-repeat='item in paragraph.gridOptions.categories|filter:{selectable:true}')
-                a(ng-click='paragraph.toggleColumns(item, !item.visible)')
-                    i.fa.fa-check-square-o.pull-left(ng-if='item.visible')
-                    i.fa.fa-square-o.pull-left(ng-if='!item.visible')
-                    span {{::item.name}}
-            li.divider
-            li
-                a(ng-click='paragraph.selectAllColumns()') Select all
-            li
-                a(ng-click='paragraph.clearAllColumns()') Clear all
-            li.divider
-            li
-                a(ng-click='$hide()') Close

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/directives/ui-grid-settings/ui-grid-settings.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/directives/ui-grid-settings/ui-grid-settings.scss b/modules/web-console/frontend/app/directives/ui-grid-settings/ui-grid-settings.scss
deleted file mode 100644
index 6517a60..0000000
--- a/modules/web-console/frontend/app/directives/ui-grid-settings/ui-grid-settings.scss
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-.ui-grid-settings {
-    ul.select.dropdown-menu > li > a {
-        padding-top: 0;
-        padding-bottom: 0;
-    }
-
-    ul.select.dropdown-menu > li > a > i {
-        position: relative;
-        line-height: 26px;
-        width: 14px;
-        margin-left: 0;
-        color: inherit;
-    }
-
-    ul.select.dropdown-menu > li > a > span {
-        line-height: 26px;
-        padding-left: 5px;
-        padding-right: 8px;
-        cursor: pointer;
-    }
-}

http://git-wip-us.apache.org/repos/asf/ignite/blob/26ee9c28/modules/web-console/frontend/app/filters/uiGridSubcategories.filter.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/filters/uiGridSubcategories.filter.js b/modules/web-console/frontend/app/filters/uiGridSubcategories.filter.js
new file mode 100644
index 0000000..f36ae6e
--- /dev/null
+++ b/modules/web-console/frontend/app/filters/uiGridSubcategories.filter.js
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export default [() => {
+    return (arr, category) => {
+        return _.filter(arr, (item) => {
+            return item.colDef.categoryDisplayName === category;
+        });
+    };
+}];