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>