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

[41/50] [abbrv] ignite git commit: Ignite Web Console beta2.

http://git-wip-us.apache.org/repos/asf/ignite/blob/541e17d0/modules/web-console/src/main/js/controllers/clusters-controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/controllers/clusters-controller.js b/modules/web-console/src/main/js/controllers/clusters-controller.js
index 8aecc9f..3277397 100644
--- a/modules/web-console/src/main/js/controllers/clusters-controller.js
+++ b/modules/web-console/src/main/js/controllers/clusters-controller.js
@@ -19,15 +19,15 @@
 import consoleModule from 'controllers/common-module';
 
 consoleModule.controller('clustersController', [
-    '$rootScope', '$scope', '$http', '$state', '$timeout', '$common', '$confirm', '$clone', '$loading', '$cleanup', '$unsavedChangesGuard', 'igniteEventGroups', 'DemoInfo',
-    function ($root, $scope, $http, $state, $timeout, $common, $confirm, $clone, $loading, $cleanup, $unsavedChangesGuard, igniteEventGroups, DemoInfo) {
+    '$rootScope', '$scope', '$http', '$state', '$timeout', '$common', '$confirm', '$clone', '$loading', '$cleanup', '$unsavedChangesGuard', 'igniteEventGroups', 'DemoInfo', '$table',
+    function($root, $scope, $http, $state, $timeout, $common, $confirm, $clone, $loading, $cleanup, $unsavedChangesGuard, igniteEventGroups, DemoInfo, $table) {
         $unsavedChangesGuard.install($scope);
 
-        var emptyCluster = {empty: true};
+        const emptyCluster = {empty: true};
 
-        var __original_value;
+        let __original_value;
 
-        var blank = {
+        const blank = {
             atomicConfiguration: {},
             binaryConfiguration: {},
             communication: {},
@@ -36,7 +36,109 @@ consoleModule.controller('clustersController', [
             marshaller: {},
             sslContextFactory: {},
             swapSpaceSpi: {},
-            transactionConfiguration: {}
+            transactionConfiguration: {},
+            collision: {}
+        };
+
+        const pairFields = {
+            attributes: {id: 'Attribute', idPrefix: 'Key', searchCol: 'name', valueCol: 'key', dupObjName: 'name', group: 'attributes'},
+            'collision.JobStealing.stealingAttributes': {id: 'CAttribute', idPrefix: 'Key', searchCol: 'name', valueCol: 'key', dupObjName: 'name', group: 'collision'}
+        };
+
+        const showPopoverMessage = $common.showPopoverMessage;
+
+        $scope.tablePairValid = function(item, field, index) {
+            const pairField = pairFields[field.model];
+
+            const pairValue = $table.tablePairValue(field, index);
+
+            if (pairField) {
+                const model = _.get(item, field.model);
+
+                if ($common.isDefined(model)) {
+                    const idx = _.findIndex(model, (pair) => {
+                        return pair[pairField.searchCol] === pairValue[pairField.valueCol];
+                    });
+
+                    // Found duplicate by key.
+                    if (idx >= 0 && idx !== index)
+                        return showPopoverMessage($scope.ui, pairField.group, $table.tableFieldId(index, pairField.idPrefix + pairField.id), 'Attribute with such ' + pairField.dupObjName + ' already exists!');
+                }
+            }
+
+            return true;
+        };
+
+        $scope.tableSave = function(field, index, stopEdit) {
+            if ($table.tablePairSaveVisible(field, index))
+                return $table.tablePairSave($scope.tablePairValid, $scope.backupItem, field, index, stopEdit);
+
+            return true;
+        };
+
+        $scope.tableReset = (trySave) => {
+            const field = $table.tableField();
+
+            if (trySave && $common.isDefined(field) && !$scope.tableSave(field, $table.tableEditedRowIndex(), true))
+                return false;
+
+            $table.tableReset();
+
+            return true;
+        };
+
+        $scope.tableNewItem = function(field) {
+            if ($scope.tableReset(true)) {
+                if (field.type === 'failoverSpi') {
+                    if ($common.isDefined($scope.backupItem.failoverSpi))
+                        $scope.backupItem.failoverSpi.push({});
+                    else
+                        $scope.backupItem.failoverSpi = {};
+                }
+                else
+                    $table.tableNewItem(field);
+            }
+        };
+
+        $scope.tableNewItemActive = $table.tableNewItemActive;
+
+        $scope.tableStartEdit = function(item, field, index) {
+            if ($scope.tableReset(true))
+                $table.tableStartEdit(item, field, index, $scope.tableSave);
+        };
+
+        $scope.tableEditing = $table.tableEditing;
+
+        $scope.tableRemove = function(item, field, index) {
+            if ($scope.tableReset(true))
+                $table.tableRemove(item, field, index);
+        };
+
+        $scope.tablePairSave = $table.tablePairSave;
+        $scope.tablePairSaveVisible = $table.tablePairSaveVisible;
+
+        $scope.attributesTbl = {
+            type: 'attributes',
+            model: 'attributes',
+            focusId: 'Attribute',
+            ui: 'table-pair',
+            keyName: 'name',
+            valueName: 'value',
+            save: $scope.tableSave
+        };
+
+        $scope.stealingAttributesTbl = {
+            type: 'attributes',
+            model: 'collision.JobStealing.stealingAttributes',
+            focusId: 'CAttribute',
+            ui: 'table-pair',
+            keyName: 'name',
+            valueName: 'value',
+            save: $scope.tableSave
+        };
+
+        $scope.removeFailoverConfiguration = function(idx) {
+            $scope.backupItem.failoverSpi.splice(idx, 1);
         };
 
         // We need to initialize backupItem with empty object in order to properly used from angular directives.
@@ -50,15 +152,13 @@ consoleModule.controller('clustersController', [
         $scope.saveBtnTipText = $common.saveBtnTipText;
         $scope.widthIsSufficient = $common.widthIsSufficient;
 
-        var showPopoverMessage = $common.showPopoverMessage;
-
-        $scope.contentVisible = function () {
-            var item = $scope.backupItem;
+        $scope.contentVisible = function() {
+            const item = $scope.backupItem;
 
             return !item.empty && (!item._id || _.find($scope.displayedRows, {_id: item._id}));
         };
 
-        $scope.toggleExpanded = function () {
+        $scope.toggleExpanded = function() {
             $scope.ui.expanded = !$scope.ui.expanded;
 
             $common.hidePopover();
@@ -77,14 +177,14 @@ consoleModule.controller('clustersController', [
 
         $scope.swapSpaceSpis = [
             {value: 'FileSwapSpaceSpi', label: 'File-based swap'},
-            {value: undefined, label: 'Not set'}
+            {value: null, label: 'Not set'}
         ];
 
         $scope.eventGroups = igniteEventGroups;
 
         $scope.clusters = [];
 
-        function _clusterLbl (cluster) {
+        function _clusterLbl(cluster) {
             return cluster.name + ', ' + _.find($scope.discoveries, {value: cluster.discovery.kind}).label;
         }
 
@@ -93,46 +193,36 @@ consoleModule.controller('clustersController', [
                 $scope.selectItem($scope.clusters[0]);
         }
 
-        function clusterCaches(item) {
-            return _.reduce($scope.caches, function (memo, cache) {
-                if (item && _.includes(item.caches, cache.value)) {
-                    memo.push(cache.cache);
-                }
-
-                return memo;
-            }, []);
-        }
-
         $loading.start('loadingClustersScreen');
 
         // When landing on the page, get clusters and show them.
         $http.post('/api/v1/configuration/clusters/list')
-            .success(function (data) {
+            .success(function(data) {
                 $scope.spaces = data.spaces;
+                $scope.clusters = data.clusters;
+                $scope.caches = _.map(data.caches, (cache) => ({value: cache._id, label: cache.name, cache}));
+                $scope.igfss = _.map(data.igfss, (igfs) => ({value: igfs._id, label: igfs.name, igfs}));
 
-                _.forEach(data.clusters, function (cluster) {
+                _.forEach($scope.clusters, (cluster) => {
                     cluster.label = _clusterLbl(cluster);
-                });
 
-                $scope.clusters = data.clusters;
+                    if (!cluster.collision || !cluster.collision.kind)
+                        cluster.collision = {kind: 'Noop', JobStealing: {stealingEnabled: true}, PriorityQueue: {starvationPreventionEnabled: true}};
 
-                $scope.caches = _.map(data.caches, function (cache) {
-                    return {value: cache._id, label: cache.name, cache: cache};
-                });
+                    if (!cluster.failoverSpi)
+                        cluster.failoverSpi = [];
 
-                $scope.igfss = _.map(data.igfss, function (igfs) {
-                    return {value: igfs._id, label: igfs.name, igfs: igfs};
+                    if (!cluster.logger)
+                        cluster.logger = {Log4j: { mode: 'Default'}};
                 });
 
-                if ($state.params.id)
-                    $scope.createItem($state.params.id);
+                if ($state.params.linkId)
+                    $scope.createItem($state.params.linkId);
                 else {
-                    var lastSelectedCluster = angular.fromJson(sessionStorage.lastSelectedCluster);
+                    const lastSelectedCluster = angular.fromJson(sessionStorage.lastSelectedCluster);
 
                     if (lastSelectedCluster) {
-                        var idx = _.findIndex($scope.clusters, function (cluster) {
-                            return cluster._id === lastSelectedCluster;
-                        });
+                        const idx = _.findIndex($scope.clusters, (cluster) => cluster._id === lastSelectedCluster);
 
                         if (idx >= 0)
                             $scope.selectItem($scope.clusters[idx]);
@@ -147,39 +237,35 @@ consoleModule.controller('clustersController', [
                 }
 
                 $scope.$watch('ui.inputForm.$valid', function(valid) {
-                    if (valid && __original_value === JSON.stringify($cleanup($scope.backupItem))) {
+                    if (valid && _.isEqual(__original_value, $cleanup($scope.backupItem)))
                         $scope.ui.inputForm.$dirty = false;
-                    }
                 });
 
-                $scope.$watch('backupItem', function (val) {
-                    var form = $scope.ui.inputForm;
+                $scope.$watch('backupItem', function(val) {
+                    const form = $scope.ui.inputForm;
 
-                    if (form.$pristine || (form.$valid && __original_value === JSON.stringify($cleanup(val))))
+                    if (form.$pristine || (form.$valid && _.isEqual(__original_value, $cleanup(val))))
                         form.$setPristine();
                     else
                         form.$setDirty();
                 }, true);
 
-                if ($root.IgniteDemoMode) {
-                    if (sessionStorage.showDemoInfo !== 'true') {
-                        sessionStorage.showDemoInfo = 'true';
+                if ($root.IgniteDemoMode && sessionStorage.showDemoInfo !== 'true') {
+                    sessionStorage.showDemoInfo = 'true';
 
-                        DemoInfo.show();
-                    }
+                    DemoInfo.show();
                 }
-
             })
-            .catch(function (errMsg) {
+            .catch(function(errMsg) {
                 $common.showError(errMsg);
             })
-            .finally(function () {
+            .finally(function() {
                 $scope.ui.ready = true;
                 $scope.ui.inputForm.$setPristine();
                 $loading.finish('loadingClustersScreen');
             });
 
-        $scope.selectItem = function (item, backup) {
+        $scope.selectItem = function(item, backup) {
             function selectItem() {
                 $scope.selectedItem = item;
 
@@ -198,11 +284,11 @@ consoleModule.controller('clustersController', [
                 else if (item)
                     $scope.backupItem = angular.copy(item);
                 else
-                    $scope.backupItem = emptyCluster ;
+                    $scope.backupItem = emptyCluster;
 
                 $scope.backupItem = angular.merge({}, blank, $scope.backupItem);
 
-                __original_value = JSON.stringify($cleanup($scope.backupItem));
+                __original_value = $cleanup($scope.backupItem);
 
                 if ($common.getQueryVariable('new'))
                     $state.go('base.configuration.clusters');
@@ -211,202 +297,150 @@ consoleModule.controller('clustersController', [
             $common.confirmUnsavedChanges($scope.backupItem && $scope.ui.inputForm.$dirty, selectItem);
         };
 
-        function prepareNewItem(id) {
-            var newItem = {
-                discovery: {
-                    kind: 'Multicast',
-                    Vm: {addresses: ['127.0.0.1:47500..47510']},
-                    Multicast: {addresses: ['127.0.0.1:47500..47510']}
-                },
-                binaryConfiguration: {
-                    typeConfigurations: [],
-                    compactFooter: true
-                },
-                communication: {
-                    tcpNoDelay: true
-                },
-                connector: {
-                    noDelay: true
-                }
-            };
-
-            newItem = angular.merge({}, blank, newItem);
-
-            newItem.caches = id && _.find($scope.caches, {value: id}) ? [id] : [];
-            newItem.igfss = id && _.find($scope.igfss, {value: id}) ? [id] : [];
-            newItem.space = $scope.spaces[0]._id;
-
-            return newItem;
+        $scope.linkId = () => $scope.backupItem._id ? $scope.backupItem._id : 'create';
+
+        function prepareNewItem(linkId) {
+            return angular.merge({}, blank, {
+                space: $scope.spaces[0]._id,
+                discovery: {kind: 'Multicast', Vm: {addresses: ['127.0.0.1:47500..47510']}, Multicast: {addresses: ['127.0.0.1:47500..47510']}},
+                binaryConfiguration: {typeConfigurations: [], compactFooter: true},
+                communication: {tcpNoDelay: true},
+                connector: {noDelay: true},
+                collision: {kind: 'Noop', JobStealing: {stealingEnabled: true}, PriorityQueue: {starvationPreventionEnabled: true}},
+                failoverSpi: [],
+                logger: {Log4j: { mode: 'Default'}},
+                caches: linkId && _.find($scope.caches, {value: linkId}) ? [linkId] : [],
+                igfss: linkId && _.find($scope.igfss, {value: linkId}) ? [linkId] : []
+            });
         }
 
         // Add new cluster.
-        $scope.createItem = function(id) {
-            $timeout(function () {
-                $common.ensureActivePanel($scope.ui, "general", 'clusterName');
-            });
+        $scope.createItem = function(linkId) {
+            $timeout(() => $common.ensureActivePanel($scope.ui, 'general', 'clusterName'));
 
-            $scope.selectItem(undefined, prepareNewItem(id));
+            $scope.selectItem(null, prepareNewItem(linkId));
         };
 
-        $scope.indexOfCache = function (cacheId) {
-            return _.findIndex($scope.caches, function (cache) {
-                return cache.value === cacheId;
-            });
+        $scope.indexOfCache = function(cacheId) {
+            return _.findIndex($scope.caches, (cache) => cache.value === cacheId);
         };
 
-        // Check cluster logical consistency.
-        function validate(item) {
-            $common.hidePopover();
-
-            if ($common.isEmptyString(item.name))
-                return showPopoverMessage($scope.ui, 'general', 'clusterName', 'Cluster name should not be empty!');
-
-            var form = $scope.ui.inputForm;
-            var errors = form.$error;
-            var errKeys = Object.keys(errors);
-
-            if (errKeys && errKeys.length > 0) {
-                var firstErrorKey = errKeys[0];
-
-                var firstError = errors[firstErrorKey][0];
-                var actualError = firstError.$error[firstErrorKey][0];
-
-                var errNameFull = actualError.$name;
-                var errNameShort = errNameFull;
-
-                if (errNameShort.endsWith('TextInput'))
-                    errNameShort = errNameShort.substring(0, errNameShort.length - 9);
+        function clusterCaches(item) {
+            return _.filter(_.map($scope.caches, (scopeCache) => scopeCache.cache),
+                (cache) => _.includes(item.caches, cache._id));
+        }
 
-                var extractErrorMessage = function (errName) {
-                    try {
-                        return errors[firstErrorKey][0].$errorMessages[errName][firstErrorKey];
-                    }
-                    catch(ignored) {
-                        try {
-                            msg = form[firstError.$name].$errorMessages[errName][firstErrorKey];
-                        }
-                        catch(ignited) {
-                            return false;
-                        }
-                    }
-                };
+        function checkCacheDatasources(item) {
+            const caches = clusterCaches(item);
 
-                var msg = extractErrorMessage(errNameFull) || extractErrorMessage(errNameShort) || 'Invalid value!';
+            const checkRes = $common.checkCachesDataSources(caches);
 
-                return showPopoverMessage($scope.ui, firstError.$name, errNameFull, msg);
+            if (!checkRes.checked) {
+                return showPopoverMessage($scope.ui, 'general', 'caches',
+                    'Found caches "' + checkRes.firstCache.name + '" and "' + checkRes.secondCache.name + '" ' +
+                    'with the same data source bean name "' + checkRes.firstCache.cacheStoreFactory[checkRes.firstCache.cacheStoreFactory.kind].dataSourceBean +
+                    '" and different databases: "' + $common.cacheStoreJdbcDialectsLabel(checkRes.firstDB) + '" in "' + checkRes.firstCache.name + '" and "' +
+                    $common.cacheStoreJdbcDialectsLabel(checkRes.secondDB) + '" in "' + checkRes.secondCache.name + '"', 10000);
             }
 
-            var caches = _.filter(_.map($scope.caches, function (scopeCache) {
-                return scopeCache.cache;
-            }), function (cache) {
-                return _.includes($scope.backupItem.caches, cache._id);
-            });
+            return true;
+        }
 
-            var checkRes = $common.checkCachesDataSources(caches);
+        function checkCacheSQLSchemas(item) {
+            const caches = clusterCaches(item);
+
+            const checkRes = $common.checkCacheSQLSchemas(caches);
 
             if (!checkRes.checked) {
                 return showPopoverMessage($scope.ui, 'general', 'caches',
                     'Found caches "' + checkRes.firstCache.name + '" and "' + checkRes.secondCache.name + '" ' +
-                    'with the same data source bean name "' + checkRes.firstCache.cacheStoreFactory[checkRes.firstCache.cacheStoreFactory.kind].dataSourceBean +
-                    '" and different databases: "' + $common.cacheStoreJdbcDialectsLabel(checkRes.firstDB) + '" in "' + checkRes.firstCache.name + '" and "' +
-                    $common.cacheStoreJdbcDialectsLabel(checkRes.secondDB) + '" in "' + checkRes.secondCache.name + '"', 10000);
+                    'with the same SQL schema name "' + checkRes.firstCache.sqlSchema + '"', 10000);
             }
 
-            var b = item.binaryConfiguration;
+            return true;
+        }
+
+        function checkBinaryConfiguration(item) {
+            const b = item.binaryConfiguration;
 
             if ($common.isDefined(b)) {
                 if (!_.isEmpty(b.typeConfigurations)) {
-                    var sameName = function (t, ix) {
-                        return ix < typeIx && t.typeName === type.typeName;
-                    };
-
-                    for (var typeIx = 0; typeIx < b.typeConfigurations.length; typeIx++) {
-                        var type = b.typeConfigurations[typeIx];
+                    for (let typeIx = 0; typeIx < b.typeConfigurations.length; typeIx++) {
+                        const type = b.typeConfigurations[typeIx];
 
                         if ($common.isEmptyString(type.typeName))
                             return showPopoverMessage($scope.ui, 'binary', 'typeName' + typeIx, 'Type name should be specified!');
 
-                        if (_.find(b.typeConfigurations, sameName))
+                        if (_.find(b.typeConfigurations, (t, ix) => ix < typeIx && t.typeName === type.typeName))
                             return showPopoverMessage($scope.ui, 'binary', 'typeName' + typeIx, 'Type with such name is already specified!');
                     }
                 }
             }
 
-            var c = item.communication;
+            return true;
+        }
+
+        function checkCommunicationConfiguration(item) {
+            const c = item.communication;
 
             if ($common.isDefined(c)) {
                 if ($common.isDefined(c.unacknowledgedMessagesBufferSize)) {
-                    if ($common.isDefined(c.messageQueueLimit))
-                        if (c.unacknowledgedMessagesBufferSize < 5 * c.messageQueueLimit)
-                            return showPopoverMessage($scope.ui, 'communication', 'unacknowledgedMessagesBufferSize', 'Maximum number of stored unacknowledged messages should be at least 5 * message queue limit!');
+                    if ($common.isDefined(c.messageQueueLimit) && c.unacknowledgedMessagesBufferSize < 5 * c.messageQueueLimit)
+                        return showPopoverMessage($scope.ui, 'communication', 'unacknowledgedMessagesBufferSize', 'Maximum number of stored unacknowledged messages should be at least 5 * message queue limit!');
 
-                    if ($common.isDefined(c.ackSendThreshold))
-                        if (c.unacknowledgedMessagesBufferSize < 5 * c.ackSendThreshold)
-                            return showPopoverMessage($scope.ui, 'communication', 'unacknowledgedMessagesBufferSize', 'Maximum number of stored unacknowledged messages should be at least 5 * ack send threshold!');
+                    if ($common.isDefined(c.ackSendThreshold) && c.unacknowledgedMessagesBufferSize < 5 * c.ackSendThreshold)
+                        return showPopoverMessage($scope.ui, 'communication', 'unacknowledgedMessagesBufferSize', 'Maximum number of stored unacknowledged messages should be at least 5 * ack send threshold!');
                 }
 
                 if (c.sharedMemoryPort === 0)
                     return showPopoverMessage($scope.ui, 'communication', 'sharedMemoryPort', 'Shared memory port should be more than "0" or equals to "-1"!');
             }
 
-            var r = item.connector;
-
-            if ($common.isDefined(r)) {
-                if (r.sslEnabled && $common.isEmptyString(r.sslFactory))
-                    return showPopoverMessage($scope.ui, 'connector', 'connectorSslFactory', 'SSL factory should not be empty!');
-            }
+            return true;
+        }
 
-            var d = item.discovery;
+        function checkDiscoveryConfiguration(item) {
+            const d = item.discovery;
 
             if (d) {
-                if ((d.maxAckTimeout != undefined ? d.maxAckTimeout : 600000) < (d.ackTimeout || 5000))
+                if ((_.isNil(d.maxAckTimeout) ? 600000 : d.maxAckTimeout) < (d.ackTimeout || 5000))
                     return showPopoverMessage($scope.ui, 'discovery', 'ackTimeout', 'Acknowledgement timeout should be less than max acknowledgement timeout!');
 
                 if (d.kind === 'Vm' && d.Vm && d.Vm.addresses.length === 0)
                     return showPopoverMessage($scope.ui, 'general', 'addresses', 'Addresses are not specified!');
-
-                if (d.kind === 'S3' && d.S3 && $common.isEmptyString(d.S3.bucketName))
-                    return showPopoverMessage($scope.ui, 'general', 'bucketName', 'Bucket name should not be empty!');
-
-                if (d.kind === 'Cloud' && d.Cloud) {
-                    if ($common.isEmptyString(d.Cloud.identity))
-                        return showPopoverMessage($scope.ui, 'general', 'identity', 'Identity should not be empty!');
-
-                    if ($common.isEmptyString(d.Cloud.provider))
-                        return showPopoverMessage($scope.ui, 'general', 'provider', 'Provider should not be empty!');
-                }
-
-                if (d.kind === 'GoogleStorage' && d.GoogleStorage) {
-                    if ($common.isEmptyString(d.GoogleStorage.projectName))
-                        return showPopoverMessage($scope.ui, 'general', 'projectName', 'Project name should not be empty!');
-
-                    if ($common.isEmptyString(d.GoogleStorage.bucketName))
-                        return showPopoverMessage($scope.ui, 'general', 'bucketName', 'Bucket name should not be empty!');
-
-                    if ($common.isEmptyString(d.GoogleStorage.serviceAccountP12FilePath))
-                        return showPopoverMessage($scope.ui, 'general', 'serviceAccountP12FilePath', 'Private key path should not be empty!');
-
-                    if ($common.isEmptyString(d.GoogleStorage.serviceAccountId))
-                        return showPopoverMessage($scope.ui, 'general', 'serviceAccountId', 'Account ID should not be empty!');
-                }
             }
 
-            var swapKind = item.swapSpaceSpi && item.swapSpaceSpi.kind;
+            return true;
+        }
+
+        function checkSwapConfiguration(item) {
+            const swapKind = item.swapSpaceSpi && item.swapSpaceSpi.kind;
 
             if (swapKind && item.swapSpaceSpi[swapKind]) {
-                var swap = item.swapSpaceSpi[swapKind];
+                const swap = item.swapSpaceSpi[swapKind];
 
-                var sparsity = swap.maximumSparsity;
+                const sparsity = swap.maximumSparsity;
 
                 if ($common.isDefined(sparsity) && (sparsity < 0 || sparsity >= 1))
                     return showPopoverMessage($scope.ui, 'swap', 'maximumSparsity', 'Maximum sparsity should be more or equal 0 and less than 1!');
 
-                var readStripesNumber = swap.readStripesNumber;
+                const readStripesNumber = swap.readStripesNumber;
 
-                if (readStripesNumber && !(readStripesNumber == -1 || (readStripesNumber & (readStripesNumber - 1)) == 0))
+                if (readStripesNumber && !(readStripesNumber === -1 || (readStripesNumber & (readStripesNumber - 1)) === 0))
                     return showPopoverMessage($scope.ui, 'swap', 'readStripesNumber', 'Read stripe size must be positive and power of two!');
             }
 
+            return true;
+        }
+
+        function checkSslConfiguration(item) {
+            const r = item.connector;
+
+            if ($common.isDefined(r)) {
+                if (r.sslEnabled && $common.isEmptyString(r.sslFactory))
+                    return showPopoverMessage($scope.ui, 'connector', 'connectorSslFactory', 'SSL factory should not be empty!');
+            }
+
             if (item.sslEnabled) {
                 if (!$common.isDefined(item.sslContextFactory) || $common.isEmptyString(item.sslContextFactory.keyStoreFilePath))
                     return showPopoverMessage($scope.ui, 'sslConfiguration', 'keyStoreFilePath', 'Key store file should not be empty!');
@@ -415,23 +449,49 @@ consoleModule.controller('clustersController', [
                     return showPopoverMessage($scope.ui, 'sslConfiguration', 'sslConfiguration-title', 'Trust storage file or managers should be configured!');
             }
 
-            if (!swapKind && item.caches) {
-                for (var i = 0; i < item.caches.length; i++) {
-                    var idx = $scope.indexOfCache(item.caches[i]);
+            return true;
+        }
 
-                    if (idx >= 0) {
-                        var cache = $scope.caches[idx];
+        function checkPoolSizes(item) {
+            if (item.rebalanceThreadPoolSize && item.systemThreadPoolSize && item.systemThreadPoolSize <= item.rebalanceThreadPoolSize)
+                return showPopoverMessage($scope.ui, 'pools', 'rebalanceThreadPoolSize', 'Rebalance thread pool size exceed or equals System thread pool size!');
 
-                        if (cache.cache.swapEnabled)
-                            return showPopoverMessage($scope.ui, 'swap', 'swapSpaceSpi',
-                                'Swap space SPI is not configured, but cache "' + cache.label + '" configured to use swap!');
-                    }
-                }
-            }
+            return true;
+        }
 
-            if (item.rebalanceThreadPoolSize && item.systemThreadPoolSize && item.systemThreadPoolSize <= item.rebalanceThreadPoolSize)
-                return showPopoverMessage($scope.ui, 'pools', 'rebalanceThreadPoolSize',
-                    'Rebalance thread pool size exceed or equals System thread pool size!');
+        // Check cluster logical consistency.
+        function validate(item) {
+            $common.hidePopover();
+
+            if ($common.isEmptyString(item.name))
+                return showPopoverMessage($scope.ui, 'general', 'clusterName', 'Cluster name should not be empty!');
+
+            if (!$common.checkFieldValidators($scope.ui))
+                return false;
+
+            if (!checkCacheSQLSchemas(item))
+                return false;
+
+            if (!checkCacheDatasources(item))
+                return false;
+
+            if (!checkBinaryConfiguration(item))
+                return false;
+
+            if (!checkCommunicationConfiguration(item))
+                return false;
+
+            if (!checkDiscoveryConfiguration(item))
+                return false;
+
+            if (!checkSwapConfiguration(item))
+                return false;
+
+            if (!checkSslConfiguration(item))
+                return false;
+
+            if (!checkPoolSizes(item))
+                return false;
 
             return true;
         }
@@ -439,12 +499,12 @@ consoleModule.controller('clustersController', [
         // Save cluster in database.
         function save(item) {
             $http.post('/api/v1/configuration/clusters/save', item)
-                .success(function (_id) {
+                .success(function(_id) {
                     item.label = _clusterLbl(item);
 
                     $scope.ui.inputForm.$setPristine();
 
-                    var idx = _.findIndex($scope.clusters, (cluster) => cluster._id === _id);
+                    const idx = _.findIndex($scope.clusters, (cluster) => cluster._id === _id);
 
                     if (idx >= 0)
                         angular.merge($scope.clusters[idx], item);
@@ -453,6 +513,20 @@ consoleModule.controller('clustersController', [
                         $scope.clusters.push(item);
                     }
 
+                    _.forEach($scope.caches, (cache) => {
+                        if (_.includes(item.caches, cache.value))
+                            cache.cache.clusters = _.union(cache.cache.clusters, [_id]);
+                        else
+                            _.remove(cache.cache.clusters, (id) => id === _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);
+                    });
+
                     $scope.selectItem(item);
 
                     $common.showInfo('Cluster "' + item.name + '" saved.');
@@ -461,10 +535,10 @@ consoleModule.controller('clustersController', [
         }
 
         // Save cluster.
-        $scope.saveItem = function () {
-            var item = $scope.backupItem;
+        $scope.saveItem = function() {
+            const item = $scope.backupItem;
 
-            var swapSpi = $common.autoClusterSwapSpiConfiguration(item, clusterCaches(item));
+            const swapSpi = $common.autoClusterSwapSpiConfiguration(item, clusterCaches(item));
 
             if (swapSpi)
                 angular.extend(item, swapSpi);
@@ -474,16 +548,14 @@ consoleModule.controller('clustersController', [
         };
 
         function _clusterNames() {
-            return _.map($scope.clusters, function (cluster) {
-                return cluster.name;
-            });
+            return _.map($scope.clusters, (cluster) => cluster.name);
         }
 
         // Clone cluster with new name.
-        $scope.cloneItem = function () {
+        $scope.cloneItem = function() {
             if (validate($scope.backupItem)) {
-                $clone.confirm($scope.backupItem.name, _clusterNames()).then(function (newName) {
-                    var item = angular.copy($scope.backupItem);
+                $clone.confirm($scope.backupItem.name, _clusterNames()).then(function(newName) {
+                    const item = angular.copy($scope.backupItem);
 
                     delete item._id;
                     item.name = newName;
@@ -494,51 +566,58 @@ consoleModule.controller('clustersController', [
         };
 
         // Remove cluster from db.
-        $scope.removeItem = function () {
-            var selectedItem = $scope.selectedItem;
+        $scope.removeItem = function() {
+            const selectedItem = $scope.selectedItem;
 
             $confirm.confirm('Are you sure you want to remove cluster: "' + selectedItem.name + '"?')
-                .then(function () {
-                    var _id = selectedItem._id;
+                .then(function() {
+                    const _id = selectedItem._id;
 
-                    $http.post('/api/v1/configuration/clusters/remove', {_id: _id})
-                        .success(function () {
+                    $http.post('/api/v1/configuration/clusters/remove', {_id})
+                        .success(function() {
                             $common.showInfo('Cluster has been removed: ' + selectedItem.name);
 
-                            var clusters = $scope.clusters;
+                            const clusters = $scope.clusters;
 
-                            var idx = _.findIndex(clusters, function (cluster) {
-                                return cluster._id === _id;
-                            });
+                            const idx = _.findIndex(clusters, (cluster) => cluster._id === _id);
 
                             if (idx >= 0) {
                                 clusters.splice(idx, 1);
 
                                 if (clusters.length > 0)
                                     $scope.selectItem(clusters[0]);
-                                else
+                                else {
                                     $scope.backupItem = emptyCluster;
+                                    $scope.ui.inputForm.$setPristine();
+                                }
+
+                                _.forEach($scope.caches, (cache) => _.remove(cache.cache.clusters, (id) => id === _id));
+                                _.forEach($scope.igfss, (igfs) => _.remove(igfs.igfs.clusters, (id) => id === _id));
                             }
                         })
-                        .error(function (errMsg) {
+                        .error(function(errMsg) {
                             $common.showError(errMsg);
                         });
                 });
         };
 
         // Remove all clusters from db.
-        $scope.removeAllItems = function () {
+        $scope.removeAllItems = function() {
             $confirm.confirm('Are you sure you want to remove all clusters?')
-                .then(function () {
+                .then(function() {
                     $http.post('/api/v1/configuration/clusters/remove/all')
-                        .success(function () {
+                        .success(function() {
                             $common.showInfo('All clusters have been removed');
 
                             $scope.clusters = [];
+
+                            _.forEach($scope.caches, (cache) => cache.cache.clusters = []);
+                            _.forEach($scope.igfss, (igfs) => igfs.igfs.clusters = []);
+
                             $scope.backupItem = emptyCluster;
                             $scope.ui.inputForm.$setPristine();
                         })
-                        .error(function (errMsg) {
+                        .error(function(errMsg) {
                             $common.showError(errMsg);
                         });
                 });