You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ak...@apache.org on 2018/04/02 12:24:42 UTC

[19/24] ignite git commit: IGNITE-5466 Web Console: Configuration reworked to cluster centric model: 1. Reworked data model. 2. Implemented migrations. 3. Reworked UI for all screens. 4. Reworked validation. 5. Many refactorings to improve code base

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

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/button-download-project/template.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-configure/components/button-download-project/template.pug b/modules/web-console/frontend/app/components/page-configure/components/button-download-project/template.pug
new file mode 100644
index 0000000..0264676
--- /dev/null
+++ b/modules/web-console/frontend/app/components/page-configure/components/button-download-project/template.pug
@@ -0,0 +1,22 @@
+//-
+    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.
+
+button.btn-ignite.btn-ignite--success(
+    type='button'
+    ng-click='$ctrl.download()'
+)
+    svg(ignite-icon='download').icon-left
+    | Download project

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/button-import-models/component.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-configure/components/button-import-models/component.js b/modules/web-console/frontend/app/components/page-configure/components/button-import-models/component.js
new file mode 100644
index 0000000..28b7aa0
--- /dev/null
+++ b/modules/web-console/frontend/app/components/page-configure/components/button-import-models/component.js
@@ -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.
+ */
+
+import template from './template.pug';
+import './style.scss';
+
+export class ButtonImportModels {
+    static $inject = ['ModalImportModels'];
+    constructor(ModalImportModels) {
+        Object.assign(this, {ModalImportModels});
+    }
+    startImport() {
+        return this.ModalImportModels.open(this.clusterID);
+    }
+}
+export const component = {
+    name: 'buttonImportModels',
+    controller: ButtonImportModels,
+    template,
+    bindings: {
+        clusterID: '<clusterId'
+    }
+};

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

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/button-import-models/style.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-configure/components/button-import-models/style.scss b/modules/web-console/frontend/app/components/page-configure/components/button-import-models/style.scss
new file mode 100644
index 0000000..4944626
--- /dev/null
+++ b/modules/web-console/frontend/app/components/page-configure/components/button-import-models/style.scss
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+button-import-models {
+    display: inline-block;
+
+    button {
+        // Ensures same height for wrapper element and button
+        vertical-align: top;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/button-import-models/template.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-configure/components/button-import-models/template.pug b/modules/web-console/frontend/app/components/page-configure/components/button-import-models/template.pug
new file mode 100644
index 0000000..25c3531
--- /dev/null
+++ b/modules/web-console/frontend/app/components/page-configure/components/button-import-models/template.pug
@@ -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.
+
+button.btn-ignite.btn-ignite--primary(
+    ng-click='$ctrl.startImport()'
+    type='button'
+) Import from Database
\ 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/components/button-preview-project/component.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-configure/components/button-preview-project/component.js b/modules/web-console/frontend/app/components/page-configure/components/button-preview-project/component.js
new file mode 100644
index 0000000..095cb0f
--- /dev/null
+++ b/modules/web-console/frontend/app/components/page-configure/components/button-preview-project/component.js
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import template from './template.pug';
+
+export class ButtonPreviewProject {
+    static $inject = ['ModalPreviewProject'];
+    constructor(ModalPreviewProject) {
+        Object.assign(this, {ModalPreviewProject});
+    }
+    preview() {
+        return this.ModalPreviewProject.open(this.cluster);
+    }
+}
+export const component = {
+    name: 'buttonPreviewProject',
+    controller: ButtonPreviewProject,
+    template,
+    bindings: {
+        cluster: '<'
+    }
+};

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

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/button-preview-project/template.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-configure/components/button-preview-project/template.pug b/modules/web-console/frontend/app/components/page-configure/components/button-preview-project/template.pug
new file mode 100644
index 0000000..3c3ca38
--- /dev/null
+++ b/modules/web-console/frontend/app/components/page-configure/components/button-preview-project/template.pug
@@ -0,0 +1,22 @@
+//-
+    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.
+
+button.btn-ignite.btn-ignite--link-dashed-success(
+    type='button'
+    ng-click='$ctrl.preview()'
+)
+    svg(ignite-icon='structure').icon-left
+    | See project structure
\ 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/components/fakeUICanExit.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-configure/components/fakeUICanExit.js b/modules/web-console/frontend/app/components/page-configure/components/fakeUICanExit.js
new file mode 100644
index 0000000..c0837ed
--- /dev/null
+++ b/modules/web-console/frontend/app/components/page-configure/components/fakeUICanExit.js
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+class FakeUiCanExitController {
+    static $inject = ['$element', '$transitions'];
+    static CALLBACK_NAME = 'uiCanExit';
+    constructor($element, $transitions) {
+        Object.assign(this, {$element, $transitions});
+    }
+    $onInit() {
+        const data = this.$element.data();
+        const {CALLBACK_NAME} = this.constructor;
+        const controllerWithCallback = Object.keys(data)
+            .map((key) => data[key])
+            .find((controller) => controller[CALLBACK_NAME]);
+        if (!controllerWithCallback) return;
+        const off = this.$transitions.onBefore({from: this.fromState}, (...args) => {
+            return controllerWithCallback[CALLBACK_NAME](...args);
+        });
+    }
+    $onDestroy() {
+        if (this.off) this.off();
+        this.$element = null;
+    }
+}
+
+export default function fakeUiCanExit() {
+    return {
+        bindToController: {
+            fromState: '@fakeUiCanExit'
+        },
+        controller: FakeUiCanExitController
+    };
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/formUICanExitGuard.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-configure/components/formUICanExitGuard.js b/modules/web-console/frontend/app/components/page-configure/components/formUICanExitGuard.js
new file mode 100644
index 0000000..0225c5f
--- /dev/null
+++ b/modules/web-console/frontend/app/components/page-configure/components/formUICanExitGuard.js
@@ -0,0 +1,59 @@
+/*
+ * 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 {default as ConfigChangesGuard} from '../services/ConfigChangesGuard';
+
+class FormUICanExitGuardController {
+    static $inject = ['$element', ConfigChangesGuard.name];
+    /**
+     * @param {JQLite} $element
+     * @param {ConfigChangesGuard} ConfigChangesGuard
+     */
+    constructor($element, ConfigChangesGuard) {
+        this.$element = $element;
+        this.ConfigChangesGuard = ConfigChangesGuard;
+    }
+    $onDestroy() {
+        this.$element = null;
+    }
+    $onInit() {
+        const data = this.$element.data();
+        const controller = Object.keys(data)
+            .map((key) => data[key])
+            .find(this._itQuacks);
+
+        if (!controller) return;
+
+        controller.uiCanExit = ($transition$) => {
+            if ($transition$.options().custom.justIDUpdate) return true;
+            $transition$.onSuccess({}, controller.reset);
+            return this.ConfigChangesGuard.guard(...controller.getValuesToCompare());
+        };
+    }
+    _itQuacks(controller) {
+        return controller.reset instanceof Function &&
+            controller.getValuesToCompare instanceof Function &&
+            !controller.uiCanExit;
+    }
+}
+
+export default function formUiCanExitGuard() {
+    return {
+        priority: 10,
+        controller: FormUICanExitGuardController
+    };
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/component.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/component.js b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/component.js
new file mode 100644
index 0000000..7f852b0
--- /dev/null
+++ b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/component.js
@@ -0,0 +1,1151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import templateUrl from './template.tpl.pug';
+import './style.scss';
+import _ from 'lodash';
+import naturalCompare from 'natural-compare-lite';
+import find from 'lodash/fp/find';
+import get from 'lodash/fp/get';
+import {Observable} from 'rxjs/Observable';
+import ObjectID from 'bson-objectid';
+import {uniqueName} from 'app/utils/uniqueName';
+import {defaultNames} from '../../defaultNames';
+
+// eslint-disable-next-line
+import {UIRouter} from '@uirouter/angularjs'
+import {default as IgniteConfirmBatch} from 'app/services/ConfirmBatch.service';
+import {default as ConfigSelectors} from 'app/components/page-configure/store/selectors';
+import {default as ConfigEffects} from 'app/components/page-configure/store/effects';
+import {default as ConfigureState} from 'app/components/page-configure/services/ConfigureState';
+// eslint-disable-next-line
+import {default as AgentManager} from 'app/modules/agent/AgentModal.service'
+import {default as SqlTypes} from 'app/services/SqlTypes.service';
+import {default as JavaTypes} from 'app/services/JavaTypes.service';
+// eslint-disable-next-line
+import {default as ActivitiesData} from 'app/core/activities/Activities.data';
+
+function _mapCaches(caches = []) {
+    return caches.map((cache) => {
+        return {label: cache.name, value: cache._id, cache};
+    });
+}
+
+const INFO_CONNECT_TO_DB = 'Configure connection to database';
+const INFO_SELECT_SCHEMAS = 'Select schemas to load tables from';
+const INFO_SELECT_TABLES = 'Select tables to import as domain model';
+const INFO_SELECT_OPTIONS = 'Select import domain model options';
+const LOADING_JDBC_DRIVERS = {text: 'Loading JDBC drivers...'};
+const LOADING_SCHEMAS = {text: 'Loading schemas...'};
+const LOADING_TABLES = {text: 'Loading tables...'};
+const SAVING_DOMAINS = {text: 'Saving domain model...'};
+
+const IMPORT_DM_NEW_CACHE = 1;
+const IMPORT_DM_ASSOCIATE_CACHE = 2;
+
+const DFLT_PARTITIONED_CACHE = {
+    label: 'PARTITIONED',
+    value: -1,
+    cache: {
+        name: 'PARTITIONED',
+        cacheMode: 'PARTITIONED',
+        atomicityMode: 'ATOMIC',
+        readThrough: true,
+        writeThrough: true
+    }
+};
+
+const DFLT_REPLICATED_CACHE = {
+    label: 'REPLICATED',
+    value: -2,
+    cache: {
+        name: 'REPLICATED',
+        cacheMode: 'REPLICATED',
+        atomicityMode: 'ATOMIC',
+        readThrough: true,
+        writeThrough: true
+    }
+};
+
+const CACHE_TEMPLATES = [DFLT_PARTITIONED_CACHE, DFLT_REPLICATED_CACHE];
+
+export class ModalImportModels {
+    /** 
+     * Cluster ID to import models into
+     * @type {string}
+     */
+    clusterID;
+
+    /** @type {ng.ICompiledExpression} */
+    onHide;
+
+    static $inject = ['$uiRouter', ConfigSelectors.name, ConfigEffects.name, ConfigureState.name, '$http', 'IgniteConfirm', IgniteConfirmBatch.name, 'IgniteFocus', SqlTypes.name, JavaTypes.name, 'IgniteMessages', '$scope', '$rootScope', 'AgentManager', 'IgniteActivitiesData', 'IgniteLoading', 'IgniteFormUtils', 'IgniteLegacyUtils'];
+    /**
+     * @param {UIRouter} $uiRouter
+     * @param {ConfigSelectors} ConfigSelectors
+     * @param {ConfigEffects} ConfigEffects
+     * @param {ConfigureState} ConfigureState
+     * @param {ng.IHttpService} $http
+     * @param {IgniteConfirmBatch} ConfirmBatch
+     * @param {SqlTypes} SqlTypes
+     * @param {JavaTypes} JavaTypes
+     * @param {ng.IScope} $scope
+     * @param {ng.IRootScopeService} $root
+     * @param {AgentManager} agentMgr
+     * @param {ActivitiesData} ActivitiesData
+     */
+    constructor($uiRouter, ConfigSelectors, ConfigEffects, ConfigureState, $http, Confirm, ConfirmBatch, Focus, SqlTypes, JavaTypes, Messages, $scope, $root, agentMgr, ActivitiesData, Loading, FormUtils, LegacyUtils) {
+        this.$uiRouter = $uiRouter;
+        this.ConfirmBatch = ConfirmBatch;
+        this.$http = $http;
+        this.ConfigSelectors = ConfigSelectors;
+        this.ConfigEffects = ConfigEffects;
+        this.ConfigureState = ConfigureState;
+        this.$root = $root;
+        this.$scope = $scope;
+        this.agentMgr = agentMgr;
+        this.JavaTypes = JavaTypes;
+        this.SqlTypes = SqlTypes;
+        this.ActivitiesData = ActivitiesData;
+        Object.assign(this, {Confirm, Focus, Messages, Loading, FormUtils, LegacyUtils});
+    }
+    loadData() {
+        return Observable.of(this.clusterID)
+        .switchMap((id = 'new') => {
+            return this.ConfigureState.state$.let(this.ConfigSelectors.selectClusterToEdit(id, defaultNames.importedCluster));
+        })
+        .switchMap((cluster) => {
+            return (!(cluster.caches || []).length && !(cluster.models || []).length)
+                ? Observable.of({
+                    cluster,
+                    caches: [],
+                    models: []
+                })
+                : Observable.fromPromise(Promise.all([
+                    this.ConfigEffects.etp('LOAD_SHORT_CACHES', {ids: cluster.caches || [], clusterID: cluster._id}),
+                    this.ConfigEffects.etp('LOAD_SHORT_MODELS', {ids: cluster.models || [], clusterID: cluster._id})
+                ]))
+                .switchMap(() => {
+                    return Observable.combineLatest(
+                        this.ConfigureState.state$.let(this.ConfigSelectors.selectShortCachesValue()),
+                        this.ConfigureState.state$.let(this.ConfigSelectors.selectShortModelsValue()),
+                        (caches, models) => ({
+                            cluster,
+                            caches,
+                            models
+                        })
+                    ).take(1);
+                });
+        })
+        .take(1);
+    }
+    saveBatch(batch) {
+        if (!batch.length) return;
+        this.Loading.start('importDomainFromDb');
+        this.ConfigureState.dispatchAction({
+            type: 'ADVANCED_SAVE_COMPLETE_CONFIGURATION',
+            changedItems: this.batchActionsToRequestBody(batch),
+            prevActions: []
+        });
+        this.saveSubscription = Observable.race(
+            this.ConfigureState.actions$.filter((a) => a.type === 'ADVANCED_SAVE_COMPLETE_CONFIGURATION_OK')
+                .do(() => this.onHide()),
+            this.ConfigureState.actions$.filter((a) => a.type === 'ADVANCED_SAVE_COMPLETE_CONFIGURATION_ERR')
+        )
+        .take(1)
+        .do(() => {
+            this.Loading.finish('importDomainFromDb');
+        })
+        .subscribe();
+    }
+    batchActionsToRequestBody(batch) {
+        const result = batch.reduce((req, action) => {
+            return {
+                ...req,
+                cluster: {
+                    ...req.cluster,
+                    models: [...req.cluster.models, action.newDomainModel._id],
+                    caches: [...req.cluster.caches, ...action.newDomainModel.caches]
+                },
+                models: [...req.models, action.newDomainModel],
+                caches: action.newCache
+                    ? [...req.caches, action.newCache]
+                    : action.cacheStoreChanges
+                        ? [...req.caches, {
+                            ...this.loadedCaches[action.cacheStoreChanges[0].cacheId],
+                            ...action.cacheStoreChanges[0].change
+                        }]
+                        : req.caches
+            };
+        }, {cluster: this.cluster, models: [], caches: [], igfss: []});
+        result.cluster.models = [...new Set(result.cluster.models)];
+        result.cluster.caches = [...new Set(result.cluster.caches)];
+        return result;
+    }
+    onTableSelectionChange(selected) {
+        this.$scope.$applyAsync(() => {
+            this.$scope.importDomain.tablesToUse = selected;
+            this.selectedTablesIDs = selected.map((t) => t.id);
+        });
+    }
+    onSchemaSelectionChange(selected) {
+        this.$scope.$applyAsync(() => {
+            this.$scope.importDomain.schemasToUse = selected;
+            this.selectedSchemasIDs = selected.map((i) => i.name);
+        });
+    }
+    onVisibleRowsChange(rows) {
+        return this.visibleTables = rows.map((r) => r.entity);
+    }
+    onCacheSelect(cacheID) {
+        if (cacheID < 0) return;
+        if (this.loadedCaches[cacheID]) return;
+        return this.onCacheSelectSubcription = Observable.merge(
+            Observable.timer(0, 1).take(1)
+                .do(() => this.ConfigureState.dispatchAction({type: 'LOAD_CACHE', cacheID})),
+            Observable.race(
+                this.ConfigureState.actions$
+                    .filter((a) => a.type === 'LOAD_CACHE_OK' && a.cache._id === cacheID).pluck('cache')
+                    .do((cache) => {
+                        this.loadedCaches[cacheID] = cache;
+                    }),
+                this.ConfigureState.actions$
+                    .filter((a) => a.type === 'LOAD_CACHE_ERR' && a.action.cacheID === cacheID)
+            ).take(1)
+        )
+        .subscribe();
+    }
+    $onDestroy() {
+        this.subscription.unsubscribe();
+        if (this.onCacheSelectSubcription) this.onCacheSelectSubcription.unsubscribe();
+        if (this.saveSubscription) this.saveSubscription.unsubscribe();
+    }
+    $onInit() {
+        // Restores old behavior
+        const {$http, Confirm, ConfirmBatch, Focus, SqlTypes, JavaTypes, Messages, $scope, $root, agentMgr, ActivitiesData, Loading, FormUtils, LegacyUtils} = this;
+
+        /**
+         * Convert some name to valid java package name.
+         *
+         * @param name to convert.
+         * @returns {string} Valid java package name.
+         */
+        const _toJavaPackage = (name) => {
+            return name ? name.replace(/[^A-Za-z_0-9/.]+/g, '_') : 'org';
+        };
+
+        const importDomainModal = {
+            hide: () => {
+                agentMgr.stopWatch();
+                this.onHide();
+            }
+        };
+
+        const _makeDefaultPackageName = (user) => user
+            ? _toJavaPackage(`${user.email.replace('@', '.').split('.').reverse().join('.')}.model`)
+            : void 0;
+
+        this.$scope.ui = {
+            generatePojo: true,
+            builtinKeys: true,
+            generateKeyFields: true,
+            usePrimitives: true,
+            generateTypeAliases: true,
+            generateFieldAliases: true,
+            packageNameUserInput: _makeDefaultPackageName($root.user)
+        };
+        this.$scope.$hide = importDomainModal.hide;
+
+        this.$scope.importCommon = {};
+
+        this.subscription = this.loadData().do((data) => {
+            this.$scope.caches = _mapCaches(data.caches);
+            this.$scope.domains = data.models;
+            this.caches = data.caches;
+            this.cluster = data.cluster;
+
+            if (!_.isEmpty(this.$scope.caches)) {
+                this.$scope.importActions.push({
+                    label: 'Associate with existing cache',
+                    shortLabel: 'Associate',
+                    value: IMPORT_DM_ASSOCIATE_CACHE
+                });
+            }
+            this.$scope.$watch('importCommon.action', this._fillCommonCachesOrTemplates(this.$scope.importCommon), true);
+            this.$scope.importCommon.action = IMPORT_DM_NEW_CACHE;
+        }).subscribe();
+
+        // New
+
+        this.loadedCaches = {
+            ...CACHE_TEMPLATES.reduce((a, c) => ({...a, [c.value]: c.cache}), {})
+        };
+        this.actions = [
+            {value: 'connect', label: this.$root.IgniteDemoMode ? 'Description' : 'Connection'},
+            {value: 'schemas', label: 'Schemas'},
+            {value: 'tables', label: 'Tables'},
+            {value: 'options', label: 'Options'}
+        ];
+
+        // Legacy
+
+        $scope.ui.invalidKeyFieldsTooltip = 'Found key types without configured key fields<br/>' +
+            'It may be a result of import tables from database without primary keys<br/>' +
+            'Key field for such key types should be configured manually';
+
+        $scope.indexType = LegacyUtils.mkOptions(['SORTED', 'FULLTEXT', 'GEOSPATIAL']);
+
+        $scope.importActions = [{
+            label: 'Create new cache by template',
+            shortLabel: 'Create',
+            value: IMPORT_DM_NEW_CACHE
+        }];
+
+
+        const _dbPresets = [
+            {
+                db: 'Oracle',
+                jdbcDriverClass: 'oracle.jdbc.OracleDriver',
+                jdbcUrl: 'jdbc:oracle:thin:@[host]:[port]:[database]',
+                user: 'system'
+            },
+            {
+                db: 'DB2',
+                jdbcDriverClass: 'com.ibm.db2.jcc.DB2Driver',
+                jdbcUrl: 'jdbc:db2://[host]:[port]/[database]',
+                user: 'db2admin'
+            },
+            {
+                db: 'SQLServer',
+                jdbcDriverClass: 'com.microsoft.sqlserver.jdbc.SQLServerDriver',
+                jdbcUrl: 'jdbc:sqlserver://[host]:[port][;databaseName=database]'
+            },
+            {
+                db: 'PostgreSQL',
+                jdbcDriverClass: 'org.postgresql.Driver',
+                jdbcUrl: 'jdbc:postgresql://[host]:[port]/[database]',
+                user: 'sa'
+            },
+            {
+                db: 'MySQL',
+                jdbcDriverClass: 'com.mysql.jdbc.Driver',
+                jdbcUrl: 'jdbc:mysql://[host]:[port]/[database]',
+                user: 'root'
+            },
+            {
+                db: 'MySQL',
+                jdbcDriverClass: 'org.mariadb.jdbc.Driver',
+                jdbcUrl: 'jdbc:mariadb://[host]:[port]/[database]',
+                user: 'root'
+            },
+            {
+                db: 'H2',
+                jdbcDriverClass: 'org.h2.Driver',
+                jdbcUrl: 'jdbc:h2:tcp://[host]/[database]',
+                user: 'sa'
+            }
+        ];
+
+        $scope.selectedPreset = {
+            db: 'Generic',
+            jdbcDriverJar: '',
+            jdbcDriverClass: '',
+            jdbcUrl: 'jdbc:[database]',
+            user: 'sa',
+            password: '',
+            tablesOnly: true
+        };
+
+        $scope.demoConnection = {
+            db: 'H2',
+            jdbcDriverClass: 'org.h2.Driver',
+            jdbcUrl: 'jdbc:h2:mem:demo-db',
+            user: 'sa',
+            password: '',
+            tablesOnly: true
+        };
+
+        function _loadPresets() {
+            try {
+                const restoredPresets = JSON.parse(localStorage.dbPresets);
+
+                _.forEach(restoredPresets, (restoredPreset) => {
+                    const preset = _.find(_dbPresets, {jdbcDriverClass: restoredPreset.jdbcDriverClass});
+
+                    if (preset) {
+                        preset.jdbcUrl = restoredPreset.jdbcUrl;
+                        preset.user = restoredPreset.user;
+                    }
+                });
+            }
+            catch (ignore) {
+                // No-op.
+            }
+        }
+
+        _loadPresets();
+
+        function _savePreset(preset) {
+            try {
+                const oldPreset = _.find(_dbPresets, {jdbcDriverClass: preset.jdbcDriverClass});
+
+                if (oldPreset)
+                    _.assign(oldPreset, preset);
+                else
+                    _dbPresets.push(preset);
+
+                localStorage.dbPresets = JSON.stringify(_dbPresets);
+            }
+            catch (err) {
+                Messages.showError(err);
+            }
+        }
+
+        function _findPreset(selectedJdbcJar) {
+            let result = _.find(_dbPresets, function(preset) {
+                return preset.jdbcDriverClass === selectedJdbcJar.jdbcDriverClass;
+            });
+
+            if (!result)
+                result = {db: 'Generic', jdbcUrl: 'jdbc:[database]', user: 'admin'};
+
+            result.jdbcDriverJar = selectedJdbcJar.jdbcDriverJar;
+            result.jdbcDriverClass = selectedJdbcJar.jdbcDriverClass;
+
+            return result;
+        }
+
+        function isValidJavaIdentifier(s) {
+            return JavaTypes.validIdentifier(s) && !JavaTypes.isKeyword(s) && JavaTypes.nonBuiltInClass(s) &&
+                SqlTypes.validIdentifier(s) && !SqlTypes.isKeyword(s);
+        }
+
+        function toJavaIdentifier(name) {
+            if (_.isEmpty(name))
+                return 'DB';
+
+            const len = name.length;
+
+            let ident = '';
+
+            let capitalizeNext = true;
+
+            for (let i = 0; i < len; i++) {
+                const ch = name.charAt(i);
+
+                if (ch === ' ' || ch === '_')
+                    capitalizeNext = true;
+                else if (ch === '-') {
+                    ident += '_';
+                    capitalizeNext = true;
+                }
+                else if (capitalizeNext) {
+                    ident += ch.toLocaleUpperCase();
+
+                    capitalizeNext = false;
+                }
+                else
+                    ident += ch.toLocaleLowerCase();
+            }
+
+            return ident;
+        }
+
+        function toJavaClassName(name) {
+            const clazzName = toJavaIdentifier(name);
+
+            if (isValidJavaIdentifier(clazzName))
+                return clazzName;
+
+            return 'Class' + clazzName;
+        }
+
+        function toJavaFieldName(dbName) {
+            const javaName = toJavaIdentifier(dbName);
+
+            const fieldName = javaName.charAt(0).toLocaleLowerCase() + javaName.slice(1);
+
+            if (isValidJavaIdentifier(fieldName))
+                return fieldName;
+
+            return 'field' + javaName;
+        }
+
+        /**
+         * Load list of database schemas.
+         */
+        const _loadSchemas = () => {
+            agentMgr.awaitAgent()
+                .then(function() {
+                    $scope.importDomain.loadingOptions = LOADING_SCHEMAS;
+                    Loading.start('importDomainFromDb');
+
+                    if ($root.IgniteDemoMode)
+                        return agentMgr.schemas($scope.demoConnection);
+
+                    const preset = $scope.selectedPreset;
+
+                    _savePreset(preset);
+
+                    return agentMgr.schemas(preset);
+                })
+                .then((schemaInfo) => {
+                    $scope.importDomain.action = 'schemas';
+                    $scope.importDomain.info = INFO_SELECT_SCHEMAS;
+                    $scope.importDomain.catalog = toJavaIdentifier(schemaInfo.catalog);
+                    $scope.importDomain.schemas = _.map(schemaInfo.schemas, (schema) => ({name: schema}));
+                    $scope.importDomain.schemasToUse = $scope.importDomain.schemas;
+                    this.selectedSchemasIDs = $scope.importDomain.schemas.map((s) => s.name);
+
+                    if ($scope.importDomain.schemas.length === 0)
+                        $scope.importDomainNext();
+                })
+                .catch(Messages.showError)
+                .then(() => Loading.finish('importDomainFromDb'));
+        };
+
+
+        this._importCachesOrTemplates = [];
+
+        $scope.tableActionView = (tbl) => {
+            const cacheName = get('label')(find({value: tbl.cacheOrTemplate}));
+
+            if (tbl.action === IMPORT_DM_NEW_CACHE)
+                return 'Create ' + tbl.generatedCacheName + ' (' + cacheName + ')';
+
+            return 'Associate with ' + cacheName;
+        };
+
+        /**
+         * Load list of database tables.
+         */
+        const _loadTables = () => {
+            agentMgr.awaitAgent()
+                .then(() => {
+                    $scope.importDomain.loadingOptions = LOADING_TABLES;
+                    Loading.start('importDomainFromDb');
+
+                    $scope.importDomain.allTablesSelected = false;
+                    this.selectedTables = [];
+
+                    const preset = $scope.importDomain.demo ? $scope.demoConnection : $scope.selectedPreset;
+
+                    preset.schemas = $scope.importDomain.schemasToUse.map((s) => s.name);
+
+                    return agentMgr.tables(preset);
+                })
+                .then((tables) => {
+                    this._importCachesOrTemplates = CACHE_TEMPLATES.concat($scope.caches);
+
+                    this._fillCommonCachesOrTemplates($scope.importCommon)($scope.importCommon.action);
+
+                    _.forEach(tables, (tbl, idx) => {
+                        tbl.id = idx;
+                        tbl.action = IMPORT_DM_NEW_CACHE;
+                        // tbl.generatedCacheName = toJavaClassName(tbl.table) + 'Cache';
+                        tbl.generatedCacheName = uniqueName(toJavaClassName(tbl.table) + 'Cache', this.caches);
+                        tbl.cacheOrTemplate = DFLT_PARTITIONED_CACHE.value;
+                        tbl.label = tbl.schema + '.' + tbl.table;
+                        tbl.edit = false;
+                    });
+
+                    $scope.importDomain.action = 'tables';
+                    $scope.importDomain.tables = tables;
+                    const tablesToUse = tables.filter((t) => LegacyUtils.isDefined(_.find(t.columns, (col) => col.key)));
+                    this.selectedTablesIDs = tablesToUse.map((t) => t.id);
+                    this.$scope.importDomain.tablesToUse = tablesToUse;
+
+                    $scope.importDomain.info = INFO_SELECT_TABLES;
+                })
+                .catch(Messages.showError)
+                .then(() => Loading.finish('importDomainFromDb'));
+        };
+
+        $scope.applyDefaults = () => {
+            _.forEach(this.visibleTables, (table) => {
+                table.edit = false;
+                table.action = $scope.importCommon.action;
+                table.cacheOrTemplate = $scope.importCommon.cacheOrTemplate;
+            });
+        };
+
+        $scope._curDbTable = null;
+
+        $scope.startEditDbTableCache = (tbl) => {
+            if ($scope._curDbTable) {
+                $scope._curDbTable.edit = false;
+
+                if ($scope._curDbTable.actionWatch) {
+                    $scope._curDbTable.actionWatch();
+
+                    $scope._curDbTable.actionWatch = null;
+                }
+            }
+
+            $scope._curDbTable = tbl;
+
+            const _fillFn = this._fillCommonCachesOrTemplates($scope._curDbTable);
+
+            _fillFn($scope._curDbTable.action);
+
+            $scope._curDbTable.actionWatch = $scope.$watch('_curDbTable.action', _fillFn, true);
+
+            $scope._curDbTable.edit = true;
+        };
+
+        /**
+         * Show page with import domain models options.
+         */
+        function _selectOptions() {
+            $scope.importDomain.action = 'options';
+            $scope.importDomain.button = 'Save';
+            $scope.importDomain.info = INFO_SELECT_OPTIONS;
+
+            Focus.move('domainPackageName');
+        }
+
+        const _saveDomainModel = (optionsForm) => {
+            if (optionsForm.$invalid)
+                return this.FormUtils.triggerValidation(optionsForm, this.$scope);
+
+            const generatePojo = $scope.ui.generatePojo;
+            const packageName = $scope.ui.packageName;
+
+            const batch = [];
+            const checkedCaches = [];
+
+            let containKey = true;
+            let containDup = false;
+
+            function dbField(name, jdbcType, nullable, unsigned) {
+                const javaTypes = (unsigned && jdbcType.unsigned) ? jdbcType.unsigned : jdbcType.signed;
+                const javaFieldType = (!nullable && javaTypes.primitiveType && $scope.ui.usePrimitives) ? javaTypes.primitiveType : javaTypes.javaType;
+
+                return {
+                    databaseFieldName: name,
+                    databaseFieldType: jdbcType.dbName,
+                    javaType: javaTypes.javaType,
+                    javaFieldName: toJavaFieldName(name),
+                    javaFieldType
+                };
+            }
+
+            _.forEach($scope.importDomain.tablesToUse, (table, curIx, tablesToUse) => {
+                const qryFields = [];
+                const indexes = [];
+                const keyFields = [];
+                const valFields = [];
+                const aliases = [];
+
+                const tableName = table.table;
+                let typeName = toJavaClassName(tableName);
+
+                if (_.find($scope.importDomain.tablesToUse,
+                        (tbl, ix) => ix !== curIx && tableName === tbl.table)) {
+                    typeName = typeName + '_' + toJavaClassName(table.schema);
+
+                    containDup = true;
+                }
+
+                let valType = tableName;
+                let typeAlias;
+
+                if (generatePojo) {
+                    if ($scope.ui.generateTypeAliases && tableName.toLowerCase() !== typeName.toLowerCase())
+                        typeAlias = tableName;
+
+                    valType = _toJavaPackage(packageName) + '.' + typeName;
+                }
+
+                let _containKey = false;
+
+                _.forEach(table.columns, function(col) {
+                    const fld = dbField(col.name, SqlTypes.findJdbcType(col.type), col.nullable, col.unsigned);
+
+                    qryFields.push({name: fld.javaFieldName, className: fld.javaType});
+
+                    const dbName = fld.databaseFieldName;
+
+                    if (generatePojo && $scope.ui.generateFieldAliases &&
+                        SqlTypes.validIdentifier(dbName) && !SqlTypes.isKeyword(dbName) &&
+                        !_.find(aliases, {field: fld.javaFieldName}) &&
+                        fld.javaFieldName.toUpperCase() !== dbName.toUpperCase())
+                        aliases.push({field: fld.javaFieldName, alias: dbName});
+
+                    if (col.key) {
+                        keyFields.push(fld);
+
+                        _containKey = true;
+                    }
+                    else
+                        valFields.push(fld);
+                });
+
+                containKey &= _containKey;
+                if (table.indexes) {
+                    _.forEach(table.indexes, (idx) => {
+                        const idxFields = _.map(idx.fields, (idxFld) => ({
+                            name: toJavaFieldName(idxFld.name),
+                            direction: idxFld.sortOrder
+                        }));
+
+                        indexes.push({
+                            name: idx.name,
+                            indexType: 'SORTED',
+                            fields: idxFields
+                        });
+                    });
+                }
+
+                const domainFound = _.find($scope.domains, (domain) => domain.valueType === valType);
+
+                const batchAction = {
+                    confirm: false,
+                    skip: false,
+                    table,
+                    newDomainModel: {
+                        _id: ObjectID.generate(),
+                        caches: [],
+                        generatePojo
+                    }
+                };
+
+                if (LegacyUtils.isDefined(domainFound)) {
+                    batchAction.newDomainModel._id = domainFound._id;
+                    // Don't touch original caches value
+                    delete batchAction.newDomainModel.caches;
+                    batchAction.confirm = true;
+                }
+
+                Object.assign(batchAction.newDomainModel, {
+                    tableName: typeAlias,
+                    keyType: valType + 'Key',
+                    valueType: valType,
+                    queryMetadata: 'Configuration',
+                    databaseSchema: table.schema,
+                    databaseTable: tableName,
+                    fields: qryFields,
+                    queryKeyFields: _.map(keyFields, (field) => field.javaFieldName),
+                    indexes,
+                    keyFields,
+                    aliases,
+                    valueFields: _.isEmpty(valFields) ? keyFields.slice() : valFields
+                });
+
+                // Use Java built-in type for key.
+                if ($scope.ui.builtinKeys && batchAction.newDomainModel.keyFields.length === 1) {
+                    const newDomain = batchAction.newDomainModel;
+                    const keyField = newDomain.keyFields[0];
+
+                    newDomain.keyType = keyField.javaType;
+                    newDomain.keyFieldName = keyField.javaFieldName;
+
+                    if (!$scope.ui.generateKeyFields) {
+                        // Exclude key column from query fields.
+                        newDomain.fields = _.filter(newDomain.fields, (field) => field.name !== keyField.javaFieldName);
+
+                        newDomain.queryKeyFields = [];
+                    }
+
+                    // Exclude key column from indexes.
+                    _.forEach(newDomain.indexes, (index) => {
+                        index.fields = _.filter(index.fields, (field) => field.name !== keyField.javaFieldName);
+                    });
+
+                    newDomain.indexes = _.filter(newDomain.indexes, (index) => !_.isEmpty(index.fields));
+                }
+
+                // Prepare caches for generation.
+                if (table.action === IMPORT_DM_NEW_CACHE) {
+                    const newCache = angular.copy(this.loadedCaches[table.cacheOrTemplate]);
+
+                    batchAction.newCache = newCache;
+
+                    // const siblingCaches = batch.filter((a) => a.newCache).map((a) => a.newCache);
+                    const siblingCaches = [];
+                    newCache._id = ObjectID.generate();
+                    newCache.name = uniqueName(typeName + 'Cache', this.caches.concat(siblingCaches));
+                    newCache.domains = [batchAction.newDomainModel._id];
+                    batchAction.newDomainModel.caches = [newCache._id];
+
+                    // POJO store factory is not defined in template.
+                    if (!newCache.cacheStoreFactory || newCache.cacheStoreFactory.kind !== 'CacheJdbcPojoStoreFactory') {
+                        const dialect = $scope.importDomain.demo ? 'H2' : $scope.selectedPreset.db;
+
+                        const catalog = $scope.importDomain.catalog;
+
+                        newCache.cacheStoreFactory = {
+                            kind: 'CacheJdbcPojoStoreFactory',
+                            CacheJdbcPojoStoreFactory: {
+                                dataSourceBean: 'ds' + dialect + '_' + catalog,
+                                dialect
+                            },
+                            CacheJdbcBlobStoreFactory: { connectVia: 'DataSource' }
+                        };
+                    }
+
+                    if (!newCache.readThrough && !newCache.writeThrough) {
+                        newCache.readThrough = true;
+                        newCache.writeThrough = true;
+                    }
+                }
+                else {
+                    const newDomain = batchAction.newDomainModel;
+                    const cacheId = table.cacheOrTemplate;
+
+                    batchAction.newDomainModel.caches = [cacheId];
+
+                    if (!_.includes(checkedCaches, cacheId)) {
+                        const cache = _.find($scope.caches, {value: cacheId}).cache;
+
+                        // TODO: move elsewhere, make sure it still works
+                        const change = LegacyUtils.autoCacheStoreConfiguration(cache, [newDomain]);
+
+                        if (change)
+                            batchAction.cacheStoreChanges = [{cacheId, change}];
+
+                        checkedCaches.push(cacheId);
+                    }
+                }
+
+                batch.push(batchAction);
+            });
+
+            /**
+             * Generate message to show on confirm dialog.
+             *
+             * @param meta Object to confirm.
+             * @returns {string} Generated message.
+             */
+            function overwriteMessage(meta) {
+                return `
+                    Domain model with name &quot;${meta.newDomainModel.databaseTable}&quot; already exists.
+                    Are you sure you want to overwrite it?
+                `;
+            }
+
+            const itemsToConfirm = _.filter(batch, (item) => item.confirm);
+
+            const checkOverwrite = () => {
+                if (itemsToConfirm.length > 0) {
+                    return ConfirmBatch.confirm(overwriteMessage, itemsToConfirm)
+                        .then(() => this.saveBatch(_.filter(batch, (item) => !item.skip)))
+                        .catch(() => Messages.showError('Importing of domain models interrupted by user.'));
+                }
+                return this.saveBatch(batch);
+            };
+
+            const checkDuplicate = () => {
+                if (containDup) {
+                    Confirm.confirm('Some tables have the same name.<br/>' +
+                        'Name of types for that tables will contain schema name too.')
+                        .then(() => checkOverwrite());
+                }
+                else
+                    checkOverwrite();
+            };
+
+            if (containKey)
+                checkDuplicate();
+            else {
+                Confirm.confirm('Some tables have no primary key.<br/>' +
+                    'You will need to configure key type and key fields for such tables after import complete.')
+                    .then(() => checkDuplicate());
+            }
+        };
+
+
+        $scope.importDomainNext = (form) => {
+            if (!$scope.importDomainNextAvailable())
+                return;
+
+            const act = $scope.importDomain.action;
+
+            if (act === 'drivers' && $scope.importDomain.jdbcDriversNotFound)
+                importDomainModal.hide();
+            else if (act === 'connect')
+                _loadSchemas();
+            else if (act === 'schemas')
+                _loadTables();
+            else if (act === 'tables')
+                _selectOptions();
+            else if (act === 'options')
+                _saveDomainModel(form);
+        };
+
+        $scope.nextTooltipText = function() {
+            const importDomainNextAvailable = $scope.importDomainNextAvailable();
+
+            const act = $scope.importDomain.action;
+
+            if (act === 'drivers' && $scope.importDomain.jdbcDriversNotFound)
+                return 'Resolve issue with JDBC drivers<br>Close this dialog and try again';
+
+            if (act === 'connect' && _.isNil($scope.selectedPreset.jdbcDriverClass))
+                return 'Input valid JDBC driver class name';
+
+            if (act === 'connect' && _.isNil($scope.selectedPreset.jdbcUrl))
+                return 'Input valid JDBC URL';
+
+            if (act === 'connect' || act === 'drivers')
+                return 'Click to load list of schemas from database';
+
+            if (act === 'schemas')
+                return importDomainNextAvailable ? 'Click to load list of tables from database' : 'Select schemas to continue';
+
+            if (act === 'tables')
+                return importDomainNextAvailable ? 'Click to show import options' : 'Select tables to continue';
+
+            if (act === 'options')
+                return 'Click to import domain model for selected tables';
+
+            return 'Click to continue';
+        };
+
+        $scope.prevTooltipText = function() {
+            const act = $scope.importDomain.action;
+
+            if (act === 'schemas')
+                return $scope.importDomain.demo ? 'Click to return on demo description step' : 'Click to return on connection configuration step';
+
+            if (act === 'tables')
+                return 'Click to return on schemas selection step';
+
+            if (act === 'options')
+                return 'Click to return on tables selection step';
+        };
+
+        $scope.importDomainNextAvailable = function() {
+            switch ($scope.importDomain.action) {
+                case 'connect':
+                    return !_.isNil($scope.selectedPreset.jdbcDriverClass) && !_.isNil($scope.selectedPreset.jdbcUrl);
+
+                case 'schemas':
+                    return _.isEmpty($scope.importDomain.schemas) || !!get('importDomain.schemasToUse.length')($scope);
+
+                case 'tables':
+                    return !!$scope.importDomain.tablesToUse.length;
+
+                default:
+                    return true;
+            }
+        };
+
+        $scope.importDomainPrev = function() {
+            $scope.importDomain.button = 'Next';
+
+            if ($scope.importDomain.action === 'options') {
+                $scope.importDomain.action = 'tables';
+                $scope.importDomain.info = INFO_SELECT_TABLES;
+            }
+            else if ($scope.importDomain.action === 'tables' && $scope.importDomain.schemas.length > 0) {
+                $scope.importDomain.action = 'schemas';
+                $scope.importDomain.info = INFO_SELECT_SCHEMAS;
+            }
+            else {
+                $scope.importDomain.action = 'connect';
+                $scope.importDomain.info = INFO_CONNECT_TO_DB;
+            }
+        };
+
+        const demo = $root.IgniteDemoMode;
+
+        $scope.importDomain = {
+            demo,
+            action: demo ? 'connect' : 'drivers',
+            jdbcDriversNotFound: demo,
+            schemas: [],
+            allSchemasSelected: false,
+            tables: [],
+            allTablesSelected: false,
+            button: 'Next',
+            info: ''
+        };
+
+        $scope.importDomain.loadingOptions = LOADING_JDBC_DRIVERS;
+
+        agentMgr.startAgentWatch('Back', this.$uiRouter.globals.current.name)
+            .then(() => {
+                ActivitiesData.post({
+                    group: 'configuration',
+                    action: 'configuration/import/model'
+                });
+
+                return true;
+            })
+            .then(() => {
+                if (demo) {
+                    $scope.ui.packageNameUserInput = $scope.ui.packageName;
+                    $scope.ui.packageName = 'model';
+
+                    return;
+                }
+
+                // Get available JDBC drivers via agent.
+                Loading.start('importDomainFromDb');
+
+                $scope.jdbcDriverJars = [];
+                $scope.ui.selectedJdbcDriverJar = {};
+
+                return agentMgr.drivers()
+                    .then((drivers) => {
+                        $scope.ui.packageName = $scope.ui.packageNameUserInput;
+
+                        if (drivers && drivers.length > 0) {
+                            drivers = _.sortBy(drivers, 'jdbcDriverJar');
+
+                            _.forEach(drivers, (drv) => {
+                                $scope.jdbcDriverJars.push({
+                                    label: drv.jdbcDriverJar,
+                                    value: {
+                                        jdbcDriverJar: drv.jdbcDriverJar,
+                                        jdbcDriverClass: drv.jdbcDriverCls
+                                    }
+                                });
+                            });
+
+                            $scope.ui.selectedJdbcDriverJar = $scope.jdbcDriverJars[0].value;
+
+                            // FormUtils.confirmUnsavedChanges(dirty, () => {
+                            $scope.importDomain.action = 'connect';
+                            $scope.importDomain.tables = [];
+                            this.selectedTables = [];
+
+                            // Focus.move('jdbcUrl');
+                            // });
+                        }
+                        else {
+                            $scope.importDomain.jdbcDriversNotFound = true;
+                            $scope.importDomain.button = 'Cancel';
+                        }
+                    })
+                    .then(() => {
+                        $scope.importDomain.info = INFO_CONNECT_TO_DB;
+
+                        Loading.finish('importDomainFromDb');
+                    });
+            });
+
+        $scope.$watch('ui.selectedJdbcDriverJar', function(val) {
+            if (val && !$scope.importDomain.demo) {
+                const foundPreset = _findPreset(val);
+
+                const selectedPreset = $scope.selectedPreset;
+
+                selectedPreset.db = foundPreset.db;
+                selectedPreset.jdbcDriverJar = foundPreset.jdbcDriverJar;
+                selectedPreset.jdbcDriverClass = foundPreset.jdbcDriverClass;
+                selectedPreset.jdbcUrl = foundPreset.jdbcUrl;
+                selectedPreset.user = foundPreset.user;
+            }
+        }, true);
+    }
+
+    _fillCommonCachesOrTemplates(item) {
+        return (action) => {
+            if (item.cachesOrTemplates)
+                item.cachesOrTemplates.length = 0;
+            else
+                item.cachesOrTemplates = [];
+
+            if (action === IMPORT_DM_NEW_CACHE)
+                item.cachesOrTemplates.push(...CACHE_TEMPLATES);
+
+            if (!_.isEmpty(this.$scope.caches)) {
+                item.cachesOrTemplates.push(...this.$scope.caches);
+                this.onCacheSelect(item.cachesOrTemplates[0].value);
+            }
+
+            if (
+                !_.find(item.cachesOrTemplates, {value: item.cacheOrTemplate}) &&
+                item.cachesOrTemplates.length
+            )
+                item.cacheOrTemplate = item.cachesOrTemplates[0].value;
+        };
+    }
+
+    schemasColumnDefs = [
+        {
+            name: 'name',
+            displayName: 'Name',
+            field: 'name',
+            enableHiding: false,
+            sort: {direction: 'asc', priority: 0},
+            filter: {
+                placeholder: 'Filter by Name…'
+            },
+            visible: true,
+            sortingAlgorithm: naturalCompare,
+            minWidth: 165
+        }
+    ];
+
+    tablesColumnDefs = [
+        {
+            name: 'schema',
+            displayName: 'Schema',
+            field: 'schema',
+            enableHiding: false,
+            enableFiltering: false,
+            sort: {direction: 'asc', priority: 0},
+            visible: true,
+            sortingAlgorithm: naturalCompare,
+            minWidth: 100
+        },
+        {
+            name: 'table',
+            displayName: 'Table',
+            field: 'table',
+            enableHiding: false,
+            enableFiltering: true,
+            filter: {
+                placeholder: 'Filter by Table…'
+            },
+            visible: true,
+            sortingAlgorithm: naturalCompare,
+            minWidth: 200
+        },
+        {
+            name: 'action',
+            displayName: 'Action',
+            field: 'action',
+            enableHiding: false,
+            enableFiltering: false,
+            cellTemplate: `
+                <tables-action-cell
+                    table='row.entity'
+                    on-edit-start='grid.appScope.$ctrl.$scope.startEditDbTableCache($event)'
+                    on-cache-select='grid.appScope.$ctrl.onCacheSelect($event)'
+                    caches='grid.appScope.$ctrl._importCachesOrTemplates'
+                    import-actions='grid.appScope.$ctrl.$scope.importActions'
+                ></tables-action-cell>
+            `,
+            visible: true,
+            minWidth: 500
+        }
+    ];
+}
+
+export const component = {
+    name: 'modalImportModels',
+    controller: ModalImportModels,
+    templateUrl,
+    bindings: {
+        onHide: '&',
+        clusterID: '<clusterId'
+    }
+};

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/index.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/index.js b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/index.js
new file mode 100644
index 0000000..b75c89a
--- /dev/null
+++ b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/index.js
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import angular from 'angular';
+import {component} from './component';
+import service from './service';
+import {component as stepIndicator} from './step-indicator/component';
+import {component as tablesActionCell} from './tables-action-cell/component';
+import {component as amountIndicator} from './selected-items-amount-indicator/component';
+
+export default angular
+.module('configuration.modal-import-models', [])
+.service(service.name, service)
+.component(tablesActionCell.name, tablesActionCell)
+.component(stepIndicator.name, stepIndicator)
+.component(amountIndicator.name, amountIndicator)
+.component(component.name, component);

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/selected-items-amount-indicator/component.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/selected-items-amount-indicator/component.js b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/selected-items-amount-indicator/component.js
new file mode 100644
index 0000000..26a6ea0
--- /dev/null
+++ b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/selected-items-amount-indicator/component.js
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import template from './template.pug';
+import './style.scss';
+export const component = {
+    name: 'selectedItemsAmountIndicator',
+    template,
+    bindings: {
+        selectedAmount: '<',
+        totalAmount: '<'
+    }
+};

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/selected-items-amount-indicator/style.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/selected-items-amount-indicator/style.scss b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/selected-items-amount-indicator/style.scss
new file mode 100644
index 0000000..c5c2a05
--- /dev/null
+++ b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/selected-items-amount-indicator/style.scss
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+selected-items-amount-indicator {
+    font-family: Roboto;
+    font-size: 14px;
+    font-style: italic;
+    color: #757575;
+    display: inline-block;
+}
\ 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/components/modal-import-models/selected-items-amount-indicator/template.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/selected-items-amount-indicator/template.pug b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/selected-items-amount-indicator/template.pug
new file mode 100644
index 0000000..9a46a77
--- /dev/null
+++ b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/selected-items-amount-indicator/template.pug
@@ -0,0 +1,17 @@
+//-
+    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.
+
+| {{ $ctrl.selectedAmount || 0}} of {{ $ctrl.totalAmount }} selected
\ 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/components/modal-import-models/service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/service.js b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/service.js
new file mode 100644
index 0000000..3c0ecbb
--- /dev/null
+++ b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/service.js
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export default class ModalImportModels {
+    static $inject = ['$modal', 'AgentManager', '$uiRouter'];
+    constructor($modal, AgentManager, $uiRouter) {
+        Object.assign(this, {$modal, AgentManager, $uiRouter});
+    }
+    _goToDynamicState() {
+        if (this._state) this.$uiRouter.stateRegistry.deregister(this._state);
+        this._state = this.$uiRouter.stateRegistry.register({
+            name: 'importModels',
+            parent: this.$uiRouter.stateService.current,
+            onEnter: () => {
+                this._open();
+            },
+            onExit: () => {
+                this._modal && this._modal.hide();
+            }
+        });
+        return this.$uiRouter.stateService.go(this._state, this.$uiRouter.stateService.params);
+    }
+    _open() {
+        this._modal = this.$modal({
+            template: `
+                <modal-import-models
+                    on-hide='$ctrl.$state.go("^")'
+                    cluster-id='$ctrl.$state.params.clusterID'
+                ></modal-import-models>
+            `,
+            controller: ['$state', function($state) {this.$state = $state;}],
+            controllerAs: '$ctrl',
+            show: false
+        });
+        return this.AgentManager.startAgentWatch('Back', this.$uiRouter.globals.current.name)
+        .then(() => this._modal.$promise)
+        .then(() => this._modal.show());
+    }
+    open() {
+        this._goToDynamicState();
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/step-indicator/component.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/step-indicator/component.js b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/step-indicator/component.js
new file mode 100644
index 0000000..4f2b141
--- /dev/null
+++ b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/step-indicator/component.js
@@ -0,0 +1,35 @@
+/*
+ * 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 template from './template.pug';
+import './style.scss';
+
+export class ModalImportModelsStepIndicator {
+    isVisited(index) {
+        return index <= this.steps.findIndex((step) => step.value === this.currentStep);
+    }
+}
+
+export const component = {
+    name: 'modalImportModelsStepIndicator',
+    template,
+    controller: ModalImportModelsStepIndicator,
+    bindings: {
+        steps: '<',
+        currentStep: '<'
+    }
+};

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/step-indicator/style.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/step-indicator/style.scss b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/step-indicator/style.scss
new file mode 100644
index 0000000..e841272
--- /dev/null
+++ b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/step-indicator/style.scss
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+modal-import-models-step-indicator {
+    @import "public/stylesheets/variables.scss";
+
+    $text-color-default: #393939;
+    $text-color-active: $ignite-brand-success;
+    $indicator-color-default: #757575;
+    $indicator-color-active: $ignite-brand-success;
+    $indicator-size: 12px;
+    $indicator-border-radius: 2px;
+    $spline-height: 1px;
+
+    display: block;
+    font-family: Roboto;
+
+    .step-indicator__steps {
+        display: flex;
+        flex-direction: row;
+        justify-content: space-between;
+        margin: 0;
+        padding: 0;
+        list-style: none;
+    }
+
+    .step-indicator__step {
+        color: $text-color-default;
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        position: relative;
+
+        &:before {
+            content: '';
+            display: block;
+            background: $indicator-color-default;
+            width: 100%;
+            height: $spline-height;
+            bottom: $indicator-size / 2;
+            position: absolute;
+        }
+
+        &:after {
+            content: '';
+            display: block;
+            background: $indicator-color-default;
+            width: $indicator-size;
+            height: $indicator-size;
+            border-radius: $indicator-border-radius;
+            margin-top: 5px;
+            z-index: 1;
+        }
+    }
+    .step-indicator__step-first,
+    .step-indicator__step-last {
+        &:before {
+            width: calc(50% - #{$indicator-size} / 2);
+        }
+    }
+    .step-indicator__step-first:before {
+        right: 0;
+    }
+    .step-indicator__step-last:before {
+        left: 0;
+    }
+    .step-indicator__step-active {
+        color: $text-color-active;
+
+        &:after {
+            background: $indicator-color-active;            
+        }
+    }
+    .step-indicator__spline {
+        background: $indicator-color-default;
+        height: $spline-height;
+        width: 100%;
+        margin-top: auto;
+        margin-bottom: $indicator-size / 2;
+    }
+    .step-indicator__step-visited {
+        &:before,
+        &+.step-indicator__spline {
+            background: $indicator-color-active;
+        }
+    }
+}
\ 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/components/modal-import-models/step-indicator/template.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/step-indicator/template.pug b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/step-indicator/template.pug
new file mode 100644
index 0000000..c196604
--- /dev/null
+++ b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/step-indicator/template.pug
@@ -0,0 +1,31 @@
+//-
+    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.
+
+nav
+    .step-indicator__steps
+        .step-indicator__step(
+            ng-repeat-start='step in ::$ctrl.steps'
+            ng-class=`{
+                "step-indicator__step-active": $ctrl.currentStep === step.value,
+                "step-indicator__step-visited": $ctrl.isVisited($index),
+                "step-indicator__step-first": $first,
+                "step-indicator__step-last": $last
+            }`
+        ) {{::step.label}}
+        .step-indicator__spline(
+            ng-repeat-end
+            ng-if='!$last'
+        )
\ 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/components/modal-import-models/style.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/style.scss b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/style.scss
new file mode 100644
index 0000000..5c109b4
--- /dev/null
+++ b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/style.scss
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+modal-import-models {
+    .modal-content {
+        min-height: 493px;
+        display: flex;
+        flex-direction: column;
+
+        .modal-body {
+            flex: 1 1 auto;
+            overflow-y: auto;
+        }
+        .modal-footer {
+            display: flex;
+            align-items: baseline;
+
+            selected-items-amount-indicator {
+                margin-left: auto;
+                margin-right: auto;
+            }
+        }
+    }
+
+    pc-items-table {
+        width: 100%;
+    }
+
+    modal-import-models-step-indicator {
+        margin-top: 15px;
+        margin-bottom: 30px;
+    }
+    .#{&}__prev-button {
+        margin-right: auto !important;
+    }
+    .#{&}__next-button {
+        margin-left: auto !important;
+    }
+}
\ 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/components/modal-import-models/tables-action-cell/component.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/tables-action-cell/component.js b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/tables-action-cell/component.js
new file mode 100644
index 0000000..072a497
--- /dev/null
+++ b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/tables-action-cell/component.js
@@ -0,0 +1,62 @@
+/*
+ * 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 template from './template.pug';
+import './style.scss';
+
+const IMPORT_DM_NEW_CACHE = 1;
+
+export class TablesActionCell {
+    static $inject = ['$element'];
+    constructor($element) {
+        Object.assign(this, {$element});
+    }
+    onClick(e) {
+        e.stopPropagation();
+    }
+    $postLink() {
+        this.$element.on('click', this.onClick);
+    }
+    $onDestroy() {
+        this.$element.off('click', this.onClick);
+        this.$element = null;
+    }
+    tableActionView(table) {
+        if (!this.caches) return;
+        const cache = this.caches.find((c) => c.value === table.cacheOrTemplate);
+        if (!cache) return;
+        const cacheName = cache.label;
+
+        if (table.action === IMPORT_DM_NEW_CACHE)
+            return 'Create ' + table.generatedCacheName + ' (' + cacheName + ')';
+
+        return 'Associate with ' + cacheName;
+    }
+}
+
+export const component = {
+    name: 'tablesActionCell',
+    controller: TablesActionCell,
+    bindings: {
+        onEditStart: '&',
+        onCacheSelect: '&?',
+        table: '<',
+        caches: '<',
+        importActions: '<'
+    },
+    template
+};