You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by dm...@apache.org on 2017/03/25 00:13:17 UTC

[37/56] [abbrv] ignite git commit: IGNITE-4686 Added ability to group registered users in admin panel.

IGNITE-4686 Added ability to group registered users in admin panel.


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

Branch: refs/heads/ignite-1192
Commit: 827befb7f368afd1a42ec52e559dee01ab57113a
Parents: 3b89a5c
Author: Dmitriy Shabalin <ds...@gridgain.com>
Authored: Wed Mar 22 10:37:05 2017 +0700
Committer: Andrey Novikov <an...@gridgain.com>
Committed: Wed Mar 22 10:37:05 2017 +0700

----------------------------------------------------------------------
 modules/web-console/frontend/app/app.js         |   1 +
 .../form-field-datepicker.pug                   |  12 +-
 .../list-of-registered-users/index.js           |   2 +
 .../list-of-registered-users.column-defs.js     |  48 ++---
 .../list-of-registered-users.controller.js      | 193 ++++++++++++++-----
 .../list-of-registered-users.scss               |  28 +++
 .../list-of-registered-users.tpl.pug            |  50 +++--
 .../ui-grid-header/ui-grid-header.scss          |   6 +
 .../ui-grid-header/ui-grid-header.tpl.pug       |   4 +-
 .../ui-grid-settings/ui-grid-settings.scss      |  10 +
 .../frontend/app/primitives/badge/index.scss    |  36 ++++
 .../frontend/app/primitives/index.js            |  19 ++
 .../frontend/app/primitives/tabs/index.scss     |  73 +++++++
 .../frontend/public/stylesheets/style.scss      |   2 +
 14 files changed, 389 insertions(+), 95 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/827befb7/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 1e21d24..26d3ad5 100644
--- a/modules/web-console/frontend/app/app.js
+++ b/modules/web-console/frontend/app/app.js
@@ -16,6 +16,7 @@
  */
 
 import '../public/stylesheets/style.scss';
+import '../app/primitives';
 import './components/ui-grid-header/ui-grid-header.scss';
 import './components/ui-grid-settings/ui-grid-settings.scss';
 import './components/form-field-datepicker/form-field-datepicker.scss';

http://git-wip-us.apache.org/repos/asf/ignite/blob/827befb7/modules/web-console/frontend/app/components/form-field-datepicker/form-field-datepicker.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/form-field-datepicker/form-field-datepicker.pug b/modules/web-console/frontend/app/components/form-field-datepicker/form-field-datepicker.pug
index c9d382c..d70476f 100644
--- a/modules/web-console/frontend/app/components/form-field-datepicker/form-field-datepicker.pug
+++ b/modules/web-console/frontend/app/components/form-field-datepicker/form-field-datepicker.pug
@@ -14,7 +14,7 @@
     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 ignite-form-field-datepicker(label, model, name, mindate, maxdate, disabled, required, placeholder, tip)
     mixin form-field-input()
         input.form-control(
             id=`{{ ${name} }}Input`
@@ -30,8 +30,10 @@ mixin ignite-form-field-datepicker(label, model, name, disabled, required, place
             bs-datepicker
             data-date-format='MMM yyyy'
             data-start-view='1'
-            data-min-view='1' 
-            data-max-date='today'
+            data-min-view='1'
+
+            data-min-date=mindate ? `{{ ${mindate} }}` : false
+            data-max-date=maxdate ? `{{ ${maxdate} }}` : `today`
 
             data-container='body > .wrapper'
 
@@ -43,7 +45,9 @@ mixin ignite-form-field-datepicker(label, model, name, disabled, required, place
         )&attributes(attributes.attributes)
 
     .ignite-form-field
-        +ignite-form-field__label(label, name, required)
+        if name
+            +ignite-form-field__label(label, name, required)
+
         .ignite-form-field__control
             if tip
                 i.tipField.icon-help(bs-tooltip='' data-title=tip)

http://git-wip-us.apache.org/repos/asf/ignite/blob/827befb7/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
index 22a89da..4e5061f 100644
--- 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
@@ -15,6 +15,8 @@
  * limitations under the License.
  */
 
+import './list-of-registered-users.scss';
+
 import templateUrl from './list-of-registered-users.tpl.pug';
 import controller from './list-of-registered-users.controller';
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/827befb7/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
index e6ba842..e859acf 100644
--- 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
@@ -49,32 +49,32 @@ const ACTIONS_TEMPLATE = `
 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: 'actions', minWidth: 65, width: 65, 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 }},
+    {name: 'actions', displayName: 'Actions', categoryDisplayName: 'Actions', cellTemplate: ACTIONS_TEMPLATE, field: 'actions', minWidth: 70, width: 70, enableFiltering: false, enableSorting: false},
+    {name: 'user', displayName: 'User', categoryDisplayName: 'User', field: 'userName', cellTemplate: USER_TEMPLATE, minWidth: 160, enableFiltering: true, filter: { placeholder: 'Filter by name...' }},
+    {name: 'email', displayName: 'Email', categoryDisplayName: 'Email', field: 'email', cellTemplate: EMAIL_TEMPLATE, minWidth: 160, enableFiltering: true, filter: { placeholder: 'Filter by email...' }},
+    {name: 'company', displayName: 'Company', categoryDisplayName: 'Company', field: 'company', minWidth: 160, enableFiltering: true, filter: { placeholder: 'Filter by company...' }},
+    {name: 'country', displayName: 'Country', categoryDisplayName: 'Country', field: 'countryCode', minWidth: 80, enableFiltering: true, filter: { placeholder: 'Filter by country...' }},
+    {name: 'lastlogin', displayName: 'Last login', categoryDisplayName: 'Last login', field: 'lastLogin', cellFilter: 'date:"M/d/yy HH:mm"', minWidth: 105, width: 105, enableFiltering: false, visible: false},
+    {name: 'lastactivity', displayName: 'Last activity', categoryDisplayName: 'Last activity', field: 'lastActivity', cellFilter: 'date:"M/d/yy HH:mm"', minWidth: 115, width: 115, 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},
+    {name: 'cfg_clusters', displayName: 'Clusters count', categoryDisplayName: 'Configurations', headerCellTemplate: CLUSTER_HEADER_TEMPLATE, field: 'counters.clusters', type: 'number', headerTooltip: 'Clusters count', minWidth: 55, width: 55, enableFiltering: false, visible: false},
+    {name: 'cfg_models', displayName: 'Models count', categoryDisplayName: 'Configurations', headerCellTemplate: MODEL_HEADER_TEMPLATE, field: 'counters.models', type: 'number', headerTooltip: 'Models count', minWidth: 55, width: 55, enableFiltering: false, visible: false},
+    {name: 'cfg_caches', displayName: 'Caches count', categoryDisplayName: 'Configurations', headerCellTemplate: CACHE_HEADER_TEMPLATE, field: 'counters.caches', type: 'number', headerTooltip: 'Caches count', minWidth: 55, width: 55, enableFiltering: false, visible: false},
+    {name: 'cfg_igfs', displayName: 'IGFS count', categoryDisplayName: 'Configurations', headerCellTemplate: IGFS_HEADER_TEMPLATE, field: 'counters.igfs', type: 'number', headerTooltip: 'IGFS count', minWidth: 55, width: 55, enableFiltering: false, visible: false},
     // Activities Total
-    {displayName: 'Cfg', categoryDisplayName: 'Total activities', field: 'activitiesTotal["configuration"] || 0', type: 'number', headerTooltip: 'Total count of configuration usages', minWidth: 50, width: 50, enableFiltering: false},
-    {displayName: 'Qry', categoryDisplayName: 'Total activities', field: 'activitiesTotal["queries"] || 0', type: 'number', headerTooltip: 'Total count of queries usages', minWidth: 50, width: 50, enableFiltering: false},
-    {displayName: 'Demo', categoryDisplayName: 'Total activities', field: 'activitiesTotal["demo"] || 0', type: 'number', headerTooltip: 'Total count of demo startup', minWidth: 60, width: 60, enableFiltering: false},
-    {displayName: 'Dnld', categoryDisplayName: 'Total activities', field: 'activitiesDetail["/agent/download"] || 0', type: 'number', headerTooltip: 'Total count of agent downloads', minWidth: 55, width: 55, enableFiltering: false},
-    {displayName: 'Starts', categoryDisplayName: 'Total activities', field: 'activitiesDetail["/agent/start"] || 0', type: 'number', headerTooltip: 'Total count of agent startup', minWidth: 60, width: 60, enableFiltering: false},
+    {name: 'cfg', displayName: 'Cfg', categoryDisplayName: 'Total activities', field: 'activitiesTotal["configuration"] || 0', type: 'number', headerTooltip: 'Total count of configuration usages', minWidth: 55, width: 55, enableFiltering: false},
+    {name: 'qry', displayName: 'Qry', categoryDisplayName: 'Total activities', field: 'activitiesTotal["queries"] || 0', type: 'number', headerTooltip: 'Total count of queries usages', minWidth: 55, width: 55, enableFiltering: false},
+    {name: 'demo', displayName: 'Demo', categoryDisplayName: 'Total activities', field: 'activitiesTotal["demo"] || 0', type: 'number', headerTooltip: 'Total count of demo startup', minWidth: 65, width: 65, enableFiltering: false},
+    {name: 'dnld', displayName: 'Dnld', categoryDisplayName: 'Total activities', field: 'activitiesDetail["/agent/download"] || 0', type: 'number', headerTooltip: 'Total count of agent downloads', minWidth: 55, width: 55, enableFiltering: false},
+    {name: 'starts', displayName: 'Starts', categoryDisplayName: 'Total activities', field: 'activitiesDetail["/agent/start"] || 0', type: 'number', headerTooltip: 'Total count of agent startup', minWidth: 65, width: 65, 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},
+    {name: 'clusters', displayName: 'Clusters', categoryDisplayName: 'Configuration\'s activities', field: 'activitiesDetail["/configuration/clusters"] || 0', type: 'number', headerTooltip: 'Configuration clusters', minWidth: 55, width: 80, enableFiltering: false, visible: false},
+    {name: 'model', displayName: 'Model', categoryDisplayName: 'Configuration\'s activities', field: 'activitiesDetail["/configuration/domains"] || 0', type: 'number', headerTooltip: 'Configuration model', minWidth: 55, width: 80, enableFiltering: false, visible: false},
+    {name: 'caches', displayName: 'Caches', categoryDisplayName: 'Configuration\'s activities', field: 'activitiesDetail["/configuration/caches"] || 0', type: 'number', headerTooltip: 'Configuration caches', minWidth: 55, width: 80, enableFiltering: false, visible: false},
+    {name: 'igfs', displayName: 'IGFS', categoryDisplayName: 'Configuration\'s activities', field: 'activitiesDetail["/configuration/igfs"] || 0', type: 'number', headerTooltip: 'Configuration IGFS', minWidth: 55, width: 80, enableFiltering: false, visible: false},
+    {name: 'summary', displayName: 'Summary', categoryDisplayName: 'Configuration\'s activities', field: 'activitiesDetail["/configuration/summary"] || 0', type: 'number', headerTooltip: 'Configuration summary', minWidth: 55, width: 80, enableFiltering: false, visible: false},
     // Activities Queries
-    {displayName: 'Execute', categoryDisplayName: 'Queries\' activities', field: 'activitiesDetail["/queries/execute"] || 0', type: 'number', headerTooltip: 'Query executions', minWidth: 50, width: 80, enableFiltering: false, visible: false},
-    {displayName: 'Explain', categoryDisplayName: 'Queries\' activities', field: 'activitiesDetail["/queries/explain"] || 0', type: 'number', headerTooltip: 'Query explain executions', minWidth: 50, width: 80, enableFiltering: false, visible: false},
-    {displayName: 'Scan', categoryDisplayName: 'Queries\' activities', field: 'activitiesDetail["/queries/scan"] || 0', type: 'number', headerTooltip: 'Scan query executions', minWidth: 50, width: 80, enableFiltering: false, visible: false}
+    {name: 'execute', displayName: 'Execute', categoryDisplayName: 'Queries\' activities', field: 'activitiesDetail["/queries/execute"] || 0', type: 'number', headerTooltip: 'Query executions', minWidth: 55, width: 80, enableFiltering: false, visible: false},
+    {name: 'explain', displayName: 'Explain', categoryDisplayName: 'Queries\' activities', field: 'activitiesDetail["/queries/explain"] || 0', type: 'number', headerTooltip: 'Query explain executions', minWidth: 55, width: 80, enableFiltering: false, visible: false},
+    {name: 'scan', displayName: 'Scan', categoryDisplayName: 'Queries\' activities', field: 'activitiesDetail["/queries/scan"] || 0', type: 'number', headerTooltip: 'Scan query executions', minWidth: 55, width: 80, enableFiltering: false, visible: false}
 ];

http://git-wip-us.apache.org/repos/asf/ignite/blob/827befb7/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
index 54971b1..acf76fa 100644
--- 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
@@ -30,34 +30,21 @@ const rowTemplate = `<div
   ui-grid-cell/>`;
 
 export default class IgniteListOfRegisteredUsersCtrl {
-    static $inject = ['$scope', '$state', '$filter', 'User', 'uiGridConstants', 'IgniteAdminData', 'IgniteNotebookData', 'IgniteConfirm', 'IgniteActivitiesUserDialog'];
+    static $inject = ['$scope', '$state', '$filter', 'User', 'uiGridGroupingConstants', 'IgniteAdminData', 'IgniteNotebookData', 'IgniteConfirm', 'IgniteActivitiesUserDialog'];
 
-    constructor($scope, $state, $filter, User, uiGridConstants, AdminData, NotebookData, Confirm, ActivitiesUserDialog) {
+    constructor($scope, $state, $filter, User, uiGridGroupingConstants, AdminData, NotebookData, Confirm, ActivitiesUserDialog) {
         const $ctrl = this;
 
-        const companySelectOptions = [];
-        const countrySelectOptions = [];
-
         const dtFilter = $filter('date');
 
+        $ctrl.groupBy = 'user';
+
         $ctrl.params = {
-            startDate: new Date()
+            startDate: new Date(),
+            endDate: new Date()
         };
 
-        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
-        };
+        $ctrl.uiGridGroupingConstants = uiGridGroupingConstants;
 
         const becomeUser = (user) => {
             AdminData.becomeUser(user._id)
@@ -105,6 +92,11 @@ export default class IgniteListOfRegisteredUsersCtrl {
             return renderableRows;
         };
 
+        $ctrl._userGridOptions = {
+            columnDefs,
+            categories
+        };
+
         $ctrl.gridOptions = {
             data: [],
             columnVirtualizationThreshold: 30,
@@ -134,19 +126,6 @@ export default class IgniteListOfRegisteredUsersCtrl {
             }
         };
 
-        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: number, endDate: number}} params
          */
@@ -154,31 +133,32 @@ export default class IgniteListOfRegisteredUsersCtrl {
             AdminData.loadUsers(params)
                 .then((data) => $ctrl.gridOptions.data = data)
                 .then((data) => {
-                    companySelectOptions.length = 0;
-                    countrySelectOptions.length = 0;
-
-                    companySelectOptions.push(...usersToFilterOptions('company'));
-                    countrySelectOptions.push(...usersToFilterOptions('countryCode'));
-
                     this.gridApi.grid.refresh();
 
+                    this.companies = _.values(_.groupBy(data, (b) => b.company.toLowerCase()));
+                    this.countries = _.values(_.groupBy(data, (b) => b.countryCode));
+
                     return data;
-                })
-                .then((data) => $ctrl.adjustHeight(data.length));
+                });
+        };
+
+        const fitlerDates = (sdt, edt) => {
+            $ctrl.gridOptions.exporterCsvFilename = `web_console_users_${dtFilter(sdt, 'yyyy_MM')}.csv`;
+
+            const startDate = Date.UTC(sdt.getFullYear(), sdt.getMonth(), 1);
+            const endDate = Date.UTC(edt.getFullYear(), edt.getMonth() + 1, 1);
+
+            reloadUsers({ startDate, endDate });
         };
 
         $scope.$watch(() => $ctrl.params.companiesExclude, () => {
             $ctrl.gridApi.grid.refreshRows();
         });
 
-        $scope.$watch(() => $ctrl.params.startDate, (dt) => {
-            $ctrl.gridOptions.exporterCsvFilename = `web_console_users_${dtFilter(dt, 'yyyy_MM')}.csv`;
+        $scope.$watch(() => $ctrl.params.startDate, (sdt) => fitlerDates(sdt, $ctrl.params.endDate));
+        $scope.$watch(() => $ctrl.params.endDate, (edt) => fitlerDates($ctrl.params.startDate, edt));
 
-            const startDate = Date.UTC(dt.getFullYear(), dt.getMonth(), 1);
-            const endDate = Date.UTC(dt.getFullYear(), dt.getMonth() + 1, 1);
-
-            reloadUsers({ startDate, endDate });
-        });
+        $scope.$watch(() => $ctrl.gridApi.grid.getVisibleRows().length, (length) => $ctrl.adjustHeight(length >= 20 ? 20 : length));
     }
 
     adjustHeight(rows) {
@@ -235,4 +215,121 @@ export default class IgniteListOfRegisteredUsersCtrl {
     exportCsv() {
         this.gridApi.exporter.csvExport('visible', 'visible');
     }
+
+    groupByUser() {
+        this.groupBy = 'user';
+
+        this.gridApi.grouping.clearGrouping();
+
+        this.gridOptions.categories = this._userGridOptions.categories;
+        this.gridOptions.columnDefs = this._userGridOptions.columnDefs;
+    }
+
+    groupByCompany() {
+        this.groupBy = 'company';
+
+        this.gridApi.grouping.clearGrouping();
+        this.gridApi.grouping.groupColumn('company');
+        this.gridApi.grouping.aggregateColumn('user', this.uiGridGroupingConstants.aggregation.COUNT);
+
+        if (this._companyGridOptions) {
+            this.gridOptions.categories = this._companyGridOptions.categories;
+            this.gridOptions.columnDefs = this._companyGridOptions.columnDefs;
+
+            return;
+        }
+
+        const _categories = _.cloneDeep(categories);
+        const _columnDefs = _.cloneDeep(columnDefs);
+
+        // Cut company category;
+        const company = _categories.splice(3, 1)[0];
+
+        // Hide Actions category;
+        _categories.splice(0, 1);
+
+        _.forEach(_.filter(_columnDefs, {displayName: 'Actions'}), (col) => {
+            col.visible = false;
+        });
+
+        // Add company as first column;
+        _categories.unshift(company);
+
+        _.forEach(_columnDefs, (col) => {
+            col.enableSorting = true;
+
+            if (col.type !== 'number')
+                return;
+
+            col.treeAggregationType = this.uiGridGroupingConstants.aggregation.SUM;
+            col.customTreeAggregationFinalizerFn = (agg) => agg.rendered = agg.value;
+        });
+
+        // Set grouping to last activity column
+        const lastactivity = _.find(_columnDefs, { name: 'lastactivity' });
+
+        if (_.nonNil(lastactivity)) {
+            lastactivity.treeAggregationType = this.uiGridGroupingConstants.aggregation.MAX;
+            lastactivity.customTreeAggregationFinalizerFn = (agg) => agg.rendered = agg.value;
+        }
+
+        this._companyGridOptions = {
+            categories: this.gridOptions.categories = _categories,
+            columnDefs: this.gridOptions.columnDefs = _columnDefs
+        };
+    }
+
+    groupByCountry() {
+        this.groupBy = 'country';
+
+        this.gridApi.grouping.clearGrouping();
+        this.gridApi.grouping.groupColumn('country');
+        this.gridApi.grouping.aggregateColumn('user', this.uiGridGroupingConstants.aggregation.COUNT);
+
+        if (this._countryGridOptions) {
+            this.gridOptions.categories = this._countryGridOptions.categories;
+            this.gridOptions.columnDefs = this._countryGridOptions.columnDefs;
+
+            return;
+        }
+
+        const _categories = _.cloneDeep(categories);
+        const _columnDefs = _.cloneDeep(columnDefs);
+
+        // Cut country category;
+        const country = _categories.splice(4, 1)[0];
+
+        // Hide Actions category;
+        _categories.splice(0, 1);
+
+        _.forEach(_.filter(_columnDefs, {displayName: 'Actions'}), (col) => {
+            col.visible = false;
+        });
+
+        // Add company as first column;
+        _categories.unshift(country);
+
+        _.forEach(_columnDefs, (col) => {
+            col.enableSorting = true;
+
+            if (col.type !== 'number')
+                return;
+
+            col.treeAggregationType = this.uiGridGroupingConstants.aggregation.SUM;
+            col.customTreeAggregationFinalizerFn = (agg) => agg.rendered = agg.value;
+        });
+
+        // Set grouping to last activity column
+        const lastactivity = _.find(_columnDefs, { name: 'lastactivity' });
+
+        if (_.nonNil(lastactivity)) {
+            lastactivity.treeAggregationType = this.uiGridGroupingConstants.aggregation.MAX;
+            lastactivity.customTreeAggregationFinalizerFn = (agg) => agg.rendered = agg.value;
+        }
+
+        this._countryGridOptions = {
+            categories: this.gridOptions.categories = _categories,
+            columnDefs: this.gridOptions.columnDefs = _columnDefs
+        };
+    }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/827befb7/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.scss b/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.scss
new file mode 100644
index 0000000..8059d70
--- /dev/null
+++ b/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.scss
@@ -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.
+ */
+
+.list-of-registered-users {
+  & > a {
+    display: inline-block;
+    margin: 10px;
+    margin-left: 0;
+
+    &.active {
+      font-weight: bold;
+    }
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/827befb7/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.tpl.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.tpl.pug b/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.tpl.pug
index 52975b9..ec4b4fd 100644
--- a/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.tpl.pug
+++ b/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.tpl.pug
@@ -34,25 +34,39 @@ mixin grid-settings()
         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
-
-        form.pull-right(ng-form=form novalidate)
-            -var form = 'admin'
 
-            button.btn.btn-primary(ng-click='$ctrl.exportCsv()' bs-tooltip data-title='Export table to csv') Export
+.list-of-registered-users
+    ul.tabs
+        li(role='presentation' ng-class='{ active: $ctrl.groupBy === "user" }') 
+            a(ng-click='$ctrl.groupByUser()') 
+                span Users
+                span.badge {{ $ctrl.gridOptions.data.length }}
+        li(role='presentation' ng-class='{ active: $ctrl.groupBy === "company" }')
+            a(ng-click='$ctrl.groupByCompany()') 
+                span Companies
+                span.badge {{ $ctrl.companies.length }}
+        li(role='presentation' ng-class='{ active: $ctrl.groupBy === "country" }')
+            a(ng-click='$ctrl.groupByCountry()')
+                span Countries
+                span.badge {{ $ctrl.countries.length }}
+
+    .panel.panel-default
+        .panel-heading.ui-grid-settings
+            +grid-settings
+            label(ng-show='$ctrl.groupBy === "user"') Showing users:&nbsp;
+                strong {{ $ctrl.gridApi.grid.getVisibleRows().length }}
+                sub(ng-show='users.length === $ctrl.gridApi.grid.getVisibleRows().length') all
+
+            -var form = 'admin'
+            form.pull-right(name=form novalidate)
+                button.btn.btn-primary(ng-click='$ctrl.exportCsv()' bs-tooltip data-title='Export table to csv') Export
 
-            .ui-grid-settings-dateperiod
-                +ignite-form-field-datepicker('Period:', '$ctrl.params.startDate', '"period"')
+                .ui-grid-settings-dateperiod
+                    +ignite-form-field-datepicker('Period:', '$ctrl.params.startDate', '"period"', null, '$ctrl.params.endDate')
+                    +ignite-form-field-datepicker('Period:', '$ctrl.params.endDate', null, '$ctrl.params.startDate', null)
 
-            .ui-grid-settings-filter
-                +ignite-form-field-text('Exclude:', '$ctrl.params.companiesExclude', '"exclude"', false, false, 'Exclude by company name...')
+                .ui-grid-settings-filter
+                    +ignite-form-field-text('Exclude:', '$ctrl.params.companiesExclude', '"exclude"', false, false, 'Exclude by company name...')
 
-    .panel-collapse
-        .grid.ui-grid--ignite(ui-grid='$ctrl.gridOptions' ui-grid-resize-columns ui-grid-selection ui-grid-exporter ui-grid-pinning)
+        .panel-collapse
+            .grid.ui-grid--ignite(ui-grid='$ctrl.gridOptions' ui-grid-resize-columns ui-grid-selection ui-grid-exporter ui-grid-pinning ui-grid-grouping)

http://git-wip-us.apache.org/repos/asf/ignite/blob/827befb7/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
index c6e7bdf..4530c02 100644
--- 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
@@ -28,6 +28,12 @@
         height: 30px;
     }
 
+    .ui-grid-header-cell {
+        .ui-grid-cell-contents > span:not(.ui-grid-header-cell-label) {
+            right: 3px;
+        }
+    }
+
     .ui-grid-header-cell [role="columnheader"] {
         display: flex;
         

http://git-wip-us.apache.org/repos/asf/ignite/blob/827befb7/modules/web-console/frontend/app/components/ui-grid-header/ui-grid-header.tpl.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/ui-grid-header/ui-grid-header.tpl.pug b/modules/web-console/frontend/app/components/ui-grid-header/ui-grid-header.tpl.pug
index 7e44d94..9b14fca 100644
--- a/modules/web-console/frontend/app/components/ui-grid-header/ui-grid-header.tpl.pug
+++ b/modules/web-console/frontend/app/components/ui-grid-header/ui-grid-header.tpl.pug
@@ -20,8 +20,10 @@
             .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')
+                        .ui-grid-header-span.ui-grid-header-cell.ui-grid-clearfix.ui-grid-category(ng-repeat='cat in grid.options.categories', ng-if='cat.visible && \
+                        (colContainer.renderedColumns | uiGridSubcategories: cat.name).length > 0')
                             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')
+                        .ui-grid-header-cell.ui-grid-clearfix(ng-if='col.colDef.name === "treeBaseRowHeaderCol"' ng-repeat='col in colContainer.renderedColumns track by col.uid' ui-grid-header-cell='' col='col' render-index='$index')

http://git-wip-us.apache.org/repos/asf/ignite/blob/827befb7/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
index 24f4d9b..d0a31f0 100644
--- 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
@@ -129,6 +129,16 @@
                     width: 60%;
                 }
             }
+
+            &:nth-child(2) {
+                float: left;
+
+                width: 100px;
+
+                .ignite-form-field__control {
+                    width: 100%;
+                }
+            }
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/827befb7/modules/web-console/frontend/app/primitives/badge/index.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/primitives/badge/index.scss b/modules/web-console/frontend/app/primitives/badge/index.scss
new file mode 100644
index 0000000..837ab5b
--- /dev/null
+++ b/modules/web-console/frontend/app/primitives/badge/index.scss
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@import '../../../public/stylesheets/variables';
+
+.badge {
+  display: inline-block;
+  min-width: 26px;
+  height: 18px;
+
+  padding: 2px 9px;
+
+  border-radius: 9px;
+
+  color: white;
+  font-family: Roboto;
+  font-size: 12px;
+  text-align: center;
+  line-height: 12px;
+
+  background-color: $brand-primary;
+}
\ No newline at end of file

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

http://git-wip-us.apache.org/repos/asf/ignite/blob/827befb7/modules/web-console/frontend/app/primitives/tabs/index.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/primitives/tabs/index.scss b/modules/web-console/frontend/app/primitives/tabs/index.scss
new file mode 100644
index 0000000..eed88cb
--- /dev/null
+++ b/modules/web-console/frontend/app/primitives/tabs/index.scss
@@ -0,0 +1,73 @@
+/*
+ * 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 '../../../public/stylesheets/variables';
+
+ul.tabs {
+    $width: auto;
+    $height: 40px;
+    $offset-vertical: 11px;
+    $offset-horizontal: 25px;
+    $font-size: 14px;
+
+    list-style: none;
+
+    padding-left: 0;
+    border-bottom: 1px solid $nav-tabs-border-color;
+
+    li {
+        position: relative;
+        top: 1px;
+
+        display: inline-block;
+
+        border-bottom: 5px solid transparent;
+
+        a {
+            display: inline-block;
+            width: $width;
+            height: $height;
+
+            padding: $offset-vertical $offset-horizontal;
+
+            color: $text-color;
+            font-size: $font-size;
+            text-align: center;
+            line-height: $height - 2*$offset-vertical;
+
+            &:hover {
+              text-decoration: none;
+            }
+
+            .badge {
+              margin-left: $offset-vertical;
+            }
+        }
+
+        &.active {
+            border-color: $brand-primary;
+        }
+
+        &:not(.active):hover {
+            border-color: lighten($brand-primary, 25%);
+        }
+
+        & + li {
+            margin-left: 45px;
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/827befb7/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 a07472e..2f4966f 100644
--- a/modules/web-console/frontend/public/stylesheets/style.scss
+++ b/modules/web-console/frontend/public/stylesheets/style.scss
@@ -2302,6 +2302,8 @@ html,body,.splash-screen {
 
 .admin-page {
     .panel-heading {
+        height: 38px;
+
         border-bottom: 0;
         padding-bottom: 0;