http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/domains/query.pug
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/modules/states/configuration/domains/query.pug
 
b/modules/web-console/frontend/app/modules/states/configuration/domains/query.pug
index b4b5abe..9dc513a 100644
--- 
a/modules/web-console/frontend/app/modules/states/configuration/domains/query.pug
+++ 
b/modules/web-console/frontend/app/modules/states/configuration/domains/query.pug
@@ -22,169 +22,237 @@ include /app/helpers/jade/mixins
 -var queryFields = `${model}.fields`
 -var queryAliases = `${model}.aliases`
 -var queryIndexes = `${model}.indexes`
--var queryFieldsForm = 'queryFields'
--var queryAliasesForm = 'queryAliases'
--var queryIndexesForm = 'queryIndexes'
-
-//- LEGACY mixin for LEGACY index fields table.
-mixin table-index-item-edit(prefix, index, sortAvailable, idAddition)
-    -var fieldName = `${prefix}FieldName`
-    -var direction = `${prefix}Direction`
-
-    -var fieldNameModel = `indexesTbl.${fieldName}`
-    -var directionModel = `indexesTbl.${direction}`
-
-    -var btnVisible = `tableIndexItemSaveVisible(indexesTbl, ${index})`
-    -var btnSave = `tableIndexItemSave(indexesTbl, itemIndex, ${index})`
-    -var btnVisibleAndSave = `${btnVisible} && ${btnSave}`
-
-    div(ng-if=sortAvailable)
-        .col-xs-8.col-sm-8.col-md-8
-            label.fieldSep /
-            .input-tip
-                button.select-toggle.form-control(id=`{{::'${fieldName}' + 
${idAddition}}}` ignite-on-enter-focus-move=`{{::'${direction}S' + 
${idAddition}}}` ng-model=fieldNameModel placeholder=`{{fields('${prefix}', 
${fieldNameModel}).length > 0 ? 'Choose field' : 'No fields configured'}}` 
bs-select bs-options=`item.value as item.label for item in fields('${prefix}', 
${fieldNameModel})` ng-disabled=`fields('${prefix}', ${fieldNameModel}).length 
=== 0` ignite-on-escape='tableReset(false)' tabindex='0')
-        .col-xs-4.col-sm-4.col-md-4
-            +btn-save(btnVisible, btnSave)
-            .input-tip
-                button.select-toggle.form-control(id=`{{::'${direction}' + 
${idAddition}}}` ng-model=directionModel bs-select bs-options='item.value as 
item.label for item in {{sortDirections}}' ignite-on-enter=btnVisibleAndSave 
ignite-on-escape='tableReset(false)' tabindex='0')
-    .col-xs-12(ng-if=`!(${sortAvailable})`)
-        +btn-save(btnVisible, btnSave)
-        .input-tip
-            button.select-toggle.form-control(id=`{{::'${fieldName}' + 
${idAddition}}}` ng-model=fieldNameModel placeholder=`{{fields('${prefix}', 
${fieldNameModel}).length > 0 ? 'Choose index field' : 'No fields 
configured'}}` bs-select bs-options=`item.value as item.label for item in 
fields('${prefix}', ${fieldNameModel})` ng-disabled=`fields('${prefix}', 
${fieldNameModel}).length === 0` ignite-on-escape='tableReset(false)' 
tabindex='0')
-
-.panel.panel-default(ng-form=form novalidate)
-    .panel-heading(bs-collapse-toggle)
+
+.pca-panel.pca-panel-default(ng-form=form novalidate)
+    .pca-panel-heading(bs-collapse-toggle)
         ignite-form-panel-chevron
-        label(id='query-title') Domain model for SQL query
-        ignite-form-field-tooltip.tipLabel
-            | Domain model properties for fields queries#[br]
-            | #[a(href='https://apacheignite.readme.io/docs/cache-queries' 
target='_blank') More info]
-        ignite-form-revert
-    .panel-collapse(role='tabpanel' bs-collapse-target id='query')
-        .panel-body
-            .col-sm-6
-                .content-not-available(ng-if=`${model}.queryMetadata === 
'Annotations'`)
+        .pca-panel-heading-title(id='query-title') Domain model for SQL query
+        .pca-panel-heading-description
+            | Domain model properties for fields queries. 
+            
a.link-success(href='https://apacheignite.readme.io/docs/cache-queries' 
target='_blank') More info
+    .pca-panel-collapse(role='tabpanel' bs-collapse-target id='query')
+        .pca-panel-body.pca-form-row
+            .pca-form-column-6.pc-form-grid-row
+                .content-not-available(
+                    ng-if=`${model}.queryMetadata === 'Annotations'`
+                    style='margin-top: 10px'
+                )
                     label Not available for annotated types
-                div(ng-if=`${model}.queryMetadata === 'Configuration'`)
-                    .settings-row
-                        +text('Table name:', `${model}.tableName`, 
'"tableName"', 'false', 'Enter table name', 'Table name for this query entity')
-                    div(ng-if='$ctrl.available("2.0.0")')
-                        .settings-row
-                            +text('Key field name:', `${model}.keyFieldName`, 
'"keyFieldName"', 'false', 'Enter key field name',
-                                'Key name.<br/>' +
-                                'Can be used in field list to denote the key 
as a whole')
-                        .settings-row
-                            +text('Value field name:', 
`${model}.valueFieldName`, '"valueFieldName"', 'false', 'Enter value field 
name',
-                                'Value name.<br/>' +
-                                'Can be used in field list to denote the 
entire value')
-                    .settings-row
-                        +ignite-form-group(ng-model=queryFields 
ng-form=queryFieldsForm)
-                            ignite-form-field-label(id='queryFields')
-                                | Fields
-                            ignite-form-group-tooltip
-                                | Collection of name-to-type mappings to be 
queried, in addition to indexed fields
-                            
ignite-form-group-add(ng-click='tableNewItem(queryFieldsTbl)')
-                                | Add field to query
-                            .group-content-empty(ng-if=`!((${queryFields} && 
${queryFields}.length > 0) || tableNewItemActive(queryFieldsTbl))`)
-                                | Not defined
-                            .group-content(ng-show=`(${queryFields} && 
${queryFields}.length > 0) || tableNewItemActive(queryFieldsTbl)`)
-                                table.links-edit(id='fields' 
st-table=queryFields)
-                                    tbody
-                                        tr(ng-repeat=`item in ${queryFields} 
track by $index`)
-                                            
td.col-sm-12(ng-hide='tableEditing(queryFieldsTbl, $index)')
-                                                
a.labelFormField(ng-click='tableStartEdit(backupItem, queryFieldsTbl, $index)') 
{{item.name}}  / {{item.className}}
-                                                
+btn-remove('tableRemove(backupItem, queryFieldsTbl, $index)', '"Remove path"')
-                                            
td.col-sm-12(ng-show='tableEditing(queryFieldsTbl, $index)')
-                                                
+table-pair-edit('queryFieldsTbl', 'cur', 'Field name', 'Field full class 
name', true, '{{::queryFieldsTbl.focusId + $index}}', '$index', '/')
-                                    
tfoot(ng-show='tableNewItemActive(queryFieldsTbl)')
-                                        tr
-                                            td.col-sm-12
-                                                
+table-pair-edit('queryFieldsTbl', 'new', 'Field name', 'Field full class 
name', true, '{{::queryFieldsTbl.focusId + $index}}', '-1', '/')
-                    .settings-row
-                        +ignite-form-field-dropdown('Key fields:', 
queryKeyFields, '"queryKeyFields"', false, false, true,
-                            'Select key fields', 'Configure available fields', 
'fields(\'cur\', ' + queryKeyFields + ')',
-                            'Query fields that belongs to the key.<br/>\
-                             Used to build / modify keys and values during SQL 
DML operations when no key - value classes are present on cluster nodes.'
+
+                .pc-form-grid-col-60(ng-if-start=`${model}.queryMetadata === 
'Configuration'`)
+                    +text('Table name:', `${model}.tableName`, '"tableName"', 
'false', 'Enter table name')
+
+                .pc-form-grid-col-30(ng-if-start='$ctrl.available("2.0.0")')
+                    +text('Key field name:', `${model}.keyFieldName`, 
'"keyFieldName"', 'false', 'Enter key field name',
+                        'Key name.<br/>' +
+                        'Can be used in field list to denote the key as a 
whole')
+                .pc-form-grid-col-30(ng-if-end)
+                    +text('Value field name:', `${model}.valueFieldName`, 
'"valueFieldName"', 'false', 'Enter value field name',
+                        'Value name.<br/>' +
+                        'Can be used in field list to denote the entire value')
+
+                .pc-form-grid-col-60
+                    mixin domains-query-fields
+                        .ignite-form-field
+                            +ignite-form-field__label('Fields:', '"fields"')
+                                +tooltip(`Collection of name-to-type mappings 
to be queried, in addition to indexed fields`)
+                            .ignite-form-field__control
+                                -let items = queryFields
+                                list-editable(
+                                    ng-model=items
+                                    name='queryFields'
+                                    
ng-change=`$ctrl.onQueryFieldsChange(${model})`
+                                )
+                                    list-editable-item-view
+                                        | {{ $item.name}} / {{ 
$item.className}}
+
+                                    list-editable-item-edit
+                                        - form = '$parent.form'
+                                        .pc-form-grid-row
+                                            .pc-form-grid-col-30(divider='/')
+                                                +ignite-form-field-text('Field 
name:', '$item.name', '"name"', false, true, 'Enter field name')(
+                                                    data-ignite-unique=items
+                                                    
data-ignite-unique-property='name'
+                                                    ignite-auto-focus
+                                                )
+                                                    +unique-feedback('"name"', 
'Property with such name already exists!')
+                                            .pc-form-grid-col-30
+                                                +java-class-typeahead('Field 
full class name:', `$item.className`, '"className"', '$ctrl.queryFieldTypes', 
true, true, 'Enter field full class name')(
+                                                    
ng-model-options='{allowInvalid: true}'
+                                                    
extra-valid-java-identifiers='$ctrl.queryFieldTypes'
+                                                )
+
+                                    list-editable-no-items
+                                        list-editable-add-item-button(
+                                            add-item=`$editLast((${items} = 
${items} || []).push({}))`
+                                            label-single='field to query'
+                                            label-multiple='fields'
+                                        )
+
+                    +domains-query-fields
+
+                .pc-form-grid-col-60
+                    +sane-ignite-form-field-dropdown({
+                        label: 'Key fields:',
+                        model: queryKeyFields,
+                        name: '"queryKeyFields"',
+                        multiple: true,
+                        placeholder: 'Select key fields',
+                        placeholderEmpty: 'Configure available fields',
+                        options: `$ctrl.fields('cur', ${queryKeyFields})`,
+                        tip: 'Query fields that belongs to the key.<br/>\
+                         Used to build / modify keys and values during SQL DML 
operations when no key - value classes are present on cluster nodes.'
+                    })
+                .pc-form-grid-col-60
+                    mixin domains-query-aliases
+                        .ignite-form-field
+                            +ignite-form-field__label('Aliases:', '"aliases"')
+                                +tooltip(`Mapping from full property name in 
dot notation to an alias that will be used as SQL column name<br />
+                                    For example: "parent.name" as 
"parentName"`)
+                            .ignite-form-field__control
+                                -let items = queryAliases
+
+                                list-editable(ng-model=items 
name='queryAliases')
+                                    list-editable-item-view
+                                        | {{ $item.field }} &rarr; {{ 
$item.alias }}
+
+                                    list-editable-item-edit
+                                        - form = '$parent.form'
+                                        .pc-form-grid-row
+                                            .pc-form-grid-col-30(divider='/')
+                                                +ignite-form-field-text('Field 
name', '$item.field', '"field"', false, true, 'Enter field name')(
+                                                    data-ignite-unique=items
+                                                    
data-ignite-unique-property='field'
+                                                    ignite-auto-focus
+                                                )
+                                                    
+unique-feedback('"field"', 'Such field already exists!')
+                                            .pc-form-grid-col-30
+                                                +ignite-form-field-text('Field 
alias', '$item.alias', '"alias"', false, true, 'Enter field alias')
+
+                                    list-editable-no-items
+                                        list-editable-add-item-button(
+                                            add-item=`$editLast((${items} = 
${items} || []).push({}))`
+                                            label-single='alias to query'
+                                            label-multiple='aliases'
+                                        )
+
+                    +domains-query-aliases
+
+                .pc-form-grid-col-60(ng-if-end)
+                    .ignite-form-field
+                        +ignite-form-field__label('Indexes:', '"indexes"')
+                        .ignite-form-field__control
+                            list-editable(
+                                ng-model=queryIndexes
+                                ng-model-options='{allowInvalid: true}'
+                                name='queryIndexes'
+                                ui-validate=`{
+                                    complete: 
'$ctrl.Models.queryIndexes.complete($value)',
+                                    fieldsExist: 
'$ctrl.Models.queryIndexes.fieldsExist($value, ${queryFields})',
+                                    indexFieldsHaveUniqueNames: 
'$ctrl.Models.queryIndexes.indexFieldsHaveUniqueNames($value)'
+                                }`
+                                ui-validate-watch=`"[${queryIndexes}, 
${queryFields}]"`
+                                ui-validate-watch-object-equality='true'
+                            )
+                                list-editable-item-view(item-name='queryIndex')
+                                    div {{ queryIndex.name }} [{{ 
queryIndex.indexType }}]
+                                    div(ng-repeat='field in queryIndex.fields 
track by field._id')
+                                        span {{ field.name }}
+                                        span(ng-if='queryIndex.indexType == 
"SORTED"')
+                                            |  / {{ field.direction ? 'ASC' : 
'DESC'}}
+
+                                list-editable-item-edit(item-name='queryIndex')
+                                    .pc-form-grid-row
+                                        .pc-form-grid-col-30(divider='/')
+                                            +sane-ignite-form-field-text({
+                                                label: 'Index name:',
+                                                model: 'queryIndex.name',
+                                                name: '"name"',
+                                                required: true,
+                                                placeholder: 'Enter index name'
+                                            })(
+                                                ignite-unique=queryIndexes
+                                                ignite-unique-property='name'
+                                                
ignite-form-field-input-autofocus='true'
+                                            )
+                                                +unique-feedback(_, 'Such 
index already exists!')
+                                        .pc-form-grid-col-30
+                                            +sane-ignite-form-field-dropdown({
+                                                label: 'Index type:',
+                                                model: `queryIndex.indexType`,
+                                                name: '"indexType"',
+                                                required: true,
+                                                placeholder: 'Select index 
type',
+                                                options: 
'::$ctrl.Models.indexType.values'
+                                            })
+                                        .pc-form-grid-col-60
+                                            .ignite-form-field
+                                                
+ignite-form-field__label('Index fields:', '"indexFields"', true)
+                                                .ignite-form-field__control
+                                                    list-editable(
+                                                        
ng-model='queryIndex.fields'
+                                                        
ng-model-options='{allowInvalid: true}'
+                                                        name='indexFields'
+                                                        ng-required='true'
+                                                    )
+                                                        
list-editable-item-view(item-name='indexField')
+                                                            | {{ 
indexField.name }} 
+                                                            
span(ng-if='queryIndex.indexType === "SORTED"')
+                                                                |  / {{ 
indexField.direction ? "ASC" : "DESC" }}
+
+                                                        
list-editable-item-edit(item-name='indexField')
+                                                            .pc-form-grid-row
+                                                                
.pc-form-grid-col-60
+                                                                    
+sane-ignite-form-field-dropdown({
+                                                                        label: 
'Index field:',
+                                                                        model: 
'indexField.name',
+                                                                        name: 
'"indexName"',
+                                                                        
placeholder: `{{ ${queryFields}.length > 0 ? 'Choose index field' : 'No fields 
configured' }}`,
+                                                                        
options: queryFields
+                                                                    })(
+                                                                        
bs-options=`queryField.name as queryField.name for queryField in ${queryFields}`
+                                                                        
ng-disabled=`${queryFields}.length === 0`
+                                                                        
ng-model-options='{allowInvalid: true}'
+                                                                        
ignite-unique='queryIndex.fields'
+                                                                        
ignite-unique-property='name'
+                                                                        
ignite-auto-focus
+                                                                    )
+                                                                        
+unique-feedback(_, 'Such field already exists!')
+                                                                
.pc-form-grid-col-60(
+                                                                    
ng-if='queryIndex.indexType === "SORTED"'
+                                                                )
+                                                                    
+sane-ignite-form-field-dropdown({
+                                                                        label: 
'Sort direction:',
+                                                                        model: 
'indexField.direction',
+                                                                        name: 
'"indexDirection"',
+                                                                        
required: true,
+                                                                        
options: '::$ctrl.Models.indexSortDirection.values'
+                                                                    })
+                                                        list-editable-no-items
+                                                            
list-editable-add-item-button(
+                                                                
add-item=`$edit($ctrl.Models.addIndexField(queryIndex.fields))`
+                                                                
label-single='field to index'
+                                                                
label-multiple='fields in index'
+                                                            )
+                                                .ignite-form-field__errors(
+                                                    
ng-messages=`$form.indexFields.$error`
+                                                    
ng-show=`$form.indexFields.$invalid`
+                                                )
+                                                    +form-field-feedback(_, 
'required', 'Index fields should be configured')
+
+                                list-editable-no-items
+                                    list-editable-add-item-button(
+                                        
add-item=`$edit($ctrl.Models.addIndex(${model}))`
+                                        label-single='index'
+                                        label-multiple='fields'
+                                    )
+                        .ignite-form-field__errors(
+                            ng-messages=`query.queryIndexes.$error`
+                            ng-show=`query.queryIndexes.$invalid`
                         )
-                    .settings-row
-                        +ignite-form-group(ng-model=queryAliases 
ng-form=queryAliasesForm)
-                            ignite-form-field-label
-                                | Aliases
-                            ignite-form-group-tooltip
-                                | Mapping from full property name in dot 
notation to an alias that will be used as SQL column name
-                                | For example: "parent.name" as "parentName"
-                            
ignite-form-group-add(ng-click='tableNewItem(aliasesTbl)')
-                                | Add alias to query
-                            .group-content-empty(ng-if=`!((${queryAliases} && 
${queryAliases}.length > 0) || tableNewItemActive(aliasesTbl))`)
-                                | Not defined
-                            .group-content(ng-show=`(${queryAliases} && 
${queryAliases}.length > 0) || tableNewItemActive(aliasesTbl)`)
-                                table.links-edit(id='aliases' 
st-table=queryAliases)
-                                    tbody
-                                        tr(ng-repeat=`item in ${queryAliases} 
track by $index`)
-                                            
td.col-sm-12(ng-hide='tableEditing(aliasesTbl, $index)')
-                                                
a.labelFormField(ng-click='tableStartEdit(backupItem, aliasesTbl, $index)') 
{{item.field}} &rarr; {{item.alias}}
-                                                
+btn-remove('tableRemove(backupItem, aliasesTbl, $index)', '"Remove alias"')
-                                            
td.col-sm-12(ng-show='tableEditing(aliasesTbl, $index)')
-                                                +table-pair-edit('aliasesTbl', 
'cur', 'Field name', 'Field Alias', false, '{{::aliasesTbl.focusId + $index}}', 
'$index', '&rarr;')
-                                    
tfoot(ng-show='tableNewItemActive(aliasesTbl)')
-                                        tr
-                                            td.col-sm-12
-                                                +table-pair-edit('aliasesTbl', 
'new', 'Field name', 'Field Alias', false, '{{::aliasesTbl.focusId + $index}}', 
'-1', '&rarr;')
-                    .settings-row(ng-init='indexesTbl={type: "table-indexes", 
model: "indexes", focusId: "IndexName", ui: "table-indexes"}')
-                        +ignite-form-group(ng-model=queryIndexes 
ng-form=queryIndexesForm)
-                            ignite-form-field-label
-                                | Indexes
-                            ignite-form-group-tooltip
-                                | Collection of indexes
-                            
ignite-form-group-add(ng-click='tableNewItem(indexesTbl)')
-                                | Add new index
-                            .group-content-empty(id='indexes-add' 
ng-show=`!((${queryIndexes} && ${queryIndexes}.length > 0) || 
tableNewItemActive(indexesTbl))`)
-                                | Not defined
-                            .group-content(ng-show=`(${queryIndexes} && 
${queryIndexes}.length > 0) || tableNewItemActive(indexesTbl)`)
-                                -var btnVisibleAndSave = 
'tableIndexSaveVisible(indexesTbl, $index) && tableIndexSave(indexesTbl, 
$index)'
-
-                                table.links-edit(st-table=queryIndexes 
ng-init='newDirection = false')
-                                    tbody
-                                        tr(ng-repeat=`item in ${queryIndexes} 
track by $index`)
-                                            td
-                                                
.col-sm-12(ng-hide='tableEditing(indexesTbl, $index)')
-                                                    
a.labelFormField(id='indexes{{$index}}' ng-click='tableStartEdit(backupItem, 
indexesTbl, $index)') {{$index + 1}}) {{item.name}} [{{item.indexType}}]
-                                                    
+btn-remove('tableRemove(backupItem, indexesTbl, $index)', '"Remove index"')
-                                                    
+btn-add('tableIndexNewItem(indexesTbl, $index)', '"Add new field to index"')
-                                                
div(ng-show='tableEditing(indexesTbl, $index)')
-                                                    .col-sm-7
-                                                        label.fieldSep /
-                                                        .input-tip
-                                                            
input.form-control(id='curIndexName{{$index}}' type='text' 
ignite-on-enter-focus-move='curIndexType{{$index}}' 
ng-model='indexesTbl.curIndexName' placeholder='Index name' 
ignite-on-enter=btnVisibleAndSave ignite-on-escape='tableReset(false)')
-                                                    .col-sm-5
-                                                        
+btn-save('tableIndexSaveVisible(indexesTbl, $index)', 
'tableIndexSave(indexesTbl, $index)')
-                                                        .input-tip
-                                                            
button.select-toggle.form-control(id='curIndexType{{$index}}' bs-select 
ng-model='indexesTbl.curIndexType' data-placeholder='Select index type' 
bs-options='item.value as item.label for item in indexType' tabindex='0' 
ignite-on-enter=btnVisibleAndSave ignite-on-escape='tableReset(false)')
-                                                .margin-left-dflt
-                                                    
table.links-edit-sub(st-table='item.fields' ng-init='itemIndex = $index')
-                                                        tbody
-                                                            
tr(ng-repeat='itemItem in item.fields track by $index')
-                                                                td
-                                                                    
div(ng-hide='tableIndexItemEditing(indexesTbl, itemIndex, $index)')
-                                                                        
a.labelFormField(ng-if='item.indexType == "SORTED"' 
ng-click='tableIndexItemStartEdit(indexesTbl, itemIndex, $index)') {{$index + 
1}}) {{itemItem.name}} / {{itemItem.direction ? "ASC" : "DESC"}}
-                                                                        
a.labelFormField(ng-if='item.indexType != "SORTED"' 
ng-click='tableIndexItemStartEdit(indexesTbl, itemIndex, $index)') {{$index + 
1}}) {{itemItem.name}}
-                                                                        
+btn-remove('tableRemoveIndexItem(item, $index)', '"Remove field from index"')
-                                                                    
div(ng-show='tableIndexItemEditing(indexesTbl, itemIndex, $index)')
-                                                                        
+table-index-item-edit('cur', '$index', 'item.indexType == "SORTED"', 
'itemIndex + "-" + $index')
-                                                        
tfoot(ng-show='tableIndexNewItemActive(indexesTbl, itemIndex)')
-                                                            
tr(style='padding-left: 18px')
-                                                                td
-                                                                    
+table-index-item-edit('new', '-1', 'item.indexType == "SORTED"', 'itemIndex')
-                                    
tfoot(ng-show='tableNewItemActive(indexesTbl)')
-                                        tr
-                                            td
-                                                .col-sm-7
-                                                    .fieldSep /
-                                                    .input-tip
-                                                        
input#newIndexName.form-control(type='text' 
ignite-on-enter-focus-move='newIndexType' ng-model='indexesTbl.newIndexName' 
placeholder='Index name' ignite-on-enter='tableIndexSaveVisible(indexesTbl, -1) 
&& tableIndexSave(indexesTbl, -1)' ignite-on-escape='tableReset(false)')
-                                                .col-sm-5
-                                                    
+btn-save('tableIndexSaveVisible(indexesTbl, -1)', 'tableIndexSave(indexesTbl, 
-1)')
-                                                    .input-tip
-                                                        
button#newIndexType.select-toggle.form-control(bs-select 
ng-model='indexesTbl.newIndexType' data-placeholder='Select index type' 
bs-options='item.value as item.label for item in indexType' tabindex='0' 
ignite-on-enter=btnVisibleAndSave ignite-on-escape='tableReset(false)')
-            .col-sm-6
+                            +form-field-feedback(_, 'complete', 'Some indexes 
are incomplete')
+                            +form-field-feedback(_, 'fieldsExist', 'Some 
indexes use unknown fields')
+                            +form-field-feedback(_, 
'indexFieldsHaveUniqueNames', 'Each query index field name should be unique')
+
+            .pca-form-column-6
                 +preview-xml-java(model, 'domainModelQuery')

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/domains/store.pug
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/modules/states/configuration/domains/store.pug
 
b/modules/web-console/frontend/app/modules/states/configuration/domains/store.pug
index 7afb8e5..eb0f9b7 100644
--- 
a/modules/web-console/frontend/app/modules/states/configuration/domains/store.pug
+++ 
b/modules/web-console/frontend/app/modules/states/configuration/domains/store.pug
@@ -20,108 +20,107 @@ include /app/helpers/jade/mixins
 -var model = 'backupItem'
 -var keyFields = `${model}.keyFields`
 -var valueFields = `${model}.valueFields`
--var keyFieldsForm = 'storeKeyFields'
--var valueFieldsForm = 'storeValueFields'
 
-//- LEGACY mixin for LEGACY db fields tables.
-mixin table-db-field-edit(tbl, prefix, focusId, index)
-    -var databaseName = `${prefix}DatabaseFieldName`
-    -var databaseType = `${prefix}DatabaseFieldType`
-    -var javaName = `${prefix}JavaFieldName`
-    -var javaType = `${prefix}JavaFieldType`
+mixin list-db-field-edit({ items, itemName, itemsName })
+    list-editable(
+        ng-model=items
+        ng-model-options='{allowInvalid: true}'
+        ui-validate=`{
+            dbFieldUnique: '$ctrl.Models.storeKeyDBFieldsUnique($value)'
+        }`
+        ui-validate-watch=`"${items}"`
+        ui-validate-watch-object-equality='true'
+    )&attributes(attributes)
+        list-editable-item-view
+            | {{ $item.databaseFieldName }} / {{ $item.databaseFieldType }} / 
{{ $item.javaFieldName }} / {{ $item.javaFieldType }}
 
-    -var databaseNameModel = `${tbl}.${databaseName}`
-    -var databaseTypeModel = `${tbl}.${databaseType}`
-    -var javaNameModel = `${tbl}.${javaName}`
-    -var javaTypeModel = `${tbl}.${javaType}`
+        list-editable-item-edit
+            .pc-form-grid-row
+                .pc-form-grid-col-30(divider='/')
+                    +sane-ignite-form-field-text({
+                        label: 'DB name:',
+                        model: '$item.databaseFieldName',
+                        name: '"databaseFieldName"',
+                        required: true,
+                        placeholder: 'Enter DB name'
+                    })(
+                        ng-model-options='{allowInvalid: true}'
+                        ignite-auto-focus
+                        ignite-unique=items
+                        ignite-unique-property='databaseFieldName'
+                    )
+                        +unique-feedback(_, 'DB name should be unique')
+                .pc-form-grid-col-30
+                    +dropdown-required('DB type:', '$item.databaseFieldType', 
'"databaseFieldType"', true, true, 'Choose DB type', 'supportedJdbcTypes')
+                .pc-form-grid-col-30(divider='/')
+                    +sane-ignite-form-field-text({
+                        label: 'Java name:',
+                        model: '$item.javaFieldName',
+                        name: '"javaFieldName"',
+                        required: true,
+                        placeholder: 'Enter Java name'
+                    })(
+                        ng-model-options='{allowInvalid: true}'
+                        ignite-unique=items
+                        ignite-unique-property='javaFieldName'
+                    )
+                        +unique-feedback(_, 'Java name should be unique')
+                .pc-form-grid-col-30
+                    +dropdown-required('Java type:', '$item.javaFieldType', 
'"javaFieldType"', true, true, 'Choose Java type', 'supportedJavaTypes')
 
-    -var databaseNameId = `${databaseName}${focusId}`
-    -var databaseTypeId = `${databaseType}${focusId}`
-    -var javaNameId = `${javaName}${focusId}`
-    -var javaTypeId = `${javaType}${focusId}`
+        list-editable-no-items
+            list-editable-add-item-button(
+                add-item=`$editLast((${items} = ${items} || []).push({}))`
+                label-single=itemName
+                label-multiple=itemsName
+            )
 
-    .col-xs-3.col-sm-3.col-md-3
-        .fieldSep /
-        .input-tip
-            input.form-control(id=databaseNameId 
ignite-on-enter-focus-move=databaseTypeId type='text' 
ng-model=databaseNameModel placeholder='DB name' 
ignite-on-enter=`${javaNameModel} = ${javaNameModel} ? ${javaNameModel} : 
${databaseNameModel}` ignite-on-escape='tableReset(false)')
-    .col-xs-3.col-sm-3.col-md-3
-        .fieldSep /
-        .input-tip
-            button.select-toggle.form-control(id=databaseTypeId 
ignite-on-enter-focus-move=javaNameId ng-model=databaseTypeModel 
data-placeholder='DB type' ng-class=`{placeholder: !${databaseTypeModel}}` 
bs-select bs-options='item.value as item.label for item in 
{{supportedJdbcTypes}}' ignite-on-escape='tableReset(false)' tabindex='0')
-    .col-xs-3.col-sm-3.col-md-3
-        .fieldSep /
-        .input-tip
-            input.form-control(id=javaNameId 
ignite-on-enter-focus-move=javaTypeId type='text' ng-model=javaNameModel 
placeholder='Java name' ignite-on-escape='tableReset(false)')
-    .col-xs-3.col-sm-3.col-md-3
-        -var btnVisible = `tableDbFieldSaveVisible(${tbl}, ${index})`
-        -var btnSave = `tableDbFieldSave(${tbl}, ${index})`
-        -var btnVisibleAndSave = `${btnVisible} && ${btnSave}`
-
-        +btn-save(btnVisible, btnSave)
-        .input-tip
-            button.select-toggle.form-control(id=javaTypeId 
ng-model=javaTypeModel data-placeholder='Java type' ng-class=`{placeholder: 
!${javaTypeModel}}` bs-select bs-options='item.value as item.label for item in 
{{supportedJavaTypes}}' ignite-on-enter=btnVisibleAndSave 
ignite-on-escape='tableReset(false)' tabindex='0')
-
-.panel.panel-default(ng-form=form novalidate)
-    .panel-heading(bs-collapse-toggle='' ng-click=`ui.loadPanel('${form}')`)
+.pca-panel.pca-panel-default(ng-form=form novalidate)
+    .pca-panel-heading(bs-collapse-toggle='' 
ng-click=`ui.loadPanel('${form}')`)
         ignite-form-panel-chevron
-        label(id='store-title') Domain model for cache store
-        ignite-form-field-tooltip.tipLabel
-            | Domain model properties for binding database with cache via POJO 
cache store#[br]
-            | #[a(href="https://apacheignite.readme.io/docs/3rd-party-store"; 
target="_blank") More info]
-        ignite-form-revert
-    .panel-collapse(role='tabpanel' bs-collapse-target id=`${form}`)
-        .panel-body(ng-if=`ui.isPanelLoaded('${form}')`)
-            .col-sm-6
-                .settings-row
+        .pca-panel-heading-title(id='store-title') Domain model for cache store
+        .pca-panel-heading-description
+            | Domain model properties for binding database with cache via POJO 
cache store. 
+            
a.link-success(href="https://apacheignite.readme.io/docs/3rd-party-store"; 
target="_blank") More info
+    .pca-panel-collapse(role='tabpanel' bs-collapse-target id=`${form}`)
+        .pca-panel-body.pca-form-row(ng-if=`ui.isPanelLoaded('${form}')`)
+            .pca-form-column-6.pc-form-grid-row
+                .pc-form-grid-col-30
                     +text('Database schema:', model + '.databaseSchema', 
'"databaseSchema"', 'false', 'Input DB schema name', 'Schema name in database')
-                .settings-row
+                .pc-form-grid-col-30
                     +text('Database table:', model + '.databaseTable', 
'"databaseTable"', 'false', 'Input DB table name', 'Table name in database')
-                .settings-row(ng-init='keysTbl={type: "table-db-fields", 
model: "keyFields", focusId: "KeyField", ui: "table-db-fields"}')
-                    +ignite-form-group(ng-form=keyFieldsForm 
ng-model=keyFields)
-                        ignite-form-field-label(id='keyFields')
-                            | Key fields
-                        ignite-form-group-tooltip
-                            | Collection of key fields descriptions for 
CacheJdbcPojoStore
-                        ignite-form-group-add(ng-click='tableNewItem(keysTbl)')
-                            | Add key field
-                        .group-content-empty(ng-show=`!((${keyFields} && 
${keyFields}.length > 0) || tableNewItemActive(keysTbl))`) Not defined
-                        .group-content(ng-show=`(${keyFields} && 
${keyFields}.length > 0) || tableNewItemActive(keysTbl)`)
-                            table.links-edit(st-table=keyFields)
-                                tbody
-                                    tr(ng-repeat=`item in ${keyFields} track 
by $index`)
-                                        td
-                                            div(ng-hide='tableEditing(keysTbl, 
$index)')
-                                                
a.labelFormField(ng-click='tableStartEdit(backupItem, keysTbl, $index)') 
{{$index + 1}}) {{item.databaseFieldName}} / {{item.databaseFieldType}} / 
{{item.javaFieldName}} / {{item.javaFieldType}}
-                                                
+btn-remove('tableRemove(backupItem, keysTbl, $index)', '"Remove key field"')
-                                            div(ng-if='tableEditing(keysTbl, 
$index)')
-                                                
+table-db-field-edit('keysTbl', 'cur', '{{::keysTbl.focusId + $index}}', 
'$index')
-                                tfoot(ng-show='tableNewItemActive(keysTbl)')
-                                    tr
-                                        td
-                                            +table-db-field-edit('keysTbl', 
'new', 'KeyField', '-1')
-                .settings-row(ng-init='valuesTbl={type: "table-db-fields", 
model: "valueFields", focusId: "ValueField", ui: "table-db-fields"}')
-                    +ignite-form-group(ng-form=valueFieldsForm 
ng-model=valueFields)
-                        ignite-form-field-label(id='valueFields')
-                            | Value fields
-                        ignite-form-group-tooltip
-                            | Collection of value fields descriptions for 
CacheJdbcPojoStore
-                        
ignite-form-group-add(ng-click='tableNewItem(valuesTbl)')
-                            | Add value field
-                        .group-content-empty(ng-show=`!((${valueFields} && 
${valueFields}.length > 0) || tableNewItemActive(valuesTbl))`) Not defined
-                        .group-content(ng-show=`(${valueFields} && 
${valueFields}.length > 0) || tableNewItemActive(valuesTbl)`)
-                            table.links-edit(st-table=valueFields)
-                                tbody
-                                    tr(ng-repeat=`item in ${valueFields} track 
by $index`)
-                                        td
-                                            
div(ng-hide='tableEditing(valuesTbl, $index)')
-                                                
a.labelFormField(ng-click='tableStartEdit(backupItem, valuesTbl, $index)') 
{{$index + 1}}) {{item.databaseFieldName}} / {{item.databaseFieldType}} / 
{{item.javaFieldName}} / {{item.javaFieldType}}
-                                                
+btn-remove('tableRemove(backupItem, valuesTbl, $index)', '"Remove key field"')
-                                            div(ng-if='tableEditing(valuesTbl, 
$index)')
-                                                
+table-db-field-edit('valuesTbl', 'cur', '{{::valuesTbl.focusId + $index}}', 
'$index')
-                                tfoot(ng-show='tableNewItemActive(valuesTbl)')
-                                    tr
-                                        td
-                                            +table-db-field-edit('valuesTbl', 
'new', 'ValueField', '-1')
-            .col-sm-6
+                .pc-form-grid-col-60
+                    .ignite-form-field
+                        +ignite-form-field__label('Key fields:', '"keyFields"')
+                            +tooltip(`Collection of key fields descriptions 
for CacheJdbcPojoStore`)
+                        .ignite-form-field__control
+                            +list-db-field-edit({
+                                items: keyFields,
+                                itemName: 'key field',
+                                itemsName: 'key fields'
+                            })(name='keyFields')
+                        .ignite-form-field__errors(
+                            ng-messages=`store.keyFields.$error`
+                            ng-show=`store.keyFields.$invalid`
+                        )
+                            +form-field-feedback(_, 'dbFieldUnique', 'Each key 
field DB name and Java name should be unique')
+
+                .pc-form-grid-col-60
+                    .ignite-form-field
+                        +ignite-form-field__label('Value fields:', 
'"valueFields"')
+                            +tooltip(`Collection of value fields descriptions 
for CacheJdbcPojoStore`)
+                        .ignite-form-field__control
+                            +list-db-field-edit({
+                                items: valueFields,
+                                itemName: 'value field',
+                                itemsName: 'value fields'
+                            })(name='valueFields')
+                        .ignite-form-field__errors(
+                            ng-messages=`store.valueFields.$error`
+                            ng-show=`store.valueFields.$invalid`
+                        )
+                            +form-field-feedback(_, 'dbFieldUnique', 'Each 
value field DB name and Java name should be unique')
+
+            .pca-form-column-6
                 +preview-xml-java(model, 'domainStore')
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/igfs/dual.pug
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/modules/states/configuration/igfs/dual.pug 
b/modules/web-console/frontend/app/modules/states/configuration/igfs/dual.pug
index 9b49be3..b05caab 100644
--- 
a/modules/web-console/frontend/app/modules/states/configuration/igfs/dual.pug
+++ 
b/modules/web-console/frontend/app/modules/states/configuration/igfs/dual.pug
@@ -19,17 +19,16 @@ include /app/helpers/jade/mixins
 -var form = 'dualMode'
 -var model = 'backupItem'
 
-.panel.panel-default(ng-show='$ctrl.available(["1.0.0", "2.0.0"])' 
ng-form=form novalidate)
-    .panel-heading(bs-collapse-toggle='' ng-click=`ui.loadPanel('${form}')`)
+.pca-panel.pca-panel-default(ng-show='$ctrl.available(["1.0.0", "2.0.0"])' 
ng-form=form novalidate)
+    .pca-panel-heading(bs-collapse-toggle='' 
ng-click=`ui.loadPanel('${form}')`)
         ignite-form-panel-chevron
-        label Dual mode
-        ignite-form-field-tooltip.tipLabel
-            | IGFS supports dual-mode that allows it to work as either a 
standalone file system in Hadoop cluster, or work in tandem with HDFS, 
providing a primary caching layer for the secondary HDFS#[br]
-            | As a caching layer it provides highly configurable read-through 
and write-through behaviour
-        ignite-form-revert
-    .panel-collapse(role='tabpanel' bs-collapse-target id=`${form}`)
-        .panel-body(ng-if=`$ctrl.available(["1.0.0", "2.0.0"]) && 
ui.isPanelLoaded('${form}')`)
-            .col-sm-6
+        .pca-panel-heading-title Dual mode
+        .pca-panel-heading-description
+            | IGFS supports dual-mode that allows it to work as either a 
standalone file system in Hadoop cluster, or work in tandem with HDFS, 
providing a primary caching layer for the secondary HDFS.
+            | As a caching layer it provides highly configurable read-through 
and write-through behaviour.
+    .pca-panel-collapse(role='tabpanel' bs-collapse-target id=`${form}`)
+        .pca-panel-body.pca-form-row(ng-if=`$ctrl.available(["1.0.0", 
"2.0.0"]) && ui.isPanelLoaded('${form}')`)
+            .pca-form-column-6
                 .settings-row
                     +number('Maximum pending puts size:', 
`${model}.dualModeMaxPendingPutsSize`, '"dualModeMaxPendingPutsSize"', 'true', 
'0', 'Number.MIN_SAFE_INTEGER',
                         'Maximum amount of pending data read from the 
secondary file system and waiting to be written to data cache<br/>\
@@ -38,5 +37,5 @@ include /app/helpers/jade/mixins
                     +java-class('Put executor service:', 
`${model}.dualModePutExecutorService`, '"dualModePutExecutorService"', 'true', 
'false', 'DUAL mode put operation executor service')
                 .settings-row
                     +checkbox('Put executor service shutdown', 
`${model}.dualModePutExecutorServiceShutdown`, 
'"dualModePutExecutorServiceShutdown"', 'DUAL mode put operation executor 
service shutdown flag')
-            .col-sm-6
+            .pca-form-column-6
                 +preview-xml-java(model, 'igfsDualMode')

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/igfs/fragmentizer.pug
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/modules/states/configuration/igfs/fragmentizer.pug
 
b/modules/web-console/frontend/app/modules/states/configuration/igfs/fragmentizer.pug
index b112697..7d5052e 100644
--- 
a/modules/web-console/frontend/app/modules/states/configuration/igfs/fragmentizer.pug
+++ 
b/modules/web-console/frontend/app/modules/states/configuration/igfs/fragmentizer.pug
@@ -19,25 +19,22 @@ include /app/helpers/jade/mixins
 -var form = 'fragmentizer'
 -var model = 'backupItem'
 
-.panel.panel-default(ng-form=form novalidate)
-    .panel-heading(bs-collapse-toggle='' ng-click=`ui.loadPanel('${form}')`)
+.pca-panel.pca-panel-default(ng-form=form novalidate)
+    .pca-panel-heading(bs-collapse-toggle='' 
ng-click=`ui.loadPanel('${form}')`)
         ignite-form-panel-chevron
-        label Fragmentizer
-        ignite-form-field-tooltip.tipLabel
-            | Fragmentizer settings
-        ignite-form-revert
-    .panel-collapse(role='tabpanel' bs-collapse-target id=`${form}`)
-        .panel-body(ng-if=`ui.isPanelLoaded('${form}')`)
-            .col-sm-6
+        .pca-panel-heading-title Fragmentizer
+    .pca-panel-collapse(role='tabpanel' bs-collapse-target id=`${form}`)
+        .pca-panel-body.pca-form-row(ng-if=`ui.isPanelLoaded('${form}')`)
+            .pca-form-column-6.pc-form-grid-row
                 -var enabled = `${model}.fragmentizerEnabled`
 
-                .settings-row
+                .pc-form-grid-col-60
                     +checkbox('Enabled', enabled, '"fragmentizerEnabled"', 
'Fragmentizer enabled flag')
-                .settings-row
+                .pc-form-grid-col-30
                     +number('Concurrent files:', 
`${model}.fragmentizerConcurrentFiles`, '"fragmentizerConcurrentFiles"', 
enabled, '0', '0', 'Number of files to process concurrently by fragmentizer')
-                .settings-row
+                .pc-form-grid-col-30
                     +number('Throttling block length:', 
`${model}.fragmentizerThrottlingBlockLength`, 
'"fragmentizerThrottlingBlockLength"', enabled, '16777216', '1', 'Length of 
file chunk to transmit before throttling is delayed')
-                .settings-row
+                .pc-form-grid-col-60
                     +number('Throttling delay:', 
`${model}.fragmentizerThrottlingDelay`, '"fragmentizerThrottlingDelay"', 
enabled, '200', '0', 'Delay in milliseconds for which fragmentizer is paused')
-            .col-sm-6
+            .pca-form-column-6
                 +preview-xml-java(model, 'igfsFragmentizer')

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/igfs/general.pug
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/modules/states/configuration/igfs/general.pug
 
b/modules/web-console/frontend/app/modules/states/configuration/igfs/general.pug
index e002d36..9f65f41 100644
--- 
a/modules/web-console/frontend/app/modules/states/configuration/igfs/general.pug
+++ 
b/modules/web-console/frontend/app/modules/states/configuration/igfs/general.pug
@@ -19,39 +19,57 @@ include /app/helpers/jade/mixins
 -var form = 'general'
 -var model = 'backupItem'
 
-.panel.panel-default(ng-form=form novalidate)
-    .panel-heading(bs-collapse-toggle)
+.pca-panel(ng-form=form novalidate)
+    .pca-panel-heading(bs-collapse-toggle)
         ignite-form-panel-chevron
-        label General
-        ignite-form-field-tooltip.tipLabel
-            | General IGFS configuration#[br]
-            | 
#[a(href="https://apacheignite-fs.readme.io/docs/in-memory-file-system"; 
target="_blank") More info]
-        ignite-form-revert
-    .panel-collapse(role='tabpanel' bs-collapse-target id='general')
-        .panel-body
-            .col-sm-6
-                .settings-row
-                    +text('Name:', `${model}.name`, '"igfsName"', 'true', 
'Input name', 'IGFS name')
-                .settings-row
-                    +clusters(model, 'Associate clusters with the current 
IGFS')
-                .settings-row
-                    +dropdown('IGFS mode:', `${model}.defaultMode`, 
'"defaultMode"', 'true', 'DUAL_ASYNC',
-                    '[\
-                        {value: "PRIMARY", label: "PRIMARY"},\
-                        {value: "PROXY", label: "PROXY"},\
-                        {value: "DUAL_SYNC", label: "DUAL_SYNC"},\
-                        {value: "DUAL_ASYNC", label: "DUAL_ASYNC"}\
-                    ]',
-                    'Mode to specify how IGFS interacts with Hadoop file 
system\
-                    <ul>\
-                        <li>PRIMARY - in this mode IGFS will not delegate to 
secondary Hadoop file system and will cache all the files in memory only</li>\
-                        <li>PROXY - in this mode IGFS will not cache any files 
in memory and will only pass them through to secondary file system</li>\
-                        <li>DUAL_SYNC - in this mode IGFS will cache files 
locally and also <b>synchronously</b> write them through to secondary file 
system</li>\
-                        <li>DUAL_ASYNC - in this mode IGFS will cache files 
locally and also <b> asynchronously </b> write them through to secondary file 
system</li>\
-                    </ul>')
-                .settings-row
-                    +number('Group size:', `${model}.affinnityGroupSize`, 
'"affinnityGroupSize"', 'true', '512', '1',
-                        'Size of the group in blocks<br/>\
-                        Required for construction of affinity mapper in IGFS 
data cache')
-            .col-sm-6
+        .pca-panel-heading-title General
+        .pca-panel-heading-description
+            | General IGFS configuration. 
+            
a.link-success(href="https://apacheignite-fs.readme.io/docs/in-memory-file-system";
 target="_blank") More info
+    .pca-panel-collapse(role='tabpanel' bs-collapse-target id='general')
+        .pca-panel-body.pca-form-row
+            .pca-form-column-6.pc-form-grid-row
+                .pc-form-grid-col-60
+                    +sane-ignite-form-field-text({
+                        label: 'Name:',
+                        model: `${model}.name`,
+                        name: '"igfsName"',
+                        placeholder: 'Input name',
+                        required: true
+                    })(
+                        ignite-unique='$ctrl.igfss'
+                        ignite-unique-property='name'
+                        ignite-unique-skip=`["_id", ${model}]`
+                    )
+                        +unique-feedback(`${model}.name`, 'IGFS name should be 
unique.')
+                .pc-form-grid-col-30
+                    +sane-ignite-form-field-dropdown({
+                        label: 'IGFS mode:',
+                        model: `${model}.defaultMode`,
+                        name: '"defaultMode"',
+                        placeholder: '{{::$ctrl.IGFSs.defaultMode.default}}',
+                        options: '{{::$ctrl.IGFSs.defaultMode.values}}',
+                        tip: `
+                        Mode to specify how IGFS interacts with Hadoop file 
system
+                        <ul>
+                            <li>PRIMARY - in this mode IGFS will not delegate 
to secondary Hadoop file system and will cache all the files in memory only</li>
+                            <li>PROXY - in this mode IGFS will not cache any 
files in memory and will only pass them through to secondary file system</li>
+                            <li>DUAL_SYNC - in this mode IGFS will cache files 
locally and also <b>synchronously</b> write them through to secondary file 
system</li>
+                            <li>DUAL_ASYNC - in this mode IGFS will cache 
files locally and also <b> asynchronously </b> write them through to secondary 
file system</li>
+                        </ul>
+                        `
+                    })
+                .pc-form-grid-col-30
+                    +sane-ignite-form-field-number({
+                        label: 'Group size:',
+                        model: `${model}.affinnityGroupSize`,
+                        name: '"affinnityGroupSize"',
+                        placeholder: 
'{{::$ctrl.IGFSs.affinnityGroupSize.default}}',
+                        min: '{{::$ctrl.IGFSs.affinnityGroupSize.min}}',
+                        tip: `
+                            Size of the group in blocks<br/>
+                            Required for construction of affinity mapper in 
IGFS data cache
+                        `
+                    })
+            .pca-form-column-6
                 +preview-xml-java(model, 'igfsGeneral')

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/igfs/ipc.pug
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/modules/states/configuration/igfs/ipc.pug 
b/modules/web-console/frontend/app/modules/states/configuration/igfs/ipc.pug
index 7c8c056..e123b3c 100644
--- a/modules/web-console/frontend/app/modules/states/configuration/igfs/ipc.pug
+++ b/modules/web-console/frontend/app/modules/states/configuration/igfs/ipc.pug
@@ -19,22 +19,20 @@ include /app/helpers/jade/mixins
 -var form = 'ipc'
 -var model = 'backupItem'
 
-.panel.panel-default(ng-form=form novalidate)
-    .panel-heading(bs-collapse-toggle='' ng-click=`ui.loadPanel('${form}')`)
+.pca-panel.pca-panel-default(ng-form=form novalidate)
+    .pca-panel-heading(bs-collapse-toggle='' 
ng-click=`ui.loadPanel('${form}')`)
         ignite-form-panel-chevron
-        label IPC
-        ignite-form-field-tooltip.tipLabel
-            | IGFS Inter-process communication properties
-        ignite-form-revert
-    .panel-collapse(role='tabpanel' bs-collapse-target id=`${form}`)
-        .panel-body(ng-if=`ui.isPanelLoaded('${form}')`)
-            .col-sm-6
+        .pca-panel-heading-title IPC
+        .pca-panel-heading-description IGFS Inter-process communication 
properties.
+    .pca-panel-collapse(role='tabpanel' bs-collapse-target id=`${form}`)
+        .pca-panel-body.pca-form-row(ng-if=`ui.isPanelLoaded('${form}')`)
+            .pca-form-column-6.pc-form-grid-row
                 -var ipcEndpointConfiguration = 
`${model}.ipcEndpointConfiguration`
                 -var enabled = `${model}.ipcEndpointEnabled`
 
-                .settings-row
+                .pc-form-grid-col-60
                     +checkbox('Enabled', enabled, '"ipcEndpointEnabled"', 'IPC 
endpoint enabled flag')
-                .settings-row
+                .pc-form-grid-col-60
                     +dropdown('Type:', `${ipcEndpointConfiguration}.type`, 
'"ipcEndpointConfigurationType"', enabled, 'TCP',
                         '[\
                             {value: "SHMEM", label: "SHMEM"},\
@@ -45,16 +43,16 @@ include /app/helpers/jade/mixins
                             <li>SHMEM - shared memory endpoint</li>\
                             <li>TCP - TCP endpoint</li>\
                         </ul>')
-                .settings-row
+                .pc-form-grid-col-30
                     +text-ip-address('Host:', 
`${ipcEndpointConfiguration}.host`, '"ipcEndpointConfigurationHost"', enabled, 
'127.0.0.1', 'Host endpoint is bound to')
-                .settings-row
+                .pc-form-grid-col-30
                     +number-min-max('Port:', 
`${ipcEndpointConfiguration}.port`, '"ipcEndpointConfigurationPort"', enabled, 
'10500', '1', '65535', 'Port endpoint is bound to')
-                .settings-row
+                .pc-form-grid-col-30
                     +number('Memory size:', 
`${ipcEndpointConfiguration}.memorySize`, 
'"ipcEndpointConfigurationMemorySize"', enabled, '262144', '1', 'Shared memory 
size in bytes allocated for endpoint communication')
-                .settings-row
-                    +text-enabled('Token directory:', 
`${ipcEndpointConfiguration}.tokenDirectoryPath`, 
'"ipcEndpointConfigurationTokenDirectoryPath"', enabled, 'false', 'ipc/shmem', 
'Directory where shared memory tokens are stored')
-                .settings-row
+                .pc-form-grid-col-30
                     +number('Thread count:', 
`${ipcEndpointConfiguration}.threadCount`, 
'"ipcEndpointConfigurationThreadCount"', enabled, 'availableProcessors', '1',
                         'Number of threads used by this endpoint to process 
incoming requests')
-            .col-sm-6
+                .pc-form-grid-col-60
+                    +text-enabled('Token directory:', 
`${ipcEndpointConfiguration}.tokenDirectoryPath`, 
'"ipcEndpointConfigurationTokenDirectoryPath"', enabled, 'false', 'ipc/shmem', 
'Directory where shared memory tokens are stored')
+            .pca-form-column-6
                 +preview-xml-java(model, 'igfsIPC')

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/igfs/misc.pug
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/modules/states/configuration/igfs/misc.pug 
b/modules/web-console/frontend/app/modules/states/configuration/igfs/misc.pug
index 72d0649..63e5e46 100644
--- 
a/modules/web-console/frontend/app/modules/states/configuration/igfs/misc.pug
+++ 
b/modules/web-console/frontend/app/modules/states/configuration/igfs/misc.pug
@@ -18,106 +18,96 @@ include /app/helpers/jade/mixins
 
 -var form = 'misc'
 -var model = 'backupItem'
--var pathModesForm = 'miscPathModes'
 -var pathModes = `${model}.pathModes`
 
-//- LEGACY mixin for LEGACY IGFS path modes table.
-mixin table-igfs-path-mode-edit(prefix, focusId, index)
-    -var keyModel = `tblPathModes.${prefix}Key`
-    -var valModel = `tblPathModes.${prefix}Value`
-
-    -var keyFocusId = `${prefix}Key${focusId}`
-    -var valFocusId = `${prefix}Value${focusId}`
-
-    .col-xs-8.col-sm-8.col-md-8
-        .fieldSep /
-        .input-tip
-            input.form-control(id=keyFocusId 
ignite-on-enter-focus-move=valFocusId type='text' ng-model=keyModel 
placeholder='Path' ignite-on-escape='tableReset(false)')
-    .col-xs-4.col-sm-4.col-md-4
-        -var arg = `${keyModel}, ${valModel}`
-        -var btnVisible = `tablePairSaveVisible(tblPathModes, ${index})`
-        -var btnSave = `tablePairSave(tablePairValid, backupItem, 
tblPathModes, ${index})`
-        -var btnVisibleAndSave = `${btnVisible} && ${btnSave}`
-        +btn-save(btnVisible, btnSave)
-        .input-tip
-            button.select-toggle.form-control(id=valFocusId bs-select 
ng-model=valModel data-placeholder='Mode' bs-options='item.value as item.label 
for item in igfsModes' tabindex='0' ignite-on-enter=btnVisibleAndSave 
ignite-on-escape='tableReset(false)')
-
-.panel.panel-default(ng-form=form novalidate)
-    .panel-heading(bs-collapse-toggle='' ng-click=`ui.loadPanel('${form}')`)
+.pca-panel.pca-panel-default(ng-form=form novalidate)
+    .pca-panel-heading(bs-collapse-toggle='' 
ng-click=`ui.loadPanel('${form}')`)
         ignite-form-panel-chevron
-        label Miscellaneous
-        ignite-form-field-tooltip.tipLabel
-            | Various miscellaneous IGFS settings
-        ignite-form-revert
-    .panel-collapse(role='tabpanel' bs-collapse-target id=`${form}`)
-        .panel-body(ng-if=`ui.isPanelLoaded('${form}')`)
-            .col-sm-6
-                .settings-row
+        .pca-panel-heading-title Miscellaneous
+        .pca-panel-heading-description Various miscellaneous IGFS settings.
+    .pca-panel-collapse(role='tabpanel' bs-collapse-target id=`${form}`)
+        .pca-panel-body.pca-form-row(ng-if=`ui.isPanelLoaded('${form}')`)
+            .pca-form-column-6.pc-form-grid-row
+                .pc-form-grid-col-60
                     +number('Block size:', `${model}.blockSize`, 
'"blockSize"', 'true', '65536', '0', 'File data block size in bytes')
 
                 //- Since ignite 2.0
-                .settings-row(ng-if='$ctrl.available("2.0.0")')
+                .pc-form-grid-col-60(ng-if='$ctrl.available("2.0.0")')
                     +number('Buffer size:', `${model}.streamBufferSize`, 
'"streamBufferSize"', 'true', '65536', '0', 'Read/write buffer size for IGFS 
stream operations in bytes')
 
                 //- Removed in ignite 2.0
-                div(ng-if='$ctrl.available(["1.0.0", "2.0.0"])')
-                    .settings-row
-                        +number('Stream buffer size:', 
`${model}.streamBufferSize`, '"streamBufferSize"', 'true', '65536', '0', 
'Read/write buffer size for IGFS stream operations in bytes')
-                    .settings-row
-                        +number('Maximum space size:', 
`${model}.maxSpaceSize`, '"maxSpaceSize"', 'true', '0', '0', 'Maximum space 
available for data cache to store file system entries')
+                .pc-form-grid-col-60(ng-if-start='$ctrl.available(["1.0.0", 
"2.0.0"])')
+                    +number('Stream buffer size:', 
`${model}.streamBufferSize`, '"streamBufferSize"', 'true', '65536', '0', 
'Read/write buffer size for IGFS stream operations in bytes')
+                .pc-form-grid-col-60(ng-if-end)
+                    +number('Maximum space size:', `${model}.maxSpaceSize`, 
'"maxSpaceSize"', 'true', '0', '0', 'Maximum space available for data cache to 
store file system entries')
 
-                .settings-row
+                .pc-form-grid-col-30
                     +number('Maximum task range length:', 
`${model}.maximumTaskRangeLength`, '"maximumTaskRangeLength"', 'true', '0', 
'0', 'Maximum default range size of a file being split during IGFS task 
execution')
-                .settings-row
+                .pc-form-grid-col-30
                     +number-min-max('Management port:', 
`${model}.managementPort`, '"managementPort"', 'true', '11400', '0', '65535', 
'Port number for management endpoint')
-                .settings-row
+                .pc-form-grid-col-30
                     +number('Per node batch size:', 
`${model}.perNodeBatchSize`, '"perNodeBatchSize"', 'true', '100', '0', 'Number 
of file blocks collected on local node before sending batch to remote node')
-                .settings-row
+                .pc-form-grid-col-30
                     +number('Per node parallel batch count:', 
`${model}.perNodeParallelBatchCount`, '"perNodeParallelBatchCount"', 'true', 
'8', '0', 'Number of file block batches that can be concurrently sent to remote 
node')
-                .settings-row
+                .pc-form-grid-col-60
                     +number('Prefetch blocks:', `${model}.prefetchBlocks`, 
'"prefetchBlocks"', 'true', '0', '0', 'Number of pre-fetched blocks if specific 
file chunk is requested')
-                .settings-row
+                .pc-form-grid-col-60
                     +number('Sequential reads before prefetch:', 
`${model}.sequentialReadsBeforePrefetch`, '"sequentialReadsBeforePrefetch"', 
'true', '0', '0', 'Amount of sequential block reads before prefetch is 
triggered')
 
                 //- Removed in ignite 2.0
-                .settings-row(ng-if='$ctrl.available(["1.0.0", "2.0.0"])')
+                .pc-form-grid-col-60(ng-if='$ctrl.available(["1.0.0", 
"2.0.0"])')
                     +number('Trash purge timeout:', 
`${model}.trashPurgeTimeout`, '"trashPurgeTimeout"', 'true', '1000', '0', 
'Maximum timeout awaiting for trash purging in case data cache oversize is 
detected')
 
-                .settings-row
+                .pc-form-grid-col-60
                     +checkbox('Colocate metadata', 
`${model}.colocateMetadata`, '"colocateMetadata"', 'Whether to co-locate 
metadata on a single node')
-                .settings-row
+                .pc-form-grid-col-60
                     +checkbox('Relaxed consistency', 
`${model}.relaxedConsistency`, '"relaxedConsistency"',
                         'If value of this flag is <b>true</b>, IGFS will skip 
expensive consistency checks<br/>\
                         It is recommended to set this flag to <b>false</b> if 
your application has conflicting\
                         operations, or you do not know how exactly users will 
use your system')
 
                 //- Since ignite 2.0
-                .settings-row(ng-if='$ctrl.available("2.0.0")')
+                .pc-form-grid-col-60(ng-if='$ctrl.available("2.0.0")')
                     +checkbox('Update file length on flush', model + 
'.updateFileLengthOnFlush', '"updateFileLengthOnFlush"', 'Update file length on 
flush flag')
 
-                .settings-row
-                    +ignite-form-group(ng-model=pathModes 
ng-form=pathModesForm)
-                        ignite-form-field-label
-                            | Path modes
-                        ignite-form-group-tooltip
-                            | Map of path prefixes to IGFS modes used for them
-                        
ignite-form-group-add(ng-click='tableNewItem(tblPathModes)')
-                            | Add path mode
-
-                        .group-content-empty(ng-if=`!((${pathModes} && 
${pathModes}.length > 0) || tableNewItemActive(tblPathModes))`) Not defined
-
-                        .group-content(ng-show=`(${pathModes} && 
${pathModes}.length > 0) || tableNewItemActive(tblPathModes)`)
-                            table.links-edit(id='pathModes' st-table=pathModes)
-                                tbody
-                                    tr(ng-repeat=`item in ${pathModes} track 
by $index`)
-                                        
td.col-sm-12(ng-hide='tableEditing(tblPathModes, $index)')
-                                            
a.labelFormField(ng-click='tableStartEdit(backupItem, tblPathModes, $index)') 
{{item.path + " [" + item.mode + "]"}}
-                                            
+btn-remove('tableRemove(backupItem, tblPathModes, $index)', '"Remove path"')
-                                        
td.col-sm-12(ng-show='tableEditing(tblPathModes, $index)')
-                                            +table-igfs-path-mode-edit('cur', 
'{{::tblPathModes.focusId + $index}}', '$index')
-                                
tfoot(ng-show='tableNewItemActive(tblPathModes)')
-                                    tr
-                                        td.col-sm-12
-                                            +table-igfs-path-mode-edit('new', 
'PathMode', '-1')
-            .col-sm-6
+                .pc-form-grid-col-60
+                    mixin igfs-misc-path-modes
+                        .ignite-form-field
+                            +ignite-form-field__label('Path modes:', 
'"pathModes"')
+                                +tooltip(`Map of path prefixes to IGFS modes 
used for them`)
+                            .ignite-form-field__control
+                                -let items = pathModes
+
+                                list-editable(ng-model=items)
+                                    list-editable-item-view
+                                        | {{ $item.path + " [" + $item.mode + 
"]"}}
+
+                                    list-editable-item-edit
+                                        - form = '$parent.form'
+
+                                        .pc-form-grid-row
+                                            .pc-form-grid-col-30
+                                                
+ignite-form-field-text('Path:', '$item.path', '"path"', false, true, 'Enter 
path')(ignite-auto-focus)
+                                            .pc-form-grid-col-30
+                                                
+sane-ignite-form-field-dropdown({
+                                                    label: 'Mode:',
+                                                    model: `$item.mode`,
+                                                    name: '"mode"',
+                                                    required: true,
+                                                    placeholder: 'Choose igfs 
mode',
+                                                    options: 
'{{::$ctrl.IGFSs.defaultMode.values}}'
+                                                })(
+                                                    
ng-model-options='{allowInvalid: true}'
+                                                )
+
+                                    list-editable-no-items
+                                        list-editable-add-item-button(
+                                            add-item=`$editLast((${items} = 
${items} || []).push({}))`
+                                            label-single='path mode'
+                                            label-multiple='path modes'
+                                        )
+
+                    +igfs-misc-path-modes
+
+            .pca-form-column-6
                 +preview-xml-java(model, 'igfsMisc')

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/igfs/secondary.pug
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/modules/states/configuration/igfs/secondary.pug
 
b/modules/web-console/frontend/app/modules/states/configuration/igfs/secondary.pug
index 797e877..da34596 100644
--- 
a/modules/web-console/frontend/app/modules/states/configuration/igfs/secondary.pug
+++ 
b/modules/web-console/frontend/app/modules/states/configuration/igfs/secondary.pug
@@ -19,27 +19,40 @@ include /app/helpers/jade/mixins
 -var form = 'secondaryFileSystem'
 -var model = 'backupItem'
 
-.panel.panel-default(ng-form=form novalidate)
-    .panel-heading(bs-collapse-toggle='' ng-click=`ui.loadPanel('${form}')`)
+.pca-panel.pca-panel-default(ng-form=form novalidate)
+    .pca-panel-heading(bs-collapse-toggle='' 
ng-click=`ui.loadPanel('${form}')`)
         ignite-form-panel-chevron
-        label(id='secondaryFileSystem-title') Secondary file system
-        ignite-form-field-tooltip.tipLabel
-            | Secondary file system is provided for pass-through, 
write-through, and read-through purposes#[br]
-            | 
#[a(href="https://apacheignite-fs.readme.io/docs/secondary-file-system"; 
target="_blank") More info]
-        ignite-form-revert
-    .panel-collapse(role='tabpanel' bs-collapse-target id=`${form}`)
-        .panel-body(ng-if=`ui.isPanelLoaded('${form}')`)
-            .col-sm-6
+        .pca-panel-heading-title Secondary file system
+        .pca-panel-heading-description
+            | Secondary file system is provided for pass-through, 
write-through, and read-through purposes. 
+            
a.link-success(href="https://apacheignite-fs.readme.io/docs/secondary-file-system";
 target="_blank") More info
+    .pca-panel-collapse(role='tabpanel' bs-collapse-target id=`${form}`)
+        .pca-panel-body.pca-form-row(ng-if=`ui.isPanelLoaded('${form}')`)
+            .pca-form-column-6.pc-form-grid-row
                 -var enabled = `${model}.secondaryFileSystemEnabled`
                 -var secondaryFileSystem = `${model}.secondaryFileSystem`
 
-                .settings-row
-                    +checkbox('Enabled', enabled, 
'"secondaryFileSystemEnabled"', 'Secondary file system enabled flag')
-                .settings-row
+                .pc-form-grid-col-60
+                    +sane-form-field-checkbox({
+                        label: 'Enabled',
+                        name: '"secondaryFileSystemEnabled"',
+                        model: enabled
+                    })(
+                        ng-model-options='{allowInvalid: true}'
+                        ui-validate=`{
+                            requiredWhenIGFSProxyMode: 
'$ctrl.IGFSs.secondaryFileSystemEnabled.requiredWhenIGFSProxyMode(${model})',
+                            requiredWhenPathModeProxyMode: 
'$ctrl.IGFSs.secondaryFileSystemEnabled.requiredWhenPathModeProxyMode(${model})'
+                        }`
+                        ui-validate-watch-collection=`"[${model}.defaultMode, 
${model}.pathModes]"`
+                        ui-validate-watch-object-equality='true'
+                    )
+                        +form-field-feedback(null, 
'requiredWhenIGFSProxyMode', 'Secondary file system should be configured for 
"PROXY" IGFS mode')
+                        +form-field-feedback(null, 
'requiredWhenPathModeProxyMode', 'Secondary file system should be configured 
for "PROXY" path mode')
+                .pc-form-grid-col-60
                     +text-enabled('URI:', `${secondaryFileSystem}.uri`, 
'"hadoopURI"', enabled, 'false', 'hdfs://[namenodehost]:[port]/[path]', 'URI of 
file system')
-                .settings-row
+                .pc-form-grid-col-60
                     +text-enabled('Config path:', 
`${secondaryFileSystem}.cfgPath`, '"cfgPath"', enabled, 'false', 'Path to 
additional config', 'Additional path to Hadoop configuration')
-                .settings-row
+                .pc-form-grid-col-60
                     +text-enabled('User name:', 
`${secondaryFileSystem}.userName`, '"userName"', enabled, 'false', 'Input user 
name', 'User name')
-            .col-sm-6
+            .pca-form-column-6
                 +preview-xml-java(model, 'igfsSecondFS')

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/summary/summary-tabs.directive.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/modules/states/configuration/summary/summary-tabs.directive.js
 
b/modules/web-console/frontend/app/modules/states/configuration/summary/summary-tabs.directive.js
deleted file mode 100644
index f8094af..0000000
--- 
a/modules/web-console/frontend/app/modules/states/configuration/summary/summary-tabs.directive.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-export default ['summaryTabs', [() => {
-    const link = (scope, $element, $attrs, [igniteUiAceTabs1, 
igniteUiAceTabs2]) => {
-        const igniteUiAceTabs = igniteUiAceTabs1 || igniteUiAceTabs2;
-
-        if (!igniteUiAceTabs)
-            return;
-
-        igniteUiAceTabs.onLoad = (editor) => {
-            editor.setReadOnly(true);
-            editor.setOption('highlightActiveLine', false);
-            editor.setAutoScrollEditorIntoView(true);
-            editor.$blockScrolling = Infinity;
-
-            const renderer = editor.renderer;
-
-            renderer.setHighlightGutterLine(false);
-            renderer.setShowPrintMargin(false);
-            renderer.setOption('fontFamily', 'monospace');
-            renderer.setOption('fontSize', '12px');
-            renderer.setOption('minLines', '25');
-            renderer.setOption('maxLines', '25');
-
-            editor.setTheme('ace/theme/chrome');
-        };
-    };
-
-    return {
-        priority: 1000,
-        restrict: 'C',
-        link,
-        require: ['?igniteUiAceTabs', '?^igniteUiAceTabs']
-    };
-}]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/summary/summary-zipper.service.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/modules/states/configuration/summary/summary-zipper.service.js
 
b/modules/web-console/frontend/app/modules/states/configuration/summary/summary-zipper.service.js
index 47ce9ad..2f6b9e3 100644
--- 
a/modules/web-console/frontend/app/modules/states/configuration/summary/summary-zipper.service.js
+++ 
b/modules/web-console/frontend/app/modules/states/configuration/summary/summary-zipper.service.js
@@ -26,10 +26,12 @@ export default ['$q', function($q) {
 
         worker.onmessage = (e) => {
             defer.resolve(e.data);
+            worker.terminate();
         };
 
         worker.onerror = (err) => {
             defer.reject(err);
+            worker.terminate();
         };
 
         return defer.promise;

Reply via email to