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/style.scss
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/tables-action-cell/style.scss
 
b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/tables-action-cell/style.scss
new file mode 100644
index 0000000..49a7b91
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/tables-action-cell/style.scss
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+tables-action-cell {
+    display: flex;
+    align-items: center;
+    height: 100%;
+    padding-left: 10px;
+    padding-right: 10px;
+
+    .table-action-cell__edit-button {
+        background: none;
+        border: 1px solid transparent;
+
+        &:hover {
+            background: white;
+            box-shadow: inset 0 1px 3px 0 rgba(0, 0, 0, 0.5);
+            border: solid 1px #c5c5c5;
+        }
+    }
+    .table-action-cell__edit-form {
+        display: flex;
+        align-items: center;
+        white-space: nowrap;
+        width: 100%;
+    }
+    .table-action-cell__action-select {
+        flex: 3;
+        margin-right: 5px;
+    }
+    .table-action-cell__cache-select {
+        flex: 6;
+        margin-right: 0;
+    }
+}
\ 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/template.pug
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/tables-action-cell/template.pug
 
b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/tables-action-cell/template.pug
new file mode 100644
index 0000000..2f51114
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/tables-action-cell/template.pug
@@ -0,0 +1,45 @@
+//-
+    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.table-action-cell__edit-button.btn-ignite(
+    type='button'
+    b_s-tooltip=''
+    d_ata-title='Click to edit'
+    title='Click to edit'
+    data-placement='top'
+    ng-click='$ctrl.onEditStart({$event: $ctrl.table})'
+    ng-if='!$ctrl.table.edit'
+)
+    | {{ $ctrl.tableActionView($ctrl.table) }}
+.table-action-cell__edit-form(ng-if='$ctrl.table.edit')
+    
.ignite-form-field.ignite-form-field-dropdown.table-action-cell__action-select
+        .ignite-form-field__control
+            .input-tip
+                button.select-toggle.form-control(
+                    type='button'
+                    bs-select
+                    ng-model='$ctrl.table.action'
+                    bs-options='item.value as item.shortLabel for item in 
$ctrl.importActions'
+                )
+    
.ignite-form-field.ignite-form-field-dropdown.table-action-cell__cache-select
+        .ignite-form-field__control
+            .input-tip
+                button.select-toggle.form-control(
+                    bs-select
+                    ng-model='$ctrl.table.cacheOrTemplate'
+                    ng-change='$ctrl.onCacheSelect({$event: 
$ctrl.table.cacheOrTemplate})'
+                    bs-options='item.value as item.label for item in 
$ctrl.table.cachesOrTemplates'
+                )
\ 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/template.tpl.pug
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/template.tpl.pug
 
b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/template.tpl.pug
new file mode 100644
index 0000000..1762ecc
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/components/modal-import-models/template.tpl.pug
@@ -0,0 +1,181 @@
+//-
+    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.
+
+include /app/helpers/jade/mixins
+
+mixin chk(mdl, change, tip)
+    input(type='checkbox' ng-model=mdl ng-change=change bs-tooltip='' 
data-title=tip data-trigger='hover' data-placement='top')
+
+mixin td-ellipses-lbl(w, lbl)
+    td.td-ellipsis(width=`${w}` style=`min-width: ${w}; max-width: ${w}`)
+        label #{lbl}
+
+.modal--ignite.modal.modal-domain-import.center(role='dialog')
+    -var tipOpts = {};
+    - tipOpts.container = '.modal-content'
+    - tipOpts.placement = 'top'
+    .modal-dialog
+        .modal-content(ignite-loading='importDomainFromDb' 
ignite-loading-text='{{importDomain.loadingOptions.text}}')
+            #errors-container.modal-header.header
+                button.close(type='button' ng-click='$hide()' 
aria-hidden='true')
+                    svg(ignite-icon="cross")
+                h4.modal-title() 
+                    span(ng-if='!importDomain.demo') Import domain models from 
database
+                    span(ng-if='importDomain.demo') Import domain models from 
demo database
+            .modal-body.theme--ignite
+                modal-import-models-step-indicator(
+                    steps='$ctrl.actions'
+                    current-step='importDomain.action'
+                )
+                .import-domain-model-wizard-page(ng-if='importDomain.action == 
"drivers" && !importDomain.jdbcDriversNotFound')
+                .import-domain-model-wizard-page(ng-if='importDomain.action == 
"drivers" && importDomain.jdbcDriversNotFound')
+                    | Domain model could not be imported
+                    ul
+                        li Agent failed to find JDBC drivers
+                        li Copy required JDBC drivers into agent 
'jdbc-drivers' folder and try again
+                        li Refer to agent README.txt for more information
+                .import-domain-model-wizard-page(ng-if='importDomain.action == 
"connect" && importDomain.demo')
+                    div(ng-if='demoConnection.db == "H2"')
+                        ul
+                            li In-memory H2 database server will be started 
inside agent.
+                            li Database will be populated with sample tables.
+                            li You could test domain model generation with 
this demo database.
+                            li Click "Next" to continue.
+                    div(ng-if='demoConnection.db != "H2"')
+                        label Demo could not be started
+                            ul
+                                li Agent failed to resolve H2 database jar
+                                li Copy h2-x.x.x.jar into agent 'jdbc-drivers' 
folder and try again
+                                li Refer to agent README.txt for more 
information
+                .import-domain-model-wizard-page(ng-if='importDomain.action == 
"connect" && !importDomain.demo')
+                    -var form = 'connectForm'
+
+                    form.pc-form-grid-row(name=form novalidate)
+                        .pc-form-grid-col-30
+                            +ignite-form-field-dropdown('Driver JAR:', 
'ui.selectedJdbcDriverJar', '"jdbcDriverJar"', false, true, false,
+                                'Choose JDBC driver', '', 'jdbcDriverJars',
+                                'Select appropriate JAR with JDBC driver<br> 
To add another driver you need to place it into "/jdbc-drivers" folder of 
Ignite Web Agent<br> Refer to Ignite Web Agent README.txt for for more 
information'
+                            )
+                        .pc-form-grid-col-30
+                            +java-class('JDBC driver:', 
'selectedPreset.jdbcDriverClass', '"jdbcDriverClass"', true, true, 'Fully 
qualified class name of JDBC driver that will be used to connect to database')
+                        .pc-form-grid-col-60
+                            +text-enabled-autofocus('JDBC URL:', 
'selectedPreset.jdbcUrl', '"jdbcUrl"', true, true, 'JDBC URL', 'JDBC URL for 
connecting to database<br>Refer to your database documentation for details')
+                        .pc-form-grid-col-30
+                            +text('User:', 'selectedPreset.user', 
'"jdbcUser"', false, '', 'User name for connecting to database')
+                        .pc-form-grid-col-30
+                            +password('Password:', 'selectedPreset.password', 
'"jdbcPassword"', false, '', 'Password for connecting to database<br>Note, 
password would not be saved in preferences for security 
reasons')(ignite-on-enter='importDomainNext()')
+                        .pc-form-grid-col-60
+                            - tipOpts.placement = 'auto'
+                            +checkbox('Tables only', 
'selectedPreset.tablesOnly', '"tablesOnly"', 'If selected, then only tables 
metadata will be parsed<br>Otherwise table and view metadata will be parsed')
+                            - tipOpts.placement = 'top'
+
+                .import-domain-model-wizard-page(ng-if='importDomain.action == 
"schemas"')
+                    pc-items-table(
+                        column-defs='::$ctrl.schemasColumnDefs'
+                        items='importDomain.schemas'
+                        hide-header='::true'
+                        one-way-selection='::true'
+                        selected-row-id='$ctrl.selectedSchemasIDs'
+                        
on-selection-change='$ctrl.onSchemaSelectionChange($event)'
+                        row-identity-key='name'
+                    )
+                .import-domain-model-wizard-page(ng-if='importDomain.action == 
"tables"')
+                    form.pc-form-grid-row(novalidate)
+                        .pc-form-grid-col-30
+                            +sane-ignite-form-field-dropdown({
+                                label: 'Action:',
+                                model: 'importCommon.action'
+                            })(
+                                bs-options='item.value as item.label for item 
in importActions'
+                            )
+                        .pc-form-grid-col-30
+                            +sane-ignite-form-field-dropdown({
+                                label: 'Cache:',
+                                model: 'importCommon.cacheOrTemplate'
+                            })(
+                                bs-options='item.value as item.label for item 
in importCommon.cachesOrTemplates'
+                                
ng-change='$ctrl.onCacheSelect(importCommon.cacheOrTemplate)'
+                            )
+                        .pc-form-grid-col-60.pc-form-grid__text-only-item
+                            | Defaults to be applied for filtered tables
+                            +tooltip('Select and apply options for caches 
generation')
+                            button.btn-ignite.btn-ignite--success(
+                                type='button'
+                                ng-click='applyDefaults()'
+                                style='margin-left: auto'
+                            ) Apply
+                    pc-items-table(
+                        column-defs='::$ctrl.tablesColumnDefs'
+                        items='importDomain.tables'
+                        hide-header='::true'
+                        
on-visible-rows-change='$ctrl.onVisibleRowsChange($event)'
+                        one-way-selection='::true'
+                        selected-row-id='$ctrl.selectedTablesIDs'
+                        
on-selection-change='$ctrl.onTableSelectionChange($event)'
+                        row-identity-key='id'
+                    )
+                .import-domain-model-wizard-page(ng-show='importDomain.action 
== "options"')
+                    -var form = 'optionsForm'
+                    -var generatePojo = 'ui.generatePojo'
+
+                    form.pc-form-grid-row(name=form novalidate)
+                        .pc-form-grid-col-60
+                            +checkbox('Use Java built-in types for keys', 
'ui.builtinKeys', '"domainBuiltinKeys"', 'Use Java built-in types like 
"Integer", "Long", "String" instead of POJO generation in case when table 
primary key contains only one field')
+                        .pc-form-grid-col-60
+                            +checkbox('Use primitive types for NOT NULL table 
columns', 'ui.usePrimitives', '"domainUsePrimitives"', 'Use primitive types 
like "int", "long", "double" for POJOs fields generation in case of NOT NULL 
columns')
+                        .pc-form-grid-col-60
+                            +checkbox('Generate query entity key fields', 
'ui.generateKeyFields', '"generateKeyFields"',
+                                'Generate key fields for query entity.<br\>\
+                                We need this for the cases when no key-value 
classes\
+                                are present on cluster nodes, and we need to 
build/modify keys and values during SQL DML operations.\
+                                Thus, setting this parameter is not mandatory 
and should be based on particular use case.')
+                        .pc-form-grid-col-60
+                            +checkbox('Generate POJO classes', generatePojo, 
'"domainGeneratePojo"', 'If selected then POJO classes will be generated from 
database tables')
+                        .pc-form-grid-col-60(ng-if=generatePojo)
+                            +checkbox('Generate aliases for query entity', 
'ui.generateTypeAliases', '"domainGenerateTypeAliases"', 'Generate aliases for 
query entity if table name is invalid Java identifier')
+                        .pc-form-grid-col-60(ng-if=generatePojo)
+                            +checkbox('Generate aliases for query fields', 
'ui.generateFieldAliases', '"domainGenerateFieldAliases"', 'Generate aliases 
for query fields with database field names when database field name differ from 
Java field name')
+                        .pc-form-grid-col-60(ng-if=generatePojo)
+                            +java-package('Package:', 'ui.packageName', 
'"domainPackageName"', true, true, 'Package that will be used for POJOs 
generation')
+            .modal-footer
+                
button.btn-ignite.btn-ignite--success.modal-import-models__prev-button(
+                    type='button'
+                    ng-hide='importDomain.action == "drivers" || 
importDomain.action == "connect"'
+                    ng-click='importDomainPrev()'
+                    b_s-tooltip=''
+                    d_ata-title='{{prevTooltipText()}}'
+                    d_ata-placement='bottom'
+                ) Prev
+                selected-items-amount-indicator(
+                    ng-if='$ctrl.$scope.importDomain.action === "schemas"'
+                    selected-amount='$ctrl.selectedSchemasIDs.length'
+                    total-amount='$ctrl.$scope.importDomain.schemas.length'
+                )
+                selected-items-amount-indicator(
+                    ng-if='$ctrl.$scope.importDomain.action === "tables"'
+                    selected-amount='$ctrl.selectedTablesIDs.length'
+                    total-amount='$ctrl.$scope.importDomain.tables.length'
+                )
+                
button.btn-ignite.btn-ignite--success.modal-import-models__next-button(
+                    type='button'
+                    ng-click='importDomainNext(optionsForm)'
+                    ng-disabled='!importDomainNextAvailable()'
+                    b_s-tooltip=''
+                    d_ata-title='{{nextTooltipText()}}'
+                    d_ata-placement='bottom'
+                )
+                    svg.icon-left(ignite-icon='checkmark' 
ng-show='importDomain.button === "Save"')
+                    | {{importDomain.button}}

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/modal-preview-project/component.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/components/modal-preview-project/component.js
 
b/modules/web-console/frontend/app/components/page-configure/components/modal-preview-project/component.js
new file mode 100644
index 0000000..462f01d
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/components/modal-preview-project/component.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 template from './template.pug';
+import './style.scss';
+import controller from './controller';
+
+export default {
+    name: 'modalPreviewProject',
+    template,
+    controller,
+    bindings: {
+        onHide: '&',
+        cluster: '<',
+        isDemo: '<'
+    }
+};

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/modal-preview-project/controller.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/components/modal-preview-project/controller.js
 
b/modules/web-console/frontend/app/components/page-configure/components/modal-preview-project/controller.js
new file mode 100644
index 0000000..e67ed25
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/components/modal-preview-project/controller.js
@@ -0,0 +1,120 @@
+/*
+ * 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 JSZip from 'jszip';
+
+export default class ModalPreviewProjectController {
+    static $inject = [
+        'PageConfigure',
+        'IgniteConfigurationResource',
+        'IgniteSummaryZipper',
+        'IgniteVersion',
+        '$scope',
+        'ConfigurationDownload',
+        'IgniteLoading',
+        'IgniteMessages'
+    ];
+
+    constructor(PageConfigure, IgniteConfigurationResource, summaryZipper, 
IgniteVersion, $scope, ConfigurationDownload, IgniteLoading, IgniteMessages) {
+        Object.assign(this, {PageConfigure, IgniteConfigurationResource, 
summaryZipper, IgniteVersion, $scope, ConfigurationDownload, IgniteLoading, 
IgniteMessages});
+    }
+
+    $onInit() {
+        this.treeOptions = {
+            nodeChildren: 'children',
+            dirSelectable: false,
+            injectClasses: {
+                iExpanded: 'fa fa-folder-open-o',
+                iCollapsed: 'fa fa-folder-o'
+            }
+        };
+        this.doStuff(this.cluster, this.isDemo);
+    }
+
+    showPreview(node) {
+        this.fileText = '';
+        if (!node) return;
+        this.fileExt = node.file.name.split('.').reverse()[0].toLowerCase();
+        if (node.file.dir) return;
+        node.file.async('string').then((text) => {
+            this.fileText = text;
+            this.$scope.$applyAsync();
+        });
+    }
+
+    doStuff(cluster, isDemo) {
+        this.IgniteLoading.start('projectStructurePreview');
+        return this.PageConfigure.getClusterConfiguration({clusterID: 
cluster._id, isDemo})
+        .then((data) => {
+            return this.IgniteConfigurationResource.populate(data);
+        })
+        .then(({clusters}) => {
+            return clusters.find(({_id}) => _id === cluster._id);
+        })
+        .then((cluster) => {
+            return this.summaryZipper({
+                cluster,
+                data: {},
+                IgniteDemoMode: isDemo,
+                targetVer: this.IgniteVersion.currentSbj.getValue()
+            });
+        })
+        .then(JSZip.loadAsync)
+        .then((val) => {
+            const convert = (files) => {
+                return Object.keys(files)
+                .map((path, i, paths) => ({
+                    fullPath: path,
+                    path: path.replace(/\/$/, ''),
+                    file: files[path],
+                    parent: files[paths.filter((p) => path.startsWith(p) && p 
!== path).sort((a, b) => b.length - a.length)[0]]
+                }))
+                .map((node, i, nodes) => Object.assign(node, {
+                    path: node.parent ? node.path.replace(node.parent.name, 
'') : node.path,
+                    children: nodes.filter((n) => n.parent && n.parent.name 
=== node.file.name)
+                }));
+            };
+
+            const nodes = convert(val.files);
+
+            this.data = [{
+                path: this.ConfigurationDownload.nameFile(cluster),
+                file: {dir: true},
+                children: nodes.filter((n) => !n.parent)
+            }];
+
+            this.selectedNode = nodes.find((n) => 
n.path.includes('server.xml'));
+            this.expandedNodes = [
+                ...this.data,
+                ...nodes.filter((n) => {
+                    return !n.fullPath.startsWith('src/main/java/')
+                        || 
/src\/main\/java(\/(config|load|startup))?\/$/.test(n.fullPath);
+                })
+            ];
+            this.showPreview(this.selectedNode);
+            this.IgniteLoading.finish('projectStructurePreview');
+        })
+        .catch((e) => {
+            this.IgniteMessages.showError('Failed to generate project preview: 
', e);
+            this.onHide();
+        });
+    }
+
+    orderBy() {
+        return;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/modal-preview-project/index.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/components/modal-preview-project/index.js
 
b/modules/web-console/frontend/app/components/page-configure/components/modal-preview-project/index.js
new file mode 100644
index 0000000..a0dc92e
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/components/modal-preview-project/index.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 'brace/mode/properties';
+import 'brace/mode/yaml';
+import angular from 'angular';
+import component from './component';
+import service from './service';
+
+export default angular
+    .module('ignite-console.page-configure.modal-preview-project', [])
+    .service(service.name, service)
+    .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-preview-project/service.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/components/modal-preview-project/service.js
 
b/modules/web-console/frontend/app/components/page-configure/components/modal-preview-project/service.js
new file mode 100644
index 0000000..83cd4f4
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/components/modal-preview-project/service.js
@@ -0,0 +1,52 @@
+/*
+ * 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 ModalPreviewProject {
+    static $inject = ['$modal'];
+    /**
+     * @param {mgcrea.ngStrap.modal.IModalService} $modal
+     */
+    constructor($modal) {
+        this.$modal = $modal;
+    }
+    /**
+     * @param {ig.config.cluster.ShortCluster} cluster
+     */
+    open(cluster) {
+        this.modalInstance = this.$modal({
+            locals: {
+                cluster
+            },
+            controller: ['cluster', '$rootScope', function(cluster, 
$rootScope) {
+                this.cluster = cluster;
+                this.isDemo = !!$rootScope.IgniteDemoMode;
+            }],
+            controllerAs: '$ctrl',
+            template: `
+                <modal-preview-project
+                    on-hide='$hide()'
+                    cluster='::$ctrl.cluster'
+                    is-demo='::$ctrl.isDemo'
+                ></modal-preview-project>
+            `,
+            show: false
+        });
+        return this.modalInstance.$promise.then((modal) => {
+            this.modalInstance.show();
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/modal-preview-project/style.scss
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/components/modal-preview-project/style.scss
 
b/modules/web-console/frontend/app/components/page-configure/components/modal-preview-project/style.scss
new file mode 100644
index 0000000..33d2fcf
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/components/modal-preview-project/style.scss
@@ -0,0 +1,67 @@
+
+/*
+ * 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-preview-project {
+    display: block;
+}
+
+.modal-preview-project-structure {
+    @import '../../../../../public/stylesheets/variables.scss';
+
+    .modal-dialog {
+        width: 900px;
+    }
+    .modal-content .modal-body {
+        display: flex;
+        flex-direction: row;
+        height: 360px;
+        padding-top: 10px;
+        padding-bottom: 0;
+    }
+    .pane-left {
+        width: 330px;
+        overflow: auto;
+        border-right: 1px solid #dddddd;
+    }
+    .pane-right {
+        width: 560px;
+    }
+    treecontrol {
+        white-space: nowrap;
+        font-family: Roboto;
+        font-size: 12px;
+        color: #393939;
+
+        ul {
+            overflow: visible;
+        }
+        li {
+            padding-left: 1em;
+        }
+        .fa {
+            margin-right: 5px;
+        }
+        .tree-selected {
+            color: $ignite-brand-success;
+        }
+    }
+    .file-preview {
+        margin: 0;
+        height: 100%;
+    }
+}
\ 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-preview-project/template.pug
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/components/modal-preview-project/template.pug
 
b/modules/web-console/frontend/app/components/page-configure/components/modal-preview-project/template.pug
new file mode 100644
index 0000000..3499277
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/components/modal-preview-project/template.pug
@@ -0,0 +1,47 @@
+//-
+    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.
+
+include /app/helpers/jade/mixins
+
+.modal.modal--ignite.theme--ignite.center.modal-preview-project-structure(tabindex='-1'
 role='dialog')
+    .modal-dialog
+        .modal-content
+            .modal-header
+                h4.modal-title 
+                    svg(ignite-icon="structure")
+                    span See Project Structure
+                button.close(type='button' aria-label='Close' 
ng-click='$ctrl.onHide()')
+                     svg(ignite-icon="cross")
+
+            .modal-body(
+                ignite-loading='projectStructurePreview'
+                ignite-loading-text='Generating project structure preview…'
+            )
+                .pane-left
+                    treecontrol(
+                        tree-model='$ctrl.data'
+                        on-selection='$ctrl.showPreview(node)'
+                        selected-node='$ctrl.selectedNode'
+                        expanded-nodes='$ctrl.expandedNodes'
+                        options='$ctrl.treeOptions'
+                        order-by='["file.dir", "-path"]'
+                    )
+                        i.fa.fa-file-text-o(ng-if='::!node.file.dir')
+                        | {{ ::node.path }}
+                .pane-right
+                    div.file-preview(ignite-ace='{mode: $ctrl.fileExt, 
readonly: true}' ng-model='$ctrl.fileText')
+            .modal-footer
+                
button.btn-ignite.btn-ignite--success(ng-click='$ctrl.onHide()') Close
\ 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/pc-form-field-size/component.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/components/pc-form-field-size/component.js
 
b/modules/web-console/frontend/app/components/page-configure/components/pc-form-field-size/component.js
new file mode 100644
index 0000000..e90a2cf
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/components/pc-form-field-size/component.js
@@ -0,0 +1,41 @@
+/*
+ * 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 controller from './controller';
+import './style.scss';
+
+export default {
+    controller,
+    template,
+    transclude: true,
+    require: {
+        ngModel: 'ngModel'
+    },
+    bindings: {
+        label: '@',
+        placeholder: '@',
+        min: '@?',
+        max: '@?',
+        tip: '@',
+        required: '<?',
+        sizeType: '@?',
+        sizeScaleLabel: '@?',
+        onScaleChange: '&?',
+        ngDisabled: '<?'
+    }
+};

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/pc-form-field-size/controller.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/components/pc-form-field-size/controller.js
 
b/modules/web-console/frontend/app/components/page-configure/components/pc-form-field-size/controller.js
new file mode 100644
index 0000000..3253fe4
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/components/pc-form-field-size/controller.js
@@ -0,0 +1,131 @@
+/*
+ * 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 get from 'lodash/get';
+
+export default class PCFormFieldSizeController {
+    /** @type {ng.INgModelController} */
+    ngModel;
+    /** @type {number} */
+    min;
+    /** @type {number} */
+    max;
+    /** @type {ng.ICompiledExpression} */
+    onScaleChange;
+    /** @type {ng.IFormController} */
+    innerForm;
+
+    static $inject = ['$element', '$attrs'];
+
+    /** @type {ig.config.formFieldSize.ISizeTypes} */
+    static sizeTypes = {
+        bytes: [
+            {label: 'Kb', value: 1024},
+            {label: 'Mb', value: 1024 * 1024},
+            {label: 'Gb', value: 1024 * 1024 * 1024}
+        ],
+        seconds: [
+            {label: 'ns', value: 1 / 1000},
+            {label: 'ms', value: 1},
+            {label: 's', value: 1000}
+        ]
+    };
+
+    /**
+     * @param {JQLite} $element
+     * @param {ng.IAttributes} $attrs
+     */
+    constructor($element, $attrs) {
+        this.$element = $element;
+        this.$attrs = $attrs;
+        this.id = Math.random();
+    }
+
+    $onDestroy() {
+        this.$element = null;
+    }
+
+    $onInit() {
+        if (!this.min) this.min = 0;
+        if (!this.sizesMenu) this.setDefaultSizeType();
+        this.$element.addClass('ignite-form-field');
+        this.ngModel.$render = () => this.assignValue(this.ngModel.$viewValue);
+    }
+
+    $postLink() {
+        if ('min' in this.$attrs)
+            this.ngModel.$validators.min = (value) => 
this.ngModel.$isEmpty(value) || value === void 0 || value >= this.min;
+        if ('max' in this.$attrs)
+            this.ngModel.$validators.max = (value) => 
this.ngModel.$isEmpty(value) || value === void 0 || value <= this.max;
+
+        this.ngModel.$validators.step = (value) => 
this.ngModel.$isEmpty(value) || value === void 0 || Math.floor(value) === value;
+    }
+
+    $onChanges(changes) {
+        if ('sizeType' in changes) {
+            this.sizesMenu = 
PCFormFieldSizeController.sizeTypes[changes.sizeType.currentValue];
+            this.sizeScale = this.chooseSizeScale(get(changes, 
'sizeScaleLabel.currentValue'));
+        }
+        if (!this.sizesMenu) this.setDefaultSizeType();
+        if ('sizeScaleLabel' in changes)
+            this.sizeScale = 
this.chooseSizeScale(changes.sizeScaleLabel.currentValue);
+
+        if ('min' in changes) this.ngModel.$validate();
+    }
+
+    /**
+     * @param {ig.config.formFieldSize.ISizeTypeOption} value
+     */
+    set sizeScale(value) {
+        this._sizeScale = value;
+        if (this.onScaleChange) this.onScaleChange({$event: this.sizeScale});
+        if (this.ngModel) this.assignValue(this.ngModel.$viewValue);
+    }
+
+    get sizeScale() {
+        return this._sizeScale;
+    }
+
+    /**
+     * @param {number} rawValue
+     */
+    assignValue(rawValue) {
+        if (!this.sizesMenu) this.setDefaultSizeType();
+        return this.value = rawValue
+            ? rawValue / this.sizeScale.value
+            : rawValue;
+    }
+
+    onValueChange() {
+        this.ngModel.$setViewValue(this.value ? this.value * 
this.sizeScale.value : this.value);
+    }
+
+    _defaultLabel() {
+        if (!this.sizesMenu) return;
+        return this.sizesMenu[1].label;
+    }
+
+    chooseSizeScale(label = this._defaultLabel()) {
+        if (!label) return;
+        return this.sizesMenu.find((option) => option.label.toLowerCase() === 
label.toLowerCase());
+    }
+
+    setDefaultSizeType() {
+        this.sizesMenu = PCFormFieldSizeController.sizeTypes.bytes;
+        this.sizeScale = this.chooseSizeScale();
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/pc-form-field-size/index.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/components/pc-form-field-size/index.js
 
b/modules/web-console/frontend/app/components/page-configure/components/pc-form-field-size/index.js
new file mode 100644
index 0000000..1fdc379
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/components/pc-form-field-size/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('ignite-console.page-configure.form-field-size', [])
+    .component('pcFormFieldSize', component);

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/pc-form-field-size/style.scss
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/components/pc-form-field-size/style.scss
 
b/modules/web-console/frontend/app/components/page-configure/components/pc-form-field-size/style.scss
new file mode 100644
index 0000000..737b2a0
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/components/pc-form-field-size/style.scss
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+pc-form-field-size {
+    @import "./../../../../../public/stylesheets/variables.scss";
+
+    .input-tip {
+        display: flex;
+        flex-direction: row;
+
+        .form-control {
+            border-top-right-radius: 0;
+            border-bottom-right-radius: 0;
+        }
+
+        input {
+            border-top-right-radius: 0 !important;
+            border-bottom-right-radius: 0 !important;
+            min-width: 0;
+        }
+
+        .btn-ignite {
+            border-top-left-radius: 0 !important;
+            border-bottom-left-radius: 0 !important;
+            flex: 0 0 auto;
+            width: 60px !important;
+            line-height: initial !important;
+        }
+    }
+
+    &.ng-invalid:not(.ng-pristine),
+    &.ng-invalid.ng-touched {
+        input, .btn-ignite {
+            border-color: $ignite-brand-primary !important;
+            box-shadow: inset 0 1px 3px 0 rgba($ignite-brand-primary, .5) 
!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/pc-form-field-size/template.pug
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/components/pc-form-field-size/template.pug
 
b/modules/web-console/frontend/app/components/page-configure/components/pc-form-field-size/template.pug
new file mode 100644
index 0000000..de62d35
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/components/pc-form-field-size/template.pug
@@ -0,0 +1,61 @@
+//-
+    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.
+
+include /app/helpers/jade/mixins
+
++ignite-form-field__label('{{ ::$ctrl.label }}', '$ctrl.id', '$ctrl.required', 
'$ctrl.ngDisabled')
+    span(ng-if='::$ctrl.tip')
+        +tooltip('{{::$ctrl.tip}}')
+.ignite-form-field__control(ng-form='$ctrl.innerForm')
+    .input-tip
+        input.form-control(
+            type='number'
+            id='{{::$ctrl.id}}Input'
+            ng-model='$ctrl.value'
+            ng-model-options='{allowInvalid: true}'
+            ng-change='$ctrl.onValueChange()'
+            name='numberInput'
+            placeholder='{{$ctrl.placeholder}}'
+            min='{{ $ctrl.min ? $ctrl.min / $ctrl.sizeScale.value : "" }}'
+            max='{{ $ctrl.max ? $ctrl.max / $ctrl.sizeScale.value : "" }}'
+            ng-required='$ctrl.required'
+            ng-disabled='$ctrl.ngDisabled'
+        )
+        button.btn-ignite.btn-ignite--secondary(
+            bs-select
+            bs-options='size as size.label for size in $ctrl.sizesMenu'
+            ng-model='$ctrl.sizeScale'
+            protect-from-bs-select-render
+            ng-disabled='$ctrl.ngDisabled'
+            type='button'
+        )
+            | {{ $ctrl.sizeScale.label }}
+            span.fa.fa-caret-down.icon-right
+.ignite-form-field__errors(
+    ng-messages='$ctrl.ngModel.$error'
+    ng-show=`($ctrl.ngModel.$dirty || $ctrl.ngModel.$touched || 
$ctrl.ngModel.$submitted) && $ctrl.ngModel.$invalid`
+)
+    div(ng-transclude)
+    div(ng-message='required')
+        | This field could not be empty
+    div(ng-message='min')
+        | Value is less than allowable minimum: {{ 
$ctrl.min/$ctrl.sizeScale.value }} {{$ctrl.sizeScale.label}}
+    div(ng-message='max')
+        | Value is more than allowable maximum: {{ 
$ctrl.max/$ctrl.sizeScale.value }} {{$ctrl.sizeScale.label}}
+    div(ng-message='number')
+        | Only numbers allowed
+    div(ng-message='step')
+        | Invalid step
\ 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/pc-items-table/component.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/components/pc-items-table/component.js
 
b/modules/web-console/frontend/app/components/page-configure/components/pc-items-table/component.js
new file mode 100644
index 0000000..1ca04c0
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/components/pc-items-table/component.js
@@ -0,0 +1,45 @@
+/*
+ * 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';
+import controller from './controller';
+
+export default {
+    template,
+    controller,
+    transclude: {
+        footerSlot: '?footerSlot'
+    },
+    bindings: {
+        items: '<',
+        onVisibleRowsChange: '&?',
+        onSortChanged: '&?',
+        onFilterChanged: '&?',
+
+        hideHeader: '<?',
+        rowIdentityKey: '@?',
+
+        columnDefs: '<',
+        tableTitle: '<',
+        selectedRowId: '<?',
+        maxRowsToShow: '@?',
+        onSelectionChange: '&?',
+        oneWaySelection: '<?',
+        incomingActionsMenu: '<?actionsMenu'
+    }
+};

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/pc-items-table/controller.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/components/pc-items-table/controller.js
 
b/modules/web-console/frontend/app/components/page-configure/components/pc-items-table/controller.js
new file mode 100644
index 0000000..f4d1f47
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/components/pc-items-table/controller.js
@@ -0,0 +1,125 @@
+/*
+ * 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 debounce from 'lodash/debounce';
+
+export default class ItemsTableController {
+    static $inject = ['$scope', 'gridUtil', '$timeout', 
'uiGridSelectionService'];
+
+    constructor($scope, gridUtil, $timeout, uiGridSelectionService) {
+        Object.assign(this, {$scope, gridUtil, $timeout, 
uiGridSelectionService});
+        this.rowIdentityKey = '_id';
+    }
+
+    $onInit() {
+        this.grid = {
+            data: this.items,
+            columnDefs: this.columnDefs,
+            rowHeight: 46,
+            enableColumnMenus: false,
+            enableFullRowSelection: true,
+            enableSelectionBatchEvent: true,
+            selectionRowHeaderWidth: 52,
+            enableColumnCategories: true,
+            flatEntityAccess: true,
+            headerRowHeight: 70,
+            modifierKeysToMultiSelect: true,
+            enableFiltering: true,
+            rowIdentity: (row) => {
+                return row[this.rowIdentityKey];
+            },
+            onRegisterApi: (api) => {
+                this.gridAPI = api;
+                api.selection.on.rowSelectionChanged(this.$scope, (row, e) => {
+                    this.onRowsSelectionChange([row], e);
+                });
+                api.selection.on.rowSelectionChangedBatch(this.$scope, (rows, 
e) => {
+                    this.onRowsSelectionChange(rows, e);
+                });
+                api.core.on.rowsVisibleChanged(this.$scope, () => {
+                    const visibleRows = api.core.getVisibleRows();
+                    if (this.onVisibleRowsChange) 
this.onVisibleRowsChange({$event: visibleRows});
+                    this.adjustHeight(api, visibleRows.length);
+                    this.showFilterNotification = this.grid.data.length && 
visibleRows.length === 0;
+                });
+                if (this.onFilterChanged) {
+                    api.core.on.filterChanged(this.$scope, () => {
+                        this.onFilterChanged();
+                    });
+                }
+                this.$timeout(() => {
+                    if (this.selectedRowId) 
this.applyIncomingSelection(this.selectedRowId);
+                });
+            },
+            appScopeProvider: this.$scope.$parent
+        };
+        this.actionsMenu = this.makeActionsMenu(this.incomingActionsMenu);
+    }
+
+    oneWaySelection = false;
+
+    onRowsSelectionChange = debounce((rows, e = {}) => {
+        if (e.ignore) return;
+        const selected = this.gridAPI.selection.legacyGetSelectedRows();
+        if (this.oneWaySelection) rows.forEach((r) => r.isSelected = false);
+        if (this.onSelectionChange) this.onSelectionChange({$event: selected});
+    });
+
+    makeActionsMenu(incomingActionsMenu = []) {
+        return incomingActionsMenu;
+    }
+
+    $onChanges(changes) {
+        const hasChanged = (binding) => binding in changes && 
changes[binding].currentValue !== changes[binding].previousValue;
+        if (hasChanged('items') && this.grid) {
+            this.grid.data = changes.items.currentValue;
+            this.gridAPI.grid.modifyRows(this.grid.data);
+            this.adjustHeight(this.gridAPI, this.grid.data.length);
+            // Without property existence check non-set selectedRowId binding 
might cause
+            // unwanted behavior, like unchecking rows during any items 
change, even if
+            // nothing really changed.
+            if ('selectedRowId' in this) 
this.applyIncomingSelection(this.selectedRowId);
+        }
+        if (hasChanged('selectedRowId') && this.grid && this.grid.data)
+            this.applyIncomingSelection(changes.selectedRowId.currentValue);
+        if ('incomingActionsMenu' in changes)
+            this.actionsMenu = 
this.makeActionsMenu(changes.incomingActionsMenu.currentValue);
+    }
+
+    applyIncomingSelection(selected = []) {
+        this.gridAPI.selection.clearSelectedRows({ignore: true});
+        const rows = this.grid.data.filter((r) => 
selected.includes(r[this.rowIdentityKey]));
+        rows.forEach((r) => {
+            this.gridAPI.selection.selectRow(r, {ignore: true});
+        });
+        if (rows.length === 1) {
+            this.$timeout(() => {
+                
this.gridAPI.grid.scrollToIfNecessary(this.gridAPI.grid.getRow(rows[0]), null);
+            });
+        }
+    }
+
+    adjustHeight(api, rows) {
+        const maxRowsToShow = this.maxRowsToShow || 5;
+        const headerBorder = 1;
+        const header = this.grid.headerRowHeight + headerBorder;
+        const optionalScroll = (rows ? this.gridUtil.getScrollbarWidth() : 0);
+        const height = Math.min(rows, maxRowsToShow) * this.grid.rowHeight + 
header + optionalScroll;
+        api.grid.element.css('height', height + 'px');
+        api.core.handleWindowResize();
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/pc-items-table/decorator.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/components/pc-items-table/decorator.js
 
b/modules/web-console/frontend/app/components/page-configure/components/pc-items-table/decorator.js
new file mode 100644
index 0000000..a63f066
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/components/pc-items-table/decorator.js
@@ -0,0 +1,34 @@
+/*
+ * 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 ['$delegate', 'uiGridSelectionService', ($delegate, 
uiGridSelectionService) => {
+    $delegate[0].require = ['^uiGrid', '?^pcItemsTable'];
+    $delegate[0].compile = () => ($scope, $el, $attr, [uiGridCtrl, 
pcItemsTable]) => {
+        const self = uiGridCtrl.grid;
+        $delegate[0].link($scope, $el, $attr, uiGridCtrl);
+        const mySelectButtonClick = (row, evt) => {
+            evt.stopPropagation();
+
+            if (evt.shiftKey)
+                uiGridSelectionService.shiftSelect(self, row, evt, 
self.options.multiSelect);
+            else
+                uiGridSelectionService.toggleRowSelection(self, row, evt, 
self.options.multiSelect, self.options.noUnselect);
+        };
+        if (pcItemsTable) $scope.selectButtonClick = mySelectButtonClick;
+    };
+    return $delegate;
+}];

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/pc-items-table/index.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/components/pc-items-table/index.js
 
b/modules/web-console/frontend/app/components/page-configure/components/pc-items-table/index.js
new file mode 100644
index 0000000..38f8777
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/components/pc-items-table/index.js
@@ -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.
+ */
+
+import angular from 'angular';
+import component from './component';
+import decorator from './decorator';
+
+export default angular
+    .module('ignite-console.page-configure.items-table', ['ui.grid'])
+    .decorator('uiGridSelectionRowHeaderButtonsDirective', decorator)
+    .component('pcItemsTable', component);

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/pc-items-table/style.scss
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/components/pc-items-table/style.scss
 
b/modules/web-console/frontend/app/components/page-configure/components/pc-items-table/style.scss
new file mode 100644
index 0000000..227f23c
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/components/pc-items-table/style.scss
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+pc-items-table {
+    @import "./../../../../../public/stylesheets/variables.scss";
+
+    display: block;
+
+    .panel-title {
+        display: flex;
+        flex-direction: row;
+    }
+    .ui-grid-settings--heading {
+        flex: 1;
+    }
+
+    // Removes unwanted box-shadow and border-right from checkboxes column
+    .ui-grid.ui-grid--ignite 
.ui-grid-pinned-container.ui-grid-pinned-container-left 
.ui-grid-render-container-left:before {
+        box-shadow: none;
+    }
+    .ui-grid-pinned-container.ui-grid-pinned-container-left 
.ui-grid-cell:last-child {
+        border-right: none;
+    }
+    .ui-grid--ignite .ui-grid-header-cell .ui-grid-cell-contents {
+        padding-top: (69px - 20px) / 2;
+        padding-bottom: (69px - 20px) / 2;
+    }
+    footer-slot {
+        $height: 36px + 11px;
+        $line-height: 16px;
+        display: block;
+        line-height: $line-height;
+        font-size: 14px;
+        padding: ($height - $line-height) / 2 20px ($height - $line-height) / 
2 70px;
+    }
+    .pco-clusters-table__column-selection {
+        margin-left: 0 !important;
+    }
+    .pco-clusters-table__actions-button {
+        margin-left: auto;
+    }
+    // Fixes header jank
+    .ui-grid-header-viewport {
+        min-height: 70px;
+    }
+    .pc-items-table__selection-count {
+        font-size: 14px;
+        font-style: italic;
+        flex: 0 0 auto;
+    }
+    .pc-items-table__table-name {
+        display: flex;
+    }
+    grid-column-selector {
+        flex: 0 0 auto;
+    }
+}
\ 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/pc-items-table/template.pug
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/components/pc-items-table/template.pug
 
b/modules/web-console/frontend/app/components/page-configure/components/pc-items-table/template.pug
new file mode 100644
index 0000000..0a8475c
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/components/pc-items-table/template.pug
@@ -0,0 +1,49 @@
+//-
+    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.
+
+include /app/helpers/jade/mixins
+
+.panel--ignite
+    .panel-heading.ui-grid-settings(ng-if='!$ctrl.hideHeader')
+        .panel-title
+            .pc-items-table__table-name.ng-animate-disabled(
+                ng-hide='$ctrl.gridAPI.selection.getSelectedCount()'
+            )
+                | {{ $ctrl.tableTitle }}
+                grid-column-selector(grid-api='$ctrl.gridAPI')
+            .pc-items-table__selection-count.ng-animate-disabled(
+                ng-show='$ctrl.gridAPI.selection.getSelectedCount()'
+            )
+                i {{ $ctrl.gridAPI.selection.getSelectedCount() }} of {{ 
$ctrl.items.length }} selected
+            .pco-clusters-table__actions-button
+                +ignite-form-field-bsdropdown({
+                    label: 'Actions',
+                    name: 'action',
+                    disabled: '!$ctrl.gridAPI.selection.getSelectedCount()',
+                    required: false,
+                    options: '$ctrl.actionsMenu'
+                })
+    .grid.ui-grid--ignite(
+        ui-grid='$ctrl.grid'
+        ui-grid-selection
+        pco-grid-column-categories
+        pc-ui-grid-filters
+        ui-grid-resize-columns
+        ui-grid-hovering
+    )
+
+    div(ng-transclude='footerSlot' ng-hide='$ctrl.showFilterNotification')
+    footer-slot(ng-if='$ctrl.showFilterNotification' 
style='font-style:italic') Nothing to display. Check your filters.
\ 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/pc-ui-grid-filters/directive.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/components/pc-ui-grid-filters/directive.js
 
b/modules/web-console/frontend/app/components/page-configure/components/pc-ui-grid-filters/directive.js
new file mode 100644
index 0000000..ccbdb8d
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/components/pc-ui-grid-filters/directive.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';
+
+export default function pcUiGridFilters(uiGridConstants) {
+    return {
+        require: 'uiGrid',
+        link: {
+            pre(scope, el, attr, grid) {
+                if (!grid.grid.options.enableFiltering) return;
+                grid.grid.options.columnDefs.filter((cd) => 
cd.multiselectFilterOptions).forEach((cd) => {
+                    cd.headerCellTemplate = template;
+                    cd.filter = {
+                        type: uiGridConstants.filter.SELECT,
+                        term: cd.multiselectFilterOptions.map((t) => t.value),
+                        condition(searchTerm, cellValue, row, column) {
+                            return searchTerm.includes(cellValue);
+                        },
+                        selectOptions: cd.multiselectFilterOptions,
+                        $$selectOptionsMapping: 
cd.multiselectFilterOptions.reduce((a, v) => Object.assign(a, {[v.value]: 
v.label}), {}),
+                        $$multiselectFilterTooltip() {
+                            const prefix = 'Active filter';
+                            switch (this.term.length) {
+                                case 0:
+                                    return `${prefix}: show none`;
+                                default:
+                                    return `${prefix}: ${this.term.map((t) => 
this.$$selectOptionsMapping[t]).join(', ')}`;
+                                case this.selectOptions.length:
+                                    return `${prefix}: show all`;
+                            }
+                        }
+                    };
+                    if (!cd.cellTemplate) {
+                        cd.cellTemplate = `
+                            <div class="ui-grid-cell-contents">
+                                {{ 
col.colDef.filter.$$selectOptionsMapping[row.entity[col.field]] }}
+                            </div>
+                        `;
+                    }
+                });
+            }
+        }
+    };
+}
+
+pcUiGridFilters.$inject = ['uiGridConstants'];

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/pc-ui-grid-filters/index.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/components/pc-ui-grid-filters/index.js
 
b/modules/web-console/frontend/app/components/page-configure/components/pc-ui-grid-filters/index.js
new file mode 100644
index 0000000..4ec96e2
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/components/pc-ui-grid-filters/index.js
@@ -0,0 +1,43 @@
+/*
+ * 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 directive from './directive';
+import flow from 'lodash/flow';
+
+export default angular
+    .module('ignite-console.page-configure.pc-ui-grid-filters', ['ui.grid'])
+    .decorator('$tooltip', ['$delegate', ($delegate) => {
+        return function(el, config) {
+            const instance = $delegate(el, config);
+            instance.$referenceElement = el;
+            instance.destroy = flow(instance.destroy, () => 
instance.$referenceElement = null);
+            instance.$applyPlacement = flow(instance.$applyPlacement, () => {
+                if (!instance.$element) return;
+                const refWidth = 
instance.$referenceElement[0].getBoundingClientRect().width;
+                const elWidth = 
instance.$element[0].getBoundingClientRect().width;
+                if (refWidth > elWidth) {
+                    instance.$element.css({
+                        width: refWidth,
+                        maxWidth: 'initial'
+                    });
+                }
+            });
+            return instance;
+        };
+    }])
+    .directive('pcUiGridFilters', directive);

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/pc-ui-grid-filters/style.scss
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/components/pc-ui-grid-filters/style.scss
 
b/modules/web-console/frontend/app/components/page-configure/components/pc-ui-grid-filters/style.scss
new file mode 100644
index 0000000..cbecc68
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/components/pc-ui-grid-filters/style.scss
@@ -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.
+ */
+
+.pc-ui-grid-filters {
+    // Decrease horizontal padding because multiselect button already has it
+    padding-left: 8px !important;
+    padding-right: 8px !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/pc-ui-grid-filters/template.pug
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/components/pc-ui-grid-filters/template.pug
 
b/modules/web-console/frontend/app/components/page-configure/components/pc-ui-grid-filters/template.pug
new file mode 100644
index 0000000..e39742d
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/components/pc-ui-grid-filters/template.pug
@@ -0,0 +1,39 @@
+//-
+    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.
+
+.ui-grid-filter-container.pc-ui-grid-filters(
+    role='columnheader'
+    ng-style='col.extraStyle'
+    ng-repeat='colFilter in col.filters'
+    ng-class="{'ui-grid-filter-cancel-button-hidden' : 
colFilter.disableCancelFilterButton === true }"
+    ng-switch='colFilter.type'
+)
+    div(ng-switch-when='select')
+        button.btn-ignite.btn-ignite--link-dashed-success(
+            ng-class=`{
+                'btn-ignite--link-dashed-success': colFilter.term.length === 
colFilter.selectOptions.length,
+                'btn-ignite--link-dashed-primary': colFilter.term.length !== 
colFilter.selectOptions.length
+            }`
+            type='button'
+            title='{{ colFilter.$$multiselectFilterTooltip() }}'
+            ng-model='colFilter.term'
+            bs-select
+            bs-options='option.value as option.label for option in 
colFilter.selectOptions'
+            data-multiple='true'
+            data-trigger='click'
+            data-placement='bottom-right'
+            protect-from-bs-select-render
+        ) {{ col.displayName }}

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/pcIsInCollection.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/components/pcIsInCollection.js
 
b/modules/web-console/frontend/app/components/page-configure/components/pcIsInCollection.js
new file mode 100644
index 0000000..d2a9ae8
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/components/pcIsInCollection.js
@@ -0,0 +1,41 @@
+/*
+ * 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 Controller {
+    $onInit() {
+        this.ngModel.$validators.isInCollection = (item) => {
+            if (!item || !this.items) return true;
+            return this.items.includes(item);
+        };
+    }
+
+    $onChanges() {
+        this.ngModel.$validate();
+    }
+}
+
+export default function pcIsInCollection() {
+    return {
+        controller: Controller,
+        require: {
+            ngModel: 'ngModel'
+        },
+        bindToController: {
+            items: '<pcIsInCollection'
+        }
+    };
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/components/pcValidation.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/components/pcValidation.js
 
b/modules/web-console/frontend/app/components/page-configure/components/pcValidation.js
new file mode 100644
index 0000000..45ca6f2
--- /dev/null
+++ 
b/modules/web-console/frontend/app/components/page-configure/components/pcValidation.js
@@ -0,0 +1,192 @@
+/*
+ * 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';
+
+export class IgniteFormField {
+    static animName = 'ignite-form-field__error-blink';
+    static eventName = 'webkitAnimationEnd oAnimationEnd msAnimationEnd 
animationend';
+    static $inject = ['$element', '$scope'];
+    constructor($element, $scope) {
+        Object.assign(this, {$element});
+        this.$scope = $scope;
+    }
+    $postLink() {
+        this.onAnimEnd = () => 
this.$element.removeClass(IgniteFormField.animName);
+        this.$element.on(IgniteFormField.eventName, this.onAnimEnd);
+    }
+    $onDestroy() {
+        this.$element.off(IgniteFormField.eventName, this.onAnimEnd);
+        this.$element = this.onAnimEnd = null;
+    }
+    notifyAboutError() {
+        if (this.$element) this.$element.addClass(IgniteFormField.animName);
+    }
+    /**
+     * Exposes control in $scope
+     * @param {ng.INgModelController} control
+     */
+    exposeControl(control, name = '$input') {
+        this.$scope[name] = control;
+        this.$scope.$on('$destroy', () => this.$scope[name] = null);
+    }
+}
+
+export default angular.module('ignite-console.page-configure.validation', [])
+    .directive('pcNotInCollection', function() {
+        class Controller {
+            /** @type {ng.INgModelController} */
+            ngModel;
+            /** @type {Array} */
+            items;
+
+            $onInit() {
+                this.ngModel.$validators.notInCollection = (item) => {
+                    if (!this.items) return true;
+                    return !this.items.includes(item);
+                };
+            }
+
+            $onChanges() {
+                this.ngModel.$validate();
+            }
+        }
+
+        return {
+            controller: Controller,
+            require: {
+                ngModel: 'ngModel'
+            },
+            bindToController: {
+                items: '<pcNotInCollection'
+            }
+        };
+    })
+    .directive('pcInCollection', function() {
+        class Controller {
+            /** @type {ng.INgModelController} */
+            ngModel;
+            /** @type {Array} */
+            items;
+            /** @type {string} */
+            pluck;
+
+            $onInit() {
+                this.ngModel.$validators.inCollection = (item) => {
+                    if (!this.items) return false;
+                    const items = this.pluck ? this.items.map((i) => 
i[this.pluck]) : this.items;
+                    return Array.isArray(item)
+                        ? item.every((i) => items.includes(i))
+                        : items.includes(item);
+                };
+            }
+
+            $onChanges() {
+                this.ngModel.$validate();
+            }
+        }
+
+        return {
+            controller: Controller,
+            require: {
+                ngModel: 'ngModel'
+            },
+            bindToController: {
+                items: '<pcInCollection',
+                pluck: '@?pcInCollectionPluck'
+            }
+        };
+    })
+    .directive('pcPowerOfTwo', function() {
+        class Controller {
+            /** @type {ng.INgModelController} */
+            ngModel;
+            $onInit() {
+                this.ngModel.$validators.powerOfTwo = (value) => {
+                    return !value || ((value & -value) === value);
+                };
+            }
+        }
+
+        return {
+            controller: Controller,
+            require: {
+                ngModel: 'ngModel'
+            },
+            bindToController: true
+        };
+    })
+    .directive('bsCollapseTarget', function() {
+        return {
+            require: {
+                bsCollapse: '^^bsCollapse'
+            },
+            bindToController: true,
+            controller: ['$element', '$scope', function($element, $scope) {
+                this.open = function() {
+                    const index = this.bsCollapse.$targets.indexOf($element);
+                    const isActive = 
this.bsCollapse.$targets.$active.includes(index);
+                    if (!isActive) this.bsCollapse.$setActive(index);
+                };
+                this.$onDestroy = () => this.open = $element = null;
+            }]
+        };
+    })
+    .directive('ngModel', ['$timeout', function($timeout) {
+        return {
+            require: ['ngModel', '?^^bsCollapseTarget', '?^^igniteFormField'],
+            link(scope, el, attr, [ngModel, bsCollapseTarget, 
igniteFormField]) {
+                const off = scope.$on('$showValidationError', (e, target) => {
+                    if (target !== ngModel) return;
+                    ngModel.$setTouched();
+                    bsCollapseTarget && bsCollapseTarget.open();
+                    $timeout(() => {
+                        if (el[0].scrollIntoViewIfNeeded)
+                            el[0].scrollIntoViewIfNeeded();
+                        else
+                            el[0].scrollIntoView();
+
+                        if (!attr.bsSelect) $timeout(() => el[0].focus());
+                        igniteFormField && igniteFormField.notifyAboutError();
+                    });
+                });
+            }
+        };
+    }])
+    .directive('igniteFormField', function() {
+        return {
+            restrict: 'C',
+            controller: IgniteFormField,
+            scope: true
+        };
+    })
+    .directive('isValidJavaIdentifier', ['IgniteLegacyUtils', 
function(LegacyUtils) {
+        return {
+            link(scope, el, attr, ngModel) {
+                ngModel.$validators.isValidJavaIdentifier = (value) => 
LegacyUtils.VALID_JAVA_IDENTIFIER.test(value);
+            },
+            require: 'ngModel'
+        };
+    }])
+    .directive('notJavaReservedWord', ['IgniteLegacyUtils', 
function(LegacyUtils) {
+        return {
+            link(scope, el, attr, ngModel) {
+                ngModel.$validators.notJavaReservedWord = (value) => 
!LegacyUtils.JAVA_KEYWORDS.includes(value);
+            },
+            require: 'ngModel'
+        };
+    }]);

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/components/page-configure/controller.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/page-configure/controller.js 
b/modules/web-console/frontend/app/components/page-configure/controller.js
index 5ead0bb..91bdf50 100644
--- a/modules/web-console/frontend/app/components/page-configure/controller.js
+++ b/modules/web-console/frontend/app/components/page-configure/controller.js
@@ -15,10 +15,39 @@
  * limitations under the License.
  */
 
+import get from 'lodash/get';
+import {Observable} from 'rxjs/Observable';
+import 'rxjs/add/observable/merge';
+import {combineLatest} from 'rxjs/observable/combineLatest';
+import 'rxjs/add/operator/distinctUntilChanged';
+import {default as ConfigureState} from './services/ConfigureState';
+import {default as ConfigSelectors} from './store/selectors';
+
 export default class PageConfigureController {
-    static $inject = ['$scope'];
+    static $inject = ['$uiRouter', 'ConfigureState', 'ConfigSelectors'];
 
-    constructor($scope) {
-        Object.assign(this, {$scope});
+    /**
+     * @param {uirouter.UIRouter} $uiRouter
+     * @param {ConfigureState} ConfigureState
+     * @param {ConfigSelectors} ConfigSelectors
+     */
+    constructor($uiRouter, ConfigureState, ConfigSelectors) {
+        this.$uiRouter = $uiRouter;
+        this.ConfigureState = ConfigureState;
+        this.ConfigSelectors = ConfigSelectors;
     }
+
+    $onInit() {
+        /** @type {Observable<string>} */
+        this.clusterID$ = this.$uiRouter.globals.params$.pluck('clusterID');
+        const cluster$ = this.clusterID$.switchMap((id) => 
this.ConfigureState.state$.let(this.ConfigSelectors.selectCluster(id)));
+        const isNew$ = this.clusterID$.map((v) => v === 'new');
+        this.clusterName$ = combineLatest(cluster$, isNew$, (cluster, isNew) 
=> {
+            return `${isNew ? 'Create' : 'Edit'} cluster configuration ${isNew 
? '' : `‘${get(cluster, 'name')}’`}`;
+        });
+
+        this.tooltipsVisible = true;
+    }
+
+    $onDestroy() {}
 }

Reply via email to