http://git-wip-us.apache.org/repos/asf/syncope/blob/19d9e93e/client/enduser/src/main/resources/META-INF/resources/app/css/editUser.css
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/css/editUser.css 
b/client/enduser/src/main/resources/META-INF/resources/app/css/editUser.css
index ec5baca..41ba63c 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/css/editUser.css
+++ b/client/enduser/src/main/resources/META-INF/resources/app/css/editUser.css
@@ -17,6 +17,8 @@ specific language governing permissions and limitations
 under the License.
 */
 
+/* Default style
+============================================================================= 
*/
 * {
   box-sizing: border-box;
 }
@@ -27,6 +29,7 @@ under the License.
   margin-top:-4px;
   margin: 0 auto;
   text-align: center;
+  background-color: #F7F7F7;
 }
 
 #form-container .breadcrumb-header{
@@ -35,20 +38,23 @@ under the License.
   padding:10px; 
 }
 
-#form-container .page-header   { background: -moz-linear-gradient(top, #a9db80 
0%, #96c56f 100%); /* FF3.6+ */
-                                 background: -webkit-gradient(linear, left 
top, left bottom, color-stop(0%,#a9db80), color-stop(100%,#96c56f)); /* 
Chrome,Safari4+ */
-                                 background: -webkit-linear-gradient(top, 
#a9db80 0%,#96c56f 100%); /* Chrome10+,Safari5.1+ */
-                                 background: -o-linear-gradient(top, #a9db80 
0%,#96c56f 100%); /* Opera 11.10+ */
-                                 background: -ms-linear-gradient(top, #a9db80 
0%,#96c56f 100%); /* IE10+ */ 
-                                 margin: 1% 9%;
-                                 width: 83%; padding:10px; 
-                                 /* shadows and rounded borders */
-                                 -moz-border-radius: 5px;
-                                 -webkit-border-radius: 5px;
-                                 border-radius: 5px;
-                                 -moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 
0.3);
-                                 -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 
0.3);
-                                 box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
+#form-container .page-header   { 
+  background: -moz-linear-gradient(top, #a9db80 0%, #96c56f 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, 
color-stop(0%,#a9db80), color-stop(100%,#96c56f)); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, #a9db80 0%,#96c56f 100%); /* 
Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, #a9db80 0%,#96c56f 100%); /* Opera 
11.10+ */
+  background: -ms-linear-gradient(top, #a9db80 0%,#96c56f 100%); /* IE10+ */ 
+  margin: 1% 9%;
+  width: 83%; padding:10px; 
+  /* shadows and rounded borders */
+  -moz-border-radius: 5px;
+  -webkit-border-radius: 5px;
+  border-radius: 5px;
+  -moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
+  -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
+  box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
+  text-align: left; 
+  font-weight: 700;
 }
 
 .signup-form {
@@ -93,79 +99,41 @@ under the License.
   list-style: none;
 }
 
-#previous {
-  background: -moz-linear-gradient(top, #a9db80 0%, #96c56f 100%);  /*FF3.6+ */
-  background: -webkit-gradient(linear, left top, left bottom, 
color-stop(0%,#a9db80), color-stop(100%,#96c56f));  /*Chrome,Safari4+ */
-  background: -webkit-linear-gradient(top, #a9db80 0%,#96c56f 100%);  
/*Chrome10+,Safari5.1+ */
-  background: -o-linear-gradient(top, #a9db80 0%,#96c56f 100%);  /*Opera 
11.10+*/ 
-  background: -ms-linear-gradient(top, #a9db80 0%,#96c56f 100%);  /*IE10+*/ 
-  display: inline-block;
-  color: black;
-  padding-left: 8px;
-  padding-right: 8px;
-  margin-left: 5px;
-}
-
-#previous:hover {
-  background: #658D5D;
-}
-
-#next{
-  background: -moz-linear-gradient(top, #a9db80 0%, #96c56f 100%); /*FF3.6+ */
+#next,
+#previous,
+#save,
+#finish {
+  background: -moz-linear-gradient(top, #a9db80 0%, #96c56f 100%);  /*FF3.6+*/ 
   background: -webkit-gradient(linear, left top, left bottom, 
color-stop(0%,#a9db80), color-stop(100%,#96c56f));  /*Chrome,Safari4+ */
   background: -webkit-linear-gradient(top, #a9db80 0%,#96c56f 100%);  
/*Chrome10+,Safari5.1+ */
   background: -o-linear-gradient(top, #a9db80 0%,#96c56f 100%);  /*Opera 
11.10+ */
   background: -ms-linear-gradient(top, #a9db80 0%,#96c56f 100%);  /*IE10+ */
   display: inline-block;
-  margin-left: 5px;
+
   padding-left: 8px;
   padding-right: 8px;
-  float: right;
+  margin-left: 5px;
+  border: none;
   color: black;
 }
 
+#save:hover,
+#previous:hover,
+#finish:hover,
 #next:hover {
   background: #658D5D;
 }
 
-#save{
-  background: -moz-linear-gradient(top, #a9db80 0%, #96c56f 100%);  /*FF3.6+*/ 
-  background: -webkit-gradient(linear, left top, left bottom, 
color-stop(0%,#a9db80), color-stop(100%,#96c56f));  /*Chrome,Safari4+ */
-  background: -webkit-linear-gradient(top, #a9db80 0%,#96c56f 100%);  
/*Chrome10+,Safari5.1+ */
-  background: -o-linear-gradient(top, #a9db80 0%,#96c56f 100%);  /*Opera 
11.10+ */
-  background: -ms-linear-gradient(top, #a9db80 0%,#96c56f 100%);  /*IE10+ */
-  color: black;
-  display: inline-block;
-  padding-left: 8px;
-  padding-right: 8px;
-  margin-left: 5px;
+#save {
   margin-right: 5px;
 }
 
-#save:hover {
-  background: #658D5D;
-}
-
-#finish{
-  background: -moz-linear-gradient(top, #a9db80 0%, #96c56f 100%);  /*FF3.6+ */
-  background: -webkit-gradient(linear, left top, left bottom, 
color-stop(0%,#a9db80), color-stop(100%,#96c56f));  /*Chrome,Safari4+ */
-  background: -webkit-linear-gradient(top, #a9db80 0%,#96c56f 100%);  
/*Chrome10+,Safari5.1+*/ 
-  background: -o-linear-gradient(top, #a9db80 0%,#96c56f 100%);  /*Opera 
11.10+ */
-  background: -ms-linear-gradient(top, #a9db80 0%,#96c56f 100%);  /*IE10+*/
-  display: inline-block;
-  margin-left:5px;
-  padding-left: 8px;
-  padding-right: 8px;
-  color: black;
-  margin-left: 5px;
+#finish {
   margin-right: 5px;
 }
 
-#finish:hover {
-  background: #658D5D;
-}
-
 #cancel {
+  color: #fff;
   margin-top: 0%;
 }
 
@@ -228,31 +196,23 @@ span.k-datetimepicker{
 
 /** Button breadcrumb **/
 .btn-breadcrumb .btn:not(:last-child):after {
-  content: " ";
-  display: block;
-  width: 0;
-  height: 0;
-  border-top: 17px solid transparent;
-  border-bottom: 17px solid transparent;
   border-left: 10px solid white;
-  position: absolute;
-  top: 50%;
-  margin-top: -17px;
-  left: 100%;
-  z-index: 3;
 }
 .btn-breadcrumb .btn:not(:last-child):before {
+  margin-left: 1px;
+  border-left: 10px solid rgb(173, 173, 173);
+}
+.btn-breadcrumb .btn:not(:last-child):before,
+.btn-breadcrumb .btn:not(:last-child):after {
   content: " ";
   display: block;
   width: 0;
   height: 0;
   border-top: 17px solid transparent;
   border-bottom: 17px solid transparent;
-  border-left: 10px solid rgb(173, 173, 173);
   position: absolute;
   top: 50%;
   margin-top: -17px;
-  margin-left: 1px;
   left: 100%;
   z-index: 3;
 }
@@ -268,31 +228,72 @@ span.k-datetimepicker{
   padding:6px 18px 6px 24px;
 }
 
-/** Default button **/
-.btn-breadcrumb .btn.btn-default:not(:last-child):after {
+/** Default buttons **/
+.breadcrumb-disabled-link {
+  pointer-events: none;
+  cursor: default;
+}
+
+.breadcrumb-btn-elem {
+  border-color: #ccc;
+}
+
+.btn-breadcrumb .breadcrumb-btn-elem:not(:last-child):after {
   border-left: 10px solid #fff;
 }
-.btn-breadcrumb .btn.btn-default:not(:last-child):before {
+.btn-breadcrumb .breadcrumb-btn-elem:not(:last-child):before {
   border-left: 10px solid #ccc;
 }
-.btn-breadcrumb .btn.btn-default:hover:not(:last-child):after {
+
+.btn-breadcrumb .breadcrumb-btn-elem.active:not(:last-child):after,
+.btn-breadcrumb .breadcrumb-btn-elem:hover:not(:last-child):after {
   border-left: 10px solid #ebebeb;
 }
-.btn-breadcrumb .btn.btn-default:hover:not(:last-child):before {
+.btn-breadcrumb .breadcrumb-btn-elem.active:not(:last-child):before,
+.btn-breadcrumb .breadcrumb-btn-elem:hover:not(:last-child):before {
   border-left: 10px solid #adadad;
 }
 
-.btn-breadcrumb .btn.btn-default.active:not(:last-child):after {
-  border-left: 10px solid #e6e6e6;
+.btn-breadcrumb .breadcrumb-btn-elem.active {
+  box-shadow: 0 0px 0px rgba(0, 0, 0, 0.125)
+}
+.btn-breadcrumb .breadcrumb-btn-elem:focus {
+  box-shadow: none !important;
 }
 
-.btn-breadcrumb .btn.btn-default.active {
-  box-shadow: 0 0px 0px rgba(0, 0, 0, 0.125)
+.btn-breadcrumb .btn:before,
+.btn-breadcrumb .btn:after {
+  transition: all .15s ease-in-out;
 }
 
-.breadcrumb-disabled-link {
-  pointer-events: none;
-  cursor: default;
+div[role="tablist"] {
+  margin-bottom: 20px;
+}
+
+/* For Bootstrap 4 */
+
+/*
+.btn-breadcrumb .breadcrumb-btn-elem:hover {
+  color: #ffffff !important;
+}
+
+.btn-breadcrumb .breadcrumb-btn-elem:not(:hover),
+.btn-breadcrumb .breadcrumb-home:not(:hover) {
+  color: #333;
+  background-color: #fff;
+}
+
+.btn-breadcrumb .breadcrumb-btn-elem:hover:not(:last-child):after,
+.btn-breadcrumb .breadcrumb-btn-elem:hover:not(:last-child):before,
+.btn-breadcrumb .breadcrumb-btn-elem.active:not(:last-child):after,
+.btn-breadcrumb .breadcrumb-btn-elem.active:not(:last-child):before {
+  border-left: 10px solid #4c4c4c;
+}
+*/
+
+.btn-file {
+  background: transparent;
+  color: black;
 }
 
 .text-validation-error{
@@ -300,9 +301,81 @@ span.k-datetimepicker{
   font-weight: 600;
 }
 
-/*--------------end default style*/
+.multivalue button{
+  float: right
+}
+.schema-type #date{
+  margin-top: 35px;
+  margin-bottom: 2px;
+}
+
+.multivalue input{
+  width: calc(100% - 70px);
+  margin-top: 10px
+}
+
+.multivalue input:disabled{
+  width: 100%;  
+}
+
+.multivalue button{
+  margin-top: 2px
+}
+
+.multivalue #datetime{
+  margin-top: 0px
+}
+
+#datetime input{
+  width: 100%;
+  margin-top: 0px
+}
+#timepicker input{
+  width: 50px;
+  margin-top: 0px
+}
 
-/*style for devices max width 350 */
+.multivalue #timepicker {
+  margin-top: -35px;
+}
+
+.suggestions{
+  font-size: 10px;
+  display: inline-block;
+  margin-bottom: 5px;
+}
+
+#status-buttons {
+  padding-left: 15px;
+  -webkit-flex-flow: row wrap; /* Safari 6.1+ */
+  flex-flow: row wrap;
+}
+
+.card.card-open {
+  background-color: #ffffff;
+}
+
+.card-header a {
+  text-decoration: none;
+}
+
+.card-header h5 a {
+  color: black;
+}
+
+.card-header {
+  padding: 2px 15px;
+  border-bottom: 1px solid transparent;
+  border-top-left-radius: 3px;
+  border-top-right-radius: 3px;
+  background-color: #f5f5f5;
+  border-color: #ddd;
+}
+/* end default style
+============================================================================= 
*/
+
+/* Style for devices max width 350
+============================================================================= 
*/
 @media only screen and (max-width: 350px) {
 
   input[type=file] {
@@ -337,39 +410,42 @@ span.k-datetimepicker{
     padding: 0px 0px 1px 0px
   }
 
-  #finish{
+  #finish,
+  #save,
+  #next,
+  #previous {
     padding-left: 2px;
     padding-right: 2px;
+  }
+
+  #finish{
     margin-left:-27px;
     margin-top: 2px;
   }
   #next{
-    padding-left: 2px;
-    padding-right: 2px;
     margin-left: 1px;
   }
   #previous{
-    padding-left: 2px;
-    padding-right: 2px;
     margin-left:-126px;
   }
   #save{
     margin-top: 4px;
-    padding-left: 2px;
-    padding-right: 2px;
     margin-top:-31px;
     margin-left: -42px;
     margin-right: 15px;
   }
-  
+
   span.k-datetimepicker {
     width: 141%;
     margin-top: -50px;
-}
+  }
 
 }
+/* 
+============================================================================= 
*/
 
-/*style for devices max width 400 */
+/* Style for devices max width 400
+============================================================================= 
*/
 @media only screen and (max-width: 400px) {
 
   input[type=file] {
@@ -418,36 +494,42 @@ span.k-datetimepicker{
     padding:6px 18px 6px 24px;
   }
 
-  #finish{
+  #next,
+  #finish,
+  #previous,
+  #save {
     padding-left: 2px;
+  }
+
+  #finish {
     padding-right: 2px;
     margin-right: -3px;
     margin-left: 0px;
   }
-  #next{
-    padding-left: 2px;
+  #next {
     padding-right: 2px;
     margin-right: 23px;
   }
-  #previous{
-    padding-left: 2px;
+  #previous {
     padding-right: 2px;
     margin-left: 0px
   }
-  #save{
-    padding-left: 2px;
+  #save {
     padding-right: 0px;
     margin-left: -21px;
     margin-top: 4px;
   }
-  
-    span.k-datetimepicker{
+
+  span.k-datetimepicker{
     width: 160%;
   }
 
 }
+/* 
+============================================================================= 
*/
 
-/*style for devices max width 500*/
+/* Style for devices max width 500
+============================================================================= 
*/
 @media only screen and (max-width: 500px) {
 
   * {
@@ -483,17 +565,13 @@ span.k-datetimepicker{
     font-size: 12px;
   }
 
-  .btn-group .btn+.btn, .btn-group .btn+.btn-group, .btn-group 
.btn-group+.btn, .btn-group .btn-group+.btn-group {
+  .btn-group .btn+.btn, 
+  .btn-group .btn+.btn-group, 
+  .btn-group .btn-group+.btn, 
+  .btn-group .btn-group+.btn-group {
     margin-left: 0px;
   }
 
-  .btn-default {
-    color: #333;
-    background-color: #fff;
-    border-color: #ccc;
-    /*width: 100%;*/
-  }
-
   .card-container.card {
     width: 295px;
     padding: 40px 40px 0px;
@@ -543,12 +621,10 @@ span.k-datetimepicker{
 
   .select2-results .select2-result-label{
     font-size: 12px;
-
   }
 
   .select2-container-multi .select2-choices .select2-search-field input{
     font-size:12px;
-
   }
 
   #datetime{
@@ -666,8 +742,11 @@ span.k-datetimepicker{
 
   }
 }
+/* 
+============================================================================= 
*/
 
-/*style for devices max width 800) */
+/* Style for devices max width 800)
+============================================================================= 
*/
 @media only screen and (max-width: 800px) {
 
   * {
@@ -702,6 +781,8 @@ span.k-datetimepicker{
   }
 
 }
+/* 
+============================================================================= 
*/
 
 /* ANIMATIONS
 ============================================================================= 
*/
@@ -729,41 +810,5 @@ span.k-datetimepicker{
   from    { -webkit-transform:translateX(300%); }
   to      { -webkit-transform: translateX(0); }
 }
-
-.multivalue button{
-  float: right
-}
-.schema-type #date{
-  margin-top: 35px;
-  margin-bottom: 2px;
-}
-
-.multivalue input{
-  width: calc(100% - 70px);
-  margin-top: 10px
-}
-
-.multivalue input:disabled{
-  width: 100%;  
-}
-
-.multivalue button{
-  margin-top: 2px
-}
-
-.multivalue #datetime{
-  margin-top: 0px
-}
-
-#datetime input{
-  width: 100%;
-  margin-top: 0px
-}
-#timepicker input{
-  width: 50px;
-  margin-top: 0px
-}
-
-.multivalue #timepicker {
-  margin-top: -35px;
-}
+/* 
+============================================================================= 
*/

http://git-wip-us.apache.org/repos/asf/syncope/blob/19d9e93e/client/enduser/src/main/resources/META-INF/resources/app/css/login.css
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/css/login.css 
b/client/enduser/src/main/resources/META-INF/resources/app/css/login.css
index a517ae5..03212f3 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/css/login.css
+++ b/client/enduser/src/main/resources/META-INF/resources/app/css/login.css
@@ -54,26 +54,6 @@ body{
   float: right;
 }
 
-.card-container.card {
-  width: 350px;
-  padding: 40px 40px 0px;
-}
-
-.card {
-  background-color: #F7F7F7;
-  /* just in case there no content*/
-  padding: 20px 25px 30px;
-  margin: 0 auto 25px;
-  margin-top: 50px;
-  /* shadows and rounded borders */
-  -moz-border-radius: 2px;
-  -webkit-border-radius: 2px;
-  border-radius: 2px;
-  -moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
-  -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
-  box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
-}
-
 .login-logo {
   width: 200px;
   margin: 0px auto 10px;
@@ -93,22 +73,11 @@ body{
   box-sizing: border-box;
 }
 
-.form-control {
-  display: block;
-  width: 100%;
-  height: 34px;
-  padding: 6px 12px;
-  font-size: 14px;
-  line-height: 1.42857;
-  color: #555;
-  background-color: #FFF;
-  background-image: none;
-  border: 1px solid #CCC;
-  border-radius: 4px;
-  box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.075) inset;
-  transition: border-color 0.15s ease-in-out 0s, box-shadow 0.15s ease-in-out 
0s;
-}
-
 input[type="number"]{
   padding-right: 0px !important
 }
+
+.dropdown-toggle {
+  width: 100%; 
+  text-align: left;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/19d9e93e/client/enduser/src/main/resources/META-INF/resources/app/css/notification.css
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/css/notification.css 
b/client/enduser/src/main/resources/META-INF/resources/app/css/notification.css
new file mode 100644
index 0000000..f2d190e
--- /dev/null
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/css/notification.css
@@ -0,0 +1,28 @@
+/*
+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.
+*/
+
+.k-notification-wrap {
+  white-space: normal !important;
+  word-wrap: break-word !important;
+}
+
+.k-icon {
+  margin-right: 5px;
+  vertical-align: sub;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/19d9e93e/client/enduser/src/main/resources/META-INF/resources/app/css/passwordReset.css
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/css/passwordReset.css
 
b/client/enduser/src/main/resources/META-INF/resources/app/css/passwordReset.css
new file mode 100644
index 0000000..4f03f25
--- /dev/null
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/css/passwordReset.css
@@ -0,0 +1,37 @@
+/*
+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.
+*/
+
+#resetpassword {
+  background: -moz-linear-gradient(top, #a9db80 0%, #96c56f 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, 
color-stop(0%,#a9db80), color-stop(100%,#96c56f)); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, #a9db80 0%,#96c56f 100%); /* 
Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, #a9db80 0%,#96c56f 100%); /* Opera 
11.10+ */
+  background: -ms-linear-gradient(top, #a9db80 0%,#96c56f 100%); /* IE10+ */
+  margin-left: 5px;
+  color: black;
+  border: none;
+}
+#resetpassword:hover {
+  background: #658D5D;
+}
+
+#captchaImg {
+  display: block;
+  margin: 0 auto;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/19d9e93e/client/enduser/src/main/resources/META-INF/resources/app/css/templates/dark/editUser.css
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/css/templates/dark/editUser.css
 
b/client/enduser/src/main/resources/META-INF/resources/app/css/templates/dark/editUser.css
new file mode 100644
index 0000000..dd0d351
--- /dev/null
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/css/templates/dark/editUser.css
@@ -0,0 +1,95 @@
+/*
+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.
+*/
+
+html,
+body {
+  height: 100%;
+}
+body {
+  background: -moz-linear-gradient(94deg, rgba(51,51,51,1) 0%, 
rgba(101,141,93,1) 100%); /* ff3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, 
rgba(101,141,93,1)), color-stop(100%, rgba(51,51,51,1))); /* safari4+,chrome */
+  background: -webkit-linear-gradient(94deg, rgba(51,51,51,1) 0%, 
rgba(101,141,93,1) 100%); /* safari5.1+,chrome10+ */
+  background: -o-linear-gradient(94deg, rgba(51,51,51,1) 0%, 
rgba(101,141,93,1) 100%); /* opera 11.10+ */
+  background: -ms-linear-gradient(94deg, rgba(51,51,51,1) 0%, 
rgba(101,141,93,1) 100%); /* ie10+ */
+  background: linear-gradient(356deg, rgba(51,51,51,1) 0%, rgba(101,141,93,1) 
100%); /* w3c */
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#658d5d', 
endColorstr='#333333',GradientType=0 ); /* ie6-9 */
+}
+
+#form-container .page-header,
+#previous,
+#next,
+#save,
+#finish,
+#resetpassword {
+  background: -moz-linear-gradient(top, #6a9647 0%, #48543d 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, 
color-stop(0%,#6a9647), color-stop(100%,#48543d)); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, #6a9647 0%,#48543d 100%); /* 
Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, #6a9647 0%,#48543d 100%); /* Opera 
11.10+ */
+  background: -ms-linear-gradient(top, #6a9647 0%,#48543d 100%); /* IE10+ */
+  color: white;
+}
+
+#form-container .btn-danger {
+  background-color: #a93f3c;
+  border-color: #ffffff;
+}
+
+
+#form-container,
+#form-container .panel {
+  background-color: #151515;
+  color: white;
+}
+
+#form-container input, 
+#form-container select,
+#form-container select option,
+#form-container label {
+  color: white;
+}
+
+#form-container input:disabled {
+  background: #6d6d6d;
+}
+
+#form-container input,
+#form-container select,
+#form-container select option,
+span.k-datepicker, 
+span.k-timepicker, 
+span.k-datetimepicker, 
+span.k-colorpicker, 
+span.k-numerictextbox, 
+span.k-combobox, 
+span.k-dropdown, 
+.k-toolbar .k-split-button,
+.k-autocomplete.k-state-default, 
+.k-picker-wrap.k-state-default, 
+.k-numeric-wrap.k-state-default, 
+.k-dropdown-wrap.k-state-default {
+  background: #313131;
+}
+
+.card-header h5 a {
+  color: white;
+}
+
+.card-header {
+  background-color: #49553e;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/19d9e93e/client/enduser/src/main/resources/META-INF/resources/app/css/templates/dark/login.css
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/css/templates/dark/login.css
 
b/client/enduser/src/main/resources/META-INF/resources/app/css/templates/dark/login.css
new file mode 100644
index 0000000..90da4b0
--- /dev/null
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/css/templates/dark/login.css
@@ -0,0 +1,78 @@
+/*
+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.
+*/
+
+html,
+body {
+  height: 100%;
+}
+body {
+  background: -moz-linear-gradient(94deg, rgba(51,51,51,1) 0%, 
rgba(101,141,93,1) 100%); /* ff3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, 
rgba(101,141,93,1)), color-stop(100%, rgba(51,51,51,1))); /* safari4+,chrome */
+  background: -webkit-linear-gradient(94deg, rgba(51,51,51,1) 0%, 
rgba(101,141,93,1) 100%); /* safari5.1+,chrome10+ */
+  background: -o-linear-gradient(94deg, rgba(51,51,51,1) 0%, 
rgba(101,141,93,1) 100%); /* opera 11.10+ */
+  background: -ms-linear-gradient(94deg, rgba(51,51,51,1) 0%, 
rgba(101,141,93,1) 100%); /* ie10+ */
+  background: linear-gradient(356deg, rgba(51,51,51,1) 0%, rgba(101,141,93,1) 
100%); /* w3c */
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#658d5d', 
endColorstr='#333333',GradientType=0 ); /* ie6-9 */
+}
+
+#login-container .card {
+  background-color: #151515;
+  color: white;
+}
+
+#login-container .login-logo {
+  background-color: rgba(255, 255, 255, 0.15);
+  border-radius: 10px;
+
+  margin: 0px auto 20px auto;
+}
+
+#login-container input,
+#login-container select,
+#login-container select option {
+  color: white;
+  background: #313131;
+}
+
+#login-container input:disabled {
+  background: #6d6d6d;
+}
+
+#login-container .btn-link {
+  color: #82b9e8;
+}
+#login-container .btn-link:focus, 
+#login-container .btn-link:hover {
+  color: #b5dcff;
+}
+
+#login-container .login-btn {
+  color: white;
+  background: -webkit-gradient(linear, left top, left bottom, 
color-stop(0%,#6a9647), color-stop(100%,#48543d));
+
+  background: linear-gradient(top, #6a9647 0%,#48543d 100%);
+  background: -webkit-linear-gradient(top, #6a9647 0%,#48543d 100%);
+  background: -moz-linear-gradient(top, #6a9647 0%, #48543d 100%);
+  background: -o-linear-gradient(top, #6a9647 0%,#48543d 100%);
+  background: -ms-linear-gradient(top, #6a9647 0%,#48543d 100%);
+}
+
+#login-container .form-group {
+  margin-bottom: 20px;
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/19d9e93e/client/enduser/src/main/resources/META-INF/resources/app/index.html
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/index.html 
b/client/enduser/src/main/resources/META-INF/resources/app/index.html
index 3aa57f5..6ef3c4d 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/index.html
+++ b/client/enduser/src/main/resources/META-INF/resources/app/index.html
@@ -32,11 +32,10 @@ under the License.
 
     <link rel="shortcut icon" href="img/favicon.png" type="image/png"/>
 
-    <link href="css/login.css" rel="stylesheet" type="text/css"/>
     <link href="../webjars/jquery-ui/${jquery-ui.version}/jquery-ui.css" 
rel="stylesheet" type="text/css"/>
     <link 
href="../webjars/bootstrap/${bootstrap.version}/css/bootstrap.min.css" 
rel="stylesheet" type="text/css"/>
     <link 
href="../webjars/bootstrap-select/${bootstrap-select.version}/css/bootstrap-select.min.css"
 rel="stylesheet" type="text/css"/>
-    <link 
href="../webjars/font-awesome/${font-awesome.version}/css/font-awesome.min.css" 
rel="stylesheet" type="text/css"/>
+    <link 
href="../webjars/font-awesome/${font-awesome.version}/css/${font-awesome.filename}"
 rel="stylesheet" type="text/css"/>
     <link href="../webjars/ionicons/${ionicons.version}/css/ionicons.min.css" 
rel="stylesheet" type="text/css"/>
     <link 
href="../webjars/angular-ui-select/${angular-ui-select.version}/select.css" 
rel="stylesheet" type="text/css"/>
     <link 
href="../webjars//kendo-ui-core/${kendo-ui-core.version}/styles/web/kendo.common.core.min.css"
 rel="stylesheet" type="text/css"/>
@@ -44,9 +43,8 @@ under the License.
     <link 
href="../webjars/angular-treasure-overlay-spinner/${angular-treasure-overlay-spinner.version}/dist/treasure-overlay-spinner.min.css"
 rel="stylesheet" type="text/css"/>
     <link 
href="../webjars/ng-password-strength/${ng-password-strength.version}/dist/styles/main.css"
 rel="stylesheet" type="text/css">
     <link href="../webjars/select2/${select2.version}/select2.css" 
rel="stylesheet"/>
-    <link href="css/app.css" rel="stylesheet" type="text/css"/>
-    <link href="css/login.css" rel="stylesheet" type="text/css"/>
-    <link href="css/editUser.css" rel="stylesheet" type="text/css"/>
+
+    <link href="css/customSpinner.css" rel="stylesheet" type="text/css"/>
   </head>
   <body ng-cloak >
 
@@ -54,10 +52,11 @@ under the License.
         <p class="browsehappy">You are using an <strong>outdated</strong> 
browser. Please <a href="http://browsehappy.com/";>upgrade your browser</a> to 
improve your experience.</p>
     <![endif]-->
 
-    <span id="notifications" kendo-notification="notifications"></span>
+    <span id="notifications"></span>
 
   <treasure-overlay-spinner active='spinner.active'>
-    <div ui-view ng-cloak ng-controller="ApplicationController" 
ng-init="initApplication()">      
+    <div ui-view ng-cloak ng-controller="ApplicationController" 
ng-init="initApplication()">   
+
     </div>    
   </treasure-overlay-spinner>
 
@@ -65,10 +64,11 @@ under the License.
   <script src="../webjars/angular/${angular.version}/angular.js"></script>
   <script 
src="../webjars/angular-ui-router/${angular-ui-router.version}/release/angular-ui-router.js"></script>
   <script 
src="../webjars/angular-animate/${angular.version}/angular-animate.js"></script>
-  <script 
src="../webjars/angular-resource/${angular.version}/angular-resource.js"></script>
+  <script 
src="../webjars/angular-resource/${angular-resource.version}/angular-resource.js"></script>
   <script 
src="../webjars/angular-cookies/${angular.version}/angular-cookies.js"></script>
   <script 
src="../webjars/angular-sanitize/${angular.version}/angular-sanitize.js"></script>
-  <script 
src="../webjars/angular-ui-bootstrap/${angular-ui-bootstrap.version}/ui-bootstrap-tpls.js"></script>
+  <script 
src="../webjars/ui-bootstrap4/${ui-bootstrap.version}/dist/ui-bootstrap.js"></script>
+  <script 
src="../webjars/ui-bootstrap4/${ui-bootstrap.version}/dist/ui-bootstrap-tpls.js"></script>
   <script 
src="../webjars/angular-ui-select/${angular-ui-select.version}/select.js"></script>
   <script 
src="../webjars/kendo-ui-core/${kendo-ui-core.version}/js/kendo.ui.core.min.js"></script>
   <script 
src="../webjars/kendo-ui-core/${kendo-ui-core.version}/js/kendo.notification.min.js"></script>
@@ -79,11 +79,11 @@ under the License.
   <script 
src="../webjars/kendo-ui-core/${kendo-ui-core.version}/js/cultures/kendo.culture.ja.js"></script>
   <script 
src="../webjars/angular-treasure-overlay-spinner/${angular-treasure-overlay-spinner.version}/dist/treasure-overlay-spinner.min.js"></script>
   <script 
src="../webjars/ng-password-strength/${ng-password-strength.version}/dist/scripts/ng-password-strength.min.js"></script>
-  <script type="text/javascript" 
src="../webjars/bootstrap-select/${bootstrap-select.version}/js/bootstrap-select.min.js"></script>
+  <script 
src="../webjars/bootstrap-select/${bootstrap-select.version}/js/bootstrap-select.min.js"></script>
   <script 
src="../webjars/FileSaver.js/${fileSaver.version}/FileSaver.js"></script>
   <script src="../webjars/lodash/${lodash.version}/lodash.min.js"></script>
   <script 
src="../webjars/angular-translate/${angular-translate.version}/angular-translate.js"></script>
-  <script 
src="../webjars/angular-translate-loader-partial/${angular-translate.version}/angular-translate-loader-partial.js"></script>
+  <script 
src="../webjars/angular-translate-loader-partial/${angular-translate-loader-partial.version}/angular-translate-loader-partial.js"></script>
   <script 
src="../webjars/angular-translate-storage-cookie/${angular-translate.version}/angular-translate-storage-cookie.js"></script>
   <script 
src="../webjars/bootstrap-fileinput/${bootstrap-fileinput.version}/js/fileinput.js"></script>
 
@@ -96,6 +96,7 @@ under the License.
   <script src="js/services/realmService.js"></script>
   <script src="js/services/securityQuestionService.js"></script>
   <script src="js/services/infoService.js"></script>
+  <script src="js/services/dynamicTemplateService.js"></script>
   <script src="js/services/resourceService.js"></script>
   <script src="js/services/groupService.js"></script>
   <script src="js/services/anyService.js"></script>
@@ -116,7 +117,7 @@ under the License.
   <script src="js/directives/dynamicPlainAttributes.js"></script>
   <script src="js/directives/dynamicDerivedAttributes.js"></script>
   <script src="js/directives/dynamicVirtualAttributes.js"></script>
-  <script src="js/directives/navigationButtons.js"></script>
+  <script src="js/directives/navigationButtonsPartial.js"></script>
   <script src="js/directives/loader.js"></script>
   <script src="js/directives/captcha.js"></script>
   <script src="js/directives/resources.js"></script>
@@ -126,6 +127,7 @@ under the License.
   <script src="js/directives/validationMessage.js"></script>
   <script src="js/directives/validateDropdown.js"></script>
   <script src="js/directives/fileInput.js"></script>
+  <script src="js/directives/dynamicTemplateItem.js"></script>
   <!--validator-->
   <script src="js/validator/validationRules.js"></script>
   <script src="js/validator/validationExecutor.js"></script>
@@ -134,5 +136,6 @@ under the License.
   <!--util-->
   <script src="js/util/userUtil.js"></script>
   <script src="js/util/genericUtil.js"></script>
+  <script src="js/util/assetsManager.js"></script>
 </body>
 </html>

http://git-wip-us.apache.org/repos/asf/syncope/blob/19d9e93e/client/enduser/src/main/resources/META-INF/resources/app/js/app.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/app.js 
b/client/enduser/src/main/resources/META-INF/resources/app/js/app.js
index 475f74b..0ed272a 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/js/app.js
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/app.js
@@ -67,12 +67,18 @@ app.config(['$stateProvider', '$urlRouterProvider', 
'$httpProvider', '$translate
     $stateProvider
             .state('home', {
               url: '/',
-              templateUrl: 'views/self.html'
+              templateUrl: 'views/self.html',
+              resolve: {
+                loadAssets: ['DynamicTemplateService', function 
(DynamicTemplateService) {
+                    return 
DynamicTemplateService.getGeneralAssetsContent(["css"]);
+                  }]
+              }
             })
             .state('self', {
               url: '/self?errorMessage',
               templateUrl: 'views/self.html'
             })
+
             /* <Extensions> */
             .state('self-saml2sp', {
               url: '/self-saml2sp',
@@ -86,7 +92,7 @@ app.config(['$stateProvider', '$urlRouterProvider', 
'$httpProvider', '$translate
               }
             })
             /* </Extensions> */
-            
+
             /* <Extensions> */
             .state('self-oidcclient', {
               url: '/self-oidcclient',
@@ -100,6 +106,7 @@ app.config(['$stateProvider', '$urlRouterProvider', 
'$httpProvider', '$translate
               }
             })
             /* </Extensions> */
+
             .state('user-self-update', {
               url: '/user-self-update',
               templateUrl: 'views/home.html',
@@ -290,8 +297,8 @@ app.config(['$stateProvider', '$urlRouterProvider', 
'$httpProvider', '$translate
       };
     });
   }]);
-app.run(['$rootScope', '$location', '$state', 'AuthService',
-  function ($rootScope, $location, $state, AuthService) {
+app.run(['$rootScope', '$location', '$state', 'AuthService', '$transitions',
+  function ($rootScope, $location, $state, AuthService, $transitions) {
     /*
      
|--------------------------------------------------------------------------
      | Main of Syncope Enduser application
@@ -300,50 +307,63 @@ app.run(['$rootScope', '$location', '$state', 
'AuthService',
      | If the route change failed due to authentication error, redirect them 
out
      
|--------------------------------------------------------------------------
      */
-    $rootScope.$on('$routeChangeError', function (event, current, previous, 
rejection) {
-      if (rejection === 'Not Authenticated') {
-        $location.path('/self');
+    $transitions.onError({}, function (trans) {
+      if (trans.error().message === 'Not Authenticated') {
+        $state.go('home');
       }
     });
-    $rootScope.$on('$stateChangeSuccess', function (event, toState) {
+
+    $transitions.onSuccess({}, function (trans) {
+      var toState = trans.$to();
+      var fromState = trans.$from();
+
       if (toState.name === 'create') {
-        $state.go('create.credentials');
+        $state.go('create' + $rootScope.getWizardFirstStep());
       } else if (toState.name === 'update') {
-        $state.go('update.credentials');
+        $state.go('update' + $rootScope.getWizardFirstStep());
       } else if (toState.name.indexOf("update") > -1) {
         AuthService.islogged().then(function (response) {
           if (response === "true") {
             $state.go(toState);
           } else {
-            $state.go('self');
+            $state.go('home');
           }
         }, function (response) {
-          console.error("not logged");
-          $state.go('self');
+          console.error("Not logged");
+          $state.go('home');
         }
         );
-
       } else if (toState.name === 'home' || toState.name === 'self') {
+        if (fromState.name === 'home' || fromState.name === 'self') {
+          return false;
+        }
+
         AuthService.islogged().then(function (response) {
           if (response === "true") {
-            $state.go('update.credentials');
-          } else {
-            $state.go('self');
+            $state.go('update' + $rootScope.getWizardFirstStep());
           }
         }, function (response) {
           console.error("not logged");
-          $state.go('self');
-        }
-        );
+          $state.go('home');
+        });
         /*
          * enable "finish" button on every page in create mode
          */
       } else if (toState.name === 'create.finish') {
+        if (fromState.name === 'create.finish') {
+          return false;
+        }
+
         $rootScope.endReached = true;
       } else {
+        if (fromState.name === toState.name) {
+          return false;
+        }
+
         $state.go(toState);
       }
     });
+
     $rootScope.spinner = {
       active: false,
       on: function () {
@@ -354,9 +374,9 @@ app.run(['$rootScope', '$location', '$state', 'AuthService',
       }
     };
   }]);
-app.controller('ApplicationController', ['$scope', '$rootScope', '$location', 
'InfoService', 'SAML2IdPService',
-  'OIDCProviderService',
-  function ($scope, $rootScope, $location, InfoService, SAML2IdPService, 
OIDCProviderService) {
+app.controller('ApplicationController', ['$scope', '$rootScope', 
'InfoService', 'SAML2IdPService',
+  'OIDCProviderService', 'DynamicTemplateService',
+  function ($scope, $rootScope, InfoService, SAML2IdPService, 
OIDCProviderService, DynamicTemplateService) {
     $scope.initApplication = function () {
       /* 
        * disable by default wizard buttons in self-registration
@@ -397,6 +417,57 @@ app.controller('ApplicationController', ['$scope', 
'$rootScope', '$location', 'I
         selected: {}
       };
 
+      var doGetDynamicTemplateJSON = function (callback) {
+        if (!$rootScope.dynTemplate) {
+          DynamicTemplateService.getContent().then(
+                  function (response) {
+                    /* 
+                     * USER dynamic template JSON
+                     */
+                    $rootScope.dynTemplate = response;
+
+                    /*
+                     * Wizard steps from JSON
+                     */
+                    $scope.wizard = response.wizard.steps;
+                    $scope.wizardFirstStep = response.wizard.firstStep;
+
+                    callback($rootScope.dynTemplate);
+                  },
+                  function (response) {
+                    console.error("Something went wrong while accessing 
dynamic template resource", response);
+                  });
+        } else {
+          callback($rootScope.dynTemplate);
+        }
+      };
+      $rootScope.getDynamicTemplateInfo = function (type, key, callback) {
+        if (type) {
+          doGetDynamicTemplateJSON(function (templateJSON) {
+            callback((templateJSON && templateJSON["templates"] && 
templateJSON["templates"][type])
+                    ? templateJSON["templates"][type][key]
+                    : "");
+          });
+        } else {
+          callback("");
+        }
+      };
+      $rootScope.getDynamicTemplateOtherInfo = function (type, key, callback) {
+        if (type) {
+          doGetDynamicTemplateJSON(function (templateJSON) {
+            callback((templateJSON && templateJSON[type])
+                    ? templateJSON[type][key]
+                    : "");
+          });
+        } else {
+          callback("");
+        }
+      };
+
+      $rootScope.getWizardFirstStep = function () {
+        return ($scope.wizardFirstStep ? ('.' + $scope.wizardFirstStep) : '');
+      };
+
       InfoService.getInfo().then(
               function (response) {
                 $rootScope.pwdResetAllowed = response.pwdResetAllowed;
@@ -408,11 +479,12 @@ app.controller('ApplicationController', ['$scope', 
'$rootScope', '$location', 'I
                 /* 
                  * USER form customization JSON
                  */
-                $rootScope.customForm = response.customForm;
+                $rootScope.customFormAttributes = 
response.customFormAttributes;
               },
               function (response) {
                 console.error("Something went wrong while accessing info 
resource", response);
               });
+
       /* <Extensions> */
       SAML2IdPService.getAvailableSAML2IdPs().then(
               function (response) {
@@ -422,10 +494,6 @@ app.controller('ApplicationController', ['$scope', 
'$rootScope', '$location', 'I
                 console.debug("No SAML 2.0 SP extension available", response);
               });
       /* </Extensions> */
-      /* 
-       * configuration getters
-       */
-
       /* <Extensions> */
       OIDCProviderService.getAvailableOIDCProviders().then(
               function (response) {
@@ -435,6 +503,7 @@ app.controller('ApplicationController', ['$scope', 
'$rootScope', '$location', 'I
                 console.debug("No OIDC Client extension available", response);
               });
       /* </Extensions> */
+
       /* 
        * configuration getters
        */
@@ -463,6 +532,7 @@ app.controller('ApplicationController', ['$scope', 
'$rootScope', '$location', 'I
       $rootScope.getMaxUploadFileSizeMB = function () {
         return $rootScope.maxUploadFileSizeMB;
       };
+
       /* 
        * USER Attributes sorting strategies
        */
@@ -478,19 +548,25 @@ app.controller('ApplicationController', ['$scope', 
'$rootScope', '$location', 'I
           return schemaNameA < schemaNameB ? 1 : schemaNameA > schemaNameB ? 
-1 : 0;
         }
       };
+
       /*
        
|--------------------------------------------------------------------------
        | Notification mgmt
        
|--------------------------------------------------------------------------
        */
-      $scope.notification = 
$('#notifications').kendoNotification().data("kendoNotification");
-      $scope.notification.setOptions({stacking: "down"});
+      $scope.notificationSuccessTimeout = 4000;
+//      $scope.notification = 
$('#notifications').kendoNotification().data("kendoNotification");
+      $scope.notification = $("#notifications").kendoNotification({
+        stacking: "down",
+        hideOnClick: true,
+        width: 320
+      }).data("kendoNotification");
       $scope.notification.options.position["top"] = 20;
       $scope.showSuccess = function (message, component) {
         if (!$scope.notificationExists(message)) {
           //forcing scrollTo since kendo doesn't disable scrollTop if pinned 
is true
           window.scrollTo(0, 0);
-          component.options.autoHideAfter = 3000;
+          component.options.autoHideAfter = $scope.notificationSuccessTimeout;
           component.show(message, "success");
         }
       };
@@ -511,7 +587,7 @@ app.controller('ApplicationController', ['$scope', 
'$rootScope', '$location', 'I
       };
       $scope.notificationExists = function (message) {
         var result = false;
-        if ($scope.notification !== null) {
+        if ($scope.notification) {
           var pendingNotifications = $scope.notification.getNotifications();
           pendingNotifications.each(function (idx, element) {
             var popup = $(element).data("kendoPopup");
@@ -524,7 +600,7 @@ app.controller('ApplicationController', ['$scope', 
'$rootScope', '$location', 'I
         return result;
       };
       $scope.hideNotifications = function (timer) {
-        if ($scope.notification !== null) {
+        if ($scope.notification) {
           var pendingNotifications = $scope.notification.getNotifications();
           if (timer && timer > 0) {
             setTimeout(function () {
@@ -548,12 +624,13 @@ app.controller('ApplicationController', ['$scope', 
'$rootScope', '$location', 'I
           }
         }
       };
+
       /*
        * Intercepting location change event
        * When a location changes, old notifications should be removed
        */
       $rootScope.$on("$locationChangeStart", function (event, next, current) {
-        $scope.hideNotifications(3000);
+        $scope.hideNotifications($scope.notificationSuccessTimeout);
       });
       //Intercepting xhr start event
       $scope.$on('xhrStarted', function (event, next, current) {
@@ -563,20 +640,7 @@ app.controller('ApplicationController', ['$scope', 
'$rootScope', '$location', 'I
       $scope.$on('hideErrorMessage', function (event, popupMessage) {
         $scope.hideError(popupMessage, $scope.notification);
       });
-      /*
-       
|--------------------------------------------------------------------------
-       | Wizard configuration
-       
|--------------------------------------------------------------------------
-       */
-      $scope.wizard = {
-        "credentials": {url: "/credentials"},
-        "groups": {url: "/groups"},
-        "plainSchemas": {url: "/plainSchemas"},
-        "derivedSchemas": {url: "/derivedSchemas"},
-        "virtualSchemas": {url: "/virtualSchemas"},
-        "resources": {url: "/resources"},
-        "finish": {url: "/finish"}
-      };
+
       /*
        
|--------------------------------------------------------------------------
        | Utilities

http://git-wip-us.apache.org/repos/asf/syncope/blob/19d9e93e/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/LoginController.js
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/LoginController.js
 
b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/LoginController.js
index d2b755c..f92c15c 100644
--- 
a/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/LoginController.js
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/LoginController.js
@@ -18,8 +18,9 @@
  */
 
 'use strict';
-angular.module("login").controller("LoginController", ['$scope', '$rootScope', 
'$http', '$location', 'AuthService',
-  function ($scope, $rootScope, $http, $location, AuthService) {
+angular.module("login").controller("LoginController", ['$scope', '$rootScope', 
'$http', '$state', '$location',
+  'AuthService',
+  function ($scope, $rootScope, $http, $state, $location, AuthService) {
 
     $scope.credentials = {
       username: '',
@@ -37,7 +38,7 @@ angular.module("login").controller("LoginController", 
['$scope', '$rootScope', '
         // reset OIDC name
         $rootScope.oidcops.selected.name = null;
         // got to update page
-        $location.path("/self/update");
+        $state.go("update" + $rootScope.getWizardFirstStep());
       }, function (response) {
         console.info("Login failed for: ", response);
         var errorMessage;
@@ -65,11 +66,11 @@ angular.module("login").controller("LoginController", 
['$scope', '$rootScope', '
     };
 
     $scope.selfCreate = function () {
-      $location.path("/self/create");
+      $state.go("create" + $rootScope.getWizardFirstStep());
     };
 
     $scope.passwordReset = function () {
-      $location.path("/passwordreset");
+      $state.go("passwordreset");
     };
 
     $scope.$watch(function () {

http://git-wip-us.apache.org/repos/asf/syncope/blob/19d9e93e/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/OIDCClientController.js
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/OIDCClientController.js
 
b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/OIDCClientController.js
index f3eab68..8d7b3cd 100644
--- 
a/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/OIDCClientController.js
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/OIDCClientController.js
@@ -18,9 +18,9 @@
  */
 
 'use strict';
-angular.module("login").controller("OIDCClientController", function ($scope, 
$rootScope, $location, userAttrs) {
+angular.module("login").controller("OIDCClientController", function ($scope, 
$rootScope, $state, userAttrs) {
   $scope.selfCreate = function () {
-    $location.path("/self/create");
+    $state.go("create" + $rootScope.getWizardFirstStep());
   };
 
   $rootScope.oidcops.userAttrs = userAttrs;

http://git-wip-us.apache.org/repos/asf/syncope/blob/19d9e93e/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/SAML2SPController.js
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/SAML2SPController.js
 
b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/SAML2SPController.js
index 05adf18..6d6bd33 100644
--- 
a/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/SAML2SPController.js
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/SAML2SPController.js
@@ -18,9 +18,9 @@
  */
 
 'use strict';
-angular.module("login").controller("SAML2SPController", function ($scope, 
$rootScope, $location, userAttrs) {
+angular.module("login").controller("SAML2SPController", function ($scope, 
$rootScope, $state, userAttrs) {
   $scope.selfCreate = function () {
-    $location.path("/self/create");
+    $state.go("create" + $rootScope.getWizardFirstStep());
   };
 
   $rootScope.saml2idps.userAttrs = userAttrs;

http://git-wip-us.apache.org/repos/asf/syncope/blob/19d9e93e/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js
 
b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js
index 629c90d..7718db7 100644
--- 
a/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js
@@ -119,12 +119,12 @@ angular.module("self").controller("UserController", 
['$scope', '$rootScope', '$l
         // initialize plain attributes
         for (var i = 0; i < schemas.plainSchemas.length; i++) {
           var plainSchemaKey = schemas.plainSchemas[i].key;
-          var initialAttributeValues = $rootScope.customForm != null
-                  && $rootScope.customForm["PLAIN"] != null
-                  && $rootScope.customForm["PLAIN"]["attributes"] != null
-                  && 
$rootScope.customForm["PLAIN"]["attributes"][plainSchemaKey] != null
-                  && 
$rootScope.customForm["PLAIN"]["attributes"][plainSchemaKey].defaultValues
-                  ? 
$rootScope.customForm["PLAIN"]["attributes"][plainSchemaKey].defaultValues
+          var initialAttributeValues = $rootScope.customFormAttributes
+                  && $rootScope.customFormAttributes["PLAIN"]
+                  && $rootScope.customFormAttributes["PLAIN"]["attributes"]
+                  && 
$rootScope.customFormAttributes["PLAIN"]["attributes"][plainSchemaKey]
+                  && 
$rootScope.customFormAttributes["PLAIN"]["attributes"][plainSchemaKey].defaultValues
+                  ? 
$rootScope.customFormAttributes["PLAIN"]["attributes"][plainSchemaKey].defaultValues
                   : [];
           if (!$scope.user.plainAttrs[plainSchemaKey]) {
             $scope.user.plainAttrs[plainSchemaKey] = {
@@ -135,7 +135,7 @@ angular.module("self").controller("UserController", 
['$scope', '$rootScope', '$l
             if ($scope.loadFromSAML2AuthSelfReg) {
               $scope.user.plainAttrs[plainSchemaKey].values = 
findLoadedSAML2AttrValue(plainSchemaKey);
             }
-            
+
             if ($scope.loadFromOIDCAuthSelfReg) {
               $scope.user.plainAttrs[plainSchemaKey].values = 
findLoadedOIDCAttrValue(plainSchemaKey);
             }
@@ -193,8 +193,8 @@ angular.module("self").controller("UserController", 
['$scope', '$rootScope', '$l
             if ($scope.loadFromSAML2AuthSelfReg) {
               $scope.user.virAttrs[virSchemaKey].values = 
findLoadedSAML2AttrValue(virSchemaKey);
             }
-            
-             if ($scope.loadFromOIDCAuthSelfReg) {
+
+            if ($scope.loadFromOIDCAuthSelfReg) {
               $scope.user.virAttrs[virSchemaKey].values = 
findLoadedOIDCAttrValue(virSchemaKey);
             }
 
@@ -221,7 +221,7 @@ angular.module("self").controller("UserController", 
['$scope', '$rootScope', '$l
 
         //clean SAML Self Reg user attributes variable
         delete $rootScope.saml2idps.userAttrs;
-        
+
         //clean OIDC Self Reg user attributes variable
         delete $rootScope.oidcops.userAttrs;
       };
@@ -364,7 +364,7 @@ angular.module("self").controller("UserController", 
['$scope', '$rootScope', '$l
             $scope.$emit("groupAdded", 
$scope.user.memberships[index].groupName);
           }
           if ($scope.user.mustChangePassword) {
-            $location.path('/mustchangepassword');
+            $state.go('mustchangepassword');
           } else {
             initProperties();
           }
@@ -469,14 +469,14 @@ angular.module("self").controller("UserController", 
['$scope', '$rootScope', '$l
           if (username.length) {
             $scope.user.username = username[0];
           }
-        } 
-        
+        }
+
         if ($scope.loadFromOIDCAuthSelfReg) {
           var username = findLoadedOIDCAttrValue("username");
           if (username.length) {
             $scope.user.username = username[0];
           }
-        } 
+        }
       } else {
         // read user from syncope core
         readUser();
@@ -490,11 +490,13 @@ angular.module("self").controller("UserController", 
['$scope', '$rootScope', '$l
           console.debug("User " + $scope.user.username + " successfully 
CREATED");
           $rootScope.currentUser = $scope.user.username;
           $rootScope.currentOp = "SUCCESSFULLY_CREATED";
-          $scope.success({successMessage: $filter('translate')(["USER"]).USER
+          $scope.success({
+            successMessage: $filter('translate')(["USER"]).USER
                     + " "
                     + $scope.user.username
                     + " "
-                    + 
$filter('translate')(["SUCCESSFULLY_CREATED"]).SUCCESSFULLY_CREATED});
+                    + 
$filter('translate')(["SUCCESSFULLY_CREATED"]).SUCCESSFULLY_CREATED
+          });
         }, function (response) {
           console.error("Error during user creation: ", response);
           var errorMessage;
@@ -510,11 +512,13 @@ angular.module("self").controller("UserController", 
['$scope', '$rootScope', '$l
           console.debug("User " + $scope.user.username + " successfully 
UPDATED");
           $rootScope.currentUser = $scope.user.username;
           $rootScope.currentOp = "SUCCESSFULLY_UPDATED";
-          $scope.logout({successMessage: $filter('translate')(["USER"]).USER
+          $scope.logout({
+            successMessage: $filter('translate')(["USER"]).USER
                     + " "
                     + $scope.user.username
                     + " "
-                    + 
$filter('translate')(["SUCCESSFULLY_UPDATED"]).SUCCESSFULLY_UPDATED});
+                    + 
$filter('translate')(["SUCCESSFULLY_UPDATED"]).SUCCESSFULLY_UPDATED
+          });
         }, function (response) {
           console.info("Error during user update: ", response);
           var errorMessage;
@@ -652,7 +656,7 @@ angular.module("self").controller("UserController", 
['$scope', '$rootScope', '$l
 
     $scope.redirect = function () {
       $translate.use($scope.languages.selectedLanguage.code);
-      $location.path('/self');
+      $state.go('home');
       $rootScope.endReached = false;
     };
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/19d9e93e/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttribute.js
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttribute.js
 
b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttribute.js
index 8ccaa02..7ed07e1 100644
--- 
a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttribute.js
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttribute.js
@@ -127,11 +127,11 @@ angular.module('self')
               };
 
               $scope.customReadonly = function (schemaKey) {
-                return  $rootScope.customForm != null
-                        && $rootScope.customForm["PLAIN"] != null
-                        && $rootScope.customForm["PLAIN"]["attributes"] != null
-                        && 
$rootScope.customForm["PLAIN"]["attributes"][schemaKey] != null
-                        && 
$rootScope.customForm["PLAIN"]["attributes"][schemaKey].readonly;
+                return  $rootScope.customFormAttributes
+                        && $rootScope.customFormAttributes["PLAIN"]
+                        && 
$rootScope.customFormAttributes["PLAIN"]["attributes"]
+                        && 
$rootScope.customFormAttributes["PLAIN"]["attributes"][schemaKey]
+                        && 
$rootScope.customFormAttributes["PLAIN"]["attributes"][schemaKey].readonly;
               };
 
               $scope.$watch(function () {

http://git-wip-us.apache.org/repos/asf/syncope/blob/19d9e93e/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicTemplateItem.js
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicTemplateItem.js
 
b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicTemplateItem.js
new file mode 100644
index 0000000..f822106
--- /dev/null
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicTemplateItem.js
@@ -0,0 +1,79 @@
+/* 
+ * 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.
+ */
+
+'use strict';
+
+angular.module('self')
+        .directive('dynamicTemplateItem', ['$rootScope', '$compile', '$http', 
'AssetsManager',
+          function ($rootScope, $compile, $http, AssetsManager) {
+
+            var checkGeneralAssets = function () {
+              $rootScope.getDynamicTemplateOtherInfo("generalAssets", "css", 
function (assets) {
+                if (assets && assets.length) {
+                  for (var i = 0; i < assets.length; i++) {
+                    if (!AssetsManager.checkAlreadyLoaded(assets[i], "css")) {
+                      AssetsManager.inject("asset_general_css_" + i, 
assets[i], "css");
+                    }
+                  }
+                }
+              });
+            };
+
+            var linker = function ($scope, $element, $attrs) {
+              // compile template
+              $rootScope.getDynamicTemplateInfo($attrs.type, "templateUrl", 
function (templateUrl) {
+                if (templateUrl) {
+                  $http.get(templateUrl).then(function (response) {
+                    $element.html(response.data).show();
+                    $compile($element.contents())($scope);
+                  }, function (e) {
+                    console.error(e);
+                  });
+                }
+              });
+
+              // inject template assets
+              $rootScope.getDynamicTemplateInfo($attrs.type, "css", function 
(assets) {
+                if (assets && assets.length) {
+                  for (var i = 0; i < assets.length; i++) {
+                    AssetsManager.inject("asset_css_" + i, assets[i], "css");
+                  }
+                }
+              });
+
+              // remove useless assets for little optimization
+              if ($attrs.type !== "login") {
+                $rootScope.getDynamicTemplateInfo("login", "css", function 
(assets) {
+                  for (var i = 0; i < assets.length; i++) {
+                    AssetsManager.remove(assets[i], "css");
+                  }
+                });
+              }
+
+              // check general assets are always loaded (in case page 
refreshing in wizard)
+              checkGeneralAssets();
+            };
+
+            return {
+              restrict: "E",
+              link: linker,
+              replace: true
+            };
+
+          }]);

http://git-wip-us.apache.org/repos/asf/syncope/blob/19d9e93e/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttribute.js
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttribute.js
 
b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttribute.js
index 85e2934..5a1d82c 100644
--- 
a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttribute.js
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttribute.js
@@ -29,12 +29,12 @@ angular.module('self')
               user: "="
             },
             controller: function ($scope, $rootScope) {
-              var customValues = $rootScope.customForm != null
-                      && $rootScope.customForm["VIRTUAL"] != null
-                      && $rootScope.customForm["VIRTUAL"]["attributes"] != null
-                      && 
$rootScope.customForm["VIRTUAL"]["attributes"][$scope.schema.key] != null
-                      && 
$rootScope.customForm["VIRTUAL"]["attributes"][$scope.schema.key].defaultValues
-                      ? 
$rootScope.customForm["VIRTUAL"]["attributes"][$scope.schema.key].defaultValues
+              var customValues = $rootScope.customFormAttributes
+                      && $rootScope.customFormAttributes["VIRTUAL"]
+                      && 
$rootScope.customFormAttributes["VIRTUAL"]["attributes"]
+                      && 
$rootScope.customFormAttributes["VIRTUAL"]["attributes"][$scope.schema.key]
+                      && 
$rootScope.customFormAttributes["VIRTUAL"]["attributes"][$scope.schema.key].defaultValues
+                      ? 
$rootScope.customFormAttributes["VIRTUAL"]["attributes"][$scope.schema.key].defaultValues
                       : [];
 
               $scope.$watch(function () {
@@ -55,11 +55,11 @@ angular.module('self')
               });
 
               $scope.customReadonly = function (schemaKey) {
-                return  $rootScope.customForm != null
-                        && $rootScope.customForm["VIRTUAL"] != null
-                        && $rootScope.customForm["VIRTUAL"]["attributes"] != 
null
-                        && 
$rootScope.customForm["VIRTUAL"]["attributes"][schemaKey] != null
-                        && 
$rootScope.customForm["VIRTUAL"]["attributes"][schemaKey].readonly;
+                return  $rootScope.customFormAttributes
+                        && $rootScope.customFormAttributes["VIRTUAL"]
+                        && 
$rootScope.customFormAttributes["VIRTUAL"]["attributes"]
+                        && 
$rootScope.customFormAttributes["VIRTUAL"]["attributes"][schemaKey]
+                        && 
$rootScope.customFormAttributes["VIRTUAL"]["attributes"][schemaKey].readonly;
               };
             }
             //replace: true

http://git-wip-us.apache.org/repos/asf/syncope/blob/19d9e93e/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttributes.js
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttributes.js
 
b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttributes.js
index 7aeb3d4..25ecafb 100644
--- 
a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttributes.js
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttributes.js
@@ -55,7 +55,8 @@ angular.module('self')
               };
 
               $scope.addVirtualAttributeField = function (virSchemaKey) {
-                
$scope.dynamicForm.virtualAttributeTable[virSchemaKey].fields.push(virSchemaKey 
+ "_" + ($scope.dynamicForm.virtualAttributeTable[virSchemaKey].fields.length));
+                
$scope.dynamicForm.virtualAttributeTable[virSchemaKey].fields.push(virSchemaKey 
+ "_"
+                        + 
($scope.dynamicForm.virtualAttributeTable[virSchemaKey].fields.length));
               };
 
               $scope.removeVirtualAttributeField = function (virSchemaKey, 
index) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/19d9e93e/client/enduser/src/main/resources/META-INF/resources/app/js/directives/fileInput.js
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/fileInput.js
 
b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/fileInput.js
index 50f4a7f..331c0f4 100644
--- 
a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/fileInput.js
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/fileInput.js
@@ -35,8 +35,8 @@ angular.module('self')
                   showClose: true,
                   showRemove: false,
                   fileActionSettings: {'showZoom': false, indicatorNew: '', 
'removeTitle': 'boh'},
-                  removeClass: "btn btn-default",
-                  browseClass: "btn btn-default",
+                  removeClass: "btn btn-secondary btn-default",
+                  browseClass: "btn btn-secondary btn-default",
                   browseLabel: '',
                   dragIcon: '',
                   browseIcon: '',

http://git-wip-us.apache.org/repos/asf/syncope/blob/19d9e93e/client/enduser/src/main/resources/META-INF/resources/app/js/directives/navigationButtons.js
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/navigationButtons.js
 
b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/navigationButtons.js
deleted file mode 100644
index 9fa8292..0000000
--- 
a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/navigationButtons.js
+++ /dev/null
@@ -1,71 +0,0 @@
-/* 
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-'use strict';
-
-angular.module('self')
-        .directive('navigationButtons', ['$state', 'GenericUtil', 
'ValidationExecutor', function ($state, GenericUtil, ValidationExecutor) {
-            return {
-              restrict: 'E',
-              templateUrl: 'views/navigationButtons.html',
-              scope: {
-                base: "@",
-                current: "@"
-              },
-              link: function (scope, element, attrs) {
-                var base = (scope.base && scope.base !== "" ? scope.base + "." 
: "");
-                scope.wizard = scope.$eval(attrs.wizard) || 
scope.$parent.wizard;
-                scope.previous = "none";
-                if (scope.wizard) {
-                  var urls = Object.keys(scope.wizard);
-                  var index = urls.indexOf(scope.current);
-                  scope.previous = (index > 0 ? base + urls[index - 1] : 
scope.previous = "none");
-                  scope.next = (index < urls.length - 1 ? base + urls[index + 
1] : scope.next = "none");
-                }
-              },
-              controller: function ($scope) {
-
-                $scope.validateAndNext = function (event, state) {
-                  //getting the enclosing form in order to access to its name  
              
-                  var currentForm = GenericUtil.getEnclosingForm(event.target);
-                  if (currentForm !== null) {
-                    if (ValidationExecutor.validate(currentForm, 
$scope.$parent)) {
-                      if (state) {
-                        $scope.nextTab(state);
-                      } else if ($scope.wizard) {
-                        $scope.nextTab($scope.next);
-                      }
-                    }
-                  }
-
-                };
-
-                $scope.nextTab = function (state) {
-                  //change route through parent event
-                  $state.go(state);
-                };
-
-                $scope.previousTab = function () {
-                  //change route through parent event
-                  $state.go($scope.previous);
-                };
-              }
-
-            }
-            ;
-          }]);

http://git-wip-us.apache.org/repos/asf/syncope/blob/19d9e93e/client/enduser/src/main/resources/META-INF/resources/app/js/directives/navigationButtonsPartial.js
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/navigationButtonsPartial.js
 
b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/navigationButtonsPartial.js
new file mode 100644
index 0000000..6d0d81d
--- /dev/null
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/navigationButtonsPartial.js
@@ -0,0 +1,71 @@
+/* 
+ * 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.
+ */
+'use strict';
+
+angular.module('self')
+        .directive('navigationButtonsPartial', ['$state', 'GenericUtil', 
'ValidationExecutor',
+          function ($state, GenericUtil, ValidationExecutor) {
+            return {
+
+              restrict: 'E',
+              templateUrl: 'views/navigationButtonsPartial.html',
+              scope: {
+                base: "@",
+                current: "@"
+              },
+              link: function (scope, element, attrs) {
+                var base = (scope.base && scope.base !== "" ? scope.base + "." 
: "");
+                scope.wizard = scope.$eval(attrs.wizard) || 
scope.$parent.wizard || scope.$parent.$parent.wizard;
+                scope.previous = "none";
+                if (scope.wizard) {
+                  var urls = Object.keys(scope.wizard);
+                  var index = urls.indexOf(scope.current);
+                  scope.previous = (index > 0 ? base + urls[index - 1] : 
scope.previous = "none");
+                  scope.next = (index < urls.length - 1 ? base + urls[index + 
1] : scope.next = "none");
+                }
+              },
+              controller: function ($scope) {
+                $scope.validateAndNext = function (event, state) {
+                  //getting the enclosing form in order to access to its name  
              
+                  var currentForm = GenericUtil.getEnclosingForm(event.target);
+                  if (currentForm !== null) {
+                    if (ValidationExecutor.validate(currentForm, 
$scope.$parent)) {
+                      if (state) {
+                        $scope.nextTab(state);
+                      } else if ($scope.wizard) {
+                        $scope.nextTab($scope.next);
+                      }
+                    }
+                  }
+
+                };
+
+                $scope.nextTab = function (state) {
+                  //change route through parent event
+                  $state.go(state);
+                };
+
+                $scope.previousTab = function () {
+                  //change route through parent event
+                  $state.go($scope.previous);
+                };
+              }
+
+            };
+          }]);

http://git-wip-us.apache.org/repos/asf/syncope/blob/19d9e93e/client/enduser/src/main/resources/META-INF/resources/app/js/services/dynamicTemplateService.js
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/js/services/dynamicTemplateService.js
 
b/client/enduser/src/main/resources/META-INF/resources/app/js/services/dynamicTemplateService.js
new file mode 100644
index 0000000..b3348e0
--- /dev/null
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/js/services/dynamicTemplateService.js
@@ -0,0 +1,67 @@
+/* 
+ * 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.
+ */
+
+'use strict';
+
+angular.module('SyncopeEnduserApp')
+        .factory('DynamicTemplateService', ['$q', '$http', 'AssetsManager',
+          function ($q, $http, AssetsManager) {
+
+            var dynTemplateService = {};
+            var dynTemplateUrl = '../api/dynamicTemplate';
+
+            var error = function (response) {
+              console.error("Something went wrong while retrieving dynamic 
template resource", response);
+              return $q.reject(response.data || response.statusText);
+            };
+
+            var loadAssets = function (category, assets, types) {
+              var allPromises = types.reduce((acc, type) => {
+                if (assets[category][type]) {
+                  var currentAssetsPromises =
+                          assets[category][type].map((url, index) => 
AssetsManager.
+                            inject("elem_" + index, url, type));
+                  return acc.concat(currentAssetsPromises);
+                }
+              }, []);
+
+              return $q.all(allPromises);
+            };
+
+            dynTemplateService.getContent = function () {
+              return $http
+                      .get(dynTemplateUrl)
+                      .then(function (response) {
+                        return response.data;
+                      }, error);
+            };
+
+            dynTemplateService.getGeneralAssetsContent = function (types) {
+              return $http
+                      .get(dynTemplateUrl)
+                      .then(function (response) {
+                        return loadAssets("generalAssets", response.data, 
types);
+                      }, error);
+            };
+
+            return dynTemplateService;
+
+          }]);
+
+

http://git-wip-us.apache.org/repos/asf/syncope/blob/19d9e93e/client/enduser/src/main/resources/META-INF/resources/app/js/util/assetsManager.js
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/js/util/assetsManager.js
 
b/client/enduser/src/main/resources/META-INF/resources/app/js/util/assetsManager.js
new file mode 100644
index 0000000..48ec119
--- /dev/null
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/js/util/assetsManager.js
@@ -0,0 +1,105 @@
+/* 
+ * 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.
+ */
+
+'use strict';
+
+angular.module('SyncopeEnduserApp')
+        .factory("AssetsManager", ['$q', function ($q) {
+            var assetsManager = {};
+
+            var createLink = function (id, url, deferred) {
+              if (!angular.element('link#' + id).length && 
!angular.element('link[href="' + url + '"').length) {
+                var link = document.createElement('link');
+                link.rel = 'stylesheet';
+                link.href = url;
+                link.onload = deferred.resolve;
+                link.onerror = deferred.reject;
+                angular.element('head').append(link);
+              }
+            };
+
+            var createScript = function (id, url, deferred) {
+              if (!angular.element('script#' + id).length && 
!angular.element('script[src="' + url + '"').length) {
+                var script = document.createElement('script');
+                script.src = url;
+                script.onload = deferred.resolve;
+                script.onerror = deferred.reject;
+                angular.element('body').append(script);
+              }
+            };
+
+            assetsManager.checkAlreadyLoaded = function (url, type) {
+              var elems = (type === 'css') ? document.styleSheets : ((type === 
'js') ? document.scripts : '');
+              var attr = (type === 'js') ? 'src' : ((type === 'css') ? 'href' 
: 'none');
+              for (var i in elems) {
+                var attrUrl = elems[i][attr] || "";
+                var assetName = attrUrl.split("/").slice(-1).join();
+                if (attrUrl !== ""
+                        && (assetName === url.split("/").slice(-1).join() || 
assetName === url)) {
+                  return true;
+                }
+              }
+              return false;
+            };
+
+            var checkLoaded = function (url, deferred, tries, type) {
+              if (assetsManager.checkAlreadyLoaded(url, type)) {
+                deferred.resolve();
+                return;
+              }
+              tries++;
+              setTimeout(function () {
+                checkLoaded(url, deferred, tries, type);
+              }, 50);
+            };
+
+            var removeLoaded = function (url, type) {
+              var tag = (type === 'js') ? 'script' : ((type === 'css') ? 
'link' : '');
+              if (assetsManager.checkAlreadyLoaded(url, type)) {
+                $(tag + '[href~="' + url + '"]').remove();
+              }
+            };
+
+            assetsManager.inject = function (id, url, type) {
+              var tries = 0,
+                      deferred = $q.defer();
+
+              switch (type) {
+                case 'js':
+                  createScript(id, url, deferred);
+                  break;
+
+                case 'css':
+                  createLink(id, url, deferred);
+                  break;
+
+                default:
+                  break;
+              }
+              checkLoaded(url, deferred, tries, type);
+
+              return deferred.promise;
+            };
+
+            assetsManager.remove = function (url, type) {
+              removeLoaded(url, type);
+            };
+
+            return assetsManager;
+          }]);

http://git-wip-us.apache.org/repos/asf/syncope/blob/19d9e93e/client/enduser/src/main/resources/META-INF/resources/app/views/captcha.html
----------------------------------------------------------------------
diff --git 
a/client/enduser/src/main/resources/META-INF/resources/app/views/captcha.html 
b/client/enduser/src/main/resources/META-INF/resources/app/views/captcha.html
index 518ab35..88dfae1 100644
--- 
a/client/enduser/src/main/resources/META-INF/resources/app/views/captcha.html
+++ 
b/client/enduser/src/main/resources/META-INF/resources/app/views/captcha.html
@@ -21,16 +21,16 @@ under the License.
     <div class="container-fluid" style="text-align: center">
       <div id="captchainput" >
         <img id="captchaImg" alt="captcha" ng-src="{{captchaUrl}}'"/>
-        <div id="captchaButtons" style="margin-top: 5%; margin-bottom: 10px">
-          <button id="refresh" type="button" class="btn btn-default btn-xs 
glyphicon glyphicon-refresh" 
+        <div id="captchaButtons">
+          <button id="refresh" type="button" class="btn btn-default btn-xs fa 
fa-refresh" 
                   ng-click="refreshCaptcha()" title="Refresh Captcha"></button>
-          <a id="refresh" class="btn btn-default btn-xs glyphicon 
glyphicon-question-sign" title="What is?"
-             href="https://it.wikipedia.org/wiki/CAPTCHA"; target="_blank"/>
+          <button id="questionCaptcha" class="btn btn-default btn-xs fa 
fa-question-circle" title="What is?"
+                  
onclick="window.open('https://it.wikipedia.org/wiki/CAPTCHA')"></button>
         </div>
         <input class="form-control" style="margin:auto; max-width: 260px" 
type="text" ng-model="input.value"/>
-      </div>
-      <div>
-        <span class="help-block">{{'CAPTCHA' | translate}}</span>
+        <div>
+          <span class="help-block">{{'CAPTCHA'| translate}}</span>
+        </div>
       </div>
     </div>
   </nav>

Reply via email to