You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by nc...@apache.org on 2015/10/02 23:12:36 UTC

[32/32] ambari git commit: AMBARI-13280. Enhanced Configs work to add Ranger User Info tab. (Andrii Babiichuk via Jaimin)

AMBARI-13280. Enhanced Configs work to add Ranger User Info tab. (Andrii Babiichuk via Jaimin)


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/ccd64811
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/ccd64811
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/ccd64811

Branch: refs/heads/branch-dev-patch-upgrade
Commit: ccd64811d87dfd9ccb3c814285c8cf082270d237
Parents: 321d57b
Author: Jaimin Jetly <ja...@hortonworks.com>
Authored: Fri Oct 2 12:33:39 2015 -0700
Committer: Jaimin Jetly <ja...@hortonworks.com>
Committed: Fri Oct 2 12:35:15 2015 -0700

----------------------------------------------------------------------
 .../server/state/theme/ConfigPlacement.java     |  12 +-
 .../ambari/server/state/theme/Subsection.java   |  40 +-
 .../RANGER/configuration/ranger-admin-site.xml  |  20 +
 .../RANGER/configuration/ranger-env.xml         |  23 +
 .../RANGER/configuration/ranger-ugsync-site.xml | 122 ++++
 .../HDP/2.3/services/RANGER/themes/theme.json   | 554 +++++++++++++++++++
 ambari-web/app/controllers/installer.js         |   1 +
 ambari-web/app/data/HDP2.3/site_properties.js   |   2 +-
 ambari-web/app/mappers/configs/themes_mapper.js |  41 +-
 ambari-web/app/models.js                        |   7 +-
 ambari-web/app/models/configs/section.js        | 138 -----
 .../app/models/configs/stack_config_property.js |   6 +
 ambari-web/app/models/configs/sub_section.js    | 176 ------
 ambari-web/app/models/configs/tab.js            |  73 ---
 ambari-web/app/models/configs/theme/section.js  | 138 +++++
 .../app/models/configs/theme/sub_section.js     | 189 +++++++
 .../app/models/configs/theme/sub_section_tab.js |  89 +++
 ambari-web/app/models/configs/theme/tab.js      |  73 +++
 .../configs/service_config_layout_tab.hbs       |  24 +
 .../configs/service_config_layout_tab_view.js   | 112 ++--
 .../test/mappers/configs/themes_mapper_test.js  |   6 +-
 21 files changed, 1402 insertions(+), 444 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/ccd64811/ambari-server/src/main/java/org/apache/ambari/server/state/theme/ConfigPlacement.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/theme/ConfigPlacement.java b/ambari-server/src/main/java/org/apache/ambari/server/state/theme/ConfigPlacement.java
index 56d2ea2..af12dc0 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/theme/ConfigPlacement.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/theme/ConfigPlacement.java
@@ -32,6 +32,8 @@ public class ConfigPlacement {
 	private String config;
 	@JsonProperty("subsection-name")
 	private String subsectionName;
+  @JsonProperty("subsection-tab-name")
+  private String subsectionTabName;
 
   @JsonProperty("property_value_attributes")
   private ValueAttributesInfo propertyValueAttributes;
@@ -56,6 +58,14 @@ public class ConfigPlacement {
     this.subsectionName = subsectionName;
   }
 
+  public String getSubsectionTabName() {
+    return subsectionTabName;
+  }
+
+  public void setSubsectionTabName(String subsectionTabName) {
+    this.subsectionTabName = subsectionTabName;
+  }
+
   public ValueAttributesInfo getPropertyValueAttributes() {
     return propertyValueAttributes;
   }
@@ -81,4 +91,4 @@ public class ConfigPlacement {
       subsectionName = parent.subsectionName;
     }
   }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/ccd64811/ambari-server/src/main/java/org/apache/ambari/server/state/theme/Subsection.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/theme/Subsection.java b/ambari-server/src/main/java/org/apache/ambari/server/state/theme/Subsection.java
index 0397545..c9f9019 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/theme/Subsection.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/theme/Subsection.java
@@ -47,6 +47,8 @@ public class Subsection {
   private Boolean leftVerticalSplitter;
   @JsonProperty("depends-on")
   private List<ConfigCondition> dependsOn;
+  @JsonProperty("subsection-tabs")
+  private List<SubsectionTab> subsectionTabs;
 
 
   public String getRowIndex() {
@@ -121,8 +123,16 @@ public class Subsection {
     this.dependsOn = dependsOn;
   }
 
+  public List<SubsectionTab> getSubsectionTabs() {
+    return subsectionTabs;
+  }
+
+  public void setSubsectionTabs(List<SubsectionTab> subsectionTabs) {
+    this.subsectionTabs = subsectionTabs;
+  }
+
   public boolean isRemoved() {
-    return rowIndex == null && rowSpan == null && columnIndex == null && columnSpan == null;
+    return rowIndex == null && rowSpan == null && columnIndex == null && columnSpan == null && dependsOn == null && subsectionTabs == null;
   }
 
   public void mergeWithParent(Subsection parent) {
@@ -150,5 +160,33 @@ public class Subsection {
     if (dependsOn == null) {
       dependsOn = parent.dependsOn;
     }
+    if (subsectionTabs == null) {
+      subsectionTabs = parent.subsectionTabs;
+    }
+  }
+
+  @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
+  @JsonIgnoreProperties(ignoreUnknown = true)
+  private static class SubsectionTab {
+    @JsonProperty("name")
+    private String name;
+    @JsonProperty("display-name")
+    private String displayName;
+
+    public String getName() {
+      return name;
+    }
+
+    public void setName(String name) {
+      this.name = name;
+    }
+
+    public String getDisplayName() {
+      return displayName;
+    }
+
+    public void setDisplayName(String displayName) {
+      this.displayName = displayName;
+    }
   }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/ccd64811/ambari-server/src/main/resources/stacks/HDP/2.3/services/RANGER/configuration/ranger-admin-site.xml
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/stacks/HDP/2.3/services/RANGER/configuration/ranger-admin-site.xml b/ambari-server/src/main/resources/stacks/HDP/2.3/services/RANGER/configuration/ranger-admin-site.xml
index 97ba241..d93d758 100644
--- a/ambari-server/src/main/resources/stacks/HDP/2.3/services/RANGER/configuration/ranger-admin-site.xml
+++ b/ambari-server/src/main/resources/stacks/HDP/2.3/services/RANGER/configuration/ranger-admin-site.xml
@@ -178,6 +178,7 @@
 
   <property>
     <name>ranger.ldap.url</name>
+    <display-name>​LDAP URL</display-name>
     <value>ldap://71.127.43.33:389</value>
     <description>LDAP Server URL, only used if Authentication method is LDAP</description>
     <value-attributes>
@@ -196,18 +197,21 @@
 
   <property>
     <name>ranger.ldap.group.searchbase</name>
+    <display-name>Group Search Base</display-name>
     <value>ou=groups,dc=xasecure,dc=net</value>
     <description>LDAP group searchbase, only used if Authentication method is LDAP</description>
   </property>
 
   <property>
     <name>ranger.ldap.group.searchfilter</name>
+    <display-name>Group Search Filter</display-name>
     <value>(member=uid={0},ou=users,dc=xasecure,dc=net)</value>
     <description>LDAP group search filter, only used if Authentication method is LDAP</description>
   </property>
 
   <property>
     <name>ranger.ldap.user.searchfilter</name>
+    <display-name>User Search Filter</display-name>
     <value>(uid={0})</value>
     <description>Search filter used for Bind Authentication</description>
     <value-attributes>
@@ -235,6 +239,7 @@
 
   <property>
     <name>ranger.ldap.bind.dn</name>
+    <display-name>Bind User</display-name>
     <value>cn=adadmin,cn=Users,dc=example,dc=com</value>
     <description>Full distinguished name (DN), including common name (CN), of an LDAP user account that has privileges to search for users. </description>
     <value-attributes>
@@ -244,6 +249,7 @@
 
   <property>
     <name>ranger.ldap.bind.password</name>
+    <display-name>​Bind User Password</display-name>
     <value></value>
     <property-type>PASSWORD</property-type>
     <description>Password for the account that can search for users</description>
@@ -263,6 +269,7 @@
 
   <property>
     <name>ranger.ldap.ad.domain</name>
+    <display-name>Domain Name</display-name>
     <value>localhost</value>
     <description>AD domain, only used if Authentication method is AD</description>
     <value-attributes>
@@ -362,7 +369,20 @@
     <display-name>Allow remote Login</display-name>
     <description>Remote login enabled? - only used if Authentication method is UNIX</description>
     <value-attributes>
+      <empty-value-valid>true</empty-value-valid>
+      <type>value-list</type>
       <overridable>false</overridable>
+      <entries>
+        <entry>
+          <value>true</value>
+          <label>Yes</label>
+        </entry>
+        <entry>
+          <value>false</value>
+          <label>No</label>
+        </entry>
+      </entries>
+      <selection-cardinality>1</selection-cardinality>
     </value-attributes>
   </property>
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/ccd64811/ambari-server/src/main/resources/stacks/HDP/2.3/services/RANGER/configuration/ranger-env.xml
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/stacks/HDP/2.3/services/RANGER/configuration/ranger-env.xml b/ambari-server/src/main/resources/stacks/HDP/2.3/services/RANGER/configuration/ranger-env.xml
index 7f3e6e0..a8ad1b6 100644
--- a/ambari-server/src/main/resources/stacks/HDP/2.3/services/RANGER/configuration/ranger-env.xml
+++ b/ambari-server/src/main/resources/stacks/HDP/2.3/services/RANGER/configuration/ranger-env.xml
@@ -53,4 +53,27 @@
     <deleted>true</deleted>
   </property>
 
+  <property>
+    <name>bind_anonymous</name>
+    <display-name>Bind Anonymous</display-name>
+    <value>false</value>
+    <value-attributes>
+      <type>value-list</type>
+      <overridable>false</overridable>
+      <entries>
+        <entry>
+          <value>true</value>
+          <label>Yes</label>
+        </entry>
+        <entry>
+          <value>false</value>
+          <label>No</label>
+        </entry>
+      </entries>
+      <selection-cardinality>1</selection-cardinality>
+      <empty-value-valid>true</empty-value-valid>
+    </value-attributes>
+  </property>
+
+
 </configuration>

http://git-wip-us.apache.org/repos/asf/ambari/blob/ccd64811/ambari-server/src/main/resources/stacks/HDP/2.3/services/RANGER/configuration/ranger-ugsync-site.xml
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/stacks/HDP/2.3/services/RANGER/configuration/ranger-ugsync-site.xml b/ambari-server/src/main/resources/stacks/HDP/2.3/services/RANGER/configuration/ranger-ugsync-site.xml
index de63dd6..c73c592 100644
--- a/ambari-server/src/main/resources/stacks/HDP/2.3/services/RANGER/configuration/ranger-ugsync-site.xml
+++ b/ambari-server/src/main/resources/stacks/HDP/2.3/services/RANGER/configuration/ranger-ugsync-site.xml
@@ -64,8 +64,25 @@
 
   <property>
     <name>ranger.usersync.enabled</name>
+    <display-name>Enable User Sync</display-name>
     <value>true</value>
     <description>Usersync enabled?</description>
+    <value-attributes>
+      <empty-value-valid>true</empty-value-valid>
+      <type>value-list</type>
+      <overridable>false</overridable>
+      <entries>
+        <entry>
+          <value>true</value>
+          <label>Yes</label>
+        </entry>
+        <entry>
+          <value>false</value>
+          <label>No</label>
+        </entry>
+      </entries>
+      <selection-cardinality>1</selection-cardinality>
+    </value-attributes>
   </property>
 
   <property>
@@ -94,11 +111,30 @@
 
   <property>
     <name>ranger.usersync.unix.minUserId</name>
+    <display-name>Minimum User ID</display-name>
     <value>500</value>
     <description>Only sync users above this user id (applicable for UNIX)</description>
   </property>
 
   <property>
+    <name>ranger.usersync.unix.group.file</name>
+    <display-name>Group File</display-name>
+    <value></value>
+    <value-attributes>
+      <empty-value-valid>true</empty-value-valid>
+    </value-attributes>
+  </property>
+
+  <property>
+    <name>ranger.usersync.unix.password.file</name>
+    <display-name>Password File</display-name>
+    <value></value>
+    <value-attributes>
+      <empty-value-valid>true</empty-value-valid>
+    </value-attributes>
+  </property>
+
+  <property>
     <name>ranger.usersync.sleeptimeinmillisbetweensynccycle</name>
     <value>5</value>
     <description>Sleeptime interval in milliseconds, if &lt; 1000 then default to 30 sec</description>
@@ -107,38 +143,69 @@
   <property>
     <name>ranger.usersync.source.impl.class</name>
     <value>org.apache.ranger.unixusersync.process.UnixUserGroupBuilder</value>
+    <display-name>Sync Source</display-name>
     <description>For Ldap: org.apache.ranger.ldapusersync.process.LdapUserGroupBuilder, For Unix: org.apache.ranger.unixusersync.process.UnixUserGroupBuilder, org.apache.ranger.unixusersync.process.FileSourceUserGroupBuilder</description>
     <value-attributes>
+      <type>value-list</type>
       <empty-value-valid>true</empty-value-valid>
+      <overridable>false</overridable>
+      <entries>
+        <entry>
+          <value></value>
+          <label>NONE</label>
+        </entry>
+        <entry>
+          <value>org.apache.ranger.unixusersync.process.UnixUserGroupBuilder</value>
+          <label>UNIX</label>
+        </entry>
+        <entry>
+          <value>org.apache.ranger.unixusersync.process.FileSourceUserGroupBuilder</value>
+          <label>FILE</label>
+        </entry>
+        <entry>
+          <value>org.apache.ranger.ldapusersync.process.LdapUserGroupBuilder</value>
+          <label>LDAP</label>
+        </entry>
+        <entry>
+          <value>org.apache.ranger.ldapusersync.process.LdapUserGroupBuilder</value>
+          <label>AD</label>
+        </entry>
+      </entries>
+      <selection-cardinality>1</selection-cardinality>
     </value-attributes>
   </property>
 
   <property>
     <name>ranger.usersync.filesource.file</name>
+    <display-name>File Name</display-name>
     <value>/tmp/usergroup.txt</value>
     <description>/tmp/usergroup.json or /tmp/usergroup.csv or /tmp/usergroup.txt</description>
   </property>
 
   <property>
     <name>ranger.usersync.filesource.text.delimiter</name>
+    <display-name>Delimeter</display-name>
     <value>,</value>
     <description>Delimiter used in file, if File based user sync is used</description>
   </property>
 
   <property>
     <name>ranger.usersync.ldap.url</name>
+    <display-name>LDAP (AD) URL</display-name>
     <value>ldap://localhost:389</value>
     <description>LDAP server URL</description>
   </property>
 
   <property>
     <name>ranger.usersync.ldap.binddn</name>
+    <display-name>​Bind User</display-name>
     <value>cn=admin,dc=xasecure,dc=net</value>
     <description>Full distinguished name (DN), including common name (CN), of an LDAP user account that has privileges to search for users. </description>
   </property>
 
   <property>
     <name>ranger.usersync.ldap.ldapbindpassword</name>
+    <display-name>Bind User Password</display-name>
     <value></value>
     <property-type>PASSWORD</property-type>
     <description>Password for the account that can search for users.</description>
@@ -174,6 +241,7 @@
 
   <property>
     <name>ranger.usersync.ldap.user.searchbase</name>
+    <display-name>User Search Base</display-name>
     <value>ou=users,dc=xasecure,dc=net</value>
     <description>"# search base for users
 # sample value would be ou=users,dc=hadoop,dc=apache,dc=org
@@ -182,6 +250,7 @@
 
   <property>
     <name>ranger.usersync.ldap.user.searchscope</name>
+    <display-name>User Search Scope</display-name>
     <value>sub</value>
     <description>"# search scope for the users, only base, one and sub are supported values
 # please customize the value to suit your deployment
@@ -190,12 +259,14 @@
 
   <property>
     <name>ranger.usersync.ldap.user.objectclass</name>
+    <display-name>User Object Class​</display-name>
     <value>person</value>
     <description>LDAP User Object Class</description>
   </property>
 
   <property>
     <name>ranger.usersync.ldap.user.searchfilter</name>
+    <display-name>​User Search Filter</display-name>
     <value>empty</value>
     <description>"optional additional filter constraining the users selected for syncing
 # a sample value would be (dept=eng)
@@ -205,6 +276,7 @@
 
   <property>
     <name>ranger.usersync.ldap.user.nameattribute</name>
+    <display-name>Username Attribute</display-name>
     <value>cn</value>
     <description>LDAP user name attribute</description>
   </property>
@@ -220,6 +292,7 @@
 
   <property>
     <name>ranger.usersync.ldap.user.groupnameattribute</name>
+    <display-name>User Group Name Attribute</display-name>
     <value>memberof, ismemberof</value>
     <description>LDAP user group name attribute</description>
   </property>
@@ -244,6 +317,7 @@
 
   <property>
     <name>ranger.usersync.group.searchenabled</name>
+    <display-name>Enable Group Sync (Group Search Filter)</display-name>
     <value>false</value>
     <description>"# do we want to do ldapsearch to find groups instead of relying on user entry attributes
 # valid values: true, false
@@ -251,20 +325,48 @@
 # default value: false"</description>
     <value-attributes>
       <empty-value-valid>true</empty-value-valid>
+      <type>value-list</type>
+      <overridable>false</overridable>
+      <entries>
+        <entry>
+          <value>true</value>
+          <label>Yes</label>
+        </entry>
+        <entry>
+          <value>false</value>
+          <label>No</label>
+        </entry>
+      </entries>
+      <selection-cardinality>1</selection-cardinality>
     </value-attributes>
   </property>
 
   <property>
     <name>ranger.usersync.group.usermapsyncenabled</name>
     <value>false</value>
+    <display-name>Group User Map Sync</display-name>
     <description>User map sync enabled?</description>
     <value-attributes>
       <empty-value-valid>true</empty-value-valid>
+      <type>value-list</type>
+      <overridable>false</overridable>
+      <entries>
+        <entry>
+          <value>true</value>
+          <label>Yes</label>
+        </entry>
+        <entry>
+          <value>false</value>
+          <label>No</label>
+        </entry>
+      </entries>
+      <selection-cardinality>1</selection-cardinality>
     </value-attributes>
   </property>
 
   <property>
     <name>ranger.usersync.group.searchbase</name>
+    <display-name>Group Search Base</display-name>
     <value> </value>
     <description>"# search base for groups
 # sample value would be ou=groups,dc=hadoop,dc=apache,dc=org
@@ -289,6 +391,7 @@
 
   <property>
     <name>ranger.usersync.group.objectclass</name>
+    <display-name>Group Object Class</display-name>
     <value> </value>
     <description></description>
     <value-attributes>
@@ -299,6 +402,7 @@
   <property>
     <name>ranger.usersync.group.searchfilter</name>
     <value> </value>
+    <display-name>Group Search Filter</display-name>
     <description>"# optional additional filter constraining the groups selected for syncing
 # a sample value would be (dept=eng)
 # please customize the value to suit your deployment
@@ -310,6 +414,7 @@
 
   <property>
     <name>ranger.usersync.group.nameattribute</name>
+    <display-name>Group Name Attribute</display-name>
     <value> </value>
     <description>LDAP group name attribute</description>
     <value-attributes>
@@ -319,6 +424,7 @@
 
   <property>
     <name>ranger.usersync.group.memberattributename</name>
+    <display-name>Group Member Attribute</display-name>
     <value> </value>
     <description>LDAP group member attribute name</description>
     <value-attributes>
@@ -330,6 +436,22 @@
     <name>ranger.usersync.pagedresultsenabled</name>
     <value>true</value>
     <description>Results can be paged?</description>
+    <value-attributes>
+      <empty-value-valid>true</empty-value-valid>
+      <type>value-list</type>
+      <overridable>false</overridable>
+      <entries>
+        <entry>
+          <value>true</value>
+          <label>Yes</label>
+        </entry>
+        <entry>
+          <value>false</value>
+          <label>No</label>
+        </entry>
+      </entries>
+      <selection-cardinality>1</selection-cardinality>
+    </value-attributes>
   </property>
 
   <property>

http://git-wip-us.apache.org/repos/asf/ambari/blob/ccd64811/ambari-server/src/main/resources/stacks/HDP/2.3/services/RANGER/themes/theme.json
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/stacks/HDP/2.3/services/RANGER/themes/theme.json b/ambari-server/src/main/resources/stacks/HDP/2.3/services/RANGER/themes/theme.json
index 7160a4f..b398a3b 100644
--- a/ambari-server/src/main/resources/stacks/HDP/2.3/services/RANGER/themes/theme.json
+++ b/ambari-server/src/main/resources/stacks/HDP/2.3/services/RANGER/themes/theme.json
@@ -114,6 +114,49 @@
                 }
               ]
             }
+          },
+          {
+            "name": "ranger_user_info",
+            "display-name": "Ranger User Info",
+            "layout": {
+              "tab-columns": "1",
+              "tab-rows": "1",
+              "sections": [
+                {
+                  "name": "section-user-info",
+                  "display-name": "Ranger User Info",
+                  "row-index": "0",
+                  "column-index": "0",
+                  "row-span": "1",
+                  "column-span": "1",
+                  "section-columns": "1",
+                  "section-rows": "1",
+                  "subsections": [
+                    {
+                      "name": "subsection-ranger-user-row1-col1",
+                      "row-index": "0",
+                      "column-index": "0",
+                      "row-span": "1",
+                      "column-span": "1",
+                      "subsection-tabs": [
+                        {
+                          "name": "ldap-common-configs",
+                          "display-name": "Common Configs"
+                        },
+                        {
+                          "name": "ldap-user-configs",
+                          "display-name": "User Configs"
+                        },
+                        {
+                          "name": "ldap-group-configs",
+                          "display-name": "Group Configs"
+                        }
+                      ]
+                    }
+                  ]
+                }
+              ]
+            }
           }
         ]
       }
@@ -192,6 +235,363 @@
           "property_value_attributes": {
             "ui_only_property": true
           }
+        },
+        {
+          "config": "ranger-ugsync-site/ranger.usersync.source.impl.class",
+          "subsection-name": "subsection-ranger-user-row1-col1"
+        },
+
+        {
+          "config": "ranger-ugsync-site/ranger.usersync.unix.minUserId",
+          "subsection-name": "subsection-ranger-user-row1-col1"
+        },
+        {
+          "config": "ranger-ugsync-site/ranger.usersync.unix.password.file",
+          "subsection-name": "subsection-ranger-user-row1-col1"
+        },
+        {
+          "config": "ranger-ugsync-site/ranger.usersync.unix.group.file",
+          "subsection-name": "subsection-ranger-user-row1-col1"
+        },
+        {
+          "config": "ranger-ugsync-site/ranger.usersync.filesource.file",
+          "subsection-name": "subsection-ranger-user-row1-col1"
+        },
+        {
+          "config": "ranger-ugsync-site/ranger.usersync.filesource.text.delimiter",
+          "subsection-name": "subsection-ranger-user-row1-col1"
+        },
+        {
+          "config": "ranger-ugsync-site/ranger.usersync.ldap.url",
+          "subsection-name": "subsection-ranger-user-row1-col1",
+          "subsection-tab-name": "ldap-common-configs"
+        },
+        {
+          "config": "ranger-admin-site/ranger.ldap.ad.domain",
+          "subsection-name": "subsection-ranger-user-row1-col1",
+          "subsection-tab-name": "ldap-common-configs"
+        },
+        {
+          "config": "ranger-env/bind_anonymous",
+          "subsection-name": "subsection-ranger-user-row1-col1",
+          "subsection-tab-name": "ldap-common-configs"
+        },
+        {
+          "config": "ranger-ugsync-site/ranger.usersync.ldap.binddn",
+          "subsection-name": "subsection-ranger-user-row1-col1",
+          "subsection-tab-name": "ldap-common-configs",
+          "depends-on": [
+            {
+              "configs":[
+                "ranger-env/bind_anonymous"
+              ],
+              "if": "${ranger-env/bind_anonymous}",
+              "then": {
+                "property_value_attributes": {
+                  "visible": true
+                }
+              },
+              "else": {
+                "property_value_attributes": {
+                  "visible": false
+                }
+              }
+            }
+          ]
+
+        },
+        {
+          "config": "ranger-ugsync-site/ranger.usersync.ldap.ldapbindpassword",
+          "subsection-name": "subsection-ranger-user-row1-col1",
+          "subsection-tab-name": "ldap-common-configs",
+          "depends-on": [
+            {
+              "configs":[
+                "ranger-env/bind_anonymous"
+              ],
+              "if": "${ranger-env/bind_anonymous}",
+              "then": {
+                "property_value_attributes": {
+                  "visible": true
+                }
+              },
+              "else": {
+                "property_value_attributes": {
+                  "visible": false
+                }
+              }
+            }
+          ]
+        },
+
+        {
+          "config": "ranger-ugsync-site/ranger.usersync.enabled",
+          "subsection-name": "subsection-ranger-user-row1-col1",
+          "subsection-tab-name": "ldap-user-configs"
+        },
+        {
+          "config": "ranger-ugsync-site/ranger.usersync.ldap.user.nameattribute",
+          "subsection-name": "subsection-ranger-user-row1-col1",
+          "subsection-tab-name": "ldap-user-configs",
+          "depends-on": [
+            {
+              "configs":[
+                "ranger-ugsync-site/ranger.usersync.enabled"
+              ],
+              "if": "${ranger-ugsync-site/ranger.usersync.enabled}",
+              "then": {
+                "property_value_attributes": {
+                  "visible": true
+                }
+              },
+              "else": {
+                "property_value_attributes": {
+                  "visible": false
+                }
+              }
+            }
+          ]
+        },
+        {
+          "config": "ranger-ugsync-site/ranger.usersync.ldap.user.objectclass",
+          "subsection-name": "subsection-ranger-user-row1-col1",
+          "subsection-tab-name": "ldap-user-configs",
+          "depends-on": [
+            {
+              "configs":[
+                "ranger-ugsync-site/ranger.usersync.enabled"
+              ],
+              "if": "${ranger-ugsync-site/ranger.usersync.enabled}",
+              "then": {
+                "property_value_attributes": {
+                  "visible": true
+                }
+              },
+              "else": {
+                "property_value_attributes": {
+                  "visible": false
+                }
+              }
+            }
+          ]
+        },
+        {
+          "config": "ranger-ugsync-site/ranger.usersync.ldap.user.searchbase",
+          "subsection-name": "subsection-ranger-user-row1-col1",
+          "subsection-tab-name": "ldap-user-configs",
+          "depends-on": [
+            {
+              "configs":[
+                "ranger-ugsync-site/ranger.usersync.enabled"
+              ],
+              "if": "${ranger-ugsync-site/ranger.usersync.enabled}",
+              "then": {
+                "property_value_attributes": {
+                  "visible": true
+                }
+              },
+              "else": {
+                "property_value_attributes": {
+                  "visible": false
+                }
+              }
+            }
+          ]
+        },
+        {
+          "config": "ranger-ugsync-site/ranger.usersync.ldap.user.searchfilter",
+          "subsection-name": "subsection-ranger-user-row1-col1",
+          "subsection-tab-name": "ldap-user-configs",
+          "depends-on": [
+            {
+              "configs":[
+                "ranger-ugsync-site/ranger.usersync.enabled"
+              ],
+              "if": "${ranger-ugsync-site/ranger.usersync.enabled}",
+              "then": {
+                "property_value_attributes": {
+                  "visible": true
+                }
+              },
+              "else": {
+                "property_value_attributes": {
+                  "visible": false
+                }
+              }
+            }
+          ]
+        },
+        {
+          "config": "ranger-ugsync-site/ranger.usersync.ldap.user.searchscope",
+          "subsection-name": "subsection-ranger-user-row1-col1",
+          "subsection-tab-name": "ldap-user-configs",
+          "depends-on": [
+            {
+              "configs":[
+                "ranger-ugsync-site/ranger.usersync.enabled"
+              ],
+              "if": "${ranger-ugsync-site/ranger.usersync.enabled}",
+              "then": {
+                "property_value_attributes": {
+                  "visible": true
+                }
+              },
+              "else": {
+                "property_value_attributes": {
+                  "visible": false
+                }
+              }
+            }
+          ]
+        },
+        {
+          "config": "ranger-ugsync-site/ranger.usersync.ldap.user.groupnameattribute",
+          "subsection-name": "subsection-ranger-user-row1-col1",
+          "subsection-tab-name": "ldap-user-configs",
+          "depends-on": [
+            {
+              "configs":[
+                "ranger-ugsync-site/ranger.usersync.enabled"
+              ],
+              "if": "${ranger-ugsync-site/ranger.usersync.enabled}",
+              "then": {
+                "property_value_attributes": {
+                  "visible": true
+                }
+              },
+              "else": {
+                "property_value_attributes": {
+                  "visible": false
+                }
+              }
+            }
+          ]
+        },
+
+        {
+          "config": "ranger-ugsync-site/ranger.usersync.group.usermapsyncenabled",
+          "subsection-name": "subsection-ranger-user-row1-col1",
+          "subsection-tab-name": "ldap-group-configs"
+        },
+        {
+          "config": "ranger-ugsync-site/ranger.usersync.group.searchenabled",
+          "subsection-name": "subsection-ranger-user-row1-col1",
+          "subsection-tab-name": "ldap-group-configs"
+        },
+        {
+          "config": "ranger-ugsync-site/ranger.usersync.group.memberattributename",
+          "subsection-name": "subsection-ranger-user-row1-col1",
+          "subsection-tab-name": "ldap-group-configs",
+          "depends-on": [
+            {
+              "configs":[
+                "ranger-ugsync-site/ranger.usersync.group.searchenabled"
+              ],
+              "if": "${ranger-ugsync-site/ranger.usersync.group.searchenabled}",
+              "then": {
+                "property_value_attributes": {
+                  "visible": true
+                }
+              },
+              "else": {
+                "property_value_attributes": {
+                  "visible": false
+                }
+              }
+            }
+          ]
+        },
+        {
+          "config": "ranger-ugsync-site/ranger.usersync.group.nameattribute",
+          "subsection-name": "subsection-ranger-user-row1-col1",
+          "subsection-tab-name": "ldap-group-configs",
+          "depends-on": [
+            {
+              "configs":[
+                "ranger-ugsync-site/ranger.usersync.group.searchenabled"
+              ],
+              "if": "${ranger-ugsync-site/ranger.usersync.group.searchenabled}",
+              "then": {
+                "property_value_attributes": {
+                  "visible": true
+                }
+              },
+              "else": {
+                "property_value_attributes": {
+                  "visible": false
+                }
+              }
+            }
+          ]
+        },
+        {
+          "config": "ranger-ugsync-site/ranger.usersync.group.objectclass",
+          "subsection-name": "subsection-ranger-user-row1-col1",
+          "subsection-tab-name": "ldap-group-configs",
+          "depends-on": [
+            {
+              "configs":[
+                "ranger-ugsync-site/ranger.usersync.group.searchenabled"
+              ],
+              "if": "${ranger-ugsync-site/ranger.usersync.group.searchenabled}",
+              "then": {
+                "property_value_attributes": {
+                  "visible": true
+                }
+              },
+              "else": {
+                "property_value_attributes": {
+                  "visible": false
+                }
+              }
+            }
+          ]
+        },
+        {
+          "config": "ranger-ugsync-site/ranger.usersync.group.searchbase",
+          "subsection-name": "subsection-ranger-user-row1-col1",
+          "subsection-tab-name": "ldap-group-configs",
+          "depends-on": [
+            {
+              "configs":[
+                "ranger-ugsync-site/ranger.usersync.group.searchenabled"
+              ],
+              "if": "${ranger-ugsync-site/ranger.usersync.group.searchenabled}",
+              "then": {
+                "property_value_attributes": {
+                  "visible": true
+                }
+              },
+              "else": {
+                "property_value_attributes": {
+                  "visible": false
+                }
+              }
+            }
+          ]
+        },
+        {
+          "config": "ranger-ugsync-site/ranger.usersync.group.searchfilter",
+          "subsection-name": "subsection-ranger-user-row1-col1",
+          "subsection-tab-name": "ldap-group-configs",
+          "depends-on": [
+            {
+              "configs":[
+                "ranger-ugsync-site/ranger.usersync.group.searchenabled"
+              ],
+              "if": "${ranger-ugsync-site/ranger.usersync.group.searchenabled}",
+              "then": {
+                "property_value_attributes": {
+                  "visible": true
+                }
+              },
+              "else": {
+                "property_value_attributes": {
+                  "visible": false
+                }
+              }
+            }
+          ]
         }
       ]
     },
@@ -287,6 +687,160 @@
             "db.connection.password": "admin-properties/db_root_password"
           }
         }
+      },
+      {
+        "config": "ranger-ugsync-site/ranger.usersync.source.impl.class",
+        "widget": {
+          "type": "combo"
+        }
+      },
+
+      {
+        "config": "ranger-ugsync-site/ranger.usersync.ldap.url",
+        "widget": {
+          "type": "text-field"
+        }
+      },
+      {
+        "config": "ranger-env/bind_anonymous",
+        "widget": {
+          "type": "toggle"
+        }
+      },
+      {
+        "config": "ranger-admin-site/ranger.ldap.ad.domain",
+        "widget": {
+          "type": "text-field"
+        }
+      },
+      {
+        "config": "ranger-ugsync-site/ranger.usersync.ldap.binddn",
+        "widget": {
+          "type": "text-field"
+        }
+      },
+      {
+        "config": "ranger-ugsync-site/ranger.usersync.ldap.ldapbindpassword",
+        "widget": {
+          "type": "password"
+        }
+      },
+
+      {
+        "config": "ranger-ugsync-site/ranger.usersync.enabled",
+        "widget": {
+          "type": "toggle"
+        }
+      },
+      {
+        "config": "ranger-ugsync-site/ranger.usersync.group.usermapsyncenabled",
+        "widget": {
+          "type": "toggle"
+        }
+      },
+      {
+        "config": "ranger-ugsync-site/ranger.usersync.ldap.user.nameattribute",
+        "widget": {
+          "type": "text-field"
+        }
+      },
+      {
+        "config": "ranger-ugsync-site/ranger.usersync.ldap.user.objectclass",
+        "widget": {
+          "type": "text-field"
+        }
+      },
+      {
+        "config": "ranger-ugsync-site/ranger.usersync.ldap.user.searchbase",
+        "widget": {
+          "type": "text-field"
+        }
+      },
+      {
+        "config": "ranger-ugsync-site/ranger.usersync.ldap.user.searchfilter",
+        "widget": {
+          "type": "text-field"
+        }
+      },
+      {
+        "config": "ranger-ugsync-site/ranger.usersync.ldap.user.searchscope",
+        "widget": {
+          "type": "text-field"
+        }
+      },
+      {
+        "config": "ranger-ugsync-site/ranger.usersync.ldap.user.groupnameattribute",
+        "widget": {
+          "type": "text-field"
+        }
+      },
+
+      {
+        "config": "ranger-ugsync-site/ranger.usersync.group.searchenabled",
+        "widget": {
+          "type": "toggle"
+        }
+      },
+      {
+        "config": "ranger-ugsync-site/ranger.usersync.group.memberattributename",
+        "widget": {
+          "type": "text-field"
+        }
+      },
+      {
+        "config": "ranger-ugsync-site/ranger.usersync.group.nameattribute",
+        "widget": {
+          "type": "text-field"
+        }
+      },
+      {
+        "config": "ranger-ugsync-site/ranger.usersync.group.objectclass",
+        "widget": {
+          "type": "text-field"
+        }
+      },
+      {
+        "config": "ranger-ugsync-site/ranger.usersync.group.searchbase",
+        "widget": {
+          "type": "text-field"
+        }
+      },
+      {
+        "config": "ranger-ugsync-site/ranger.usersync.group.searchfilter",
+        "widget": {
+          "type": "text-field"
+        }
+      },
+      {
+        "config": "ranger-ugsync-site/ranger.usersync.unix.minUserId",
+        "widget": {
+          "type": "text-field"
+        }
+      },
+      {
+        "config": "ranger-ugsync-site/ranger.usersync.unix.password.file",
+        "widget": {
+          "type": "text-field"
+        }
+      },
+      {
+        "config": "ranger-ugsync-site/ranger.usersync.unix.group.file",
+        "widget": {
+          "type": "text-field"
+        }
+      },
+
+      {
+        "config": "ranger-ugsync-site/ranger.usersync.filesource.file",
+        "widget": {
+          "type": "text-field"
+        }
+      },
+      {
+        "config": "ranger-ugsync-site/ranger.usersync.filesource.text.delimiter",
+        "widget": {
+          "type": "text-field"
+        }
       }
     ]
   }

http://git-wip-us.apache.org/repos/asf/ambari/blob/ccd64811/ambari-web/app/controllers/installer.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/installer.js b/ambari-web/app/controllers/installer.js
index 811f858..013f318 100644
--- a/ambari-web/app/controllers/installer.js
+++ b/ambari-web/app/controllers/installer.js
@@ -236,6 +236,7 @@ App.InstallerController = App.WizardController.extend({
     App.StackConfigProperty.find().clear();
     App.Section.find().clear();
     App.SubSection.find().clear();
+    App.SubSectionTab.find().clear();
     App.Tab.find().clear();
     this.set('stackConfigsLoaded', false);
     if (stacks && stacks.get('length')) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/ccd64811/ambari-web/app/data/HDP2.3/site_properties.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/data/HDP2.3/site_properties.js b/ambari-web/app/data/HDP2.3/site_properties.js
index 8495dee..12d1b9a 100644
--- a/ambari-web/app/data/HDP2.3/site_properties.js
+++ b/ambari-web/app/data/HDP2.3/site_properties.js
@@ -112,7 +112,7 @@ hdp23properties.push({
       },
       {
         displayName: 'ACTIVE_DIRECTORY',
-        foreignKeys: ['ranger.ldap.ad.domain', 'ranger.ldap.ad.url','ranger.ldap.ad.base.dn','ranger.ldap.ad.bind.dn','ranger.ldap.ad.bind.password','ranger.ldap.ad.referral','ranger.ldap.ad.user.searchfilter']
+        foreignKeys: ['ranger.ldap.ad.url','ranger.ldap.ad.base.dn','ranger.ldap.ad.bind.dn','ranger.ldap.ad.bind.password','ranger.ldap.ad.referral','ranger.ldap.ad.user.searchfilter']
       },
       {
         displayName: 'UNIX',

http://git-wip-us.apache.org/repos/asf/ambari/blob/ccd64811/ambari-web/app/mappers/configs/themes_mapper.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mappers/configs/themes_mapper.js b/ambari-web/app/mappers/configs/themes_mapper.js
index b55a695..c296713 100644
--- a/ambari-web/app/mappers/configs/themes_mapper.js
+++ b/ambari-web/app/mappers/configs/themes_mapper.js
@@ -21,6 +21,7 @@ App.themesMapper = App.QuickDataMapper.create({
   tabModel: App.Tab,
   sectionModel: App.Section,
   subSectionModel: App.SubSection,
+  subSectionTabModel: App.SubSectionTab,
   configConditionModel: App.ConfigCondition,
 
   tabConfig: {
@@ -61,6 +62,13 @@ App.themesMapper = App.QuickDataMapper.create({
     "left_vertical_splitter": "left-vertical-splitter"
   },
 
+  subSectionTabConfig: {
+    "id": "name",
+    "name": "name",
+    "display_name": "display-name",
+    "sub_section_id": "sub_section_id"
+  },
+
   map: function (json) {
     var tabs = [];
     json.items.forEach(function(item) {
@@ -100,6 +108,21 @@ App.themesMapper = App.QuickDataMapper.create({
                   var parsedSubSection = this.parseIt(subSection, this.get("subSectionConfig"));
                   parsedSubSection.section_id = parsedSection.id;
 
+                  if (subSection['subsection-tabs']) {
+                    var subSectionTabs = [];
+
+                    subSection['subsection-tabs'].forEach(function (subSectionTab) {
+                      var parsedSubSectionTab = this.parseIt(subSectionTab, this.get("subSectionTabConfig"));
+                      parsedSubSectionTab.sub_section_id = parsedSubSection.id;
+
+                      subSectionTabs.push(parsedSubSectionTab);
+                    }, this);
+                    subSectionTabs[0].is_active = true;
+
+                    App.store.loadMany(this.get("subSectionTabModel"), subSectionTabs);
+                    parsedSubSection.sub_section_tabs = subSectionTabs.mapProperty("id");
+                  }
+
                   subSections.push(parsedSubSection);
                 }, this);
                 App.store.loadMany(this.get("subSectionModel"), subSections);
@@ -130,15 +153,25 @@ App.themesMapper = App.QuickDataMapper.create({
     Em.getWithDefault(json, "ThemeInfo.theme_data.Theme.configuration.placement.configs", []).forEach(function(configLink) {
       var configId = this.getConfigId(configLink);
       var subSectionId = configLink["subsection-name"];
-      var subSection = App.SubSection.find(subSectionId);
+      var subSectionTabId = configLink["subsection-tab-name"];
+      if (subSectionTabId) {
+        var subSectionTab = App.SubSectionTab.find(subSectionTabId);
+        var subSectionTabDependsOnConfigs = subSectionTab.get('dependsOn');
+      } else if (subSectionId) {
+        var subSection = App.SubSection.find(subSectionId);
+        var subSectionDependsOnConfigs = subSection.get('dependsOn');
+      }
       var configProperty = App.StackConfigProperty.find(configId);
-      var subSectionDependsOnConfigs = subSection.get('dependsOn');
-      var configDependsOnOtherConfigs =  configLink["depends-on"] || [];
-      var dependsOnConfigs = configDependsOnOtherConfigs.concat(subSectionDependsOnConfigs);
+
+      var configDependsOnOtherConfigs = configLink["depends-on"] || [];
+      var dependsOnConfigs = configDependsOnOtherConfigs.concat(subSectionDependsOnConfigs || []).concat(subSectionTabDependsOnConfigs || []);
 
       if (configProperty.get('id') && subSection) {
         subSection.get('configProperties').pushObject(configProperty);
         configProperty.set('subSection', subSection);
+      } else if (configProperty.get('id') && subSectionTab) {
+        subSectionTab.get('configProperties').pushObject(configProperty);
+        configProperty.set('subSectionTab', subSectionTab);
       } else {
         console.log('there is no such property: ' + configId + '. Or subsection: ' + subSectionId);
         var valueAttributes = configLink["property_value_attributes"];

http://git-wip-us.apache.org/repos/asf/ambari/blob/ccd64811/ambari-web/app/models.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models.js b/ambari-web/app/models.js
index 1b34b0c..8117f62 100644
--- a/ambari-web/app/models.js
+++ b/ambari-web/app/models.js
@@ -66,9 +66,10 @@ require('models/configs/stack_config_property');
 require('models/configs/config_group');
 require('models/configs/config_version');
 require('models/configs/config_property');
-require('models/configs/tab');
-require('models/configs/section');
-require('models/configs/sub_section');
+require('models/configs/theme/tab');
+require('models/configs/theme/section');
+require('models/configs/theme/sub_section');
+require('models/configs/theme/sub_section_tab');
 require('models/configs/objects/service_config');
 require('models/configs/objects/service_config_category');
 require('models/configs/objects/service_config_property');

http://git-wip-us.apache.org/repos/asf/ambari/blob/ccd64811/ambari-web/app/models/configs/section.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/configs/section.js b/ambari-web/app/models/configs/section.js
deleted file mode 100644
index c04665e..0000000
--- a/ambari-web/app/models/configs/section.js
+++ /dev/null
@@ -1,138 +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.
- */
-
-var App = require('app');
-
-App.Section = DS.Model.extend({
-
-  id: DS.attr('string'),
-
-  /**
-   * @type {string}
-   */
-  name: DS.attr('string'),
-
-  /**
-   * @type {string}
-   */
-  displayName: DS.attr('string'),
-
-  /**
-   * @type {number}
-   */
-  rowIndex: DS.attr('number', {defaultValue: 1}),
-
-  /**
-   * @type {number}
-   */
-  rowSpan: DS.attr('number', {defaultValue: 1}),
-
-  /**
-   * @type {number}
-   */
-  columnIndex: DS.attr('number', {defaultValue: 1}),
-
-  /**
-   * @type {number}
-   */
-  columnSpan: DS.attr('number', {defaultValue: 1}),
-
-  /**
-   * @type {number}
-   */
-  sectionColumns: DS.attr('number', {defaultValue: 1}),
-
-  /**
-   * @type {number}
-   */
-  sectionRows: DS.attr('number', {defaultValue: 1}),
-
-  /**
-   * @type {App.SubSection[]}
-   */
-  subSections: DS.hasMany('App.SubSection'),
-
-  /**
-   * @type {App.Tab}
-   */
-  tab: DS.belongsTo('App.Tab'),
-
-  /**
-   * Number of the errors in all subsections in the current section
-   * @type {number}
-   */
-  errorsCount: function () {
-    var errors = this.get('subSections').filterProperty('isSectionVisible').mapProperty('errorsCount');
-    return errors.length ? errors.reduce(Em.sum) : 0;
-  }.property('subSections.@each.errorsCount', 'subSections.@each.isSectionVisible'),
-
-  /**
-   * @type {boolean}
-   */
-  isFirstRow: function () {
-    return this.get('rowIndex') == 0;
-  }.property('rowIndex'),
-
-  /**
-   * @type {boolean}
-   */
-  isMiddleRow: function () {
-    return this.get('rowIndex') != 0 && (this.get('rowIndex') + this.get('rowSpan') < this.get('tab.rows'));
-  }.property('rowIndex', 'rowSpan', 'tab.rows'),
-
-  /**
-   * @type {boolean}
-   */
-  isLastRow: function () {
-    return this.get('rowIndex') + this.get('rowSpan') == this.get('tab.rows');
-  }.property('rowIndex', 'rowSpan', 'tab.rows'),
-
-  /**
-   * @type {boolean}
-   */
-  isFirstColumn: function () {
-    return this.get('columnIndex') == 0;
-  }.property('columnIndex'),
-
-  /**
-   * @type {boolean}
-   */
-  isMiddleColumn: function () {
-    return this.get('columnIndex') != 0 && (this.get('columnIndex') + this.get('columnSpan') < this.get('tab.columns'));
-  }.property('columnIndex', 'columnSpan', 'tab.columns'),
-
-  /**
-   * @type {boolean}
-   */
-  isLastColumn: function () {
-    return this.get('columnIndex') + this.get('columnSpan') == this.get('tab.columns');
-  }.property('columnIndex', 'columnSpan', 'tab.columns'),
-
-  /**
-   * Determines if section is filtered out (all it's subsections should be hidden)
-   * @type {boolean}
-   */
-  isHiddenByFilter: function () {
-    return !this.get('subSections').someProperty('isSectionVisible', true);
-  }.property('subSections.@each.isHiddenByFilter')
-
-});
-
-
-App.Section.FIXTURES = [];
-

http://git-wip-us.apache.org/repos/asf/ambari/blob/ccd64811/ambari-web/app/models/configs/stack_config_property.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/configs/stack_config_property.js b/ambari-web/app/models/configs/stack_config_property.js
index 76e3b5f..a02c8df 100644
--- a/ambari-web/app/models/configs/stack_config_property.js
+++ b/ambari-web/app/models/configs/stack_config_property.js
@@ -156,6 +156,12 @@ App.StackConfigProperty = DS.Model.extend({
    */
   subSection: DS.belongsTo('App.SubSection'),
 
+  /**
+   * sub section tab to which belongs this property
+   * @property {App.SubSectionTab}
+   */
+  subSectionTab: DS.belongsTo('App.SubSectionTab'),
+
   /******************************* UI properties ****************************************/
 
   /**

http://git-wip-us.apache.org/repos/asf/ambari/blob/ccd64811/ambari-web/app/models/configs/sub_section.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/configs/sub_section.js b/ambari-web/app/models/configs/sub_section.js
deleted file mode 100644
index b7abb4f..0000000
--- a/ambari-web/app/models/configs/sub_section.js
+++ /dev/null
@@ -1,176 +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.
- */
-
-var App = require('app');
-
-App.SubSection = DS.Model.extend({
-
-  id: DS.attr('string'),
-
-  /**
-   * @type {string}
-   */
-  name: DS.attr('string'),
-
-  /**
-   * @type {string}
-   */
-  displayName: DS.attr('string'),
-
-  /**
-   * @type {boolean}
-   */
-  border: DS.attr('boolean', {defaultValue: false}),
-
-  /**
-   * @type {number}
-   */
-  rowIndex: DS.attr('number', {defaultValue: 1}),
-
-  /**
-   * @type {number}
-   */
-  columnIndex: DS.attr('number', {defaultValue: 1}),
-
-  /**
-   * @type {number}
-   */
-  rowSpan: DS.attr('number', {defaultValue: 1}),
-
-  /**
-   * @type {number}
-   */
-  columnSpan: DS.attr('number', {defaultValue: 1}),
-
-  /**
-   * @type {App.Section}
-   */
-  section: DS.belongsTo('App.Section'),
-
-  /**
-   * @type {App.StackConfigProperty[]}
-   */
-  configProperties: DS.hasMany('App.StackConfigProperty'),
-
-  dependsOn: DS.attr('array', {defaultValue: []}),
-
-  /**
-   * @type {boolean}
-   */
-  leftVerticalSplitter: DS.attr('boolean', {defaultValue: true}),
-
-  /**
-   * @type {App.ServiceConfigProperty[]}
-   */
-  configs: [],
-
-  /**
-   * Number of the errors in all configs
-   * @type {number}
-   */
-  errorsCount: function () {
-    return this.get('configs').filter(function(config) {
-      return !config.get('isValid') || (config.get('overrides') || []).someProperty('isValid', false);
-    }).length;
-  }.property('configs.@each.isValid', 'configs.@each.overrideErrorTrigger'),
-
-  /**
-   * @type {boolean}
-   */
-  addLeftVerticalSplitter: function() {
-    return !this.get('isFirstColumn') && this.get('leftVerticalSplitter');
-  }.property('isFirstColumn', 'leftVerticalSplitter'),
-
-  /**
-   * @type {boolean}
-   */
-  addRightVerticalSplitter: function() {
-    return !this.get('isLastColumn');
-  }.property('isLastColumn'),
-
-  /**
-   * @type {boolean}
-   */
-  showTopSplitter: function() {
-    return !this.get('isFirstRow') && !this.get('border');
-  }.property('isFirstRow', 'border'),
-
-  /**
-   * @type {boolean}
-   */
-  isFirstRow: function () {
-    return this.get('rowIndex') == 0;
-  }.property('rowIndex'),
-
-  /**
-   * @type {boolean}
-   */
-  isMiddleRow: function () {
-    return this.get('rowIndex') != 0 && (this.get('rowIndex') + this.get('rowSpan') < this.get('section.sectionRows'));
-  }.property('rowIndex', 'rowSpan', 'section.sectionRows'),
-
-  /**
-   * @type {boolean}
-   */
-  isLastRow: function () {
-    return this.get('rowIndex') + this.get('rowSpan') == this.get('section.sectionRows');
-  }.property('rowIndex', 'rowSpan', 'section.sectionRows'),
-
-  /**
-   * @type {boolean}
-   */
-  isFirstColumn: function () {
-    return this.get('columnIndex') == 0;
-  }.property('columnIndex'),
-
-  /**
-   * @type {boolean}
-   */
-  isMiddleColumn: function () {
-    return this.get('columnIndex') != 0 && (this.get('columnIndex') + this.get('columnSpan') < this.get('section.sectionColumns'));
-  }.property('columnIndex', 'columnSpan', 'section.sectionColumns'),
-
-  /**
-   * @type {boolean}
-   */
-  isLastColumn: function () {
-    return this.get('columnIndex') + this.get('columnSpan') == this.get('section.sectionColumns');
-  }.property('columnIndex', 'columnSpan', 'section.sectionColumns'),
-
-  /**
-   * Determines if subsection is filtered by checking it own configs
-   * If there is no configs, subsection can't be hidden
-   * @type {boolean}
-   */
-  isHiddenByFilter: function () {
-    var configs = this.get('configs');
-    return configs.length ? configs.everyProperty('isHiddenByFilter', true) : false;
-  }.property('configs.@each.isHiddenByFilter'),
-
-  /**
-   * Determines if subsection is visible
-   * @type {boolean}
-   */
-  isSectionVisible: function () {
-    return !this.get('isHiddenByFilter') && this.get('configs').someProperty('isVisible', true);
-  }.property('isHiddenByFilter', 'configs.@each.isVisible')
-});
-
-
-App.SubSection.FIXTURES = [];
-

http://git-wip-us.apache.org/repos/asf/ambari/blob/ccd64811/ambari-web/app/models/configs/tab.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/configs/tab.js b/ambari-web/app/models/configs/tab.js
deleted file mode 100644
index 0940022..0000000
--- a/ambari-web/app/models/configs/tab.js
+++ /dev/null
@@ -1,73 +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.
- */
-
-var App = require('app');
-
-App.Tab = DS.Model.extend({
-  id: DS.attr('string'),
-  name: DS.attr('string'),
-  displayName: DS.attr('string'),
-  columns: DS.attr('number', {defaultValue: 1}),
-  rows: DS.attr('number', {defaultValue: 1}),
-  isAdvanced: DS.attr('boolean', {defaultValue: false}),
-  serviceName: DS.attr('string'),
-  sections: DS.hasMany('App.Section'),
-  isAdvancedHidden: DS.attr('boolean', {defaultValue: false}),
-  isRendered: DS.attr('boolean', {defaultValue: false}),
-
-  /**
-   * Number of the errors in all sections in the current tab
-   * @type {number}
-   */
-  errorsCount: function () {
-    var errors = this.get('sections').mapProperty('errorsCount');
-    return errors.length ? errors.reduce(Em.sum) : 0;
-  }.property('sections.@each.errorsCount'),
-
-  /**
-   * Class name used for tab switching
-   *
-   * @type {String}
-   * @property headingClass
-   */
-  headingClass: function() {
-    return '.' + this.get('id');
-  }.property('id'),
-
-  /**
-   * tooltip message.
-   * for now used when tab is disabled
-   * @type {String}
-   */
-  tooltipMsg: function() {
-    return this.get('isHiddenByFilter') ? Em.I18n.t('services.service.config.nothing.to.display') : '';
-  }.property('isHiddenByFilter'),
-
-  /**
-   * Determines if tab is filtered out (all it's sections should be hidden)
-   * If it's an Advanced Tab it can't be hidden
-   * @type {boolean}
-   */
-  isHiddenByFilter: function () {
-    return this.get('isAdvanced') ? this.get('isAdvancedHidden') : this.get('sections').everyProperty('isHiddenByFilter', true);
-  }.property('isAdvanced', 'sections.@each.isHiddenByFilter', 'isAdvancedHidden')
-
-});
-
-
-App.Tab.FIXTURES = [];

http://git-wip-us.apache.org/repos/asf/ambari/blob/ccd64811/ambari-web/app/models/configs/theme/section.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/configs/theme/section.js b/ambari-web/app/models/configs/theme/section.js
new file mode 100644
index 0000000..c04665e
--- /dev/null
+++ b/ambari-web/app/models/configs/theme/section.js
@@ -0,0 +1,138 @@
+/**
+ * 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.
+ */
+
+var App = require('app');
+
+App.Section = DS.Model.extend({
+
+  id: DS.attr('string'),
+
+  /**
+   * @type {string}
+   */
+  name: DS.attr('string'),
+
+  /**
+   * @type {string}
+   */
+  displayName: DS.attr('string'),
+
+  /**
+   * @type {number}
+   */
+  rowIndex: DS.attr('number', {defaultValue: 1}),
+
+  /**
+   * @type {number}
+   */
+  rowSpan: DS.attr('number', {defaultValue: 1}),
+
+  /**
+   * @type {number}
+   */
+  columnIndex: DS.attr('number', {defaultValue: 1}),
+
+  /**
+   * @type {number}
+   */
+  columnSpan: DS.attr('number', {defaultValue: 1}),
+
+  /**
+   * @type {number}
+   */
+  sectionColumns: DS.attr('number', {defaultValue: 1}),
+
+  /**
+   * @type {number}
+   */
+  sectionRows: DS.attr('number', {defaultValue: 1}),
+
+  /**
+   * @type {App.SubSection[]}
+   */
+  subSections: DS.hasMany('App.SubSection'),
+
+  /**
+   * @type {App.Tab}
+   */
+  tab: DS.belongsTo('App.Tab'),
+
+  /**
+   * Number of the errors in all subsections in the current section
+   * @type {number}
+   */
+  errorsCount: function () {
+    var errors = this.get('subSections').filterProperty('isSectionVisible').mapProperty('errorsCount');
+    return errors.length ? errors.reduce(Em.sum) : 0;
+  }.property('subSections.@each.errorsCount', 'subSections.@each.isSectionVisible'),
+
+  /**
+   * @type {boolean}
+   */
+  isFirstRow: function () {
+    return this.get('rowIndex') == 0;
+  }.property('rowIndex'),
+
+  /**
+   * @type {boolean}
+   */
+  isMiddleRow: function () {
+    return this.get('rowIndex') != 0 && (this.get('rowIndex') + this.get('rowSpan') < this.get('tab.rows'));
+  }.property('rowIndex', 'rowSpan', 'tab.rows'),
+
+  /**
+   * @type {boolean}
+   */
+  isLastRow: function () {
+    return this.get('rowIndex') + this.get('rowSpan') == this.get('tab.rows');
+  }.property('rowIndex', 'rowSpan', 'tab.rows'),
+
+  /**
+   * @type {boolean}
+   */
+  isFirstColumn: function () {
+    return this.get('columnIndex') == 0;
+  }.property('columnIndex'),
+
+  /**
+   * @type {boolean}
+   */
+  isMiddleColumn: function () {
+    return this.get('columnIndex') != 0 && (this.get('columnIndex') + this.get('columnSpan') < this.get('tab.columns'));
+  }.property('columnIndex', 'columnSpan', 'tab.columns'),
+
+  /**
+   * @type {boolean}
+   */
+  isLastColumn: function () {
+    return this.get('columnIndex') + this.get('columnSpan') == this.get('tab.columns');
+  }.property('columnIndex', 'columnSpan', 'tab.columns'),
+
+  /**
+   * Determines if section is filtered out (all it's subsections should be hidden)
+   * @type {boolean}
+   */
+  isHiddenByFilter: function () {
+    return !this.get('subSections').someProperty('isSectionVisible', true);
+  }.property('subSections.@each.isHiddenByFilter')
+
+});
+
+
+App.Section.FIXTURES = [];
+

http://git-wip-us.apache.org/repos/asf/ambari/blob/ccd64811/ambari-web/app/models/configs/theme/sub_section.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/configs/theme/sub_section.js b/ambari-web/app/models/configs/theme/sub_section.js
new file mode 100644
index 0000000..ba6cc99
--- /dev/null
+++ b/ambari-web/app/models/configs/theme/sub_section.js
@@ -0,0 +1,189 @@
+/**
+ * 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.
+ */
+
+var App = require('app');
+
+App.SubSection = DS.Model.extend({
+
+  id: DS.attr('string'),
+
+  /**
+   * @type {string}
+   */
+  name: DS.attr('string'),
+
+  /**
+   * @type {string}
+   */
+  displayName: DS.attr('string'),
+
+  /**
+   * @type {boolean}
+   */
+  border: DS.attr('boolean', {defaultValue: false}),
+
+  /**
+   * @type {number}
+   */
+  rowIndex: DS.attr('number', {defaultValue: 1}),
+
+  /**
+   * @type {number}
+   */
+  columnIndex: DS.attr('number', {defaultValue: 1}),
+
+  /**
+   * @type {number}
+   */
+  rowSpan: DS.attr('number', {defaultValue: 1}),
+
+  /**
+   * @type {number}
+   */
+  columnSpan: DS.attr('number', {defaultValue: 1}),
+
+  /**
+   * @type {App.Section}
+   */
+  section: DS.belongsTo('App.Section'),
+
+  /**
+   * @type {App.StackConfigProperty[]}
+   */
+  configProperties: DS.hasMany('App.StackConfigProperty'),
+
+  /**
+   * @type {App.SubSectionTab[]}
+   */
+  subSectionTabs: DS.hasMany('App.SubSectionTab'),
+
+
+  dependsOn: DS.attr('array', {defaultValue: []}),
+
+  /**
+   * @type {boolean}
+   */
+  leftVerticalSplitter: DS.attr('boolean', {defaultValue: true}),
+
+  /**
+   * @type {App.ServiceConfigProperty[]}
+   */
+  configs: [],
+
+  /**
+   * @type {boolean}
+   */
+  hasTabs: function() {
+    return this.get('subSectionTabs.length');
+  }.property('subSectionTabs.length'),
+
+  /**
+   * Number of the errors in all configs
+   * @type {number}
+   */
+  errorsCount: function () {
+    return this.get('configs').filter(function(config) {
+      return !config.get('isValid') || (config.get('overrides') || []).someProperty('isValid', false);
+    }).length;
+  }.property('configs.@each.isValid', 'configs.@each.overrideErrorTrigger'),
+
+  /**
+   * @type {boolean}
+   */
+  addLeftVerticalSplitter: function() {
+    return !this.get('isFirstColumn') && this.get('leftVerticalSplitter');
+  }.property('isFirstColumn', 'leftVerticalSplitter'),
+
+  /**
+   * @type {boolean}
+   */
+  addRightVerticalSplitter: function() {
+    return !this.get('isLastColumn');
+  }.property('isLastColumn'),
+
+  /**
+   * @type {boolean}
+   */
+  showTopSplitter: function() {
+    return !this.get('isFirstRow') && !this.get('border');
+  }.property('isFirstRow', 'border'),
+
+  /**
+   * @type {boolean}
+   */
+  isFirstRow: function () {
+    return this.get('rowIndex') == 0;
+  }.property('rowIndex'),
+
+  /**
+   * @type {boolean}
+   */
+  isMiddleRow: function () {
+    return this.get('rowIndex') != 0 && (this.get('rowIndex') + this.get('rowSpan') < this.get('section.sectionRows'));
+  }.property('rowIndex', 'rowSpan', 'section.sectionRows'),
+
+  /**
+   * @type {boolean}
+   */
+  isLastRow: function () {
+    return this.get('rowIndex') + this.get('rowSpan') == this.get('section.sectionRows');
+  }.property('rowIndex', 'rowSpan', 'section.sectionRows'),
+
+  /**
+   * @type {boolean}
+   */
+  isFirstColumn: function () {
+    return this.get('columnIndex') == 0;
+  }.property('columnIndex'),
+
+  /**
+   * @type {boolean}
+   */
+  isMiddleColumn: function () {
+    return this.get('columnIndex') != 0 && (this.get('columnIndex') + this.get('columnSpan') < this.get('section.sectionColumns'));
+  }.property('columnIndex', 'columnSpan', 'section.sectionColumns'),
+
+  /**
+   * @type {boolean}
+   */
+  isLastColumn: function () {
+    return this.get('columnIndex') + this.get('columnSpan') == this.get('section.sectionColumns');
+  }.property('columnIndex', 'columnSpan', 'section.sectionColumns'),
+
+  /**
+   * Determines if subsection is filtered by checking it own configs
+   * If there is no configs, subsection can't be hidden
+   * @type {boolean}
+   */
+  isHiddenByFilter: function () {
+    var configs = this.get('configs');
+    return configs.length ? configs.everyProperty('isHiddenByFilter', true) : false;
+  }.property('configs.@each.isHiddenByFilter'),
+
+  /**
+   * Determines if subsection is visible
+   * @type {boolean}
+   */
+  isSectionVisible: function () {
+    return !this.get('isHiddenByFilter') && this.get('configs').someProperty('isVisible', true);
+  }.property('isHiddenByFilter', 'configs.@each.isVisible')
+});
+
+
+App.SubSection.FIXTURES = [];
+

http://git-wip-us.apache.org/repos/asf/ambari/blob/ccd64811/ambari-web/app/models/configs/theme/sub_section_tab.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/configs/theme/sub_section_tab.js b/ambari-web/app/models/configs/theme/sub_section_tab.js
new file mode 100644
index 0000000..1d6eedf
--- /dev/null
+++ b/ambari-web/app/models/configs/theme/sub_section_tab.js
@@ -0,0 +1,89 @@
+/**
+ * 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.
+ */
+
+var App = require('app');
+
+App.SubSectionTab = DS.Model.extend({
+
+  id: DS.attr('string'),
+
+  /**
+   * @type {string}
+   */
+  name: DS.attr('string'),
+
+  /**
+   * @type {string}
+   */
+  displayName: DS.attr('string'),
+
+  /**
+   * @type {App.Section}
+   */
+  subSection: DS.belongsTo('App.SubSection'),
+
+  /**
+   * @type {App.StackConfigProperty[]}
+   */
+  configProperties: DS.hasMany('App.StackConfigProperty'),
+
+  /**
+   * @type {App.ServiceConfigProperty[]}
+   */
+  configs: [],
+
+
+  dependsOn: DS.attr('array', {defaultValue: []}),
+
+  /**
+   * @type {boolean}
+   */
+  isActive: DS.attr('boolean', {defaultValue: false}),
+
+  /**
+   * Number of the errors in all configs
+   * @type {number}
+   */
+  errorsCount: function () {
+    return this.get('configs').filter(function(config) {
+      return !config.get('isValid') || (config.get('overrides') || []).someProperty('isValid', false);
+    }).length;
+  }.property('configs.@each.isValid', 'configs.@each.overrideErrorTrigger'),
+
+  /**
+   * Determines if subsection is filtered by checking it own configs
+   * If there is no configs, subsection can't be hidden
+   * @type {boolean}
+   */
+  isHiddenByFilter: function () {
+    var configs = this.get('configs');
+    return configs.length ? configs.everyProperty('isHiddenByFilter', true) : false;
+  }.property('configs.@each.isHiddenByFilter'),
+
+  /**
+   * Determines if subsection is visible
+   * @type {boolean}
+   */
+  isVisible: function () {
+    return !this.get('isHiddenByFilter') && this.get('configs').someProperty('isVisible', true);
+  }.property('isHiddenByFilter', 'configs.@each.isVisible')
+});
+
+
+App.SubSectionTab.FIXTURES = [];
+

http://git-wip-us.apache.org/repos/asf/ambari/blob/ccd64811/ambari-web/app/models/configs/theme/tab.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/configs/theme/tab.js b/ambari-web/app/models/configs/theme/tab.js
new file mode 100644
index 0000000..0940022
--- /dev/null
+++ b/ambari-web/app/models/configs/theme/tab.js
@@ -0,0 +1,73 @@
+/**
+ * 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.
+ */
+
+var App = require('app');
+
+App.Tab = DS.Model.extend({
+  id: DS.attr('string'),
+  name: DS.attr('string'),
+  displayName: DS.attr('string'),
+  columns: DS.attr('number', {defaultValue: 1}),
+  rows: DS.attr('number', {defaultValue: 1}),
+  isAdvanced: DS.attr('boolean', {defaultValue: false}),
+  serviceName: DS.attr('string'),
+  sections: DS.hasMany('App.Section'),
+  isAdvancedHidden: DS.attr('boolean', {defaultValue: false}),
+  isRendered: DS.attr('boolean', {defaultValue: false}),
+
+  /**
+   * Number of the errors in all sections in the current tab
+   * @type {number}
+   */
+  errorsCount: function () {
+    var errors = this.get('sections').mapProperty('errorsCount');
+    return errors.length ? errors.reduce(Em.sum) : 0;
+  }.property('sections.@each.errorsCount'),
+
+  /**
+   * Class name used for tab switching
+   *
+   * @type {String}
+   * @property headingClass
+   */
+  headingClass: function() {
+    return '.' + this.get('id');
+  }.property('id'),
+
+  /**
+   * tooltip message.
+   * for now used when tab is disabled
+   * @type {String}
+   */
+  tooltipMsg: function() {
+    return this.get('isHiddenByFilter') ? Em.I18n.t('services.service.config.nothing.to.display') : '';
+  }.property('isHiddenByFilter'),
+
+  /**
+   * Determines if tab is filtered out (all it's sections should be hidden)
+   * If it's an Advanced Tab it can't be hidden
+   * @type {boolean}
+   */
+  isHiddenByFilter: function () {
+    return this.get('isAdvanced') ? this.get('isAdvancedHidden') : this.get('sections').everyProperty('isHiddenByFilter', true);
+  }.property('isAdvanced', 'sections.@each.isHiddenByFilter', 'isAdvancedHidden')
+
+});
+
+
+App.Tab.FIXTURES = [];

http://git-wip-us.apache.org/repos/asf/ambari/blob/ccd64811/ambari-web/app/templates/common/configs/service_config_layout_tab.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/common/configs/service_config_layout_tab.hbs b/ambari-web/app/templates/common/configs/service_config_layout_tab.hbs
index 69de315..0afe6ac 100644
--- a/ambari-web/app/templates/common/configs/service_config_layout_tab.hbs
+++ b/ambari-web/app/templates/common/configs/service_config_layout_tab.hbs
@@ -47,6 +47,30 @@
                                 {{/if}}
                               {{/if}}
                             {{/each}}
+                            {{#if subsection.hasTabs}}
+                              <ul class="nav nav-tabs mbm config-tabs">
+                                {{#each subSectionTab in subsection.subSectionTabs}}
+                                  <li rel='tooltip' {{bindAttr class="subSectionTab.isActive:active subSectionTab.isHiddenByFilter:disabled" data-original-title="tab.tooltipMsg"}}>
+                                    <a href="#" {{action setActiveSubTab subSectionTab target="view"}}{{bindAttr data-target="subSectionTab.id"}} data-toggle="tab">
+                                      {{subSectionTab.displayName}}
+                                    </a>
+                                  </li>
+                                {{/each}}
+                              </ul>
+                              <div class="tab-content service-config-tab-content">
+                              {{#each subSectionTab in subsection.subSectionTabs}}
+                                {{#each config in subSectionTab.configs}}
+                                  <div {{bindAttr class=":tab-pane subSectionTab.isActive:active subSectionTab.id"}}>
+                                    {{#if config.isVisible}}
+                                      {{#unless config.isHiddenByFilter}}
+                                        {{view config.widget configBinding="config" canEditBinding="view.canEdit" sectionBinding="section" subSectionBinding="subsection" tabBinding="tab"}}
+                                      {{/unless}}
+                                    {{/if}}
+                                  </div>
+                                {{/each}}
+                              {{/each}}
+                              </div>
+                            {{/if}}
                           </div>
                         </div>
                       </td>

http://git-wip-us.apache.org/repos/asf/ambari/blob/ccd64811/ambari-web/app/views/common/configs/service_config_layout_tab_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/configs/service_config_layout_tab_view.js b/ambari-web/app/views/common/configs/service_config_layout_tab_view.js
index 6815a0d..5cce947 100644
--- a/ambari-web/app/views/common/configs/service_config_layout_tab_view.js
+++ b/ambari-web/app/views/common/configs/service_config_layout_tab_view.js
@@ -72,58 +72,18 @@ App.ServiceConfigLayoutTabView = Em.View.extend(App.ConfigOverridable, {
    * Prepare configs for render
    * <code>subsection.configs</code> is an array of App.StackConfigProperty, but not App.ConfigProperty,
    * so proper config-properties should be linked to the subsections.
-   * Also correct widget should be used for each config (it's selected according to <code>widget.type</code> and
-   * <code>widgetTypeMap</code>). It may throw an error if needed widget can't be found in the <code>widgetTypeMap</code>
    * @method prepareConfigProperties
    */
   prepareConfigProperties: function () {
-    var widgetTypeMap = this.get('widgetTypeMap');
     var self = this;
-    var serviceName = self.get('controller.selectedService.serviceName');
     this.get('content.sectionRows').forEach(function (row) {
       row.forEach(function (section) {
         section.get('subsectionRows').forEach(function (subRow) {
           subRow.forEach(function (subsection) {
-            var subsectionName = subsection.get('name');
-            var uiOnlyConfigs = App.uiOnlyConfigDerivedFromTheme.filterProperty('subSection.name', subsectionName);
-
-            subsection.set('configs', []);
-            subsection.get('configProperties').toArray().concat(uiOnlyConfigs).forEach(function (config) {
-
-              var service = self.get('controller.stepConfigs').findProperty('serviceName', serviceName);
-              if (!service) return;
-              var configProperty = service.get('configs').findProperty('name', config.get('name'));
-              if (!configProperty) return;
-
-              subsection.get('configs').pushObject(configProperty);
-              var configWidgetType = config.get('widget.type');
-              var widget = widgetTypeMap[configWidgetType];
-              Em.assert('Unknown config widget view for config ' + configProperty.get('id') + ' with type ' + configWidgetType, widget);
-
-              var additionalProperties = {
-                widget: widget,
-                stackConfigProperty: config
-              };
-
-              var configConditions = App.ConfigCondition.find().filter(function(_configCondition){
-                var conditionalConfigs = _configCondition.get('configs').filterProperty('fileName', config.get('filename')).filterProperty('configName', config.get('name'));
-                return (conditionalConfigs && conditionalConfigs.length);
-              }, this);
-
-              if (configConditions && configConditions.length) {
-                additionalProperties.configConditions = configConditions;
-              }
-              configProperty.setProperties(additionalProperties);
-
-              if (configProperty.get('overrides')) {
-                configProperty.get('overrides').setEach('stackConfigProperty', config);
-              }
-              if (configProperty.get('compareConfigs')) {
-                configProperty.get('compareConfigs').invoke('setProperties', {
-                  isComparison: false,
-                  stackConfigProperty: config
-                });
-              }
+            var uiOnlyConfigs = App.uiOnlyConfigDerivedFromTheme.filterProperty('subSection.name', subsection.get('name'));
+            self.setConfigsToContainer(subsection, uiOnlyConfigs);
+            subsection.get('subSectionTabs').forEach(function (subSectionTab) {
+              self.setConfigsToContainer(subSectionTab);
             });
           });
         });
@@ -131,6 +91,70 @@ App.ServiceConfigLayoutTabView = Em.View.extend(App.ConfigOverridable, {
     });
   },
 
+  /**
+   * set {code} configs {code} array of subsection or subsection tab.
+   * Also correct widget should be used for each config (it's selected according to <code>widget.type</code> and
+   * <code>widgetTypeMap</code>). It may throw an error if needed widget can't be found in the <code>widgetTypeMap</code>
+   * @param containerObject
+   * @param [uiOnlyConfigs]
+   */
+  setConfigsToContainer: function(containerObject, uiOnlyConfigs) {
+    var self = this;
+    var service = this.get('controller.stepConfigs').findProperty('serviceName', this.get('controller.selectedService.serviceName'));
+    if (!service) return;
+    containerObject.set('configs', []);
+
+    containerObject.get('configProperties').toArray().concat(uiOnlyConfigs || []).forEach(function (config) {
+
+      var configProperty = service.get('configs').findProperty('name', config.get('name'));
+      if (!configProperty) return;
+
+      containerObject.get('configs').pushObject(configProperty);
+      var configWidgetType = config.get('widget.type');
+      var widget = self.get('widgetTypeMap')[configWidgetType];
+      Em.assert('Unknown config widget view for config ' + configProperty.get('id') + ' with type ' + configWidgetType, widget);
+
+      var additionalProperties = {
+        widget: widget,
+        stackConfigProperty: config
+      };
+
+      var configConditions = App.ConfigCondition.find().filter(function (_configCondition) {
+        var conditionalConfigs = _configCondition.get('configs').filterProperty('fileName', config.get('filename')).filterProperty('configName', config.get('name'));
+        return (conditionalConfigs && conditionalConfigs.length);
+      }, this);
+
+      if (configConditions && configConditions.length) {
+        additionalProperties.configConditions = configConditions;
+      }
+      configProperty.setProperties(additionalProperties);
+
+      if (configProperty.get('overrides')) {
+        configProperty.get('overrides').setEach('stackConfigProperty', config);
+      }
+      if (configProperty.get('compareConfigs')) {
+        configProperty.get('compareConfigs').invoke('setProperties', {
+          isComparison: false,
+          stackConfigProperty: config
+        });
+      }
+    });
+  },
+
+  /**
+   * changes active subsection tab
+   * @param event
+   */
+  setActiveSubTab: function(event) {
+    if (!event.context) return;
+    try {
+      event.context.get('subSection.subSectionTabs').setEach('isActive', false);
+      event.context.set('isActive', true);
+    } catch (e) {
+      console.error('Can\'t update active subsection tab');
+    }
+  },
+
   didInsertElement: function () {
     this.set('dataIsReady', false);
     this._super();

http://git-wip-us.apache.org/repos/asf/ambari/blob/ccd64811/ambari-web/test/mappers/configs/themes_mapper_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/mappers/configs/themes_mapper_test.js b/ambari-web/test/mappers/configs/themes_mapper_test.js
index 99456e0..d3d6e2e 100644
--- a/ambari-web/test/mappers/configs/themes_mapper_test.js
+++ b/ambari-web/test/mappers/configs/themes_mapper_test.js
@@ -18,9 +18,9 @@
 
 var App = require('app');
 require('mappers/configs/themes_mapper');
-require('models/configs/tab');
-require('models/configs/section');
-require('models/configs/sub_section');
+require('models/configs/theme/tab');
+require('models/configs/theme/section');
+require('models/configs/theme/sub_section');
 require('models/configs/stack_config_property');
 
 describe('App.themeMapper', function () {