http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/helpers/jade/form/form-field-number.pug
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/helpers/jade/form/form-field-number.pug 
b/modules/web-console/frontend/app/helpers/jade/form/form-field-number.pug
index 0b8bce7..75f2a20 100644
--- a/modules/web-console/frontend/app/helpers/jade/form/form-field-number.pug
+++ b/modules/web-console/frontend/app/helpers/jade/form/form-field-number.pug
@@ -15,6 +15,8 @@
     limitations under the License.
 
 mixin ignite-form-field-number(label, model, name, disabled, required, 
placeholder, min, max, step, tip)
+    -var errLbl = label.substring(0, label.length - 1)
+
     mixin form-field-input()
         input.form-control(
             id=`{{ ${name} }}Input`
@@ -26,27 +28,32 @@ mixin ignite-form-field-number(label, model, name, 
disabled, required, placehold
             max=max ? max : '{{ Number.MAX_VALUE }}'
             step=step ? step : '1'
 
-            data-ng-model=model
-
-            data-ng-required=required && `${required}`
-            data-ng-disabled=disabled && `${disabled}`
-            data-ng-focus='tableReset()'
+            ng-model=model
 
-            data-ignite-form-panel-field=''
+            ng-required=required && `${required}`
+            ng-disabled=disabled && `${disabled}`
+            expose-ignite-form-field-control='$input'
         )&attributes(attributes.attributes)
 
     .ignite-form-field
-        +ignite-form-field__label(label, name, required)
-        .ignite-form-field__control
+        +ignite-form-field__label(label, name, required, disabled)
             +tooltip(tip, tipOpts)
-            
-            +form-field-feedback(name, 'required', 'This field could not be 
empty')
-            +form-field-feedback(name, 'min', 'Value is less than allowable 
minimum: '+ min || 0)
-            +form-field-feedback(name, 'max', 'Value is more than allowable 
maximum: '+ max)
-            +form-field-feedback(name, 'number', 'Only numbers allowed')
-
-            if block
-                block
-
+        .ignite-form-field__control
             .input-tip
                 +form-field-input(attributes=attributes)
+        .ignite-form-field__errors(
+            ng-messages=`$input.$error`
+            ng-show=`($input.$dirty || $input.$touched || $input.$submitted) 
&& $input.$invalid`
+        )
+            if block
+                block
+            +form-field-feedback(name, 'required', `${errLbl} could not be 
empty`)
+            +form-field-feedback(name, 'min', `${errLbl} is less than 
allowable minimum: ${min || 0}`)
+            +form-field-feedback(name, 'max', `${errLbl} is more than 
allowable maximum: ${max}`)
+            +form-field-feedback(name, 'number', `Only numbers are allowed`)
+            +form-field-feedback(name, 'step', `${errLbl} step should be 
${step || 1}`)
+
+mixin sane-ignite-form-field-number({label, model, name, disabled = 'false', 
required = false, placeholder, min = '0', max, step = '1', tip})
+    +ignite-form-field-number(label, model, name, disabled, required, 
placeholder, min, max, step, tip)&attributes(attributes)
+        if block
+            block
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/helpers/jade/form/form-field-password.pug
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/helpers/jade/form/form-field-password.pug 
b/modules/web-console/frontend/app/helpers/jade/form/form-field-password.pug
index a567e77..6de29c8 100644
--- a/modules/web-console/frontend/app/helpers/jade/form/form-field-password.pug
+++ b/modules/web-console/frontend/app/helpers/jade/form/form-field-password.pug
@@ -21,27 +21,27 @@ mixin ignite-form-field-password-input(name, model, 
disabled, required, placehol
         placeholder=placeholder
         type='password'
 
-        data-ng-model=model
+        ng-model=model
 
-        data-ng-required=required && `${required}`
-        data-ng-disabled=disabled && `${disabled}`
-        data-ng-focus='tableReset()'
-
-        data-ignite-form-panel-field=''
+        ng-required=required && `${required}`
+        ng-disabled=disabled && `${disabled}`
+        expose-ignite-form-field-control='$input'
     )&attributes(attributes ? attributes.attributes ? attributes.attributes : 
attributes : {})
 
 mixin ignite-form-field-password(label, model, name, disabled, required, 
placeholder, tip)
     -var errLbl = label.substring(0, label.length - 1)
 
     .ignite-form-field
-        +ignite-form-field__label(label, name, required)
-        .ignite-form-field__control
+        +ignite-form-field__label(label, name, required, disabled)
             +tooltip(tip, tipOpts)
-            
+        .ignite-form-field__control
+            .input-tip
+                +ignite-form-field-password-input(name, model, disabled, 
required, placeholder)(attributes=attributes)
+        .ignite-form-field__errors(
+            ng-messages=`$input.$error`
+            ng-if=`!$input.$pristine && $input.$invalid`
+        )
             if block
                 block
 
-            +form-field-feedback(name, 'required', errLbl + ' could not be 
empty!')
-
-            .input-tip
-                +ignite-form-field-password-input(name, model, disabled, 
required, placeholder)(attributes=attributes)
+            +form-field-feedback(name, 'required', `${errLbl} could not be 
empty!`)
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/helpers/jade/form/form-field-text.pug
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/helpers/jade/form/form-field-text.pug 
b/modules/web-console/frontend/app/helpers/jade/form/form-field-text.pug
index 8207271..d1c6491 100644
--- a/modules/web-console/frontend/app/helpers/jade/form/form-field-text.pug
+++ b/modules/web-console/frontend/app/helpers/jade/form/form-field-text.pug
@@ -20,28 +20,34 @@ mixin ignite-form-field-input(name, model, disabled, 
required, placeholder)
         name=`{{ ${name} }}`
         placeholder=placeholder
 
-        data-ng-model=model
+        ng-model=model
 
-        data-ng-required=required && `${required}`
-        data-ng-disabled=disabled && `${disabled}`
-        data-ng-focus='tableReset()'
+        ng-required=required && `${required}`
+        ng-disabled=disabled && `${disabled}`
+        expose-ignite-form-field-control='$input'
 
-        data-ignite-form-panel-field=''
     )&attributes(attributes ? attributes.attributes ? attributes.attributes : 
attributes : {})
 
 mixin ignite-form-field-text(lbl, model, name, disabled, required, 
placeholder, tip)
-    -var errLbl = lbl.substring(0, lbl.length - 1)
+    -let errLbl = lbl[lbl.length - 1] === ':' ? lbl.substring(0, lbl.length - 
1) : lbl
 
     .ignite-form-field
-        +ignite-form-field__label(lbl, name, required)
-        .ignite-form-field__control
+        +ignite-form-field__label(lbl, name, required, disabled)
             +tooltip(tip, tipOpts)
-
+        .ignite-form-field__control
+            .input-tip
+                +ignite-form-field-input(name, model, disabled, required, 
placeholder)(attributes=attributes)
+        .ignite-form-field__errors(
+            ng-messages=`$input.$error`
+            ng-show=`($input.$dirty || $input.$touched || $input.$submitted) 
&& $input.$invalid`
+        )
             if block
                 block
 
             if required
                 +form-field-feedback(name, 'required', `${errLbl} could not be 
empty!`)
 
-            .input-tip
-                +ignite-form-field-input(name, model, disabled, required, 
placeholder)(attributes=attributes)
+mixin sane-ignite-form-field-text({label, model, name, disabled, required, 
placeholder, tip})
+    +ignite-form-field-text(label, model, name, disabled, required, 
placeholder, tip)&attributes(attributes)
+        if block
+            block
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/helpers/jade/form/form-group.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/helpers/jade/form/form-group.pug 
b/modules/web-console/frontend/app/helpers/jade/form/form-group.pug
deleted file mode 100644
index 8fb7b1f..0000000
--- a/modules/web-console/frontend/app/helpers/jade/form/form-group.pug
+++ /dev/null
@@ -1,23 +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.
-
-mixin ignite-form-group()
-    .group-section(ignite-form-group)&attributes(attributes)
-        .group(ng-if='true' ng-init='group = {}')
-            .group-legend
-                label {{::group.label}}
-            if block
-                block

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/helpers/jade/mixins.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/helpers/jade/mixins.pug 
b/modules/web-console/frontend/app/helpers/jade/mixins.pug
index 1d3b161..62290c4 100644
--- a/modules/web-console/frontend/app/helpers/jade/mixins.pug
+++ b/modules/web-console/frontend/app/helpers/jade/mixins.pug
@@ -27,7 +27,10 @@ include ../../primitives/form-field/index
 //- Mixin for advanced options toggle.
 mixin advanced-options-toggle(click, cond, showMessage, hideMessage)
     .advanced-options
-        i.fa(ng-click=`${click}` ng-class=`${cond} ? 'fa-chevron-circle-down' 
: 'fa-chevron-circle-right'`)
+        i.fa(
+            ng-click=`${click}`
+            ng-class=`${cond} ? 'fa-chevron-circle-down' : 
'fa-chevron-circle-right'`
+        )
         a(ng-click=click) {{ #{cond} ? '#{hideMessage}' : '#{showMessage}' }}
 
 //- Mixin for advanced options toggle with default settings.
@@ -61,20 +64,40 @@ mixin save-remove-clone-undo-buttons(objectName)
     -var cloneTip = '"Clone current ' + objectName + '"'
     -var undoTip = '"Undo all changes for current ' + objectName + '"'
 
-    div(ng-show='contentVisible()' style='display: inline-block;')
-        .panel-tip-container(ng-hide='!backupItem || backupItem._id')
-            a.btn.btn-primary(ng-disabled='!ui.inputForm.$dirty' 
ng-click='ui.inputForm.$dirty && saveItem()' bs-tooltip='' 
data-title=`{{saveBtnTipText(ui.inputForm.$dirty, '${objectName}')}}` 
data-placement='bottom' data-trigger='hover') Save
-        .panel-tip-container(ng-show='backupItem._id')
-            a.btn.btn-primary(id='save-item' 
ng-disabled='!ui.inputForm.$dirty' ng-click='ui.inputForm.$dirty && saveItem()' 
bs-tooltip='' data-title=`{{saveBtnTipText(ui.inputForm.$dirty, 
'${objectName}')}}` data-placement='bottom' data-trigger='hover') Save
-        .panel-tip-container(ng-show='backupItem._id')
-            a.btn.btn-primary(id='clone-item' ng-click='cloneItem()' 
bs-tooltip=cloneTip data-placement='bottom' data-trigger='hover') Clone
-
-        -var options = [{ text: "Remove", click: "removeItem()" }, { text: 
"Remove All", click: "removeAllItems()" }]
-
-        +btn-group(false, options, removeTip)(ng-show='backupItem._id')
-
-        .panel-tip-container(ng-show='backupItem')
-            i.btn.btn-primary.fa.fa-undo(id='undo-item' 
ng-disabled='!ui.inputForm.$dirty' ng-click='ui.inputForm.$dirty && resetAll()' 
bs-tooltip=undoTip data-placement='bottom' data-trigger='hover')
+    button.btn-ignite.btn-ignite--success(
+        ng-disabled='!ui.inputForm.$dirty'
+        ng-click='ui.inputForm.$dirty && saveItem()'
+    ) Save
+    button.btn-ignite.btn-ignite--success(
+        ng-show='backupItem._id && contentVisible()'
+        type='button'
+        id='clone-item'
+        ng-click='cloneItem()'
+    ) Clone
+
+    .btn-ignite-group(ng-show='backupItem._id && contentVisible()')
+        button.btn-ignite.btn-ignite--success(
+            ng-click='removeItem()'
+            type='button'
+        )
+            | Remove
+        button.btn-ignite.btn-ignite--success(
+            bs-dropdown='$ctrl.extraFormActions'
+            data-placement='top-right'
+            type='button'
+        )
+            span.icon.fa.fa-caret-up
+
+    button.btn-ignite.btn-ignite--success(
+        ng-show='contentVisible()'
+        id='undo-item'
+        ng-disabled='!ui.inputForm.$dirty'
+        ng-click='ui.inputForm.$dirty && resetAll()'
+        bs-tooltip=undoTip
+        data-placement='top'
+        data-trigger='hover'
+    )
+        i.icon.fa.fa-undo()
 
 //- Mixin for feedback on specified error.
 mixin error-feedback(visible, error, errorMessage, name)
@@ -130,7 +153,7 @@ mixin java-class-autofocus-placholder(lbl, model, name, 
enabled, required, autof
         data-java-built-in-class='true'
         data-ignite-form-field-input-autofocus=autofocus
         data-validation-active=validationActive ? `{{ ${validationActive} }}` 
: `'always'`
-    )
+    )&attributes(attributes)
         if  block
             block
 
@@ -141,7 +164,7 @@ mixin java-class-autofocus-placholder(lbl, model, name, 
enabled, required, autof
 
 //- Mixin for Java class name field with auto focus condition.
 mixin java-class-autofocus(lbl, model, name, enabled, required, autofocus, 
tip, validationActive)
-    +java-class-autofocus-placholder(lbl, model, name, enabled, required, 
autofocus, 'Enter fully qualified class name', tip, validationActive)
+    +java-class-autofocus-placholder(lbl, model, name, enabled, required, 
autofocus, 'Enter fully qualified class name', tip, 
validationActive)&attributes(attributes)
         if  block
             block
 
@@ -155,7 +178,7 @@ mixin java-class(lbl, model, name, enabled, required, tip, 
validationActive)
 mixin java-class-typeahead(lbl, model, name, options, enabled, required, 
placeholder, tip, validationActive)
     -var errLbl = lbl.substring(0, lbl.length - 1)
 
-    +form-field-datalist(lbl, model, name, enabledToDisabled(enabled), 
required, placeholder, options, tip)(
+    +form-field-datalist(lbl, model, name, enabledToDisabled(enabled), 
required, placeholder, options, tip)&attributes(attributes)(
         data-java-identifier='true'
         data-java-package-specified='allow-built-in'
         data-java-keywords='true'
@@ -196,7 +219,7 @@ mixin text-enabled(lbl, model, name, enabled, required, 
placeholder, tip)
 mixin text-enabled-autofocus(lbl, model, name, enabled, required, placeholder, 
tip)
     +ignite-form-field-text(lbl, model, name, enabledToDisabled(enabled), 
required, placeholder, tip)(
         data-ignite-form-field-input-autofocus='true'
-    )
+    )&attributes(attributes)
         if  block
             block
 
@@ -244,7 +267,7 @@ mixin number(lbl, model, name, enabled, placeholder, min, 
tip)
 
 //- Mixin for required dropdown field.
 mixin dropdown-required-empty(lbl, model, name, enabled, required, 
placeholder, placeholderEmpty, options, tip)
-    +ignite-form-field-dropdown(lbl, model, name, enabledToDisabled(enabled), 
required, false, placeholder, placeholderEmpty, options, tip)
+    +ignite-form-field-dropdown(lbl, model, name, enabledToDisabled(enabled), 
required, false, placeholder, placeholderEmpty, options, 
tip)&attributes(attributes)
         if  block
             block
 
@@ -258,7 +281,7 @@ mixin dropdown-required-empty-autofocus(lbl, model, name, 
enabled, required, pla
 
 //- Mixin for required dropdown field.
 mixin dropdown-required(lbl, model, name, enabled, required, placeholder, 
options, tip)
-    +ignite-form-field-dropdown(lbl, model, name, enabledToDisabled(enabled), 
required, false, placeholder, '', options, tip)
+    +ignite-form-field-dropdown(lbl, model, name, enabledToDisabled(enabled), 
required, false, placeholder, '', options, tip)&attributes(attributes)
         if  block
             block
 
@@ -282,40 +305,36 @@ mixin dropdown-multiple(lbl, model, name, enabled, 
placeholder, placeholderEmpty
         if  block
             block
 
-//- Mixin for table text field.
-mixin table-text-field(name, model, items, valid, save, placeholder, newItem)
-    -var resetOnEnter = newItem ? '(stopblur = true) && (group.add = [{}])' : 
'(field.edit = false)'
-    -var onEnter = `${valid} && (${save}); ${valid} && ${resetOnEnter};`
-
-    -var onEscape = newItem ? 'group.add = []' : 'field.edit = false'
-
-    -var resetOnBlur = newItem ? '!stopblur && (group.add = [])' : 'field.edit 
= false'
-    -var onBlur = `${valid} && (${save}); ${resetOnBlur};`
-
-    div(ignite-on-focus-out=onBlur)
-        if block
-            block
+mixin list-text-field({ items, lbl, name, itemName, itemsName })
+    list-editable(ng-model=items)&attributes(attributes)
+        list-editable-item-view
+            | {{ $item }}
 
-        .input-tip
-            +ignite-form-field-input(name, model, false, 'true', placeholder)(
+        list-editable-item-edit
+            +ignite-form-field-text(lbl, '$item', `"${name}"`, false, true, 
`Enter ${lbl.toLowerCase()}`)(
                 data-ignite-unique=items
                 data-ignite-form-field-input-autofocus='true'
-
-                ignite-on-enter=onEnter
-                ignite-on-escape=onEscape
             )
+                if  block
+                    block
 
-//- Mixin for table java class field.
-mixin table-java-class-field(lbl, name, model, items, valid, save, newItem)
-    -var resetOnEnter = newItem ? '(stopblur = true) && (group.add = [{}])' : 
'(field.edit = false)'
-    -var onEnter = `${valid} && (${save}); ${valid} && ${resetOnEnter};`
-
-    -var onEscape = newItem ? 'group.add = []' : 'field.edit = false'
+        list-editable-no-items
+            list-editable-add-item-button(
+                add-item=`$editLast((${items} = ${items} || []).push(''))`
+                label-single=itemName
+                label-multiple=itemsName
+            )
 
-    -var resetOnBlur = newItem ? '!stopblur && (group.add = [])' : 'field.edit 
= false'
-    -var onBlur = `${valid} && (${save}); ${resetOnBlur};`
+mixin list-java-class-field(lbl, model, name, items)
+    +ignite-form-field-text(lbl, model, name, false, true, 'Enter fully 
qualified class name')(
+        data-java-identifier='true'
+        data-java-package-specified='true'
+        data-java-keywords='true'
+        data-java-built-in-class='true'
 
-    div(ignite-on-focus-out=onBlur)
+        data-ignite-unique=items
+        data-ignite-form-field-input-autofocus='true'
+    )
         +form-field-feedback(name, 'javaBuiltInClass', lbl + ' should not be 
the Java built-in class!')
         +form-field-feedback(name, 'javaKeywords', lbl + ' could not contains 
reserved Java keyword!')
         +form-field-feedback(name, 'javaPackageSpecified', lbl + ' does not 
have package specified!')
@@ -324,156 +343,60 @@ mixin table-java-class-field(lbl, name, model, items, 
valid, save, newItem)
         if block
             block
 
-        .input-tip
-            +ignite-form-field-input(name, model, false, 'true', 'Enter fully 
qualified class name')(
-                data-java-identifier='true'
-                data-java-package-specified='true'
-                data-java-keywords='true'
-                data-java-built-in-class='true'
-
-                data-ignite-unique=items
-                data-ignite-form-field-input-autofocus='true'
-
-                ignite-on-enter=onEnter
-                ignite-on-escape=onEscape
-            )
-
-//- Mixin for table java package field.
-mixin table-java-package-field(name, model, items, valid, save, newItem)
-    -var resetOnEnter = newItem ? '(stopblur = true) && (group.add = [{}])' : 
'(field.edit = false)'
-    -var onEnter = `${valid} && (${save}); ${valid} && ${resetOnEnter};`
-
-    -var onEscape = newItem ? 'group.add = []' : 'field.edit = false'
-
-    -var resetOnBlur = newItem ? '!stopblur && (group.add = [])' : 'field.edit 
= false'
-    -var onBlur = `${valid} && (${save}); ${resetOnBlur};`
+mixin list-java-package-field(lbl, model, name, items)
+    +ignite-form-field-text(lbl, model, name, false, true, 'Enter package 
name')(
+        data-java-keywords='true'
+        data-java-package-name='package-only'
 
-    div(ignite-on-focus-out=onBlur)
+        data-ignite-unique=items
+        data-ignite-form-field-input-autofocus='true'
+    )&attributes(attributes)
         +form-field-feedback(name, 'javaKeywords', 'Package name could not 
contains reserved Java keyword!')
         +form-field-feedback(name, 'javaPackageName', 'Package name is 
invalid!')
 
         if block
             block
 
-        .input-tip
-            +ignite-form-field-input(name, model, false, 'true', 'Enter 
package name')(
-                data-java-keywords='true'
-                data-java-package-name='package-only'
-
-                data-ignite-unique=items
-                data-ignite-form-field-input-autofocus='true'
-
-                ignite-on-enter=onEnter
-                ignite-on-escape=onEscape
-            )
-
-//- Mixin for table url field.
-mixin table-url-field(name, model, items, valid, save, newItem)
-    -var resetOnEnter = newItem ? '(stopblur = true) && (group.add = [{}])' : 
'(field.edit = false)'
-    -var onEnter = `${valid} && (${save}); ${valid} && ${resetOnEnter};`
-
-    -var onEscape = newItem ? 'group.add = []' : 'field.edit = false'
+mixin list-url-field(lbl, model, name, items)
+    +ignite-form-field-text(lbl, model, name, false, true, 'Enter URL')(
+        type='url'
 
-    -var resetOnBlur = newItem ? '!stopblur && (group.add = [])' : 'field.edit 
= false'
-    -var onBlur = `${valid} && (${save}); ${resetOnBlur};`
-
-    div(ignite-on-focus-out=onBlur)
+        data-ignite-unique=items
+        data-ignite-form-field-input-autofocus='true'
+    )
         +form-field-feedback(name, 'url', 'URL should be valid!')
 
         if block
             block
 
-        .input-tip
-            +ignite-form-field-input(name, model, false, 'true', 'Enter URL')(
-                type='url'
-
-                data-ignite-unique=items
-                data-ignite-form-field-input-autofocus='true'
-
-                ignite-on-enter=onEnter
-                ignite-on-escape=onEscape
-            )
-
-//- Mixin for table address field.
-mixin table-address-field(name, model, items, valid, save, newItem, portRange)
-    -var resetOnEnter = newItem ? '(stopblur = true) && (group.add = [{}])' : 
'(field.edit = false)'
-    -var onEnter = `${valid} && (${save}); ${valid} && ${resetOnEnter};`
-
-    -var onEscape = newItem ? 'group.add = []' : 'field.edit = false'
-
-    -var resetOnBlur = newItem ? '!stopblur && (group.add = [])' : 'field.edit 
= false'
-    -var onBlur = `${valid} && (${save}); ${resetOnBlur};`
-
-    div(ignite-on-focus-out=onBlur)
-        +ipaddress-feedback(name)
-        +ipaddress-port-feedback(name)
-        +ipaddress-port-range-feedback(name)
-        +form-field-feedback(name, 'required', 'IP address:port could not be 
empty!')
-
-        if block
-            block
-
-        .input-tip
-            +ignite-form-field-input(name, model, false, 'true', 'IP 
address:port')(
+mixin list-addresses({ items, name, tip, withPortRange = true })
+    list-editable(
+        ng-model=items
+        name=name
+        list-editable-cols=`::[{name: "Addresses:", tip: "${tip}"}]`
+    )&attributes(attributes)
+        list-editable-item-view {{ $item }}
+        list-editable-item-edit(item-name='address')
+            +ignite-form-field-text('Address', 'address', '"address"', false, 
true, 'IP address:port')(
                 data-ipaddress='true'
                 data-ipaddress-with-port='true'
-                data-ipaddress-with-port-range=portRange ? 'true' : null
+                data-ipaddress-with-port-range=withPortRange
                 data-ignite-unique=items
                 data-ignite-form-field-input-autofocus='true'
-
-                ignite-on-enter=onEnter
-                ignite-on-escape=onEscape
             )
-
-//- Mixin for table UUID field.
-mixin table-uuid-field(name, model, items, valid, save, newItem)
-    -var resetOnEnter = newItem ? '(stopblur = true) && (group.add = [{}])' : 
'(field.edit = false)'
-    -var onEnter = `${valid} && (${save}); ${valid} && ${resetOnEnter};`
-
-    -var onEscape = newItem ? 'group.add = []' : 'field.edit = false'
-
-    -var resetOnBlur = newItem ? '!stopblur && (group.add = [])' : 'field.edit 
= false'
-    -var onBlur = `${valid} && (${save}); ${resetOnBlur};`
-
-    div(ignite-on-focus-out=onBlur)
-        if block
-            block
-
-        .input-tip
-            +ignite-form-field-input(name, model, false, 'true', 
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx')(
-                data-uuid='true'
-                data-ignite-unique=items
-                data-ignite-form-field-input-autofocus='true'
-
-                ignite-on-enter=onEnter
-                ignite-on-escape=onEscape
+                +unique-feedback('"address"', 'Such IP address already 
exists!')
+                +ipaddress-feedback('"address"')
+                +ipaddress-port-feedback('"address"')
+                +ipaddress-port-range-feedback('"address"')
+                +form-field-feedback('"address"', 'required', 'IP address:port 
could not be empty!')
+
+        list-editable-no-items
+            list-editable-add-item-button(
+                add-item=`$editLast((${items} = ${items} || []).push(""))`
+                label-multiple='addresses'
+                label-single='address'
             )
 
-//- Mixin for table save button.
-   "||" used instead of "&&" to workaround escaping of "&&" to "&&"
-mixin table-save-button(valid, save, newItem)
-    -var reset = newItem ? 'group.add = []' : 'field.edit = false'
-
-    i.fa.fa-floppy-o.form-field-save(
-        ng-show=valid
-        ng-click=`!(${valid}) || (${save}); !(${valid}) || (${reset});`
-        bs-tooltip
-        data-title='Click icon or press [Enter] to save item'
-    )
-
-//- Mixin for table remove button.
-mixin table-remove-conditional-button(items, show, tip, row)
-    i.tipField.fa.fa-remove(
-        ng-hide=`!${show} || field.edit`
-        bs-tooltip
-        data-title=tip
-        ng-click=`${items}.splice(${items}.indexOf(${row}), 1)`
-    )
-
-//- Mixin for table remove button.
-mixin table-remove-button(items, tip)
-    +table-remove-conditional-button(items, 'true', tip, 'model')
-
 //- Mixin for cache mode.
 mixin cacheMode(lbl, model, name, placeholder)
     +dropdown(lbl, model, name, 'true', placeholder,
@@ -495,33 +418,57 @@ mixin evictionPolicy(model, name, enabled, required, tip)
     -var kind = model + '.kind'
     -var policy = model + '[' + kind + ']'
 
-    +dropdown-required('Eviction policy:', kind, name + '+ "Kind"', enabled, 
required, 'Not set',
-        '[\
-            {value: "LRU", label: "LRU"},\
-            {value: "FIFO", label: "FIFO"},\
-            {value: "SORTED", label: "Sorted"},\
-            {value: null, label: "Not set"}\
-        ]', tip)
-    span(ng-show=kind)
-        +showHideLink('expanded', 'settings')
-            .details-row
-                +number('Batch size', policy + '.batchSize', name + '+ 
"batchSize"', enabled, '1', '1',
-                    'Number of entries to remove on shrink')
-            .details-row
-                +number('Max memory size', policy + '.maxMemorySize', name + 
'+ "maxMemorySize"', enabled, '0', '0',
-                    'Maximum allowed cache size in bytes')
-            .details-row
-                +number('Max size', policy + '.maxSize', name + '+ "maxSize"', 
enabled, '100000', '0',
-                    'Maximum allowed size of cache before entry will start 
getting evicted')
+    .pc-form-grid-col-60
+        +sane-ignite-form-field-dropdown({
+            label: 'Eviction policy:',
+            model: kind,
+            name: `${name}+"Kind"`,
+            disabled: enabledToDisabled(enabled),
+            required: required,
+            placeholder: '{{ ::$ctrl.Caches.evictionPolicy.kind.default }}',
+            options: '::$ctrl.Caches.evictionPolicy.values',
+            tip: tip
+        })
+    .pc-form-group.pc-form-grid-row(ng-if=kind)
+        .pc-form-grid-col-30
+            +number('Batch size', policy + '.batchSize', name + '+ 
"batchSize"', enabled, '1', '1',
+                'Number of entries to remove on shrink')
+        .pc-form-grid-col-30
+            pc-form-field-size(
+                label='Max memory size:'
+                ng-model=`${policy}.maxMemorySize`
+                ng-model-options='{allowInvalid: true}'
+                name=`${name}.maxMemorySize`
+                ng-disabled=enabledToDisabled(enabled)
+                tip='Maximum allowed cache size'
+                placeholder='{{ 
::$ctrl.Caches.evictionPolicy.maxMemorySize.default }}'
+                min=`{{ 
$ctrl.Caches.evictionPolicy.maxMemorySize.min(${model}) }}`
+                size-scale-label='mb'
+                size-type='bytes'
+            )
+                +form-field-feedback(null, 'min', 'Either maximum memory size 
or maximum size should be greater than 0')
+        .pc-form-grid-col-60
+            +sane-ignite-form-field-number({
+                label: 'Max size:',
+                model: policy + '.maxSize',
+                name: name + '+ "maxSize"',
+                disabled: enabledToDisabled(enabled),
+                placeholder: '{{ ::$ctrl.Caches.evictionPolicy.maxSize.default 
}}',
+                min: `{{ $ctrl.Caches.evictionPolicy.maxSize.min(${model}) }}`,
+                tip: 'Maximum allowed size of cache before entry will start 
getting evicted'
+            })(
+                ng-model-options='{allowInvalid: true}'
+            )
+                +form-field-feedback(null, 'min', 'Either maximum memory size 
or maximum size should be greater than 0')
 
 //- Mixin for clusters dropdown.
 mixin clusters(model, tip)
-    +dropdown-multiple('<span>Clusters:</span>' + '<a 
ui-sref="base.configuration.tabs.advanced.clusters({linkId: linkId()})"> 
(add)</a>',
+    +dropdown-multiple('Clusters:',
         model + '.clusters', '"clusters"', true, 'Choose clusters', 'No 
clusters configured', 'clusters', tip)
 
 //- Mixin for caches dropdown.
 mixin caches(model, tip)
-    +dropdown-multiple('<span>Caches:</span>' + '<a 
ui-sref="base.configuration.tabs.advanced.caches({linkId: linkId()})"> 
(add)</a>',
+    +dropdown-multiple('Caches:',
         model + '.caches', '"caches"', true, 'Choose caches', 'No caches 
configured', 'caches', tip)
 
 //- Mixin for XML, Java, .Net preview.
@@ -572,29 +519,30 @@ mixin btn-remove(click, tip)
 mixin btn-remove-cond(cond, click, tip)
     i.tipField.fa.fa-remove(ng-show=cond ng-click=click bs-tooltip=tip 
data-trigger='hover')
 
-//- LEGACY mixin for LEGACY pair values tables.
-mixin table-pair-edit(tbl, prefix, keyPlaceholder, valPlaceholder, 
valueJavaBuiltInClasses, focusId, index, divider)
-    -var keyModel = `${tbl}.${prefix}Key`
-    -var valModel = `${tbl}.${prefix}Value`
-
-    -var keyFocusId = `${prefix}Key${focusId}`
-    -var valFocusId = `${prefix}Value${focusId}`
-
-    .col-xs-6.col-sm-6.col-md-6
-        .fieldSep !{divider}
-        .input-tip
-            input.form-control(id=keyFocusId 
ignite-on-enter-focus-move=valFocusId type='text' ng-model=keyModel 
placeholder=keyPlaceholder ignite-on-escape='tableReset(false)')
-    .col-xs-6.col-sm-6.col-md-6
-        -var btnVisible = 'tablePairSaveVisible(' + tbl + ', ' + index + ')'
-        -var btnSave = 'tablePairSave(tablePairValid, backupItem, ' + tbl + ', 
' + index + ')'
-        -var btnVisibleAndSave = btnVisible + ' && ' + btnSave
-
-        +btn-save(btnVisible, btnSave)
-        .input-tip
-            if valueJavaBuiltInClasses
-                input.form-control(id=valFocusId type='text' ng-model=valModel 
placeholder=valPlaceholder bs-typeahead container='body' 
ignite-retain-selection data-min-length='1' bs-options='javaClass for javaClass 
in javaBuiltInClasses' ignite-on-enter=btnVisibleAndSave 
ignite-on-escape='tableReset(false)')
-            else
-                input.form-control(id=valFocusId type='text' ng-model=valModel 
placeholder=valPlaceholder ignite-on-enter=btnVisibleAndSave 
ignite-on-escape='tableReset(false)')
+mixin list-pair-edit({ items, keyLbl, valLbl, itemName, itemsName })
+    list-editable(ng-model=items)
+        list-editable-item-view
+            | {{ $item.name }} = {{ $item.value }}
+
+        list-editable-item-edit
+            - form = '$parent.form'
+            .pc-form-grid-row
+                .pc-form-grid-col-30(divider='=')
+                    +ignite-form-field-text(keyLbl, '$item.name', '"name"', 
false, true, keyLbl)(
+                        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
+                    +ignite-form-field-text(valLbl, '$item.value', '"value"', 
false, true, valLbl)
+
+        list-editable-no-items
+            list-editable-add-item-button(
+                add-item=`$editLast((${items} = ${items} || []).push({}))`
+                label-single=itemName
+                label-multiple=itemsName
+            )
 
 //- Mixin for DB dialect.
 mixin dialect(lbl, model, name, required, tipTitle, genericDialectName, 
placeholder)
@@ -617,13 +565,3 @@ mixin dialect(lbl, model, name, required, tipTitle, 
genericDialectName, placehol
             <li>PostgreSQL</li>\
             <li>H2 database</li>\
         </ul>')
-
-//- Mixin for show/hide links.
-mixin showHideLink(name, text)
-    span(ng-init='__ = {};')
-        a.customize(ng-show=`__.${name}` ng-click=`__.${name} = false`) Hide 
#{text}
-        a.customize(ng-hide=`__.${name}` ng-click=`__.${name} = true; 
ui.loadPanel('${name}');`) Show #{text}
-        div(ng-if=`ui.isPanelLoaded('${name}')`)
-            .panel-details(ng-show=`__.${name}`)
-                if block
-                    block

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/agent/AgentManager.service.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/modules/agent/AgentManager.service.js 
b/modules/web-console/frontend/app/modules/agent/AgentManager.service.js
index 0e1c1bf..ca8d812 100644
--- a/modules/web-console/frontend/app/modules/agent/AgentManager.service.js
+++ b/modules/web-console/frontend/app/modules/agent/AgentManager.service.js
@@ -15,6 +15,9 @@
  * limitations under the License.
  */
 
+import _ from 'lodash';
+import {nonEmpty, nonNil} from 'app/utils/lodashMixins';
+
 import { BehaviorSubject } from 'rxjs/BehaviorSubject';
 
 import Worker from 'worker!./decompress.worker';
@@ -58,7 +61,7 @@ class ConnectionState {
         if (_.isNil(this.cluster))
             this.cluster = _.head(clusters);
 
-        if (_.nonNil(this.cluster))
+        if (nonNil(this.cluster))
             this.cluster.connected = !!_.find(clusters, {id: this.cluster.id});
 
         if (count === 0)
@@ -70,7 +73,7 @@ class ConnectionState {
     }
 
     useConnectedCluster() {
-        if (_.nonEmpty(this.clusters) && !this.cluster.connected) {
+        if (nonEmpty(this.clusters) && !this.cluster.connected) {
             this.cluster = _.head(this.clusters);
 
             this.cluster.connected = true;
@@ -148,7 +151,7 @@ export default class IgniteAgentManager {
     connect() {
         const self = this;
 
-        if (_.nonNil(self.socket))
+        if (nonNil(self.socket))
             return;
 
         self.socket = self.socketFactory();

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/configuration/generator/Beans.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/modules/configuration/generator/Beans.js 
b/modules/web-console/frontend/app/modules/configuration/generator/Beans.js
index f4d86f7..3bd0c0f 100644
--- a/modules/web-console/frontend/app/modules/configuration/generator/Beans.js
+++ b/modules/web-console/frontend/app/modules/configuration/generator/Beans.js
@@ -17,9 +17,17 @@
 
 import _ from 'lodash';
 
-_.mixin({
-    nonNil: _.negate(_.isNil),
-    nonEmpty: _.negate(_.isEmpty)
+import negate from 'lodash/negate';
+import isNil from 'lodash/isNil';
+import isEmpty from 'lodash/isEmpty';
+import mixin from 'lodash/mixin';
+
+const nonNil = negate(isNil);
+const nonEmpty = negate(isEmpty);
+
+mixin({
+    nonNil,
+    nonEmpty
 });
 
 export class EmptyBean {
@@ -42,7 +50,7 @@ export class EmptyBean {
     }
 
     isComplex() {
-        return _.nonEmpty(this.properties) || !!_.find(this.arguments, (arg) 
=> arg.clsName === 'MAP');
+        return nonEmpty(this.properties) || !!_.find(this.arguments, (arg) => 
arg.clsName === 'MAP');
     }
 
     nonComplex() {
@@ -99,7 +107,7 @@ export class Bean extends EmptyBean {
     }
 
     isEmpty() {
-        return _.isEmpty(this.arguments) && _.isEmpty(this.properties);
+        return isEmpty(this.arguments) && isEmpty(this.properties);
     }
 
     constructorArgument(clsName, value) {
@@ -109,23 +117,23 @@ export class Bean extends EmptyBean {
     }
 
     stringConstructorArgument(model) {
-        return this._property(this.arguments, 'java.lang.String', model, null, 
_.nonEmpty);
+        return this._property(this.arguments, 'java.lang.String', model, null, 
nonEmpty);
     }
 
     intConstructorArgument(model) {
-        return this._property(this.arguments, 'int', model, null, _.nonNil);
+        return this._property(this.arguments, 'int', model, null, nonNil);
     }
 
     boolConstructorArgument(model) {
-        return this._property(this.arguments, 'boolean', model, null, 
_.nonNil);
+        return this._property(this.arguments, 'boolean', model, null, nonNil);
     }
 
     classConstructorArgument(model) {
-        return this._property(this.arguments, 'java.lang.Class', model, null, 
_.nonEmpty);
+        return this._property(this.arguments, 'java.lang.Class', model, null, 
nonEmpty);
     }
 
     pathConstructorArgument(model) {
-        return this._property(this.arguments, 'PATH', model, null, _.nonEmpty);
+        return this._property(this.arguments, 'PATH', model, null, nonEmpty);
     }
 
     constantConstructorArgument(model) {
@@ -135,7 +143,7 @@ export class Bean extends EmptyBean {
         const value = _.get(this.src, model);
         const dflt = _.get(this.dflts, model);
 
-        if (_.nonNil(value) && _.nonNil(dflt) && value !== dflt.value)
+        if (nonNil(value) && nonNil(dflt) && value !== dflt.value)
             this.arguments.push({clsName: dflt.clsName, constant: true, 
value});
 
         return this;
@@ -170,7 +178,7 @@ export class Bean extends EmptyBean {
 
         const dflt = _.get(this.dflts, model);
 
-        if (_.nonEmpty(entries) && _.nonNil(dflt) && entries !== dflt.entries) 
{
+        if (nonEmpty(entries) && nonNil(dflt) && entries !== dflt.entries) {
             this.arguments.push({
                 clsName: 'MAP',
                 id,
@@ -194,7 +202,7 @@ export class Bean extends EmptyBean {
             const value = _.get(this.src, path);
             const dflt = _.get(this.dflts, path);
 
-            return _.nonNil(value) && value !== dflt;
+            return nonNil(value) && value !== dflt;
         });
     }
 
@@ -203,27 +211,27 @@ export class Bean extends EmptyBean {
     }
 
     boolProperty(model, name = model) {
-        return this._property(this.properties, 'boolean', model, name, 
_.nonNil);
+        return this._property(this.properties, 'boolean', model, name, nonNil);
     }
 
     byteProperty(model, name = model) {
-        return this._property(this.properties, 'byte', model, name, _.nonNil);
+        return this._property(this.properties, 'byte', model, name, nonNil);
     }
 
     intProperty(model, name = model) {
-        return this._property(this.properties, 'int', model, name, _.nonNil);
+        return this._property(this.properties, 'int', model, name, nonNil);
     }
 
     longProperty(model, name = model) {
-        return this._property(this.properties, 'long', model, name, _.nonNil);
+        return this._property(this.properties, 'long', model, name, nonNil);
     }
 
     floatProperty(model, name = model) {
-        return this._property(this.properties, 'float', model, name, _.nonNil);
+        return this._property(this.properties, 'float', model, name, nonNil);
     }
 
     doubleProperty(model, name = model) {
-        return this._property(this.properties, 'double', model, name, 
_.nonNil);
+        return this._property(this.properties, 'double', model, name, nonNil);
     }
 
     property(name, value, hint) {
@@ -245,15 +253,15 @@ export class Bean extends EmptyBean {
     }
 
     stringProperty(model, name = model, mapper) {
-        return this._property(this.properties, 'java.lang.String', model, 
name, _.nonEmpty, mapper);
+        return this._property(this.properties, 'java.lang.String', model, 
name, nonEmpty, mapper);
     }
 
     pathProperty(model, name = model) {
-        return this._property(this.properties, 'PATH', model, name, 
_.nonEmpty);
+        return this._property(this.properties, 'PATH', model, name, nonEmpty);
     }
 
     classProperty(model, name = model) {
-        return this._property(this.properties, 'java.lang.Class', model, name, 
_.nonEmpty);
+        return this._property(this.properties, 'java.lang.Class', model, name, 
nonEmpty);
     }
 
     enumProperty(model, name = model) {
@@ -263,7 +271,7 @@ export class Bean extends EmptyBean {
         const value = _.get(this.src, model);
         const dflt = _.get(this.dflts, model);
 
-        if (_.nonNil(value) && _.nonNil(dflt) && value !== dflt.value)
+        if (nonNil(value) && nonNil(dflt) && value !== dflt.value)
             this.properties.push({clsName: dflt.clsName, name, value: 
dflt.mapper ? dflt.mapper(value) : value});
 
         return this;
@@ -276,7 +284,7 @@ export class Bean extends EmptyBean {
         const cls = _.get(this.src, model);
         const dflt = _.get(this.dflts, model);
 
-        if (_.nonEmpty(cls) && cls !== dflt)
+        if (nonEmpty(cls) && cls !== dflt)
             this.properties.push({clsName: 'BEAN', name, value: new 
EmptyBean(cls)});
 
         return this;
@@ -350,7 +358,7 @@ export class Bean extends EmptyBean {
         const entries = _.isString(model) ? _.get(this.src, model) : model;
         const dflt = _.isString(model) ? _.get(this.dflts, model) : 
_.get(this.dflts, name);
 
-        if (_.nonEmpty(entries) && _.nonNil(dflt) && entries !== dflt.entries) 
{
+        if (nonEmpty(entries) && nonNil(dflt) && entries !== dflt.entries) {
             this.properties.push({
                 clsName: 'MAP',
                 id,
@@ -373,7 +381,7 @@ export class Bean extends EmptyBean {
 
         const entries = _.get(this.src, model);
 
-        if (_.nonEmpty(entries))
+        if (nonEmpty(entries))
             this.properties.push({clsName: 'java.util.Properties', id, name, 
entries});
 
         return this;

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/configuration/generator/ConfigurationGenerator.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/modules/configuration/generator/ConfigurationGenerator.js
 
b/modules/web-console/frontend/app/modules/configuration/generator/ConfigurationGenerator.js
index fa47de6..45d9ad1 100644
--- 
a/modules/web-console/frontend/app/modules/configuration/generator/ConfigurationGenerator.js
+++ 
b/modules/web-console/frontend/app/modules/configuration/generator/ConfigurationGenerator.js
@@ -27,6 +27,9 @@ import IgniteIGFSDefaults from './defaults/IGFS.service';
 import JavaTypes from '../../../services/JavaTypes.service';
 import VersionService from 'app/services/Version.service';
 
+import isNil from 'lodash/isNil';
+import {nonNil, nonEmpty} from 'app/utils/lodashMixins';
+
 const clusterDflts = new IgniteClusterDefaults();
 const cacheDflts = new IgniteCacheDefaults();
 const igfsDflts = new IgniteIGFSDefaults();
@@ -202,7 +205,7 @@ export default class IgniteConfigurationGenerator {
 
         cfg.stringProperty('localHost');
 
-        if (_.isNil(cluster.discovery))
+        if (isNil(cluster.discovery))
             return cfg;
 
         const discovery = new 
Bean('org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi', 'discovery',
@@ -348,7 +351,7 @@ export default class IgniteConfigurationGenerator {
                         case 'Custom':
                             const className = _.get(policy, 
'Custom.className');
 
-                            if (_.nonEmpty(className))
+                            if (nonEmpty(className))
                                 retryPolicyBean = new EmptyBean(className);
 
                             break;
@@ -435,7 +438,7 @@ export default class IgniteConfigurationGenerator {
         if (acfg.valueOf('cacheMode') === 'PARTITIONED')
             acfg.intProperty('backups');
 
-        if (available('2.1.0') && _.nonNil(atomics))
+        if (available('2.1.0') && nonNil(atomics))
             this.affinity(atomics.affinity, acfg);
 
         if (acfg.isEmpty())
@@ -775,7 +778,7 @@ export default class IgniteConfigurationGenerator {
                 default:
                     return null;
             }
-        }), (checkpointBean) => _.nonNil(checkpointBean));
+        }), (checkpointBean) => nonNil(checkpointBean));
 
         cfg.arrayProperty('checkpointSpi', 'checkpointSpi', cfgs, 
'org.apache.ignite.spi.checkpoint.CheckpointSpi');
 
@@ -863,7 +866,7 @@ export default class IgniteConfigurationGenerator {
 
                 break;
             case 'Custom':
-                if (_.nonNil(_.get(collision, 'Custom.class')))
+                if (nonNil(_.get(collision, 'Custom.class')))
                     colSpi = new EmptyBean(collision.Custom.class);
 
                 break;
@@ -871,7 +874,7 @@ export default class IgniteConfigurationGenerator {
                 return cfg;
         }
 
-        if (_.nonNil(colSpi))
+        if (nonNil(colSpi))
             cfg.beanProperty('collisionSpi', colSpi);
 
         return cfg;
@@ -1109,7 +1112,7 @@ export default class IgniteConfigurationGenerator {
             if (!eventStorageBean.isEmpty() || !available(['1.0.0', '2.0.0']))
                 cfg.beanProperty('eventStorageSpi', eventStorageBean);
 
-            if (_.nonEmpty(cluster.includeEventTypes)) {
+            if (nonEmpty(cluster.includeEventTypes)) {
                 const eventGrps = _.filter(this.eventGrps, ({value}) => 
_.includes(cluster.includeEventTypes, value));
 
                 cfg.eventTypes('evts', 'includeEventTypes', 
this.filterEvents(eventGrps, available));
@@ -1307,7 +1310,7 @@ export default class IgniteConfigurationGenerator {
 
         switch (_.get(logger, 'kind')) {
             case 'Log4j':
-                if (logger.Log4j && (logger.Log4j.mode === 'Default' || 
logger.Log4j.mode === 'Path' && _.nonEmpty(logger.Log4j.path))) {
+                if (logger.Log4j && (logger.Log4j.mode === 'Default' || 
logger.Log4j.mode === 'Path' && nonEmpty(logger.Log4j.path))) {
                     loggerBean = new 
Bean('org.apache.ignite.logger.log4j.Log4JLogger',
                         'logger', logger.Log4j, clusterDflts.logger.Log4j);
 
@@ -1319,7 +1322,7 @@ export default class IgniteConfigurationGenerator {
 
                 break;
             case 'Log4j2':
-                if (logger.Log4j2 && _.nonEmpty(logger.Log4j2.path)) {
+                if (logger.Log4j2 && nonEmpty(logger.Log4j2.path)) {
                     loggerBean = new 
Bean('org.apache.ignite.logger.log4j2.Log4J2Logger',
                         'logger', logger.Log4j2, clusterDflts.logger.Log4j2);
 
@@ -1345,7 +1348,7 @@ export default class IgniteConfigurationGenerator {
 
                 break;
             case 'Custom':
-                if (logger.Custom && _.nonEmpty(logger.Custom.class))
+                if (logger.Custom && nonEmpty(logger.Custom.class))
                     loggerBean = new EmptyBean(logger.Custom.class);
 
                 break;
@@ -1706,20 +1709,20 @@ export default class IgniteConfigurationGenerator {
 
     // Java code generator for cluster's SSL configuration.
     static clusterSsl(cluster, cfg = this.igniteConfigurationBean(cluster)) {
-        if (cluster.sslEnabled && _.nonNil(cluster.sslContextFactory)) {
+        if (cluster.sslEnabled && nonNil(cluster.sslContextFactory)) {
             const bean = new Bean('org.apache.ignite.ssl.SslContextFactory', 
'sslCtxFactory',
                 cluster.sslContextFactory);
 
             bean.intProperty('keyAlgorithm')
                 .pathProperty('keyStoreFilePath');
 
-            if (_.nonEmpty(bean.valueOf('keyStoreFilePath')))
+            if (nonEmpty(bean.valueOf('keyStoreFilePath')))
                 bean.propertyChar('keyStorePassword', 
'ssl.key.storage.password', 'YOUR_SSL_KEY_STORAGE_PASSWORD');
 
             bean.intProperty('keyStoreType')
                 .intProperty('protocol');
 
-            if (_.nonEmpty(cluster.sslContextFactory.trustManagers)) {
+            if (nonEmpty(cluster.sslContextFactory.trustManagers)) {
                 bean.arrayProperty('trustManagers', 'trustManagers',
                     _.map(cluster.sslContextFactory.trustManagers, (clsName) 
=> new EmptyBean(clsName)),
                     'javax.net.ssl.TrustManager');
@@ -1727,7 +1730,7 @@ export default class IgniteConfigurationGenerator {
             else {
                 bean.pathProperty('trustStoreFilePath');
 
-                if (_.nonEmpty(bean.valueOf('trustStoreFilePath')))
+                if (nonEmpty(bean.valueOf('trustStoreFilePath')))
                     bean.propertyChar('trustStorePassword', 
'ssl.trust.storage.password', 'YOUR_SSL_TRUST_STORAGE_PASSWORD');
 
                 bean.intProperty('trustStoreType');
@@ -1837,7 +1840,7 @@ export default class IgniteConfigurationGenerator {
     static domainModelGeneral(domain, cfg = 
this.domainConfigurationBean(domain)) {
         switch (cfg.valueOf('queryMetadata')) {
             case 'Annotations':
-                if (_.nonNil(domain.keyType) && _.nonNil(domain.valueType)) {
+                if (nonNil(domain.keyType) && nonNil(domain.valueType)) {
                     cfg.varArgProperty('indexedTypes', 'indexedTypes',
                         [javaTypes.fullClassName(domain.keyType), 
javaTypes.fullClassName(domain.valueType)],
                         'java.lang.Class');
@@ -2004,7 +2007,7 @@ export default class IgniteConfigurationGenerator {
         ccfg.intProperty('copyOnRead');
 
         if (ccfg.valueOf('cacheMode') === 'PARTITIONED' && 
ccfg.valueOf('atomicityMode') === 'TRANSACTIONAL')
-            ccfg.intProperty('invalidate');
+            ccfg.intProperty('isInvalidate', 'invalidate');
 
         return ccfg;
     }
@@ -2157,7 +2160,7 @@ export default class IgniteConfigurationGenerator {
                     };
 
                     const types = _.reduce(domains, (acc, domain) => {
-                        if (_.isNil(domain.databaseTable))
+                        if (isNil(domain.databaseTable))
                             return acc;
 
                         const typeBean = this.domainJdbcTypeBean(_.merge({}, 
domain, {cacheName: cache.name}))
@@ -2252,7 +2255,7 @@ export default class IgniteConfigurationGenerator {
 
         const settings = _.get(filter, kind);
 
-        if (!_.isNil(settings)) {
+        if (!isNil(settings)) {
             switch (kind) {
                 case 'IGFS':
                     const foundIgfs = _.find(igfss, {_id: settings.igfs});
@@ -2264,7 +2267,7 @@ export default class IgniteConfigurationGenerator {
 
                     break;
                 case 'Custom':
-                    if (_.nonEmpty(settings.className))
+                    if (nonEmpty(settings.className))
                         return new EmptyBean(settings.className);
 
                     break;
@@ -2355,7 +2358,7 @@ export default class IgniteConfigurationGenerator {
     // Generate domain models configs.
     static cacheDomains(domains, available, ccfg) {
         const qryEntities = _.reduce(domains, (acc, domain) => {
-            if (_.isNil(domain.queryMetadata) || domain.queryMetadata === 
'Configuration') {
+            if (isNil(domain.queryMetadata) || domain.queryMetadata === 
'Configuration') {
                 const qryEntity = this.domainModelGeneral(domain);
 
                 this.domainModelQuery(domain, available, qryEntity);

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/configuration/generator/JavaTransformer.service.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/modules/configuration/generator/JavaTransformer.service.js
 
b/modules/web-console/frontend/app/modules/configuration/generator/JavaTransformer.service.js
index a8d6d2d..ee31246 100644
--- 
a/modules/web-console/frontend/app/modules/configuration/generator/JavaTransformer.service.js
+++ 
b/modules/web-console/frontend/app/modules/configuration/generator/JavaTransformer.service.js
@@ -15,6 +15,8 @@
  * limitations under the License.
  */
 
+import {nonEmpty} from 'app/utils/lodashMixins';
+
 import AbstractTransformer from './AbstractTransformer';
 import StringBuilder from './StringBuilder';
 import VersionService from 'app/services/Version.service';
@@ -327,7 +329,7 @@ export default class IgniteJavaTransformer extends 
AbstractTransformer {
 
         sb.append(`${this.varInit(clsName, id, vars)} = 
${this._newBean(bean)};`);
 
-        if (_.nonEmpty(bean.properties)) {
+        if (nonEmpty(bean.properties)) {
             sb.emptyLine();
 
             this._setProperties(sb, bean, vars, limitLines, id);
@@ -660,7 +662,7 @@ export default class IgniteJavaTransformer extends 
AbstractTransformer {
                 case 'MAP':
                     this._constructMap(sb, prop, vars);
 
-                    if (_.nonEmpty(prop.entries))
+                    if (nonEmpty(prop.entries))
                         sb.emptyLine();
 
                     this._setProperty(sb, id, prop.name, prop.id);
@@ -669,7 +671,7 @@ export default class IgniteJavaTransformer extends 
AbstractTransformer {
                 case 'java.util.Properties':
                     sb.append(`${this.varInit('Properties', prop.id, vars)} = 
new Properties();`);
 
-                    if (_.nonEmpty(prop.entries))
+                    if (nonEmpty(prop.entries))
                         sb.emptyLine();
 
                     _.forEach(prop.entries, (entry) => {
@@ -904,7 +906,7 @@ export default class IgniteJavaTransformer extends 
AbstractTransformer {
 
         const nearCacheBeans = [];
 
-        if (_.nonEmpty(clientNearCaches)) {
+        if (nonEmpty(clientNearCaches)) {
             
imports.push('org.apache.ignite.configuration.NearCacheConfiguration');
 
             _.forEach(clientNearCaches, (cache) => {
@@ -1295,14 +1297,14 @@ export default class IgniteJavaTransformer extends 
AbstractTransformer {
                 // Process only  domains with 'generatePojo' flag and skip 
already generated classes.
                 if (domain.generatePojo && !_.find(pojos, {valueType: 
domain.valueType}) &&
                     // Skip domain models without value fields.
-                    _.nonEmpty(domain.valueFields)) {
+                    nonEmpty(domain.valueFields)) {
                     const pojo = {
                         keyType: domain.keyType,
                         valueType: domain.valueType
                     };
 
                     // Key class generation only if key is not build in java 
class.
-                    if (this.javaTypes.nonBuiltInClass(domain.keyType) && 
_.nonEmpty(domain.keyFields))
+                    if (this.javaTypes.nonBuiltInClass(domain.keyType) && 
nonEmpty(domain.keyFields))
                         pojo.keyClass = this.pojo(domain.keyType, 
domain.keyFields, addConstructor);
 
                     const valueFields = _.clone(domain.valueFields);
@@ -1368,10 +1370,10 @@ export default class IgniteJavaTransformer extends 
AbstractTransformer {
 
         // Prepare array of cache and his demo domain model list. Every domain 
is contained only in first cache.
         const demoTypes = _.reduce(cachesWithDataSource, (acc, cache) => {
-            const domains = _.filter(cache.domains, (domain) => 
_.nonEmpty(domain.valueFields) &&
+            const domains = _.filter(cache.domains, (domain) => 
nonEmpty(domain.valueFields) &&
                 !_.includes(uniqDomains, domain));
 
-            if (_.nonEmpty(domains)) {
+            if (nonEmpty(domains)) {
                 uniqDomains.push(...domains);
 
                 acc.push({
@@ -1383,7 +1385,7 @@ export default class IgniteJavaTransformer extends 
AbstractTransformer {
             return acc;
         }, []);
 
-        if (_.nonEmpty(demoTypes)) {
+        if (nonEmpty(demoTypes)) {
             // Group domain modes by data source
             const typeByDs = _.groupBy(demoTypes, ({cache}) => 
cache.cacheStoreFactory[cache.cacheStoreFactory.kind].dataSourceBean);
 
@@ -1611,7 +1613,7 @@ export default class IgniteJavaTransformer extends 
AbstractTransformer {
             shortFactoryCls = this.javaTypes.shortClassName(factoryCls);
         }
 
-        if ((_.nonEmpty(clientNearCaches) || demo) && shortFactoryCls)
+        if ((nonEmpty(clientNearCaches) || demo) && shortFactoryCls)
             imports.push('org.apache.ignite.Ignite');
 
         sb.append(`package ${pkg};`)
@@ -1658,7 +1660,7 @@ export default class IgniteJavaTransformer extends 
AbstractTransformer {
             sb.emptyLine();
         }
 
-        if ((_.nonEmpty(clientNearCaches) || demo) && shortFactoryCls) {
+        if ((nonEmpty(clientNearCaches) || demo) && shortFactoryCls) {
             imports.push('org.apache.ignite.Ignite');
 
             sb.append(`Ignite ignite = Ignition.start(${cfgRef});`);

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/configuration/generator/PlatformGenerator.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/modules/configuration/generator/PlatformGenerator.js
 
b/modules/web-console/frontend/app/modules/configuration/generator/PlatformGenerator.js
index 2f652d4..99b93cc 100644
--- 
a/modules/web-console/frontend/app/modules/configuration/generator/PlatformGenerator.js
+++ 
b/modules/web-console/frontend/app/modules/configuration/generator/PlatformGenerator.js
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-// import _ from 'lodash';
+import {nonEmpty} from 'app/utils/lodashMixins';
 import { EmptyBean, Bean } from './Beans';
 
 export default ['JavaTypes', 'igniteClusterPlatformDefaults', 
'igniteCachePlatformDefaults', (JavaTypes, clusterDflts, cacheDflts) => {
@@ -219,7 +219,7 @@ export default ['JavaTypes', 
'igniteClusterPlatformDefaults', 'igniteCachePlatfo
 
         // Generate events group.
         static clusterEvents(cluster, cfg = 
this.igniteConfigurationBean(cluster)) {
-            if (_.nonEmpty(cluster.includeEventTypes))
+            if (nonEmpty(cluster.includeEventTypes))
                 cfg.eventTypes('events', 'includeEventTypes', 
cluster.includeEventTypes);
 
             return cfg;
@@ -262,7 +262,7 @@ export default ['JavaTypes', 
'igniteClusterPlatformDefaults', 'igniteCachePlatfo
         static clusterCaches(cluster, caches, igfss, isSrvCfg, cfg = 
this.igniteConfigurationBean(cluster)) {
             // const cfg = this.clusterGeneral(cluster, cfg);
             //
-            // if (_.nonEmpty(caches)) {
+            // if (nonEmpty(caches)) {
             //     const ccfgs = _.map(caches, (cache) => 
this.cacheConfiguration(cache));
             //
             //     cfg.collectionProperty('', '', ccfgs, );
@@ -285,7 +285,7 @@ export default ['JavaTypes', 
'igniteClusterPlatformDefaults', 'igniteCachePlatfo
             ccfg.intProperty('copyOnRead');
 
             if (ccfg.valueOf('cacheMode') === 'PARTITIONED' && 
ccfg.valueOf('atomicityMode') === 'TRANSACTIONAL')
-                ccfg.intProperty('invalidate');
+                ccfg.intProperty('isInvalidate', 'invalidate');
 
             return ccfg;
         }

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/configuration/generator/SpringTransformer.service.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/modules/configuration/generator/SpringTransformer.service.js
 
b/modules/web-console/frontend/app/modules/configuration/generator/SpringTransformer.service.js
index a4b616c..3d2bac1 100644
--- 
a/modules/web-console/frontend/app/modules/configuration/generator/SpringTransformer.service.js
+++ 
b/modules/web-console/frontend/app/modules/configuration/generator/SpringTransformer.service.js
@@ -24,7 +24,7 @@ import VersionService from 'app/services/Version.service';
 const versionService = new VersionService();
 
 export default class IgniteSpringTransformer extends AbstractTransformer {
-    static escapeXml(str) {
+    static escapeXml(str = '') {
         return str.replace(/&/g, '&amp;')
             .replace(/"/g, '&quot;')
             .replace(/'/g, '&apos;')

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/demo/Demo.module.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/demo/Demo.module.js 
b/modules/web-console/frontend/app/modules/demo/Demo.module.js
index bf602f8..5dbd775 100644
--- a/modules/web-console/frontend/app/modules/demo/Demo.module.js
+++ b/modules/web-console/frontend/app/modules/demo/Demo.module.js
@@ -34,7 +34,7 @@ angular
         .state('demo.resume', {
             url: '/resume',
             permission: 'demo',
-            redirectTo: 'base.configuration.tabs',
+            redirectTo: 'base.configuration.overview',
             unsaved: true,
             tfMetaTags: {
                 title: 'Demo resume'
@@ -47,11 +47,11 @@ angular
                 const $http = trans.injector().get('$http');
 
                 return $http.post('/api/v1/demo/reset')
-                    .then(() => 'base.configuration.tabs')
+                    .then(() => 'base.configuration.overview')
                     .catch((err) => {
                         trans.injector().get('IgniteMessages').showError(err);
 
-                        return 'base.configuration.tabs';
+                        return 'base.configuration.overview';
                     });
             },
             unsaved: true,

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/form/field/bs-select-placeholder.directive.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/modules/form/field/bs-select-placeholder.directive.js
 
b/modules/web-console/frontend/app/modules/form/field/bs-select-placeholder.directive.js
index 83f438d..56aa82a 100644
--- 
a/modules/web-console/frontend/app/modules/form/field/bs-select-placeholder.directive.js
+++ 
b/modules/web-console/frontend/app/modules/form/field/bs-select-placeholder.directive.js
@@ -24,17 +24,19 @@ export default ['bsSelect', [() => {
         const $render = ngModel.$render;
 
         ngModel.$render = () => {
-            $render();
+            if (scope.$destroyed) return;
+            scope.$applyAsync(() => {
+                $render();
+                const value = ngModel.$viewValue;
 
-            const value = ngModel.$viewValue;
+                if (_.isNil(value) || (attrs.multiple && !value.length)) {
+                    $element.html(attrs.placeholder);
 
-            if (_.isNil(value) || (attrs.multiple && !value.length)) {
-                $element.html(attrs.placeholder);
-
-                $element.addClass('placeholder');
-            }
-            else
-                $element.removeClass('placeholder');
+                    $element.addClass('placeholder');
+                }
+                else
+                    $element.removeClass('placeholder');
+            });
         };
     };
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/form/form.module.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/form/form.module.js 
b/modules/web-console/frontend/app/modules/form/form.module.js
index 01dd57c..59fedff 100644
--- a/modules/web-console/frontend/app/modules/form/form.module.js
+++ b/modules/web-console/frontend/app/modules/form/form.module.js
@@ -23,10 +23,7 @@ import './field/feedback.scss';
 import './field/input/text.scss';
 
 // Panel.
-import igniteFormPanel from './panel/panel.directive';
-import igniteFormPanelField from './panel/field.directive';
 import igniteFormPanelChevron from './panel/chevron.directive';
-import igniteFormRevert from './panel/revert.directive';
 
 // Field.
 import igniteFormFieldLabel from './field/label.directive';
@@ -62,10 +59,7 @@ angular
 
 ])
 // Panel.
-.directive(...igniteFormPanel)
-.directive(...igniteFormPanelField)
 .directive(...igniteFormPanelChevron)
-.directive(...igniteFormRevert)
 // Field.
 .directive(...igniteFormFieldLabel)
 .directive(...igniteFormFieldTooltip)

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/form/panel/chevron.directive.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/modules/form/panel/chevron.directive.js 
b/modules/web-console/frontend/app/modules/form/panel/chevron.directive.js
index 6af560b..f5ad957 100644
--- a/modules/web-console/frontend/app/modules/form/panel/chevron.directive.js
+++ b/modules/web-console/frontend/app/modules/form/panel/chevron.directive.js
@@ -15,15 +15,15 @@
  * limitations under the License.
  */
 
-const template = `<i class='fa' ng-class='isOpen ? "fa-chevron-circle-down" : 
"fa-chevron-circle-right"'></i>`; // eslint-disable-line quotes
+const template = `<img ng-src="{{ isOpen ? '/images/collapse.svg' : 
'/images/expand.svg' }}" style='width:13px;height:13px;' />`;
 
-export default ['igniteFormPanelChevron', [() => {
+export default ['igniteFormPanelChevron', ['$timeout', ($timeout) => {
     const controller = [() => {}];
 
     const link = ($scope, $element, $attrs, [bsCollapseCtrl]) => {
-        const $target = $element.parent().parent().find('.panel-collapse');
+        const $target = 
$element.parent().parent().find('[bs-collapse-target]');
 
-        bsCollapseCtrl.$viewChangeListeners.push(function() {
+        const listener = function() {
             const index = bsCollapseCtrl.$targets.reduce((acc, el, i) => {
                 if (el[0] === $target[0])
                     acc.push(i);
@@ -37,7 +37,10 @@ export default ['igniteFormPanelChevron', [() => {
 
             if ((active instanceof Array) && active.indexOf(index) !== -1 || 
active === index)
                 $scope.isOpen = true;
-        });
+        };
+
+        bsCollapseCtrl.$viewChangeListeners.push(listener);
+        $timeout(listener);
     };
 
     return {
@@ -46,8 +49,8 @@ export default ['igniteFormPanelChevron', [() => {
         link,
         template,
         controller,
-        replace: true,
-        transclude: true,
+        // replace: true,
+        // transclude: true,
         require: ['^bsCollapse']
     };
 }]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/form/panel/field.directive.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/modules/form/panel/field.directive.js 
b/modules/web-console/frontend/app/modules/form/panel/field.directive.js
deleted file mode 100644
index cf8101a..0000000
--- a/modules/web-console/frontend/app/modules/form/panel/field.directive.js
+++ /dev/null
@@ -1,69 +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 ['igniteFormPanelField', ['$parse', 'IgniteLegacyTable', 
($parse, LegacyTable) => {
-    const link = (scope, element, attrs, [ngModelCtrl, formCtrl]) => {
-        formCtrl.$defaults = formCtrl.$defaults || {};
-
-        const { name, ngModel } = attrs;
-        const getter = () => $parse(ngModel)(scope);
-
-        const saveDefault = () => {
-            formCtrl.$defaults[name] = _.cloneDeep(getter());
-        };
-
-        const resetDefault = () => {
-            ngModelCtrl.$viewValue = formCtrl.$defaults[name];
-
-            ngModelCtrl.$valid = true;
-            ngModelCtrl.$invalid = false;
-            ngModelCtrl.$error = {};
-            ngModelCtrl.$render();
-        };
-
-        if (!(_.isNull(formCtrl.$defaults[name]) || 
_.isUndefined(formCtrl.$defaults[name])))
-            resetDefault();
-        else
-            saveDefault();
-
-        scope.tableReset = (trySave) => {
-            if (trySave === false || !LegacyTable.tableSaveAndReset())
-                LegacyTable.tableReset();
-        };
-
-        scope.$watch(() => formCtrl.$pristine, () => {
-            if (!formCtrl.$pristine)
-                return;
-
-            saveDefault();
-            resetDefault();
-        });
-
-        scope.$watch(() => ngModelCtrl.$modelValue, () => {
-            if (!formCtrl.$pristine)
-                return;
-
-            saveDefault();
-        });
-    };
-
-    return {
-        restrict: 'A',
-        link,
-        require: ['ngModel', '^form']
-    };
-}]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/form/panel/panel.directive.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/modules/form/panel/panel.directive.js 
b/modules/web-console/frontend/app/modules/form/panel/panel.directive.js
deleted file mode 100644
index b8e7c25..0000000
--- a/modules/web-console/frontend/app/modules/form/panel/panel.directive.js
+++ /dev/null
@@ -1,37 +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 ['form', [() => {
-    const link = (scope, $element, $attrs, [form]) => {
-        const $form = $element.parent().closest('form');
-
-        scope.$watch(() => {
-            return $form.hasClass('ng-pristine');
-        }, (value) => {
-            if (!value)
-                return;
-
-            form.$setPristine();
-        });
-    };
-
-    return {
-        restrict: 'E',
-        link,
-        require: ['^form']
-    };
-}]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/form/panel/revert.directive.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/modules/form/panel/revert.directive.js 
b/modules/web-console/frontend/app/modules/form/panel/revert.directive.js
deleted file mode 100644
index c2454fd..0000000
--- a/modules/web-console/frontend/app/modules/form/panel/revert.directive.js
+++ /dev/null
@@ -1,54 +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.
- */
-
-const template = '<i ng-show="form.$dirty" class="fa fa-undo pull-right" 
ng-click="revert($event)"></i>';
-
-export default ['igniteFormRevert', ['$tooltip', 'IgniteLegacyTable', 
($tooltip, LegacyTable) => {
-    const link = (scope, $element, $attrs, [form]) => {
-        $tooltip($element, { title: 'Undo unsaved changes' });
-
-        scope.form = form;
-
-        scope.revert = (e) => {
-            e.stopPropagation();
-
-            LegacyTable.tableReset();
-
-            _.forOwn(form.$defaults, (value, name) => {
-                const field = form[name];
-
-                if (field) {
-                    field.$viewValue = value;
-                    field.$setViewValue && field.$setViewValue(value);
-                    field.$setPristine();
-                    field.$render && field.$render();
-                }
-            });
-
-            form.$setPristine();
-        };
-    };
-
-    return {
-        restrict: 'E',
-        scope: { },
-        template,
-        link,
-        replace: true,
-        require: ['^form']
-    };
-}]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/form/validator/java-identifier.directive.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/modules/form/validator/java-identifier.directive.js
 
b/modules/web-console/frontend/app/modules/form/validator/java-identifier.directive.js
index 21ebfa0..a61d309 100644
--- 
a/modules/web-console/frontend/app/modules/form/validator/java-identifier.directive.js
+++ 
b/modules/web-console/frontend/app/modules/form/validator/java-identifier.directive.js
@@ -20,8 +20,11 @@ export default ['javaIdentifier', ['JavaTypes', (JavaTypes) 
=> {
         if (_.isNil(attrs.javaIdentifier) || attrs.javaIdentifier !== 'true')
             return;
 
+        /** @type {Array<string>} */
+        const extraValidIdentifiers = 
scope.$eval(attrs.extraValidJavaIdentifiers) || [];
+
         ngModel.$validators.javaIdentifier = (value) => attrs.validationActive 
=== 'false' ||
-            _.isEmpty(value) || JavaTypes.validClassName(value);
+            _.isEmpty(value) || JavaTypes.validClassName(value) || 
extraValidIdentifiers.includes(value);
 
         if (attrs.validationActive !== 'always')
             attrs.$observe('validationActive', () => ngModel.$validate());

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/form/validator/unique.directive.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/modules/form/validator/unique.directive.js 
b/modules/web-console/frontend/app/modules/form/validator/unique.directive.js
index 0e6af18..d32c565 100644
--- 
a/modules/web-console/frontend/app/modules/form/validator/unique.directive.js
+++ 
b/modules/web-console/frontend/app/modules/form/validator/unique.directive.js
@@ -15,35 +15,69 @@
  * limitations under the License.
  */
 
-export default ['igniteUnique', ['$parse', ($parse) => {
-    const link = (scope, el, attrs, [ngModel]) => {
-        if (_.isUndefined(attrs.igniteUnique) || !attrs.igniteUnique)
-            return;
+import {ListEditableTransclude} from 
'app/components/list-editable/components/list-editable-transclude/directive';
+import isNumber from 'lodash/fp/isNumber';
+import get from 'lodash/fp/get';
 
-        const isNew = _.startsWith(attrs.name, 'new');
-        const property = attrs.igniteUniqueProperty;
+class Controller {
+    /** @type {ng.INgModelController} */
+    ngModel;
+    /** @type {ListEditableTransclude} */
+    listEditableTransclude;
+    /** @type {Array} */
+    items;
 
-        ngModel.$validators.igniteUnique = (value) => {
-            const arr = $parse(attrs.igniteUnique)(scope);
+    static $inject = ['$scope'];
 
-            // Return true in case if array not exist, array empty.
-            if (!arr || !arr.length)
-                return true;
+    constructor($scope) {
+        this.$scope = $scope;
+    }
 
-            const idx = _.findIndex(arr, (item) => (property ? item[property] 
: item) === value);
+    $onInit() {
+        const isNew = this.key && this.key.startsWith('new');
+        const shouldNotSkip = (item) => get(this.skip[0], item) !== 
get(...this.skip);
 
-            // In case of new element check all items.
-            if (isNew)
-                return idx < 0;
+        this.ngModel.$validators.igniteUnique = (value) => {
+            const matches = (item) => (this.key ? item[this.key] : item) === 
value;
 
-            // Check for $index in case of editing in-place.
-            return (_.isNumber(scope.$index) && (idx < 0 || scope.$index === 
idx));
+            if (!this.skip) {
+                // Return true in case if array not exist, array empty.
+                if (!this.items || !this.items.length) return true;
+
+                const idx = this.items.findIndex(matches);
+
+                // In case of new element check all items.
+                if (isNew) return idx < 0;
+
+                // Case for new component list editable.
+                const $index = this.listEditableTransclude
+                    ? this.listEditableTransclude.$index
+                    : isNumber(this.$scope.$index) ? this.$scope.$index : void 
0;
+
+                // Check for $index in case of editing in-place.
+                return (isNumber($index) && (idx < 0 || $index === idx));
+            }
+            // TODO: converge both branches, use $index as idKey
+            return !(this.items || []).filter(shouldNotSkip).some(matches);
         };
-    };
+    }
+
+    $onChanges(changes) {
+        this.ngModel.$validate();
+    }
+}
 
+export default ['igniteUnique', () => {
     return {
-        restrict: 'A',
-        link,
-        require: ['ngModel']
+        controller: Controller,
+        require: {
+            ngModel: 'ngModel',
+            listEditableTransclude: '?^listEditableTransclude'
+        },
+        bindToController: {
+            items: '<igniteUnique',
+            key: '@?igniteUniqueProperty',
+            skip: '<?igniteUniqueSkip'
+        }
     };
-}]];
+}];

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/nodes/nodes-dialog.controller.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/modules/nodes/nodes-dialog.controller.js 
b/modules/web-console/frontend/app/modules/nodes/nodes-dialog.controller.js
index 3e588ac..fbe6203 100644
--- a/modules/web-console/frontend/app/modules/nodes/nodes-dialog.controller.js
+++ b/modules/web-console/frontend/app/modules/nodes/nodes-dialog.controller.js
@@ -29,7 +29,7 @@ export default ['$scope', '$animate', 'uiGridConstants', 
'nodes', 'options', fun
     const $ctrl = this;
 
     const updateSelected = () => {
-        const nids = $ctrl.gridApi.selection.getSelectedRows().map((node) => 
node.nid).sort();
+        const nids = 
$ctrl.gridApi.selection.legacyGetSelectedRows().map((node) => node.nid).sort();
 
         if (!_.isEqual(nids, $ctrl.selected))
             $ctrl.selected = nids;

Reply via email to