ignite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From yzhda...@apache.org
Subject [07/40] ignite git commit: Web console beta-7.
Date Mon, 09 Jan 2017 17:02:39 GMT
http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/modules/web-console/frontend/app/services/Messages.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/services/Messages.service.js b/modules/web-console/frontend/app/services/Messages.service.js
index e679488..fefdae9 100644
--- a/modules/web-console/frontend/app/services/Messages.service.js
+++ b/modules/web-console/frontend/app/services/Messages.service.js
@@ -24,6 +24,9 @@ export default ['IgniteMessages', ['$alert', ($alert) => {
         prefix = prefix || '';
 
         if (err) {
+            if (err.hasOwnProperty('data'))
+                err = err.data;
+
             if (err.hasOwnProperty('message'))
                 return prefix + err.message;
 
@@ -38,26 +41,26 @@ export default ['IgniteMessages', ['$alert', ($alert) => {
             msgModal.hide();
     };
 
-    const _showMessage = (err, type, duration, icon) => {
+    const _showMessage = (message, err, type, duration) => {
         hideAlert();
 
-        const title = errorMessage(null, err);
+        const title = err ? errorMessage(message, err) : errorMessage(null, message);
 
         msgModal = $alert({type, title, duration});
 
-        msgModal.$scope.icon = icon;
+        msgModal.$scope.icon = `icon-${type}`;
     };
 
     return {
         errorMessage,
         hideAlert,
-        showError(err) {
-            _showMessage(err, 'danger', 10, 'fa-exclamation-triangle');
+        showError(message, err) {
+            _showMessage(message, err, 'danger', 10);
 
             return false;
         },
-        showInfo(err) {
-            _showMessage(err, 'success', 3, 'fa-check-circle-o');
+        showInfo(message) {
+            _showMessage(message, null, 'success', 3);
         }
     };
 }]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/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
index 7004301..cf7fd71 100644
--- a/modules/web-console/frontend/controllers/admin-controller.js
+++ b/modules/web-console/frontend/controllers/admin-controller.js
@@ -15,79 +15,220 @@
  * 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', 'IgniteMessages', 'IgniteConfirm', 'User', 'IgniteNotebookData', 'IgniteCountries',
-    ($rootScope, $scope, $http, $q, $state, Messages, Confirm, User, Notebook, Countries) => {
+    '$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 _reloadUsers = () => {
-            $http.post('/api/v1/admin/list')
-                .success((users) => {
-                    $scope.users = users;
+        const companySelectOptions = [];
+        const countrySelectOptions = [];
 
-                    _.forEach($scope.users, (user) => {
-                        user.userName = user.firstName + ' ' + user.lastName;
-                        user.countryCode = Countries.getByName(user.country).code;
-                        user.label = user.userName + ' ' + user.email + ' ' +
-                            (user.company || '') + ' ' + (user.countryCode || '');
-                    });
-                })
-                .error(Messages.showError);
-        };
+        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}
+        ];
 
-        _reloadUsers();
+        const ctrl = $scope.ctrl = {};
 
-        $scope.becomeUser = function(user) {
+        const becomeUser = function(user) {
             $http.get('/api/v1/admin/become', { params: {viewedUserId: user._id}})
-                .catch(({data}) => Promise.reject(data))
                 .then(() => User.load())
-                .then((becomeUser) => {
-                    $rootScope.$broadcast('user', becomeUser);
-
-                    $state.go('base.configuration.clusters');
-                })
+                .then(() => $state.go('base.configuration.clusters'))
                 .then(() => Notebook.load())
                 .catch(Messages.showError);
         };
 
-        $scope.removeUser = (user) => {
-            Confirm.confirm('Are you sure you want to remove user: "' + user.userName + '"?')
+        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})
-                        .success(() => {
+                        .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 + '"');
+                            Messages.showInfo(`User has been removed: "${user.userName}"`);
                         })
-                        .error((err, status) => {
+                        .catch(({data, status}) => {
                             if (status === 503)
-                                Messages.showInfo(err);
+                                Messages.showInfo(data);
                             else
-                                Messages.showError(Messages.errorMessage('Failed to remove user: ', err));
+                                Messages.showError('Failed to remove user: ', data);
                         });
                 });
         };
 
-        $scope.toggleAdmin = (user) => {
+        const toggleAdmin = (user) => {
             if (user.adminChanging)
                 return;
 
             user.adminChanging = true;
 
             $http.post('/api/v1/admin/save', {userId: user._id, adminFlag: !user.admin})
-                .success(() => {
+                .then(() => {
                     user.admin = !user.admin;
 
-                    Messages.showInfo('Admin right was successfully toggled for user: "' + user.userName + '"');
+                    Messages.showInfo(`Admin right was successfully toggled for user: "${user.userName}"`);
                 })
-                .error((err) => {
-                    Messages.showError(Messages.errorMessage('Failed to toggle admin right for user: ', err));
+                .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/2b3a180f/modules/web-console/frontend/controllers/caches-controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/controllers/caches-controller.js b/modules/web-console/frontend/controllers/caches-controller.js
index 8c01173..e7521b5 100644
--- a/modules/web-console/frontend/controllers/caches-controller.js
+++ b/modules/web-console/frontend/controllers/caches-controller.js
@@ -467,14 +467,14 @@ export default ['cachesController', [
         // Save cache in database.
         function save(item) {
             $http.post('/api/v1/configuration/caches/save', item)
-                .success(function(_id) {
+                .then(({data}) => {
+                    const _id = data;
+
                     item.label = _cacheLbl(item);
 
                     $scope.ui.inputForm.$setPristine();
 
-                    const idx = _.findIndex($scope.caches, function(cache) {
-                        return cache._id === _id;
-                    });
+                    const idx = _.findIndex($scope.caches, {_id});
 
                     if (idx >= 0)
                         _.assign($scope.caches[idx], item);
@@ -487,21 +487,21 @@ export default ['cachesController', [
                         if (_.includes(item.clusters, cluster.value))
                             cluster.caches = _.union(cluster.caches, [_id]);
                         else
-                            _.remove(cluster.caches, (id) => id === _id);
+                            _.pull(cluster.caches, _id);
                     });
 
                     _.forEach($scope.domains, (domain) => {
                         if (_.includes(item.domains, domain.value))
                             domain.meta.caches = _.union(domain.meta.caches, [_id]);
                         else
-                            _.remove(domain.meta.caches, (id) => id === _id);
+                            _.pull(domain.meta.caches, _id);
                     });
 
                     $scope.selectItem(item);
 
                     Messages.showInfo('Cache "' + item.name + '" saved.');
                 })
-                .error(Messages.showError);
+                .catch(Messages.showError);
         }
 
         // Save cache.
@@ -559,7 +559,7 @@ export default ['cachesController', [
                     const _id = selectedItem._id;
 
                     $http.post('/api/v1/configuration/caches/remove', {_id})
-                        .success(function() {
+                        .then(() => {
                             Messages.showInfo('Cache has been removed: ' + selectedItem.name);
 
                             const caches = $scope.caches;
@@ -582,7 +582,7 @@ export default ['cachesController', [
                                 _.forEach($scope.domains, (domain) => _.remove(domain.meta.caches, (id) => id === _id));
                             }
                         })
-                        .error(Messages.showError);
+                        .catch(Messages.showError);
                 });
         };
 
@@ -591,7 +591,7 @@ export default ['cachesController', [
             Confirm.confirm('Are you sure you want to remove all caches?')
                 .then(function() {
                     $http.post('/api/v1/configuration/caches/remove/all')
-                        .success(function() {
+                        .then(() => {
                             Messages.showInfo('All caches have been removed');
 
                             $scope.caches = [];
@@ -603,7 +603,7 @@ export default ['cachesController', [
                             $scope.ui.inputForm.$error = {};
                             $scope.ui.inputForm.$setPristine();
                         })
-                        .error(Messages.showError);
+                        .catch(Messages.showError);
                 });
         };
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/modules/web-console/frontend/controllers/clusters-controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/controllers/clusters-controller.js b/modules/web-console/frontend/controllers/clusters-controller.js
index f92a2f1..7f90b90 100644
--- a/modules/web-console/frontend/controllers/clusters-controller.js
+++ b/modules/web-console/frontend/controllers/clusters-controller.js
@@ -17,7 +17,7 @@
 
 // Controller for Clusters screen.
 export default ['clustersController', [
-    '$rootScope', '$scope', '$http', '$state', '$timeout', 'IgniteLegacyUtils', 'IgniteMessages', 'IgniteConfirm', 'IgniteClone', 'IgniteLoading', 'IgniteModelNormalizer', 'IgniteUnsavedChangesGuard', 'igniteEventGroups', 'DemoInfo', 'IgniteLegacyTable', 'IgniteConfigurationResource', 'IgniteErrorPopover', 'IgniteFormUtils',
+    '$rootScope', '$scope', '$http', '$state', '$timeout', 'IgniteLegacyUtils', 'IgniteMessages', 'IgniteConfirm', 'IgniteClone', 'IgniteLoading', 'IgniteModelNormalizer', 'IgniteUnsavedChangesGuard', 'IgniteEventGroups', 'DemoInfo', 'IgniteLegacyTable', 'IgniteConfigurationResource', 'IgniteErrorPopover', 'IgniteFormUtils',
     function($root, $scope, $http, $state, $timeout, LegacyUtils, Messages, Confirm, Clone, Loading, ModelNormalizer, UnsavedChangesGuard, igniteEventGroups, DemoInfo, LegacyTable, Resource, ErrorPopover, FormUtils) {
         UnsavedChangesGuard.install($scope);
 
@@ -31,6 +31,12 @@ export default ['clustersController', [
             cacheKeyConfiguration: [],
             communication: {},
             connector: {},
+            deploymentSpi: {
+                URI: {
+                    uriList: [],
+                    scanners: []
+                }
+            },
             discovery: {
                 Cloud: {
                     regions: [],
@@ -38,6 +44,7 @@ export default ['clustersController', [
                 }
             },
             marshaller: {},
+            peerClassLoadingLocalClassPathExclude: [],
             sslContextFactory: {
                 trustManagers: []
             },
@@ -276,6 +283,16 @@ export default ['clustersController', [
 
                     if (!cluster.eventStorage)
                         cluster.eventStorage = { kind: 'Memory' };
+
+                    if (!cluster.peerClassLoadingLocalClassPathExclude)
+                        cluster.peerClassLoadingLocalClassPathExclude = [];
+
+                    if (!cluster.deploymentSpi) {
+                        cluster.deploymentSpi = {URI: {
+                            uriList: [],
+                            scanners: []
+                        }};
+                    }
                 });
 
                 if ($state.params.linkId)
@@ -699,17 +716,20 @@ export default ['clustersController', [
         // Save cluster in database.
         function save(item) {
             $http.post('/api/v1/configuration/clusters/save', item)
-                .success(function(_id) {
+                .then(({data}) => {
+                    const _id = data;
+
                     item.label = _clusterLbl(item);
 
                     $scope.ui.inputForm.$setPristine();
 
-                    const idx = _.findIndex($scope.clusters, (cluster) => cluster._id === _id);
+                    const idx = _.findIndex($scope.clusters, {_id});
 
                     if (idx >= 0)
                         _.assign($scope.clusters[idx], item);
                     else {
                         item._id = _id;
+
                         $scope.clusters.push(item);
                     }
 
@@ -717,21 +737,21 @@ export default ['clustersController', [
                         if (_.includes(item.caches, cache.value))
                             cache.cache.clusters = _.union(cache.cache.clusters, [_id]);
                         else
-                            _.remove(cache.cache.clusters, (id) => id === _id);
+                            _.pull(cache.cache.clusters, _id);
                     });
 
                     _.forEach($scope.igfss, (igfs) => {
                         if (_.includes(item.igfss, igfs.value))
                             igfs.igfs.clusters = _.union(igfs.igfs.clusters, [_id]);
                         else
-                            _.remove(igfs.igfs.clusters, (id) => id === _id);
+                            _.pull(igfs.igfs.clusters, _id);
                     });
 
                     $scope.selectItem(item);
 
-                    Messages.showInfo('Cluster "' + item.name + '" saved.');
+                    Messages.showInfo(`Cluster "${item.name}" saved.`);
                 })
-                .error(Messages.showError);
+                .catch(Messages.showError);
         }
 
         // Save cluster.
@@ -774,7 +794,7 @@ export default ['clustersController', [
                     const _id = selectedItem._id;
 
                     $http.post('/api/v1/configuration/clusters/remove', {_id})
-                        .success(function() {
+                        .then(() => {
                             Messages.showInfo('Cluster has been removed: ' + selectedItem.name);
 
                             const clusters = $scope.clusters;
@@ -795,7 +815,7 @@ export default ['clustersController', [
                                 _.forEach($scope.igfss, (igfs) => _.remove(igfs.igfs.clusters, (id) => id === _id));
                             }
                         })
-                        .error(Messages.showError);
+                        .catch(Messages.showError);
                 });
         };
 
@@ -804,7 +824,7 @@ export default ['clustersController', [
             Confirm.confirm('Are you sure you want to remove all clusters?')
                 .then(function() {
                     $http.post('/api/v1/configuration/clusters/remove/all')
-                        .success(() => {
+                        .then(() => {
                             Messages.showInfo('All clusters have been removed');
 
                             $scope.clusters = [];
@@ -816,7 +836,7 @@ export default ['clustersController', [
                             $scope.ui.inputForm.$error = {};
                             $scope.ui.inputForm.$setPristine();
                         })
-                        .error(Messages.showError);
+                        .catch(Messages.showError);
                 });
         };
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/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 2d7b875..303110e 100644
--- a/modules/web-console/frontend/controllers/domains-controller.js
+++ b/modules/web-console/frontend/controllers/domains-controller.js
@@ -756,15 +756,15 @@ export default ['domainsController', [
                 Loading.start('importDomainFromDb');
 
                 $http.post('/api/v1/configuration/domains/save/batch', batch)
-                    .success(function(savedBatch) {
+                    .then(({data}) => {
                         let lastItem;
                         const newItems = [];
 
-                        _.forEach(_mapCaches(savedBatch.generatedCaches), function(cache) {
+                        _.forEach(_mapCaches(data.generatedCaches), function(cache) {
                             $scope.caches.push(cache);
                         });
 
-                        _.forEach(savedBatch.savedDomains, function(savedItem) {
+                        _.forEach(data.savedDomains, function(savedItem) {
                             const idx = _.findIndex($scope.domains, function(domain) {
                                 return domain._id === savedItem._id;
                             });
@@ -792,7 +792,7 @@ export default ['domainsController', [
 
                         $scope.ui.showValid = true;
                     })
-                    .error(Messages.showError)
+                    .catch(Messages.showError)
                     .finally(() => {
                         Loading.finish('importDomainFromDb');
 
@@ -1382,10 +1382,10 @@ export default ['domainsController', [
                 item.kind = 'store';
 
             $http.post('/api/v1/configuration/domains/save', item)
-                .success(function(res) {
+                .then(({data}) => {
                     $scope.ui.inputForm.$setPristine();
 
-                    const savedMeta = res.savedDomains[0];
+                    const savedMeta = data.savedDomains[0];
 
                     const idx = _.findIndex($scope.domains, function(domain) {
                         return domain._id === savedMeta._id;
@@ -1400,16 +1400,16 @@ export default ['domainsController', [
                         if (_.includes(item.caches, cache.value))
                             cache.cache.domains = _.union(cache.cache.domains, [savedMeta._id]);
                         else
-                            _.remove(cache.cache.domains, (id) => id === savedMeta._id);
+                            _.pull(cache.cache.domains, savedMeta._id);
                     });
 
                     $scope.selectItem(savedMeta);
 
-                    Messages.showInfo('Domain model "' + item.valueType + '" saved.');
+                    Messages.showInfo(`Domain model "${item.valueType}" saved.`);
 
                     _checkShowValidPresentation();
                 })
-                .error(Messages.showError);
+                .catch(Messages.showError);
         }
 
         // Save domain model.
@@ -1469,14 +1469,12 @@ export default ['domainsController', [
                     const _id = selectedItem._id;
 
                     $http.post('/api/v1/configuration/domains/remove', {_id})
-                        .success(function() {
+                        .then(() => {
                             Messages.showInfo('Domain model has been removed: ' + selectedItem.valueType);
 
                             const domains = $scope.domains;
 
-                            const idx = _.findIndex(domains, function(domain) {
-                                return domain._id === _id;
-                            });
+                            const idx = _.findIndex(domains, {_id});
 
                             if (idx >= 0) {
                                 domains.splice(idx, 1);
@@ -1488,12 +1486,12 @@ export default ['domainsController', [
                                 else
                                     $scope.backupItem = emptyDomain;
 
-                                _.forEach($scope.caches, (cache) => _.remove(cache.cache.domains, (id) => id === _id));
+                                _.forEach($scope.caches, (cache) => _.pull(cache.cache.domains, _id));
                             }
 
                             _checkShowValidPresentation();
                         })
-                        .error(Messages.showError);
+                        .catch(Messages.showError);
                 });
         };
 
@@ -1504,7 +1502,7 @@ export default ['domainsController', [
             Confirm.confirm('Are you sure you want to remove all domain models?')
                 .then(function() {
                     $http.post('/api/v1/configuration/domains/remove/all')
-                        .success(function() {
+                        .then(() => {
                             Messages.showInfo('All domain models have been removed');
 
                             $scope.domains = [];
@@ -1516,7 +1514,7 @@ export default ['domainsController', [
                             $scope.ui.inputForm.$error = {};
                             $scope.ui.inputForm.$setPristine();
                         })
-                        .error(Messages.showError);
+                        .catch(Messages.showError);
                 });
         };
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/modules/web-console/frontend/controllers/igfs-controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/controllers/igfs-controller.js b/modules/web-console/frontend/controllers/igfs-controller.js
index e505f1c..b3c6043 100644
--- a/modules/web-console/frontend/controllers/igfs-controller.js
+++ b/modules/web-console/frontend/controllers/igfs-controller.js
@@ -296,12 +296,12 @@ export default ['igfsController', [
         // Save IGFS in database.
         function save(item) {
             $http.post('/api/v1/configuration/igfs/save', item)
-                .success(function(_id) {
+                .then(({data}) => {
+                    const _id = data;
+
                     $scope.ui.inputForm.$setPristine();
 
-                    const idx = _.findIndex($scope.igfss, function(igfs) {
-                        return igfs._id === _id;
-                    });
+                    const idx = _.findIndex($scope.igfss, {_id});
 
                     if (idx >= 0)
                         _.assign($scope.igfss[idx], item);
@@ -312,9 +312,9 @@ export default ['igfsController', [
 
                     $scope.selectItem(item);
 
-                    Messages.showInfo('IGFS "' + item.name + '" saved.');
+                    Messages.showInfo(`IGFS "${item.name}" saved.`);
                 })
-                .error(Messages.showError);
+                .catch(Messages.showError);
         }
 
         // Save IGFS.
@@ -359,7 +359,7 @@ export default ['igfsController', [
                     const _id = selectedItem._id;
 
                     $http.post('/api/v1/configuration/igfs/remove', {_id})
-                        .success(function() {
+                        .then(() => {
                             Messages.showInfo('IGFS has been removed: ' + selectedItem.name);
 
                             const igfss = $scope.igfss;
@@ -379,7 +379,7 @@ export default ['igfsController', [
                                     $scope.backupItem = emptyIgfs;
                             }
                         })
-                        .error(Messages.showError);
+                        .catch(Messages.showError);
                 });
         };
 
@@ -390,7 +390,7 @@ export default ['igfsController', [
             Confirm.confirm('Are you sure you want to remove all IGFS?')
                 .then(function() {
                     $http.post('/api/v1/configuration/igfs/remove/all')
-                        .success(function() {
+                        .then(() => {
                             Messages.showInfo('All IGFS have been removed');
 
                             $scope.igfss = [];
@@ -398,7 +398,7 @@ export default ['igfsController', [
                             $scope.ui.inputForm.$error = {};
                             $scope.ui.inputForm.$setPristine();
                         })
-                        .error(Messages.showError);
+                        .catch(Messages.showError);
                 });
         };
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/modules/web-console/frontend/controllers/profile-controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/controllers/profile-controller.js b/modules/web-console/frontend/controllers/profile-controller.js
index fd595d9..87a8805 100644
--- a/modules/web-console/frontend/controllers/profile-controller.js
+++ b/modules/web-console/frontend/controllers/profile-controller.js
@@ -74,7 +74,6 @@ export default ['profileController', [
 
         $scope.saveUser = () => {
             $http.post('/api/v1/profile/save', $scope.user)
-                .catch(({data}) => Promise.reject(data))
                 .then(User.load)
                 .then(() => {
                     if ($scope.expandedPassword)
@@ -89,7 +88,7 @@ export default ['profileController', [
 
                     $root.$broadcast('user', $scope.user);
                 })
-                .catch((err) => Messages.showError(Messages.errorMessage('Failed to save profile: ', err)));
+                .catch((res) => Messages.showError('Failed to save profile: ', res));
         };
     }
 ]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/modules/web-console/frontend/gulpfile.babel.js/webpack/common.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/gulpfile.babel.js/webpack/common.js b/modules/web-console/frontend/gulpfile.babel.js/webpack/common.js
index 2463d24..7360ac4 100644
--- a/modules/web-console/frontend/gulpfile.babel.js/webpack/common.js
+++ b/modules/web-console/frontend/gulpfile.babel.js/webpack/common.js
@@ -20,7 +20,7 @@ import fs from 'fs';
 import webpack from 'webpack';
 import autoprefixer from 'autoprefixer-core';
 import jade from 'jade';
-import progressPlugin from './plugins/progress';
+import ProgressBarPlugin from 'progress-bar-webpack-plugin';
 import eslintFormatter from 'eslint-friendly-formatter';
 
 import ExtractTextPlugin from 'extract-text-webpack-plugin';
@@ -61,7 +61,6 @@ export default () => {
         // Output system.
         output: {
             path: destDir,
-            publicPath: './',
             filename: '[name].js'
         },
 
@@ -111,8 +110,10 @@ export default () => {
                     loader: 'babel-loader',
                     query: {
                         cacheDirectory: true,
-                        plugins: ['transform-runtime',
-                            'add-module-exports'],
+                        plugins: [
+                            'transform-runtime',
+                            'add-module-exports'
+                        ],
                         presets: ['angular']
 
                     }
@@ -126,10 +127,8 @@ export default () => {
                     loader: development ? `style-loader!${stylesLoader}` : ExtractTextPlugin.extract('style-loader', stylesLoader)
                 },
                 {
-                    test: /\.(woff2|woff|ttf|eot|svg)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
-                    loaders: [
-                        `${assetsLoader}?name=assets/fonts/[name].[ext]`
-                    ]
+                    test: /\.(ttf|eot|svg|woff(2)?)(\?v=[\d.]+)?(\?[a-z0-9#-]+)?$/,
+                    loaders: [`${assetsLoader}?name=assets/fonts/[name].[ext]`]
                 },
                 {
                     test: /\.(jpe?g|png|gif)$/i,
@@ -186,7 +185,7 @@ export default () => {
                 },
                 favicon
             }),
-            progressPlugin
+            new ProgressBarPlugin()
         ]
     };
 };

http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/modules/web-console/frontend/gulpfile.babel.js/webpack/environments/development.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/gulpfile.babel.js/webpack/environments/development.js b/modules/web-console/frontend/gulpfile.babel.js/webpack/environments/development.js
index cad9133..34e1f6a 100644
--- a/modules/web-console/frontend/gulpfile.babel.js/webpack/environments/development.js
+++ b/modules/web-console/frontend/gulpfile.babel.js/webpack/environments/development.js
@@ -20,9 +20,8 @@ import webpack from 'webpack';
 
 import {destDir, rootDir, srcDir} from '../../paths';
 
-const devServerHost = 'localhost';
+const backendPort = 3000;
 const devServerPort = 9000;
-const devServerUrl = `http://${devServerHost}:${devServerPort}/`;
 
 export default () => {
     const plugins = [
@@ -31,11 +30,10 @@ export default () => {
 
     return {
         entry: {
-            webpack: `webpack-dev-server/client?${devServerUrl}`,
             app: [path.join(srcDir, 'app.js'), 'webpack/hot/only-dev-server']
         },
         output: {
-            publicPath: devServerUrl
+            publicPath: `http://localhost:${devServerPort}/`
         },
         context: rootDir,
         debug: true,
@@ -44,24 +42,22 @@ export default () => {
         devServer: {
             compress: true,
             historyApiFallback: true,
-            publicPath: '/',
             contentBase: destDir,
-            info: true,
             hot: true,
             inline: true,
             proxy: {
                 '/socket.io': {
-                    target: 'http://localhost:3000',
+                    target: `http://localhost:${backendPort}`,
                     changeOrigin: true,
                     ws: true
                 },
                 '/agents': {
-                    target: 'http://localhost:3000',
+                    target: `http://localhost:${backendPort}`,
                     changeOrigin: true,
                     ws: true
                 },
                 '/api/v1/*': {
-                    target: 'http://localhost:3000',
+                    target: `http://localhost:${backendPort}`,
                     changeOrigin: true,
                     pathRewrite: {
                         '^/api/v1': ''

http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/modules/web-console/frontend/gulpfile.babel.js/webpack/environments/production.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/gulpfile.babel.js/webpack/environments/production.js b/modules/web-console/frontend/gulpfile.babel.js/webpack/environments/production.js
index db66720..1194568 100644
--- a/modules/web-console/frontend/gulpfile.babel.js/webpack/environments/production.js
+++ b/modules/web-console/frontend/gulpfile.babel.js/webpack/environments/production.js
@@ -37,8 +37,7 @@ export default () => {
         devtool: 'cheap-source-map',
         output: {
             publicPath: '/',
-            filename: '[name].[chunkhash].js',
-            path: destDir
+            filename: '[name].[chunkhash].js'
         },
         plugins
     };

http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/modules/web-console/frontend/gulpfile.babel.js/webpack/plugins/progress.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/gulpfile.babel.js/webpack/plugins/progress.js b/modules/web-console/frontend/gulpfile.babel.js/webpack/plugins/progress.js
deleted file mode 100644
index 5f753c7..0000000
--- a/modules/web-console/frontend/gulpfile.babel.js/webpack/plugins/progress.js
+++ /dev/null
@@ -1,82 +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 ProgressPlugin from 'webpack/lib/ProgressPlugin';
-
-let chars = 0;
-let lastState = 0;
-let lastStateTime = 0;
-
-const outputStream = process.stdout;
-
-const _goToLineStart = (nextMessage) => {
-    let str = '';
-
-    for (; chars > nextMessage.length; chars--)
-        str += '\b \b';
-
-    chars = nextMessage.length;
-
-    for (let i = 0; i < chars; i++)
-        str += '\b';
-
-    if (str)
-        outputStream.write(str);
-};
-
-export default new ProgressPlugin((percentage, msg) => {
-    let state = msg;
-
-    if (percentage < 1) {
-        percentage = Math.floor(percentage * 100);
-
-        msg = percentage + '% ' + msg;
-
-        if (percentage < 100)
-            msg = ' ' + msg;
-
-        if (percentage < 10)
-            msg = ' ' + msg;
-    }
-
-    state = state.replace(/^\d+\/\d+\s+/, '');
-
-    if (percentage === 0) {
-        lastState = null;
-        lastStateTime = (new Date()).getTime();
-    }
-    else if (state !== lastState || percentage === 1) {
-        const now = (new Date()).getTime();
-
-        if (lastState) {
-            const stateMsg = (now - lastStateTime) + 'ms ' + lastState;
-
-            _goToLineStart(stateMsg);
-
-            outputStream.write(stateMsg + '\n');
-
-            chars = 0;
-        }
-
-        lastState = state;
-        lastStateTime = now;
-    }
-
-    _goToLineStart(msg);
-
-    outputStream.write(msg);
-});

http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/modules/web-console/frontend/package.json
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/package.json b/modules/web-console/frontend/package.json
index b511ca1..fd50d5b 100644
--- a/modules/web-console/frontend/package.json
+++ b/modules/web-console/frontend/package.json
@@ -29,97 +29,99 @@
     "win32"
   ],
   "dependencies": {
-    "angular": "^1.5.5",
-    "angular-acl": "^0.1.7",
-    "angular-animate": "^1.5.5",
-    "angular-aria": "^1.5.5",
-    "angular-cookies": "^1.5.5",
-    "angular-drag-and-drop-lists": "^1.4.0",
-    "angular-gridster": "^0.13.3",
-    "angular-motion": "^0.4.4",
-    "angular-nvd3": "^1.0.7",
-    "angular-retina": "^0.3.13",
-    "angular-sanitize": "^1.5.5",
-    "angular-smart-table": "^2.1.8",
-    "angular-socket-io": "^0.7.0",
-    "angular-strap": "^2.3.8",
-    "angular-touch": "^1.5.5",
-    "angular-tree-control": "^0.2.26",
-    "angular-ui-grid": "^3.1.1",
-    "angular-ui-router": "^0.3.1",
-    "bootstrap-sass": "^3.3.6",
-    "brace": "^0.8.0",
-    "es6-promise": "^3.0.2",
-    "file-saver": "^1.3.2",
-    "font-awesome": "^4.6.3",
-    "glob": "^7.0.3",
-    "jquery": "^3.0.0",
-    "jszip": "^3.0.0",
-    "lodash": "^4.8.2",
-    "nvd3": "^1.8.3",
-    "raleway-webfont": "^3.0.1",
-    "roboto-font": "^0.1.0",
-    "socket.io-client": "^1.4.6",
-    "ui-router-metatags": "^1.0.3"
+    "angular": "~1.5.9",
+    "angular-acl": "~0.1.7",
+    "angular-animate": "~1.5.9",
+    "angular-aria": "~1.5.9",
+    "angular-cookies": "~1.5.9",
+    "angular-drag-and-drop-lists": "~1.4.0",
+    "angular-gridster": "~0.13.3",
+    "angular-motion": "~0.4.4",
+    "angular-nvd3": "~1.0.9",
+    "angular-retina": "~0.3.13",
+    "angular-sanitize": "~1.5.9",
+    "angular-smart-table": "~2.1.8",
+    "angular-socket-io": "~0.7.0",
+    "angular-strap": "~2.3.8",
+    "angular-touch": "~1.5.9",
+    "angular-tree-control": "~0.2.26",
+    "angular-ui-grid": "~3.2.9",
+    "angular-ui-router": "~0.3.1",
+    "bootstrap-sass": "~3.3.6",
+    "brace": "~0.8.0",
+    "es6-promise": "~3.3.1",
+    "file-saver": "~1.3.2",
+    "font-awesome": "~4.7.0",
+    "glob": "~7.1.1",
+    "jquery": "~3.1.1",
+    "jszip": "~3.1.3",
+    "lodash": "~4.17.2",
+    "nvd3": "1.8.4",
+    "raleway-webfont": "~3.0.1",
+    "roboto-font": "~0.1.0",
+    "socket.io-client": "~1.7.2",
+    "ui-router-metatags": "~1.0.3"
   },
   "devDependencies": {
-    "assets-webpack-plugin": "^3.2.0",
-    "autoprefixer-core": "^6.0.1",
-    "babel-core": "^6.7.6",
-    "babel-eslint": "^7.0.0",
-    "babel-loader": "^6.2.4",
-    "babel-plugin-add-module-exports": "^0.2.1",
-    "babel-plugin-transform-builtin-extend": "^1.1.0",
-    "babel-plugin-transform-runtime": "^6.7.5",
-    "babel-polyfill": "^6.7.4",
-    "babel-preset-angular": "^6.0.15",
-    "babel-preset-es2015": "^6.9.0",
-    "babel-runtime": "^6.6.1",
-    "chai": "^3.5.0",
-    "cross-env": "^1.0.7",
-    "css-loader": "^0.23.0",
-    "eslint": "^3.0.0",
-    "eslint-friendly-formatter": "^2.0.5",
-    "eslint-loader": "^1.0.0",
-    "expose-loader": "^0.7.1",
-    "extract-text-webpack-plugin": "^1.0.1",
-    "file-loader": "^0.9.0",
-    "gulp": "^3.9.1",
-    "gulp-eslint": "^3.0.0",
-    "gulp-inject": "^4.0.0",
-    "gulp-jade": "^1.1.0",
-    "gulp-ll": "^1.0.4",
-    "gulp-rimraf": "^0.2.0",
-    "gulp-sequence": "^0.4.1",
-    "gulp-util": "^3.0.7",
-    "html-loader": "^0.4.3",
-    "html-webpack-plugin": "^2.21.0",
-    "jade": "^1.11.0",
+    "assets-webpack-plugin": "~3.5.0",
+    "autoprefixer-core": "~6.0.1",
+    "babel-core": "~6.20.0",
+    "babel-eslint": "~7.0.0",
+    "babel-loader": "~6.2.4",
+    "babel-plugin-add-module-exports": "~0.2.1",
+    "babel-plugin-transform-builtin-extend": "~1.1.0",
+    "babel-plugin-transform-runtime": "~6.15.0",
+    "babel-polyfill": "~6.20.0",
+    "babel-preset-angular": "~6.0.15",
+    "babel-preset-es2015": "~6.18.0",
+    "babel-runtime": "~6.20.0",
+    "chai": "~3.5.0",
+    "cross-env": "~1.0.7",
+    "css-loader": "~0.23.0",
+    "eslint": "~3.12.2",
+    "eslint-friendly-formatter": "~2.0.5",
+    "eslint-loader": "~1.6.1",
+    "expose-loader": "~0.7.1",
+    "extract-text-webpack-plugin": "~1.0.1",
+    "file-loader": "~0.9.0",
+    "gulp": "~3.9.1",
+    "gulp-eslint": "~3.0.0",
+    "gulp-inject": "~4.1.0",
+    "gulp-jade": "~1.1.0",
+    "gulp-ll": "~1.0.4",
+    "gulp-rimraf": "~0.2.0",
+    "gulp-sequence": "~0.4.1",
+    "gulp-util": "~3.0.7",
+    "html-loader": "~0.4.3",
+    "html-webpack-plugin": "~2.24.1",
+    "jade": "~1.11.0",
     "jade-html-loader": "git://github.com/courcelan/jade-html-loader",
-    "jasmine-core": "^2.4.1",
-    "json-loader": "^0.5.4",
-    "karma": "^0.13.22",
-    "karma-babel-preprocessor": "^6.0.1",
-    "karma-jasmine": "^1.0.2",
-    "karma-mocha": "^1.0.1",
-    "karma-mocha-reporter": "^2.2.0",
-    "karma-phantomjs-launcher": "^1.0.0",
-    "karma-teamcity-reporter": "^1.0.0",
-    "karma-webpack": "^1.7.0",
+    "jasmine-core": "~2.5.2",
+    "json-loader": "~0.5.4",
+    "karma": "~0.13.22",
+    "karma-babel-preprocessor": "~6.0.1",
+    "karma-jasmine": "~1.1.0",
+    "karma-mocha": "~1.3.0",
+    "karma-mocha-reporter": "~2.2.0",
+    "karma-phantomjs-launcher": "~1.0.0",
+    "karma-teamcity-reporter": "~1.0.0",
+    "karma-webpack": "~1.8.0",
     "mocha": "~2.5.3",
-    "mocha-teamcity-reporter": "^1.0.0",
-    "morgan": "^1.7.0",
-    "ngtemplate-loader": "^1.3.1",
-    "node-sass": "^3.4.2",
-    "phantomjs-prebuilt": "^2.1.7",
-    "postcss-loader": "^0.9.1",
-    "require-dir": "^0.3.0",
-    "resolve-url-loader": "^1.4.3",
-    "sass-loader": "^3.1.1",
-    "style-loader": "^0.13.1",
-    "url": "^0.11.0",
-    "url-loader": "^0.5.6",
-    "webpack": "^1.13.1",
-    "webpack-dev-server": "^1.15.0"
+    "mocha-teamcity-reporter": "~1.1.1",
+    "morgan": "~1.7.0",
+    "ngtemplate-loader": "~1.3.1",
+    "node-sass": "~3.13.1",
+    "phantomjs-prebuilt": "~2.1.7",
+    "postcss-loader": "~0.9.1",
+    "progress-bar-webpack-plugin": "~1.9.0",
+    "require-dir": "~0.3.0",
+    "resolve-url-loader": "~1.6.1",
+    "sass-loader": "~3.1.1",
+    "style-loader": "~0.13.1",
+    "url": "~0.11.0",
+    "url-loader": "~0.5.6",
+    "webpack": "~1.14.0",
+    "webpack-dev-server": "~1.16.2",
+    "worker-loader": "~0.7.1"
   }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/modules/web-console/frontend/public/images/cache.png
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/public/images/cache.png b/modules/web-console/frontend/public/images/cache.png
index 83fd987..3ff3103 100644
Binary files a/modules/web-console/frontend/public/images/cache.png and b/modules/web-console/frontend/public/images/cache.png differ

http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/modules/web-console/frontend/public/images/domains.png
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/public/images/domains.png b/modules/web-console/frontend/public/images/domains.png
index 39abfcb..41c0470 100644
Binary files a/modules/web-console/frontend/public/images/domains.png and b/modules/web-console/frontend/public/images/domains.png differ

http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/modules/web-console/frontend/public/images/igfs.png
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/public/images/igfs.png b/modules/web-console/frontend/public/images/igfs.png
index 47c659e..b62c27b 100644
Binary files a/modules/web-console/frontend/public/images/igfs.png and b/modules/web-console/frontend/public/images/igfs.png differ

http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/modules/web-console/frontend/public/images/query-chart.png
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/public/images/query-chart.png b/modules/web-console/frontend/public/images/query-chart.png
index c6e4cce..1b7ef41 100644
Binary files a/modules/web-console/frontend/public/images/query-chart.png and b/modules/web-console/frontend/public/images/query-chart.png differ

http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/modules/web-console/frontend/public/images/query-metadata.png
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/public/images/query-metadata.png b/modules/web-console/frontend/public/images/query-metadata.png
index 698cd6e..1b6c73c 100644
Binary files a/modules/web-console/frontend/public/images/query-metadata.png and b/modules/web-console/frontend/public/images/query-metadata.png differ

http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/modules/web-console/frontend/public/images/query-table.png
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/public/images/query-table.png b/modules/web-console/frontend/public/images/query-table.png
index 53becda..4d63a68 100644
Binary files a/modules/web-console/frontend/public/images/query-table.png and b/modules/web-console/frontend/public/images/query-table.png differ

http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/modules/web-console/frontend/public/images/summary.png
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/public/images/summary.png b/modules/web-console/frontend/public/images/summary.png
index ff88438..fda0abf 100644
Binary files a/modules/web-console/frontend/public/images/summary.png and b/modules/web-console/frontend/public/images/summary.png differ

http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/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 15ee60c..bfa6c6c 100644
--- a/modules/web-console/frontend/public/stylesheets/_font-awesome-custom.scss
+++ b/modules/web-console/frontend/public/stylesheets/_font-awesome-custom.scss
@@ -47,4 +47,25 @@ $fa-font-path: '~font-awesome/fonts';
   @extend .fa-question-circle-o;
 
   cursor: default;
-}
\ No newline at end of file
+}
+
+.icon-note {
+  @extend .fa;
+  @extend .fa-info-circle;
+
+  cursor: default;
+}
+
+.icon-danger {
+  @extend .fa;
+  @extend .fa-exclamation-triangle;
+
+  cursor: default;
+}
+
+.icon-success {
+  @extend .fa;
+  @extend .fa-check-circle-o;
+
+  cursor: default;
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/modules/web-console/frontend/public/stylesheets/form-field.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/public/stylesheets/form-field.scss b/modules/web-console/frontend/public/stylesheets/form-field.scss
index f126786..ae33d75 100644
--- a/modules/web-console/frontend/public/stylesheets/form-field.scss
+++ b/modules/web-console/frontend/public/stylesheets/form-field.scss
@@ -106,3 +106,40 @@
         @include make-lg-column(8);
     }
 }
+
+.ignite-form-field {
+    &__btn {
+        overflow: hidden;
+
+        border-top-left-radius: 0;
+        border-bottom-left-radius: 0;
+
+        &.btn {
+            float: right;
+            margin-right: 0;
+
+            line-height: 20px;
+        }
+
+        input {
+            position: absolute;
+            left: 100px;
+        }
+
+        input:checked + span {
+            color: $brand-info;
+        }
+    }
+
+    &__btn ~ &__btn {
+        border-right: 0;
+        border-top-right-radius: 0;
+        border-bottom-right-radius: 0;  
+    }
+
+    &__btn ~ .input-tip input {
+        border-right: 0;
+        border-top-right-radius: 0;
+        border-bottom-right-radius: 0;  
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/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 172abf4..4318fc2 100644
--- a/modules/web-console/frontend/public/stylesheets/style.scss
+++ b/modules/web-console/frontend/public/stylesheets/style.scss
@@ -227,7 +227,7 @@ ul.navbar-nav, .sidebar-nav {
         overflow: hidden;
         white-space: nowrap;
         text-overflow: ellipsis;
-        
+
         &:hover,
         &:focus {
             text-decoration: none;
@@ -601,6 +601,10 @@ button.form-control {
 .theme-line .notebook-header {
     border-color: $gray-lighter;
 
+    button:last-child {
+        margin-right: 0;
+    }
+
     h1 {
         padding: 0;
         margin: 0;
@@ -611,7 +615,7 @@ button.form-control {
             overflow: hidden;
             text-overflow: ellipsis;
             white-space: nowrap;
-            margin-top: 5px;
+            height: 24px;
         }
 
         .btn-group {
@@ -637,7 +641,7 @@ button.form-control {
 }
 
 .theme-line .paragraphs {
-    .panel-group .panel + .panel {
+    .panel-group .panel-paragraph + .panel-paragraph {
         margin-top: 30px;
     }
 
@@ -679,8 +683,14 @@ button.form-control {
         line-height: 55px;
     }
 
-    .sql-controls {
+    .panel-collapse {
         border-top: 1px solid $ignite-border-color;
+    }
+
+    .sql-controls {
+        position: relative;
+        top: -1px;
+        border-top: 1px solid #ddd;
 
         padding: 10px 10px;
 
@@ -690,9 +700,50 @@ button.form-control {
         }
 
         label {
-            line-height: 20px !important;
+            line-height: 28px;
             vertical-align: middle;
         }
+
+        .btn {
+            line-height: 20px;
+        }
+
+        .ignite-form-field {
+            margin-right: 10px;
+
+            .ignite-form-field__label {
+                float: left;
+                width: auto;
+                margin-right: 5px;
+                line-height: 28px;
+            }
+
+            .ignite-form-field__label + div {
+                display: block;
+                float: none;
+                width: auto;
+            }
+        }
+
+        .tipLabel .btn {
+            float: right;
+        }
+
+        .pull-right {
+            margin-left: 10px;
+
+            .ignite-form-field {
+                margin-right: -24px;
+
+                label {
+                    margin-left: 5px;
+                }
+            }
+        }
+
+        .col-sm-3 + .tipLabel {
+            margin-left: 0;
+        }
     }
 
     .sql-result {
@@ -1243,6 +1294,17 @@ button.form-control {
     .clickable { cursor: pointer; }
 }
 
+
+.theme-line .summary {
+    .actions-note {
+        i {
+            margin-right: 5px;
+        }
+
+        margin: 15px 0;
+    }
+}
+
 .theme-line .popover.summary-project-structure {
     @extend .popover.settings;
 
@@ -1693,6 +1755,7 @@ th[st-sort] {
 }
 
 .chart-settings-link {
+    margin-top: -2px;
     padding-left: 10px;
     line-height: $input-height;
 
@@ -1991,6 +2054,10 @@ treecontrol.tree-classic {
         margin-right: 0
     }
 
+    .ui-grid-cell-actions {
+        line-height: 28px;
+    }
+
     .no-rows {
         .center-container {
             background: white;
@@ -2226,3 +2293,37 @@ html,body,.splash-screen {
         animation: none 0s;
     }
 }
+
+.admin-page {
+    .panel-heading {
+        border-bottom: 0;
+        padding-bottom: 0;
+
+        cursor: default;
+
+        i {
+            margin-right: 10px;
+        }
+
+        label {
+            cursor: default;
+            line-height: 24px;
+        }
+
+        sub {
+            bottom: 0;
+        }
+    }
+
+    .ui-grid-header-cell input {
+      font-weight: normal;
+    }
+
+    .ui-grid-header-cell input {
+      font-weight: normal;
+    }
+
+    .ui-grid-filter-select {
+        width: calc(100% - 10px);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/modules/web-console/frontend/test/unit/JavaTypes.test.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/test/unit/JavaTypes.test.js b/modules/web-console/frontend/test/unit/JavaTypes.test.js
index 2df8c6a..49e78cc 100644
--- a/modules/web-console/frontend/test/unit/JavaTypes.test.js
+++ b/modules/web-console/frontend/test/unit/JavaTypes.test.js
@@ -17,11 +17,11 @@
 
 import JavaTypes from '../../app/services/JavaTypes.service.js';
 
-import ClusterDflts from '../../app/modules/configuration/generator/defaults/cluster.provider';
-import CacheDflts from '../../app/modules/configuration/generator/defaults/cache.provider';
-import IgfsDflts from '../../app/modules/configuration/generator/defaults/igfs.provider';
+import ClusterDflts from '../../app/modules/configuration/generator/defaults/Cluster.service';
+import CacheDflts from '../../app/modules/configuration/generator/defaults/Cache.service';
+import IgfsDflts from '../../app/modules/configuration/generator/defaults/IGFS.service';
 
-const INSTANCE = new JavaTypes((new ClusterDflts()).$get[0](), (new CacheDflts()).$get[0](), (new IgfsDflts()).$get[0]());
+const INSTANCE = new JavaTypes(new ClusterDflts(), new CacheDflts(), new IgfsDflts());
 
 import { assert } from 'chai';
 
@@ -58,9 +58,14 @@ suite('JavaTypesTestsSuite', () => {
 
     test('shortClassName', () => {
         assert.equal(INSTANCE.shortClassName('java.math.BigDecimal'), 'BigDecimal');
+        assert.equal(INSTANCE.shortClassName('BigDecimal'), 'BigDecimal');
         assert.equal(INSTANCE.shortClassName('int'), 'int');
         assert.equal(INSTANCE.shortClassName('java.lang.Integer'), 'Integer');
+        assert.equal(INSTANCE.shortClassName('Integer'), 'Integer');
         assert.equal(INSTANCE.shortClassName('java.util.UUID'), 'UUID');
+        assert.equal(INSTANCE.shortClassName('java.sql.Date'), 'Date');
+        assert.equal(INSTANCE.shortClassName('Date'), 'Date');
+        assert.equal(INSTANCE.shortClassName('com.my.Abstract'), 'Abstract');
         assert.equal(INSTANCE.shortClassName('Abstract'), 'Abstract');
     });
 
@@ -113,8 +118,8 @@ suite('JavaTypesTestsSuite', () => {
         assert.equal(INSTANCE.isKeyword(' '), false);
     });
 
-    test('isJavaPrimitive', () => {
-        assert.equal(INSTANCE.isJavaPrimitive('boolean'), true);
+    test('isPrimitive', () => {
+        assert.equal(INSTANCE.isPrimitive('boolean'), true);
     });
 
     test('validUUID', () => {

http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/modules/web-console/frontend/test/unit/Version.test.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/test/unit/Version.test.js b/modules/web-console/frontend/test/unit/Version.test.js
index a67fde8..2d75ab5 100644
--- a/modules/web-console/frontend/test/unit/Version.test.js
+++ b/modules/web-console/frontend/test/unit/Version.test.js
@@ -39,7 +39,13 @@ suite('VersionServiceTestsSuite', () => {
     });
 
     test('Version a = b', () => {
-        assert.equal(INSTANCE.compare('1.7.0', '1.7.0'), 0);
+        assert.equal(INSTANCE.compare('1.0.0', '1.0.0'), 0);
+        assert.equal(INSTANCE.compare('1.2.0', '1.2.0'), 0);
+        assert.equal(INSTANCE.compare('1.2.3', '1.2.3'), 0);
+
+        assert.equal(INSTANCE.compare('1.0.0-1', '1.0.0-1'), 0);
+        assert.equal(INSTANCE.compare('1.2.0-1', '1.2.0-1'), 0);
+        assert.equal(INSTANCE.compare('1.2.3-1', '1.2.3-1'), 0);
     });
 
     test('Version a < b', () => {

http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/modules/web-console/frontend/views/configuration/domains-import.jade
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/views/configuration/domains-import.jade b/modules/web-console/frontend/views/configuration/domains-import.jade
index e4f95bc..bbcb391 100644
--- a/modules/web-console/frontend/views/configuration/domains-import.jade
+++ b/modules/web-console/frontend/views/configuration/domains-import.jade
@@ -62,7 +62,10 @@ mixin td-ellipses-lbl(w, lbl)
                             +ignite-form-field-dropdown('Driver JAR:', 'ui.selectedJdbcDriverJar', '"jdbcDriverJar"', false, true, false,
                                 'Choose JDBC driver', '', 'jdbcDriverJars',
                                 'Select appropriate JAR with JDBC driver<br> To add another driver you need to place it into "/jdbc-drivers" folder of Ignite Web Agent<br> Refer to Ignite Web Agent README.txt for for more information'
-                            )(data-container='.modal-domain-import')
+                            )(
+                                data-container='.modal-domain-import'
+                                data-ignite-form-field-input-autofocus='true'
+                            )
                         .settings-row.settings-row_small-label
                             +java-class('JDBC driver:', 'selectedPreset.jdbcDriverClass', '"jdbcDriverClass"', true, true, 'Fully qualified class name of JDBC driver that will be used to connect to database')
                         .settings-row.settings-row_small-label

http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/modules/web-console/frontend/views/configuration/summary.jade
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/views/configuration/summary.jade b/modules/web-console/frontend/views/configuration/summary.jade
index 9a6e553..a04f0db 100644
--- a/modules/web-console/frontend/views/configuration/summary.jade
+++ b/modules/web-console/frontend/views/configuration/summary.jade
@@ -21,7 +21,7 @@ mixin hard-link(ref, txt)
 
 .docs-header
     h1 Configurations Summary
-.docs-body
+.docs-body.summary
     ignite-information
         ul
             li Preview XML configurations for #[a(href='https://apacheignite.readme.io/docs/clients-vs-servers' target='_blank') server and client] nodes
@@ -29,7 +29,6 @@ mixin hard-link(ref, txt)
             li Preview #[a(href='https://apacheignite.readme.io/docs/docker-deployment' target='_blank') Docker file]
             li Preview POM dependencies
             li Download ready-to-use Maven project
-
     hr
     .padding-dflt(ng-if='ui.ready && (!clusters || clusters.length == 0)')
         | You have no clusters configured. Please configure them #[a(ui-sref='base.configuration.clusters') here].
@@ -37,13 +36,21 @@ mixin hard-link(ref, txt)
     div(ng-show='clusters && clusters.length > 0' ignite-loading='summaryPage' ignite-loading-text='Loading summary screen...' ignite-loading-position='top')
         +main-table('clusters', 'clustersView', 'clusterName', 'selectItem(row)', '{{$index + 1}}) {{row.name}}', 'name')
         div(ng-show='selectedItem && contentVisible(displayedRows, selectedItem)')
-            .padding-top-dflt(bs-affix)
-                button.btn.btn-primary(id='download' ng-click='downloadConfiguration()' bs-tooltip='' data-title='Download project' data-placement='bottom') Download project
-                .btn.btn-primary(bs-tooltip='' data-title='Preview generated project structure' data-placement='bottom')
-                    div(bs-popover data-template-url='/configuration/summary-project-structure.html', data-placement='bottom', data-trigger='click' data-auto-close='true')
-                        i.fa.fa-sitemap
-                        label.tipLabel Project structure
-                button.btn.btn-primary(id='proprietary-jdbc-drivers' ng-if='downloadJdbcDriversVisible()' ng-click='downloadJdbcDrivers()' bs-tooltip='' data-title='Open proprietary JDBC drivers download pages' data-placement='bottom') Download JDBC drivers
+            .actions.padding-top-dflt(bs-affix)
+                div
+                    button.btn.btn-primary(id='download' ng-click='downloadConfiguration()' bs-tooltip='' data-title='Download project' data-placement='bottom' ng-disabled='isPrepareDownloading')
+                        div
+                            i.fa.fa-fw.fa-download(ng-hide='isPrepareDownloading')
+                            i.fa.fa-fw.fa-refresh.fa-spin(ng-show='isPrepareDownloading')
+                            span.tipLabel Download project
+                    button.btn.btn-primary(bs-tooltip='' data-title='Preview generated project structure' data-placement='bottom')
+                        div(bs-popover data-template-url='/configuration/summary-project-structure.html', data-placement='bottom', data-trigger='click' data-auto-close='true')
+                            i.fa.fa-sitemap
+                            label.tipLabel Project structure
+                    button.btn.btn-primary(id='proprietary-jdbc-drivers' ng-if='downloadJdbcDriversVisible()' ng-click='downloadJdbcDrivers()' bs-tooltip='' data-title='Open proprietary JDBC drivers download pages' data-placement='bottom') Download JDBC drivers
+                .actions-note(ng-show='ui.isSafari')
+                    i.icon-note
+                    label "Download project" is not fully supported in Safari. Please rename downloaded file from "Unknown" to "&lt;project-name&gt;.zip"
                 hr
             .bs-affix-fix
             .panel-group(bs-collapse ng-init='ui.activePanels=[0,1]' ng-model='ui.activePanels' data-allow-multiple='true')

http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/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 862d959..c985826 100644
--- a/modules/web-console/frontend/views/settings/admin.jade
+++ b/modules/web-console/frontend/views/settings/admin.jade
@@ -14,63 +14,38 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 
-.row(ng-controller='adminController')
+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')
     .docs-content.greedy
         .docs-header
             h1 List of registered users
             hr
         .docs-body
-            .col-xs-12
-                table.table.table-striped.table-vertical-middle.admin(st-table='displayedUsers' st-safe-src='users')
-                    thead
-                        tr
-                            th.header(colspan='10')
-                                .col-xs-3
-                                    input.form-control(type='text' st-search='label' placeholder='Filter users...')
-                                .col-xs-9.admin-summary.text-right(colspan='10')
-                                    strong Total users: {{ users.length }}
-                                .col-xs-offset-6.col-xs-6.text-right
-                                    div(st-pagination st-items-by-page='15' st-displayed-pages='5' st-template='../templates/pagination.html')
-                        tr
-                            th(st-sort='userName') User
-                            th(st-sort='email') Email
-                            th(st-sort='company') Company
-                            th(st-sort='country') Country
-                            th.col-xs-2(st-sort='lastLogin' st-sort-default='reverse') Last login
-                            th.text-nowrap(st-sort='counters.clusters' st-descending-first bs-tooltip='"Clusters count"' data-placement='top')
-                                i.fa.fa-sitemap()
-                            th.text-nowrap(st-sort='counters.models' st-descending-first bs-tooltip='"Models count"' data-placement='top')
-                                i.fa.fa-object-group()
-                            th.text-nowrap(st-sort='counters.caches' st-descending-first bs-tooltip='"Caches count"' data-placement='top')
-                                i.fa.fa-database()
-                            th.text-nowrap(st-sort='counters.igfs' st-descending-first bs-tooltip='"IGFS count"' data-placement='top')
-                                i.fa.fa-folder-o()
-                            th(width='1%') Actions
-                    tbody
-                        tr(ng-repeat='row in displayedUsers track by row._id')
-                            td {{::row.userName}}
-                            td
-                                a(ng-href='mailto:{{::row.email}}') {{::row.email}}
-                            td {{::row.company}}
-                            td {{::row.countryCode}}
-                            td {{::row.lastLogin | date:'medium'}}
-                            td {{::row.counters.clusters}}
-                            td {{::row.counters.models}}
-                            td {{::row.counters.caches}}
-                            td {{::row.counters.igfs}}
-                            td.text-center
-                                a.btn.btn-default.dropdown-toggle(bs-dropdown='' ng-show='row._id != user._id' data-placement='bottom-right')
-                                    i.fa.fa-gear &nbsp;
-                                    span.caret
-                                ul.dropdown-menu(role='menu')
-                                    li
-                                        a(ng-click='becomeUser(row)') Become this user
-                                    li
-                                        a(ng-click='toggleAdmin(row)' ng-if='row.admin && row._id !== user._id') Revoke admin
-                                        a(ng-click='toggleAdmin(row)' ng-if='!row.admin && row._id !== user._id')  Grant admin
-                                    li
-                                        a(ng-click='removeUser(row)') Remove user
-                    tfoot
-                        tr
-                            td.text-right(colspan='10')
-                                div(st-pagination st-items-by-page='15' st-displayed-pages='5' st-template='../templates/pagination.html')
+            .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)

http://git-wip-us.apache.org/repos/asf/ignite/blob/2b3a180f/modules/web-console/frontend/views/sql/notebook-new.jade
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/views/sql/notebook-new.jade b/modules/web-console/frontend/views/sql/notebook-new.jade
index 8d9e8c4..9585e92 100644
--- a/modules/web-console/frontend/views/sql/notebook-new.jade
+++ b/modules/web-console/frontend/views/sql/notebook-new.jade
@@ -21,7 +21,7 @@
                 button.close(ng-click='$hide()') &times;
                 h4.modal-title
                     i.fa.fa-file-o
-                    | New SQL notebook
+                    | New query notebook
             form.form-horizontal.modal-body.row(name='ui.inputForm' novalidate)
                 div
                     .col-sm-2


Mime
View raw message