http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/store/effects.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/store/effects.js 
b/modules/web-console/frontend/app/components/page-configure/store/effects.js
new file mode 100644
index 0000000..3ab216a
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/store/effects.js
@@ -0,0 +1,664 @@
+/*
+ * 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 {Observable} from 'rxjs/Observable';
+import 'rxjs/add/operator/ignoreElements';
+import 'rxjs/add/operator/let';
+import 'rxjs/add/operator/zip';
+import {merge} from 'rxjs/observable/merge';
+import {empty} from 'rxjs/observable/empty';
+import {of} from 'rxjs/observable/of';
+import {fromPromise} from 'rxjs/observable/fromPromise';
+import {uniqueName} from 'app/utils/uniqueName';
+import uniq from 'lodash/uniq';
+
+import {
+    clustersActionTypes,
+    cachesActionTypes,
+    shortClustersActionTypes,
+    shortCachesActionTypes,
+    shortModelsActionTypes,
+    shortIGFSsActionTypes,
+    modelsActionTypes,
+    igfssActionTypes
+} from './../reducer';
+
+import {
+    REMOVE_CLUSTER_ITEMS,
+    REMOVE_CLUSTER_ITEMS_CONFIRMED,
+    CONFIRM_CLUSTERS_REMOVAL,
+    CONFIRM_CLUSTERS_REMOVAL_OK,
+    COMPLETE_CONFIGURATION,
+    ADVANCED_SAVE_CLUSTER,
+    ADVANCED_SAVE_CACHE,
+    ADVANCED_SAVE_IGFS,
+    ADVANCED_SAVE_MODEL,
+    BASIC_SAVE,
+    BASIC_SAVE_AND_DOWNLOAD,
+    BASIC_SAVE_OK
+} from './actionTypes';
+
+import {
+    removeClusterItemsConfirmed,
+    advancedSaveCompleteConfiguration,
+    confirmClustersRemoval,
+    confirmClustersRemovalOK,
+    completeConfiguration,
+    basicSave,
+    basicSaveOK,
+    basicSaveErr
+} from './actionCreators';
+
+import ConfigureState from 
'app/components/page-configure/services/ConfigureState';
+import ConfigurationDownload from 
'app/components/page-configure/services/ConfigurationDownload';
+import ConfigSelectors from 'app/components/page-configure/store/selectors';
+import Clusters from 'app/services/Clusters';
+import Caches from 'app/services/Caches';
+import Models from 'app/services/Models';
+import IGFSs from 'app/services/IGFSs';
+import {Confirm} from 'app/services/Confirm.service';
+
+export const ofType = (type) => (s) => s.filter((a) => a.type === type);
+
+export default class ConfigEffects {
+    static $inject = [
+        ConfigureState.name,
+        Caches.name,
+        IGFSs.name,
+        Models.name,
+        ConfigSelectors.name,
+        Clusters.name,
+        '$state',
+        'IgniteMessages',
+        'IgniteConfirm',
+        Confirm.name,
+        ConfigurationDownload.name
+    ];
+    /**
+     * @param {ConfigureState} ConfigureState
+     * @param {Caches} Caches
+     * @param {IGFSs} IGFSs
+     * @param {Models} Models
+     * @param {ConfigSelectors} ConfigSelectors
+     * @param {Clusters} Clusters
+     * @param {object} $state
+     * @param {object} IgniteMessages
+     * @param {object} IgniteConfirm
+     * @param {Confirm} Confirm
+     * @param {ConfigurationDownload} ConfigurationDownload
+     */
+    constructor(ConfigureState, Caches, IGFSs, Models, ConfigSelectors, 
Clusters, $state, IgniteMessages, IgniteConfirm, Confirm, 
ConfigurationDownload) {
+        this.ConfigureState = ConfigureState;
+        this.ConfigSelectors = ConfigSelectors;
+        this.IGFSs = IGFSs;
+        this.Models = Models;
+        this.Caches = Caches;
+        this.Clusters = Clusters;
+        this.$state = $state;
+        this.IgniteMessages = IgniteMessages;
+        this.IgniteConfirm = IgniteConfirm;
+        this.Confirm = Confirm;
+        this.configurationDownload = ConfigurationDownload;
+
+        this.loadConfigurationEffect$ = this.ConfigureState.actions$
+            .let(ofType('LOAD_COMPLETE_CONFIGURATION'))
+            .exhaustMap((action) => {
+                return 
fromPromise(this.Clusters.getConfiguration(action.clusterID))
+                    .switchMap(({data}) => of(
+                        completeConfiguration(data),
+                        {type: 'LOAD_COMPLETE_CONFIGURATION_OK', data}
+                    ))
+                    .catch((error) => of({
+                        type: 'LOAD_COMPLETE_CONFIGURATION_ERR',
+                        error: {
+                            message: `Failed to load cluster configuration: 
${error.data}.`
+                        },
+                        action
+                    }));
+            });
+
+        this.storeConfigurationEffect$ = this.ConfigureState.actions$
+            .let(ofType(COMPLETE_CONFIGURATION))
+            .exhaustMap(({configuration: {cluster, caches, models, igfss}}) => 
of(...[
+                cluster && {type: clustersActionTypes.UPSERT, items: 
[cluster]},
+                caches && caches.length && {type: cachesActionTypes.UPSERT, 
items: caches},
+                models && models.length && {type: modelsActionTypes.UPSERT, 
items: models},
+                igfss && igfss.length && {type: igfssActionTypes.UPSERT, 
items: igfss}
+            ].filter((v) => v)));
+
+        this.saveCompleteConfigurationEffect$ = this.ConfigureState.actions$
+            .let(ofType('ADVANCED_SAVE_COMPLETE_CONFIGURATION'))
+            .switchMap((action) => {
+                const actions = [
+                    {
+                        type: modelsActionTypes.UPSERT,
+                        items: action.changedItems.models
+                    },
+                    {
+                        type: shortModelsActionTypes.UPSERT,
+                        items: action.changedItems.models.map((m) => 
this.Models.toShortModel(m))
+                    },
+                    {
+                        type: igfssActionTypes.UPSERT,
+                        items: action.changedItems.igfss
+                    },
+                    {
+                        type: shortIGFSsActionTypes.UPSERT,
+                        items: action.changedItems.igfss
+                    },
+                    {
+                        type: cachesActionTypes.UPSERT,
+                        items: action.changedItems.caches
+                    },
+                    {
+                        type: shortCachesActionTypes.UPSERT,
+                        items: 
action.changedItems.caches.map(Caches.toShortCache)
+                    },
+                    {
+                        type: clustersActionTypes.UPSERT,
+                        items: [action.changedItems.cluster]
+                    },
+                    {
+                        type: shortClustersActionTypes.UPSERT,
+                        items: 
[Clusters.toShortCluster(action.changedItems.cluster)]
+                    }
+                ].filter((a) => a.items.length);
+
+                return of(...actions)
+                .merge(
+                    fromPromise(Clusters.saveAdvanced(action.changedItems))
+                    .switchMap((res) => {
+                        return of(
+                            {type: 'EDIT_CLUSTER', cluster: 
action.changedItems.cluster},
+                            {type: 'ADVANCED_SAVE_COMPLETE_CONFIGURATION_OK', 
changedItems: action.changedItems}
+                        );
+                    })
+                    .catch((res) => {
+                        return of({
+                            type: 'ADVANCED_SAVE_COMPLETE_CONFIGURATION_ERR',
+                            changedItems: action.changedItems,
+                            action,
+                            error: {
+                                message: `Failed to save cluster 
"${action.changedItems.cluster.name}": ${res.data}.`
+                            }
+                        }, {
+                            type: 'UNDO_ACTIONS',
+                            actions
+                        });
+                    })
+                );
+            });
+
+        this.addCacheToEditEffect$ = this.ConfigureState.actions$
+            .let(ofType('ADD_CACHE_TO_EDIT'))
+            .switchMap(() => 
this.ConfigureState.state$.let(this.ConfigSelectors.selectCacheToEdit('new')).take(1))
+            .map((cache) => ({type: 'UPSERT_CLUSTER_ITEM', itemType: 'caches', 
item: cache}));
+
+        this.errorNotificationsEffect$ = this.ConfigureState.actions$
+            .filter((a) => a.error)
+            .do((action) => this.IgniteMessages.showError(action.error))
+            .ignoreElements();
+
+        this.loadUserClustersEffect$ = this.ConfigureState.actions$
+            .let(ofType('LOAD_USER_CLUSTERS'))
+            .exhaustMap((a) => {
+                return fromPromise(this.Clusters.getClustersOverview())
+                    .switchMap(({data}) => of(
+                        {type: shortClustersActionTypes.SET, items: data},
+                        {type: `${a.type}_OK`}
+                    ))
+                    .catch((error) => of({
+                        type: `${a.type}_ERR`,
+                        error: {
+                            message: `Failed to load clusters: ${error.data}`
+                        },
+                        action: a
+                    }));
+            });
+
+        this.loadAndEditClusterEffect$ = ConfigureState.actions$
+            .let(ofType('LOAD_AND_EDIT_CLUSTER'))
+            .exhaustMap((a) => {
+                if (a.clusterID === 'new') {
+                    return of(
+                        {type: 'EDIT_CLUSTER', cluster: 
this.Clusters.getBlankCluster()},
+                        {type: 'LOAD_AND_EDIT_CLUSTER_OK'}
+                    );
+                }
+                return 
this.ConfigureState.state$.let(this.ConfigSelectors.selectCluster(a.clusterID)).take(1)
+                    .switchMap((cluster) => {
+                        if (cluster) {
+                            return of(
+                                {type: 'EDIT_CLUSTER', cluster},
+                                {type: 'LOAD_AND_EDIT_CLUSTER_OK'}
+                            );
+                        }
+                        return 
fromPromise(this.Clusters.getCluster(a.clusterID))
+                        .switchMap(({data}) => of(
+                            {type: clustersActionTypes.UPSERT, items: [data]},
+                            {type: 'EDIT_CLUSTER', cluster: data},
+                            {type: 'LOAD_AND_EDIT_CLUSTER_OK'}
+                        ))
+                        .catch((error) => of({
+                            type: 'LOAD_AND_EDIT_CLUSTER_ERR',
+                            error: {
+                                message: `Failed to load cluster: 
${error.data}.`
+                            }
+                        }));
+                    });
+            });
+
+        this.loadCacheEffect$ = this.ConfigureState.actions$
+            .let(ofType('LOAD_CACHE'))
+            .exhaustMap((a) => {
+                return 
this.ConfigureState.state$.let(this.ConfigSelectors.selectCache(a.cacheID)).take(1)
+                    .switchMap((cache) => {
+                        if (cache) return of({type: `${a.type}_OK`, cache});
+                        return fromPromise(this.Caches.getCache(a.cacheID))
+                        .switchMap(({data}) => of(
+                            {type: 'CACHE', cache: data},
+                            {type: `${a.type}_OK`, cache: data}
+                        ));
+                    })
+                    .catch((error) => of({
+                        type: `${a.type}_ERR`,
+                        error: {
+                            message: `Failed to load cache: ${error.data}.`
+                        }
+                    }));
+            });
+
+        this.storeCacheEffect$ = this.ConfigureState.actions$
+            .let(ofType('CACHE'))
+            .map((a) => ({type: cachesActionTypes.UPSERT, items: [a.cache]}));
+
+        this.loadShortCachesEffect$ = ConfigureState.actions$
+            .let(ofType('LOAD_SHORT_CACHES'))
+            .exhaustMap((a) => {
+                if (!(a.ids || []).length) return of({type: `${a.type}_OK`});
+                return 
this.ConfigureState.state$.let(this.ConfigSelectors.selectShortCaches()).take(1)
+                    .switchMap((items) => {
+                        if (!items.pristine && a.ids && a.ids.every((_id) => 
items.value.has(_id)))
+                            return of({type: `${a.type}_OK`});
+
+                        return 
fromPromise(this.Clusters.getClusterCaches(a.clusterID))
+                            .switchMap(({data}) => of(
+                                {type: shortCachesActionTypes.UPSERT, items: 
data},
+                                {type: `${a.type}_OK`}
+                            ));
+                    })
+                    .catch((error) => of({
+                        type: `${a.type}_ERR`,
+                        error: {
+                            message: `Failed to load caches: ${error.data}.`
+                        },
+                        action: a
+                    }));
+            });
+
+        this.loadIgfsEffect$ = this.ConfigureState.actions$
+            .let(ofType('LOAD_IGFS'))
+            .exhaustMap((a) => {
+                return 
this.ConfigureState.state$.let(this.ConfigSelectors.selectIGFS(a.igfsID)).take(1)
+                    .switchMap((igfs) => {
+                        if (igfs) return of({type: `${a.type}_OK`, igfs});
+                        return fromPromise(this.IGFSs.getIGFS(a.igfsID))
+                        .switchMap(({data}) => of(
+                            {type: 'IGFS', igfs: data},
+                            {type: `${a.type}_OK`, igfs: data}
+                        ));
+                    })
+                    .catch((error) => of({
+                        type: `${a.type}_ERR`,
+                        error: {
+                            message: `Failed to load IGFS: ${error.data}.`
+                        }
+                    }));
+            });
+
+        this.storeIgfsEffect$ = this.ConfigureState.actions$
+            .let(ofType('IGFS'))
+            .map((a) => ({type: igfssActionTypes.UPSERT, items: [a.igfs]}));
+
+        this.loadShortIgfssEffect$ = ConfigureState.actions$
+            .let(ofType('LOAD_SHORT_IGFSS'))
+            .exhaustMap((a) => {
+                if (!(a.ids || []).length) {
+                    return of(
+                        {type: shortIGFSsActionTypes.UPSERT, items: []},
+                        {type: `${a.type}_OK`}
+                    );
+                }
+                return 
this.ConfigureState.state$.let(this.ConfigSelectors.selectShortIGFSs()).take(1)
+                    .switchMap((items) => {
+                        if (!items.pristine && a.ids && a.ids.every((_id) => 
items.value.has(_id)))
+                            return of({type: `${a.type}_OK`});
+
+                        return 
fromPromise(this.Clusters.getClusterIGFSs(a.clusterID))
+                            .switchMap(({data}) => of(
+                                {type: shortIGFSsActionTypes.UPSERT, items: 
data},
+                                {type: `${a.type}_OK`}
+                            ));
+                    })
+                    .catch((error) => of({
+                        type: `${a.type}_ERR`,
+                        error: {
+                            message: `Failed to load IGFSs: ${error.data}.`
+                        },
+                        action: a
+                    }));
+            });
+
+        this.loadModelEffect$ = this.ConfigureState.actions$
+            .let(ofType('LOAD_MODEL'))
+            .exhaustMap((a) => {
+                return 
this.ConfigureState.state$.let(this.ConfigSelectors.selectModel(a.modelID)).take(1)
+                    .switchMap((model) => {
+                        if (model) return of({type: `${a.type}_OK`, model});
+                        return fromPromise(this.Models.getModel(a.modelID))
+                        .switchMap(({data}) => of(
+                            {type: 'MODEL', model: data},
+                            {type: `${a.type}_OK`, model: data}
+                        ));
+                    })
+                    .catch((error) => of({
+                        type: `${a.type}_ERR`,
+                        error: {
+                            message: `Failed to load domain model: 
${error.data}.`
+                        }
+                    }));
+            });
+
+        this.storeModelEffect$ = this.ConfigureState.actions$
+            .let(ofType('MODEL'))
+            .map((a) => ({type: modelsActionTypes.UPSERT, items: [a.model]}));
+
+        this.loadShortModelsEffect$ = this.ConfigureState.actions$
+            .let(ofType('LOAD_SHORT_MODELS'))
+            .exhaustMap((a) => {
+                if (!(a.ids || []).length) {
+                    return of(
+                        {type: shortModelsActionTypes.UPSERT, items: []},
+                        {type: `${a.type}_OK`}
+                    );
+                }
+                return 
this.ConfigureState.state$.let(this.ConfigSelectors.selectShortModels()).take(1)
+                    .switchMap((items) => {
+                        if (!items.pristine && a.ids && a.ids.every((_id) => 
items.value.has(_id)))
+                            return of({type: `${a.type}_OK`});
+
+                        return 
fromPromise(this.Clusters.getClusterModels(a.clusterID))
+                            .switchMap(({data}) => of(
+                                {type: shortModelsActionTypes.UPSERT, items: 
data},
+                                {type: `${a.type}_OK`}
+                            ));
+                    })
+                    .catch((error) => of({
+                        type: `${a.type}_ERR`,
+                        error: {
+                            message: `Failed to load domain models: 
${error.data}.`
+                        },
+                        action: a
+                    }));
+            });
+
+        this.basicSaveRedirectEffect$ = this.ConfigureState.actions$
+            .let(ofType(BASIC_SAVE_OK))
+            .do((a) => this.$state.go('base.configuration.edit.basic', 
{clusterID: a.changedItems.cluster._id}, {location: 'replace', custom: 
{justIDUpdate: true}}))
+            .ignoreElements();
+
+        this.basicDownloadAfterSaveEffect$ = 
this.ConfigureState.actions$.let(ofType(BASIC_SAVE_AND_DOWNLOAD))
+            .zip(this.ConfigureState.actions$.let(ofType(BASIC_SAVE_OK)))
+            .pluck('1')
+            .do((a) => 
this.configurationDownload.downloadClusterConfiguration(a.changedItems.cluster))
+            .ignoreElements();
+
+        this.advancedSaveRedirectEffect$ = this.ConfigureState.actions$
+            .let(ofType('ADVANCED_SAVE_COMPLETE_CONFIGURATION_OK'))
+            
.withLatestFrom(this.ConfigureState.actions$.let(ofType('ADVANCED_SAVE_COMPLETE_CONFIGURATION')))
+            .pluck('1', 'changedItems')
+            .map((req) => {
+                const firstChangedItem = Object.keys(req).filter((k) => k !== 
'cluster')
+                    .map((k) => Array.isArray(req[k]) ? [k, req[k][0]] : [k, 
req[k]])
+                    .filter((v) => v[1])
+                    .pop();
+                return firstChangedItem ? [...firstChangedItem, req.cluster] : 
['cluster', req.cluster, req.cluster];
+            })
+            .do(([type, value, cluster]) => {
+                const go = (state, params = {}) => this.$state.go(
+                    state, {...params, clusterID: cluster._id}, {location: 
'replace', custom: {justIDUpdate: true}}
+                );
+                switch (type) {
+                    case 'models': {
+                        const state = 
'base.configuration.edit.advanced.models.model';
+                        this.IgniteMessages.showInfo(`Model 
"${value.valueType}" saved`);
+                        if (
+                            this.$state.is(state) && 
this.$state.params.modelID !== value._id
+                        ) return go(state, {modelID: value._id});
+                        break;
+                    }
+                    case 'caches': {
+                        const state = 
'base.configuration.edit.advanced.caches.cache';
+                        this.IgniteMessages.showInfo(`Cache "${value.name}" 
saved`);
+                        if (
+                            this.$state.is(state) && 
this.$state.params.cacheID !== value._id
+                        ) return go(state, {cacheID: value._id});
+                        break;
+                    }
+                    case 'igfss': {
+                        const state = 
'base.configuration.edit.advanced.igfs.igfs';
+                        this.IgniteMessages.showInfo(`IGFS "${value.name}" 
saved`);
+                        if (
+                            this.$state.is(state) && this.$state.params.igfsID 
!== value._id
+                        ) return go(state, {igfsID: value._id});
+                        break;
+                    }
+                    case 'cluster': {
+                        const state = 
'base.configuration.edit.advanced.cluster';
+                        this.IgniteMessages.showInfo(`Cluster "${value.name}" 
saved`);
+                        if (
+                            this.$state.is(state) && 
this.$state.params.clusterID !== value._id
+                        ) return go(state);
+                        break;
+                    }
+                    default: break;
+                }
+            })
+            .ignoreElements();
+
+        this.removeClusterItemsEffect$ = this.ConfigureState.actions$
+            .let(ofType(REMOVE_CLUSTER_ITEMS))
+            .exhaustMap((a) => {
+                return a.confirm
+                    // TODO: list items to remove in confirmation
+                    ? fromPromise(this.Confirm.confirm('Are you sure want to 
remove these items?'))
+                        .mapTo(a)
+                        .catch(() => empty())
+                    : of(a);
+            })
+            .map((a) => removeClusterItemsConfirmed(a.clusterID, a.itemType, 
a.itemIDs));
+
+        this.persistRemovedClusterItemsEffect$ = this.ConfigureState.actions$
+            .let(ofType(REMOVE_CLUSTER_ITEMS_CONFIRMED))
+            
.withLatestFrom(this.ConfigureState.actions$.let(ofType(REMOVE_CLUSTER_ITEMS)))
+            .filter(([a, b]) => {
+                return a.itemType === b.itemType
+                    && b.save
+                    && JSON.stringify(a.itemIDs) === JSON.stringify(b.itemIDs);
+            })
+            .pluck('0')
+            .withLatestFrom(this.ConfigureState.state$.pluck('edit'))
+            .map(([action, edit]) => advancedSaveCompleteConfiguration(edit));
+
+        this.confirmClustersRemovalEffect$ = this.ConfigureState.actions$
+            .let(ofType(CONFIRM_CLUSTERS_REMOVAL))
+            .pluck('clusterIDs')
+            .switchMap((ids) => 
this.ConfigureState.state$.let(this.ConfigSelectors.selectClusterNames(ids)).take(1))
+            .exhaustMap((names) => {
+                return fromPromise(this.Confirm.confirm(`
+                    <p>Are you sure want to remove these clusters?</p>
+                    <ul>${names.map((name) => 
`<li>${name}</li>`).join('')}</ul>
+                `))
+                .map(confirmClustersRemovalOK)
+                .catch(() => Observable.empty());
+            });
+
+        this.persistRemovedClustersLocallyEffect$ = 
this.ConfigureState.actions$
+            .let(ofType(CONFIRM_CLUSTERS_REMOVAL_OK))
+            
.withLatestFrom(this.ConfigureState.actions$.let(ofType(CONFIRM_CLUSTERS_REMOVAL)))
+            .switchMap(([, {clusterIDs}]) => of(
+                {type: shortClustersActionTypes.REMOVE, ids: clusterIDs},
+                {type: clustersActionTypes.REMOVE, ids: clusterIDs}
+            ));
+
+        this.persistRemovedClustersRemotelyEffect$ = 
this.ConfigureState.actions$
+            .let(ofType(CONFIRM_CLUSTERS_REMOVAL_OK))
+            .withLatestFrom(
+                
this.ConfigureState.actions$.let(ofType(CONFIRM_CLUSTERS_REMOVAL)),
+                
this.ConfigureState.actions$.let(ofType(shortClustersActionTypes.REMOVE)),
+                
this.ConfigureState.actions$.let(ofType(clustersActionTypes.REMOVE))
+            )
+            .switchMap(([, {clusterIDs}, ...backup]) => 
this.Clusters.removeCluster$(clusterIDs)
+                .mapTo({
+                    type: 'REMOVE_CLUSTERS_OK'
+                })
+                .catch((e) => of(
+                    {
+                        type: 'REMOVE_CLUSTERS_ERR',
+                        error: {
+                            message: `Failed to remove clusters: ${e.data}`
+                        }
+                    },
+                    {
+                        type: 'UNDO_ACTIONS',
+                        actions: backup
+                    }
+                ))
+            );
+
+        this.notifyRemoteClustersRemoveSuccessEffect$ = 
this.ConfigureState.actions$
+            .let(ofType('REMOVE_CLUSTERS_OK'))
+            
.withLatestFrom(this.ConfigureState.actions$.let(ofType(CONFIRM_CLUSTERS_REMOVAL)))
+            .do(([, {clusterIDs}]) => this.IgniteMessages.showInfo(`Cluster(s) 
removed: ${clusterIDs.length}`))
+            .ignoreElements();
+
+        const _applyChangedIDs = (edit, {cache, igfs, model, cluster} = {}) => 
({
+            cluster: {
+                ...edit.changes.cluster,
+                ...(cluster ? cluster : {}),
+                caches: cache ? uniq([...edit.changes.caches.ids, cache._id]) 
: edit.changes.caches.ids,
+                igfss: igfs ? uniq([...edit.changes.igfss.ids, igfs._id]) : 
edit.changes.igfss.ids,
+                models: model ? uniq([...edit.changes.models.ids, model._id]) 
: edit.changes.models.ids
+            },
+            caches: cache ? uniq([...edit.changes.caches.changedItems, cache]) 
: edit.changes.caches.changedItems,
+            igfss: igfs ? uniq([...edit.changes.igfss.changedItems, igfs]) : 
edit.changes.igfss.changedItems,
+            models: model ? uniq([...edit.changes.models.changedItems, model]) 
: edit.changes.models.changedItems
+        });
+
+        this.advancedSaveCacheEffect$ = merge(
+            this.ConfigureState.actions$.let(ofType(ADVANCED_SAVE_CLUSTER)),
+            this.ConfigureState.actions$.let(ofType(ADVANCED_SAVE_CACHE)),
+            this.ConfigureState.actions$.let(ofType(ADVANCED_SAVE_IGFS)),
+            this.ConfigureState.actions$.let(ofType(ADVANCED_SAVE_MODEL)),
+        )
+            .withLatestFrom(this.ConfigureState.state$.pluck('edit'))
+            .map(([action, edit]) => ({
+                type: 'ADVANCED_SAVE_COMPLETE_CONFIGURATION',
+                changedItems: _applyChangedIDs(edit, action)
+            }));
+
+        this.basicSaveEffect$ = this.ConfigureState.actions$
+            .let(ofType(BASIC_SAVE))
+            
.merge(this.ConfigureState.actions$.let(ofType(BASIC_SAVE_AND_DOWNLOAD)))
+            .withLatestFrom(this.ConfigureState.state$.pluck('edit'))
+            .switchMap(([action, edit]) => {
+                const changedItems = _applyChangedIDs(edit, {cluster: 
action.cluster});
+                const actions = [{
+                    type: cachesActionTypes.UPSERT,
+                    items: changedItems.caches
+                },
+                {
+                    type: shortCachesActionTypes.UPSERT,
+                    items: changedItems.caches
+                },
+                {
+                    type: clustersActionTypes.UPSERT,
+                    items: [changedItems.cluster]
+                },
+                {
+                    type: shortClustersActionTypes.UPSERT,
+                    items: [this.Clusters.toShortCluster(changedItems.cluster)]
+                }
+                ].filter((a) => a.items.length);
+
+                return Observable.of(...actions)
+                .merge(
+                    
Observable.fromPromise(this.Clusters.saveBasic(changedItems))
+                    .switchMap((res) => Observable.of(
+                        {type: 'EDIT_CLUSTER', cluster: changedItems.cluster},
+                        basicSaveOK(changedItems)
+                    ))
+                    .catch((res) => Observable.of(
+                        basicSaveErr(changedItems, res),
+                        {type: 'UNDO_ACTIONS', actions}
+                    ))
+                );
+            });
+
+        this.basicSaveOKMessagesEffect$ = this.ConfigureState.actions$
+            .let(ofType(BASIC_SAVE_OK))
+            .do((action) => this.IgniteMessages.showInfo(`Cluster 
"${action.changedItems.cluster.name}" saved.`))
+            .ignoreElements();
+    }
+
+    /**
+     * @name etp
+     * @function
+     * @param {object} action
+     * @returns {Promise}
+     */
+    /**
+     * @name etp^2
+     * @function
+     * @param {string} type
+     * @param {object} [params]
+     * @returns {Promise}
+     */
+    etp = (...args) => {
+        const action = typeof args[0] === 'object' ? args[0] : {type: args[0], 
...args[1]};
+        const ok = `${action.type}_OK`;
+        const err = `${action.type}_ERR`;
+
+        setTimeout(() => this.ConfigureState.dispatchAction(action));
+        return this.ConfigureState.actions$
+            .filter((a) => a.type === ok || a.type === err)
+            .take(1)
+            .map((a) => {
+                if (a.type === err)
+                    throw a;
+                else
+                    return a;
+            })
+            .toPromise();
+    };
+
+    connect() {
+        return merge(
+            ...Object.keys(this).filter((k) => k.endsWith('Effect$')).map((k) 
=> this[k])
+        ).do((a) => this.ConfigureState.dispatchAction(a)).subscribe();
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/store/selectors.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/store/selectors.js 
b/modules/web-console/frontend/app/components/page-configure/store/selectors.js
new file mode 100644
index 0000000..0f60214
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/store/selectors.js
@@ -0,0 +1,170 @@
+/*
+ * 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 {uniqueName} from 'app/utils/uniqueName';
+import {of} from 'rxjs/observable/of';
+import {empty} from 'rxjs/observable/empty';
+import {combineLatest} from 'rxjs/observable/combineLatest';
+import 'rxjs/add/operator/mergeMap';
+import 'rxjs/add/observable/combineLatest';
+import {Observable} from 'rxjs/Observable';
+import {defaultNames} from '../defaultNames';
+
+import {default as Caches} from 'app/services/Caches';
+import {default as Clusters} from 'app/services/Clusters';
+import {default as IGFSs} from 'app/services/IGFSs';
+import {default as Models} from 'app/services/Models';
+
+const isDefined = (s) => s.filter((v) => v);
+const selectItems = (path) => (s) => s.filter((s) => s).pluck(path).filter((v) 
=> v);
+const selectValues = (s) => s.map((v) => v && [...v.value.values()]);
+export const selectMapItem = (mapPath, key) => (s) => s.pluck(mapPath).map((v) 
=> v && v.get(key));
+const selectMapItems = (mapPath, keys) => (s) => s.pluck(mapPath).map((v) => v 
&& keys.map((key) => v.get(key)));
+const selectItemToEdit = ({items, itemFactory, defaultName = '', itemID}) => 
(s) => s.switchMap((item) => {
+    if (item) return of(Object.assign(itemFactory(), item));
+    if (itemID === 'new') return items.take(1).map((items) => 
Object.assign(itemFactory(), {name: uniqueName(defaultName, items)}));
+    if (!itemID) return of(null);
+    return empty();
+});
+const currentShortItems = ({changesKey, shortKey}) => (state$) => {
+    return Observable.combineLatest(
+        state$.pluck('edit', 'changes', 
changesKey).let(isDefined).distinctUntilChanged(),
+        state$.pluck(shortKey, 'value').let(isDefined).distinctUntilChanged()
+    )
+        .map(([{ids = [], changedItems}, shortItems]) => {
+            if (!ids.length || !shortItems) return [];
+            return ids.map((id) => changedItems.find(({_id}) => _id === id) || 
shortItems.get(id));
+        })
+        .map((v) => v.filter((v) => v));
+};
+
+const selectNames = (itemIDs, nameAt = 'name') => (items) => items
+    .pluck('value')
+    .map((items) => itemIDs.map((id) => items.get(id)[nameAt]));
+
+export default class ConfigSelectors {
+    static $inject = ['Caches', 'Clusters', 'IGFSs', 'Models'];
+    /**
+     * @param {Caches} Caches
+     * @param {Clusters} Clusters
+     * @param {IGFSs} IGFSs
+     * @param {Models} Models
+     */
+    constructor(Caches, Clusters, IGFSs, Models) {
+        this.Caches = Caches;
+        this.Clusters = Clusters;
+        this.IGFSs = IGFSs;
+        this.Models = Models;
+
+        /**
+         * @param {string} id
+         * @returns {(state$: Observable) => 
Observable<ig.config.model.DomainModel>}
+         */
+        this.selectModel = (id) => selectMapItem('models', id);
+        /**
+         * @returns {(state$: Observable) => Observable<{pristine: boolean, 
value: Map<string, ig.config.model.ShortDomainModel>}>}
+         */
+        this.selectShortModels = () => selectItems('shortModels');
+        this.selectShortModelsValue = () => (state$) => 
state$.let(this.selectShortModels()).let(selectValues);
+        /**
+         * @returns {(state$: Observable) => 
Observable<Array<ig.config.cluster.ShortCluster>>}
+         */
+        this.selectShortClustersValue = () => (state$) => 
state$.let(this.selectShortClusters()).let(selectValues);
+        /**
+         * @returns {(state$: Observable) => Observable<Array<string>>}
+         */
+        this.selectClusterNames = (clusterIDs) => (state$) => state$
+            .let(this.selectShortClusters())
+            .let(selectNames(clusterIDs));
+    }
+    selectCluster = (id) => selectMapItem('clusters', id);
+    selectShortClusters = () => selectItems('shortClusters');
+    selectCache = (id) => selectMapItem('caches', id);
+    selectIGFS = (id) => selectMapItem('igfss', id);
+    selectShortCaches = () => selectItems('shortCaches');
+    selectShortCachesValue = () => (state$) => 
state$.let(this.selectShortCaches()).let(selectValues);
+    selectShortIGFSs = () => selectItems('shortIgfss');
+    selectShortIGFSsValue = () => (state$) => 
state$.let(this.selectShortIGFSs()).let(selectValues);
+    selectShortModelsValue = () => (state$) => 
state$.let(this.selectShortModels()).let(selectValues);
+    selectCacheToEdit = (cacheID) => (state$) => state$
+        .let(this.selectCache(cacheID))
+        .distinctUntilChanged()
+        .let(selectItemToEdit({
+            items: state$.let(this.selectCurrentShortCaches),
+            itemFactory: () => this.Caches.getBlankCache(),
+            defaultName: defaultNames.cache,
+            itemID: cacheID
+        }));
+    selectIGFSToEdit = (itemID) => (state$) => state$
+        .let(this.selectIGFS(itemID))
+        .distinctUntilChanged()
+        .let(selectItemToEdit({
+            items: state$.let(this.selectCurrentShortIGFSs),
+            itemFactory: () => this.IGFSs.getBlankIGFS(),
+            defaultName: defaultNames.igfs,
+            itemID
+        }));
+    selectModelToEdit = (itemID) => (state$) => state$
+        .let(this.selectModel(itemID))
+        .distinctUntilChanged()
+        .let(selectItemToEdit({
+            items: state$.let(this.selectCurrentShortModels),
+            itemFactory: () => this.Models.getBlankModel(),
+            itemID
+        }));
+    selectClusterToEdit = (clusterID, defaultName = defaultNames.cluster) => 
(state$) => state$
+        .let(this.selectCluster(clusterID))
+        .distinctUntilChanged()
+        .let(selectItemToEdit({
+            items: state$.let(this.selectShortClustersValue()),
+            itemFactory: () => this.Clusters.getBlankCluster(),
+            defaultName,
+            itemID: clusterID
+        }));
+    selectCurrentShortCaches = currentShortItems({changesKey: 'caches', 
shortKey: 'shortCaches'});
+    selectCurrentShortIGFSs = currentShortItems({changesKey: 'igfss', 
shortKey: 'shortIgfss'});
+    selectCurrentShortModels = currentShortItems({changesKey: 'models', 
shortKey: 'shortModels'});
+    selectClusterShortCaches = (clusterID) => (state$) => {
+        if (clusterID === 'new') return of([]);
+        return combineLatest(
+            state$.let(this.selectCluster(clusterID)).pluck('caches'),
+            state$.let(this.selectShortCaches()).pluck('value'),
+            (ids, items) => ids.map((id) => items.get(id))
+        );
+    };
+    selectCompleteClusterConfiguration = ({clusterID, isDemo}) => (state$) => {
+        const hasValues = (array) => !array.some((v) => !v);
+        return state$.let(this.selectCluster(clusterID))
+        .exhaustMap((cluster) => {
+            if (!cluster) return of({__isComplete: false});
+            const withSpace = (array) => array.map((c) => ({...c, space: 
cluster.space}));
+            return Observable.forkJoin(
+                state$.let(selectMapItems('caches', cluster.caches || 
[])).take(1),
+                state$.let(selectMapItems('models', cluster.models || 
[])).take(1),
+                state$.let(selectMapItems('igfss', cluster.igfss || 
[])).take(1),
+            )
+            .map(([caches, models, igfss]) => ({
+                cluster,
+                caches,
+                domains: models,
+                igfss,
+                spaces: [{_id: cluster.space, demo: isDemo}],
+                __isComplete: !!cluster && !(!hasValues(caches) || 
!hasValues(models) || !hasValues(igfss))
+            }));
+        });
+    };
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/style.scss
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/style.scss 
b/modules/web-console/frontend/app/components/page-configure/style.scss
index 424e6ba..94a3ebc 100644
--- a/modules/web-console/frontend/app/components/page-configure/style.scss
+++ b/modules/web-console/frontend/app/components/page-configure/style.scss
@@ -16,8 +16,289 @@
  */
 
 page-configure {
-    h1 version-picker {
+    font-family: Roboto;
+    flex: 1 0 auto;
+    display: flex;
+    flex-direction: column;
+
+    &>.pc-page-header {
+        display: flex;
+
+        .pc-page-header-title {
+            margin-right: auto;
+        }
+    }
+
+    .pc-form-actions-panel {
+        display: flex;
+        flex-direction: row;
+        padding: 10px 20px 10px 30px;
+        box-shadow: 0 0px 4px 0 rgba(0, 0, 0, 0.2), 0px 3px 4px -1px rgba(0, 
0, 0, 0.2);
+        position: sticky;
+        bottom: 0px;
+        // margin: 20px -30px -30px;
+        background: white;
+        border-radius: 0 0 4px 4px;
+        z-index: 10;
+
+        &>*+* {
+            margin-left: 10px;
+        }
+
+        .link-primary + .link-primary {
+            margin-left: 40px;
+        }
+
+        .pc-form-actions-panel__right-after {
+            width: 0;
+            margin-left: auto;
+        }
+    }
+
+    .pc-hide-tooltips {
+        .tipField:not(.fa-remove), .icon-help, [ignite-icon='info'] {
+            display: none;
+        }
+    }
+
+    .pc-content-container {
+        position: relative;
+
+        &, &>ui-view {
+            flex: 1 0 auto;
+            display: flex;
+            flex-direction: column;
+        }
+
+        .pc-tooltips-toggle {
+            position: absolute;
+            top: 0;
+            right: 0;
+        }
+
+        &>.tabs {
+            flex: 0 0 auto;
+        }
+    }
+
+    .pc-tooltips-toggle {
+        display: inline-flex;
+        height: 40px;
+        align-items: center;
+        width: auto;
+        max-width: none !important;
+        user-select: none;
+
+        &>*:not(input) {
+            margin-left: 5px;
+            flex: 0 0 auto;
+        }
+
+        input {
+            pointer-events: none;
+        }
+
+        &>div {
+            margin-left: 10px !important;
+        }
+    }
+}
+
+.pc-form-group {
+    $input-height: 36px;
+    width: 100%;
+    border: 1px solid rgba(197, 197, 197, 0.5);
+    border-radius: 4px;
+    padding-bottom: 10px;
+    padding-top: $input-height / 2;
+    margin-top: $input-height / -2;
+
+    &:empty {
+        display: none;
+    }
+
+}
+
+.pc-form-group__text-title {
+    transform: translateY(-9px);
+    --pc-form-group-title-bg-color: white;
+
+    &>span {
+        padding-left: 10px;
+        padding-right: 10px;
+        background: var(--pc-form-group-title-bg-color);
+    }
+
+    &>.form-field-checkbox .ignite-form-field__control {
+        & > span {
+            position: relative;
+
+            &:after {
+                content: '';
+                display: block;
+                position: absolute;
+                background-color: var(--pc-form-group-title-bg-color);
+                z-index: -1;
+                top: 0;
+                bottom: 0;
+                left: -26px;
+                right: -5px;
+            }
+        }
+        [ignite-icon] {
+            background-color: var(--pc-form-group-title-bg-color);            
+        }
+    }
+
+    &+.pc-form-group {
+        padding-top: 10px;
+    }
+}
+
+.pc-form-grid-row > .pc-form-group__text-title[class*='pc-form-grid-col-'] {
+    margin-top: 20px !important;
+}
+
+list-editable .pc-form-group__text-title {
+    --pc-form-group-title-bg-color: var(--le-row-bg-color);
+}
+
+.pc-form-grid-row {
+    display: flex;
+    flex-direction: row;
+    flex-wrap: wrap;
+    align-content: flex-start;
+
+    &>[class*='pc-form-grid-col-'] {
+        margin: 10px 0 0 !important;
+        max-width: none;
+        padding: 0 10px;
+        flex-grow: 0;
+        flex-shrink: 0;
+    }
+
+    .group-section {
+        width: 100%;
+    }
+
+    &>.pc-form-grid__break {
+        flex: 1 0 100%;
+    }
+}
+
+.pc-form-grid-row {
+    &>.pc-form-grid-col-10 {
+        flex-basis: calc(100% / 6);
+    }
+
+    &>.pc-form-grid-col-20 {
+        flex-basis: calc(100% / 3);
+    }
+
+    &>.pc-form-grid-col-30 {
+        flex-basis: calc(100% / 2);
+    }
+
+    &>.pc-form-grid-col-40 {
+        flex-basis: calc(100% / 1.5);
+    }
+
+    &>.pc-form-grid-col-60 {
+        flex-basis: calc(100% / 1);
+    }
+
+    @media(max-width: 992px) {
+        &>.pc-form-grid-col-10 {
+            flex-basis: calc(25%);
+        }
+        &>.pc-form-grid-col-20 {
+            flex-basis: calc(50%);
+        }
+
+        &>.pc-form-grid-col-30 {
+            flex-basis: calc(100%);
+        }
+
+        &>.pc-form-grid-col-60 {
+            flex-basis: calc(100%);
+        }
+    }
+    // IE11 does not count padding towards flex width
+    @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
+        &>.pc-form-grid-col-10 {
+            flex-basis: calc(99.9% / 6 - 20px);
+        }
+
+        &>.pc-form-grid-col-20 {
+            flex-basis: calc(99.9% / 3 - 20px);
+        }
+
+        &>.pc-form-grid-col-30 {
+            flex-basis: calc(100% / 2 - 20px);
+        }
+
+        &>.pc-form-grid-col-40 {
+            flex-basis: calc(100% / 1.5 - 20px);
+        }
+
+        &>.pc-form-grid-col-60 {
+            flex-basis: calc(100% / 1 - 20px);
+        }
+
+        @media(max-width: 992px) {
+            &>.pc-form-grid-col-20 {
+                flex-basis: calc(50% - 20px);
+            }
+
+            &>.pc-form-grid-col-30 {
+                flex-basis: calc(100% - 20px);
+            }
+
+            &>.pc-form-grid-col-60 {
+                flex-basis: calc(100% - 20px);
+            }
+        }
+    }
+}
+.pc-page-header {
+    font-family: Roboto;
+    font-size: 24px;
+    line-height: 36px;
+    color: #393939;
+    margin: 40px 0 30px;
+    padding: 0;
+    border: none;
+    word-break: break-all;
+
+    .pc-page-header-sub {
+        font-size: 14px;
+        line-height: 20px;
+        color: #757575;
+        margin-left: 8px;
+    }
+
+    version-picker {
         margin-left: 8px;
-        vertical-align: bottom;
+        vertical-align: 2px;
+    }
+
+    button-import-models {
+        flex: 0 0 auto;
+        margin-left: 30px;
+        position: relative;
+        // For some reason button is heigher up by 1px than on overview page
+        top: 1px;
     }
+}
+
+.pc-form-grid__text-only-item {
+    padding-top: 16px;
+    align-self: flex-start;
+    height: 54px;
+    display: flex;
+    justify-content: center;
+    align-content: center;
+    align-items: center;
+    background: white;
+    z-index: 2;
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/template.pug
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/template.pug 
b/modules/web-console/frontend/app/components/page-configure/template.pug
index 72ca614..c56320b 100644
--- a/modules/web-console/frontend/app/components/page-configure/template.pug
+++ b/modules/web-console/frontend/app/components/page-configure/template.pug
@@ -14,17 +14,34 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 
-.docs-content
-    header
-        h1
-            | Configure
-            version-picker
-    div
-        ul.tabs.tabs--blue
-            li(role='presentation' ui-sref-active='active')
-                a(ui-sref='base.configuration.tabs.basic') Basic
-            li(role='presentation' ui-sref-active='active')
-                a(ui-sref='base.configuration.tabs.advanced') Advanced
+h1.pc-page-header
+    span.pc-page-header-title
+        | {{ $ctrl.clusterName$|async:this }}
+        version-picker
+    button-import-models(cluster-id='$ctrl.clusterID$|async:this')
+div.pc-content-container
+    ul.tabs.tabs--blue
+        li(role='presentation' ui-sref-active='active')
+            a(ui-sref='base.configuration.edit.basic') Basic
+        li(role='presentation' ui-sref-active='{active: 
"base.configuration.edit.advanced"}')
+            a(ui-sref='base.configuration.edit.advanced.cluster') Advanced
 
-        .panel--ignite
-            ui-view
\ No newline at end of file
+    label.pc-tooltips-toggle.switcher--ignite
+        svg.icon-left(
+            ignite-icon='info'
+            bs-tooltip=''
+            data-title='This setting is needed to hide and show tooltips with 
hints.'
+            data-placement='left'
+        )
+        span Tooltips
+        input(type='checkbox' ng-model='$ctrl.tooltipsVisible')
+        div
+
+    ui-view.theme--ignite(
+        ignite-loading='configuration'
+        ignite-loading-text='{{ $ctrl.loadingText }}'
+        ignite-loading-position='top'
+        ng-class=`{
+            'pc-hide-tooltips': !$ctrl.tooltipsVisible
+        }`
+    )
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/transitionHooks/errorState.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/transitionHooks/errorState.js
 
b/modules/web-console/frontend/app/components/page-configure/transitionHooks/errorState.js
new file mode 100644
index 0000000..f36a931
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/transitionHooks/errorState.js
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {RejectType} from '@uirouter/angularjs';
+
+const isPromise = (object) => object && typeof object.then === 'function';
+const match = {
+    to(state) {
+        return state.data && state.data.errorState;
+    }
+};
+const go = ($transition) => $transition.router.stateService.go(
+    $transition.to().data.errorState,
+    $transition.params(),
+    {location: 'replace'}
+);
+
+/**
+ * @returns {Array<Promise>}
+ */
+const getResolvePromises = ($transition) => $transition.getResolveTokens()
+    .filter((token) => typeof token === 'string')
+    .map((token) => $transition.injector().getAsync(token))
+    .filter(isPromise);
+
+/**
+ * Global transition hook that redirects to data.errorState if:
+ * 1. Transition throws an error.
+ * 2. Any resolve promise throws an error. onError does not work for this case 
if resolvePolicy is set to 'NOWAIT'.
+ */
+export const errorState = ($uiRouter) => {
+    $uiRouter.transitionService.onError(match, ($transition) => {
+        if ($transition.error().type !== RejectType.ERROR) return;
+        go($transition);
+    });
+    $uiRouter.transitionService.onStart(match, ($transition) => {
+        Promise.all(getResolvePromises($transition)).catch((e) => 
go($transition));
+    });
+};
+
+errorState.$inject = ['$uiRouter'];

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/types/uirouter.d.ts
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/types/uirouter.d.ts
 
b/modules/web-console/frontend/app/components/page-configure/types/uirouter.d.ts
new file mode 100644
index 0000000..90d8434
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/types/uirouter.d.ts
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as _uirouter from '@uirouter/angularjs'
+export as namespace uirouter
+export = _uirouter
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-profile/style.scss
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-profile/style.scss 
b/modules/web-console/frontend/app/components/page-profile/style.scss
index a96914e..f4eae1a 100644
--- a/modules/web-console/frontend/app/components/page-profile/style.scss
+++ b/modules/web-console/frontend/app/components/page-profile/style.scss
@@ -28,4 +28,8 @@ page-profile {
     .btn-ignite + .btn-ignite {
         margin-left: 10px;
     }
+
+    [ignite-icon='expand'], [ignite-icon='collapse'] {
+        color: #757575;
+    }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-queries/components/queries-notebook/controller.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-queries/components/queries-notebook/controller.js
 
b/modules/web-console/frontend/app/components/page-queries/components/queries-notebook/controller.js
index 55bee85..fcb3609 100644
--- 
a/modules/web-console/frontend/app/components/page-queries/components/queries-notebook/controller.js
+++ 
b/modules/web-console/frontend/app/components/page-queries/components/queries-notebook/controller.js
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
+import {nonEmpty, nonNil} from 'app/utils/lodashMixins';
 import 'rxjs/add/operator/mergeMap';
 import 'rxjs/add/operator/merge';
 import 'rxjs/add/operator/switchMap';
@@ -147,8 +147,8 @@ class Paragraph {
 
             let cause = err;
 
-            while (_.nonNil(cause)) {
-                if (_.nonEmpty(cause.className) &&
+            while (nonNil(cause)) {
+                if (nonEmpty(cause.className) &&
                     _.includes(['SQLException', 'JdbcSQLException', 
'QueryCancelledException'], JavaTypes.shortClassName(cause.className))) {
                     this.error.message = cause.message || cause.className;
 
@@ -158,10 +158,10 @@ class Paragraph {
                 cause = cause.cause;
             }
 
-            if (_.isEmpty(this.error.message) && _.nonEmpty(err.className)) {
+            if (_.isEmpty(this.error.message) && nonEmpty(err.className)) {
                 this.error.message = 'Internal cluster error';
 
-                if (_.nonEmpty(err.className))
+                if (nonEmpty(err.className))
                     this.error.message += ': ' + err.className;
             }
         };
@@ -171,7 +171,7 @@ class Paragraph {
         if (_.isNil(this.queryArgs))
             return null;
 
-        if (_.nonEmpty(this.error.message))
+        if (nonEmpty(this.error.message))
             return 'error';
 
         if (_.isEmpty(this.rows))
@@ -197,7 +197,7 @@ class Paragraph {
     }
 
     queryExecuted() {
-        return _.nonEmpty(this.meta) || _.nonEmpty(this.error.message);
+        return nonEmpty(this.meta) || nonEmpty(this.error.message);
     }
 
     scanExplain() {
@@ -209,11 +209,11 @@ class Paragraph {
     }
 
     chartColumnsConfigured() {
-        return _.nonEmpty(this.chartKeyCols) && _.nonEmpty(this.chartValCols);
+        return nonEmpty(this.chartKeyCols) && nonEmpty(this.chartValCols);
     }
 
     chartTimeLineEnabled() {
-        return _.nonEmpty(this.chartKeyCols) && _.eq(this.chartKeyCols[0], 
TIME_LINE);
+        return nonEmpty(this.chartKeyCols) && _.eq(this.chartKeyCols[0], 
TIME_LINE);
     }
 
     executionInProgress(showLocal = false) {
@@ -922,7 +922,7 @@ export class NotebookCtrl {
                     });
 
                     // Await for demo caches.
-                    if (!$ctrl.demoStarted && $root.IgniteDemoMode && 
_.nonEmpty(cacheNames)) {
+                    if (!$ctrl.demoStarted && $root.IgniteDemoMode && 
nonEmpty(cacheNames)) {
                         $ctrl.demoStarted = true;
 
                         Loading.finish('sqlLoading');
@@ -935,7 +935,7 @@ export class NotebookCtrl {
 
         const _startWatch = () => {
             const awaitClusters$ = fromPromise(
-                agentMgr.startClusterWatch('Back to Configuration', 
'base.configuration.tabs.advanced.clusters'));
+                agentMgr.startClusterWatch('Back to Configuration', 
'base.configuration.overview'));
 
             const finishLoading$ = defer(() => {
                 if (!$root.IgniteDemoMode)
@@ -1919,7 +1919,7 @@ export class NotebookCtrl {
                 const tab = '&nbsp;&nbsp;&nbsp;&nbsp;';
 
                 const addToTrace = (item) => {
-                    if (_.nonNil(item)) {
+                    if (nonNil(item)) {
                         const clsName = _.isEmpty(item.className) ? '' : '[' + 
JavaTypes.shortClassName(item.className) + '] ';
 
                         scope.content.push((scope.content.length > 0 ? tab : 
'') + clsName + (item.message || ''));

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-queries/components/queries-notebooks-list/controller.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-queries/components/queries-notebooks-list/controller.js
 
b/modules/web-console/frontend/app/components/page-queries/components/queries-notebooks-list/controller.js
index 8131483..7c06f2a 100644
--- 
a/modules/web-console/frontend/app/components/page-queries/components/queries-notebooks-list/controller.js
+++ 
b/modules/web-console/frontend/app/components/page-queries/components/queries-notebooks-list/controller.js
@@ -126,7 +126,7 @@ export class NotebooksListCtrl {
 
     _checkActionsAllow() {
         // Dissallow clone and rename if more then one item is selectted.
-        const oneItemIsSelected  = 
this.gridApi.selection.getSelectedRows().length === 1;
+        const oneItemIsSelected  = 
this.gridApi.selection.legacyGetSelectedRows().length === 1;
         this.actionOptions[0].available = oneItemIsSelected;
         this.actionOptions[1].available = oneItemIsSelected;
     }
@@ -154,7 +154,7 @@ export class NotebooksListCtrl {
 
     async renameNotebok() {
         try {
-            const currentNotebook =  
this.gridApi.selection.getSelectedRows()[0];
+            const currentNotebook =  
this.gridApi.selection.legacyGetSelectedRows()[0];
             const newNotebookName =  await this.IgniteInput.input('Rename 
notebook', 'Notebook name', currentNotebook.name);
 
             if (this.getNotebooksNames().find((name) => newNotebookName === 
name))
@@ -174,7 +174,7 @@ export class NotebooksListCtrl {
 
     async cloneNotebook() {
         try {
-            const clonedNotebook = Object.assign({}, 
this.gridApi.selection.getSelectedRows()[0]);
+            const clonedNotebook = Object.assign({}, 
this.gridApi.selection.legacyGetSelectedRows()[0]);
             const newNotebookName = await 
this.IgniteInput.clone(clonedNotebook.name, this.getNotebooksNames());
 
             this.IgniteLoading.start('notebooksLoading');
@@ -201,7 +201,7 @@ export class NotebooksListCtrl {
     async deleteNotebooks() {
         try {
             this.IgniteLoading.start('notebooksLoading');
-            await 
this.IgniteNotebook.removeBatch(this.gridApi.selection.getSelectedRows());
+            await 
this.IgniteNotebook.removeBatch(this.gridApi.selection.legacyGetSelectedRows());
             await this.IgniteLoading.finish('notebooksLoading');
 
             this._loadAllNotebooks();

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-queries/components/queries-notebooks-list/template.tpl.pug
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-queries/components/queries-notebooks-list/template.tpl.pug
 
b/modules/web-console/frontend/app/components/page-queries/components/queries-notebooks-list/template.tpl.pug
index 25785a4..75b5e99 100644
--- 
a/modules/web-console/frontend/app/components/page-queries/components/queries-notebooks-list/template.tpl.pug
+++ 
b/modules/web-console/frontend/app/components/page-queries/components/queries-notebooks-list/template.tpl.pug
@@ -34,7 +34,7 @@ page-queries-slot(slot-name="'queriesButtons'" 
ng-if="!$root.IgniteDemoMode")
                         label: 'Actions',
                         model: '$ctrl.action',
                         name: 'action',
-                        disabled: 
'$ctrl.gridApi.selection.getSelectedRows().length === 0',
+                        disabled: 
'$ctrl.gridApi.selection.legacyGetSelectedRows().length === 0',
                         required: false,
                         options: '$ctrl.actionOptions'
                     })

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/version-picker/style.scss
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/version-picker/style.scss 
b/modules/web-console/frontend/app/components/version-picker/style.scss
index 98a4e9d..6d962c2 100644
--- a/modules/web-console/frontend/app/components/version-picker/style.scss
+++ b/modules/web-console/frontend/app/components/version-picker/style.scss
@@ -29,9 +29,8 @@ version-picker {
         padding-bottom: 1px;
     }
 
-    .icon-help {
+    [ignite-icon] {
         margin-left: 5px;
-        font-size: 16px;
     }
 
     .dropdown-menu a {

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/version-picker/template.pug
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/version-picker/template.pug 
b/modules/web-console/frontend/app/components/version-picker/template.pug
index 1abe471..5d35b78 100644
--- a/modules/web-console/frontend/app/components/version-picker/template.pug
+++ b/modules/web-console/frontend/app/components/version-picker/template.pug
@@ -24,10 +24,14 @@
 )
     | {{$ctrl.currentVersion}}
     span.icon-right.fa.fa-caret-down
-    
-i.icon-help(
+
+svg.icon-help(
+    ignite-icon='info'
     bs-tooltip=''
-    data-title='Web Console supports multiple Ignite versions.<br /> \
-                Select version you need to configure cluster.'
+    data-title=`
+        Web Console supports multiple Ignite versions.
+        <br>
+        Select version you need to configure cluster.
+    `
     data-placement='right'
-)
+)
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/core/activities/Activities.data.d.ts
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/core/activities/Activities.data.d.ts 
b/modules/web-console/frontend/app/core/activities/Activities.data.d.ts
new file mode 100644
index 0000000..88f9dd4
--- /dev/null
+++ b/modules/web-console/frontend/app/core/activities/Activities.data.d.ts
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+interface IActivityDataResponse {
+    action: string,
+    amount: number,
+    date: string,
+    group: string,
+    owner: string,
+    _id: string
+}
+
+/**
+ * Activities data service
+ */
+declare class ActivitiesData {
+    /** 
+     * Posts activity to backend, sends current state if no options specified
+     */
+    post({group, action}?:{group?: string, action?: string}): 
ng.IPromise<ng.IHttpResponse<IActivityDataResponse>>    
+}
+
+export default ActivitiesData
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/core/activities/Activities.data.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/core/activities/Activities.data.js 
b/modules/web-console/frontend/app/core/activities/Activities.data.js
index 8d9447c..35b44e6 100644
--- a/modules/web-console/frontend/app/core/activities/Activities.data.js
+++ b/modules/web-console/frontend/app/core/activities/Activities.data.js
@@ -18,6 +18,10 @@
 export default class ActivitiesData {
     static $inject = ['$http', '$state'];
 
+    /**
+     * @param {ng.IHttpService} $http
+     * @param {uirouter.StateService} $state
+     */
     constructor($http, $state) {
         this.$http = $http;
         this.$state = $state;
@@ -26,8 +30,10 @@ export default class ActivitiesData {
     post(options = {}) {
         let { group, action } = options;
 
-        action = action || this.$state.$current.url.source;
-        group = group || action.match(/^\/([^/]+)/)[1];
+        // TODO IGNITE-5466: since upgrade to UIRouter 1, "url.source" is 
undefined.
+        // Actions like that won't be saved to DB. Think of a better solution 
later.
+        action = action || this.$state.$current.url.source || '';
+        group = group || (action.match(/^\/([^/]+)/) || [])[1];
 
         return this.$http.post('/api/v1/activities/page', { group, action });
     }

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/data/getting-started.json
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/data/getting-started.json 
b/modules/web-console/frontend/app/data/getting-started.json
index fd1869c..caed920 100644
--- a/modules/web-console/frontend/app/data/getting-started.json
+++ b/modules/web-console/frontend/app/data/getting-started.json
@@ -74,23 +74,6 @@
         ]
     },
     {
-      "title": "Summary",
-      "message": [
-          "<div class='col-xs-7'>",
-          " <img src='/images/summary.png' width='100%' />",
-          "</div>",
-          "<div class='col-xs-5'>",
-          " <ul>",
-          "  <li>Preview XML configuration</li>",
-          "  <li>Preview code configuration</li>",
-          "  <li>Preview Docker file</li>",
-          "  <li>Preview POM dependencies</li>",
-          "  <li>Download ready-to-use project</li>",
-          " </ul>",
-          "</div>"
-      ]
-    },
-    {
         "title": "SQL Queries",
         "message": [
             "<div class='col-xs-7'>",

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/data/i18n.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/data/i18n.js 
b/modules/web-console/frontend/app/data/i18n.js
index 3385f60..d992153 100644
--- a/modules/web-console/frontend/app/data/i18n.js
+++ b/modules/web-console/frontend/app/data/i18n.js
@@ -18,11 +18,19 @@
 export default {
     '/agent/start': 'Agent start',
     '/agent/download': 'Agent download',
-    '/configuration/clusters': 'Configure clusters',
-    '/configuration/caches': 'Configure caches',
-    '/configuration/domains': 'Configure domain model',
-    '/configuration/igfs': 'Configure IGFS',
-    '/configuration/summary': 'Configurations summary',
+    'base.configuration.overview': 'Cluster configurations',
+    '/configuration/overview': 'Cluster configurations',
+    'base.configuration.edit.basic': 'Basic cluster configuration edit',
+    '/configuration/new': 'Сluster configuration create',
+    '/configuration/new/basic': 'Basic cluster configuration create',
+    '/configuration/new/advanced/cluster': 'Advanced cluster configuration 
create',
+    'base.configuration.edit.advanced.cluster': 'Advanced cluster 
configuration edit',
+    'base.configuration.edit.advanced.caches': 'Advanced cluster caches',
+    'base.configuration.edit.advanced.caches.cache': 'Advanced cluster cache 
edit',
+    'base.configuration.edit.advanced.models': 'Advanced cluster models',
+    'base.configuration.edit.advanced.models.model': 'Advanced cluster model 
edit',
+    'base.configuration.edit.advanced.igfs': 'Advanced cluster IGFSs',
+    'base.configuration.edit.advanced.igfs.igfs': 'Advanced cluster IGFS edit',
     '/configuration/download': 'Download project',
     '/demo/resume': 'Demo resume',
     '/demo/reset': 'Demo reset',

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/directives/on-focus-out.directive.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/directives/on-focus-out.directive.js 
b/modules/web-console/frontend/app/directives/on-focus-out.directive.js
index 802691f..fe59477 100644
--- a/modules/web-console/frontend/app/directives/on-focus-out.directive.js
+++ b/modules/web-console/frontend/app/directives/on-focus-out.directive.js
@@ -15,23 +15,93 @@
  * limitations under the License.
  */
 
-export default ['$parse', ($parse) => {
-    return ($scope, $element, $attrs) => {
-        const parsedExpr = $parse($attrs.igniteOnFocusOut);
+/**
+ * @type {ng.IComponentController}
+ */
+class OnFocusOutController {
+    /** @type {OnFocusOutController} */
+    parent;
+    /** @type {Array<OnFocusOutController>} */
+    children = [];
+    /** @type {Array<string>} */
+    ignoredClasses = [];
+    /** @type {function} */
+    igniteOnFocusOut;
 
-        const handlerCheckFocusOut = (FocusClick) => {
-            if ($element.find(FocusClick.target).length)
-                return;
+    static $inject = ['$element', '$window', '$scope'];
+    /**
+     * @param {JQLite} $element
+     * @param {ng.IWindowService} $window 
+     * @param {ng.IScope} $scope
+     */
+    constructor($element, $window, $scope) {
+        this.$element = $element;
+        this.$window = $window;
+        this.$scope = $scope;
 
-            $scope.$evalAsync(() => parsedExpr($scope));
+        /** @param {MouseEvent|FocusEvent} e */
+        this._eventHandler = (e) => {
+            this.children.forEach((c) => c._eventHandler(e));
+            if (this.shouldPropagate(e) && this.isFocused) {
+                this.$scope.$applyAsync(() => {
+                    this.igniteOnFocusOut();
+                    this.isFocused = false;
+                });
+            }
         };
+        /** @param {FocusEvent} e */
+        this._onFocus = (e) => {
+            this.isFocused = true;
+        };
+    }
+    $onDestroy() {
+        this.$window.removeEventListener('click', this._eventHandler, true);
+        this.$window.removeEventListener('focusin', this._eventHandler, true);
+        this.$element[0].removeEventListener('focus', this._onFocus, true);
+        if (this.parent) 
this.parent.children.splice(this.parent.children.indexOf(this), 1);
+        this.$element = this.$window = this._eventHandler = this._onFocus = 
null;
+    }
+    shouldPropagate(e) {
+        return !this.targetHasIgnoredClasses(e) && 
this.targetIsOutOfElement(e);
+    }
+    targetIsOutOfElement(e) {
+        return !this.$element.find(e.target).length;
+    }
+    targetHasIgnoredClasses(e) {
+        return this.ignoredClasses.some((c) => e.target.classList.contains(c));
+    }
+    /**
+     * @param {ng.IOnChangesObject} changes [description]
+     */
+    $onChanges(changes) {
+        if (
+            'ignoredClasses' in changes &&
+            changes.ignoredClasses.currentValue !== 
changes.ignoredClasses.previousValue
+        )
+            this.ignoredClasses = changes.ignoredClasses.currentValue.split(' 
').concat('body-overlap');
+    }
+    $onInit() {
+        if (this.parent) this.parent.children.push(this);
+    }
+    $postLink() {
+        this.$window.addEventListener('click', this._eventHandler, true);
+        this.$window.addEventListener('focusin', this._eventHandler, true);
+        this.$element[0].addEventListener('focus', this._onFocus, true);
+    }
+}
 
-        window.addEventListener('click', handlerCheckFocusOut, true);
-        window.addEventListener('focusin', handlerCheckFocusOut, true);
-
-        $scope.$on('$destroy', () => {
-            window.removeEventListener('click', handlerCheckFocusOut, true);
-            window.removeEventListener('focusin', handlerCheckFocusOut, true);
-        });
+/**
+ * @type {ng.IDirectiveFactory}
+ */
+export default function() {
+    return {
+        controller: OnFocusOutController,
+        require: {
+            parent: '^^?igniteOnFocusOut'
+        },
+        bindToController: {
+            igniteOnFocusOut: '&',
+            ignoredClasses: '@?igniteOnFocusOutIgnoredClasses'
+        }
     };
-}];
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/directives/ui-ace-pojos/ui-ace-pojos.controller.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/directives/ui-ace-pojos/ui-ace-pojos.controller.js
 
b/modules/web-console/frontend/app/directives/ui-ace-pojos/ui-ace-pojos.controller.js
index 774d73e..3f9580f 100644
--- 
a/modules/web-console/frontend/app/directives/ui-ace-pojos/ui-ace-pojos.controller.js
+++ 
b/modules/web-console/frontend/app/directives/ui-ace-pojos/ui-ace-pojos.controller.js
@@ -15,6 +15,8 @@
  * limitations under the License.
  */
 
+import {nonNil} from 'app/utils/lodashMixins';
+
 export default ['$scope', 'JavaTypes', 'JavaTransformer', function($scope, 
JavaTypes, generator) {
     const ctrl = this;
 
@@ -47,7 +49,7 @@ export default ['$scope', 'JavaTypes', 'JavaTransformer', 
function($scope, JavaT
             const classes = ctrl.classes = [];
 
             _.forEach(ctrl.pojos, (pojo) => {
-                if (_.nonNil(pojo.keyClass))
+                if (nonNil(pojo.keyClass))
                     classes.push(pojo.keyType);
 
                 classes.push(pojo.valueType);

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/directives/ui-ace.controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/directives/ui-ace.controller.js 
b/modules/web-console/frontend/app/directives/ui-ace.controller.js
index 96e9c5e..3b35b2a 100644
--- a/modules/web-console/frontend/app/directives/ui-ace.controller.js
+++ b/modules/web-console/frontend/app/directives/ui-ace.controller.js
@@ -34,23 +34,6 @@ export default class IgniteUiAceGeneratorFactory {
                 this.generate = (cluster) => 
this.generatorFactory.cluster(cluster, this.Version.currentSbj.getValue(), 
this.client === 'true');
 
                 break;
-            case 'clusterCaches':
-                this.generate = (cluster, caches) => {
-                    const clusterCaches = _.reduce(caches, (acc, cache) => {
-                        if (_.includes(cluster.caches, cache.value))
-                            acc.push(cache.cache);
-
-                        return acc;
-                    }, []);
-
-                    const cfg = 
this.generatorFactory.generator.clusterGeneral(cluster, available);
-
-                    this.generatorFactory.generator.clusterCaches(cluster, 
clusterCaches, null, available, false, cfg);
-
-                    return this.generatorFactory.toSection(cfg);
-                };
-
-                break;
             case 'cacheStore':
             case 'cacheQuery':
                 this.generate = (cache, domains) => {
@@ -79,27 +62,13 @@ export default class IgniteUiAceGeneratorFactory {
                 break;
             case 'clusterServiceConfiguration':
                 this.generate = (cluster, caches) => {
-                    const clusterCaches = _.reduce(caches, (acc, cache) => {
-                        if (_.includes(cluster.caches, cache.value))
-                            acc.push(cache.cache);
-
-                        return acc;
-                    }, []);
-
-                    return 
this.generatorFactory.clusterServiceConfiguration(cluster.serviceConfigurations,
 clusterCaches);
+                    return 
this.generatorFactory.clusterServiceConfiguration(cluster.serviceConfigurations,
 caches);
                 };
 
                 break;
             case 'clusterCheckpoint':
                 this.generate = (cluster, caches) => {
-                    const clusterCaches = _.reduce(caches, (acc, cache) => {
-                        if (_.includes(cluster.caches, cache.value))
-                            acc.push(cache.cache);
-
-                        return acc;
-                    }, []);
-
-                    return this.generatorFactory.clusterCheckpoint(cluster, 
available, clusterCaches);
+                    return this.generatorFactory.clusterCheckpoint(cluster, 
available, caches);
                 };
 
                 break;

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/helpers/jade/form.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/helpers/jade/form.pug 
b/modules/web-console/frontend/app/helpers/jade/form.pug
index 6fddbf6..44eaed9 100644
--- a/modules/web-console/frontend/app/helpers/jade/form.pug
+++ b/modules/web-console/frontend/app/helpers/jade/form.pug
@@ -24,5 +24,3 @@ include ./form/form-field-checkbox
 include ./form/form-field-number
 include ./form/form-field-up
 include ./form/form-field-down
-
-include ./form/form-group

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/helpers/jade/form/form-field-checkbox.pug
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/helpers/jade/form/form-field-checkbox.pug 
b/modules/web-console/frontend/app/helpers/jade/form/form-field-checkbox.pug
index fcd6f9d..a8236a9 100644
--- a/modules/web-console/frontend/app/helpers/jade/form/form-field-checkbox.pug
+++ b/modules/web-console/frontend/app/helpers/jade/form/form-field-checkbox.pug
@@ -15,25 +15,30 @@
     limitations under the License.
 
 mixin form-field-checkbox(label, model, name, disabled, required, tip)
-    .checkbox(id=`{{ ${name} }}Field`)
-        label(id=`{{ ${name} }}Label`)
-            .input-tip
+        label.form-field-checkbox.ignite-form-field
+            .ignite-form-field__control
+                input(
+                    id=`{{ ${name} }}Input`
+                    name=`{{ ${name} }}`
+                    type='checkbox'
+
+                    ng-model=model
+                    ng-required=required && `${required}`
+                    ng-disabled=disabled && `${disabled}`
+                    expose-ignite-form-field-control='$input'
+                )&attributes(attributes ? attributes.attributes ? 
attributes.attributes : attributes : {})
+                span #{label}
+                +tooltip(tip, tipOpts, 'tipLabel')
+            .ignite-form-field__errors(
+                ng-messages=`$input.$error`
+                ng-show=`($input.$dirty || $input.$touched || 
$input.$submitted) && $input.$invalid`
+            )
                 if block
                     block
-                else
-                    input(
-                        id=`{{ ${name} }}Input`
-                        name=`{{ ${name} }}`
-                        type='checkbox'
-
-                        data-ng-model=model
-                        data-ng-required=required && `${required}`
-                        data-ng-disabled=disabled && `${disabled}`
-
-                        data-ng-focus='tableReset()'
-
-                        data-ignite-form-panel-field=''
-                    )
-            span #{label}
+                if required
+                    +form-field-feedback(name, 'required', `${errLbl} could 
not be empty!`)
 
-            +tooltip(tip, tipOpts, 'tipLabel')
+mixin sane-form-field-checkbox({label, model, name, disabled, required, tip})
+    +form-field-checkbox(label, model, name, disabled = false, required = 
false, tip)&attributes(attributes)
+        if block
+            block
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/helpers/jade/form/form-field-datalist.pug
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/helpers/jade/form/form-field-datalist.pug 
b/modules/web-console/frontend/app/helpers/jade/form/form-field-datalist.pug
index 6da1255..888634b 100644
--- a/modules/web-console/frontend/app/helpers/jade/form/form-field-datalist.pug
+++ b/modules/web-console/frontend/app/helpers/jade/form/form-field-datalist.pug
@@ -23,29 +23,30 @@ mixin form-field-datalist(label, model, name, disabled, 
required, placeholder, o
             name=`{{ ${name} }}`
             placeholder=placeholder
            
-            data-ng-model=model
+            ng-model=model
 
-            data-ng-required=required && `${required}`
-            data-ng-disabled=disabled && `${disabled}` || `!${options}.length`
+            ng-required=required && `${required}`
+            ng-disabled=disabled && `${disabled}` || `!${options}.length`
 
             bs-typeahead
             bs-options=`item for item in ${options}`
             container='body'
             data-min-length='1'
             ignite-retain-selection
-
-            data-ignite-form-panel-field=''
+            expose-ignite-form-field-control='$input'
         )&attributes(attributes.attributes)
 
     .ignite-form-field
-        +ignite-form-field__label(label, name, required)
-        .ignite-form-field__control
+        +ignite-form-field__label(label, name, required, disabled)
             +tooltip(tip, tipOpts)
-
-            +form-field-feedback(name, 'required', errLbl + ' could not be 
empty!')
-
+        .ignite-form-field__control
+            .input-tip
+                +form-field-input(attributes=attributes)
+        .ignite-form-field__errors(
+            ng-messages=`$input.$error`
+            ng-if=`($input.$dirty || $input.$touched || $input.$submitted) && 
$input.$invalid`
+        )
             if block
                 block
 
-            .input-tip
-                +form-field-input(attributes=attributes)
+            +form-field-feedback(name, 'required', `${errLbl} could not be 
empty!`)

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/helpers/jade/form/form-field-dropdown.pug
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/helpers/jade/form/form-field-dropdown.pug 
b/modules/web-console/frontend/app/helpers/jade/form/form-field-dropdown.pug
index cf7d50a..c6579e3 100644
--- a/modules/web-console/frontend/app/helpers/jade/form/form-field-dropdown.pug
+++ b/modules/web-console/frontend/app/helpers/jade/form/form-field-dropdown.pug
@@ -16,33 +16,45 @@
 
 mixin ignite-form-field-dropdown(label, model, name, disabled, required, 
multiple, placeholder, placeholderEmpty, options, tip)
     mixin form-field-input()
+        -var errLbl = label.substring(0, label.length - 1)
+
         button.select-toggle.form-control(
+            type='button'
             id=`{{ ${name} }}Input`
             name=`{{ ${name} }}`
 
             data-placeholder=placeholderEmpty ? `{{ ${options}.length > 0 ? 
'${placeholder}' : '${placeholderEmpty}' }}` : placeholder
             
-            data-ng-model=model
-            data-ng-disabled=disabled && `${disabled}`
-            data-ng-required=required && `${required}`
+            ng-model=model
+            ng-disabled=disabled && `${disabled}`
+            ng-required=required && `${required}`
 
             bs-select
             bs-options=`item.value as item.label for item in ${options}`
+            expose-ignite-form-field-control='$input'
 
             data-multiple=multiple ? '1' : false
 
             tabindex='0'
-
-            data-ignite-form-panel-field=''
         )&attributes(attributes.attributes)
 
-    .ignite-form-field
-        +ignite-form-field__label(label, name, required)
-        .ignite-form-field__control
+    .ignite-form-field.ignite-form-field-dropdown
+        +ignite-form-field__label(label, name, required, disabled)
             +tooltip(tip, tipOpts)
-
+        .ignite-form-field__control
+            .input-tip
+                +form-field-input(attributes=attributes)
+        .ignite-form-field__errors(
+            ng-messages=`$input.$error`
+            ng-show=`($input.$dirty || $input.$touched || $input.$submitted) 
&& $input.$invalid`
+        )
             if block
                 block
 
-            .input-tip
-                +form-field-input(attributes=attributes)
+            if required
+                +form-field-feedback(name, 'required', multiple ? 'At least 
one option should be selected' : 'An option should be selected')
+
+mixin sane-ignite-form-field-dropdown({label, model, name, disabled = false, 
required = false, multiple = false, placeholder, placeholderEmpty, options, 
tip})
+    +ignite-form-field-dropdown(label, model, name, disabled, required, 
multiple, placeholder, placeholderEmpty, options, tip)&attributes(attributes)
+        if block
+            block
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/helpers/jade/form/form-field-feedback.pug
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/helpers/jade/form/form-field-feedback.pug 
b/modules/web-console/frontend/app/helpers/jade/form/form-field-feedback.pug
index c70e7a3..2fa0a3c 100644
--- a/modules/web-console/frontend/app/helpers/jade/form/form-field-feedback.pug
+++ b/modules/web-console/frontend/app/helpers/jade/form/form-field-feedback.pug
@@ -15,18 +15,4 @@
     limitations under the License.
 
 mixin form-field-feedback(name, error, message)
-    -var __field = `${form}[${name}]`
-    -var __error = `${__field}.$error.${error}`
-    -var __pristine = `${__field}.$pristine`
-
-    i.fa.fa-exclamation-triangle.form-field-feedback(
-        ng-if=`!${__pristine} && ${__error}`
-        name=`{{ ${name} }}`
-
-        bs-tooltip
-        data-title=message
-
-        ignite-error=error
-        ignite-error-message=message
-        ignite-restore-input-focus
-    )
+    div(ng-message=error) #{message}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/helpers/jade/form/form-field-label.pug
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/helpers/jade/form/form-field-label.pug 
b/modules/web-console/frontend/app/helpers/jade/form/form-field-label.pug
index d0275c9..2edd115 100644
--- a/modules/web-console/frontend/app/helpers/jade/form/form-field-label.pug
+++ b/modules/web-console/frontend/app/helpers/jade/form/form-field-label.pug
@@ -14,10 +14,12 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 
-mixin ignite-form-field__label(label, name, required)
+mixin ignite-form-field__label(label, name, required, disabled)
     label.ignite-form-field__label(
         id=`{{ ${name} }}Label`
         for=`{{ ${name} }}Input`
-        class=`{{ ${required} ? 'required' : '' }}`
+        ng-class=disabled && `{'ignite-form-field__label-disabled': 
${disabled}}`
     )
-        span !{label}
+        span(class=`{{ ${required} ? 'required' : '' }}`) !{label}
+        if block
+            block
\ No newline at end of file

Reply via email to