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

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

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/clusters/general.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/states/configuration/clusters/general.pug b/modules/web-console/frontend/app/modules/states/configuration/clusters/general.pug
index e28dd33..d31eba6 100644
--- a/modules/web-console/frontend/app/modules/states/configuration/clusters/general.pug
+++ b/modules/web-console/frontend/app/modules/states/configuration/clusters/general.pug
@@ -17,7 +17,7 @@
 include /app/helpers/jade/mixins
 
 -var form = 'general'
--var model = 'backupItem'
+-var model = '$ctrl.clonedCluster'
 -var modelDiscoveryKind = model + '.discovery.kind'
 
 include ./general/discovery/cloud
@@ -30,60 +30,63 @@ include ./general/discovery/vm
 include ./general/discovery/zookeeper
 include ./general/discovery/kubernetes
 
-.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
-            | Common cluster configuration#[br]
-            | #[a(href="https://apacheignite.readme.io/docs/clustering" target="_blank") More info]
-        ignite-form-revert
-    .panel-collapse(role='tabpanel' bs-collapse-target id=`${form}`)
-        .panel-body
-            .col-sm-6
-                .settings-row
-                    +text('Name:', `${model}.name`, '"clusterName"', 'true', 'Input name', 'Instance name allows to indicate to what grid this particular grid instance belongs to')
-                .settings-row
-                    +caches(model, 'Select caches to start in cluster or add a new cache')
-                .settings-row
+        .pca-panel-heading-title General
+        .pca-panel-heading-description
+            | Common cluster configuration.
+            | #[a.link-success(href="https://apacheignite.readme.io/docs/clustering" target="_blank") More info]
+    .pca-panel-collapse(role='tabpanel' bs-collapse-target id=`${form}`)
+        .pca-panel-body.pca-form-row
+            .pca-form-column-6.pc-form-grid-row
+                .pc-form-grid-col-30
+                    +sane-ignite-form-field-text({
+                        label: 'Name:',
+                        model: `${model}.name`,
+                        name: '"clusterName"',
+                        disabled: 'false',
+                        placeholder: 'Input name',
+                        required: true,
+                        tip: 'Instance name allows to indicate to what grid this particular grid instance belongs to'
+                    })(
+                        ignite-unique='$ctrl.shortClusters'
+                        ignite-unique-property='name'
+                        ignite-unique-skip=`["_id", ${model}]`
+                    )
+                        +unique-feedback(`${model}.name`, 'Cluster name should be unique.')
+
+                .pc-form-grid-col-30
                     +text-ip-address('Local host:', `${model}.localHost`, '"localHost"', 'true', '0.0.0.0',
                         'System-wide local address or host for all Ignite components to bind to<br/>\
                         If not defined then Ignite tries to use local wildcard address<br/>\
                         That means that all services will be available on all network interfaces of the host machine')
-                .settings-row
-                    +dropdown('Discovery:', `${model}.discovery.kind`, '"discovery"', 'true', 'Choose discovery', 'discoveries',
-                        'Discovery allows to discover remote nodes in grid\
-                        <ul>\
-                            <li>Static IPs - IP Finder which works only with pre configured list of IP addresses specified</li>\
-                            <li>Multicast - Multicast based IP finder</li>\
-                            <li>AWS S3 - AWS S3 based IP finder that automatically discover cluster nodes on Amazon EC2 cloud</li>\
-                            <li>Apache jclouds - Apache jclouds multi cloud toolkit based IP finder for cloud platforms with unstable IP addresses</li>\
-                            <li>Google cloud storage - Google Cloud Storage based IP finder that automatically discover cluster nodes on Google Compute Engine cluster</li>\
-                            <li>JDBC - JDBC based IP finder that use database to store node IP address</li>\
-                            <li>Shared filesystem - Shared filesystem based IP finder that use file to store node IP address</li>\
-                            <li>Apache ZooKeeper - Apache ZooKeeper based IP finder when you use ZooKeeper to coordinate your distributed environment</li>\
-                            <li>Kubernetes - IP finder for automatic lookup of Ignite nodes running in Kubernetes environment</li>\
-                        </ul>')
-                .settings-row
-                    .panel-details
-                        div(ng-if=`${modelDiscoveryKind} === 'Cloud'`)
-                            +discovery-cloud()
-                        div(ng-if=`${modelDiscoveryKind} === 'GoogleStorage'`)
-                            +discovery-google()
-                        div(ng-if=`${modelDiscoveryKind} === 'Jdbc'`)
-                            +discovery-jdbc()
-                        div(ng-if=`${modelDiscoveryKind} === 'Multicast'`)
-                            +discovery-multicast()
-                        div(ng-if=`${modelDiscoveryKind} === 'S3'`)
-                            +discovery-s3()
-                        div(ng-if=`${modelDiscoveryKind} === 'SharedFs'`)
-                            +discovery-shared()
-                        div(ng-if=`${modelDiscoveryKind} === 'Vm'`)
-                            +discovery-vm()
-                        div(ng-if=`${modelDiscoveryKind} === 'ZooKeeper'`)
-                            +discovery-zookeeper()
-                        div(ng-if=`${modelDiscoveryKind} === 'Kubernetes'`)
-                            +discovery-kubernetes()
-            .col-sm-6
-                -var model = 'backupItem'
-                +preview-xml-java(model, 'clusterCaches', 'caches')
+
+                .pc-form-grid-col-60
+                    +dropdown('Discovery:', `${model}.discovery.kind`, '"discovery"', 'true', 'Choose discovery', '$ctrl.Clusters.discoveries',
+                    'Discovery allows to discover remote nodes in grid\
+                    <ul>\
+                        <li>Static IPs - IP Finder which works only with pre configured list of IP addresses specified</li>\
+                        <li>Multicast - Multicast based IP finder</li>\
+                        <li>AWS S3 - AWS S3 based IP finder that automatically discover cluster nodes on Amazon EC2 cloud</li>\
+                        <li>Apache jclouds - Apache jclouds multi cloud toolkit based IP finder for cloud platforms with unstable IP addresses</li>\
+                        <li>Google cloud storage - Google Cloud Storage based IP finder that automatically discover cluster nodes on Google Compute Engine cluster</li>\
+                        <li>JDBC - JDBC based IP finder that use database to store node IP address</li>\
+                        <li>Shared filesystem - Shared filesystem based IP finder that use file to store node IP address</li>\
+                        <li>Apache ZooKeeper - Apache ZooKeeper based IP finder when you use ZooKeeper to coordinate your distributed environment</li>\
+                        <li>Kubernetes - IP finder for automatic lookup of Ignite nodes running in Kubernetes environment</li>\
+                    </ul>')
+                .pc-form-group
+                    +discovery-cloud()(ng-if=`${modelDiscoveryKind} === 'Cloud'`)
+                    +discovery-google()(ng-if=`${modelDiscoveryKind} === 'GoogleStorage'`)
+                    +discovery-jdbc()(ng-if=`${modelDiscoveryKind} === 'Jdbc'`)
+                    +discovery-multicast()(ng-if=`${modelDiscoveryKind} === 'Multicast'`)
+                    +discovery-s3()(ng-if=`${modelDiscoveryKind} === 'S3'`)
+                    +discovery-shared()(ng-if=`${modelDiscoveryKind} === 'SharedFs'`)
+                    +discovery-vm()(ng-if=`${modelDiscoveryKind} === 'Vm'`)
+                    +discovery-zookeeper()(ng-if=`${modelDiscoveryKind} === 'ZooKeeper'`)
+                    +discovery-kubernetes()(ng-if=`${modelDiscoveryKind} === 'Kubernetes'`)
+
+            .pca-form-column-6
+                -var model = '$ctrl.clonedCluster'
+                +preview-xml-java(model, 'clusterGeneral')

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/cloud.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/cloud.pug b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/cloud.pug
index a6f9158..074756e 100644
--- a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/cloud.pug
+++ b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/cloud.pug
@@ -16,7 +16,7 @@
 
 include /app/helpers/jade/mixins
 
-mixin discovery-cloud(modelAt='backupItem')
+mixin discovery-cloud(modelAt='$ctrl.clonedCluster')
 
     -const model = `${modelAt}.discovery.Cloud`
     -const discoveryKind = 'Cloud'
@@ -26,113 +26,53 @@ mixin discovery-cloud(modelAt='backupItem')
     -const formRegions = 'discoveryCloudRegions'
     -const formZones = 'discoveryCloudZones'
 
-    div
-        .details-row
+    div.pc-form-grid-row&attributes(attributes=attributes)
+        .pc-form-grid-col-30
             +text('Credential:', `${model}.credential`, '"credential"', 'false', 'Input cloud credential',
                 'Credential that is used during authentication on the cloud<br/>\
                 Depending on a cloud platform it can be a password or access key')
-        .details-row
-            +text('Path to credential:', `${model}.credentialPath`, '"credentialPath"', 'false', 'Input pathto credential',
+        .pc-form-grid-col-30
+            +text('Path to credential:', `${model}.credentialPath`, '"credentialPath"', 'false', 'Input path to credential',
                 'Path to a credential that is used during authentication on the cloud<br/>\
                 Access key or private key should be stored in a plain or PEM file without a passphrase')
-        .details-row
+        .pc-form-grid-col-30
             +text('Identity:', `${model}.identity`, '"' + discoveryKind + 'Identity"', required, 'Input identity',
                 'Identity that is used as a user name during a connection to the cloud<br/>\
                 Depending on a cloud platform it can be an email address, user name, etc')
-        .details-row
+        .pc-form-grid-col-30
             +text('Provider:', `${model}.provider`, '"' + discoveryKind + 'Provider"', required, 'Input provider', 'Cloud provider to use')
-        .pcb-flex-grid-break
-        .pcb-flex-grid-break
-        .details-row
-            -var form = formRegions;
-            +ignite-form-group(ng-model=`${regions}` ng-form=form)
-                -var uniqueTip = 'Such region already exists!'
-
-                ignite-form-field-label
-                    | Regions
-                ignite-form-group-tooltip
-                    | List of regions where VMs are located#[br]
-                    | If the regions are not set then every region, that a cloud provider has, will be investigated. This could lead to significant performance degradation#[br]
-                    | Note, that some cloud providers, like Google Compute Engine, doesn't have a notion of a region. For such providers regions are redundant
-                ignite-form-group-add(ng-click='group.add = [{}]')
-                    | Add new region
-
-                .group-content(ng-if=`${regions}.length`)
-                    -var model = 'field.model';
-                    -var name = '"edit" + $index'
-                    -var valid = `${form}[${name}].$valid`
-                    -var save = `${regions}[$index] = ${model}`
-
-                    div(ng-repeat=`model in ${regions} track by $index`)
-                        label.col-xs-12.col-sm-12.col-md-12(ng-init='field = {}')
-                            .indexField
-                                | {{ $index+1 }})
-                            +table-remove-button(regions, 'Remove region')
-                            span(ng-hide='field.edit')
-                                a.labelFormField(ng-click=`field.edit = true; ${model} = model;`) {{ model }}
-                            span(ng-if='field.edit')
-                                +table-text-field(name, model, regions, valid, save, 'Region name', false)
-                                    +table-save-button(valid, save, false)
-                                    +unique-feedback(name, uniqueTip)
-
-                .group-content(ng-repeat='field in group.add')
-                    -var model = 'field.new';
-                    -var name = '"new"'
-                    -var valid = `${form}[${name}].$valid`
-                    -var save = `${regions}.push(${model})`
-
-                    div
-                        label.col-xs-12.col-sm-12.col-md-12
-                            +table-text-field(name, model, regions, valid, save, 'Region name', true)
-                                +table-save-button(valid, save, false)
-                                +unique-feedback(name, uniqueTip)
-
-                .group-content-empty(ng-if=`!(${regions}.length) && !group.add.length`)
-                    | Not defined
-        .details-row
-            -var form = formZones;
-            +ignite-form-group(ng-model=zones ng-form=form)
-                -var uniqueTip = 'Such zone already exists!'
-
-                ignite-form-field-label
-                    | Zones
-                ignite-form-group-tooltip
-                    | List of zones where VMs are located#[br]
-                    | If the zones are not set then every zone from specified regions, will be taken into account#[br]
-                    | Note, that some cloud providers, like Rackspace, doesn't have a notion of a zone. For such providers zones are redundant
-                ignite-form-group-add(ng-click='group.add = [{}]')
-                    | Add new zone
-
-                -var form = formZones;
-                .group-content(ng-if=`${zones}.length`)
-                    -var model = 'field.model';
-                    -var name = '"edit" + $index'
-                    -var valid = `${form}[${name}].$valid`
-                    -var save = `${zones}[$index] = ${model}`
-
-                    div(ng-repeat=`model in ${zones} track by $index`)
-                        label.col-xs-12.col-sm-12.col-md-12(ng-init='field = {}')
-                            .indexField
-                                | {{ $index+1 }})
-                            +table-remove-button(zones, 'Remove zone')
-                            span(ng-hide='field.edit')
-                                a.labelFormField(ng-click=`field.edit = true; ${model} = model;`) {{ model }}
-                            span(ng-if='field.edit')
-                                +table-text-field(name, model, zones, valid, save, 'Zone name', false)
-                                    +table-save-button(valid, save, false)
-                                    +unique-feedback(name, uniqueTip)
-
-                .group-content(ng-repeat='field in group.add')
-                    -var model = 'field.new';
-                    -var name = '"new"'
-                    -var valid = `${form}[${name}].$valid`
-                    -var save = `${zones}.push(${model})`
-
-                    div
-                        label.col-xs-12.col-sm-12.col-md-12
-                            +table-text-field(name, model, zones, valid, save, 'Zone name', true)
-                                +table-save-button(valid, save, true)
-                                +unique-feedback(name, uniqueTip)
-
-                .group-content-empty(ng-if=`!(${zones}.length) && !group.add.length`)
-                    | Not defined
+        .pc-form-grid-col-60
+            .ignite-form-field
+                +list-text-field({
+                    items: regions,
+                    lbl: 'Region name',
+                    name: 'regionName',
+                    itemName: 'region',
+                    itemsName: 'regions'
+                })(
+                    list-editable-cols=`::[{
+                        name: 'Regions:',
+                        tip: "List of regions where VMs are located<br />
+                        If the regions are not set then every region, that a cloud provider has, will be investigated. This could lead to significant performance degradation<br />
+                        Note, that some cloud providers, like Google Compute Engine, doesn't have a notion of a region. For such providers regions are redundant"
+                    }]`
+                )
+                    +unique-feedback(_, 'Such region already exists!')
+
+        .pc-form-grid-col-60
+            .ignite-form-field
+                +list-text-field({
+                    items: zones,
+                    lbl: 'Zone name',
+                    name: 'zoneName',
+                    itemName: 'zone',
+                    itemsName: 'zones'
+                })(
+                    list-editable-cols=`::[{
+                        name: 'Zones:',
+                        tip: "List of zones where VMs are located<br />
+                        If the zones are not set then every zone from specified regions, will be taken into account<br />
+                        Note, that some cloud providers, like Rackspace, doesn't have a notion of a zone. For such providers zones are redundant"
+                    }]`
+                )
+                    +unique-feedback(_, 'Such zone already exists!')

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/google.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/google.pug b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/google.pug
index 43efa2a..7de3843 100644
--- a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/google.pug
+++ b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/google.pug
@@ -16,23 +16,23 @@
 
 include /app/helpers/jade/mixins
 
-mixin discovery-google(modelAt = 'backupItem')
+mixin discovery-google(modelAt = '$ctrl.clonedCluster')
     -const discoveryKind = 'GoogleStorage'
     -const required = `${modelAt}.discovery.kind == '${discoveryKind}'`
     -const model = `${modelAt}.discovery.GoogleStorage`
 
-    div
-        .details-row
+    .pc-form-grid-row&attributes(attributes=attributes)
+        .pc-form-grid-col-30
             +text('Project name:', `${model}.projectName`, `'${discoveryKind}ProjectName'`, required, 'Input project name', '' +
                 'Google Cloud Platforms project name<br/>\
                 Usually this is an auto generated project number(ex. 208709979073) that can be found in "Overview" section of Google Developer Console')
-        .details-row
+        .pc-form-grid-col-30
             +text('Bucket name:', `${model}.bucketName`, `'${discoveryKind}BucketName'`, required, 'Input bucket name',
                 'Google Cloud Storage bucket name<br/>\
                 If the bucket does not exist Ignite will automatically create it<br/>\
                 However the name must be unique across whole Google Cloud Storage and Service Account Id must be authorized to perform this operation')
-        .details-row
+        .pc-form-grid-col-30
             +text('Private key path:', `${model}.serviceAccountP12FilePath`, `'${discoveryKind}ServiceAccountP12FilePath'`, required, 'Input private key path',
                 'Full path to the private key in PKCS12 format of the Service Account')
-        .details-row
+        .pc-form-grid-col-30
             +text('Account id:', `${model}.serviceAccountId`, `'${discoveryKind}ServiceAccountId'`, required, 'Input account id', 'Service account ID (typically an e-mail address)')

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/jdbc.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/jdbc.pug b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/jdbc.pug
index 628b519..7b23a22 100644
--- a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/jdbc.pug
+++ b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/jdbc.pug
@@ -16,17 +16,20 @@
 
 include /app/helpers/jade/mixins
 
-mixin discovery-jdbc(modelAt = 'backupItem')
+mixin discovery-jdbc(modelAt = '$ctrl.clonedCluster')
     -const model = `${modelAt}.discovery.Jdbc`
     -const required = `${modelAt}.discovery.kind === "Jdbc"`
 
-    .details-row
-        +text('Data source bean name:', `${model}.dataSourceBean`,
-            '"dataSourceBean"', required, 'Input bean name', 'Name of the data source bean in Spring context')
-    .details-row
-        +dialect('Dialect:', `${model}.dialect`, '"dialect"', required,
-            'Dialect of SQL implemented by a particular RDBMS:', 'Generic JDBC dialect', 'Choose JDBC dialect')
-    .details-row
-        +checkbox('DB schema should be initialized by Ignite', `${model}.initSchema`, '"initSchema"',
-            'Flag indicating whether DB schema should be initialized by Ignite or was explicitly created by user')
-    .pcb-flex-grid-break
\ No newline at end of file
+    .pc-form-grid-row&attributes(attributes=attributes)
+        .pc-form-grid-col-30
+            +text('Data source bean name:', `${model}.dataSourceBean`,
+                '"dataSourceBean"', required, 'Input bean name', 'Name of the data source bean in Spring context')
+        .pc-form-grid-col-30
+            +dialect('Dialect:', `${model}.dialect`, '"dialect"', required,
+                'Dialect of SQL implemented by a particular RDBMS:', 'Generic JDBC dialect', 'Choose JDBC dialect')
+        .pc-form-grid-col-60
+            +checkbox('DB schema should be initialized by Ignite', `${model}.initSchema`, '"initSchema"',
+                'Flag indicating whether DB schema should be initialized by Ignite or was explicitly created by user')
+        .pc-form-grid-col-30(ng-if=`$ctrl.Clusters.requiresProprietaryDrivers(${modelAt})`)
+            a.link-success(ng-href=`{{ $ctrl.Clusters.JDBCDriverURL(${modelAt}) }}` target='_blank')
+                | Download JDBC drivers?
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/kubernetes.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/kubernetes.pug b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/kubernetes.pug
index 0519b59..9232022 100644
--- a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/kubernetes.pug
+++ b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/kubernetes.pug
@@ -16,23 +16,23 @@
 
 include /app/helpers/jade/mixins
 
-mixin discovery-kubernetes(modelAt = 'backupItem')
+mixin discovery-kubernetes(modelAt = '$ctrl.clonedCluster')
     -const discoveryKind = 'Kubernetes'
     -const model = `${modelAt}.discovery.Kubernetes`
 
-    div
-        .details-row
+    .pc-form-grid-row&attributes(attributes=attributes)
+        .pc-form-grid-col-30
             +text('Service name:', `${model}.serviceName`, `'${discoveryKind}ServiceName'`, 'false', 'ignite',
                 "The name of Kubernetes service for Ignite pods' IP addresses lookup.<br/>\
                 The name of the service must be equal to the name set in service's Kubernetes configuration.<br/>\
                 If this parameter is not changed then the name of the service has to be set to 'ignite' in the corresponding Kubernetes configuration.")
-        .details-row
+        .pc-form-grid-col-30
             +text('Namespace:', `${model}.namespace`, `'${discoveryKind}Namespace'`, 'false', 'default',
                 "The namespace the Kubernetes service belongs to.<br/>\
                 By default, it's supposed that the service is running under Kubernetes `default` namespace.")
-        .details-row
+        .pc-form-grid-col-60
             +url('Kubernetes server:', `${model}.masterUrl`, `'${discoveryKind}MasterUrl'`, 'true', 'false', 'https://kubernetes.default.svc.cluster.local:443',
                 'The host name of the Kubernetes API server')
-        .details-row
+        .pc-form-grid-col-60
             +text('Service token file:', `${model}.accountToken`, `'${discoveryKind}AccountToken'`, 'false', '/var/run/secrets/kubernetes.io/serviceaccount/token',
                 'The path to the service token file')

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/multicast.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/multicast.pug b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/multicast.pug
index 42613c5..639a374 100644
--- a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/multicast.pug
+++ b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/multicast.pug
@@ -16,87 +16,48 @@
 
 include /app/helpers/jade/mixins
 
-mixin discovery-multicast(modelAt = 'backupItem')
+mixin discovery-multicast(modelAt = '$ctrl.clonedCluster')
     -const model = `${modelAt}.discovery.Multicast`
     -const addresses = `${model}.addresses`
-    -var form = 'general'
 
-    div
-        .details-row
+    .pc-form-grid-row&attributes(attributes=attributes)
+        .pc-form-grid-col-30
             +text-ip-address('IP address:', `${model}.multicastGroup`, '"multicastGroup"', 'true', '228.1.2.4', 'IP address of multicast group')
-        .details-row
+        .pc-form-grid-col-30
             +number-min-max('Port number:', `${model}.multicastPort`, '"multicastPort"', 'true', '47400', '0', '65535', 'Port number which multicast messages are sent to')
-        .details-row
+        .pc-form-grid-col-20
             +number('Waits for reply:', `${model}.responseWaitTime`, '"responseWaitTime"', 'true', '500', '0',
                 'Time in milliseconds IP finder waits for reply to multicast address request')
-        .details-row
+        .pc-form-grid-col-20
             +number('Attempts count:', `${model}.addressRequestAttempts`, '"addressRequestAttempts"', 'true', '2', '0',
                 'Number of attempts to send multicast address request<br/>\
                 IP finder re - sends request only in case if no reply for previous request is received')
-        .details-row
+        .pc-form-grid-col-20
             +text-ip-address('Local address:', `${model}.localAddress`, '"localAddress"', 'true', '0.0.0.0',
                 'Local host address used by this IP finder<br/>\
                 If provided address is non - loopback then multicast socket is bound to this interface<br/>\
                 If local address is not set or is any local address then IP finder creates multicast sockets for all found non - loopback addresses')
-        .pcb-flex-grid-break
-        .details-row
-            -var form = 'discoveryMulticastAddresses';
-
-            +ignite-form-group(ng-model=`${addresses}` ng-form=form)
-                -var uniqueTip = 'Such IP address already exists!'
-                -var ipAddressTip = 'Invalid IP address!'
-
-                ignite-form-field-label
-                    | Addresses
-                ignite-form-group-tooltip
-                    | Addresses may be represented as follows:#[br]
-                    ul: li IP address (e.g. 127.0.0.1, 9.9.9.9, etc)
-                        li IP address and port (e.g. 127.0.0.1:47500, 9.9.9.9:47501, etc)
-                        li IP address and port range (e.g. 127.0.0.1:47500..47510, 9.9.9.9:47501..47504, etc)
-                        li Hostname (e.g. host1.com, host2, etc)
-                        li Hostname and port (e.g. host1.com:47500, host2:47502, etc)
-                        li Hostname and port range (e.g. host1.com:47500..47510, host2:47502..47508, etc)
-                    | If port is 0 or not provided then default port will be used (depends on discovery SPI configuration)#[br]
-                    | If port range is provided (e.g. host:port1..port2) the following should be considered:#[br]
-                    ul: li port1 &lt; port2 should be true
-                        li Both port1 and port2 should be greater than 0
-                ignite-form-group-add(ng-click='group.add = [{}]')
-                    | Add new address
-
-                .group-content(ng-if=`${addresses}.length`)
-                    -var model = 'obj.model';
-                    -var name = '"edit" + $index'
-                    -var valid = `${form}[${name}].$valid`
-                    -var save = `${addresses}[$index] = ${model}`
-
-                    div(ng-repeat=`model in ${addresses} track by $index` ng-init='obj = {}')
-                        label.col-xs-12.col-sm-12.col-md-12
-                            .indexField
-                                | {{ $index+1 }})
-                            +table-remove-button(addresses, 'Remove address')
-
-                            +ignite-form-field-down(ng-if='!$last' ng-hide='field.edit' data-ng-model='model' data-models=addresses)
-                            +ignite-form-field-up(ng-if='!$first' ng-hide='field.edit' data-ng-model='model' data-models=addresses)
-
-                            span(ng-hide='field.edit')
-                                a.labelFormField(ng-click=`field.edit = true; ${model} = model;`) {{ model }}
-                            span(ng-if='field.edit')
-                                +table-address-field(name, model, addresses, valid, save, false, true)
-                                    +table-save-button(valid, save, false)
-                                    +unique-feedback(name, uniqueTip)
-
-                .group-content(ng-repeat='field in group.add')
-                    -var model = 'new';
-                    -var name = '"new"'
-                    -var valid = `${form}[${name}].$valid`
-                    -var save = `${addresses}.push(${model})`
-
-                    div
-                        label.col-xs-12.col-sm-12.col-md-12
-                            +table-address-field(name, model, addresses, valid, save, true, true)
-                                +table-save-button(valid, save, true)
-                                +unique-feedback(name, uniqueTip)
-
-                .group-content-empty(ng-if=`!(${addresses}.length) && !group.add.length`)
-                    | Not defined
-        .pcb-flex-grid-break
+        .pc-form-grid-col-60
+            .ignite-form-field
+                .ignite-form-field__control
+                    +list-addresses({
+                        items: addresses,
+                        name: 'multicastAddresses',
+                        tip: `Addresses may be represented as follows:
+                        <ul>
+                            <li>IP address (e.g. 127.0.0.1, 9.9.9.9, etc)</li>
+                            <li>IP address and port (e.g. 127.0.0.1:47500, 9.9.9.9:47501, etc)</li>
+                            <li>IP address and port range (e.g. 127.0.0.1:47500..47510, 9.9.9.9:47501..47504, etc)</li>
+                            <li>Hostname (e.g. host1.com, host2, etc)</li>
+                            <li>Hostname and port (e.g. host1.com:47500, host2:47502, etc)</li>
+                            <li>Hostname and port range (e.g. host1.com:47500..47510, host2:47502..47508, etc)</li>
+                        </ul>
+                        If port is 0 or not provided then default port will be used (depends on discovery SPI configuration)<br />
+                        If port range is provided (e.g. host:port1..port2) the following should be considered:
+                        </ul>
+                        <ul>
+                            <li> port1 &lt; port2 should be true</li>
+                            <li> Both port1 and port2 should be greater than 0</li>
+                        </ul>`
+                    })
+                    

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/s3.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/s3.pug b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/s3.pug
index d459191..41d45ac 100644
--- a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/s3.pug
+++ b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/s3.pug
@@ -16,21 +16,23 @@
 
 include /app/helpers/jade/mixins
 
-mixin discovery-s3(modelAt = 'backupItem')
+mixin discovery-s3(modelAt = '$ctrl.clonedCluster')
 
     -var discoveryKind = 'S3'
     -var required = `${modelAt}.discovery.kind == '${discoveryKind}'`
     -var model = `${modelAt}.discovery.S3`
 
-    .details-row
-        +text('Bucket name:', `${model}.bucketName`, `'${discoveryKind}BucketName'`, required, 'Input bucket name', 'Bucket name for IP finder')
-    .details-row(ng-if-start=`$ctrl.available("2.4.0")`)
-        +text('Bucket endpoint:', `${model}.bucketEndpoint`, `'${discoveryKind}BucketEndpoint'`, false, 'Input bucket endpoint',
-        'Bucket endpoint for IP finder<br/> \
-        For information about possible endpoint names visit <a href="http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region">docs.aws.amazon.com</a>')
-    .details-row(ng-if-end)
-        +text('SSE algorithm:', `${model}.SSEAlgorithm`, `'${discoveryKind}SSEAlgorithm'`, false, 'Input SSE algorithm',
-        'Server-side encryption algorithm for Amazon S3-managed encryption keys<br/> \
-        For information about possible S3-managed encryption keys visit <a href="http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingServerSideEncryption.html">docs.aws.amazon.com</a>')
-    .details-row
-        label Note, AWS credentials will be generated as stub
+    .pc-form-grid-row&attributes(attributes=attributes)
+        .pc-form-grid-col-30
+            +text('Bucket name:', `${model}.bucketName`, `'${discoveryKind}BucketName'`, required, 'Input bucket name', 'Bucket name for IP finder')
+        .pc-form-grid-col-30
+            .pc-form-grid__text-only-item(style='font-style: italic;color: #424242;')
+                | AWS credentials will be generated as stub
+        .pc-form-grid-col-40(ng-if-start=`$ctrl.available("2.4.0")`)
+            +text('Bucket endpoint:', `${model}.bucketEndpoint`, `'${discoveryKind}BucketEndpoint'`, false, 'Input bucket endpoint',
+            'Bucket endpoint for IP finder<br/> \
+            For information about possible endpoint names visit <a href="http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region">docs.aws.amazon.com</a>')
+        .pc-form-grid-col-20(ng-if-end)
+            +text('SSE algorithm:', `${model}.SSEAlgorithm`, `'${discoveryKind}SSEAlgorithm'`, false, 'Input SSE algorithm',
+            'Server-side encryption algorithm for Amazon S3-managed encryption keys<br/> \
+            For information about possible S3-managed encryption keys visit <a href="http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingServerSideEncryption.html">docs.aws.amazon.com</a>')
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/shared.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/shared.pug b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/shared.pug
index 0a96484..83e8f2a 100644
--- a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/shared.pug
+++ b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/shared.pug
@@ -16,9 +16,9 @@
 
 include /app/helpers/jade/mixins
 
-mixin discovery-shared(modelAt = 'backupItem')
+mixin discovery-shared(modelAt = '$ctrl.clonedCluster')
     -const model = `${modelAt}.discovery.SharedFs`
 
-    .details-row
-        +text('File path:', `${model}.path`, '"path"', 'false', 'disco/tcp', 'Shared path')
-    .pcb-flex-grid-break
\ No newline at end of file
+    .pc-form-grid-row&attributes(attributes=attributes)
+        .pc-form-grid-col-60
+            +text('File path:', `${model}.path`, '"path"', 'false', 'disco/tcp', 'Shared path')

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/vm.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/vm.pug b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/vm.pug
index c83cb13..1266f86 100644
--- a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/vm.pug
+++ b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/vm.pug
@@ -16,67 +16,40 @@
 
 include /app/helpers/jade/mixins
 
-mixin discovery-vm(modelAt = 'backupItem')
-
+//- Static discovery
+mixin discovery-vm(modelAt = '$ctrl.clonedCluster')
     -const model = `${modelAt}.discovery.Vm`
     -const addresses = `${model}.addresses`
-    -const form = 'discoveryVmAddresses'
-
-    .details-row
-        +ignite-form-group(ng-form=form ng-model=`${addresses}`)
-            -var uniqueTip = 'Such IP address already exists!'
-
-            ignite-form-field-label
-                | Addresses
-            ignite-form-group-tooltip
-                | Addresses may be represented as follows:
-                ul: li IP address (e.g. 127.0.0.1, 9.9.9.9, etc)
-                    li IP address and port (e.g. 127.0.0.1:47500, 9.9.9.9:47501, etc)
-                    li IP address and port range (e.g. 127.0.0.1:47500..47510, 9.9.9.9:47501..47504, etc)
-                    li Hostname (e.g. host1.com, host2, etc)
-                    li Hostname and port (e.g. host1.com:47500, host2:47502, etc)
-                    li Hostname and port range (e.g. host1.com:47500..47510, host2:47502..47508, etc)
-                | If port is 0 or not provided then default port will be used (depends on discovery SPI configuration)#[br]
-                | If port range is provided (e.g. host:port1..port2) the following should be considered:
-                ul: li port1 &lt; port2 should be true
-                    li Both port1 and port2 should be greater than 0
-            ignite-form-group-add(ng-click='group.add = [{}]')
-                | Add new address
-
-            .group-content(ng-if=`${addresses}.length`)
-                -var model = 'obj.model';
-                -var name = '"edit" + $index'
-                -var valid = `${form}[${name}].$valid`
-                -var save = `${addresses}[$index] = ${model}`
-
-                div(ng-repeat=`model in ${addresses} track by $index` ng-init='obj = {}')
-                    label.col-xs-12.col-sm-12.col-md-12
-                        .indexField
-                            | {{ $index+1 }})
-                        +table-remove-button(addresses, 'Remove address')
-
-                        +ignite-form-field-down(ng-if='!$last' ng-hide='field.edit' data-ng-model='model' data-models=addresses)
-                        +ignite-form-field-up(ng-if='!$first' ng-hide='field.edit' data-ng-model='model' data-models=addresses)
-
-                        span(ng-hide='field.edit')
-                            a.labelFormField(ng-click=`field.edit = true; ${model} = model;`) {{ model }}
-                        span(ng-if='field.edit')
-                            +table-address-field(name, model, addresses, valid, save, false, true)
-                                +table-save-button(valid, save, false)
-                                +unique-feedback(name, uniqueTip)
-
-            .group-content(ng-repeat='field in group.add')
-                -var model = 'new';
-                -var name = '"new"'
-                -var valid = `${form}[${name}].$valid`
-                -var save = `${addresses}.push(${model})`
-
-                div
-                    label.col-xs-12.col-sm-12.col-md-12
-                        +table-address-field(name, model, addresses, valid, save, true, true)
-                            +table-save-button(valid, save, true)
-                            +unique-feedback(name, uniqueTip)
 
-            .group-content-empty(id='addresses' ng-if=`!(${addresses}.length) && !group.add.length`)
-                    | Not defined
-    .pcb-flex-grid-break
\ No newline at end of file
+    .pc-form-grid-row&attributes(attributes=attributes)
+        .pc-form-grid-col-60
+            .ignite-form-field
+                .ignite-form-field__control
+                    +list-addresses({
+                        items: addresses,
+                        name: 'vmAddresses',
+                        tip: `Addresses may be represented as follows:
+                            <ul>
+                                <li>IP address (e.g. 127.0.0.1, 9.9.9.9, etc)</li>
+                                <li>IP address and port (e.g. 127.0.0.1:47500, 9.9.9.9:47501, etc)</li>
+                                <li>IP address and port range (e.g. 127.0.0.1:47500..47510, 9.9.9.9:47501..47504, etc)</li>
+                                <li>Hostname (e.g. host1.com, host2, etc)</li>
+                                <li>Hostname and port (e.g. host1.com:47500, host2:47502, etc)</li>
+                                <li>Hostname and port range (e.g. host1.com:47500..47510, host2:47502..47508, etc)</li>
+                            </ul>
+                            If port is 0 or not provided then default port will be used (depends on discovery SPI configuration)<br />
+                            If port range is provided (e.g. host:port1..port2) the following should be considered:
+                            </ul>
+                            <ul>
+                                <li> port1 &lt; port2 should be true</li>
+                                <li> Both port1 and port2 should be greater than 0</li>
+                            </ul>`
+                    })(
+                        ng-required='true'
+                        expose-ignite-form-field-control='$vmAddresses'
+                    )
+                .ignite-form-field__errors(
+                    ng-messages=`$vmAddresses.$error`
+                    ng-show=`$vmAddresses.$invalid`
+                )
+                    +form-field-feedback(_, 'required', 'Addresses should be configured')

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper.pug b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper.pug
index 6531d1d..826e09b 100644
--- a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper.pug
+++ b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper.pug
@@ -16,73 +16,68 @@
 
 include /app/helpers/jade/mixins
 
-mixin discovery-zookeeper(modelAt = 'backupItem')
+mixin discovery-zookeeper(modelAt = '$ctrl.clonedCluster')
 
-    -var form = 'general'
     -var discoveryKind = 'ZooKeeper'
     -var required = `${modelAt}.discovery.kind == '${discoveryKind}'`
     -var model = `${modelAt}.discovery.ZooKeeper`
     -var modelRetryPolicyKind = `${model}.retryPolicy.kind`
 
-    div
-        .details-row
+    .pc-form-grid-row&attributes(attributes=attributes)
+        .pc-form-grid-col-60
             +java-class('Curator:', `${model}.curator`, '"curator"', 'true', 'false',
                 'The Curator framework in use<br/>\
                 By default generates curator of org.apache.curator. framework.imps.CuratorFrameworkImpl\
                 class with configured connect string, retry policy, and default session and connection timeouts', required)
-        .details-row
+        .pc-form-grid-col-60
             +text('Connect string:', `${model}.zkConnectionString`, `'${discoveryKind}ConnectionString'`, required, 'host:port[chroot][,host:port[chroot]]',
-                'When "IGNITE_ZK_CONNECTION_STRING" system property is not configured this property will be used')
-        .details-row
+                'When <b>IGNITE_ZK_CONNECTION_STRING</b> system property is not configured this property will be used.<br><br>This should be a comma separated host:port pairs, each corresponding to a zk server. e.g. "127.0.0.1:3000,127.0.0.1:3001".<br>If the optional chroot suffix is used the example would look like: "127.0.0.1:3000,127.0.0.1:3002/app/a".<br><br>Where the client would be rooted at "/app/a" and all paths would be relative to this root - ie getting/setting/etc... "/foo/bar" would result in operations being run on "/app/a/foo/bar" (from the server perspective).<br><br><a href="https://zookeeper.apache.org/doc/r3.2.2/api/org/apache/zookeeper/ZooKeeper.html#ZooKeeper(java.lang.String,%20int,%20org.apache.zookeeper.Watcher)">Zookeeper docs</a>')
+        .pc-form-grid-col-60
             +dropdown('Retry policy:', `${model}.retryPolicy.kind`, '"retryPolicy"', 'true', 'Default',
-                '[\
-                    {value: "ExponentialBackoff", label: "Exponential backoff"},\
-                    {value: "BoundedExponentialBackoff", label: "Bounded exponential backoff"},\
-                    {value: "UntilElapsed", label: "Until elapsed"},\
-                    {value: "NTimes", label: "Max number of times"},\
-                    {value: "OneTime", label: "Only once"},\
-                    {value: "Forever", label: "Always allow retry"},\
-                    {value: "Custom", label: "Custom"},\
-                    {value: null, label: "Default"}\
-                ]',
-                'Available retry policies:\
-                <ul>\
-                    <li>Exponential backoff - retries a set number of times with increasing sleep time between retries</li>\
-                    <li>Bounded exponential backoff - retries a set number of times with an increasing (up to a maximum bound) sleep time between retries</li>\
-                    <li>Until elapsed - retries until a given amount of time elapses</li>\
-                    <li>Max number of times - retries a max number of times</li>\
-                    <li>Only once - retries only once</li>\
-                    <li>Always allow retry - retries infinitely</li>\
-                    <li>Custom - custom retry policy implementation</li>\
-                    <li>Default - exponential backoff retry policy with configured base sleep time equal to 1000ms and max retry count equal to 10</li>\
-                </ul>')
-        .pcb-flex-grid-break(ng-if-start=`${model}.retryPolicy.kind`)
-        .details-row
-            .panel-details
-                    div(ng-show=`${modelRetryPolicyKind} === 'ExponentialBackoff'`)
-                        include ./zookeeper/retrypolicy/exponential-backoff
-                    div(ng-show=`${modelRetryPolicyKind} === 'BoundedExponentialBackoff'`)
-                        include ./zookeeper/retrypolicy/bounded-exponential-backoff
-                    div(ng-show=`${modelRetryPolicyKind} === 'UntilElapsed'`)
-                        include ./zookeeper/retrypolicy/until-elapsed
-                    div(ng-show=`${modelRetryPolicyKind} === 'NTimes'`)
-                        include ./zookeeper/retrypolicy/n-times
-                    div(ng-show=`${modelRetryPolicyKind} === 'OneTime'`)
-                        include ./zookeeper/retrypolicy/one-time
-                    div(ng-show=`${modelRetryPolicyKind} === 'Forever'`)
-                        include ./zookeeper/retrypolicy/forever
-                    div(ng-show=`${modelRetryPolicyKind} === 'Custom'`)
-                        include ./zookeeper/retrypolicy/custom
-        .pcb-flex-grid-break(ng-if-end)
-        .details-row
+            '[\
+                {value: "ExponentialBackoff", label: "Exponential backoff"},\
+                {value: "BoundedExponentialBackoff", label: "Bounded exponential backoff"},\
+                {value: "UntilElapsed", label: "Until elapsed"},\
+                {value: "NTimes", label: "Max number of times"},\
+                {value: "OneTime", label: "Only once"},\
+                {value: "Forever", label: "Always allow retry"},\
+                {value: "Custom", label: "Custom"},\
+                {value: null, label: "Default"}\
+            ]',
+            'Available retry policies:\
+            <ul>\
+                <li>Exponential backoff - retries a set number of times with increasing sleep time between retries</li>\
+                <li>Bounded exponential backoff - retries a set number of times with an increasing (up to a maximum bound) sleep time between retries</li>\
+                <li>Until elapsed - retries until a given amount of time elapses</li>\
+                <li>Max number of times - retries a max number of times</li>\
+                <li>Only once - retries only once</li>\
+                <li>Always allow retry - retries infinitely</li>\
+                <li>Custom - custom retry policy implementation</li>\
+                <li>Default - exponential backoff retry policy with configured base sleep time equal to 1000ms and max retry count equal to 10</li>\
+            </ul>')
+
+        .pc-form-grid__break
+
+        include ./zookeeper/retrypolicy/exponential-backoff
+        include ./zookeeper/retrypolicy/bounded-exponential-backoff
+        include ./zookeeper/retrypolicy/until-elapsed
+        include ./zookeeper/retrypolicy/n-times
+        include ./zookeeper/retrypolicy/one-time
+        include ./zookeeper/retrypolicy/forever
+        include ./zookeeper/retrypolicy/custom
+
+        .pc-form-grid-col-30
             -var model = `${modelAt}.discovery.ZooKeeper`
 
             +text('Base path:', `${model}.basePath`, '"basePath"', 'false', '/services', 'Base path for service registration')
-        .details-row
+        .pc-form-grid-col-30
             +text('Service name:', `${model}.serviceName`, '"serviceName"', 'false', 'ignite',
                 'Service name to use, as defined by Curator&#39;s ServiceDiscovery recipe<br/>\
                 In physical ZooKeeper terms, it represents the node under basePath, under which services will be registered')
-        .details-row
+
+        .pc-form-grid__break
+
+        .pc-form-grid-col-60
             +checkbox('Allow duplicate registrations', `${model}.allowDuplicateRegistrations`, '"allowDuplicateRegistrations"',
                 'Whether to register each node only once, or if duplicate registrations are allowed<br/>\
                 Nodes will attempt to register themselves, plus those they know about<br/>\

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/bounded-exponential-backoff.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/bounded-exponential-backoff.pug b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/bounded-exponential-backoff.pug
index a983264..0ddc1e9 100644
--- a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/bounded-exponential-backoff.pug
+++ b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/bounded-exponential-backoff.pug
@@ -18,10 +18,9 @@ include /app/helpers/jade/mixins
 
 -var model = `${modelAt}.discovery.ZooKeeper.retryPolicy.BoundedExponentialBackoff`
 
-div
-    .details-row
-        +number('Base interval:', `${model}.baseSleepTimeMs`, '"beBaseSleepTimeMs"', 'true', '1000', '0', 'Initial amount of time in ms to wait between retries')
-    .details-row
-        +number('Max interval:', `${model}.maxSleepTimeMs`, '"beMaxSleepTimeMs"', 'true', 'Integer.MAX_VALUE', '0', 'Max time in ms to sleep on each retry')
-    .details-row
-        +number-min-max('Max retries:', `${model}.maxRetries`, '"beMaxRetries"', 'true', '10', '0', '29', 'Max number of times to retry')
+.pc-form-grid-col-20(ng-if-start=`${modelRetryPolicyKind} === 'BoundedExponentialBackoff'`)
+    +number('Base interval:', `${model}.baseSleepTimeMs`, '"beBaseSleepTimeMs"', 'true', '1000', '0', 'Initial amount of time in ms to wait between retries')
+.pc-form-grid-col-20
+    +number('Max interval:', `${model}.maxSleepTimeMs`, '"beMaxSleepTimeMs"', 'true', 'Integer.MAX_VALUE', '0', 'Max time in ms to sleep on each retry')
+.pc-form-grid-col-20(ng-if-end)
+    +number-min-max('Max retries:', `${model}.maxRetries`, '"beMaxRetries"', 'true', '10', '0', '29', 'Max number of times to retry')

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/custom.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/custom.pug b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/custom.pug
index 0982e6c..6a1bcfb 100644
--- a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/custom.pug
+++ b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/custom.pug
@@ -20,5 +20,6 @@ include /app/helpers/jade/mixins
 -var retry = `${model}.Custom`
 -var required = `${modelAt}.discovery.kind === "ZooKeeper" && ${modelAt}.discovery.ZooKeeper.retryPolicy.kind === "Custom"`
 
-.details-row
+.pc-form-grid-col-60(ng-if-start=`${modelRetryPolicyKind} === 'Custom'`)
     +java-class('Class name:', `${retry}.className`, '"customClassName"', 'true', required, 'Custom retry policy implementation class name', required)
+.pc-form-grid__break(ng-if-end)
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/exponential-backoff.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/exponential-backoff.pug b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/exponential-backoff.pug
index ae9d590..bfc3c02 100644
--- a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/exponential-backoff.pug
+++ b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/exponential-backoff.pug
@@ -18,10 +18,9 @@ include /app/helpers/jade/mixins
 
 -var model = `${modelAt}.discovery.ZooKeeper.retryPolicy.ExponentialBackoff`
 
-div
-    .details-row
-        +number('Base interval:', `${model}.baseSleepTimeMs`, '"expBaseSleepTimeMs"', 'true', '1000', '0', 'Initial amount of time in ms to wait between retries')
-    .details-row
-        +number-min-max('Max retries:', `${model}.maxRetries`, '"expMaxRetries"', 'true', '10', '0', '29', 'Max number of times to retry')
-    .details-row
-        +number('Max interval:', `${model}.maxSleepMs`, '"expMaxSleepMs"', 'true', 'Integer.MAX_VALUE', '0', 'Max time in ms to sleep on each retry')
+.pc-form-grid-col-20(ng-if-start=`${modelRetryPolicyKind} === 'ExponentialBackoff'`)
+    +number('Base interval:', `${model}.baseSleepTimeMs`, '"expBaseSleepTimeMs"', 'true', '1000', '0', 'Initial amount of time in ms to wait between retries')
+.pc-form-grid-col-20
+    +number-min-max('Max retries:', `${model}.maxRetries`, '"expMaxRetries"', 'true', '10', '0', '29', 'Max number of times to retry')
+.pc-form-grid-col-20(ng-if-end)
+    +number('Max interval:', `${model}.maxSleepMs`, '"expMaxSleepMs"', 'true', 'Integer.MAX_VALUE', '0', 'Max time in ms to sleep on each retry')

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/forever.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/forever.pug b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/forever.pug
index 2d7cf42..575106b 100644
--- a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/forever.pug
+++ b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/forever.pug
@@ -18,5 +18,6 @@ include /app/helpers/jade/mixins
 
 -var model = `${modelAt}.discovery.ZooKeeper.retryPolicy.Forever`
 
-.details-row
+.pc-form-grid-col-30(ng-if-start=`${modelRetryPolicyKind} === 'Forever'`)
     +number('Interval:', `${model}.retryIntervalMs`, '"feRetryIntervalMs"', 'true', '1000', '0', 'Time in ms between retry attempts')
+.pc-form-grid__break(ng-if-end)

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/n-times.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/n-times.pug b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/n-times.pug
index ddbdb61..dbb54e5 100644
--- a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/n-times.pug
+++ b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/n-times.pug
@@ -18,8 +18,7 @@ include /app/helpers/jade/mixins
 
 -var model = `${modelAt}.discovery.ZooKeeper.retryPolicy.NTimes`
 
-div
-    .details-row
-        +number('Retries:', `${model}.n`, '"n"', 'true', '10', '0', 'Number of times to retry')
-    .details-row
-        +number('Interval:', `${model}.sleepMsBetweenRetries`, '"ntSleepMsBetweenRetries"', 'true', '1000', '0', 'Time in ms between retry attempts')
+.pc-form-grid-col-30(ng-if-start=`${modelRetryPolicyKind} === 'NTimes'`)
+    +number('Retries:', `${model}.n`, '"n"', 'true', '10', '0', 'Number of times to retry')
+.pc-form-grid-col-30(ng-if-end)
+    +number('Interval:', `${model}.sleepMsBetweenRetries`, '"ntSleepMsBetweenRetries"', 'true', '1000', '0', 'Time in ms between retry attempts')

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/one-time.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/one-time.pug b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/one-time.pug
index e965a07..4ff1644 100644
--- a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/one-time.pug
+++ b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/one-time.pug
@@ -18,6 +18,6 @@ include /app/helpers/jade/mixins
 
 -var model = `${modelAt}.discovery.ZooKeeper.retryPolicy.OneTime`
 
-div
-    .details-row
-        +number('Interval:', `${model}.sleepMsBetweenRetry`, '"oneSleepMsBetweenRetry"', 'true', '1000', '0', 'Time in ms to retry attempt')
+.pc-form-grid-col-30(ng-if-start=`${modelRetryPolicyKind} === 'OneTime'`)
+    +number('Interval:', `${model}.sleepMsBetweenRetry`, '"oneSleepMsBetweenRetry"', 'true', '1000', '0', 'Time in ms to retry attempt')
+.pc-form-grid__break(ng-if-end)
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/until-elapsed.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/until-elapsed.pug b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/until-elapsed.pug
index ad185fc..ebde01c 100644
--- a/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/until-elapsed.pug
+++ b/modules/web-console/frontend/app/modules/states/configuration/clusters/general/discovery/zookeeper/retrypolicy/until-elapsed.pug
@@ -18,8 +18,7 @@ include /app/helpers/jade/mixins
 
 -var model = `${modelAt}.discovery.ZooKeeper.retryPolicy.UntilElapsed`
 
-div
-    .details-row
-        +number('Total time:', `${model}.maxElapsedTimeMs`, '"ueMaxElapsedTimeMs"', 'true', '60000', '0', 'Total time in ms for execution of retry attempt')
-    .details-row
-        +number('Interval:', `${model}.sleepMsBetweenRetries`, '"ueSleepMsBetweenRetries"', 'true', '1000', '0', 'Time in ms between retry attempts')
+.pc-form-grid-col-30(ng-if-start=`${modelRetryPolicyKind} === 'UntilElapsed'`)
+    +number('Total time:', `${model}.maxElapsedTimeMs`, '"ueMaxElapsedTimeMs"', 'true', '60000', '0', 'Total time in ms for execution of retry attempt')
+.pc-form-grid-col-30(ng-if-end)
+    +number('Interval:', `${model}.sleepMsBetweenRetries`, '"ueSleepMsBetweenRetries"', 'true', '1000', '0', 'Time in ms between retry attempts')

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/clusters/hadoop.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/states/configuration/clusters/hadoop.pug b/modules/web-console/frontend/app/modules/states/configuration/clusters/hadoop.pug
index 149c5db..7bfef7e 100644
--- a/modules/web-console/frontend/app/modules/states/configuration/clusters/hadoop.pug
+++ b/modules/web-console/frontend/app/modules/states/configuration/clusters/hadoop.pug
@@ -17,26 +17,25 @@
 include /app/helpers/jade/mixins
 
 -var form = 'hadoop'
--var model = 'backupItem.hadoopConfiguration'
+-var model = '$ctrl.clonedCluster.hadoopConfiguration'
 -var plannerModel = model + '.mapReducePlanner'
 -var weightedModel = plannerModel + '.Weighted'
 -var weightedPlanner = plannerModel + '.kind === "Weighted"'
 -var customPlanner = plannerModel + '.kind === "Custom"'
 -var libs = model + '.nativeLibraryNames'
 
-.panel.panel-default(ng-form=form novalidate)
+.pca-panel.pca-panel-default(ng-form=form novalidate)
     -var uniqueTip = 'Such native library already exists!'
 
-    .panel-heading(bs-collapse-toggle ng-click=`ui.loadPanel('${form}')`)
+    .pca-panel-heading(bs-collapse-toggle ng-click=`ui.loadPanel('${form}')`)
         ignite-form-panel-chevron
-        label Hadoop configuration
-        ignite-form-field-tooltip.tipLabel
-            | Hadoop Accelerator configuration
-        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 Hadoop configuration
+        .pca-panel-heading-description
+            | Hadoop Accelerator configuration.
+    .pca-panel-collapse(role='tabpanel' bs-collapse-target id=`${form}`)
+        .pca-panel-body(ng-if=`ui.isPanelLoaded('${form}')`).pca-form-row
+            .pca-form-column-6.pc-form-grid-row
+                .pc-form-grid-col-60
                     +dropdown('Map reduce planner:', plannerModel + '.kind', '"MapReducePlanner"', 'true', 'Default', '[\
                         {value: "Weighted", label: "Weighted"},\
                         {value: "Custom", label: "Custom"},\
@@ -47,73 +46,46 @@ include /app/helpers/jade/mixins
                         <li>Custom - Custom planner implementation</li>\
                         <li>Default - Default planner implementation</li>\
                     </ul>')
-                .settings-row(ng-show=weightedPlanner)
-                    .panel-details
-                        .details-row
-                            +number('Local mapper weight:', weightedModel + '.localMapperWeight', '"LocalMapperWeight"', 'true', 100, '0',
-                                'This weight is added to a node when a mapper is assigned and it is input split data is located on this node')
-                        .details-row
-                            +number('Remote mapper weight:', weightedModel + '.remoteMapperWeight', '"remoteMapperWeight"', 'true', 100, '0',
-                                'This weight is added to a node when a mapper is assigned, but it is input split data is not located on this node')
-                        .details-row
-                            +number('Local reducer weight:', weightedModel + '.localReducerWeight', '"localReducerWeight"', 'true', 100, '0',
-                                'This weight is added to a node when a reducer is assigned and the node have at least one assigned mapper')
-                        .details-row
-                            +number('Remote reducer weight:', weightedModel + '.remoteReducerWeight', '"remoteReducerWeight"', 'true', 100, '0',
-                                'This weight is added to a node when a reducer is assigned, but the node does not have any assigned mappers')
-                        .details-row
-                            +number('Local mapper weight:', weightedModel + '.preferLocalReducerThresholdWeight', '"preferLocalReducerThresholdWeight"', 'true', 200, '0',
-                                "When threshold is reached, a node with mappers is no longer considered as preferred for further reducer assignments")
-                .settings-row(ng-show=customPlanner)
-                    .panel-details
-                        .details-row
-                            +java-class('Class name:', plannerModel + '.Custom.className', '"MapReducePlannerCustomClass"', 'true', customPlanner,
-                                'Custom planner implementation')
-                .settings-row
+                .pc-form-group.pc-form-grid-row(ng-show=weightedPlanner)
+                    .pc-form-grid-col-20
+                        +number('Local mapper weight:', weightedModel + '.localMapperWeight', '"LocalMapperWeight"', 'true', 100, '0',
+                            'This weight is added to a node when a mapper is assigned and it is input split data is located on this node')
+                    .pc-form-grid-col-20
+                        +number('Remote mapper weight:', weightedModel + '.remoteMapperWeight', '"remoteMapperWeight"', 'true', 100, '0',
+                            'This weight is added to a node when a mapper is assigned, but it is input split data is not located on this node')
+                    .pc-form-grid-col-20
+                        +number('Local reducer weight:', weightedModel + '.localReducerWeight', '"localReducerWeight"', 'true', 100, '0',
+                            'This weight is added to a node when a reducer is assigned and the node have at least one assigned mapper')
+                    .pc-form-grid-col-30
+                        +number('Remote reducer weight:', weightedModel + '.remoteReducerWeight', '"remoteReducerWeight"', 'true', 100, '0',
+                            'This weight is added to a node when a reducer is assigned, but the node does not have any assigned mappers')
+                    .pc-form-grid-col-30
+                        +number('Local mapper weight:', weightedModel + '.preferLocalReducerThresholdWeight', '"preferLocalReducerThresholdWeight"', 'true', 200, '0',
+                            "When threshold is reached, a node with mappers is no longer considered as preferred for further reducer assignments")
+                .pc-form-group.pc-form-grid-row(ng-show=customPlanner)
+                    .pc-form-grid-col-60
+                        +java-class('Class name:', plannerModel + '.Custom.className', '"MapReducePlannerCustomClass"', 'true', customPlanner,
+                            'Custom planner implementation')
+                .pc-form-grid-col-30
                     +number('Finished job info TTL:', model + '.finishedJobInfoTtl', '"finishedJobInfoTtl"', 'true', '30000', '0',
                         'Finished job info time-to-live in milliseconds')
-                .settings-row
+                .pc-form-grid-col-30
                     +number('Max parallel tasks:', model + '.maxParallelTasks', '"maxParallelTasks"', 'true', 'availableProcessors * 2', '1',
                         'Max number of local tasks that may be executed in parallel')
-                .settings-row
+                .pc-form-grid-col-30
                     +number('Max task queue size:', model + '.maxTaskQueueSize', '"maxTaskQueueSize"', 'true', '8192', '1', 'Max task queue size')
-                .settings-row
-                    +ignite-form-group(ng-form=form ng-model=`${libs}`)
-                        ignite-form-field-label
-                            | Native libraries
-                        ignite-form-group-tooltip
-                            | Library names
-                        ignite-form-group-add(ng-click='group.add = [{}]')
-                            | Add new library
-                        .group-content(ng-if=`${libs}.length`)
-                            -var model = 'obj.model';
-                            -var name = '"edit" + $index'
-                            -var valid = `${form}[${name}].$valid`
-                            -var save = `${libs}[$index] = ${model}`
+                .pc-form-grid-col-60
+                    .ignite-form-field
+                        +list-text-field({
+                            items: libs,
+                            lbl: 'Library name',
+                            name: 'libraryName',
+                            itemName: 'library name',
+                            itemsName: 'library names'
+                        })(
+                            list-editable-cols=`::[{name: 'Native libraries:'}]`
+                        )
+                            +unique-feedback(_, `${uniqueTip}`)
 
-                            div(ng-repeat=`model in ${libs} track by $index` ng-init='obj = {}')
-                                label.col-xs-12.col-sm-12.col-md-12
-                                    .indexField
-                                        | {{ $index+1 }})
-                                    +table-remove-button(libs, 'Remove library')
-                                    span(ng-hide='field.edit')
-                                        a.labelFormField(ng-click=`field.edit = true; ${model} = model;`) {{ model }}
-                                    span(ng-if='field.edit')
-                                        +table-text-field(name, model, libs, valid, save, 'Input library name', false)
-                                            +table-save-button(valid, save, false)
-                                            +unique-feedback(name, uniqueTip)
-                        .group-content(ng-repeat='field in group.add')
-                            -var model = 'new';
-                            -var name = '"new"'
-                            -var valid = `${form}[${name}].$valid`
-                            -var save = `${libs}.push(${model})`
-
-                            div
-                                label.col-xs-12.col-sm-12.col-md-12
-                                    +table-text-field(name, model, libs, valid, save, 'Input library name', true)
-                                        +table-save-button(valid, save, true)
-                                        +unique-feedback(name, uniqueTip)
-                        .group-content-empty(id='libs' ng-if=`!(${libs}.length) && !group.add.length`)
-                            | Not defined
-            .col-sm-6
+            .pca-form-column-6
                 +preview-xml-java(model, 'clusterHadoop')

http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/clusters/igfs.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/states/configuration/clusters/igfs.pug b/modules/web-console/frontend/app/modules/states/configuration/clusters/igfs.pug
index 10930cd..cfe4d74 100644
--- a/modules/web-console/frontend/app/modules/states/configuration/clusters/igfs.pug
+++ b/modules/web-console/frontend/app/modules/states/configuration/clusters/igfs.pug
@@ -17,22 +17,21 @@
 include /app/helpers/jade/mixins
 
 -var form = 'igfs'
--var model = 'backupItem'
+-var model = '$ctrl.clonedCluster'
 
-.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 IGFS
-        ignite-form-field-tooltip.tipLabel
-            | IGFS (Ignite In-Memory File System) configurations assigned to cluster#[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=`${form}`)
-        .panel-body(ng-if=`ui.isPanelLoaded('${form}')`)
-            .col-sm-6
+        .pca-panel-heading-title IGFS
+        .pca-panel-heading-description
+            | IGFS (Ignite In-Memory File System) configurations assigned to cluster. 
+            | #[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=`${form}`)
+        .pca-panel-body(ng-if=`ui.isPanelLoaded('${form}')`).pca-form-row
+            .pca-form-column-6
                 .settings-row
-                    +dropdown-multiple('<span>IGFS:</span><a ui-sref="base.configuration.tabs.advanced.igfs({linkId: linkId()})"> (add)</a>',
+                    +dropdown-multiple('<span>IGFS:</span><a ui-sref="base.configuration.edit.advanced.igfs({linkId: linkId()})"> (add)</a>',
                         `${model}.igfss`, '"igfss"', true, 'Choose IGFS', 'No IGFS configured', 'igfss',
                         'Select IGFS to start in cluster or add a new IGFS')
-            .col-sm-6
+            .pca-form-column-6
                 +preview-xml-java(model, 'igfss', 'igfss')