You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by ak...@apache.org on 2017/10/19 14:37:25 UTC

ambari git commit: AMBARI-21307 Updated UI form (akovalenko)

Repository: ambari
Updated Branches:
  refs/heads/feature-branch-AMBARI-21307 d3120b009 -> 403973781


AMBARI-21307 Updated UI form (akovalenko)


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

Branch: refs/heads/feature-branch-AMBARI-21307
Commit: 40397378140b11f6b7e42e28e59a4de2fa4c2960
Parents: d3120b0
Author: Aleksandr Kovalenko <ak...@hortonworks.com>
Authored: Thu Oct 19 17:36:24 2017 +0300
Committer: Aleksandr Kovalenko <ak...@hortonworks.com>
Committed: Thu Oct 19 17:36:24 2017 +0300

----------------------------------------------------------------------
 .../resources/ui/admin-web/app/scripts/app.js   |   2 +-
 .../authentication/AuthenticationMainCtrl.js    |  11 +-
 .../ui/admin-web/app/scripts/i18n.config.js     |  22 +++-
 .../authentication/advancedAttributesForm.html  |  84 +++++++++++++
 .../app/views/authentication/main.html          | 118 +++++++++++--------
 5 files changed, 179 insertions(+), 58 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/40397378/ambari-admin/src/main/resources/ui/admin-web/app/scripts/app.js
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/app.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/app.js
index 80e2813..1caca33 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/app.js
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/app.js
@@ -30,7 +30,7 @@ angular.module('ambariAdminConsole', [
 	baseUrl: '{proxy_root}/api/v1'.replace(/\{.+\}/g, ''),
   testMode: (window.location.port == 8000),
   mockDataPrefix: 'assets/data/',
-  isLDAPConfigurationSupported: false,
+  isLDAPConfigurationSupported: true,
   isLoginActivitiesSupported: false,
   maxStackTraceLength: 1000,
   errorStorageSize: 500000

http://git-wip-us.apache.org/repos/asf/ambari/blob/40397378/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/authentication/AuthenticationMainCtrl.js
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/authentication/AuthenticationMainCtrl.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/authentication/AuthenticationMainCtrl.js
index bce9189..b8428a0 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/authentication/AuthenticationMainCtrl.js
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/authentication/AuthenticationMainCtrl.js
@@ -30,7 +30,9 @@ angular.module('ambariAdminConsole')
       trustStoreTypeOptions: ['jks', 'jceks', 'pkcs12']
     };
     $scope.attributes = {
-      detection: 'auto'
+      detection: 'ad',
+      referralHandling: 'Follow',
+      referralHandlingOptions: ['Follow', 'Ignore']
     };
 
     $scope.isConnectivityFormInvalid = true;
@@ -58,6 +60,8 @@ angular.module('ambariAdminConsole')
 
     $scope.isTestAttributesFormShown = false;
 
+    $scope.isAdvancedCollapsed = true;
+
     $scope.toggleAuthentication = function () {
       $scope.isConnectionTestRunning = false;
       $scope.isConnectionTestComplete = false;
@@ -140,6 +144,10 @@ angular.module('ambariAdminConsole')
       $scope.isSavingSuccessful = false;
     };
 
+    $scope.toggleAdvanced = function () {
+      $scope.isAdvancedCollapsed = !$scope.isAdvancedCollapsed;
+    };
+
     $scope.$watch('connectivity', function (form, oldForm, scope) {
       scope.isConnectivityFormInvalid = !(form.host && form.port
         && (form.trustStore === 'default' || form.trustStorePath && form.trustStorePassword)
@@ -158,6 +166,7 @@ angular.module('ambariAdminConsole')
       scope.isTestAttributesFormShown = false;
       scope.isAttributeDetectionComplete = false;
       scope.isAttributeDetectionSuccessful = false;
+      scope.isAdvancedCollapsed = true;
     });
 
     $scope.$watch(function (scope) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/40397378/ambari-admin/src/main/resources/ui/admin-web/app/scripts/i18n.config.js
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/i18n.config.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/i18n.config.js
index 183a276..fc33fd3 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/i18n.config.js
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/i18n.config.js
@@ -96,6 +96,7 @@ angular.module('ambariAdminConsole')
     'common.undo': 'Undo',
     'common.fromGroupMark': '(from group)',
     'common.copy': '_Copy',
+    'common.advanced': 'Advanced',
 
     'common.clusterNameChangeConfirmation.title': 'Confirm Cluster Name Change',
     'common.clusterNameChangeConfirmation.message': 'Are you sure you want to change the cluster name to {{clusterName}}?',
@@ -432,6 +433,7 @@ angular.module('ambariAdminConsole')
     'authentication.connectivity.host': 'LDAP Server Host',
     'authentication.connectivity.port': 'LDAP Server Port',
     'authentication.connectivity.ssl': 'Use SSL?',
+    'authentication.connectivity.anonymousBind': 'Anonymous Bind?',
 
     'authentication.connectivity.trustStore.label': 'Trust Store',
 
@@ -447,10 +449,12 @@ angular.module('ambariAdminConsole')
 
     'authentication.attributes.title': 'LDAP Attribute Configuration',
 
-    'authentication.attributes.detection.label': 'Identifying the proper attributes to be used when authenticating and looking up users and groups can be specified manually, or automatically detected. Please choose:',
+    'authentication.attributes.detection.label': 'Please choose which type of LDAP Server will be used for this configuration.',
 
-    'authentication.attributes.detection.options.manual': 'Define Attributes Manually',
-    'authentication.attributes.detection.options.auto': 'Auto-Detect Attributes',
+    'authentication.attributes.detection.options.custom': 'Custom',
+    'authentication.attributes.detection.options.auto': 'Auto-Detect',
+    'authentication.attributes.detection.options.ad': 'Microsoft Active Directory',
+    'authentication.attributes.detection.options.freeIPA': 'FreeIPA/RHEL IDM',
 
     'authentication.attributes.userSearch': 'User Search Base',
     'authentication.attributes.groupSearch': 'Group Search Base',
@@ -461,15 +465,23 @@ angular.module('ambariAdminConsole')
     'authentication.attributes.groupNameAttr': 'Group Name Attribute',
     'authentication.attributes.groupMemberAttr': 'Group Member Attribute',
     'authentication.attributes.distinguishedNameAttr': 'Distinguished Name Attribute',
+    'authentication.attributes.userSearchFilter': 'User Search Filter',
+    'authentication.attributes.userMemberReplacePattern': 'User Member Replace Pattern',
+    'authentication.attributes.userMemberFilter': 'User Member Filter',
+    'authentication.attributes.groupSearchFilter': 'Group Search Filter',
+    'authentication.attributes.groupMemberReplacePattern': 'Group Member Replace Pattern',
+    'authentication.attributes.groupMemberFilter': 'Group Member Filter',
+    'authentication.attributes.forceLowercaseUsernames': 'Group Member Filter',
+    'authentication.attributes.referralHandling.label': 'Referral Handling',
+    'authentication.attributes.enablePagination': 'Enable Pagination',
 
-    'authentication.attributes.test.description': 'To quickly test the chosen attributes click the button below. During this process you can specify a test user name and password and Ambari will attempt to authenticate and retrieve group membership information',
     'authentication.attributes.test.username': 'Test Username',
     'authentication.attributes.test.password': 'Test Password',
 
     'authentication.attributes.groupsList': 'List of Groups',
 
     'authentication.attributes.controls.autoDetect': 'Perform Auto-Detection',
-    'authentication.attributes.controls.testAttrs': 'Test Attributes',
+    'authentication.attributes.controls.testAttrs': 'Test Configuration',
 
     'authentication.attributes.alerts.successfulAuth': 'Successful Authentication',
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/40397378/ambari-admin/src/main/resources/ui/admin-web/app/views/authentication/advancedAttributesForm.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/authentication/advancedAttributesForm.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/authentication/advancedAttributesForm.html
new file mode 100644
index 0000000..b1c190e
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/authentication/advancedAttributesForm.html
@@ -0,0 +1,84 @@
+<!--
+* 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.
+-->
+
+<div>
+  <div class="col-sm-4" style="text-align: right; line-height: 34px;">
+    <a href="javascript:void(null);"
+       aria-expanded="false"
+       ng-click="toggleAdvanced()"
+       aria-controls="advancedAttributes">
+      <i class="glyphicon ng-class: isAdvancedCollapsed ? 'glyphicon-chevron-right' : 'glyphicon-chevron-down'"></i> {{'common.advanced' | translate}}
+    </a>
+  </div>
+  <div class="col-sm-12 collapse ng-class: isAdvancedCollapsed ? '' : 'in'" id="advancedAttributes">
+    <div class="form-group">
+      <label for="userSearchFilter" class="control-label col-sm-4">{{'authentication.attributes.userSearchFilter' | translate}}</label>
+      <div class="col-sm-8">
+        <input type="text" class="form-control" id="userSearchFilter" ng-model="attributes.userSearchFilter">
+      </div>
+    </div>
+    <div class="form-group">
+      <label for="userMemberReplacePattern" class="control-label col-sm-4">{{'authentication.attributes.userMemberReplacePattern' | translate}}</label>
+      <div class="col-sm-8">
+        <input type="text" class="form-control" id="userMemberReplacePattern" ng-model="attributes.userMemberReplacePattern">
+      </div>
+    </div>
+    <div class="form-group">
+      <label for="userMemberFilter" class="control-label col-sm-4">{{'authentication.attributes.userMemberFilter' | translate}}</label>
+      <div class="col-sm-8">
+        <input type="text" class="form-control" id="userMemberFilter" ng-model="attributes.userMemberFilter">
+      </div>
+    </div>
+    <div class="form-group">
+      <label for="groupSearchFilter" class="control-label col-sm-4">{{'authentication.attributes.groupSearchFilter' | translate}}</label>
+      <div class="col-sm-8">
+        <input type="text" class="form-control" id="groupSearchFilter" ng-model="attributes.groupSearchFilter">
+      </div>
+    </div>
+    <div class="form-group">
+      <label for="groupMemberReplacePattern" class="control-label col-sm-4">{{'authentication.attributes.groupMemberReplacePattern' | translate}}</label>
+      <div class="col-sm-8">
+        <input type="text" class="form-control" id="groupMemberReplacePattern" ng-model="attributes.groupMemberReplacePattern">
+      </div>
+    </div>
+    <div class="form-group">
+      <label for="groupMemberFilter" class="control-label col-sm-4">{{'authentication.attributes.groupMemberFilter' | translate}}</label>
+      <div class="col-sm-8">
+        <input type="text" class="form-control" id="groupMemberFilter" ng-model="attributes.groupMemberFilter">
+      </div>
+    </div>
+    <div class="form-group">
+      <label for="forceLowercaseUsernames" class="control-label col-sm-4">{{'authentication.attributes.forceLowercaseUsernames' | translate}}</label>
+      <div class="col-sm-8">
+        <input type="checkbox" id="forceLowercaseUsernames" ng-model="attributes.forceLowercaseUsernames">
+      </div>
+    </div>
+    <div class="form-group">
+      <label for="referralHandling" class="control-label col-sm-4">{{'authentication.attributes.referralHandling.label' | translate}}</label>
+      <div class="col-sm-3">
+        <select class="form-control" id="referralHandling" ng-model="attributes.referralHandling" ng-options="item for item in attributes.referralHandlingOptions"></select>
+      </div>
+    </div>
+    <div class="form-group">
+      <label for="enablePagination" class="control-label col-sm-4">{{'authentication.attributes.enablePagination' | translate}}</label>
+      <div class="col-sm-8">
+        <input type="checkbox" id="enablePagination" ng-model="attributes.enablePagination">
+      </div>
+    </div>
+  </div>
+</div>

http://git-wip-us.apache.org/repos/asf/ambari/blob/40397378/ambari-admin/src/main/resources/ui/admin-web/app/views/authentication/main.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/authentication/main.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/authentication/main.html
index fddedb9..ce75dd7 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/views/authentication/main.html
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/authentication/main.html
@@ -90,6 +90,12 @@
         </div>
       </div>
       <div class="form-group">
+        <label for="anonymous-bind" class="control-label col-sm-4">{{'authentication.connectivity.anonymousBind' | translate}}</label>
+        <div class="col-sm-8">
+          <input type="checkbox" id="anonymous-bind" ng-model="connectivity.anonymousBind">
+        </div>
+      </div>
+      <div class="form-group">
         <label for="dn" class="control-label col-sm-4">{{'authentication.connectivity.dn' | translate}}</label>
         <div class="col-sm-8">
           <input type="text" class="form-control" id="dn" ng-model="connectivity.dn">
@@ -118,20 +124,28 @@
       </div>
       <hr>
 
-      <form class="form-horizontal" ng-submit="detectAttributes()">
+      <form class="form-horizontal" ng-submit="save()" id="attributes">
         <div class="form-group col-sm-12">{{'authentication.attributes.detection.label' | translate}}</div>
         <div class="form-group">
-          <label for="manual-detection" class="col-sm-12">
-            <input type="radio" id="manual-detection" name="detection" ng-model="attributes.detection" ng-disabled="isAttributeDetectionRunning" value="manual">
-            {{'authentication.attributes.detection.options.manual' | translate}}
+          <label for="active-directory" class="col-sm-12">
+            <input type="radio" id="active-directory" name="detection" ng-model="attributes.detection" ng-disabled="isAttributeDetectionRunning" value="ad">
+            {{'authentication.attributes.detection.options.ad' | translate}}
+          </label>
+          <label for="free-ipa" class="col-sm-12">
+            <input type="radio" id="free-ipa" name="detection" ng-model="attributes.detection" ng-disabled="isAttributeDetectionRunning" value="freeIPA">
+            {{'authentication.attributes.detection.options.freeIPA' | translate}}
           </label>
           <label for="auto-detection" class="col-sm-12">
             <input type="radio" id="auto-detection" name="detection" ng-model="attributes.detection" ng-disabled="isAttributeDetectionRunning" value="auto">
             {{'authentication.attributes.detection.options.auto' | translate}}
           </label>
+          <label for="custom-detection" class="col-sm-12">
+            <input type="radio" id="custom-detection" name="detection" ng-model="attributes.detection" ng-disabled="isAttributeDetectionRunning" value="custom">
+            {{'authentication.attributes.detection.options.custom' | translate}}
+          </label>
         </div>
 
-        <div ng-show="attributes.detection === 'auto'">
+        <div ng-show="attributes.detection === 'auto' || attributes.detection === 'ad' || attributes.detection === 'freeIPA'">
 
           <div class="form-group">
             <label for="user-search" class="control-label col-sm-4">{{'authentication.attributes.userSearch' | translate}}</label>
@@ -146,9 +160,10 @@
             </div>
           </div>
 
-          <div class="form-group">
+          <ng-include ng-if="attributes.detection === 'auto'" src="'views/authentication/advancedAttributesForm.html'"></ng-include>
+          <div class="form-group" ng-show="attributes.detection === 'auto'">
             <div class="col-sm-offset-4 col-sm-8">
-              <button class="btn btn-primary" ng-disabled="isAutoDetectFormInvalid || isRequestRunning">{{'authentication.attributes.controls.autoDetect' | translate}}</button>
+              <button class="btn btn-primary" ng-click="detectAttributes()" ng-disabled="isAutoDetectFormInvalid || isRequestRunning">{{'authentication.attributes.controls.autoDetect' | translate}}</button>
               <i class="test-ldap-icon fa ng-class: {'fa-spin fa-spinner': isAttributeDetectionRunning, 'fa-check-circle': isAttributeDetectionSuccessful, 'fa-times-circle': isAttributeDetectionComplete && !isAttributeDetectionSuccessful}" ng-show="isAttributeDetectionRunning || isAttributeDetectionComplete"></i>
             </div>
           </div>
@@ -156,11 +171,7 @@
 
         </div>
 
-      </form>
-
-      <div ng-show="attributes.detection === 'manual' || isAttributeDetectionComplete && isAttributeDetectionSuccessful">
-
-        <form id="attributes" class="form-horizontal" ng-submit="save()">
+        <div ng-show="attributes.detection === 'custom' || isAttributeDetectionComplete && isAttributeDetectionSuccessful">
 
           <div class="form-group">
             <label for="user-obj-class" class="control-label col-sm-4">{{'authentication.attributes.userObjClass' | translate}}</label>
@@ -198,7 +209,7 @@
               <input type="text" class="form-control" id="distinguished-name-attr" ng-model="attributes.distinguishedNameAttr">
             </div>
           </div>
-          <div ng-show="attributes.detection === 'manual'">
+          <div ng-show="attributes.detection === 'custom'">
             <div class="form-group">
               <label for="user-search-manual" class="control-label col-sm-4">{{'authentication.attributes.userSearch' | translate}}</label>
               <div class="col-sm-8">
@@ -212,57 +223,62 @@
               </div>
             </div>
           </div>
-          <div class="form-group col-sm-12">{{'authentication.attributes.test.description' | translate}}</div>
-          <div class="form-group">
-            <div class="col-sm-offset-4 col-sm-8">
-              <button type="submit" class="btn btn-primary" ng-click="showTestAttributesForm()" ng-disabled="isAttributesFormInvalid || isTestAttributesFormShown || isRequestRunning">{{'authentication.attributes.controls.testAttrs' | translate}}</button>
-            </div>
+        </div>
+        <ng-include ng-if="attributes.detection !== 'auto'" src="'views/authentication/advancedAttributesForm.html'"></ng-include>
+        <div class="form-group" ng-show="attributes.detection !== 'auto' || isAttributeDetectionComplete && isAttributeDetectionSuccessful">
+          <div class="col-sm-offset-4 col-sm-8">
+            <button type="submit" class="btn btn-primary" ng-click="showTestAttributesForm()"
+                    ng-disabled="(attributes.detection === 'custom' && isAttributesFormInvalid) ||
+                    (attributes.detection === 'auto' && (isAttributesFormInvalid || isAutoDetectFormInvalid)) ||
+                    ((attributes.detection === 'ad' || attributes.detection === 'freeIPA') && isAutoDetectFormInvalid) ||
+                    isTestAttributesFormShown || isRequestRunning"> {{'authentication.attributes.controls.testAttrs' | translate}}</button>
           </div>
-        </form>
+        </div>
+      </form>
 
-        <form class="form-horizontal" ng-show="isTestAttributesFormShown" ng-submit="testAttributes()">
-          <div class="form-group">
-            <label for="username" class="control-label col-sm-4">{{'authentication.attributes.test.username' | translate}}</label>
-            <div class="col-sm-8">
-              <input type="text" class="form-control" id="username" ng-model="attributes.username">
-            </div>
+      <form class="form-horizontal" ng-show="isTestAttributesFormShown" ng-submit="testAttributes()">
+        <div class="form-group">
+          <label for="username" class="control-label col-sm-4">{{'authentication.attributes.test.username' | translate}}</label>
+          <div class="col-sm-8">
+            <input type="text" class="form-control" id="username" ng-model="attributes.username">
           </div>
-          <div class="form-group">
-            <label for="password" class="control-label col-sm-4">{{'authentication.attributes.test.password' | translate}}</label>
-            <div class="col-sm-8">
-              <input type="password" class="form-control" id="password" ng-model="attributes.password">
-            </div>
+        </div>
+        <div class="form-group">
+          <label for="password" class="control-label col-sm-4">{{'authentication.attributes.test.password' | translate}}</label>
+          <div class="col-sm-8">
+            <input type="password" class="form-control" id="password" ng-model="attributes.password">
           </div>
-          <div class="form-group">
-            <div class="col-sm-offset-4 col-sm-8">
-              <button type="submit" class="btn btn-primary" ng-disabled="isTestAttributesFormInvalid || isRequestRunning">{{'authentication.controls.test' | translate}}</button>
-              <i class="test-ldap-icon fa ng-class: {'fa-spin fa-spinner': isTestAttributesRunning, 'fa-times-circle': isTestAttributesComplete && !isTestAttributesSuccessful}" ng-show="isTestAttributesRunning || isTestAttributesComplete"></i>
-            </div>
+        </div>
+        <div class="form-group">
+          <div class="col-sm-offset-4 col-sm-8">
+            <button type="submit" class="btn btn-primary" ng-disabled="isTestAttributesFormInvalid || isRequestRunning">
+              {{'authentication.controls.test' | translate}}
+            </button>
+            <i class="test-ldap-icon fa ng-class: {'fa-spin fa-spinner': isTestAttributesRunning, 'fa-times-circle': isTestAttributesComplete && !isTestAttributesSuccessful}" ng-show="isTestAttributesRunning || isTestAttributesComplete"></i>
           </div>
-        </form>
+        </div>
+      </form>
 
-        <div class="form-horizontal" ng-show="isTestAttributesSuccessful">
-          <div class="form-group">
+      <div class="form-horizontal" ng-show="isTestAttributesSuccessful">
+        <div class="form-group">
             <span class="control-label col-sm-4">
               {{'authentication.attributes.alerts.successfulAuth' | translate}}
             </span>
-            <div class="col-sm-1">
-              <i class="control-label test-ldap-icon fa fa-check-circle"></i>
-            </div>
-          </div>
-          <div class="form-group">
-            <label for="groups" class="control-label col-sm-4">{{'authentication.attributes.groupsList' | translate}}</label>
-            <div class="col-sm-6">
-              <select multiple class="form-control" id="groups" form="attributes" ng-model="attributes.groups" ng-options="item for item in attributes.availableGroups"></select>
-            </div>
+          <div class="col-sm-1">
+            <i class="control-label test-ldap-icon fa fa-check-circle"></i>
           </div>
         </div>
-
-        <div class="text-center form-group">
-          <button type="submit" form="attributes" class="btn btn-primary" ng-disabled="isAttributesFormInvalid || isRequestRunning">{{'common.controls.save' | translate}}</button>
-          <i class="test-ldap-icon fa fa-spin fa-spinner" ng-show="isSaving"></i>
+        <div class="form-group">
+          <label for="groups" class="control-label col-sm-4">{{'authentication.attributes.groupsList' | translate}}</label>
+          <div class="col-sm-6">
+            <select multiple class="form-control" id="groups" form="attributes"  ng-model="attributes.groups" ng-options="item for item in attributes.availableGroups"></select>
+          </div>
         </div>
+      </div>
 
+      <div class="text-center form-group">
+        <button type="submit" form="attributes" class="btn btn-primary" ng-disabled="isAttributesFormInvalid || isRequestRunning">{{'common.controls.save' | translate}}</button>
+        <i class="test-ldap-icon fa fa-spin fa-spinner" ng-show="isSaving"></i>
       </div>
 
     </div>