keycloak-aplcache

client template ui

12/11/2015 8:56:45 PM

Changes

pom.xml 2(+1 -1)

Details

diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_ca.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_ca.properties
old mode 100644
new mode 100755
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties
old mode 100644
new mode 100755
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
index bfbcee9..826a3da 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
@@ -784,7 +784,7 @@ module.config([ '$routeProvider', function($routeProvider) {
             controller : 'AddBuiltinProtocolMapperCtrl'
         })
         .when('/realms/:realm/clients/:client/mappers/:id', {
-            templateUrl : resourceUrl + '/partials/protocol-mapper-detail.html',
+            templateUrl : resourceUrl + '/partials/client-protocol-mapper-detail.html',
             resolve : {
                 realm : function(RealmLoader) {
                     return RealmLoader();
@@ -803,7 +803,7 @@ module.config([ '$routeProvider', function($routeProvider) {
             controller : 'ClientProtocolMapperCtrl'
         })
         .when('/create/client/:realm/:client/mappers', {
-            templateUrl : resourceUrl + '/partials/protocol-mapper-detail.html',
+            templateUrl : resourceUrl + '/partials/client-protocol-mapper-detail.html',
             resolve : {
                 realm : function(RealmLoader) {
                     return RealmLoader();
@@ -817,6 +817,70 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'ClientProtocolMapperCreateCtrl'
         })
+        .when('/realms/:realm/client-templates/:template/mappers', {
+            templateUrl : resourceUrl + '/partials/client-template-mappers.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                template : function(ClientTemplateLoader) {
+                    return ClientTemplateLoader();
+                },
+                serverInfo : function(ServerInfoLoader) {
+                    return ServerInfoLoader();
+                }
+            },
+            controller : 'ClientTemplateProtocolMapperListCtrl'
+        })
+        .when('/realms/:realm/client-templates/:template/add-mappers', {
+            templateUrl : resourceUrl + '/partials/client-template-mappers-add.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                template : function(ClientTemplateLoader) {
+                    return ClientTemplateLoader();
+                },
+                serverInfo : function(ServerInfoLoader) {
+                    return ServerInfoLoader();
+                }
+            },
+            controller : 'ClientTemplateAddBuiltinProtocolMapperCtrl'
+        })
+        .when('/realms/:realm/client-templates/:template/mappers/:id', {
+            templateUrl : resourceUrl + '/partials/client-template-protocol-mapper-detail.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                template : function(ClientTemplateLoader) {
+                    return ClientTemplateLoader();
+                },
+                serverInfo : function(ServerInfoLoader) {
+                    return ServerInfoLoader();
+                },
+                mapper : function(ClientTemplateProtocolMapperLoader) {
+                    return ClientTemplateProtocolMapperLoader();
+                }
+
+            },
+            controller : 'ClientTemplateProtocolMapperCtrl'
+        })
+        .when('/create/client-template/:realm/:template/mappers', {
+            templateUrl : resourceUrl + '/partials/client-template-protocol-mapper-detail.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                serverInfo : function(ServerInfoLoader) {
+                    return ServerInfoLoader();
+                },
+                template : function(ClientTemplateLoader) {
+                    return ClientTemplateLoader();
+                }
+            },
+            controller : 'ClientTemplateProtocolMapperCreateCtrl'
+        })
         .when('/realms/:realm/clients/:client/sessions', {
             templateUrl : resourceUrl + '/partials/client-sessions.html',
             resolve : {
@@ -1063,6 +1127,9 @@ module.config([ '$routeProvider', function($routeProvider) {
                 realm : function(RealmLoader) {
                     return RealmLoader();
                 },
+                templates : function(ClientTemplateListLoader) {
+                    return ClientTemplateListLoader();
+                },
                 clients : function(ClientListLoader) {
                     return ClientListLoader();
                 },
@@ -1081,6 +1148,9 @@ module.config([ '$routeProvider', function($routeProvider) {
                 realm : function(RealmLoader) {
                     return RealmLoader();
                 },
+                templates : function(ClientTemplateListLoader) {
+                    return ClientTemplateListLoader();
+                },
                 clients : function(ClientListLoader) {
                     return ClientListLoader();
                 },
@@ -1093,6 +1163,42 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'ClientDetailCtrl'
         })
+        .when('/create/client-template/:realm', {
+            templateUrl : resourceUrl + '/partials/client-template-detail.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                templates : function(ClientTemplateListLoader) {
+                    return ClientTemplateListLoader();
+                },
+                template : function() {
+                    return {};
+                },
+                serverInfo : function(ServerInfoLoader) {
+                    return ServerInfoLoader();
+                }
+            },
+            controller : 'ClientTemplateDetailCtrl'
+        })
+        .when('/realms/:realm/client-templates/:template', {
+            templateUrl : resourceUrl + '/partials/client-template-detail.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                templates : function(ClientTemplateListLoader) {
+                    return ClientTemplateListLoader();
+                },
+                template : function(ClientTemplateLoader) {
+                    return ClientTemplateLoader();
+                },
+                serverInfo : function(ServerInfoLoader) {
+                    return ServerInfoLoader();
+                }
+            },
+            controller : 'ClientTemplateDetailCtrl'
+        })
         .when('/realms/:realm/clients', {
             templateUrl : resourceUrl + '/partials/client-list.html',
             resolve : {
@@ -1109,6 +1215,22 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'ClientListCtrl'
         })
+        .when('/realms/:realm/client-templates', {
+            templateUrl : resourceUrl + '/partials/client-template-list.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                templates : function(ClientTemplateListLoader) {
+                    return ClientTemplateListLoader();
+                },
+                serverInfo : function(ServerInfoLoader) {
+                    return ServerInfoLoader();
+                }
+
+            },
+            controller : 'ClientTemplateListCtrl'
+        })
         .when('/import/client/:realm', {
             templateUrl : resourceUrl + '/partials/client-import.html',
             resolve : {
@@ -2025,6 +2147,15 @@ module.directive('kcTabsClient', function () {
     }
 });
 
+module.directive('kcTabsClientTemplate', function () {
+    return {
+        scope: true,
+        restrict: 'E',
+        replace: true,
+        templateUrl: resourceUrl + '/templates/kc-tabs-client-template.html'
+    }
+});
+
 module.directive('kcNavigationUser', function () {
     return {
         scope: true,
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
index 4e81a43..420517d 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
@@ -726,7 +726,10 @@ module.controller('ClientInstallationCtrl', function($scope, realm, client, Clie
     }
 });
 
-module.controller('ClientDetailCtrl', function($scope, realm, client, $route, serverInfo, Client, ClientDescriptionConverter, $location, $modal, Dialog, Notifications) {
+module.controller('ClientDetailCtrl', function($scope, realm, client, templates, $route, serverInfo, Client, ClientDescriptionConverter, $location, $modal, Dialog, Notifications) {
+
+
+
     $scope.accessTypes = [
         "confidential",
         "public",
@@ -735,6 +738,12 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, $route, se
 
     $scope.protocols = Object.keys(serverInfo.providers['login-protocol'].providers).sort();
 
+    $scope.templates = [ {name:'NONE'}];
+    for (var i = 0; i < templates.length; i++) {
+        var template = templates[i];
+        $scope.templates.push(template);
+    }
+
     $scope.signatureAlgorithms = [
         "RSA_SHA1",
         "RSA_SHA256",
@@ -1444,22 +1453,32 @@ module.controller('ClientProtocolMapperListCtrl', function($scope, realm, client
 });
 
 module.controller('ClientProtocolMapperCtrl', function($scope, realm, serverInfo, client, mapper, ClientProtocolMapper, Notifications, Dialog, $location) {
+    /*
     $scope.realm = realm;
     $scope.client = client;
     $scope.create = false;
-    if (client.protocol == null) {
-        client.protocol = 'openid-connect';
-    }
     $scope.protocol = client.protocol;
     $scope.mapper = angular.copy(mapper);
     $scope.changed = false;
-    $scope.boolval = true;
-    $scope.boolvalId = 'boolval';
+    */
+
+    if (client.protocol == null) {
+        client.protocol = 'openid-connect';
+    }
+
+    $scope.model = {
+        realm: realm,
+        client: client,
+        create: false,
+        protocol: client.protocol,
+        mapper: angular.copy(mapper),
+        changed: false
+    }
 
     var protocolMappers = serverInfo.protocolMapperTypes[client.protocol];
     for (var i = 0; i < protocolMappers.length; i++) {
         if (protocolMappers[i].id == mapper.protocolMapper) {
-            $scope.mapperType = protocolMappers[i];
+            $scope.model.mapperType = protocolMappers[i];
         }
     }
     $scope.$watch(function() {
@@ -1468,9 +1487,9 @@ module.controller('ClientProtocolMapperCtrl', function($scope, realm, serverInfo
         $scope.path = $location.path().substring(1).split("/");
     });
 
-    $scope.$watch('mapper', function() {
-        if (!angular.equals($scope.mapper, mapper)) {
-            $scope.changed = true;
+    $scope.$watch('model.mapper', function() {
+        if (!angular.equals($scope.model.mapper, mapper)) {
+            $scope.model.changed = true;
         }
     }, true);
 
@@ -1479,17 +1498,17 @@ module.controller('ClientProtocolMapperCtrl', function($scope, realm, serverInfo
             realm : realm.realm,
             client: client.id,
             id : mapper.id
-        }, $scope.mapper, function() {
-            $scope.changed = false;
+        }, $scope.model.mapper, function() {
+            $scope.model.changed = false;
             mapper = angular.copy($scope.mapper);
-            $location.url("/realms/" + realm.realm + '/clients/' + client.id + "/mappers/" + mapper.id);
+            $location.url("/realms/" + realm.realm + '/clients/' + client.id + "/mappers/" + $scope.model.mapper.id);
             Notifications.success("Your changes have been saved.");
         });
     };
 
     $scope.reset = function() {
-        $scope.mapper = angular.copy(mapper);
-        $scope.changed = false;
+        $scope.model.mapper = angular.copy(mapper);
+        $scope.model.changed = false;
     };
 
     $scope.cancel = function() {
@@ -1499,7 +1518,7 @@ module.controller('ClientProtocolMapperCtrl', function($scope, realm, serverInfo
 
     $scope.remove = function() {
         Dialog.confirmDelete($scope.mapper.name, 'mapper', function() {
-            ClientProtocolMapper.remove({ realm: realm.realm, client: client.id, id : $scope.mapper.id }, function() {
+            ClientProtocolMapper.remove({ realm: realm.realm, client: client.id, id : $scope.model.mapper.id }, function() {
                 Notifications.success("The mapper has been deleted.");
                 $location.url("/realms/" + realm.realm + '/clients/' + client.id + "/mappers");
             });
@@ -1509,16 +1528,27 @@ module.controller('ClientProtocolMapperCtrl', function($scope, realm, serverInfo
 });
 
 module.controller('ClientProtocolMapperCreateCtrl', function($scope, realm, serverInfo, client, ClientProtocolMapper, Notifications, Dialog, $location) {
-    $scope.realm = realm;
-    $scope.client = client;
-    $scope.create = true;
     if (client.protocol == null) {
         client.protocol = 'openid-connect';
     }
     var protocol = client.protocol;
+    /*
+    $scope.realm = realm;
+    $scope.client = client;
+    $scope.create = true;
     $scope.protocol = protocol;
     $scope.mapper = { protocol :  client.protocol, config: {}};
     $scope.mapperTypes = serverInfo.protocolMapperTypes[protocol];
+    */
+    $scope.model = {
+        realm: realm,
+        client: client,
+        create: true,
+        protocol: client.protocol,
+        mapper: { protocol :  client.protocol, config: {}},
+        changed: false,
+        mapperTypes: serverInfo.protocolMapperTypes[protocol]
+    }
 
     $scope.$watch(function() {
         return $location.path();
@@ -1527,10 +1557,10 @@ module.controller('ClientProtocolMapperCreateCtrl', function($scope, realm, serv
     });
 
     $scope.save = function() {
-        $scope.mapper.protocolMapper = $scope.mapperType.id;
+        $scope.model.mapper.protocolMapper = $scope.model.mapperType.id;
         ClientProtocolMapper.save({
             realm : realm.realm, client: client.id
-        }, $scope.mapper, function(data, headers) {
+        }, $scope.model.mapper, function(data, headers) {
             var l = headers().location;
             var id = l.substring(l.lastIndexOf("/") + 1);
             $location.url("/realms/" + realm.realm + '/clients/' + client.id + "/mappers/" + id);
@@ -1546,5 +1576,335 @@ module.controller('ClientProtocolMapperCreateCtrl', function($scope, realm, serv
 
 });
 
+module.controller('ClientTemplateTabCtrl', function(Dialog, $scope, Current, Notifications, $location) {
+    $scope.removeClientTemplate = function() {
+        Dialog.confirmDelete($scope.template.name, 'client template', function() {
+            $scope.template.$remove({
+                realm : Current.realm.realm,
+                template : $scope.template.id
+            }, function() {
+                $location.url("/realms/" + Current.realm.realm + "/client-templates");
+                Notifications.success("The client template has been deleted.");
+            });
+        });
+    };
+});
+
+
+
+module.controller('ClientTemplateListCtrl', function($scope, realm, templates, ClientTemplate, serverInfo, $route, Dialog, Notifications) {
+    $scope.realm = realm;
+    $scope.templates = templates;
+
+    $scope.removeClientTemplate = function(template) {
+        Dialog.confirmDelete(template.name, 'client template', function() {
+            ClientTemplate.remove({
+                realm : realm.realm,
+                template : template.id
+            }, function() {
+                $route.reload();
+                Notifications.success("The client template been deleted.");
+            });
+        });
+    };
+});
+
+module.controller('ClientTemplateDetailCtrl', function($scope, realm, template, $route, serverInfo, ClientTemplate, $location, $modal, Dialog, Notifications) {
+    $scope.protocols = Object.keys(serverInfo.providers['login-protocol'].providers).sort();
+
+    $scope.realm = realm;
+    $scope.create = !template.name;
+
+    function updateProperties() {
+        if ($scope.template.protocol) {
+            $scope.protocol = $scope.protocols[$scope.protocols.indexOf($scope.template.protocol)];
+        } else {
+            $scope.protocol = $scope.protocols[0];
+        }
+    }
+
+    if (!$scope.create) {
+        $scope.template = angular.copy(template);
+        updateProperties();
+    } else {
+        $scope.template = {
+        };
+        $scope.protocol = $scope.protocols[0];
+    }
+
+
+    $scope.switchChange = function() {
+        $scope.changed = true;
+    }
+
+    $scope.changeProtocol = function() {
+        if ($scope.protocol == "openid-connect") {
+            $scope.template.protocol = "openid-connect";
+        } else if ($scope.protocol == "saml") {
+            $scope.template.protocol = "saml";
+        }
+    };
+
+    $scope.$watch(function() {
+        return $location.path();
+    }, function() {
+        $scope.path = $location.path().substring(1).split("/");
+    });
+
+    function isChanged() {
+        if (!angular.equals($scope.template, template)) {
+            return true;
+        }
+        return false;
+    }
+
+    $scope.$watch('template', function() {
+        $scope.changed = isChanged();
+    }, true);
+
+    $scope.save = function() {
+        $scope.template.protocol = $scope.protocol;
+
+        if ($scope.create) {
+            ClientTemplate.save({
+                realm: realm.realm,
+                template: ''
+            }, $scope.template, function (data, headers) {
+                $scope.changed = false;
+                var l = headers().location;
+                var id = l.substring(l.lastIndexOf("/") + 1);
+                $location.url("/realms/" + realm.realm + "/client-templates/" + id);
+                Notifications.success("The client template has been created.");
+            });
+        } else {
+            ClientTemplate.update({
+                realm : realm.realm,
+                template : template.id
+            }, $scope.template, function() {
+                $scope.changed = false;
+                template = angular.copy($scope.template);
+                $location.url("/realms/" + realm.realm + "/client-templates/" + template.id);
+                Notifications.success("Your changes have been saved to the client template.");
+            });
+        }
+    };
+
+    $scope.reset = function() {
+        $route.reload();
+    };
+
+    $scope.cancel = function() {
+        $location.url("/realms/" + realm.realm + "/client-templates");
+    };
+});
+
+module.controller('ClientTemplateProtocolMapperListCtrl', function($scope, realm, template, serverInfo,
+                                                           ClientTemplateProtocolMappersByProtocol, ClientTemplateProtocolMapper,
+                                                           $route, Dialog, Notifications) {
+    $scope.realm = realm;
+    $scope.template = template;
+    if (template.protocol == null) {
+        template.protocol = 'openid-connect';
+    }
+
+    var protocolMappers = serverInfo.protocolMapperTypes[template.protocol];
+    var mapperTypes = {};
+    for (var i = 0; i < protocolMappers.length; i++) {
+        mapperTypes[protocolMappers[i].id] = protocolMappers[i];
+    }
+    $scope.mapperTypes = mapperTypes;
+
+    $scope.removeMapper = function(mapper) {
+        console.debug(mapper);
+        Dialog.confirmDelete(mapper.name, 'mapper', function() {
+            ClientTemplateProtocolMapper.remove({ realm: realm.realm, template: template.id, id : mapper.id }, function() {
+                Notifications.success("The mapper has been deleted.");
+                $route.reload();
+            });
+        });
+    };
+
+    var updateMappers = function() {
+        $scope.mappers = ClientTemplateProtocolMappersByProtocol.query({realm : realm.realm, template : template.id, protocol : template.protocol});
+    };
+
+    updateMappers();
+});
+
+module.controller('ClientTemplateProtocolMapperCtrl', function($scope, realm, serverInfo, template, mapper, ClientTemplateProtocolMapper, Notifications, Dialog, $location) {
+
+    if (template.protocol == null) {
+        template.protocol = 'openid-connect';
+    }
+
+    $scope.model = {
+        realm: realm,
+        template: template,
+        create: false,
+        protocol: template.protocol,
+        mapper: angular.copy(mapper),
+        changed: false
+    }
+
+    var protocolMappers = serverInfo.protocolMapperTypes[template.protocol];
+    for (var i = 0; i < protocolMappers.length; i++) {
+        if (protocolMappers[i].id == mapper.protocolMapper) {
+            $scope.model.mapperType = protocolMappers[i];
+        }
+    }
+    $scope.$watch(function() {
+        return $location.path();
+    }, function() {
+        $scope.path = $location.path().substring(1).split("/");
+    });
+
+    $scope.$watch('model.mapper', function() {
+        if (!angular.equals($scope.model.mapper, mapper)) {
+            $scope.model.changed = true;
+        }
+    }, true);
+
+    $scope.save = function() {
+        ClientTemplateProtocolMapper.update({
+            realm : realm.realm,
+            template: template.id,
+            id : mapper.id
+        }, $scope.model.mapper, function() {
+            $scope.model.changed = false;
+            mapper = angular.copy($scope.mapper);
+            $location.url("/realms/" + realm.realm + '/client-templates/' + template.id + "/mappers/" + $scope.model.mapper.id);
+            Notifications.success("Your changes have been saved.");
+        });
+    };
+
+    $scope.reset = function() {
+        $scope.model.mapper = angular.copy(mapper);
+        $scope.model.changed = false;
+    };
+
+    $scope.cancel = function() {
+        //$location.url("/realms");
+        window.history.back();
+    };
+
+    $scope.remove = function() {
+        Dialog.confirmDelete($scope.mapper.name, 'mapper', function() {
+            ClientTemplateProtocolMapper.remove({ realm: realm.realm, template: template.id, id : $scope.model.mapper.id }, function() {
+                Notifications.success("The mapper has been deleted.");
+                $location.url("/realms/" + realm.realm + '/client-templates/' + template.id + "/mappers");
+            });
+        });
+    };
+
+});
+
+module.controller('ClientTemplateProtocolMapperCreateCtrl', function($scope, realm, serverInfo, template, ClientTemplateProtocolMapper, Notifications, Dialog, $location) {
+    if (template.protocol == null) {
+        template.protocol = 'openid-connect';
+    }
+    var protocol = template.protocol;
+    $scope.model = {
+        realm: realm,
+        template: template,
+        create: true,
+        protocol: template.protocol,
+        mapper: { protocol :  template.protocol, config: {}},
+        changed: false,
+        mapperTypes: serverInfo.protocolMapperTypes[protocol]
+    }
+
+    $scope.$watch(function() {
+        return $location.path();
+    }, function() {
+        $scope.path = $location.path().substring(1).split("/");
+    });
+
+    $scope.save = function() {
+        $scope.model.mapper.protocolMapper = $scope.model.mapperType.id;
+        ClientTemplateProtocolMapper.save({
+            realm : realm.realm, template: template.id
+        }, $scope.model.mapper, function(data, headers) {
+            var l = headers().location;
+            var id = l.substring(l.lastIndexOf("/") + 1);
+            $location.url("/realms/" + realm.realm + '/client-templates/' + template.id + "/mappers/" + id);
+            Notifications.success("Mapper has been created.");
+        });
+    };
+
+    $scope.cancel = function() {
+        //$location.url("/realms");
+        window.history.back();
+    };
+
+
+});
+
+module.controller('ClientTemplateAddBuiltinProtocolMapperCtrl', function($scope, realm, template, serverInfo,
+                                                           ClientTemplateProtocolMappersByProtocol,
+                                                           $http, $location, Dialog, Notifications) {
+    $scope.realm = realm;
+    $scope.template = template;
+    if (template.protocol == null) {
+        template.protocol = 'openid-connect';
+    }
+
+    var protocolMappers = serverInfo.protocolMapperTypes[template.protocol];
+    var mapperTypes = {};
+    for (var i = 0; i < protocolMappers.length; i++) {
+        mapperTypes[protocolMappers[i].id] = protocolMappers[i];
+    }
+    $scope.mapperTypes = mapperTypes;
+
+
+
+
+    var updateMappers = function() {
+        var clientMappers = ClientTemplateProtocolMappersByProtocol.query({realm : realm.realm, template : template.id, protocol : template.protocol}, function() {
+            var builtinMappers = serverInfo.builtinProtocolMappers[template.protocol];
+            for (var i = 0; i < clientMappers.length; i++) {
+                for (var j = 0; j < builtinMappers.length; j++) {
+                    if (builtinMappers[j].name == clientMappers[i].name
+                        && builtinMappers[j].protocolMapper == clientMappers[i].protocolMapper) {
+                        builtinMappers.splice(j, 1);
+                        break;
+                    }
+                }
+            }
+            $scope.mappers = builtinMappers;
+            for (var i = 0; i < $scope.mappers.length; i++) {
+                $scope.mappers[i].isChecked = false;
+            }
+
+
+        });
+    };
+
+    updateMappers();
+
+    $scope.add = function() {
+        var toAdd = [];
+        for (var i = 0; i < $scope.mappers.length; i++) {
+            if ($scope.mappers[i].isChecked) {
+                delete $scope.mappers[i].isChecked;
+                toAdd.push($scope.mappers[i]);
+            }
+        }
+        $http.post(authUrl + '/admin/realms/' + realm.realm + '/client-templates/' + template.id + '/protocol-mappers/add-models',
+            toAdd).success(function() {
+                Notifications.success("Mappers added");
+                $location.url('/realms/' + realm.realm + '/client-templates/' + template.id +  '/mappers');
+            }).error(function() {
+                Notifications.error("Error adding mappers");
+                $location.url('/realms/' + realm.realm + '/client-templates/' + template.id +  '/mappers');
+            });
+    };
+
+});
+
+
+
+
+
+
 
 
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js
index 61b608f..94854f3 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js
@@ -107,6 +107,16 @@ module.factory('ClientProtocolMapperLoader', function(Loader, ClientProtocolMapp
     });
 });
 
+module.factory('ClientTemplateProtocolMapperLoader', function(Loader, ClientTemplateProtocolMapper, $route, $q) {
+    return Loader.get(ClientTemplateProtocolMapper, function() {
+        return {
+            realm : $route.current.params.realm,
+            template : $route.current.params.template,
+            id: $route.current.params.id
+        }
+    });
+});
+
 module.factory('UserLoader', function(Loader, User, $route, $q) {
     return Loader.get(User, function() {
         return {
@@ -311,6 +321,23 @@ module.factory('ClientListLoader', function(Loader, Client, $route, $q) {
     });
 });
 
+module.factory('ClientTemplateLoader', function(Loader, ClientTemplate, $route, $q) {
+    return Loader.get(ClientTemplate, function() {
+        return {
+            realm : $route.current.params.realm,
+            template : $route.current.params.template
+        }
+    });
+});
+
+module.factory('ClientTemplateListLoader', function(Loader, ClientTemplate, $route, $q) {
+    return Loader.query(ClientTemplate, function() {
+        return {
+            realm : $route.current.params.realm
+        }
+    });
+});
+
 module.factory('ClientServiceAccountUserLoader', function(Loader, ClientServiceAccountUser, $route, $q) {
     return Loader.get(ClientServiceAccountUser, function() {
         return {
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
index 7b94680..7a47a83 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
@@ -309,6 +309,18 @@ module.factory('ClientProtocolMapper', function($resource) {
     });
 });
 
+module.factory('ClientTemplateProtocolMapper', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/client-templates/:template/protocol-mappers/models/:id', {
+        realm : '@realm',
+        template: '@template',
+        id : "@id"
+    }, {
+        update : {
+            method : 'PUT'
+        }
+    });
+});
+
 module.factory('User', function($resource) {
     return $resource(authUrl + '/admin/realms/:realm/users/:userId', {
         realm : '@realm',
@@ -828,6 +840,14 @@ module.factory('ClientProtocolMappersByProtocol', function($resource) {
     });
 });
 
+module.factory('ClientTemplateProtocolMappersByProtocol', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/client-templates/:template/protocol-mappers/protocol/:protocol', {
+        realm : '@realm',
+        template : "@template",
+        protocol : "@protocol"
+    });
+});
+
 module.factory('ClientSessionStats', function($resource) {
     return $resource(authUrl + '/admin/realms/:realm/clients/:client/session-stats', {
         realm : '@realm',
@@ -956,6 +976,18 @@ module.factory('Client', function($resource) {
     });
 });
 
+module.factory('ClientTemplate', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/client-templates/:template', {
+        realm : '@realm',
+        template : '@template'
+    },  {
+        update : {
+            method : 'PUT'
+        }
+    });
+});
+
+
 module.factory('ClientDescriptionConverter', function($resource) {
     return $resource(authUrl + '/admin/realms/:realm/client-description-converter', {
         realm : '@realm'
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
index 5b81a01..6a2bd48 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
@@ -72,6 +72,18 @@
                 </div>
                 <kc-tooltip>{{:: 'client-protocol.tooltip' | translate}}</kc-tooltip>
             </div>
+            <div class="form-group">
+                <label class="col-md-2 control-label" for="protocol">Client Template</label>
+                <div class="col-sm-6">
+                    <div>
+                        <select class="form-control" id="template"
+                                ng-model="client.clientTemplate"
+                                ng-options="template.name as template.name for template in templates">
+                        </select>
+                    </div>
+                </div>
+                <kc-tooltip>Client template this client inherits configuration from</kc-tooltip>
+            </div>
             <div class="form-group" data-ng-show="protocol == 'openid-connect'">
                 <label class="col-md-2 control-label" for="accessType">{{:: 'access-type' | translate}}</label>
                 <div class="col-sm-6">
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-protocol-mapper-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-protocol-mapper-detail.html
new file mode 100755
index 0000000..b2af24a
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-protocol-mapper-detail.html
@@ -0,0 +1,13 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+
+    <ol class="breadcrumb">
+        <li><a href="#/realms/{{model.realm.realm}}/clients">{{:: 'clients' | translate}}</a></li>
+        <li><a href="#/realms/{{model.realm.realm}}/clients/{{model.client.id}}">{{model.client.clientId}}</a></li>
+        <li><a href="#/realms/{{model.realm.realm}}/clients/{{model.client.id}}/mappers">{{:: 'mappers' | translate}}</a></li>
+        <li class="active" data-ng-show="model.create">{{:: 'create-protocol-mappers' | translate}}</li>
+        <li class="active" data-ng-hide="model.create">{{model.mapper.name}}</li>
+    </ol>
+    <div ng-include="resourceUrl + '/partials/protocol-mapper-detail.html'"/>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-template-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-template-detail.html
new file mode 100755
index 0000000..5b286a9
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-template-detail.html
@@ -0,0 +1,55 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+
+    <ol class="breadcrumb">
+        <li><a href="#/realms/{{realm.realm}}/client-templates">Client Templates</a></li>
+        <li data-ng-show="create">Add Client Template</li>
+        <li data-ng-hide="create">{{template.name}}</li>
+    </ol>
+
+    <kc-tabs-client-template></kc-tabs-client-template>
+
+    <form class="form-horizontal" name="clientForm" novalidate kc-read-only="!access.manageClients">
+        <fieldset class="border-top">
+            <div class="form-group">
+                <label class="col-md-2 control-label" for="name">Name </label>
+                <div class="col-sm-6">
+                    <input class="form-control" type="text" id="name" name="name" data-ng-model="template.name" autofocus>
+                </div>
+                <kc-tooltip>Name of the client template.  Must be unique in the realm</kc-tooltip>
+            </div>
+            <div class="form-group">
+                <label class="col-md-2 control-label" for="description">Description </label>
+                <div class="col-sm-6">
+                    <input class="form-control" type="text" id="description" name="description" data-ng-model="template.description">
+                </div>
+                <kc-tooltip>Description of the client template</kc-tooltip>
+            </div>
+            <div class="form-group">
+                <label class="col-md-2 control-label" for="protocol">Protocol</label>
+                <div class="col-sm-6">
+                    <div>
+                        <select class="form-control" id="protocol"
+                                ng-change="changeProtocol()"
+                                ng-model="protocol"
+                                ng-options="aProtocol for aProtocol in protocols">
+                        </select>
+                    </div>
+                </div>
+                <kc-tooltip>Which SSO protocol configuration is being supplied by this client template</kc-tooltip>
+            </div>
+         </fieldset>
+
+        <div class="form-group">
+            <div class="col-md-10 col-md-offset-2" data-ng-show="create && access.manageClients">
+                <button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
+                <button kc-cancel data-ng-click="cancel()">{{:: 'cancel' | translate}}</button>
+            </div>
+            <div class="col-md-10 col-md-offset-2" data-ng-show="!create && access.manageClients">
+                <button kc-save  data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
+                <button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
+            </div>
+        </div>
+    </form>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-template-list.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-template-list.html
new file mode 100755
index 0000000..81d6d6f
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-template-list.html
@@ -0,0 +1,50 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+    <h1>
+        <span>Client Templates</span>
+        <kc-tooltip>Client templates allow you to define common configuration that is shared between multiple clients</kc-tooltip>
+    </h1>
+
+    <table class="table table-striped table-bordered">
+        <thead>
+        <tr>
+            <th class="kc-table-actions" colspan="5">
+                <div class="form-inline">
+                    <div class="form-group">
+                        <div class="input-group">
+                            <input type="text" placeholder="{{:: 'search.placeholder' | translate}}" data-ng-model="search.name" class="form-control search" onkeyup="if(event.keyCode == 13){$(this).next('I').click();}">
+                            <div class="input-group-addon">
+                                <i class="fa fa-search" type="submit"></i>
+                            </div>
+                        </div>
+                    </div>
+
+                    <div class="pull-right" data-ng-show="access.manageClients">
+                        <a id="createClient" class="btn btn-default" href="#/create/client-template/{{realm.realm}}">{{:: 'create' | translate}}</a>
+                    </div>
+                </div>
+            </th>
+        </tr>
+        <tr data-ng-hide="clients.length == 0">
+            <th>{{:: 'name' | translate}}</th>
+            <th colspan="2">{{:: 'actions' | translate}}</th>
+        </tr>
+        </thead>
+        <tbody>
+        <tr ng-repeat="template in templates | filter:search | orderBy:'name'">
+            <td><a href="#/realms/{{realm.realm}}/client-templates/{{template.id}}">{{template.name}}</a></td>
+            <td class="kc-action-cell">
+                <button class="btn btn-default btn-block btn-sm" kc-open="/realms/{{realm.realm}}/client-templates/{{template.id}}">{{:: 'edit' | translate}}</button>
+            </td>
+            <td class="kc-action-cell">
+                <button class="btn btn-default btn-block btn-sm" data-ng-click="removeClientTemplate(template)">{{:: 'delete' | translate}}</button>
+            </td>
+        </tr>
+        <tr data-ng-show="(clients | filter:search).length == 0">
+            <td class="text-muted" colspan="3" data-ng-show="search.name">{{:: 'no-results' | translate}}</td>
+            <td class="text-muted" colspan="3" data-ng-hide="search.name">{{:: 'no-clients-available' | translate}}</td>
+        </tr>
+        </tbody>
+    </table>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-template-mappers.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-template-mappers.html
new file mode 100755
index 0000000..7b767b2
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-template-mappers.html
@@ -0,0 +1,57 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+
+    <ol class="breadcrumb">
+        <li><a href="#/realms/{{realm.realm}}/client-templates">Client Templates</a></li>
+        <li>{{template.name}}</li>
+    </ol>
+
+    <kc-tabs-client-template></kc-tabs-client-template>
+
+    <table class="table table-striped table-bordered">
+        <thead>
+        <tr>
+            <th class="kc-table-actions" colspan="6">
+                <div class="form-inline">
+                    <div class="form-group">
+                        <div class="input-group">
+                            <input type="text" placeholder="{{:: 'search.placeholder' | translate}}" data-ng-model="search.name" class="form-control search" onkeyup="if(event.keyCode == 13){$(this).next('I').click();}">
+                            <div class="input-group-addon">
+                                <i class="fa fa-search" type="submit"></i>
+                            </div>
+                        </div>
+                    </div>
+
+                    <div class="pull-right" data-ng-show="access.manageClients">
+                        <a class="btn btn-default" href="#/create/client-template/{{realm.realm}}/{{template.id}}/mappers">{{:: 'create' | translate}}</a>
+                        <a class="btn btn-default" href="#/realms/{{realm.realm}}/client-templates/{{template.id}}/add-mappers">{{:: 'add-builtin' | translate}}</a>
+                    </div>
+                </div>
+            </th>
+        </tr>
+        <tr data-ng-hide="mappers.length == 0">
+            <th>{{:: 'name' | translate}}</th>
+            <th>{{:: 'category' | translate}}</th>
+            <th>{{:: 'type' | translate}}</th>
+            <th colspan="2">{{:: 'actions' | translate}}</th>
+        </tr>
+        </thead>
+        <tbody>
+        <tr ng-repeat="mapper in mappers | filter:search">
+            <td><a href="#/realms/{{realm.realm}}/client-templates/{{template.id}}/mappers/{{mapper.id}}">{{mapper.name}}</a></td>
+            <td>{{mapperTypes[mapper.protocolMapper].category}}</td>
+            <td>{{mapperTypes[mapper.protocolMapper].name}}</td>
+            <td class="kc-action-cell">
+                <button class="btn btn-default btn-block btn-sm" kc-open="/realms/{{realm.realm}}/client-templates/{{template.id}}/mappers/{{mapper.id}}">{{:: 'edit' | translate}}</button>
+            </td>
+            <td class="kc-action-cell">
+                <button class="btn btn-default btn-block btn-sm" data-ng-click="removeMapper(mapper)">{{:: 'delete' | translate}}</button>
+            </td>
+        </tr>
+        <tr data-ng-show="mappers.length == 0">
+            <td>{{:: 'no-mappers-available' | translate}}</td>
+        </tr>
+        </tbody>
+    </table>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-template-mappers-add.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-template-mappers-add.html
new file mode 100755
index 0000000..cb09fbd
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-template-mappers-add.html
@@ -0,0 +1,53 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+
+    <ol class="breadcrumb">
+        <li><a href="#/realms/{{realm.realm}}/client-templates">Client Templates</a></li>
+        <li><a href="#/realms/{{realm.realm}}/client-templates/{{template.id}}">{{template.name}}</a></li>
+        <li><a href="#/realms/{{realm.realm}}/client-templates/{{template.id}}/mappers">{{:: 'mappers' | translate}}</a></li>
+        <li class="active">{{:: 'add-builtin-protocol-mappers' | translate}}</li>
+    </ol>
+
+    <h1>{{:: 'add-builtin-protocol-mapper' | translate}}</h1>
+
+    <table class="table table-striped table-bordered">
+        <thead>
+        <tr>
+            <th class="kc-table-actions" colspan="4">
+                <div class="form-inline">
+                    <div class="form-group">
+                        <div class="input-group">
+                            <input type="text" placeholder="{{:: 'search.placeholder' | translate}}" data-ng-model="search.name" class="form-control search" onkeyup="if(event.keyCode == 13){$(this).next('I').click();}">
+                            <div class="input-group-addon">
+                                <i class="fa fa-search" type="submit"></i>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </th>
+        </tr>
+        <tr data-ng-hide="mappers.length == 0">
+            <th>{{:: 'name' | translate}}</th>
+            <th>{{:: 'category' | translate}}</th>
+            <th>{{:: 'type' | translate}}</th>
+            <th>{{:: 'add' | translate}}</th>
+        </tr>
+        </thead>
+        <tbody>
+        <tr ng-repeat="mapper in mappers | filter:search">
+            <td>{{mapper.name}}</td>
+            <td>{{mapperTypes[mapper.protocolMapper].category}}</td>
+            <td>{{mapperTypes[mapper.protocolMapper].name}}</td>
+            <td><input type="checkbox" ng-model="mapper.isChecked" id="{{mapper.protocolMapper}}"></td>
+        </tr>
+        <tr data-ng-show="mappers.length == 0">
+            <td>{{:: 'no-mappers-available' | translate}}</td>
+        </tr>
+        </tbody>
+    </table>
+
+    <div data-ng-show="access.manageRealm">
+        <button class="btn btn-primary" data-ng-click="add()">{{:: 'add-selected' | translate}}</button>
+    </div>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-template-protocol-mapper-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-template-protocol-mapper-detail.html
new file mode 100755
index 0000000..78ded5a
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-template-protocol-mapper-detail.html
@@ -0,0 +1,13 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+
+    <ol class="breadcrumb">
+        <li><a href="#/realms/{{model.realm.realm}}/client-templates">Client Templates</a></li>
+        <li><a href="#/realms/{{model.realm.realm}}/client-templates/{{model.template.id}}">{{model.template.name}}</a></li>
+        <li><a href="#/realms/{{model.realm.realm}}/client-templates/{{model.template.id}}/mappers">{{:: 'mappers' | translate}}</a></li>
+        <li class="active" data-ng-show="model.create">{{:: 'create-protocol-mappers' | translate}}</li>
+        <li class="active" data-ng-hide="model.create">{{model.mapper.name}}</li>
+    </ol>
+    <div ng-include="resourceUrl + '/partials/protocol-mapper-detail.html'"/>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/protocol-mapper-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/protocol-mapper-detail.html
index afc9a5f..13855ad 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/protocol-mapper-detail.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/protocol-mapper-detail.html
@@ -1,16 +1,6 @@
-<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
-
-    <ol class="breadcrumb">
-        <li><a href="#/realms/{{realm.realm}}/clients">{{:: 'clients' | translate}}</a></li>
-        <li><a href="#/realms/{{realm.realm}}/clients/{{client.id}}">{{client.clientId}}</a></li>
-        <li><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/mappers">{{:: 'mappers' | translate}}</a></li>
-        <li class="active" data-ng-show="create">{{:: 'create-protocol-mappers' | translate}}</li>
-        <li class="active" data-ng-hide="create">{{mapper.name}}</li>
-    </ol>
-
-    <h1 data-ng-show="create">{{:: 'create-protocol-mapper' | translate}}</h1>
-    <h1 data-ng-hide="create">{{mapper.name|capitalize}}<i class="pficon pficon-delete clickable" data-ng-show="!create && access.manageRealm" 
-    	data-ng-hide="changed" data-ng-click="remove()"></i></h1>
+    <h1 data-ng-show="model.create">{{:: 'create-protocol-mapper' | translate}}</h1>
+    <h1 data-ng-hide="model.create">{{model.mapper.name|capitalize}}<i class="pficon pficon-delete clickable" data-ng-show="!model.create && access.manageRealm"
+    	data-ng-hide="model.changed" data-ng-click="remove()"></i></h1>
 
     <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
 
@@ -18,75 +8,72 @@
             <div class="form-group clearfix">
                 <label class="col-md-2 control-label" for="protocol">{{:: 'protocol' | translate}}</label>
                 <div class="col-md-6">
-                    <input class="form-control" id="protocol" type="text" ng-model="protocol" readonly>
+                    <input class="form-control" id="protocol" type="text" ng-model="model.protocol" readonly>
                 </div>
                 <kc-tooltip>{{:: 'protocol.tooltip' | translate}}</kc-tooltip>
             </div>
-            <div class="form-group clearfix" data-ng-show="!create">
+            <div class="form-group clearfix" data-ng-show="!model.create">
                 <label class="col-md-2 control-label" for="mapperId">{{:: 'id' | translate}} </label>
                 <div class="col-md-6">
-                    <input class="form-control" id="mapperId" type="text" ng-model="mapper.id" readonly>
+                    <input class="form-control" id="mapperId" type="text" ng-model="model.mapper.id" readonly>
                 </div>
             </div>
             <div class="form-group clearfix">
                 <label class="col-md-2 control-label" for="name">{{:: 'name' | translate}}</label>
                 <div class="col-md-6">
-                    <input class="form-control" id="name" type="text" ng-model="mapper.name" data-ng-readonly="!create" required>
+                    <input class="form-control" id="name" type="text" ng-model="model.mapper.name" data-ng-readonly="!model.create" required>
                 </div>
                 <kc-tooltip>{{:: 'mapper.name.tooltip' | translate}}</kc-tooltip>
             </div>
             <div class="form-group">
                 <label for="consentRequired" class="col-sm-2 control-label">{{:: 'consent-required' | translate}}</label>
                 <div class="col-md-6">
-                    <input ng-model="mapper.consentRequired" name="consentRequired" id="consentRequired" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
+                    <input ng-model="model.mapper.consentRequired" name="consentRequired" id="consentRequired" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
                 </div>
                 <kc-tooltip>{{:: 'mapper.consent-required.tooltip' | translate}}</kc-tooltip>
             </div>
-            <div class="form-group" data-ng-show="mapper.consentRequired">
+            <div class="form-group" data-ng-show="model.mapper.consentRequired">
                 <label class="col-md-2 control-label" for="consentText">{{:: 'consent-text' | translate}} </label>
 
                 <div class="col-md-6">
-                    <textarea class="form-control" rows="5" cols="50" id="consentText" name="consentText" data-ng-model="mapper.consentText"></textarea>
+                    <textarea class="form-control" rows="5" cols="50" id="consentText" name="consentText" data-ng-model="model.mapper.consentText"></textarea>
                 </div>
                 <kc-tooltip>{{:: 'consent-text.tooltip' | translate}}</kc-tooltip>
             </div>
-            <div class="form-group" data-ng-show="create">
+            <div class="form-group" data-ng-show="model.create">
                 <label class="col-md-2 control-label" for="mapperTypeCreate">{{:: 'mapper-type' | translate}}</label>
                 <div class="col-sm-6">
                     <div>
                         <select class="form-control" id="mapperTypeCreate"
-                                ng-model="mapperType"
-                                ng-options="mapperType.name for mapperType in mapperTypes"
+                                ng-model="model.mapperType"
+                                ng-options="mapperType.name for mapperType in model.mapperTypes"
                                 required>
                         </select>
                     </div>
                 </div>
-                <kc-tooltip>{{mapperType.helpText}}</kc-tooltip>
+                <kc-tooltip>{{model.mapperType.helpText}}</kc-tooltip>
             </div>
-            <div class="form-group clearfix" data-ng-hide="create">
+            <div class="form-group clearfix" data-ng-hide="model.create">
                 <label class="col-md-2 control-label" for="mapperType">{{:: 'mapper-type' | translate}}</label>
                 <div class="col-md-6">
-                    <input class="form-control" id="mapperType" type="text" ng-model="mapperType.name" data-ng-readonly="true">
+                    <input class="form-control" id="mapperType" type="text" ng-model="model.mapperType.name" data-ng-readonly="true">
                 </div>
-                <kc-tooltip>{{mapperType.helpText}}</kc-tooltip>
+                <kc-tooltip>{{model.mapperType.helpText}}</kc-tooltip>
             </div>
-            <kc-provider-config config="mapper.config" properties="mapperType.properties" realm="realm"></kc-provider-config>
+            <kc-provider-config config="model.mapper.config" properties="model.mapperType.properties" realm="model.realm"></kc-provider-config>
         </fieldset>
 
         <div class="form-group">
-            <div class="col-md-10 col-md-offset-2" data-ng-show="create && access.manageRealm">
+            <div class="col-md-10 col-md-offset-2" data-ng-show="model.create && access.manageRealm">
                 <button kc-save>{{:: 'save' | translate}}</button>
                 <button kc-cancel data-ng-click="cancel()">{{:: 'cancel' | translate}}</button>
             </div>
         </div>
 
         <div class="form-group">
-            <div class="col-md-10 col-md-offset-2" data-ng-show="!create && access.manageRealm">
-                <button kc-save  data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
-                <button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
+            <div class="col-md-10 col-md-offset-2" data-ng-show="!model.create && access.manageRealm">
+                <button kc-save  data-ng-disabled="!model.changed">{{:: 'save' | translate}}</button>
+                <button kc-reset data-ng-disabled="!model.changed">{{:: 'cancel' | translate}}</button>
             </div>
         </div>
     </form>
-</div>
-
-<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
index 8da0e56..5904fd7 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
@@ -29,7 +29,8 @@
     || path[2] == 'keys-settings' || path[2] == 'smtp-settings' || path[2] == 'ldap-settings' || path[2] == 'auth-settings') && path[3] != 'clients') && 'active'">
                 <a href="#/realms/{{realm.realm}}"><span class="pficon pficon-settings"></span> Realm Settings</a>
             </li>
-            <li data-ng-show="access.viewClients" data-ng-class="(path[2] == 'clients' || path[1] == 'client' || path[3] == 'clients') && 'active'"><a href="#/realms/{{realm.realm}}/clients"><i class="fa fa-cubes"></i> Clients</a></li>
+            <li data-ng-show="access.viewClients" data-ng-class="(path[2] == 'clients' || path[1] == 'client' || path[3] == 'clients') && 'active'"><a href="#/realms/{{realm.realm}}/clients"><i class="fa fa-cube"></i> Clients</a></li>
+            <li data-ng-show="access.viewClients" data-ng-class="(path[2] == 'client-templates' || path[1] == 'client-template' || path[3] == 'client-templates') && 'active'"><a href="#/realms/{{realm.realm}}/client-templates"><i class="fa fa-cubes"></i> Client Templates</a></li>
             <li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'roles' || path[2] == 'default-roles' || (path[1] == 'role' && path[3] != 'clients')) && 'active'"><a href="#/realms/{{realm.realm}}/roles"><i class="fa fa-tasks"></i> Roles</a></li>
             <li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'identity-provider-settings' || path[2] == 'identity-provider-mappers') && 'active'"><a href="#/realms/{{realm.realm}}/identity-provider-settings"><i class="fa fa-exchange"></i> Identity Providers</a></li>
             <li data-ng-show="access.viewRealm" data-ng-class="(path[1] == 'user-federation' || path[2] == 'user-federation') && 'active'"><a href="#/realms/{{realm.realm}}/user-federation"><i class="fa fa-database"></i> User Federation</a></li>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client-template.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client-template.html
new file mode 100755
index 0000000..728e091
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client-template.html
@@ -0,0 +1,16 @@
+<div data-ng-controller="ClientTemplateTabCtrl">
+
+    <h1 data-ng-show="create">Add Client Template</h1>
+    <h1 data-ng-hide="create">
+        {{template.name|capitalize}}
+        <i id="removeClientTemplate" class="pficon pficon-delete clickable" data-ng-show="access.manageClients" data-ng-click="removeClientTemplate()"></i>
+    </h1>
+
+    <ul class="nav nav-tabs nav-tabs-pf"  data-ng-hide="create && !path[4]">
+        <li ng-class="{active: !path[4]}"><a href="#/realms/{{realm.realm}}/client-templates/{{template.id}}">{{:: 'settings' | translate}}</a></li>
+        <li ng-class="{active: path[4] == 'mappers'}">
+            <a href="#/realms/{{realm.realm}}/client-templates/{{template.id}}/mappers">{{:: 'mappers' | translate}}</a>
+            <kc-tooltip>{{:: 'mappers.tooltip' | translate}}</kc-tooltip>
+        </li>
+    </ul>
+</div>
\ No newline at end of file

pom.xml 2(+1 -1)

diff --git a/pom.xml b/pom.xml
index c7b2c69..b074d3f 100755
--- a/pom.xml
+++ b/pom.xml
@@ -1393,7 +1393,7 @@
                     <artifactId>maven-surefire-plugin</artifactId>
                     <configuration>
                         <forkMode>once</forkMode>
-                        <argLine>-Xms512m -Xmx1024m -XX:MaxPermSize=512m</argLine>
+                        <argLine>-Xms512m -Xmx2048m -XX:MaxPermSize=1024m</argLine>
                     </configuration>
                 </plugin>
                 <plugin>