[PORTAL-7] Rebase

This rebasing includes common libraries and common overlays projects
abstraction of components

Change-Id: I9a24a338665c7cd058978e8636bc412d9e2fdce8
Signed-off-by: st782s <statta@research.att.com>
diff --git a/ecomp-portal-FE-common/client/app/directives/auto-focus/auto-focus.directive.js b/ecomp-portal-FE-common/client/app/directives/auto-focus/auto-focus.directive.js
new file mode 100644
index 0000000..7396411
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/directives/auto-focus/auto-focus.directive.js
@@ -0,0 +1,46 @@
+/*-
+ * ================================================================================
+ * eCOMP Portal
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property
+ * ================================================================================
+ * Licensed 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.
+ * ================================================================================
+ */
+
+
+
+//angular.module('ecompApp')
+//    .directive('autoFocus', function () {
+//        return {
+//            restrict: 'A',
+//            scope: {},
+//            link: function (scope, element) {
+//                element[0].focus();
+//            }
+//        };
+//    });
+
+/* istanbul ignore next */
+(function(){
+    class AutoFocusDirective{
+        constructor(){
+            this.restrict = 'A';
+            this.link = this._link.bind(this);
+        }
+        _link(scope, element){
+            element[0].focus();
+        }
+    }
+    angular.module('ecompApp').directive('autoFocus', () => new AutoFocusDirective());
+})();
diff --git a/ecomp-portal-FE-common/client/app/directives/b2b-leftnav-ext/b2b-leftnav-ext.directive.js b/ecomp-portal-FE-common/client/app/directives/b2b-leftnav-ext/b2b-leftnav-ext.directive.js
new file mode 100644
index 0000000..7284e4d
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/directives/b2b-leftnav-ext/b2b-leftnav-ext.directive.js
@@ -0,0 +1,68 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+(function () {

+   	/*

+   	 * Custom version of b2b-left-navigation directive:

+   	 * 1. Make parent menu a link if no child menus.

+   	 * 2. Add unique IDs to all items.

+   	 * 3. Hide icon if no child menus.

+   	 * 4. Add arrow toggle button.

+   	 * 5. Adjust the page on collapse/expand.

+   	 */

+    class B2BLeftMenu {

+        constructor($rootScope) {

+            this.templateUrl = 'app/directives/b2b-leftnav-ext/b2b-leftnav-ext.tpl.html';

+            this.restrict = 'EA';

+            this.$rootScope = $rootScope;

+            this.link = this._link.bind(this);

+            this.scope = {

+            	menuData: '='

+            }

+        }

+        _link(scope) {

+        	scope.idx = -1;

+            scope.itemIdx = -1;

+            scope.navIdx = -1;

+            scope.toggleNav = function (val,link) {

+                if (val === scope.idx) {

+                    scope.idx = -1;

+                    return;

+                }

+                scope.idx = val;

+            };

+            /*New function for ECOMP sdk*/

+            scope.toggleDrawer = function(showmenu){

+            	scope.idx=-1; /*hide the sunmenus*/

+            	if(showmenu){

+        			document.getElementById('page-content').style.paddingLeft = "50px";

+        		}

+        		else

+        			document.getElementById('page-content').style.paddingLeft = "230px";           	

+            };

+            scope.liveLink = function (evt, val1, val2) {

+                scope.itemIdx = val1;

+                scope.navIdx = val2;

+                evt.stopPropagation();

+            };

+        }

+    }

+    angular.module('ecompApp').directive('leftMenuEcomp', ($rootScope) => new B2BLeftMenu($rootScope));

+})();

+

diff --git a/ecomp-portal-FE-common/client/app/directives/b2b-leftnav-ext/b2b-leftnav-ext.less b/ecomp-portal-FE-common/client/app/directives/b2b-leftnav-ext/b2b-leftnav-ext.less
new file mode 100644
index 0000000..91a10aa
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/directives/b2b-leftnav-ext/b2b-leftnav-ext.less
@@ -0,0 +1,4 @@
+.b2b-nav-menu .b2b-subnav-container li{

+	background-color:white;

+	z-index:10;

+}
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/directives/b2b-leftnav-ext/b2b-leftnav-ext.tpl.html b/ecomp-portal-FE-common/client/app/directives/b2b-leftnav-ext/b2b-leftnav-ext.tpl.html
new file mode 100644
index 0000000..240c8c9
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/directives/b2b-leftnav-ext/b2b-leftnav-ext.tpl.html
@@ -0,0 +1,62 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="b2b-nav-menu" id="left-menu-main-div" ng-init="(showmenu = true)" ng-class="{false: 'left-menu-collapsed'}[showmenu]">

+	<div class="b2b-subnav-container" id="left-menu-subnav-container">

+		<ul class="b2b-subnav-content" id="left-menu-subnav-content">

+			<li id="left-menu-subnav-content-li">

+				<div ng-class="{true: 'leftmenu-arrow-expand', false: 'leftmenu-arrow-collapse'}[showmenu]"	id="left-menu-arrow-toggle-div">

+					<a ng-click="toggleDrawer(showmenu);(showmenu = !showmenu) " class="text-right" id="left-menu-arrow-toggle-anchor"> 

+						<i ng-class="{true: 'icon-controls-left', false: 'icon-controls-right'}[showmenu]" id="left-menuf-arrow-toggle-icon">&nbsp;</i>

+					</a>

+				</div>

+			</li>

+			<li ng-repeat="menu in menuData" ui-sref="{{menu.state}}" id="left-menu-li-{{menu.name.split(' ').join('-')}}">

+				<span ng-class="{true: 'menu-icon', false: 'menu-icon-collapse'}[showmenu]"	

+					id="left-menu-span-{{menu.name.split(' ').join('-')}}"> 

+					<span class="{{menu.imageSrc}}" id="icon-image-{{menu.name.split(' ').join('-')}}"></span>

+				</span> 

+				<a ng-class="{expand: isOpen($index)}" ng-if="showmenu" title="{{menu.name}}"

+					aria-label="{{menu.name}}" aria-expanded="{{(idx==$index)?true:false;}}"

+					href="javascript:void(0);" id="parent-item-{{menu.name.split(' ').join('-')}}">

+					{{menu.name}} 

+					<i aria-hidden="true" ng-if="(menu.menuItems.length > 0)"

+						class="b2b-icon-primary-plus-minus"

+						ng-class="idx==$index ? 'icon-primary-expanded' : 'icon-primary-collapsed'"></i>

+				</a>

+				<div role="region" aria-hidden="{{(isOpen($index))?false:true;}}" 

+					id="left-menu-child-div-{{menu.name.split(' ').join('-')}}">

+					<ul ng-class="{expand: idx==$index}"

+						id="left-menu-child-ul-{{menu.name.split(' ').join('-')}}">

+						<li ng-repeat="menuItem in menu.menuItems"

+							ng-click="liveLink($event, $index, $parent.$index)"

+							id="left-menu-child-li-{{menuItem.name.split(' ').join('-')}}-{{menu.name.split(' ').join('-')}}">

+							<a ng-class="{active: itemIdx==$index && navIdx==$parent.$index}"

+								aria-hidden="{{!(idx==$parent.$index)}}" aria-label="{{menuItem.name}}" 

+								title="{{menuItem.name}}" href="{{menuItem.href}}"

+								tabindex="{{(idx==$parent.$index)?0:-1;}}"

+								id="child-item-{{menuItem.name.split(' ').join('-')}}">{{menuItem.name}}

+							</a>

+						</li>

+					</ul>

+				</div>

+			</li>

+		</ul>

+	</div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/directives/file-upload/file-upload.directive.js b/ecomp-portal-FE-common/client/app/directives/file-upload/file-upload.directive.js
new file mode 100644
index 0000000..bfedcd7
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/directives/file-upload/file-upload.directive.js
@@ -0,0 +1,39 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+/**

+ * Created by Rui Lu on 12/12/16.

+ */

+'use strict';

+

+angular.module('ecompApp').directive('fileModel', [ '$parse', function($parse) {

+	return {

+		restrict : 'A',

+		link : function(scope, element, attrs) {

+			var model = $parse(attrs.fileModel);

+			var modelSetter = model.assign;

+

+			element.bind('change', function() {

+				scope.$apply(function() {

+					modelSetter(scope, element[0].files[0]);

+				});

+			});

+		}

+	};

+} ]);

diff --git a/ecomp-portal-FE-common/client/app/directives/image-upload/image-upload.directive.js b/ecomp-portal-FE-common/client/app/directives/image-upload/image-upload.directive.js
new file mode 100644
index 0000000..e031ed0
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/directives/image-upload/image-upload.directive.js
@@ -0,0 +1,238 @@
+/*-
+ * ================================================================================
+ * eCOMP Portal
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property
+ * ================================================================================
+ * Licensed 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('ecompApp').directive('imageUpload', function factory($q) {
+    var imageMimeRgx = /^image\/[a-zA-Z0-9]*$/;
+
+    var URL = window.URL || window.webkitURL;
+
+    var getResizeArea = function () {
+        var resizeAreaId = 'fileupload-resize-area';
+
+        var resizeArea = document.getElementById(resizeAreaId);
+
+        if (!resizeArea) {
+            resizeArea = document.createElement('canvas');
+            resizeArea.id = resizeAreaId;
+            resizeArea.style.visibility = 'hidden';
+            document.body.appendChild(resizeArea);
+        }
+
+        return resizeArea;
+    };
+
+    var resizeImage = function (origImage, options) {
+        var maxHeight = options.resizeMaxHeight || 300;
+        var maxWidth = options.resizeMaxWidth || 250;
+        var quality = options.resizeQuality || 0.7;
+        var type = options.resizeType || 'image/jpg';
+
+        var canvas = getResizeArea();
+
+        var height = origImage.height;
+        var width = origImage.width;
+
+        //image redraw starting points
+        var x0, y0;
+
+        // calculate the width and height, constraining the proportions
+        if (width > height) {
+            if (width > maxWidth) {
+                height = Math.round(height *= maxWidth / width);
+                width = maxWidth;
+
+                x0 = 0;
+                y0 = Math.round((maxHeight - height)/2);
+            }else{
+                maxHeight = height;
+                maxWidth = width;
+                x0 = 0;
+                y0 = 0;
+            }
+        } else {
+            if (height > maxHeight) {
+                width = Math.round(width *= maxHeight / height);
+                height = maxHeight;
+
+                x0 = Math.round((maxWidth - width)/2);
+                y0 = 0;
+            }else{
+                maxHeight = height;
+                maxWidth = width;
+                x0 = 0;
+                y0 = 0;
+            }
+        }
+
+        canvas.width = maxWidth;
+        canvas.height = maxHeight;
+
+        //draw image on canvas
+        var ctx = canvas.getContext('2d');
+
+        //set background color
+        if(options.backgroundColor){
+            ctx.fillStyle = options.backgroundColor;
+            ctx.fillRect(0,0,maxWidth,maxHeight);
+        }
+
+
+        ctx.drawImage(origImage, x0, y0, width, height);
+
+        // get the data from canvas as 70% jpg (or specified type).
+        return canvas.toDataURL(type, quality);
+    };
+
+    var createImage = function(url, callback) {
+        var image = new Image();
+        image.onload = function() {
+            callback(image);
+        };
+        image.src = url;
+    };
+
+    var fileToDataURL = function (file) {
+        var deferred = $q.defer();
+        var reader = new FileReader();
+        reader.onload = function (e) {
+            deferred.resolve(e.target.result);
+        };
+        reader.readAsDataURL(file);
+        return deferred.promise;
+    };
+
+    /**
+     * Image Upload directive
+     * ************************
+     * image-upload: image object , Mandatory
+     * image-upload-resize-max-height: <Number>, Optional (default 300), resize maximum height
+     * image-upload-resize-max-width: <Number>, Optional (default 270), resize maximum width
+     * image-upload-resize-quality: <Number>, Optional, value can be 0.0-1.0 (default 0.7), resize compression quality
+     * image-upload-resize-type: <String>, Optional, (default 'image/jpg'), image mime type
+     * image-upload-api: <Object>, Optional, pass an api  reference object and get api.clearFile() function - clear input field and reset form validation
+     * image-upload-background-color: <String> color name, Optional, background color fill if image doesn't fit the whole desired resize area.
+     *
+     * in addition, if <input> element is part of <form>, in order to get form validation please  set its 'name' attribute and 'ng-model'  to get the following field validation errors:
+     * - 'mimeType' : in case the uploaded image is not an image
+     * - 'imageSize' : in case the image size (in bytes) is too large
+     */
+
+    return {
+        restrict: 'A',
+        require: '^form',
+        scope: {
+            image: '=imageUpload',
+            resizeMaxHeight: '@?imageUploadResizeMaxHeight',
+            resizeMaxWidth: '@?imageUploadResizeMaxWidth',
+            resizeQuality: '@?imageUploadResizeQuality',
+            resizeType: '@?imageUploadResizeType',
+            imageApi: '=?imageUploadApi',
+            backgroundColor: '@?imageUploadBackgroundColor'
+        },
+        compile: function compile(tElement, tAttrs, transclude) {
+            return function postLink(scope, iElement, iAttrs, formCtrl) {
+                var doResizing = function(imageResult, callback) {
+                    createImage(imageResult.url, function(image) {
+                        var dataURL = resizeImage(image, scope);
+                        imageResult.resized = {
+                            dataURL: dataURL,
+                            type: dataURL.match(/:(.+\/.+);/)[1]
+                        };
+                        callback(imageResult);
+                    });
+                };
+
+                var applyScope = function(imageResult) {
+                    scope.$apply(function() {
+                        //console.log(imageResult);
+                        if(iAttrs.multiple)
+                            scope.image.push(imageResult);
+                        else
+                            scope.image = imageResult;
+                    });
+                };
+
+                iElement.bind('change', function (evt) {
+                    //when multiple always return an array of images
+                    if(iAttrs.multiple)
+                        scope.image = [];
+
+                    var files = evt.target.files;
+                    for(var i = 0; i < files.length; i++) {
+                        setInputValidity(files[i]);
+
+                        //create a result object for each file in files
+                        var imageResult = {
+                            file: files[i],
+                            url: URL.createObjectURL(files[i])
+                        };
+
+                        fileToDataURL(files[i]).then(function (dataURL) {
+                            imageResult.dataURL = dataURL;
+                        });
+
+                        if(scope.resizeMaxHeight || scope.resizeMaxWidth) { //resize image
+                            doResizing(imageResult, function(imageResult) {
+                                applyScope(imageResult);
+                            });
+                        }
+                        else { //no resizing
+                            applyScope(imageResult);
+                        }
+                    }
+                });
+
+                //API for otter actions
+                scope.imageApi = scope.imageApi || {};
+                scope.imageApi.clearFile = () => {
+                    iElement[0].value = '';
+                    setInputValidity();
+                };
+
+
+                let setInputValidity = file => {
+                    //if form validation supported
+
+                    if(formCtrl && iAttrs.name && formCtrl[iAttrs.name]){
+                        formCtrl[iAttrs.name].$setDirty();
+                        if(file && file.type && !imageMimeRgx.test(file.type)){
+                            //set form invalid
+                            formCtrl[iAttrs.name].$setValidity('mimeType', false);
+                            applyScope();
+                            return;
+                        }
+                        if(file && file.size && file.size > 1000000){
+                            //set form invalid
+                            formCtrl[iAttrs.name].$setValidity('imageSize', false);
+                            applyScope();
+                            return;
+                        }
+                        //set valid
+                        formCtrl[iAttrs.name].$setValidity('mimeType', true);
+                        formCtrl[iAttrs.name].$setValidity('imageSize', true);
+                    }
+
+                }
+            }
+        }
+    }
+});
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/directives/left-menu/left-menu.directive.js b/ecomp-portal-FE-common/client/app/directives/left-menu/left-menu.directive.js
new file mode 100644
index 0000000..98979e2
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/directives/left-menu/left-menu.directive.js
@@ -0,0 +1,90 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+/**

+ * Created by nnaffar on 1/28/16.

+ */

+(function () {

+    class LeftMenu {

+        constructor($rootScope, userbarUpdateService,notificationService,auditLogService) {

+            this.templateUrl = 'app/directives/left-menu/left-menu.tpl.html';

+            this.restrict = 'AE';

+            this.$rootScope = $rootScope;

+            this.userbarUpdateService = userbarUpdateService;

+            this.notificationService = notificationService;

+            this.auditLogService= auditLogService;

+            this.link = this._link.bind(this);

+            this.scope = {

+                sidebarModel: '='

+            }

+        }

+        _link(scope) {

+            let init = () => {

+                scope.isOpen = true;

+            };

+

+            init();

+

+            scope.refreshOnlineUsers = () => {

+            	this.userbarUpdateService.setRefreshCount(this.userbarUpdateService.maxCount);

+            };

+            

+            scope.refreshNotification = () => {

+            	this.notificationService.setRefreshCount(this.notificationService.maxCount);

+            };

+

+            scope.toggleSidebar = () => {

+                scope.isOpen = !scope.isOpen;

+                if(scope.isOpen==true)

+                	setContentPos(1);

+                else

+                	setContentPos(0);

+            };

+            scope.auditLog =(name) => {         

+        		this.auditLogService.storeAudit(1,'leftMenu',name);

+        	};

+

+

+            scope.isBrowserInternetExplorer = false;

+            scope.browserName = bowser.name;

+

+            if (bowser.msie || bowser.msedge) {

+                scope.isBrowserInternetExplorer = true;

+            } else {

+                scope.isBrowserInternetExplorer = false;

+            }

+

+

+            this.$rootScope.$on('$stateChangeStart', () => {

+                scope.isOpen = true;

+            });

+        }

+    }

+    angular.module('ecompApp').directive('leftMenu', ($rootScope,userbarUpdateService,notificationService,auditLogService) => new LeftMenu($rootScope,userbarUpdateService,notificationService,auditLogService));

+})();

+

+function setContentPos(open) {

+	// console.log("*******************************************");

+	if(open==1){

+		$("#page-content" ).css( "padding-left", "210px" );

+	}else{

+		$("#page-content" ).css( "padding-left", "50px" );

+	}

+

+}

diff --git a/ecomp-portal-FE-common/client/app/directives/left-menu/left-menu.less b/ecomp-portal-FE-common/client/app/directives/left-menu/left-menu.less
new file mode 100644
index 0000000..6c2043d
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/directives/left-menu/left-menu.less
@@ -0,0 +1,177 @@
+/**
+ * Created by nnaffar on 1/28/16.
+ */
+@sidebar-width: 200px;
+@transition-duration: 0.25s;
+@overlayer-opacity: 0.65;
+
+.left-menu-close-button {
+    padding-right: 10px;
+  font-size: 1.25em;
+  line-height: 18px;
+  position: absolute;
+  cursor: pointer;
+  vertical-align: middle;
+  top: @second-level-top;
+  left: 160px;
+  //-webkit-font-smoothing: antialiased;
+  height: 25px;
+  z-index: 101;
+  //box-shadow: 0 4px 5px rgba(0, 0, 0, .2);
+}
+
+@-moz-document url-prefix() {
+    .close-button-arrow {
+        top: 115px; // bug in Firefox
+    }
+}
+.close-button-arrow{
+    left: 180px;
+}
+
+@-moz-document url-prefix() {
+    .open-button-arrow{
+        top: 115px; // bug in Firefox
+    }
+}
+
+.close-button-arrow{
+    left: 180px;
+}
+
+.open-button-arrow{
+    left: 0;
+    padding-left: 13px;
+}
+
+.ecomp-sidebar-container {
+    position: absolute;
+    display: block;
+    left: 0;
+    //width: inherit;
+    z-index: 100;
+    //transition: left @transition-duration;
+    margin-top: -15px;
+
+  .ecomp-sidebar-main {
+      left: 0;
+    //background-color: ;
+    position: absolute;
+    margin-top: 125px;
+    width: @sidebar-width;
+    height: 100vh;
+    // .bg_portalWhite;//white for 1610
+    .bg_portalGray;  // gray for 1702
+    //box-shadow: 0 4px 5px rgba(0, 0, 0, .2);
+
+    //padding-right: 10px;
+    //padding-left: 10px;
+
+    .accordion-container{
+      margin-top: 45px;
+    }
+    .portal-accordion-font{
+      font-size: .875rem;
+      //color: #666;
+      display: inline-block;
+    }
+
+    .portal-accordion-active{
+        color: @funcBlueLink !important;
+        .bg_portalWhite;
+        padding-left: 10px;
+    }
+
+    .sub-item{
+      .portal-accordion-font;
+      cursor: pointer;
+      height: 37px;
+      line-height: 37px;
+      padding-left: 20px;
+      padding-bottom: 10px;
+      vertical-align: middle;
+      width: 100%;
+    }
+    .sub-item:hover{
+      .portal-accordion-active;
+    }
+
+    .parent-item{
+        .portal-accordion-font;
+        border-bottom: 1px solid @portalLGray;
+        cursor: pointer;
+        height: 37px;
+        line-height: 37px;
+        padding-bottom: 10px;
+        vertical-align: middle;
+        width: 100%;
+        padding-left: 10px;
+    }
+
+    .parent-item:hover{
+      .portal-accordion-active;
+    }
+
+  }
+}
+
+.open-sidebar {
+  width: @sidebar-width;
+}
+
+.close-sidebar {
+	display: none;
+    width: 35px !important;
+    span {color: transparent}
+}
+
+.content-overlayed {
+  position: fixed;
+  top: 110px;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  background: none repeat scroll 0 0 @funcBkgGray;
+  z-index: 9999;
+}
+.fade-animation{
+  opacity: @overlayer-opacity;
+  transition: opacity @transition-duration ease-in-out;
+}
+.fade-animation.ng-hide {
+  opacity:0;
+  transition: opacity @transition-duration ease-in-out;
+}
+
+.left-menu-collapsed {
+	width: 22px !important;
+} 
+
+.leftmenu-arrow-expand{
+	margin-left:200px
+}
+.leftmenu-arrow-collapse{
+	margin-left:0px;
+}
+
+.left-menu-div{
+	margin-top:110px;
+	float: left;
+}
+.b2b-subnav-content > li > a {
+	display: inline-block;
+}
+
+.leftment-items{
+	margin-left:40px;
+}
+
+.menu-icon {
+	line-height: 40px;
+}
+
+.menu-icon-collapse {
+	line-height: 40px;
+	margin-top:10px;
+	margin-bottom:10px;
+}
diff --git a/ecomp-portal-FE-common/client/app/directives/left-menu/left-menu.tpl.html b/ecomp-portal-FE-common/client/app/directives/left-menu/left-menu.tpl.html
new file mode 100644
index 0000000..4478bc8
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/directives/left-menu/left-menu.tpl.html
@@ -0,0 +1,22 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+ <div class="left-menu-div">

+ 	<left-menu-ecomp menu-data="sidebarModel.navItems"></left-menu-ecomp>

+ </div> 

diff --git a/ecomp-portal-FE-common/client/app/directives/multiple-select/multiple-select.directive.js b/ecomp-portal-FE-common/client/app/directives/multiple-select/multiple-select.directive.js
new file mode 100644
index 0000000..fb56d43
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/directives/multiple-select/multiple-select.directive.js
@@ -0,0 +1,113 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+/**

+ * Created by nnaffar on 12/21/15.+

+ */

+angular.module('ecompApp')

+    .directive('multipleSelect', function ($window) {

+        return {

+            restrict: 'E',

+            templateUrl: 'app/directives/multiple-select/multiple-select.tpl.html',

+            scope: {

+                onChange: '&',

+                nameAttr: '@',

+                valueAttr: '@',

+                ngModel: '=',

+                placeholder: '@',

+                uniqueData: '@?',

+                onDropdownClose: '&?'

+            },

+            link: function(scope, elm, attrs){

+                scope.isExpanded = false;

+

+                scope.isDisabled = !scope.ngModel || !scope.ngModel.length;

+                scope.$watch('ngModel', function(newVal){

+                    scope.isDisabled = !newVal || !newVal.length;

+                });

+

+

+                let startListening = () => {

+                    console.log('listening on $window!');

+                    angular.element($window).on('click', function () {

+                        stopListening();

+                    });

+

+                    angular.element('multiple-select').on('click', function(e) {

+                        if($(e.target).closest('multiple-select')[0].attributes['unique-data'].value === attrs.uniqueData){

+                            console.log('ignored that..:', attrs.uniqueData);

+                            e.stopPropagation();

+                        }else{

+                            console.log('shouldnt ignore, close expanded!:', attrs.uniqueData);

+                            scope.isExpanded = false;

+                            scope.$applyAsync();

+                        }

+                    });

+                };

+

+                let stopListening = function() {

+                    if(scope.onDropdownClose){

+                        scope.onDropdownClose();

+                    }

+                    scope.isExpanded = false;

+                    scope.$applyAsync();

+                    console.log('stop listening on $window and multiple-element!');

+                    angular.element($window).off('click');

+                    angular.element('multiple-select').off('click');

+                };

+

+                scope.showCheckboxes = function(){

+                    scope.isExpanded = !scope.isExpanded;

+                    if(scope.isExpanded){

+                        startListening();

+                    }else{

+                        stopListening();

+                        if(scope.onDropdownClose){

+                            scope.onDropdownClose();

+                        }

+                    }

+                };

+

+                scope.onCheckboxClicked = function() {

+                    console.log('checkbox clicked; unique data: ',attrs.uniqueData);

+                    if(scope.onChange) {

+                        scope.onChange();

+                    }

+                }

+

+                scope.getTitle = function(){

+                    var disp = '';

+                    if(!scope.ngModel || !scope.ngModel.length) {

+                        return disp;

+                    }

+                    scope.ngModel.forEach(function(item){

+                        if(item[scope.valueAttr]){

+                            disp+=item[scope.nameAttr] + ',';

+                        }

+                    });

+                    if(disp!==''){

+                        disp = disp.slice(0,disp.length-1);

+                    }else{

+                        disp = scope.placeholder;

+                    }

+                    return disp;

+                };

+            }

+        };

+    });

diff --git a/ecomp-portal-FE-common/client/app/directives/multiple-select/multiple-select.less b/ecomp-portal-FE-common/client/app/directives/multiple-select/multiple-select.less
new file mode 100644
index 0000000..7035a32
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/directives/multiple-select/multiple-select.less
@@ -0,0 +1,62 @@
+.multiple-select{
+  position: relative;
+  width: 100%;
+
+  .selectBox{
+    cursor: pointer;
+    position: relative;
+    border: 1px solid @portalDGray;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    line-height: 30px;
+    height: 30px;
+    padding-left: 10px;
+    padding-right: 10px;
+
+    &.open::after{
+      content: '';
+      .arrow_up;
+      display: block;
+      position: absolute;
+      top: 12px;
+      right: 4px;
+    }
+    &.closed::after{
+      content: '';
+      .arrow_down;
+      display: block;
+      position: absolute;
+      top: 12px;
+      right: 4px;
+    }
+    &.disabled{
+      cursor: default;
+      background: @portalLGray;
+    }
+
+  }
+
+  .checkboxes{
+    z-index: 99999;
+    padding-left: 8px;
+    padding-right: 8px;
+    position: absolute;
+    top: 30px;
+    width: 100%;
+    background: @portalWhite;
+
+    display: block;
+    border: 1px @portalLGray solid;
+
+    label{
+      cursor: pointer;
+      display: block;
+    }
+    input{
+      cursor: pointer;
+    }
+
+
+  }
+}
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/directives/multiple-select/multiple-select.tpl.html b/ecomp-portal-FE-common/client/app/directives/multiple-select/multiple-select.tpl.html
new file mode 100644
index 0000000..3896bf2
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/directives/multiple-select/multiple-select.tpl.html
@@ -0,0 +1,36 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div id="app-multilple-select" class="multiple-select">

+    <div class="selectBox"

+    	 id="app-select-{{getTitle()}}"

+         ng-click="isDisabled || showCheckboxes()"

+         title="{{getTitle()}}"

+         ng-bind="getTitle()"

+         ng-class="{open: isExpanded, closed: !isExpanded, disabled: isDisabled}"></div>

+

+    <div class="checkboxes" ng-show="isExpanded">

+        <div ng-repeat="item in ngModel" id="{{item[nameAttr]}}-checkbox-div">

+            <label id="{{item[nameAttr]}}-checkbox-label">

+                <input type="checkbox" id="{{item[nameAttr]}}-checkbox" ng-model="item[valueAttr]" ng-change="onCheckboxClicked()">

+                {{item[nameAttr]}}

+            </label>

+        </div>

+    </div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/directives/multiple-select/multiple-select2.directive.js b/ecomp-portal-FE-common/client/app/directives/multiple-select/multiple-select2.directive.js
new file mode 100644
index 0000000..e047163
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/directives/multiple-select/multiple-select2.directive.js
@@ -0,0 +1,113 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+/**

+ * Created by nnaffar on 12/21/15.+

+ */

+angular.module('ecompApp')

+    .directive('multipleSelect2', function ($window) {

+        return {

+            restrict: 'E',

+            templateUrl: 'app/directives/multiple-select/multiple-select2.tpl.html',

+            scope: {

+                onChange: '&',

+                nameAttr: '@',

+                valueAttr: '@',

+                ngModel: '=',

+                placeholder: '@',

+                uniqueData: '@?',

+                onDropdownClose: '&?'

+            },

+            link: function(scope, elm, attrs){

+                scope.isExpanded = false;

+

+                scope.isDisabled = !scope.ngModel || !scope.ngModel.length;

+                scope.$watch('ngModel', function(newVal){

+                    scope.isDisabled = !newVal || !newVal.length;

+                });

+

+

+                let startListening = () => {

+                    console.log('listening on $window!');

+                    angular.element($window).on('click', function () {

+                        stopListening();

+                    });

+

+                    angular.element('multiple-select2').on('click', function(e) {

+                        if($(e.target).closest('multiple-select2')[0].attributes['unique-data'].value === attrs.uniqueData){

+                            console.log('ignored that..:', attrs.uniqueData);

+                            e.stopPropagation();

+                        }else{

+                            console.log('shouldnt ignore, close expanded!:', attrs.uniqueData);

+                            scope.isExpanded = false;

+                            scope.$applyAsync();

+                        }

+                    });

+                };

+

+                let stopListening = function() {

+                    if(scope.onDropdownClose){

+                        scope.onDropdownClose();

+                    }

+                    scope.isExpanded = false;

+                    scope.$applyAsync();

+                    console.log('stop listening on $window and multiple-element!');

+                    angular.element($window).off('click');

+                    angular.element('multiple-select2').off('click');

+                };

+

+                scope.showCheckboxes = function(){

+                    scope.isExpanded = !scope.isExpanded;

+                    if(scope.isExpanded){

+                        startListening();

+                    }else{

+                        stopListening();

+                        if(scope.onDropdownClose){

+                            scope.onDropdownClose();

+                        }

+                    }

+                };

+

+                scope.onCheckboxClicked = function() {

+                    console.log('checkbox clicked; unique data: ',attrs.uniqueData);

+                    if(scope.onChange) {

+                        scope.onChange();

+                    }

+                }

+

+                scope.getTitle = function(){

+                    var disp = '';

+                    if(!scope.ngModel || !scope.ngModel.length) {

+                        return disp;

+                    }

+                    scope.ngModel.forEach(function(item){

+                        if(item[scope.valueAttr]){

+                            disp+=item[scope.nameAttr] + ',';

+                        }

+                    });

+                    if(disp!==''){

+                        disp = disp.slice(0,disp.length-1);

+                    }else{

+                        disp = scope.placeholder;

+                    }

+                    return disp;

+                };

+            }

+        };

+    });

diff --git a/ecomp-portal-FE-common/client/app/directives/multiple-select/multiple-select2.tpl.html b/ecomp-portal-FE-common/client/app/directives/multiple-select/multiple-select2.tpl.html
new file mode 100644
index 0000000..34a6cea
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/directives/multiple-select/multiple-select2.tpl.html
@@ -0,0 +1,36 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div id="app-multilple-select" class="multiple-select2">

+    <div class="selectBox"

+    	 id="app-select-{{getTitle()}}"

+         ng-click="isDisabled || showCheckboxes()"

+         title="{{getTitle()}}"

+         ng-bind="getTitle()"

+         ng-class="{open: isExpanded, closed: !isExpanded, disabled: isDisabled}"></div>

+

+    <div class="checkboxes" ng-show="isExpanded" >

+        <div ng-repeat="item in ngModel" id="{{item[nameAttr]}}-checkbox-div">

+            <label id="{{item[nameAttr]}}-checkbox-label">

+                <input type="checkbox" id="{{item[nameAttr]}}-checkbox" disabled="disabled" ng-model="item[valueAttr]" ng-change="onCheckboxClicked()">

+                {{item[nameAttr]}}

+            </label>

+        </div>

+    </div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/directives/multiple-select/mutliple-select2.less b/ecomp-portal-FE-common/client/app/directives/multiple-select/mutliple-select2.less
new file mode 100644
index 0000000..b31d00e
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/directives/multiple-select/mutliple-select2.less
@@ -0,0 +1,62 @@
+.multiple-select2{
+  position: relative;
+  width: 100%;
+
+  .selectBox{
+    cursor: pointer;
+    position: relative;
+    border: 1px solid @portalDGray;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    line-height: 30px;
+    height: 30px;
+    padding-left: 10px;
+    padding-right: 10px;
+
+    &.open::after{
+      content: '';
+      .arrow_up;
+      display: block;
+      position: absolute;
+      top: 12px;
+      right: 4px;
+    }
+    &.closed::after{
+      content: '';
+      .arrow_down;
+      display: block;
+      position: absolute;
+      top: 12px;
+      right: 4px;
+    }
+    &.disabled{
+      cursor: default;
+      background: @portalLGray;
+    }
+
+  }
+
+  .checkboxes{
+    z-index: 99999;
+    padding-left: 8px;
+    padding-right: 8px;
+    position: absolute;
+    top: 30px;
+    width: 100%;
+    background: @portalWhite;
+
+    display: block;
+    border: 1px @portalLGray solid;
+
+    label{
+      cursor: pointer;
+      display: block;
+    }
+    input{
+      cursor: pointer;
+    }
+
+
+  }
+}
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/directives/right-click-menu/right-click-menu.directive.js b/ecomp-portal-FE-common/client/app/directives/right-click-menu/right-click-menu.directive.js
new file mode 100644
index 0000000..ff33932
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/directives/right-click-menu/right-click-menu.directive.js
@@ -0,0 +1,41 @@
+/*-
+ * ================================================================================
+ * eCOMP Portal
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property
+ * ================================================================================
+ * Licensed 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('ecompApp').directive( 'contextMenu', function($compile){
+    contextMenu = {};
+    contextMenu.restrict = 'AE';
+    contextMenu.link = function( lScope, lElem, lAttr ){
+        lElem.on('contextmenu', function (e) {
+            e.preventDefault(); // default context menu is disabled
+            //  The customized context menu is defined in the main controller. To function the ng-click functions the, contextmenu HTML should be compiled.
+            lElem.append( $compile( lScope[ lAttr.contextMenu ])(lScope) );
+            // The location of the context menu is defined on the click position and the click position is catched by the right click event.
+            $('#contextmenu-node').css('left', e.clientX);
+            $('#contextmenu-node').css('top', e.clientY);
+        });
+        lElem.on('mouseleave', function(e){
+            console.log('Leaved the div');
+            // on mouse leave, the context menu is removed.
+            if($('#contextmenu-node') )
+                $('#contextmenu-node').remove();
+        });
+    };
+    return contextMenu;
+});
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/directives/right-click/ng-right-click-directive.js b/ecomp-portal-FE-common/client/app/directives/right-click/ng-right-click-directive.js
new file mode 100644
index 0000000..b8af5eb
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/directives/right-click/ng-right-click-directive.js
@@ -0,0 +1,32 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+angular.module('ecompApp').

+    directive('ngRightClick', function($parse) {

+        return function(scope, element, attrs)

+        {

+        var fn = $parse(attrs.ngRightClick);

+        element.bind('contextmenu', function(event) {

+            scope.$apply(function() {

+                event.preventDefault();

+                fn(scope, {$event:event});

+            });

+        });

+        };

+    });

diff --git a/ecomp-portal-FE-common/client/app/directives/scroll-top/scroll-top.directive.js b/ecomp-portal-FE-common/client/app/directives/scroll-top/scroll-top.directive.js
new file mode 100644
index 0000000..d067257
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/directives/scroll-top/scroll-top.directive.js
@@ -0,0 +1,35 @@
+/*-
+ * ================================================================================
+ * eCOMP Portal
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property
+ * ================================================================================
+ * Licensed 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.
+ * ================================================================================
+ */
+
+angular.module('ecompApp')
+    .directive('scrollTop', function () {
+        return {
+            restrict: 'A',
+            scope: {api: '=scrollTop'},
+            link: function (scope, element) {
+                scope.api = scope.api || {};
+                scope.api.scrollTop = function() {
+                    element.animate({
+                        scrollTop : 0
+                    }, 500);
+                };
+            }
+        };
+    });
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/directives/search-users/search-users.controller.js b/ecomp-portal-FE-common/client/app/directives/search-users/search-users.controller.js
new file mode 100644
index 0000000..6fd402c
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/directives/search-users/search-users.controller.js
@@ -0,0 +1,92 @@
+/*-
+ * ================================================================================
+ * eCOMP Portal
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property
+ * ================================================================================
+ * Licensed 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';
+(function () {
+    class SearchUsersCtrl {
+        constructor($log, usersService, $scope) {
+            $scope.UserSearchsIsNull=false;
+            this.scrollApi = {};//scrollTop directive
+            $scope.txtResults = 'result';
+            /**
+             * Handle all active HTTP requests
+             * activeRequests @type {Array[requests with cancel option]}
+             */
+            let activeRequests = [];
+            let clearReq = (req) => {
+                activeRequests.splice(activeRequests.indexOf(req), 1);
+            };
+
+            /**
+             * this function retrieves users info 
+             */
+            this.searchUsers = () => {
+                this.isLoading = true;
+                if(this.searchUsersInProgress){
+                    return;
+                }
+                this.selectedUser = null;
+                this.searchUsersInProgress = true;
+                this.searchUsersResults = null;
+
+                let searchUsersReq = usersService.searchUsers(this.searchUserString);
+                activeRequests.push(searchUsersReq);
+                searchUsersReq.promise().then(usersList => {
+                    $log.debug('searchUsers found the following users: ', JSON.stringify(usersList));
+                    this.searchUsersResults = usersList;
+                    $log.debug('searchUsersResults length: ', usersList.length);
+                    if (usersList.length != 1) {
+                        $scope.txtResults = 'results'
+                    } else {
+                        $scope.txtResults = 'result'
+                    }
+                    $scope.UserSearchsIsNull=false;
+                }).catch(err => {
+                    $log.error('SearchUsersCtrl.searchUsers: ' + err);
+                    $scope.UserSearchsIsNull=true;
+                }).finally(() => {
+                    this.scrollApi.scrollTop();
+                    this.searchUsersInProgress = false;
+                    clearReq(searchUsersReq);
+                    this.isLoading = false;
+                });
+            };
+
+            let init = () => {
+                this.isLoading = false;
+                this.searchUsersInProgress = false;
+            };
+
+            this.setSelectedUser = user => {
+                this.selectedUser = user;
+            };
+
+            init();
+
+            $scope.$on('$destroy', () => {
+                //cancel all active requests when closing the modal
+                activeRequests.forEach(req => {
+                    req.cancel();
+                });
+            });
+        }
+    }
+    SearchUsersCtrl.$inject = ['$log', 'usersService', '$scope'];
+    angular.module('ecompApp').controller('SearchUsersCtrl', SearchUsersCtrl);
+})();
diff --git a/ecomp-portal-FE-common/client/app/directives/search-users/search-users.controller.spec.js b/ecomp-portal-FE-common/client/app/directives/search-users/search-users.controller.spec.js
new file mode 100644
index 0000000..e3c9711
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/directives/search-users/search-users.controller.spec.js
@@ -0,0 +1,176 @@
+/*-
+ * ================================================================================
+ * eCOMP Portal
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property
+ * ================================================================================
+ * Licensed 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';
+//
+//describe('Controller: NewAdminCtrl ', () => {
+//    beforeEach(module('ecompApp'));
+//
+//    //destroy $http default cache before starting to prevent the error 'default cache already exists'
+//    beforeEach(inject((_CacheFactory_)=> {
+//        _CacheFactory_.destroyAll();
+//    }));
+//
+//
+//    let newCtrl, $controller, $q, $rootScope, $log;
+//
+//    beforeEach(inject((_$controller_, _$q_, _$rootScope_, _$log_)=> {
+//        [$controller, $q, $rootScope, $log] = [_$controller_, _$q_, _$rootScope_, _$log_];
+//    }));
+//
+//    let deferredUsersList, deferredAdminAppsRoles, deferredUpdateRolesRes;
+//    let usersServiceMock, adminsServiceMock;
+//    beforeEach(()=> {
+//        [deferredUsersList, deferredAdminAppsRoles, deferredUpdateRolesRes] = [$q.defer(), $q.defer(), $q.defer()];
+//
+//        //usersServiceMock = jasmine.createSpyObj('usersServiceMock', ['searchUsers']);
+//        usersServiceMock = {
+//            searchUsers: () => {
+//                var promise = () => {return deferredUsersList.promise};
+//                var cancel = jasmine.createSpy();
+//                return {
+//                    promise: promise,
+//                    cancel: cancel
+//                }
+//            }
+//        };
+//
+//        adminsServiceMock = jasmine.createSpyObj('adminsServiceMock', ['getAdminAppsRoles', 'updateAdminAppsRoles']);
+//
+//        //usersServiceMock.searchUsers.and.returnValue(deferredUsersList.promise);
+//        adminsServiceMock.getAdminAppsRoles.and.returnValue(deferredAdminAppsRoles.promise);
+//        adminsServiceMock.updateAdminAppsRoles.and.returnValue(deferredUpdateRolesRes.promise);
+//
+//        newCtrl = $controller('NewAdminModalCtrl', {
+//            $log: $log,
+//            usersService: usersServiceMock,
+//            adminsService: adminsServiceMock,
+//            $scope: $rootScope
+//        });
+//    });
+//
+//    it('should init default values when loading the controller', ()=> {
+//        //expect(newCtrl.searchUsersInProgress).toBe(false);
+//        expect(newCtrl.dialogState).toBe(1);
+//        expect(newCtrl.selectedUser).toBe(null);
+//    });
+//
+//    it('should populate retrieved users when search users service returns a list ', ()=> {
+//        //spyOn(usersServiceMock, 'searchUsers');
+//        let usersListRes = [{user: 1}, {user: 2}];
+//        newCtrl.searchUserString = 'some att user name';
+//        deferredUsersList.resolve(usersListRes);
+//        newCtrl.searchUsers();
+//        $rootScope.$apply();
+//
+//        //expect(usersServiceMock.searchUsers).toHaveBeenCalledWith(newCtrl.searchUserString);
+//        expect(newCtrl.searchUsersResults).toEqual(usersListRes);
+//        expect(newCtrl.searchUsersInProgress).toBe(false);
+//    });
+//
+//    it('should log the error when search users fails', ()=> {
+//        spyOn($log, 'error');
+//        deferredUsersList.reject('oh snap!');
+//        newCtrl.searchUsers();
+//        $rootScope.$apply();
+//        expect($log.error).toHaveBeenCalled();
+//    });
+//
+//    it('should populate admin apps roles and move to the next screen when adminsService.getAdminAppsRoles succeeded', ()=> {
+//        let userApps = {appsRoles: [{id: 1, isAdmin: false}, {id: 2, isAdmin: true}]};
+//        deferredAdminAppsRoles.resolve(userApps);
+//
+//        newCtrl.searchUsersInProgress = false;
+//        newCtrl.selectedUser = {orgUserId: 'orgUserId'};
+//
+//        newCtrl.getAdminAppsRoles();
+//        $rootScope.$apply();
+//
+//        expect(adminsServiceMock.getAdminAppsRoles).toHaveBeenCalledWith(newCtrl.selectedUser.orgUserId);
+//        expect(newCtrl.adminAppsRoles).toEqual(userApps.appsRoles);
+//        expect(newCtrl.dialogState).toBe(2);
+//    });
+//
+//    it('should  log the error when adminsService.getAdminAppsRoles fails', ()=> {
+//        spyOn($log, 'error');
+//        deferredAdminAppsRoles.reject('some error');
+//
+//        newCtrl.searchUsersInProgress = false;
+//        newCtrl.selectedUser = {orgUserId: 'orgUserId'};
+//
+//        newCtrl.getAdminAppsRoles();
+//        $rootScope.$apply();
+//
+//        expect($log.error).toHaveBeenCalled();
+//    });
+//    it('should  log the error when trying to getAdminAppsRoles without selecting user ', ()=> {
+//        spyOn($log, 'error');
+//
+//        newCtrl.searchUsersInProgress = false;
+//        newCtrl.selectedUser = null;
+//
+//        newCtrl.getAdminAppsRoles();
+//        $rootScope.$apply();
+//
+//        expect($log.error).toHaveBeenCalled();
+//    });
+//    //it('should setSelectedUser when choosing user', ()=> {
+//    //
+//    //});
+//    //it('should set isAdmin as false when removing app from the administrated apps list', ()=> {
+//    //});
+//    it('should set isAdmin as true when adding app via the dropdown menu', ()=> {
+//        newCtrl.adminAppsRoles = [{id: 1, isAdmin: false},{id: 2, isAdmin: true}];
+//        //simulate UI change
+//        $rootScope.$apply('newAdmin.selectedNewApp = null');
+//        $rootScope.$apply('newAdmin.selectedNewApp = {id: 1, isAdmin: true}');
+//
+//        expect(newCtrl.adminAppsRoles[0].isAdmin).toBe(true);
+//        expect(newCtrl.selectedNewApp).toBe(null);
+//    });
+//
+//    it('should close the modal when updating apps roles succeeded', ()=> {
+//        $rootScope.closeThisDialog = () => {};
+//        spyOn($rootScope,'closeThisDialog');
+//
+//        newCtrl.selectedUser = {orgUserId: 'orgUserId'};
+//        newCtrl.adminAppsRoles = [{id: 1}];
+//
+//        deferredUpdateRolesRes.resolve();
+//        newCtrl.updateAdminAppsRoles();
+//        $rootScope.$apply();
+//
+//        expect(adminsServiceMock.updateAdminAppsRoles).toHaveBeenCalledWith({orgUserId: newCtrl.selectedUser.orgUserId, appsRoles: newCtrl.adminAppsRoles});
+//        expect($rootScope.closeThisDialog).toHaveBeenCalled();
+//    });
+//    it('should log the error when updating apps roles fails', ()=> {
+//        newCtrl.selectedUser = {orgUserId: 'orgUserId'};
+//        newCtrl.adminAppsRoles = [{id: 1}];
+//
+//        spyOn($log,'error');
+//        deferredUpdateRolesRes.reject();
+//        newCtrl.updateAdminAppsRoles();
+//        $rootScope.$apply();
+//        expect($log.error).toHaveBeenCalled();
+//    });
+//    //it('should display the add admin dropdown when clicking the add button', ()=> {
+//    //});
+//
+//});
diff --git a/ecomp-portal-FE-common/client/app/directives/search-users/search-users.directive.js b/ecomp-portal-FE-common/client/app/directives/search-users/search-users.directive.js
new file mode 100644
index 0000000..e297c7f
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/directives/search-users/search-users.directive.js
@@ -0,0 +1,34 @@
+/*-
+ * ================================================================================
+ * eCOMP Portal
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property
+ * ================================================================================
+ * Licensed 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.
+ * ================================================================================
+ */
+
+angular.module('ecompApp')
+    .directive('searchUsers', function () {
+        return {
+            restrict: 'E',
+            templateUrl: 'app/directives/search-users/search-users.tpl.html',
+            controller: 'SearchUsersCtrl',
+            controllerAs: 'searchUsers',
+            bindToController: true,
+            scope: {
+                selectedUser: '=',
+                searchTitle: '@'
+            }
+        };
+    });
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/filters/elipsis/elipsis.filter.js b/ecomp-portal-FE-common/client/app/filters/elipsis/elipsis.filter.js
new file mode 100644
index 0000000..310649b
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/filters/elipsis/elipsis.filter.js
@@ -0,0 +1,38 @@
+/*-
+ * ================================================================================
+ * eCOMP Portal
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property
+ * ================================================================================
+ * Licensed 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('ecompApp')
+    .filter('elipsis', () => {
+        return (val, limit) => {
+            if (!val) {
+                return val;
+            }
+            limit = parseInt(limit);
+
+            if (!limit || val.length <= limit) {
+                return val;
+            }
+
+            val = val.substr(0, limit);
+
+            return val + '...';
+        }
+    });
diff --git a/ecomp-portal-FE-common/client/app/filters/elipsis/elipsis.filter.spec.js b/ecomp-portal-FE-common/client/app/filters/elipsis/elipsis.filter.spec.js
new file mode 100644
index 0000000..a9dc783
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/filters/elipsis/elipsis.filter.spec.js
@@ -0,0 +1,58 @@
+/*-
+ * ================================================================================
+ * eCOMP Portal
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property
+ * ================================================================================
+ * Licensed 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.
+ * ================================================================================
+ */
+describe('Filter: elipsis', function () {
+    'use strict';
+    let filter;
+
+    beforeEach(function () {
+        module('ecompApp');
+    });
+
+    beforeEach(inject((_CacheFactory_)=> {
+        _CacheFactory_.destroyAll();
+
+    }));
+
+    it('should trim the text and return it with ... when the text length is greater than the limit"', inject(function (_$filter_) {
+        filter = _$filter_;
+        // given
+        let text = `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
+        et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex
+        ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat
+        nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim
+        id est laborum`;
+        let limit = 50;
+
+        var result = filter('elipsis')(text, limit);
+
+        expect(result).toBe(text.substr(0, 50) + '...');
+    }));
+
+    it("should return the exact string where there's no limit", inject(function (_$filter_) {
+        filter = _$filter_;
+
+        let text = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore';
+
+        let result = filter('elipsis')(text);
+
+        expect(result).toEqual(text);
+    }));
+
+});
diff --git a/ecomp-portal-FE-common/client/app/filters/trusted-url/trusted-url.filter.js b/ecomp-portal-FE-common/client/app/filters/trusted-url/trusted-url.filter.js
new file mode 100644
index 0000000..a6e70d6
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/filters/trusted-url/trusted-url.filter.js
@@ -0,0 +1,26 @@
+/*-
+ * ================================================================================
+ * eCOMP Portal
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property
+ * ================================================================================
+ * Licensed 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.
+ * ================================================================================
+ */
+
+angular.module('ecompApp')
+    .filter('trusted', function($sce){
+        return function(url) {
+            return $sce.trustAsResourceUrl(url);
+        };
+    });
diff --git a/ecomp-portal-FE-common/client/app/router.js b/ecomp-portal-FE-common/client/app/router.js
new file mode 100644
index 0000000..9fa71d2
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/router.js
@@ -0,0 +1,238 @@
+/*-
+ * ================================================================================
+ * eCOMP Portal
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property
+ * ================================================================================
+ * Licensed 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('ecompApp')
+    .config($stateProvider => {
+        $stateProvider
+           .state('root', {
+            abstract: true,
+            views: {
+                'header@': {
+                    templateUrl: 'app/views/header/header.tpl.html',
+                    controller: 'HeaderCtrl',
+                    controllerAs: 'header'
+                },
+                'tabbar@': {
+                    templateUrl: 'app/views/tabs/tabs.tpl.html',
+                    controller: 'TabsCtrl',
+                    controllerAs: 'tabsHome'
+                },
+                'sidebar@':{
+                    templateUrl: 'app/views/sidebar/sidebar.tpl.html',
+                    controller: 'SidebarCtrl',
+                    controllerAs: 'sidebar'
+                },
+                'userbar@':{
+                    templateUrl: 'app/views/userbar/userbar.tpl.html',
+                    controller: 'UserbarCtrl',
+                    controllerAs: 'userbar'
+                },
+                'footer@': {
+                    templateUrl: 'app/views/footer/footer.tpl.html',
+                    controller: 'FooterCtrl',
+                    controllerAs: 'footer'
+                }
+            }
+        }).state('root.applicationsHome', {
+            url: '/applicationsHome',
+            views: {
+            	'content@': {
+                    templateUrl: 'app/views/dashboard/dashboard.tpl.html',
+                    controller: 'DashboardCtrl',
+                    controllerAs: 'dashboard'
+                }
+            }
+        }).state('root.appCatalog', {
+            url: '/appCatalog',
+            views: {
+            	'content@': {
+                    templateUrl: 'app/views/catalog/catalog.tpl.html',
+                    controller: 'CatalogCtrl',
+                    controllerAs: 'catalog'
+                }
+            }
+        }).state('root.accountOnboarding', {
+            url: '/accountOnboarding',
+            views: {
+                'content@': {
+		            templateUrl: 'app/views/account-onboarding/account-onboarding.tpl.html',
+		            controller: 'AccountOnboardingCtrl',
+		            controllerAs: 'accountOnboarding'
+                }
+            }
+        }).state('root.widgetCatalog', {
+            url: '/widgetCatalog',
+            views: {
+                'content@': {
+                    templateUrl: 'app/views/widget-catalog/widget-catalog.tpl.html',
+                    controller: 'WidgetCatalogCtrl',
+                    controllerAs: 'widgetCatalog'
+                }
+            }
+        }).state('root.admins', {
+            url: '/admins',
+            views: {
+                'content@': {
+                    templateUrl: 'app/views/admins/admins.tpl.html',
+                    controller: 'AdminsCtrl',
+                    controllerAs: 'admins'
+                }
+            }
+        }).state('root.roles', {
+            url: '/roles',
+            views: {
+                'content@': { 
+                    templateUrl: 'app/views/role/role_list.html',
+                    controller: 'roleListController',
+                    controllerAs: 'roles'
+                }
+            }
+        }).state('root.role', {
+            url: '/role/:roleId',
+            views: {
+                'content@': { 
+                    templateUrl: 'app/views/role/role.html',
+                    controller: 'roleController',
+                    controllerAs: 'role'
+                }
+            }
+        }).state('root.roleFunctions', {
+            url: '/roleFunctions',
+            views: {
+                'content@': { 
+                    templateUrl: 'app/views/role/role_function_list.html',
+                    controller: 'roleFunctionListController',
+                    controllerAs: 'roleFunctions'
+                }
+            }
+        }).state('root.users', {
+            url: '/users',
+            views: {
+                'content@': {
+                    templateUrl: 'app/views/users/users.tpl.html',
+                    controller: 'UsersCtrl',
+                    controllerAs: 'users'
+                }
+            }
+        }).state('root.applications', {
+            url: '/applications',
+            views: {
+                'content@': {
+                    templateUrl: 'app/views/applications/applications.tpl.html',
+                    controller: 'ApplicationsCtrl',
+                    controllerAs: 'apps'
+                }
+            }
+        }).state('root.widgetOnboarding', {
+            url: '/widgetOnboarding',
+            views: {
+                'content@': {
+                	templateUrl: 'app/views/widget-onboarding/widget-onboarding.tpl.html',
+                	controller: 'WidgetOnboardingCtrl',
+                	controllerAs: 'widgetOnboarding'
+                }
+            }
+        }).state('root.functionalMenu', {
+            url: '/functionalMenu',
+            views: {
+                'content@': {
+                    templateUrl: 'app/views/functionalMenu/functionalMenu.tpl.html',
+                    controller: 'FunctionalMenuCtrl',
+                    controllerAs: 'functionalMenu'
+                }
+            }
+        }).state('root.getAccess', {
+            url: '/getAccess',
+            params: {
+                appName: null,
+              },
+            views: {
+                'content@': {
+                    templateUrl: 'app/views/support/get-access/get-access.tpl.html',
+                    controller: 'GetAccessCtrl',
+                    controllerAs: 'access'
+                }
+            }
+        }).state('root.contactUs', {
+            url: '/contactUs',
+            views: {
+                'content@': {
+                    templateUrl: 'app/views/support/contact-us/contact-us.tpl.html',
+                    controller: 'ContactUsCtrl',
+                    controllerAs: 'contact'
+                }
+            }
+        }).state('root.userNotifications', {
+            url: '/userNotifications',
+            views: {
+                'content@': {
+                    templateUrl: 'app/views/user-notifications-admin/user.notifications.tpl.html',
+                    controller: 'userNotificationsCtrl',
+                    controllerAs: 'userNotifications'
+                }
+            }
+        }).state('root.notificationHistory', {
+            url: '/notificationHistory',
+            views: {
+                'content@': {
+                    templateUrl: 'app/views/notification-history/notificationhistory.tpl.html',
+                    controller: 'notificationHistoryCtrl',
+                    controllerAs: 'notificationHistory'
+                }
+            }
+        }).state('root.portalAdmins', {
+            url: '/portalAdmins',
+            views: {
+                'content@': {
+                    templateUrl: 'app/views/portal-admin/portal-admin.tpl.html',
+                    controller: 'PortalAdminsCtrl',
+                    controllerAs: 'portalAdmin'
+                }
+            }
+        }).state('root.error404', {
+            url: '/error404',
+            views: {
+                'content@': {
+                    templateUrl: 'app/views/errors/error.404.tpl.html',
+                    controller: 'ErrorCtrl',
+                    controllerAs: 'error'
+                }
+            }
+        }).state('noUserError', {
+            url: '/noUserError',
+            views: {
+                'error@': {
+                    templateUrl: 'app/views/errors/error.tpl.html',
+                    controller: 'ErrorCtrl',
+                    controllerAs: 'error'
+                }
+            }
+        }).state('unKnownError', {
+            url: '/unKnownError',
+            views: {
+                'error@': {
+                    templateUrl: 'app/views/errors/error.tpl.html',
+                    controller: 'ErrorCtrl',
+                    controllerAs: 'error'
+                }
+            }
+        });
+    });
diff --git a/ecomp-portal-FE-common/client/app/services/admins/admins.service.js b/ecomp-portal-FE-common/client/app/services/admins/admins.service.js
new file mode 100644
index 0000000..3658052
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/services/admins/admins.service.js
@@ -0,0 +1,221 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+/**

+ * Created by nnaffar on 11/22/2015.

+ */

+'use strict';

+

+(function () {

+    class AdminsService {

+        constructor($q, $log, $http, conf, uuid, utilsService) {

+            this.$q = $q;

+            this.$log = $log;

+            this.$http = $http;

+            this.conf = conf;

+            this.uuid = uuid;

+            this.utilsService = utilsService;

+        }

+

+        getAccountAdmins() {

+            let deferred = this.$q.defer();

+            //this.$log.info('AdminsService::get all applications admins list');

+            this.$http({

+                    method: "GET",

+                    cache: false,

+                    url: this.conf.api.accountAdmins,

+                    headers: {

+                        'X-ECOMP-RequestID':this.uuid.generate()

+                    }

+                })

+                .then( res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success

+                    if (this.utilsService.isValidJSON(res)=== false) {

+                    	this.$log.error('AdminsService::getAccountAdmins Failed');

+                        deferred.reject("AdminsService::getAccountAdmins Failed");

+                    } else {

+                        // this.$log.info('AdminsService::getAccountAdmins Succeeded');

+                        deferred.resolve(res.data);

+                    }

+                })

+                .catch( status => {

+                	this.$log.error('AdminsService::getAccountAdmins Failed', status);

+                    deferred.reject(status);

+                });

+           

+            return deferred.promise;

+        }

+

+        getAdminAppsRoles(orgUserId) {

+            let deferred = this.$q.defer();

+            //this.$log.info('AdminsService::getAdminAppsRoles.adminAppsRoles');

+

+            this.$http({

+                method: "GET",

+                url: this.conf.api.adminAppsRoles,

+                params: {user: orgUserId},

+                cache: false,

+                headers: {

+                    'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then( res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success

+                    if (this.utilsService.isValidJSON(res)=== false) {

+                        this.$log.error('AdminsService::getAdminAppsRoles.adminAppsRoles Failed');

+                        deferred.reject("AdminsService::getAdminAppsRoles.adminAppsRoles Failed");

+                    } else {

+                    	// this.$log.info('AdminsService::getAdminAppsRoles.adminAppsRoles Succeeded');

+                        deferred.resolve(res.data);

+                    }

+                })

+                .catch( status => {

+                	this.$log.error('AdminsService::getAdminAppsRoles.adminAppsRoles Failed', status);

+                    deferred.reject(status);

+                });

+

+            return deferred.promise;

+        }

+        /*Author: Rui*/

+        getRolesByApp(appId) {

+            let deferred = this.$q.defer();

+            this.$log.info('AdminsService::getRolesByApp');

+            let url = this.conf.api.adminAppsRoles + '/' + appId;

+            this.$http({

+                method: "GET",

+                url: url,

+                cache: false,

+                headers: {

+                    'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then( res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success

+                    if (Object.keys(res.data).length == 0) {

+                        deferred.reject("AdminsService::getAdminAppsRoles.getRolesByApp Failed");

+                    } else {

+                        this.$log.info('AdminsService::getAdminAppsRoles.getRolesByApp Succeeded');

+                        deferred.resolve(res.data);

+                    }

+                })

+                .catch( status => {

+                    deferred.reject(status);

+                });

+            

+            return deferred.promise;

+        }

+        

+        updateAdminAppsRoles(newAdminAppRoles) {

+            let deferred = this.$q.defer();

+            // this.$log.info('AdminsService::updateAdminAppsRoles');

+            this.$http({

+                method: "PUT",

+                url: this.conf.api.adminAppsRoles,

+                data: newAdminAppRoles,

+                headers: {

+                    'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then( res => {

+                if (this.utilsService.isValidJSON(res)=== false) {

+                    this.$log.error('AdminsService::updateAdminAppsRoles Failed');

+                    deferred.reject("AdminsService::updateAdminAppsRoles Failed");

+                } else {

+                    //this.$log.info('AdminsService::updateAdminAppsRoles success:');

+                    deferred.resolve(res.data);

+                }

+

+                })

+                .catch( status => {

+                    this.$log.error('AdminsService::updateAdminAppsRoles: rejection:' + status);

+                    deferred.reject(status);

+                });

+

+            return deferred.promise;

+        }

+        

+        /**

+         * Tests the specified password against complexity requirements.

+         * Returns an explanation message if the test fails; null if it passes.

+         */

+        isComplexPassword(str) {

+        	let minLength = 8;

+        	let message = 'Password is too simple.  Minimum length is '+ minLength + ', '

+        				  + 'and it must use letters, digits and special characters.';

+        	if (str == null)

+        		return message;

+

+        	let hasLetter = false;

+        	let hasDigit = false;

+        	let hasSpecial = false;

+        	var code, i, len;

+        	for (i = 0, len = str.length; i < len; i++) {

+        		code = str.charCodeAt(i);

+        		if (code > 47 && code < 58) // numeric (0-9)

+        			hasDigit = true;

+        		else if ((code > 64 && code < 91) || (code > 96 && code < 123)) // A-Z, a-z

+        			hasLetter = true;

+        		else

+        			hasSpecial = true;

+        	} // for

+

+        	if (str.length < minLength || !hasLetter || !hasDigit || !hasSpecial)

+        		return message;

+        	

+        	// All is well.

+        	return null;

+        }

+        

+        addNewUser(newUser,checkDuplicate) {

+        	// this.$log.info(newContactUs)

+        	let deferred = this.$q.defer();

+            // this.$log.info('ContactUsService:: add Contact Us' + JSON.stringify(newContactUs));

+            

+            var newUserObj={

+            		firstName:newUser.firstName,

+            		middleInitial:newUser.middleName,

+            		lastName:newUser.lastName,

+            		email:newUser.emailAddress,

+            		loginId:newUser.loginId,

+            		loginPwd:newUser.loginPwd,            		

+            };

+            this.$http({

+                url: this.conf.api.saveNewUser + "?isCheck=" + checkDuplicate,

+                method: 'POST',

+                cache: false,

+                headers: {

+                    'X-ECOMP-RequestID':this.uuid.generate()

+                },

+                data: newUserObj

+            }).then(res => {

+                // this.$log.info('ContactUsService:: add Contact Us res' ,res);

+                // If response comes back as a redirected HTML page which IS NOT a success

+                if (res==null || Object.keys(res.data).length == 0 || res.data.message == 'failure') {

+                    deferred.reject("Add new User failed");

+                    this.$log.error('adminService:: add New User failed');

+                } else {

+                    deferred.resolve(res.data);

+                }

+            }).catch(errRes => {

+                   deferred.reject(errRes);

+             });

+            return deferred.promise;

+        }

+        

+    }

+    AdminsService.$inject = ['$q', '$log', '$http', 'conf','uuid4', 'utilsService'];

+    angular.module('ecompApp').service('adminsService', AdminsService)

+})();

diff --git a/ecomp-portal-FE-common/client/app/services/applications/applications.service.js b/ecomp-portal-FE-common/client/app/services/applications/applications.service.js
new file mode 100644
index 0000000..d723a27
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/services/applications/applications.service.js
@@ -0,0 +1,591 @@
+/*-
+ * ================================================================================
+ * eCOMP Portal
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property
+ * ================================================================================
+ * Licensed 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';
+
+(function () {
+    class ApplicationsService {
+        constructor($q, $log, $http, conf, uuid, utilsService) {
+            this.$q = $q;
+            this.$log = $log;
+            this.$http = $http;
+            this.conf = conf;
+            this.uuid = uuid;
+            this.utilsService = utilsService
+        }
+        
+        getPersUserApps() {
+            let deferred = this.$q.defer();
+            var _this0 = this;
+            // this.$log.info('ApplicationsService::getPersUserApps');
+            this.$http.get(this.conf.api.persUserApps,
+                {
+                    cache: false,
+                    headers: {
+                        'X-ECOMP-RequestID':this.uuid.generate()
+                    }
+                })
+                .then( res => {
+                    // If response comes back as a redirected HTML page which IS NOT a success
+                	// But don't declare an empty list to be an error.
+                    if (res == null || res.data == null || _this0.utilsService.isValidJSON(res.data) == false) {
+                        deferred.reject("ApplicationsService::getPersUserApps Failed");
+                    } else {
+                        deferred.resolve(res.data);
+                    }
+                })
+                .catch( status => {
+                    deferred.reject(status);
+                });
+            return deferred.promise;
+        }
+        
+        getAppsOrderBySortPref(userAppSortTypePref) {
+            let deferred = this.$q.defer();
+            var _this1 = this;
+            // this.$log.info('ApplicationsService::getAppsOrderBySortPref');
+            this.$http.get(this.conf.api.userAppsOrderBySortPref,
+                {
+                    cache: false,
+                    params:{'mparams':userAppSortTypePref},
+                    headers: {
+                        'X-ECOMP-RequestID':this.uuid.generate()
+                    }
+                })
+                .then( res => {
+                    // If response comes back as a redirected HTML page which IS NOT a success
+                	// But don't declare an empty list to be an error.
+                    if (res == null || res.data == null || _this1.utilsService.isValidJSON(res.data)== false) {
+                        deferred.reject("ApplicationsService::getAppsOrderBySortPref Failed");
+                    } else {
+                        // this.$log.info('ApplicationsService::getAppsOrderBySortPref Succeeded');
+                        deferred.resolve(res.data);
+                    }
+                })
+                .catch( status => {
+                    deferred.reject(status);
+                });
+
+            return deferred.promise;
+        }
+
+        saveAppsSortTypeManual(appsSortManual){
+            let deferred = this.$q.defer();
+            if (appsSortManual== undefined
+					|| appsSortManual == null
+					|| appsSortManual.length == 0) {
+            	this.$log.error('ApplicationsService::saveAppsSortTypeManual: Apps Sort Manual received empty string!'); 
+            	return deferred.reject('Apps Sort Manual received empty string ');
+            }
+            
+            this.$http({
+                method: 'PUT',
+                url: this.conf.api.saveUserAppsSortingManual,
+                data: appsSortManual,
+                cache: false,
+                headers: {
+                    'X-ECOMP-RequestID':this.uuid.generate()
+                }
+            }).then( res => {
+                    // If response comes back as a redirected HTML page which IS
+					// NOT a success
+                	// But don't declare an empty list to be an error.
+                    if (res == null || res.data == null) {
+                        deferred.reject("ApplicationsService::saveAppsSortTypeManual Failed");
+                    } else {
+                        // this.$log.info('ApplicationsService::saveAppsSortTypeManual
+						// Succeeded');
+                        deferred.resolve(res.data);
+                    }
+                })
+                .catch( status => {
+                    deferred.reject(status);
+                });
+            return deferred.promise;
+        }
+            
+        saveAppsSortTypePreference(appsSortPreference){
+            let deferred = this.$q.defer();
+            var _this2 = this;
+            if (appsSortPreference== undefined
+					|| appsSortPreference == null
+					|| appsSortPreference.length == 0){
+            	this.$log.error('ApplicationsService::saveAppsSortTypePreference: Apps Sort type Preference received empty string!'); 
+           		 return deferred.reject('Apps Sort type Preference received empty string ');
+            }
+            this.$http({
+                method: 'PUT',
+                url: this.conf.api.saveUserAppsSortingPreference,
+                data: appsSortPreference,
+                cache: false,
+                headers: {
+                    'X-ECOMP-RequestID':this.uuid.generate()
+                }
+            }).then( res => {
+                    // If response comes back as a redirected HTML page which IS NOT a success
+                	// But don't declare an empty list to be an error.
+                    if (res == null || res.data == null || _this2.utilsService.isValidJSON(res) == false) {
+                        deferred.reject("ApplicationsService::saveAppsSortTypePreference Failed");
+                    } else {
+                        // this.$log.info('ApplicationsService::saveAppsSortTypePreference Succeeded');
+                        deferred.resolve(res.data);
+                    }
+                })
+                .catch( status => {
+                    deferred.reject(status);
+                });
+            return deferred.promise;
+        }
+        
+        getUserAppsSortTypePreference() {
+        	let deferred = this.$q.defer();
+        	var _this3 = this;
+        	this.$http({
+                        method: "GET",
+                        url: this.conf.api.userAppsSortTypePreference,
+                        cache: false,
+                        headers: {
+                            'X-ECOMP-RequestID':this.uuid.generate()
+                        }
+                    })
+                    .then( res => {
+                        if (res == null || res.data == null || _this3.utilsService.isValidJSON(res) == false) {  
+                        	deferred.reject("ApplicationsService::getSortTypePreference Failed");
+                        } else {
+                            // this.$log.info('ApplicationsService::getUserAppsSortTypePreference Succeeded');
+                            deferred.resolve(res.data);
+                        }
+                    })
+                    .catch( status => {
+                        deferred.reject(status);
+                    });
+        		return deferred.promise;
+        }
+        
+        saveWidgetsSortManual(widgetsSortManual){
+            let deferred = this.$q.defer();
+            var _this4 = this;
+            if (widgetsSortManual== undefined
+					|| widgetsSortManual == null
+					|| widgetsSortManual.length == 0){
+            	this.$log.error('ApplicationsService::saveWidgetsSortManual: Widegts Sort type Preference received empty string!'); 
+           		 return deferred.reject('Widgets Sort Manual received empty string ');
+            }
+            this.$http({
+                method: 'PUT',
+                url: this.conf.api.saveUserWidgetsSortManual,
+                data: widgetsSortManual,
+                cache: false,
+                headers: {
+                    'X-ECOMP-RequestID':this.uuid.generate()
+                }
+            }).then( res => {
+                    if (res == null || res.data == null || _this4.utilsService.isValidJSON(res) == false) {
+                        deferred.reject("ApplicationsService::saveWidgetsSortManual Failed");
+                    } else {
+                        // this.$log.info('ApplicationsService::saveWidgetsSortManual
+						// Succeeded');
+                        deferred.resolve(res.data);
+                    }
+                })
+                .catch( status => {
+                    deferred.reject(status);
+                });
+            return deferred.promise;
+        }
+        
+        delWidgetsSortPref(widgetsData){
+            let deferred = this.$q.defer();
+            var _this5 = this;
+             
+            if (widgetsData== undefined
+					|| widgetsData == null
+					|| widgetsData.length == 0){
+            	this.$log.error('ApplicationsService::delWidgetsSortPref: While deleting, widgets  received empty string!'); 
+            	return deferred.reject('Widgets received empty string ');
+            }
+            
+            this.$http({
+                method: 'PUT',
+                url: this.conf.api.updateWidgetsSortPref,
+                data: widgetsData,
+                cache: false,
+                headers: {
+                    'X-ECOMP-RequestID':this.uuid.generate()
+                }
+            }).then( res => {
+                    if (res == null || res.data == null || _this5.utilsService.isValidJSON(res) == false) {
+                        deferred.reject("ApplicationsService::delWidgetsSortPref Failed");
+                    } else {
+                        // this.$log.info('ApplicationsService::delWidgetsSortPref
+						// Succeeded');
+                        deferred.resolve(res.data);
+                    }
+                })
+                .catch( status => {
+                    deferred.reject(status);
+                });
+            return deferred.promise;
+        }
+        
+        getAvailableApps() {
+            let deferred = this.$q.defer();
+            var _this6 = this;
+            // this.$log.info('ApplicationsService::getAvailableApps');
+            
+            this.$http(
+                {
+                    method: "GET",
+                    url: this.conf.api.availableApps,
+                    cache: false,
+                    headers: {
+                        'X-ECOMP-RequestID':this.uuid.generate()
+                    }
+                })
+                .then( res => {
+                    if (res == null || res.data == null || _this6.utilsService.isValidJSON(res) == false) {
+                        deferred.reject("ApplicationsService::getAvailableApps Failed");
+                    } else {
+                        // this.$log.info('ApplicationsService::getAvailableApps Succeeded');
+                        deferred.resolve(res.data);
+                    }
+                })
+                .catch( status => {
+                    deferred.reject(status);
+                });
+
+            return deferred.promise;
+        }
+
+        getAdminApps(){
+            var _this7 = this;
+            let canceller = this.$q.defer();
+            let isActive = false;
+
+            let cancel = () => {
+                if(isActive){
+                    this.$log.debug('ApplicationsService::getAdminApps: canceling the request');
+                    canceller.resolve();
+                }
+            };
+
+            let promise = () => {
+                isActive = true;
+                let deferred = this.$q.defer();
+                // this.$log.info('ApplicationsService::getAdminApps: starting');
+                this.$http({method: "GET",
+                        url: this.conf.api.adminApps,
+                        cache: false,
+                        timeout: canceller.promise,
+                        headers: {
+                            'X-ECOMP-RequestID':this.uuid.generate()
+                        }
+                    }).then(res => {
+                        isActive = false;
+                        if (res == null || res.data == null || _this7.utilsService.isValidJSON(res) == false) {
+                            deferred.reject("ApplicationsService::adminApps Failed");
+                        } else {
+                            // this.$log.info('ApplicationsService::adminApps Succeeded');
+                            deferred.resolve(res.data);
+                        }
+                    })
+                    .catch(status => {
+                        isActive = false;
+                        deferred.reject(status);
+                    });
+                return deferred.promise;
+            };
+
+            return {
+                cancel: cancel,
+                promise: promise
+            };
+        }
+
+        getLeftMenuItems(){
+            let deferred = this.$q.defer();
+            var _this8 = this;
+            // this.$log.info('ApplicationsService::getAppsForSuperAdminAndAccountAdmin');
+            this.$http({method: "GET",
+                url: this.conf.api.leftmenuItems,
+                cache: false,
+                headers: {
+                    'X-ECOMP-RequestID':this.uuid.generate()
+                }
+            }).then(res => {
+                    // If response comes back as a redirected HTML page which IS NOT a success
+                	// But don't declare an empty list to be an error.
+                this.$log.debug('getLeftMenuItems::getAdminApps: canceling the request',res);
+
+                    if (res == null || res.data == null || _this8.utilsService.isValidJSON(res) == false) {
+                        deferred.reject("ApplicationsService::getLeftMenuItems Failed");
+                    } else {
+                        // this.$log.info('ApplicationsService::getAppsForSuperAdminAndAccountAdmin Succeeded');
+                        deferred.resolve(res.data);
+                    }
+                })
+                .catch(status => {
+                    deferred.reject(status);
+                });
+            return deferred.promise;
+        }
+        
+        getAppsForSuperAdminAndAccountAdmin(){
+            let deferred = this.$q.defer();
+            var _this9 = this;
+            // this.$log.info('ApplicationsService::getAppsForSuperAdminAndAccountAdmin');
+            this.$http({method: "GET",
+                url: this.conf.api.appsForSuperAdminAndAccountAdmin,
+                cache: false,
+                headers: {
+                    'X-ECOMP-RequestID':this.uuid.generate()
+                }
+            }).then(res => {
+                    // If response comes back as a redirected HTML page which IS NOT a success
+                	// But don't declare an empty list to be an error.
+                    if (res == null || res.data == null || _this9.utilsService.isValidJSON(res) == false) {
+                        deferred.reject("ApplicationsService::getAppsForSuperAdminAndAccountAdmin Failed");
+                    } else {
+                        // this.$log.info('ApplicationsService::getAppsForSuperAdminAndAccountAdmin Succeeded');
+                        deferred.resolve(res.data);
+                    }
+                })
+                .catch(status => {
+                    deferred.reject(status);
+                });
+            return deferred.promise;
+        }
+
+        getAdminAppsSimpler(){
+        	let deferred = this.$q.defer();
+        	var _this10 = this;
+            // this.$log.info('ApplicationsService::getAdminAppsSimpler');
+        	this.$http({method: "GET",
+        		url: this.conf.api.adminApps,
+        		cache: false,
+        		headers: {
+        			'X-ECOMP-RequestID':this.uuid.generate()
+        		}
+        	}).then(res => {
+        		// If response comes back as a redirected HTML page which IS NOT a success
+        		// But don't declare an empty list to be an error.
+        		if (res == null || res.data == null || _this10.utilsService.isValidJSON(res.data) == false) {
+        			deferred.reject("ApplicationsService::getAdminAppsSimpler Failed");
+        		} else {
+        			// this.$log.info('ApplicationsService::getAdminAppsSimpler Succeeded');
+        			deferred.resolve(res.data);
+        		}
+        	})
+        	.catch(status => {
+        		deferred.reject(status);
+        	});
+        	return deferred.promise;
+        }
+
+        getOnboardingApps(){
+            let deferred = this.$q.defer();
+            var _this11 = this;
+            // this.$log.debug('applications-service::getOnboardingApps');
+            this.$http.get(this.conf.api.onboardingApps,
+                {
+                    cache: false,
+                    headers: {
+                        'X-ECOMP-RequestID':this.uuid.generate()
+                    }
+                })
+                .then( res => {
+                    // If response comes back as a redirected HTML page which IS NOT a success
+                	// But don't declare an empty list to be an error.
+                    if (res == null || res.data == null || _this11.utilsService.isValidJSON(res.data) == false) {
+                        deferred.reject("ApplicationsService::getOnboardingApps Failed");
+                    } else {
+                        // this.$log.info('ApplicationsService::getOnboardingApps Succeeded');
+                        deferred.resolve(res.data);
+                    }
+                })
+                .catch( status => {
+                    deferred.reject(status);
+                });
+
+            return deferred.promise;
+        }
+
+        addOnboardingApp(newApp){
+            let deferred = this.$q.defer();
+            // this.$log.debug('applications-service::addOnboardingApp with:', newApp);
+            this.$http({
+                method: "POST",
+                url: this.conf.api.onboardingApps,
+                data: newApp,
+                cache: false,
+                headers: {
+                    'X-ECOMP-RequestID':this.uuid.generate()
+                }
+            }).then( res => {
+                    // If response comes back as a redirected HTML page which IS NOT a success
+                	// But don't declare an empty list to be an error.
+                    if (res == null || res.data == null) {
+                        deferred.reject("ApplicationsService::addOnboardingApp Failed");
+                    } else {
+                        // this.$log.info('ApplicationsService::addOnboardingApp Succeeded');
+                        deferred.resolve(res.data);
+                    }
+                })
+                .catch( status => {
+                    deferred.reject(status);
+                });
+            return deferred.promise;
+        }
+
+        updateOnboardingApp(appData){
+            let deferred = this.$q.defer();
+            // this.$log.info('ApplicationsService::addOnboardingApp');
+            if(!appData.id){
+                this.$log.error('ApplicationsService::addOnboardingApp: App id not found!');
+                return deferred.reject('App id not found');
+            }
+
+            this.$http({
+                method: "PUT",
+                url: this.conf.api.onboardingApps,
+                data: appData,
+                cache: false,
+                headers: {
+                    'X-ECOMP-RequestID':this.uuid.generate()
+                }
+            }).then( res => {
+                    // If response comes back as a redirected HTML page which IS NOT a success
+                	// But don't declare an empty list to be an error.
+                    if (res == null || res.data == null) {
+                        deferred.reject("ApplicationsService::updateOnboardingApp Failed");
+                    } else {
+                        // this.$log.info('ApplicationsService::updateOnboardingApp Succeeded');
+                        deferred.resolve(res.data);
+                    }
+                })
+                .catch( status => {
+                    deferred.reject(status);
+                });
+            return deferred.promise;
+        }
+        
+        saveUserAppsRoles(UserAppRolesRequest) {
+          	 let deferred = this.$q.defer();
+          	 
+   	        if (UserAppRolesRequest== undefined
+   				|| UserAppRolesRequest == null
+   				|| UserAppRolesRequest.length == 0){
+   	    	this.$log.error('ApplicationsService::saveAppsSortTypeManual: Apps Sort Manual received empty string!'); 
+   	   		 return deferred.reject('Apps Sort Manual received empty string ');
+   	        	}
+   			// var manualAppsJson = angular.toJson(appsSortManual);
+      
+   		    // console.log(manual_jsonData);
+   		    this.$http({
+   		        method: 'PUT',
+   		        url: this.conf.api.saveUserAppRoles,
+   		        data: UserAppRolesRequest,
+   		        cache: false,
+   		        headers: {
+   		            'X-ECOMP-RequestID':this.uuid.generate()
+   		        }
+   		    }).then( res => {
+   		            // If response comes back as a redirected HTML page which IS
+   					// NOT a success
+   		        	// But don't declare an empty list to be an error.
+   		            if (res == null || res.data == null) {
+   		                deferred.reject("ApplicationsService::saveAppsSortTypeManual Failed");
+   		            } else {
+   		                // this.$log.info('ApplicationsService::saveAppsSortTypeManual
+   						// Succeeded');
+   		                deferred.resolve(res.data);
+   		            }
+   		        })
+   		        .catch( status => {
+   		            deferred.reject(status);
+   		        });
+   		    return deferred.promise;
+
+        }
+         
+
+        deleteOnboardingApp(appId) {
+            let deferred = this.$q.defer();
+            let url = this.conf.api.onboardingApps + '/' + appId;
+            // this.$log.info('applications.service::deleteOnboardingApp' +appId);
+
+            this.$http({
+                method: "DELETE",
+                url: url,
+                cache: false,
+                data:'',
+                headers: {
+                    'X-ECOMP-RequestID':this.uuid.generate()
+                }
+            }).then(res => {
+                    // If response comes back as a redirected HTML page which IS NOT a success
+                	// But don't declare an empty list to be an error.
+                    if (res == null || res.data == null) {
+                        deferred.reject("applications.service::deleteOnboardingApp Failed");
+                    } else {
+                        // this.$log.info('applications.service::deleteOnboardingApp succeeded: ');
+                        deferred.resolve(res.data);
+                    }
+                })
+                .catch(errRes => {
+                    deferred.reject(errRes);
+                });
+            return deferred.promise;
+        }
+
+        getTopMenuData(selectedApp) {
+            let deferred = this.$q.defer();
+            // this.$log.info('ApplicationsService:getTopMenuData');
+            this.$log.debug('ApplicationsService:getTopMenuData with:', selectedApp);
+        }
+
+        ping(){
+            let deferred = this.$q.defer();
+            // this.$log.info('ApplicationsService::ping: ');
+            this.$http.get(this.conf.api.ping,
+                {
+                    cache: false,
+                    headers: {
+                        'X-ECOMP-RequestID':this.uuid.generate()
+                    }
+                })
+                .success( res => {
+                    // If response comes back as a redirected HTML page which IS NOT a success
+                    if (Object.keys(res).length == 0) {
+                        deferred.reject("ApplicationsService::ping: Failed");
+                    } else {
+                        // this.$log.info('ApplicationsService::ping: Succeeded');
+                        deferred.resolve(res);
+                    }
+                })
+                .error( status => {
+                    deferred.reject(status);
+                });
+
+            return deferred.promise;
+        }
+    }
+    ApplicationsService.$inject = ['$q', '$log', '$http', 'conf','uuid4','utilsService'];
+    angular.module('ecompApp').service('applicationsService', ApplicationsService)
+})();
diff --git a/ecomp-portal-FE-common/client/app/services/audit-log/audit-log.service.js b/ecomp-portal-FE-common/client/app/services/audit-log/audit-log.service.js
new file mode 100644
index 0000000..93a6131
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/services/audit-log/audit-log.service.js
@@ -0,0 +1,92 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+/**

+ * Created by robertlo on 11/18/2016.

+ */

+'use strict';

+

+(function () {

+    class AuditLogService {

+        constructor($q, $log, $http, conf, uuid) {

+            this.$q = $q;

+            this.$log = $log;

+            this.$http = $http;

+            this.conf = conf;

+          

+            this.uuid = uuid;

+        }

+        storeAudit(affectedAppId) {

+            // this.$log.error('ecomp::storeAudit storeAudit',affectedAppId);

+        	let deferred = this.$q.defer();

+        	this.$http({

+                    method: "GET",

+                    url: this.conf.api.storeAuditLog+'?affectedAppId=' + affectedAppId +"&type=''&comment=''",

+                    cache: false,

+                    headers: {

+                        'X-ECOMP-RequestID':this.uuid.generate()

+                    }

+            })

+        	return deferred.promise;

+        }

+        

+        storeAudit(affectedAppId,type) {

+            // this.$log.error('ecomp::storeAudit storeAudit',affectedAppId + " " +type);

+        	let deferred = this.$q.defer();

+        	this.$http({

+                    method: "GET",

+                    url: this.conf.api.storeAuditLog+'?affectedAppId=' + affectedAppId + '&type='+type+"&comment=''",

+                    cache: false,

+                    headers: {

+                        'X-ECOMP-RequestID':this.uuid.generate()

+                    }

+            })

+        	return deferred.promise;

+        }

+        storeAudit(affectedAppId,type,comment) {

+        	comment = filterDummyValue(comment);

+        	let deferred = this.$q.defer();

+        	var url =this.conf.api.storeAuditLog+'?affectedAppId=' + affectedAppId;

+        	if(type!=''){

+        		url= url+'&type='+type;

+        	}

+        	if(comment!=''){

+        		url= url+'&comment='+comment;

+        	}

+        	this.$http({

+                    method: "GET",

+                    url: url,

+                    cache: false,

+                    headers: {

+                        'X-ECOMP-RequestID':this.uuid.generate()

+                    }

+            })

+        	return deferred.promise;

+        }

+    }  

+    AuditLogService.$inject = ['$q', '$log', '$http', 'conf', 'uuid4'];

+    angular.module('ecompApp').service('auditLogService', AuditLogService)

+})();

+

+function filterDummyValue(comment){

+	var n = comment.indexOf("?dummyVar");

+	if(n!=-1)

+		comment = comment.substring(0, n);

+	return comment;

+}

diff --git a/ecomp-portal-FE-common/client/app/services/base64/base64.service.js b/ecomp-portal-FE-common/client/app/services/base64/base64.service.js
new file mode 100644
index 0000000..5a68478
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/services/base64/base64.service.js
@@ -0,0 +1,69 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+/**

+ * Author: Rui Lu

+ * 12/15/2016

+ */

+(function () {

+    class Base64Service {

+    	 constructor(){

+    		

+    	 }

+    	 encode(input) {

+    		 var keyStr = 'ABCDEFGHIJKLMNOP' +

+             'QRSTUVWXYZabcdef' +

+             'ghijklmnopqrstuv' +

+             'wxyz0123456789+/' +

+             '=';

+    		 var output = "";

+             var chr1, chr2, chr3 = "";

+             var enc1, enc2, enc3, enc4 = "";

+             var i = 0;

+

+             do {

+                 chr1 = input.charCodeAt(i++);

+                 chr2 = input.charCodeAt(i++);

+                 chr3 = input.charCodeAt(i++);

+

+                 enc1 = chr1 >> 2;

+                 enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);

+                 enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);

+                 enc4 = chr3 & 63;

+

+                 if (isNaN(chr2)) {

+                     enc3 = enc4 = 64;

+                 } else if (isNaN(chr3)) {

+                     enc4 = 64;

+                 }

+

+                 output = output +

+                         keyStr.charAt(enc1) +

+                         keyStr.charAt(enc2) +

+                         keyStr.charAt(enc3) +

+                         keyStr.charAt(enc4);

+                 chr1 = chr2 = chr3 = "";

+                 enc1 = enc2 = enc3 = enc4 = "";

+             } while (i < input.length);

+

+             return output;

+    	 }

+    }

+    angular.module('ecompApp').service('base64Service', Base64Service)

+})();

diff --git a/ecomp-portal-FE-common/client/app/services/basic-auth-account/basic-auth-account.service.js b/ecomp-portal-FE-common/client/app/services/basic-auth-account/basic-auth-account.service.js
new file mode 100644
index 0000000..a95e0c0
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/services/basic-auth-account/basic-auth-account.service.js
@@ -0,0 +1,124 @@
+'use strict';
+
+(function () {
+    class BasicAuthAccountService {
+        constructor($q, $log, $http, conf,uuid, utilsService) {
+            this.$q = $q;
+            this.$log = $log;
+            this.$http = $http;
+            this.conf = conf;
+            this.uuid = uuid;
+            this.utilsService = utilsService;   
+        }
+        
+        createAccount(newAccount) {
+        	let deferred = this.$q.defer();
+        	this.$http({
+                method: "POST",
+                url: this.conf.api.basicAuthAccount,
+                data: newAccount,
+                cache: false,
+                headers:{
+            		'X-ECOMP-RequestID':this.uuid.generate()
+                }
+            }).then(res => {
+            	if (res == null || res.data == null) {
+        	 		this.$log.error('BasicAuthAccountService::createAccount Failed: Result or result.data is null');
+                     deferred.reject("BasicAuthAccountService::createAccount Failed: Result or result.data is null");
+                } else if (!this.utilsService.isValidJSON(res.data)) {
+                 	this.$log.error('BasicAuthAccountService::createAccount Failed: Invalid JSON format');
+                    deferred.reject("BasicAuthAccountService::createAccount Failed: Invalid JSON format");
+                } else {
+                    deferred.resolve(res.data);
+                }
+            })
+            .catch(errRes => {
+                deferred.reject(errRes);
+            });
+            return deferred.promise;
+        }
+        
+        updateAccount(accountId, newAccount) {
+        	let deferred = this.$q.defer();
+        	this.$http({
+                method: "PUT",
+                url: this.conf.api.basicAuthAccount + "/" + accountId,
+                data: newAccount,
+                cache: false,
+                headers:{
+            		'X-ECOMP-RequestID':this.uuid.generate()
+                }
+            }).then(res => {
+            	if (res == null || res.data == null) {
+        	 		this.$log.error('BasicAuthAccountService::updateAccount Failed: Result or result.data is null');
+                     deferred.reject("BasicAuthAccountService::updateAccount Failed: Result or result.data is null");
+                } else if (!this.utilsService.isValidJSON(res.data)) {
+                 	this.$log.error('BasicAuthAccountService::updateAccount Failed: Invalid JSON format');
+                    deferred.reject("BasicAuthAccountService::updateAccount Failed: Invalid JSON format");
+                } else {
+                    deferred.resolve(res.data);
+                }
+            })
+            .catch(errRes => {
+                deferred.reject(errRes);
+            });
+            return deferred.promise;
+        }
+        
+        getAccountList() {
+        	let deferred = this.$q.defer();
+        	this.$http({
+                method: "GET",
+                url: this.conf.api.basicAuthAccount,
+                cache: false,
+                headers:{
+            		'X-ECOMP-RequestID':this.uuid.generate()
+                }
+            }).then(res => {
+            	if (res == null || res.data == null) {
+        	 		this.$log.error('BasicAuthAccountService::getAccountList Failed: Result or result.data is null');
+                     deferred.reject("BasicAuthAccountService::getAccountList Failed: Result or result.data is null");
+                } else if (!this.utilsService.isValidJSON(res.data)) {
+                 	this.$log.error('BasicAuthAccountService::getAccountList Failed: Invalid JSON format');
+                    deferred.reject("BasicAuthAccountService::getAccountList Failed: Invalid JSON format");
+                } else {
+                    deferred.resolve(res.data.response);
+                }
+            })
+            .catch(errRes => {
+                deferred.reject(errRes);
+            });
+            return deferred.promise;
+        }
+        
+        deleteAccount(accountId) {
+        	let deferred = this.$q.defer();
+        	this.$http({
+                method: "DELETE",
+                url: this.conf.api.basicAuthAccount + "/" + accountId,
+                cache: false,
+                headers:{
+            		'X-ECOMP-RequestID':this.uuid.generate()
+                }
+            }).then(res => {
+            	if (res == null || res.data == null) {
+        	 		this.$log.error('BasicAuthAccountService::deleteAccount Failed: Result or result.data is null');
+                     deferred.reject("BasicAuthAccountService::deleteAccount Failed: Result or result.data is null");
+                } else if (!this.utilsService.isValidJSON(res.data)) {
+                 	this.$log.error('BasicAuthAccountService::deleteAccount Failed: Invalid JSON format');
+                    deferred.reject("BasicAuthAccountService::deleteAccount Failed: Invalid JSON format");
+                } else {
+                    deferred.resolve(res.data);
+                }
+            })
+            .catch(errRes => {
+                deferred.reject(errRes);
+            });
+            return deferred.promise;
+        }
+       
+    }
+    
+    BasicAuthAccountService.$inject = ['$q', '$log', '$http', 'conf','uuid4', 'utilsService'];
+    angular.module('ecompApp').service('basicAuthAccountService', BasicAuthAccountService)
+})();
diff --git a/ecomp-portal-FE-common/client/app/services/be-property-reader/be-property-reader.service.js b/ecomp-portal-FE-common/client/app/services/be-property-reader/be-property-reader.service.js
new file mode 100644
index 0000000..b3ad6b3
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/services/be-property-reader/be-property-reader.service.js
@@ -0,0 +1,70 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+/**

+ * Created by nnaffar on 11/22/2015.

+ */

+'use strict';

+

+(function () {

+    class BeReaderService {

+        constructor($q, $log, $http, conf, uuid, utilsService) {

+            this.$q = $q;

+            this.$log = $log;

+            this.$http = $http;

+            this.conf = conf;

+            this.uuid = uuid;

+            this.utilsService = utilsService;

+        }

+

+        getProperty(property) {

+            let deferred = this.$q.defer();

+            //this.$log.info('BeReaderService::get all applications admins list');

+            

+            let url = this.conf.api.beProperty + "?key=" + property;

+            this.$http({

+                    method: "GET",

+                    cache: false,

+                    url: url,

+                    headers: {

+                        'X-ECOMP-RequestID':this.uuid.generate()

+                    }

+                })

+                .then( res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success

+                    //if (this.utilsService.isValidJSON(res)=== false) {

+                    //	this.$log.error('BeReaderService::getAccountAdmins Failed');

+                    //    deferred.reject("BeReaderService::getAccountAdmins Failed");

+                    //} else {

+                        // this.$log.info('BeReaderService::getAccountAdmins Succeeded');

+                    deferred.resolve(res.data);

+                    //}

+                })

+                .catch( status => {

+                	this.$log.error('BeReaderService::getAccountAdmins Failed', status);

+                    deferred.reject(status);

+                });

+           

+            return deferred.promise;

+

+        }

+    }

+    BeReaderService.$inject = ['$q', '$log', '$http', 'conf','uuid4', 'utilsService'];

+    angular.module('ecompApp').service('beReaderService', BeReaderService)

+})();

diff --git a/ecomp-portal-FE-common/client/app/services/catalog/catalog.service.js b/ecomp-portal-FE-common/client/app/services/catalog/catalog.service.js
new file mode 100644
index 0000000..63d5b96
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/services/catalog/catalog.service.js
@@ -0,0 +1,172 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+

+(function () {

+    class CatalogService {

+    	

+        constructor($q, $log, $http, conf, uuid, utilsService) {

+            this.$q = $q;

+            this.$log = $log;

+            this.$http = $http;

+            this.conf = conf;

+            this.uuid = uuid;

+            this.debug = false;

+            this.utilsService = utilsService;

+        }

+

+        getAppCatalog() {

+            let deferred = this.$q.defer();

+            this.$http({

+                	method: "GET",

+                	url: this.conf.api.appCatalog,

+                    cache: false,

+                    headers: {

+                        'X-ECOMP-RequestID':this.uuid.generate()

+                    }

+                })

+                .then( res => {

+                	if (this.debug)

+                		this.$log.debug('CatalogService::getAppCatalog: result is ' + JSON.stringify(res));

+                    // Res is always JSON, but the data object might be an HTML error page.

+                    if (! this.utilsService.isValidJSON(res.data)) {

+                    	var msg = 'CatalogService::getAppCatalog: result data is not JSON';

+                    	if (this.debug)

+                    		this.$log.debug(msg);

+                    	deferred.reject(msg);

+                    } else {

+                    	if (this.debug)

+                    		this.$log.debug('CatalogService::getAppCatalog: success');

+                        deferred.resolve(res.data);

+                    }

+                })

+                .catch( status => {

+                	this.$log.error('CatalogService:getAppCatalog failed: ' + status);

+                    deferred.reject(status);

+                });

+            return deferred.promise;

+        }

+        

+        // Expects an object with fields matching model class AppCatalogSelection:

+        // appId (number), select (boolean), pending (boolean).

+        updateAppCatalog(appData) {

+            let deferred = this.$q.defer();

+            // Validate the request, maybe this is overkill

+            if (appData == null || appData.appId == null || appData.select == null) {

+            	var msg = 'CatalogService::updateAppCatalog: field appId and/or select not found';

+                this.$log.error(msg);

+                return deferred.reject(msg);

+            }

+            this.$http({

+                method: "PUT",

+                url: this.conf.api.appCatalog,

+                data: appData,

+                headers: {

+                    'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then( res => {

+                    // Detect missing result

+                    if (res == null || res.data == null) {

+                        deferred.reject("CatalogService::updateAppCatalog Failed");

+                    } else {

+                        deferred.resolve(res.data);

+                    }

+                })

+                .catch( status => {

+                	this.$log.error('CatalogService:updateAppCatalog failed: ' + status);

+                    deferred.reject(status);

+                });

+            return deferred.promise;

+        }

+        

+        // Expects an object with fields and used to update records for ep_pers_user_app_man_sort table:

+        // appId (number), select (boolean).

+        updateManualAppSort(appData) {

+            let deferred = this.$q.defer();

+            

+            // Validate the request, maybe this is overkill

+            if (appData == null || appData.appId == null || appData.select == null) {

+            	var msg = 'CatalogService::updateManualAppSort: field appId and/or select not found';

+                this.$log.error(msg);

+                return deferred.reject(msg);

+            }

+            this.$http({

+                method: "PUT",

+                url: this.conf.api.UpdateUserAppsSortManual,

+                data: appData,

+                headers: {

+                    'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then( res => {

+                    // Detect missing result

+                    if (res == null || res.data == null) {

+                        deferred.reject("CatalogService::updateManualAppSort Failed");

+                    } else {

+                        deferred.resolve(res.data);

+                    }

+                })

+                .catch( status => {

+                	this.$log.error('CatalogService:updateManualAppSort failed: ' + status);

+                    deferred.reject(status);

+                });

+            

+            return deferred.promise;

+        }

+           

+        getuserAppRolesCatalog(item) {

+            let deferred = this.$q.defer();

+            this.$http({

+                	method: "GET",

+                	url: this.conf.api.appCatalogRoles,

+                	params:{appName:item},

+                    cache: false,

+                    headers: {

+                        'X-ECOMP-RequestID':this.uuid.generate()

+                    }

+                })

+                .then( res => {

+                	if (this.debug)

+                		this.$log.debug('CatalogService::getAppCatalog: result is ' + JSON.stringify(res));

+                    // Res is always JSON, but the data object might be an HTML error page.

+                    if (! this.utilsService.isValidJSON(res.data)) {

+                    	var msg = 'CatalogService::getAppCatalog: result data is not JSON';

+                    	if (this.debug)

+                    		this.$log.debug(msg);

+                    	deferred.reject(msg);

+                    } else {

+                    	if (this.debug)

+                    		this.$log.debug('CatalogService::getAppCatalog: success');

+                        deferred.resolve(res.data);

+                    }

+                })

+                .catch( status => {

+                	this.$log.error('CatalogService:getAppCatalog failed: ' + status);

+                    deferred.reject(status);

+                });

+            return deferred.promise;

+        }

+        

+        

+

+    }

+    

+    CatalogService.$inject = ['$q', '$log', '$http', 'conf','uuid4', 'utilsService'];

+    angular.module('ecompApp').service('catalogService', CatalogService)

+})();

diff --git a/ecomp-portal-FE-common/client/app/services/confirm-box/confirm-box.service.js b/ecomp-portal-FE-common/client/app/services/confirm-box/confirm-box.service.js
new file mode 100644
index 0000000..97ebb1e
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/services/confirm-box/confirm-box.service.js
@@ -0,0 +1,236 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+/**

+ * Created by nnaffar on 1/18/16.

+ */

+'use strict';

+

+(function () {

+    class ConfirmBoxService {

+        constructor($q, $log, ngDialog) {

+            this.$q = $q;

+            this.$log = $log;

+            this.ngDialog = ngDialog;

+        }

+

+        showInformation(message) {

+            let deferred = this.$q.defer();

+            this.ngDialog.open({

+                templateUrl: 'app/views/confirmation-box/information-box.tpl.html',

+                controller: 'ConfirmationBoxCtrl',

+                controllerAs: 'confirmBox',

+                className: 'confirm-box ngdialog-theme-default',

+                showClose: false,

+                data: {

+                    message: message

+                }

+            }).closePromise.then(confirmed => {

+                deferred.resolve(confirmed.value);

+            }).catch(err => {

+                deferred.reject(err);

+            });

+            return deferred.promise;

+        };

+        

+        editItem(message) {

+            let deferred = this.$q.defer();

+            this.ngDialog.open({

+                templateUrl: 'app/views/confirmation-box/confirmation-box.tpl.html',

+                controller: 'ConfirmationBoxCtrl',

+                controllerAs: 'confirmBox',

+                className: 'confirm-box ngdialog-theme-default',

+                showClose: false,

+                data: {

+                    message: message

+                }

+            }).closePromise.then(confirmed => {

+                deferred.resolve(confirmed.value);

+            }).catch(err => {

+                deferred.reject(err);

+            });

+            return deferred.promise;

+        };

+       

+        

+        showDynamicInformation(message, templatePath, controller) {

+            let deferred = this.$q.defer();

+            this.ngDialog.open({

+            	templateUrl: templatePath,

+                controller: controller,

+                controllerAs: 'confirmBox',

+                className: 'confirm-box ngdialog-theme-default',

+                showClose: false,

+                data: {

+                    message: message

+                }

+            }).closePromise.then(confirmed => {

+                deferred.resolve(confirmed.value);

+            }).catch(err => {

+                deferred.reject(err);

+            });

+            return deferred.promise;

+        };

+        

+        confirm(message) {

+            let deferred = this.$q.defer();

+            this.ngDialog.open({

+                templateUrl: 'app/views/confirmation-box/confirmation-box.tpl.html',

+                controller: 'ConfirmationBoxCtrl',

+                controllerAs: 'confirmBox',

+                className: 'confirm-box ngdialog-theme-default',

+                showClose: false,

+                data: {

+                    message: message

+                }

+            }).closePromise.then(confirmed => {

+                deferred.resolve(confirmed.value);

+            }).catch(err => {

+                deferred.reject(err);

+            });

+            return deferred.promise;

+        };

+

+        deleteItem(item) {

+            let deferred = this.$q.defer();

+            this.ngDialog.open({

+                templateUrl: 'app/views/confirmation-box/confirmation-box.tpl.html',

+                controller: 'ConfirmationBoxCtrl',

+                controllerAs: 'confirmBox',

+                className: 'confirm-box ngdialog-theme-default',

+                showClose: false,

+                data: {

+                    item: item,

+                    title: 'Functional Menu - Delete'

+                }

+            }).closePromise.then(confirmed => {

+                deferred.resolve(confirmed.value);

+            }).catch(err => {

+                deferred.reject(err);

+            });

+            return deferred.promise;

+        };

+

+        moveMenuItem(message) {

+            let deferred = this.$q.defer();

+            this.ngDialog.open({

+                templateUrl: 'app/views/confirmation-box/dragdrop-confirmation-box.tpl.html',

+                controller: 'ConfirmationBoxCtrl',

+                controllerAs: 'confirmBox',

+                className: 'confirm-box ngdialog-theme-default',

+                showClose: false,

+                data: {

+                    message: message,

+                    title:'Functional Menu - Move'

+                }

+            }).closePromise.then(confirmed => {

+                deferred.resolve(confirmed.value);

+            }).catch(err => {

+                deferred.reject(err);

+            });

+            return deferred.promise;

+        };

+

+        makeAdminChanges(message) {

+            let deferred = this.$q.defer();

+            this.ngDialog.open({

+                templateUrl: 'app/views/confirmation-box/admin-confirmation-box.tpl.html',

+                controller: 'ConfirmationBoxCtrl',

+                controllerAs: 'confirmBox',

+                className: 'confirm-box ngdialog-theme-default',

+                showClose: false,

+                data: {

+                    message: message,

+                    title: 'Admin Update'

+                }

+            }).closePromise.then(confirmed => {

+                deferred.resolve(confirmed.value);

+            }).catch(err => {

+                deferred.reject(err);

+            });

+            return deferred.promise;

+        };

+        

+        

+        makeUserAppRoleCatalogChanges(message) {

+            let deferred = this.$q.defer();

+            this.ngDialog.open({

+                templateUrl: 'app/views/confirmation-box/admin-confirmation-box.tpl.html',

+                controller: 'ConfirmationBoxCtrl',

+                controllerAs: 'confirmBox',

+                className: 'confirm-box ngdialog-theme-default',

+                showClose: false,

+                data: {

+                    message: message,

+                    title: 'UserRoles Update'

+                }

+            }).closePromise.then(confirmed => {

+                deferred.resolve(confirmed.value);

+            }).catch(err => {

+                deferred.reject(err);

+            });

+            return deferred.promise;

+        };

+     

+        

+        webAnalyticsChanges(message) {

+            let deferred = this.$q.defer();

+            this.ngDialog.open({

+                templateUrl: 'app/views/confirmation-box/admin-confirmation-box.tpl.html',

+                controller: 'ConfirmationBoxCtrl',

+                controllerAs: 'confirmBox',

+                className: 'confirm-box ngdialog-theme-default',

+                showClose: false,

+                data: {

+                    message: message,

+                    title: 'Add WebAnalytics Source'

+                }

+            }).closePromise.then(confirmed => {

+                deferred.resolve(confirmed.value);

+            }).catch(err => {

+                deferred.reject(err);

+            });

+            return deferred.promise;

+        };

+

+        

+        updateWebAnalyticsReport(message) {

+            let deferred = this.$q.defer();

+            this.ngDialog.open({

+                templateUrl: 'app/views/confirmation-box/admin-confirmation-box.tpl.html',

+                controller: 'ConfirmationBoxCtrl',

+                controllerAs: 'confirmBox',

+                className: 'confirm-box ngdialog-theme-default',

+                showClose: false,

+                data: {

+                    message: message,

+                    title: 'Update WebAnalytics Source'

+                }

+            }).closePromise.then(confirmed => {

+                deferred.resolve(confirmed.value);

+            }).catch(err => {

+                deferred.reject(err);

+            });

+            return deferred.promise;

+        };

+

+    }

+    ConfirmBoxService.$inject = ['$q', '$log', 'ngDialog'];

+    angular.module('ecompApp').service('confirmBoxService', ConfirmBoxService)

+})();

diff --git a/ecomp-portal-FE-common/client/app/services/contact-us/contact-us.service.js b/ecomp-portal-FE-common/client/app/services/contact-us/contact-us.service.js
new file mode 100644
index 0000000..45c95e9
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/services/contact-us/contact-us.service.js
@@ -0,0 +1,247 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+/**

+ * Created by robertlo on 10/10/2016.

+ */

+'use strict';

+

+(function () {

+    class ContactUsService {

+        constructor($q, $log, $http, conf, uuid) {

+            this.$q = $q;

+            this.$log = $log;

+            this.$http = $http;

+            this.conf = conf;

+            this.uuid = uuid;

+        }

+        getListOfApp() {

+            // this.$log.info('ContactUsService::getListOfavailableApps: get all app list');

+            let deferred = this.$q.defer();

+            // this.$log.info('ContactUsService::getListOfavailableApps: ', this.conf.api.listOfApp);

+            this.$http({

+                method: "GET",

+                url: this.conf.api.availableApps,           

+                cache: false

+            }).then( res => {

+                // If response comes back as a redirected HTML page which IS NOT a success

+            	// this.$log.info('ContactUsService::getListOfavailableApps availableApps response: ', res);

+                if (Object.keys(res).length == 0) {

+                    deferred.reject("ContactUsService::getListOfavailableApps: Failed"); 

+                } else {

+                    // this.$log.debug('ContactUsService::getListOfavailableApps: Succeeded results: ', res);

+                    deferred.resolve(res);

+                }

+            }).catch( status => {

+            	this.$log.error('ContactUsService::getListOfavailableApps: query error: ',status);

+                deferred.reject(status);

+            });

+            return deferred.promise;           

+        }

+        

+        getContactUs() {

+            let deferred = this.$q.defer();

+            // this.$log.info('ContactUsService::get all Contact Us list');

+            this.$http({

+                    url: this.conf.api.getContactUS,

+                    method: 'GET',

+                    cache: false,

+                    headers: {

+                        'X-ECOMP-RequestID':this.uuid.generate()

+                    }

+                }).then(res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success

+                    if (Object.keys(res.data).length == 0) {

+                        deferred.reject("ContactUsService::getContactUs Failed");

+                    } else {

+                        deferred.resolve(res.data);

+                    }

+                })

+                .catch(status => {

+                    deferred.reject(status);

+                });

+            return deferred.promise;

+        }

+        

+        getAppsAndContacts() {

+            let deferred = this.$q.defer();

+            // this.$log.info('ContactUsService::getAppsAndContacts');

+            this.$http({

+                    url: this.conf.api.getAppsAndContacts,

+                    method: 'GET',

+                    cache: false,

+                    headers: {

+                        'X-ECOMP-RequestID':this.uuid.generate()

+                    }

+                }).then(res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success

+                    if (Object.keys(res.data).length == 0) {

+                        deferred.reject("ContactUsService::getAppsAndContacts Failed");

+                    } else {

+                        deferred.resolve(res.data);

+                    }

+                })

+                .catch(status => {

+                    deferred.reject(status);

+                });

+            return deferred.promise;

+        }

+

+        getContactUSPortalDetails(){

+        	let deferred = this.$q.defer();

+            // this.$log.info('ContactUsService::get all Contact Us Portal Details');

+            this.$http({

+                    url: this.conf.api.getContactUSPortalDetails,

+                    method: 'GET',

+                    cache: false,

+                    headers: {

+                        'X-ECOMP-RequestID':this.uuid.generate()

+                    }

+                }).then(res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success

+                    if (Object.keys(res.data).length == 0) {

+                        deferred.reject("ContactUsService::getContactUSPortalDetails Failed");

+                    } else {

+                        deferred.resolve(res.data);

+                    }

+                })

+                .catch(status => {

+                    deferred.reject(status);

+                });

+            return deferred.promise;

+        }

+

+        getAppCategoryFunctions(){

+        	let deferred = this.$q.defer();

+            // this.$log.info('ContactUsService::get all App Category Functions');

+            this.$http({

+                    url: this.conf.api.getAppCategoryFunctions,

+                    method: 'GET',

+                    cache: false,

+                    headers: {

+                        'X-ECOMP-RequestID':this.uuid.generate()

+                    }

+                }).then(res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success

+                    if (Object.keys(res.data).length == 0) {

+                        deferred.reject("ContactUsService::getAppCategoryFunctions Failed");

+                    } else {

+                        deferred.resolve(res.data);

+                    }

+                })

+                .catch(status => {

+                    deferred.reject(status);

+                });

+            return deferred.promise;

+        }

+        

+        addContactUs(newContactUs) {

+        	// this.$log.info('ContactUsService::add a new Contact Us');

+        	// this.$log.info(newContactUs)

+        	let deferred = this.$q.defer();

+            // this.$log.info('ContactUsService:: add Contact Us' + JSON.stringify(newContactUs));

+            

+            var contactUsObj={

+            		appId:newContactUs.app.value,

+            		appName:newContactUs.app.title,

+            		description:newContactUs.desc,

+            		contactName:newContactUs.name,

+            		contactEmail:newContactUs.email,

+            		url:newContactUs.url,            		

+            };

+            this.$http({

+                url: this.conf.api.saveContactUS,

+                method: 'POST',

+                cache: false,

+                headers: {

+                    'X-ECOMP-RequestID':this.uuid.generate()

+                },

+                data: contactUsObj

+            }).then(res => {

+                // this.$log.info('ContactUsService:: add Contact Us res' ,res);

+                // If response comes back as a redirected HTML page which IS NOT a success

+                if (res==null || Object.keys(res.data).length == 0 || res.data.message == 'failure') {

+                    deferred.reject("Add Contact Us failed");

+                    this.$log.error('ContactUsService:: add Contact Us failed');

+                } else {

+                    deferred.resolve(res.data);

+                }

+            }).catch(errRes => {

+                   deferred.reject(errRes);

+             });

+            return deferred.promise;

+        }

+        

+        modifyContactUs(contactUsObj) {

+        	// this.$log.info('ContactUsService::edit Contact Us',contactUsObj);        	

+        	let deferred = this.$q.defer();

+            this.$http({

+                url: this.conf.api.saveContactUS,

+                method: 'POST',

+                cache: false,

+                headers: {

+                    'X-ECOMP-RequestID':this.uuid.generate()

+                },

+                data: contactUsObj

+            }).then(res => {

+                // this.$log.info('ContactUsService:: edit Contact Us res' ,res);

+                // If response comes back as a redirected HTML page which IS NOT a success

+                if (res==null || Object.keys(res.data).length == 0 || res.data.message == 'failure') {

+                    deferred.reject("Edit Contact Us failed");

+                    this.$log.error('ContactUsService:: edit Contact Us failed');

+                } else {

+                    deferred.resolve(res.data);

+                }

+            }).catch(errRes => {

+                   deferred.reject(errRes);

+             });

+            return deferred.promise;

+        }

+

+        removeContactUs(id) {

+            let deferred = this.$q.defer();

+            let url = this.conf.api.deleteContactUS + '/' + id;

+            // this.$log.info('ContactUsService:: remove Contact Us');

+            this.$http({

+                url: url,

+                method: 'POST',

+                cache: false,

+                data: '',

+                headers: {

+                    'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then(res => {

+                // If response comes back as a redirected HTML page which IS NOT a success

+            	// this.$log.info("ContactUsService::removeContactUs res",res);

+            	deferred.resolve(res.data);

+                if (Object.keys(res.data).length == 0) {

+                    deferred.reject("ContactUsService::removeContactUs Failed");

+                } else {

+                    deferred.resolve(res.data);

+                }

+            }).catch(errRes => {

+                deferred.reject(errRes);

+            });

+

+            return deferred.promise;

+        }

+    }

+    ContactUsService.$inject = ['$q', '$log', '$http', 'conf', 'uuid4'];

+    angular.module('ecompApp').service('contactUsService', ContactUsService)

+})();

diff --git a/ecomp-portal-FE-common/client/app/services/dashboard/dashboard.service.js b/ecomp-portal-FE-common/client/app/services/dashboard/dashboard.service.js
new file mode 100644
index 0000000..c4b2e57
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/services/dashboard/dashboard.service.js
@@ -0,0 +1,185 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+/**

+ * Created by robertlo on 09/26/2016.

+ */

+'use strict';

+

+(function () {

+    class DashboardService {

+        constructor($q, $log, $http, conf, uuid) {

+            this.$q = $q;

+            this.$log = $log;

+            this.$http = $http;

+            this.conf = conf;

+            this.dashboardService = null;

+            this.uuid = uuid;

+        }

+

+        getCommonWidgetData(widgetType) {

+            // this.$log.info('ecomp::dashboard-service::getting news data');

+            let deferred = this.$q.defer();

+            let url = this.conf.api.commonWidget + '?resourceType=' + widgetType;

+         	 

+            this.$http({

+                    method: "GET",

+                    url: url,

+                    cache: false,

+                    headers: {

+                        'X-ECOMP-RequestID':this.uuid.generate()

+                    }

+                })

+                .then( res => {

+                	 // this.$log.info('ecomp::dashboard-service::getting news data',res);

+                    // If response comes back as a redirected HTML page which IS NOT a success

+                    if (Object.keys(res.data).length == 0 || Object.keys(res.data.response) ==null || Object.keys(res.data.response.items) ==null) {

+                        deferred.reject("ecomp::dashboard-service::getNewsData Failed");

+                    } else {

+                        this.userProfile = res.data;

+                        // this.$log.info('ecomp::dashboard-service::getNewsData Succeeded');

+                        deferred.resolve(res.data);

+                    }

+                })

+                .catch( status => {

+                    deferred.reject(status);

+                });

+            return deferred.promise;

+        }

+        

+        saveCommonWidgetData(newData){

+            let deferred = this.$q.defer();

+            //this.$log.info('ecomp::dashboard-service::saveCommonWidgetData');

+            //this.$log.debug('ecomp::dashboard-service::saveCommonWidgetData with:', newData);

+

+            this.$http({

+                method: "POST",

+                url: this.conf.api.commonWidget,

+                data: newData,

+                cache: false,

+                headers: {

+                    'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then( res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success

+            		// this.$log.info(res.data);

+                    if (Object.keys(res.data).length == 0) {

+                        deferred.reject("ecomp::dashboard-service::saveCommonWidgetData Failed");

+                    } else {

+                        // this.$log.info('ecomp::dashboard-service::saveCommonWidgetData Succeeded');

+                        deferred.resolve(res.data);

+                    }

+                })

+                .catch( status => {

+                    deferred.reject(status);

+                });

+            return deferred.promise;

+        }

+        

+        

+        

+        removeCommonWidgetData(widgetToRemove){

+            let deferred = this.$q.defer();

+            // this.$log.info('ecomp::dashboard-service::removeCommonWidgetData');

+            // this.$log.debug('ecomp::dashboard-service::removeCommonWidgetData with:', widgetToRemove);

+            this.$http({

+                method: "POST",

+                url: this.conf.api.deleteCommonWidget,

+                data: widgetToRemove,

+                cache: false,

+                headers: {

+                    'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then( res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success

+            		// this.$log.info(res.data);

+                    if (Object.keys(res.data).length == 0) {

+                        deferred.reject("ecomp::dashboard-service::saveCommonWidgetData Failed");

+                    } else {

+                        // this.$log.info('ecomp::dashboard-service::saveCommonWidgetData Succeeded');

+                        deferred.resolve(res.data);

+                    }

+                })

+                .catch( status => {

+                    deferred.reject(status);

+                });

+            return deferred.promise;

+        }

+

+        getSearchAllByStringResults(searchStr) {

+        	// this.$log.info('ecomp::getSearchAllByStringResults::getting search by string results');

+            let deferred = this.$q.defer();

+            let url = this.conf.api.getSearchAllByStringResults;

+            

+            this.$http({

+                    method: "GET",

+                    url : url,

+                    params : {

+                    	'searchString' : searchStr

+                    },

+                    cache: false,

+                    headers: {

+                        'X-ECOMP-RequestID':this.uuid.generate()

+                    }

+                }).then( res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success

+                    if (Object.keys(res.data).length == 0) {

+                        deferred.reject("ecomp::dashboard-service::getSearchAllByStringResults Failed");

+                    } else {

+                        //this.searchResults = res.data;

+                        // this.$log.info('ecomp::dashboard-service::getSearchAllByStringResults Succeeded');

+                        deferred.resolve(res.data.response);

+                    }

+                }).catch( status => {

+                    this.$log.error('ecomp::getSearchAllByStringResults error');

+                    deferred.reject(status);

+                });

+            return deferred.promise;

+        	

+        }

+        

+        getOnlineUserUpdateRate() {

+            let deferred = this.$q.defer();

+            let url = this.conf.api.onlineUserUpdateRate;

+            this.$http({

+                    method: "GET",

+                    url: url,

+                    cache: false,

+                    headers: {

+                        'X-ECOMP-RequestID':this.uuid.generate()

+                    }

+                }).then( res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success

+                	if (Object.keys(res.data).length == 0) {

+                        deferred.reject("ecomp::dashboard-service::getOnlineUserUpdateRate Failed");

+                    } else {

+                    	// this.$log.info('ecomp::dashboard-service::getOnlineUserUpdateRate Succeeded',res);

+                        deferred.resolve(res.data);

+                    }

+                }).catch( status => {

+                    deferred.reject(status);

+                });

+            return deferred.promise;

+        }

+    

+    }

+    

+    DashboardService.$inject = ['$q', '$log', '$http', 'conf', 'uuid4'];

+    angular.module('ecompApp').service('dashboardService', DashboardService)

+})();

diff --git a/ecomp-portal-FE-common/client/app/services/error-messages/error-messages.service.js b/ecomp-portal-FE-common/client/app/services/error-messages/error-messages.service.js
new file mode 100644
index 0000000..a19f509
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/services/error-messages/error-messages.service.js
@@ -0,0 +1,22 @@
+/*-
+ * ================================================================================
+ * eCOMP Portal
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property
+ * ================================================================================
+ * Licensed 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';
+let errorMessageByCode = {1201: 'Value already exists'};
+angular.module('ecompApp').constant('errorMessageByCode', errorMessageByCode);
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/services/external-request-access-service/external-request-access-service.js b/ecomp-portal-FE-common/client/app/services/external-request-access-service/external-request-access-service.js
new file mode 100644
index 0000000..0ef930c
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/services/external-request-access-service/external-request-access-service.js
@@ -0,0 +1,63 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+

+(function () {

+    class ExternalRequestAccessService {

+        constructor($q, $log, $http, conf, uuid, utilsService) {

+            this.$q = $q;

+            this.$log = $log;

+            this.$http = $http;

+            this.conf = conf;

+            this.uuid = uuid;

+            this.utilsService = utilsService;

+        }

+

+        getExternalRequestAccessServiceInfo() {

+            let deferred = this.$q.defer();

+            var _this = this;

+            let url = this.conf.api.externalRequestAccessSystem;

+            this.$http({

+                    method: "GET",

+                    cache: false,

+                    url: url,

+                    headers: {

+                        'X-ECOMP-RequestID':this.uuid.generate()

+                    }

+                })

+                .then( res => {

+                	if (res == null || res.data == null || _this.utilsService.isValidJSON(res.data) == false) {

+                        deferred.reject("ExternalRequestAccessService::getExternalRequestAccessServiceInfo Failed");

+                    }else{

+                    deferred.resolve(res.data);

+                    }

+                })

+                .catch( status => {

+                	this.$log.error('ExternalRequestAccessService::getExternalRequestAccessServiceInfo Failed', status);

+                    deferred.reject(status);

+                });

+           

+            return deferred.promise;

+

+        }

+    }

+    ExternalRequestAccessService.$inject = ['$q', '$log', '$http', 'conf','uuid4', 'utilsService'];

+    angular.module('ecompApp').service('ExternalRequestAccessService', ExternalRequestAccessService)

+})();

diff --git a/ecomp-portal-FE-common/client/app/services/functionalMenu/functionalMenu.service.js b/ecomp-portal-FE-common/client/app/services/functionalMenu/functionalMenu.service.js
new file mode 100644
index 0000000..742b984
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/services/functionalMenu/functionalMenu.service.js
@@ -0,0 +1,318 @@
+/*-
+ * ================================================================================
+ * eCOMP Portal
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property
+ * ================================================================================
+ * Licensed 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';
+
+(function () {
+    class FunctionalMenuService {
+        constructor($q, $log, $http, conf, uuid, utilsService) {
+            this.$q = $q;
+            this.$log = $log;
+            this.$http = $http;
+            this.conf = conf;
+            this.uuid = uuid;
+            this.utilsService = utilsService;
+        }
+
+
+        getManagedRolesMenu( appId )
+        {
+            let deferred = this.$q.defer();
+            this.$log.info('FunctionalMenuService::getManagedRolesMenu');
+            let url = this.conf.api.appRoles.replace(':appId', appId);
+            this.$log.info('FunctionalMenuService::getManagedRolesMenu url: '+url);
+
+            this.$http({
+                method: 'GET',
+                url: url,
+                cache: false,
+                headers: {
+                    'X-ECOMP-RequestID':this.uuid.generate()
+                }
+            }).then(res => {
+                    // If response comes back as a redirected HTML page which IS NOT a success
+                    if (this.utilsService.isValidJSON(res)== false) {
+                        deferred.reject('functionalMenu.service::getManagedRolesMenu Failed');
+                    } else {
+                        this.$log.info('functionalMenu.service::getManagedRolesMenu succeeded: ');
+                        deferred.resolve(res.data);
+                    }
+                })
+                .catch(status => {
+                    deferred.reject(status);
+                });
+            return deferred.promise;
+        }
+
+        getAvailableApplications()
+        {
+            let deferred = this.$q.defer();
+            this.$log.info('FunctionalMenuService::getManagedRolesMenu:getAvailableApplications');
+
+            this.$http({
+                method: 'GET',
+//                url: this.conf.api.availableApps,
+                url: this.conf.api.allAvailableApps,
+                cache: false,
+                headers: {
+                    'X-ECOMP-RequestID':this.uuid.generate()
+                }
+            }).then(res => {
+                    // If response comes back as a redirected HTML page which IS NOT a success
+                    if (this.utilsService.isValidJSON(res)== false) {
+                        deferred.reject('FunctionalMenuService::getManagedRolesMenu:getAvailableApplications Failed');
+                    } else {
+                        this.$log.info('FunctionalMenuService::getManagedRolesMenu:getAvailableApplications succeeded: ');
+                        deferred.resolve(res.data);
+                    }
+                })
+                .catch(status => {
+                    deferred.reject(status);
+                });
+            return deferred.promise;
+        }
+        getMenuDetails( menuId )
+        {
+            let deferred = this.$q.defer();
+            this.$log.info('FunctionalMenuService::getMenuDetails:getMenuDetails');
+            let url = this.conf.api.functionalMenuItemDetails.replace(':menuId',menuId);
+            this.$log.info('FunctionalMenuService::getMenuDetails url: '+url);
+
+
+            this.$http({
+                method: 'GET',
+                url: url,
+                cache: false,
+                headers: {
+                    'X-ECOMP-RequestID':this.uuid.generate()
+                }
+            }).then(res => {
+                    // If response comes back as a redirected HTML page which IS NOT a success
+                if (this.utilsService.isValidJSON(res)== false) {
+                        deferred.reject('FunctionalMenuService::getMenuDetails:getMenuDetails Failed');
+                    } else {
+                        this.$log.info('FunctionalMenuService::getMenuDetails:getMenuDetails succeeded: ');
+                        deferred.resolve(res.data);
+                    }
+                })
+                .catch(status => {
+                    deferred.reject(status);
+                });
+            return deferred.promise;
+        }
+
+
+        getManagedFunctionalMenu() {
+            let deferred = this.$q.defer();
+            this.$log.info('FunctionalMenuService::getMenuDetails:getManagedFunctionalMenu');
+
+            this.$http({
+                method: 'GET',
+//                url: this.conf.api.functionalMenuForAuthUser,
+                url: this.conf.api.functionalMenuForEditing,
+                cache: false,
+                headers: {
+                    'X-ECOMP-RequestID':this.uuid.generate()
+                }
+            }).then(res => {
+                    // If response comes back as a redirected HTML page which IS NOT a success
+                    if (this.utilsService.isValidJSON(res)== false) {
+                        deferred.reject('FunctionalMenuService::getManagedFunctionalMenu Failed');
+                    } else {
+                        this.$log.info('FunctionalMenuService::getManagedFunctionalMenu succeeded: ');
+                        deferred.resolve(res.data);
+                    }
+                })
+                .catch(status => {
+                    deferred.reject(status);
+                });
+            return deferred.promise;
+        }
+        
+        getManagedFunctionalMenuForNotificationTree() {
+            let deferred = this.$q.defer();
+            this.$log.info('FunctionalMenuService::getMenuDetails:getManagedFunctionalMenuForNotificationTree');
+            this.$http({
+                method: 'GET',
+                url: this.conf.api.functionalMenuForNotificationTree,
+                cache: false,
+                headers: {
+                    'X-ECOMP-RequestID':this.uuid.generate()
+                }
+            }).then(res => {
+                    // If response comes back as a redirected HTML page which IS NOT a success
+                    if (this.utilsService.isValidJSON(res)== false) {
+                        deferred.reject('FunctionalMenuService::getManagedFunctionalMenuForNotificationTree Failed');
+                        
+                    } else {
+                        this.$log.info('FunctionalMenuService::getManagedFunctionalMenuForNotificationTree succeeded: ');
+                        deferred.resolve(res.data);
+                    }
+                })
+                .catch(status => {
+                    deferred.reject(status);
+                });
+            return deferred.promise;
+        }
+
+
+        regenerateFunctionalMenuAncestors() {
+            let deferred = this.$q.defer();
+            this.$log.info('FunctionalMenuService::regenerateFunctionalMenuAncestors');
+
+            this.$http({
+                method: 'GET',
+                url: this.conf.api.regenerateFunctionalMenuAncestors,
+                cache: false,
+                headers: {
+                    'X-ECOMP-RequestID':this.uuid.generate()
+                }
+            }).then(res => {
+                    // If response comes back as a redirected HTML page which IS NOT a success
+                    if (this.utilsService.isValidJSON(res)== false) {
+                        deferred.reject('FunctionalMenuService::regenerateFunctionalMenuAncestors Failed');
+                    } else {
+                        this.$log.info('FunctionalMenuService::regenerateFunctionalMenuAncestors succeeded: ');
+                        deferred.resolve(res.data);
+                    }
+                })
+                .catch(status => {
+                    deferred.reject(status);
+                });
+            return deferred.promise;
+        }
+
+        saveEditedMenuItem(menuData) {
+            let deferred = this.$q.defer();
+            this.$log.info('FunctionalMenuService::saveEditedMenuItem: ' + menuData);
+        
+            let url = this.conf.api.functionalMenuItem;
+            this.$http({
+                method: 'PUT',
+                url: url,
+                cache: false,
+                data: menuData,
+                headers: {
+                    'X-ECOMP-RequestID':this.uuid.generate()
+                }
+            }).then(res => {
+                // If response comes back as a redirected HTML page which IS NOT a success
+                if (this.utilsService.isValidJSON(res)== false) {
+                    deferred.reject('FunctionalMenuService::saveEditedMenuItem Failed');
+                } else {
+                    this.$log.info('FunctionalMenuService::saveEditedMenuItem succeeded: ');
+                    deferred.resolve(res.data);
+                }
+                })
+                .catch(errRes => {
+                    deferred.reject(errRes);
+                });
+            return deferred.promise;
+        }
+        
+        saveMenuItem(menuData) {
+            let deferred = this.$q.defer();
+            this.$log.info('FunctionalMenuService::saveMenuItem: ' + JSON.stringify(menuData));
+
+            let url = this.conf.api.functionalMenuItem;
+            this.$http({
+                method: 'POST',
+                url: url,
+                cache: false,
+                headers: {
+                    'X-ECOMP-RequestID':this.uuid.generate()
+                },
+                data: menuData
+            }).then(res => {
+                    // If response comes back as a redirected HTML page which IS NOT a success
+                    if (this.utilsService.isValidJSON(res)== false) {
+                        deferred.reject('FunctionalMenuService::saveMenuItem:  Failed');
+                    } else {
+                        this.$log.info('FunctionalMenuService::saveMenuItem:  succeeded: ');
+                        deferred.resolve(res.data);
+                    }
+                })
+                .catch(errRes => {
+                    deferred.reject(errRes);
+                });
+            return deferred.promise;
+        }
+
+
+        deleteMenuItem(menuId) {
+            let deferred = this.$q.defer();
+            let url = this.conf.api.functionalMenuItem + '/' + menuId;
+
+            this.$log.info('FunctionalMenuService::deleteMenuItem: ' +menuId);
+
+            this.$http({
+                method: 'DELETE',
+                url: url,
+                cache: false,
+                data:'',
+                headers: {
+                    'X-ECOMP-RequestID':this.uuid.generate()
+                }
+            }).then(res => {
+                    // If response comes back as a redirected HTML page which IS NOT a success
+                    if (this.utilsService.isValidJSON(res)== false) {
+                        deferred.reject('FunctionalMenuService::deleteMenuItem Failed');
+                    } else {
+                        this.$log.info('FunctionalMenuService::deleteMenuItem succeeded: ');
+                        deferred.resolve(res.data);
+                    }
+                })
+                .catch(errRes => {
+                    deferred.reject(errRes);
+                });
+            return deferred.promise;
+        }
+
+
+        getFunctionalMenuRole()
+        {
+            let deferred = this.$q.defer();
+            this.$log.info('FunctionalMenuService::getFunctionalMenuRole');
+
+            this.$http({
+                method: 'GET',
+                url: this.conf.api.getFunctionalMenuRole,
+                cache: false,
+                headers: {
+                    'X-ECOMP-RequestID':this.uuid.generate()
+                }
+            }).then(res => {
+                    // If response comes back as a redirected HTML page which IS NOT a success
+                    if (this.utilsService.isValidJSON(res)== false) {
+                        deferred.reject('FunctionalMenuService::getFunctionalMenuRole Failed');
+                    } else {
+                        this.$log.info('FunctionalMenuService::getFunctionalMenuRole succeeded: ');
+                        deferred.resolve(res.data);
+                    }
+                })
+                .catch(status => {
+                    deferred.reject(status);
+                });
+            return deferred.promise;
+        }
+
+}
+    FunctionalMenuService.$inject = ['$q', '$log', '$http', 'conf','uuid4', 'utilsService'];
+    angular.module('ecompApp').service('functionalMenuService', FunctionalMenuService)
+})();
diff --git a/ecomp-portal-FE-common/client/app/services/global-constants/global-constants.js b/ecomp-portal-FE-common/client/app/services/global-constants/global-constants.js
new file mode 100644
index 0000000..3e3e1a5
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/services/global-constants/global-constants.js
@@ -0,0 +1,23 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+/**

+ * Created by nnaffar on 1/21/16.

+ */

+angular.module('ecompApp').value('ECOMP_URL_REGEX', /^((?:https?\:\/\/|ftp?\:\/\/)?(w{3}.)?(?:[-a-z0-9]+\.)*[-a-z0-9]+.*)[^-_.]$/i);

diff --git a/ecomp-portal-FE-common/client/app/services/manifest/manifest.service.js b/ecomp-portal-FE-common/client/app/services/manifest/manifest.service.js
new file mode 100644
index 0000000..120cb64
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/services/manifest/manifest.service.js
@@ -0,0 +1,64 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+/**

+ * Created by mlittle on 9/9/2016.

+ */

+'use strict';

+

+(function () {

+    class ManifestService {

+        constructor($q, $log, $http, conf, uuid, utilsService) {

+            this.$q = $q;

+            this.$log = $log;

+            this.$http = $http;

+            this.conf = conf;

+            this.uuid = uuid;

+            this.utilsService = utilsService;

+        }

+

+        getManifest() {

+            let deferred = this.$q.defer();

+            this.$http({

+                method: "GET",

+                url: this.conf.api.getManifest,

+                cache: false,

+                headers: {

+                    'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then( res => {

+                if (this.utilsService.isValidJSON(res)== false) {

+                    this.$log.error('ManifestService.getManifest failed: ');

+                    deferred.reject('ManifestService.getManifest: response.data null or not object');

+                } else {

+                    // this.$log.info('ManifestService.getManifest Succeeded');

+                    // this.$log.debug('ManifestService.getManifest: ', JSON.stringify(res))

+                    deferred.resolve(res.data);

+                }

+            }).catch( status => {

+                this.$log.error('ManifestService.getManifest failed: ' + status.data);

+                deferred.reject(status);

+            });

+            return deferred.promise;

+        }

+

+    }

+    ManifestService.$inject = ['$q', '$log', '$http', 'conf', 'uuid4', 'utilsService'];

+    angular.module('ecompApp').service('manifestService', ManifestService)

+})();

diff --git a/ecomp-portal-FE-common/client/app/services/menus/menus.service.js b/ecomp-portal-FE-common/client/app/services/menus/menus.service.js
new file mode 100644
index 0000000..6cc0eff
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/services/menus/menus.service.js
@@ -0,0 +1,147 @@
+/*-
+ * ================================================================================
+ * eCOMP Portal
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property
+ * ================================================================================
+ * Licensed 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';
+
+(function () {
+  class MenusService {
+    constructor($q, $log, $http, conf, uuid, utilsService) {
+        this.$q = $q;
+        this.$log = $log;
+        this.$http = $http;
+        this.conf = conf;
+        this.uuid = uuid;
+        this.utilsService = utilsService;
+    }
+
+    GetFunctionalMenuForUser() {
+      let deferred = this.$q.defer();
+      // this.$log.info('MenusService::GetFunctionalMenuForUser via REST API');
+      this.$http({
+        method: 'GET',
+        url: this.conf.api.functionalMenuForAuthUser,
+        cache: false,
+        headers: {
+          'X-ECOMP-RequestID':this.uuid.generate()
+        }
+      }).then( res => {
+              // If response comes back as a redirected HTML page which IS NOT a success
+              if (this.utilsService.isValidJSON(res)== false) {
+                  deferred.reject('MenusService::GetFunctionalMenuForUser Failed');
+              } else {
+                  // this.$log.info('MenusService::GetFunctionalMenuForUser success:');
+                  deferred.resolve(res.data);
+              }
+          })
+          .catch( status => {
+            this.$log.error('MenusService::rejection:' + status);
+            deferred.reject(status);
+          });
+
+      return deferred.promise;
+    }
+      
+    getFavoriteItems() {
+      let deferred = this.$q.defer();
+      // this.$log.info('MenusService::getFavoriteItems via REST API');
+        this.$http({
+            method: 'GET',
+            url: this.conf.api.getFavoriteItems +'?date='+new Date().getTime(),
+            cache: false,
+            headers: {
+                'X-ECOMP-RequestID':this.uuid.generate()
+            }
+        }).then( res => {
+                // If response comes back as a redirected HTML page which IS NOT a success
+            if (this.utilsService.isValidJSON(res)== false) {
+                    deferred.reject('MenusService::getFavoriteItems has no favorites');
+                } else {
+                    // this.$log.info('MenusService::getFavoriteItems success:');
+                    deferred.resolve(res.data);
+                }
+            })
+            .catch( status => {
+                this.$log.error('MenusService::getFavoriteItems rejection:' + status);
+                deferred.reject(status);
+            });
+
+        return deferred.promise;
+    }
+    
+    setFavoriteItem(menuId) {
+      let deferred = this.$q.defer();
+      // this.$log.info('menus-service.service::setFavoriteItem  via REST API' + menuId);
+      let url = this.conf.api.setFavoriteItem;
+      this.$http({
+          method: 'POST',
+          url: url,
+          cache: false,
+          headers: {
+              'X-ECOMP-RequestID':this.uuid.generate(),
+              'Content-Type': 'application/json'
+          },
+          data: menuId
+      }).then(res => {
+              // If response comes back as a redirected HTML page which IS NOT a success
+              if (this.utilsService.isValidJSON(res)== false) {
+                  deferred.reject('MenusService::setFavoriteItem Failed');
+              } else {
+                  // this.$log.info('MenusService::setFavoriteItem success:');
+                  deferred.resolve(res.data);
+              }
+          })
+          .catch(errRes => {
+              this.$log.error('MenusService::setFavoriteItem rejection:' + JSON.stringify(errRes));
+              deferred.reject(errRes);
+          });
+      return deferred.promise;
+    }
+    
+      removeFavoriteItem(menuId) {
+          let deferred = this.$q.defer();
+          // this.$log.info('menus-service.service::removeFavoriteItem  via REST API');
+          let url = this.conf.api.removeFavoriteItem.replace(':menuId', menuId);
+          this.$http({
+              method: 'DELETE',
+              url: url,
+              cache: false,
+              headers: {
+                  'X-ECOMP-RequestID':this.uuid.generate()
+              }
+          }).then(res => {
+              // If response comes back as a redirected HTML page which IS NOT a success
+              if (this.utilsService.isValidJSON(res)== false) {
+                  deferred.reject('MenusService::removeFavoriteItem Failed');
+              } else {
+                  // this.$log.info('MenusService::removeFavoriteItem success:');
+                  deferred.resolve(res.data);
+              }
+          })
+              .catch(errRes => {
+                  this.$log.error('MenusService::removeFavoriteItem rejection:' + status);
+                  deferred.reject(errRes);
+              });
+          return deferred.promise;
+      }
+
+    
+  }
+  MenusService.$inject = ['$q', '$log', '$http', 'conf', 'uuid4', 'utilsService'];
+  angular.module('ecompApp').service('menusService', MenusService)
+})();
diff --git a/ecomp-portal-FE-common/client/app/services/microservice/microservice.service.js b/ecomp-portal-FE-common/client/app/services/microservice/microservice.service.js
new file mode 100644
index 0000000..cb27cd4
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/services/microservice/microservice.service.js
@@ -0,0 +1,218 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+

+(function () {

+    class MicroserviceService {

+        constructor($q, $log, $http, conf,uuid, utilsService) {

+            this.$q = $q;

+            this.$log = $log;

+            this.$http = $http;

+            this.conf = conf;

+            this.uuid = uuid;

+            this.utilsService = utilsService;   

+        }

+        

+        createService(newService) {

+        	let deferred = this.$q.defer();

+        	this.$http({

+                method: "POST",

+                url: this.conf.api.widgetCommon,

+                data: newService,

+                cache: false,

+                headers:{

+            		'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then(res => {

+            	if (res == null || res.data == null) {

+        	 		this.$log.error('MicroserviceService::createService Failed: Result or result.data is null');

+                     deferred.reject("MicroserviceService::createService Failed: Result or result.data is null");

+                } else if (!this.utilsService.isValidJSON(res.data)) {

+                 	this.$log.error('MicroserviceService::createService Failed: Invalid JSON format');

+                    deferred.reject("MicroserviceService::createService Failed: Invalid JSON format");

+                } else {

+                    deferred.resolve(res.data);

+                }

+            })

+            .catch(errRes => {

+                deferred.reject(errRes);

+            });

+            return deferred.promise;

+        }

+        

+        

+        updateService(serviceId, newService) {

+        	let deferred = this.$q.defer();

+        	this.$http({

+                method: "PUT",

+                url: this.conf.api.widgetCommon + "/" + serviceId,

+                data: newService,

+                cache: false,

+                headers:{

+            		'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then(res => {

+            	if (res == null || res.data == null) {

+        	 		this.$log.error('MicroserviceService::updateService Failed: Result or result.data is null');

+                     deferred.reject("MicroserviceService::updateService Failed: Result or result.data is null");

+                } else if (!this.utilsService.isValidJSON(res.data)) {

+                 	this.$log.error('MicroserviceService::updateService Failed: Invalid JSON format');

+                    deferred.reject("MicroserviceService::updateService Failed: Invalid JSON format");

+                } else {

+                    deferred.resolve(res.data);

+                }

+            })

+            .catch(errRes => {

+                deferred.reject(errRes);

+            });

+            return deferred.promise;

+        }

+        

+        deleteService(serviceId) {

+        	let deferred = this.$q.defer();

+        	this.$http({

+                method: "DELETE",

+                url: this.conf.api.widgetCommon + "/" + serviceId,

+                cache: false,

+                headers:{

+            		'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then(res => {

+            	if (res == null || res.data == null) {

+        	 		this.$log.error('MicroserviceService::deleteService Failed: Result or result.data is null');

+                     deferred.reject("MicroserviceService::deleteService Failed: Result or result.data is null");

+                } else if (!this.utilsService.isValidJSON(res.data)) {

+                 	this.$log.error('MicroserviceService::deleteService Failed: Invalid JSON format');

+                    deferred.reject("MicroserviceService::deleteService Failed: Invalid JSON format");

+                } else {

+                    deferred.resolve(res.data);

+                }

+            })

+            .catch(errRes => {

+                deferred.reject(errRes);

+            });

+            return deferred.promise;

+        }

+        

+        getServiceList() {

+          	let deferred = this.$q.defer();

+        	this.$http({

+                method: "GET",

+                url: this.conf.api.widgetCommon,

+                cache: false,

+                headers:{

+            		'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then(res => {

+            	if (res == null || res.data == null) {

+        	 		this.$log.error('MicroserviceService::getServiceList Failed: Result or result.data is null');

+                     deferred.reject("MicroserviceService::getServiceList Failed: Result or result.data is null");

+                } else if (!this.utilsService.isValidJSON(res.data)) {

+                 	this.$log.error('MicroserviceService::getServiceList Failed: Invalid JSON format');

+                    deferred.reject("MicroserviceService::getServiceList Failed: Invalid JSON format");

+                }  else {

+                    deferred.resolve(res.data);

+                }

+            })

+            .catch(errRes => {

+                deferred.reject(errRes);

+            });

+            return deferred.promise;

+        }

+        

+        getWidgetListByService(serviceId) {

+          	let deferred = this.$q.defer();

+        	this.$http({

+                method: "GET",

+                url: this.conf.api.widgetCommon + '/' + serviceId,

+                cache: false,

+                headers:{

+            		'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then(res => {

+            	if (res == null || res.data == null) {

+        	 		this.$log.error('MicroserviceService::getWidgetListByService Failed: Result or result.data is null');

+                     deferred.reject("MicroserviceService::getWidgetListByService Failed: Result or result.data is null");

+                } else if (!this.utilsService.isValidJSON(res.data)) {

+                 	this.$log.error('MicroserviceService::getWidgetListByService Failed: Invalid JSON format');

+                    deferred.reject("MicroserviceService::getWidgetListByService Failed: Invalid JSON format");

+                } else {

+                    deferred.resolve(res.data);

+                }

+            })

+            .catch(errRes => {

+                deferred.reject(errRes);

+            });

+            return deferred.promise;

+        }

+        

+        getUserParameterById(paramId){

+        	let deferred = this.$q.defer();

+        	this.$http({

+                method: "GET",

+                url: this.conf.api.widgetCommon + '/services/' +  paramId,

+                cache: false,

+                headers:{

+            		'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then(res => {

+            	if (res == null || res.data == null) {

+        	 		this.$log.error('MicroserviceService::getUserParameterById Failed: Result or result.data is null');

+                     deferred.reject("MicroserviceService::getUserParameterById Failed: Result or result.data is null");

+                } else if (!this.utilsService.isValidJSON(res.data)) {

+                 	this.$log.error('MicroserviceService::getUserParameterById Failed: Invalid JSON format');

+                    deferred.reject("MicroserviceService::getUserParameterById Failed: Invalid JSON format");

+                } else {

+                    deferred.resolve(res.data);

+                }

+            })

+            .catch(errRes => {

+                deferred.reject(errRes);

+            });

+            return deferred.promise;

+        }

+        

+        deleteUserParameterById(paramId){

+        	let deferred = this.$q.defer();

+        	this.$http({

+                method: "DELETE",

+                url: this.conf.api.widgetCommon + '/services/' +  paramId,

+                cache: false,

+                headers:{

+            		'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then(res => {

+            	if (res == null || res.data == null) {

+        	 		this.$log.error('MicroserviceService::deleteUserParameterById Failed: Result or result.data is null');

+                     deferred.reject("MicroserviceService::deleteUserParameterById Failed: Result or result.data is null");

+                } else {

+                    deferred.resolve(res.data);

+                }

+            })

+            .catch(errRes => {

+                deferred.reject(errRes);

+            });

+            return deferred.promise;

+        }

+    }

+    

+    MicroserviceService.$inject = ['$q', '$log', '$http', 'conf','uuid4', 'utilsService'];

+    angular.module('ecompApp').service('microserviceService', MicroserviceService)

+})();

diff --git a/ecomp-portal-FE-common/client/app/services/notification/notification.service.js b/ecomp-portal-FE-common/client/app/services/notification/notification.service.js
new file mode 100644
index 0000000..cf180e8
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/services/notification/notification.service.js
@@ -0,0 +1,322 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+/**

+ * Created by wl849v on 12/14/2016.

+ */

+'use strict';

+(function () {

+    class NotificationService {

+    	constructor($q, $log, $http, conf, uuid,utilsService) {

+            this.$q = $q;

+            this.$log = $log;

+            this.$http = $http;

+            this.conf = conf;

+            this.uuid = uuid;

+            this.notificationCount = {count:0};

+            this.refreshCount = 0;

+            this.maxCount = 0;

+            this.utilsService = utilsService;            

+        }   	

+        getNotificationCount() {

+        	 return this.notificationCount;

+        }

+        setNotificationCount(count) {

+        	this.notificationCount.count = count;

+        }

+        getRefreshCount() {

+            return this.refreshCount;

+        }

+        setRefreshCount(count){

+            this.refreshCount = count;

+        }

+        setMaxRefreshCount(count){

+            this.maxCount = count;

+        }

+        decrementRefreshCount(){

+            this.refreshCount = this.refreshCount - 1;

+        }

+        

+        getNotificationRate() {

+            let deferred = this.$q.defer();

+            let url = this.conf.api.notificationUpdateRate;

+            this.$http({

+                    method: "GET",

+                    url: url,

+                    cache: false,

+                    headers: {

+                        'X-ECOMP-RequestID':this.uuid.generate()

+                    }

+                }).then( res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success

+                	if (Object.keys(res.data).length == 0) {

+                        deferred.reject("ecomp::NotificationService::getNotificationRate Failed");

+                    } else {

+                        deferred.resolve(res.data);

+                    }

+                }).catch( status => {

+                    deferred.reject(status);

+                });

+            return deferred.promise;

+        }

+        

+        getNotification(){

+    		let deferred = this.$q.defer();

+            this.$http({

+                    method: "GET",

+                    cache: false,

+                    url: this.conf.api.getNotifications,

+                    headers: {

+                        'X-ECOMP-RequestID':this.uuid.generate()

+                    }

+                })

+                .then( res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success           	

+                    if (this.utilsService.isValidJSON(res)=== false) {

+                    	this.$log.error('NotificationService::getNotification Failed');

+                        deferred.reject("NotificationService::getNotification Failed");

+                    } else {

+                        deferred.resolve(res);

+                    }

+                })

+                .catch( status => {

+                	this.$log.error('NotificationService::getNotification Failed', status);

+                    deferred.reject(status);

+                });

+           

+            return deferred.promise;

+    	}

+        getNotificationHistory(){

+    		let deferred = this.$q.defer();

+            this.$http({

+                    method: "GET",

+                    cache: false,

+                    url: this.conf.api.getNotificationHistory,

+                    headers: {

+                        'X-ECOMP-RequestID':this.uuid.generate()

+                    }

+                })

+                .then( res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success           	

+                    if (this.utilsService.isValidJSON(res)=== false) {

+                    	this.$log.error('NotificationService::getNotification Failed');

+                        deferred.reject("NotificationService::getNotification Failed");

+                    } else {

+                        deferred.resolve(res);

+                    }

+                })

+                .catch( status => {

+                	this.$log.error('NotificationService::getNotification Failed', status);

+                    deferred.reject(status);

+                });

+           

+            return deferred.promise;

+    	}

+        

+        getAdminNotification(){

+    		let deferred = this.$q.defer();

+            this.$http({

+                    method: "GET",

+                    cache: false,

+                    url: this.conf.api.getAdminNotifications,

+                    headers: {

+                        'X-ECOMP-RequestID':this.uuid.generate()

+                    }

+                })

+                .then( res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success           	

+                    if (this.utilsService.isValidJSON(res)=== false) {

+                    	this.$log.error('NotificationService::getAdminNotification Failed');

+                        deferred.reject("NotificationService::getAdminNotification Failed");

+                    } else {

+                        deferred.resolve(res);

+                    }

+                })

+                .catch( status => {

+                	this.$log.error('NotificationService::getAdminNotification Failed', status);

+                    deferred.reject(status);

+                });

+           

+            return deferred.promise;

+    	}

+

+        

+        getMessageRecipients(notificationId){

+    		let deferred = this.$q.defer();

+            this.$http({

+                    method: "GET",

+                    cache: false,

+                   url: this.conf.api.getMessageRecipients+"?notificationId="+notificationId,

+                    headers: {

+                        'X-ECOMP-RequestID':this.uuid.generate()

+                    }

+                })

+                .then( res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success           	

+                    if (this.utilsService.isValidJSON(res.data)=== false) {

+                    	this.$log.error('NotificationService::getMessageRecipients Failed');

+                        deferred.reject("NotificationService::getMessageRecipients Failed");

+                    } else {

+                        deferred.resolve(res.data);

+                    }

+                })

+                .catch( status => {

+                	this.$log.error('NotificationService::getMappedRecipients Failed', status);

+                    deferred.reject(status);

+                });

+           

+            return deferred.promise;

+    	}

+        

+        getAppRoleIds(){

+    		let deferred = this.$q.defer();

+            this.$http({

+                    method: "GET",

+                    cache: false,

+                    url: this.conf.api.getAllAppRoleIds,

+                    headers: {

+                        'X-ECOMP-RequestID':this.uuid.generate()

+                    }

+                })

+                .then( res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success           	

+                    if (this.utilsService.isValidJSON(res)=== false) {

+                    	this.$log.error('NotificationService::getAppRoleIds Failed');

+                        deferred.reject("NotificationService::getAppRoleIds Failed");

+                    } else {

+                        deferred.resolve(res);

+                    }

+                })

+                .catch( status => {

+                	this.$log.error('NotificationService::getAppRoleIds Failed', status);

+                    deferred.reject(status);

+                });           

+            return deferred.promise;

+    	}

+

+        getNotificationRoles(notificationId){

+    		let deferred = this.$q.defer();

+            this.$http({

+                    method: "GET",

+                    cache: false,

+                    url: this.conf.api.getNotificationRoles + '/'+notificationId+'/roles',

+                    headers: {

+                        'X-ECOMP-RequestID':this.uuid.generate()

+                    }

+                })

+                .then( res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success           	

+                    if (this.utilsService.isValidJSON(res)=== false) {

+                    	this.$log.error('NotificationService::getAdminNotification Failed');

+                        deferred.reject("NotificationService::getAdminNotification Failed");

+                    } else {

+                        deferred.resolve(res);

+                    }

+                })

+                .catch( status => {

+                	this.$log.error('NotificationService::getAdminNotification Failed', status);

+                    deferred.reject(status);

+                });           

+            return deferred.promise;

+    	}

+

+        addAdminNotification(newAdminNotif){

+            let deferred = this.$q.defer();

+            // this.$log.debug('applications-service::addOnboardingApp with:', newApp);

+

+            this.$http({

+                method: "POST",

+                url: this.conf.api.saveNotification,

+                data: newAdminNotif,

+                cache: false,

+                headers: {

+                    'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then( res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success

+                	// But don't declare an empty list to be an error.

+                    if (res == null || res.data == null) {

+                        deferred.reject("NotificationService::addAdminNotification Failed");

+                    } else {

+                        // this.$log.info('NotificationService::addAdminNotification Succeeded');

+                        deferred.resolve(res.data);

+                    }

+                })

+                .catch( status => {

+                    deferred.reject(status);

+                });

+            return deferred.promise;

+        }

+

+        updateAdminNotification(adminNotif){

+            let deferred = this.$q.defer();

+            this.$http({

+                method: "POST",

+                url: this.conf.api.saveNotification,

+                data: adminNotif,

+                cache: false,

+                headers: {

+                    'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then( res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success

+                	// But don't declare an empty list to be an error.

+                    if (res == null || res.data == null) {

+                        deferred.reject("NotificationService::updateAdminNotification Failed");

+                    } else {

+                        // this.$log.info('NotificationService::updateAdminNotification Succeeded');

+                        deferred.resolve(res.data);

+                    }

+                })

+                .catch( status => {

+                    deferred.reject(status);

+                });

+            return deferred.promise;

+        }

+

+        setNotificationRead(notificationId){

+        	let deferred = this.$q.defer();

+            this.$http({

+                    method: "GET",

+                    cache: false,

+                    url: this.conf.api.notificationRead+"?notificationId="+notificationId,

+                    headers: {

+                        'X-ECOMP-RequestID':this.uuid.generate()

+                    }

+                })

+                .then( res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success

+                    if (this.utilsService.isValidJSON(res)=== false) {

+                    	this.$log.error('NotificationService::setNotificationRead Failed');

+                        deferred.reject("NotificationService::setNotificationRead Failed");

+                    } else {

+                        deferred.resolve(res);

+                    }

+                })

+                .catch( status => {

+                	this.$log.error('NotificationService::setNotificationRead Failed', status);

+                    deferred.reject(status);

+                });

+           

+            return deferred.promise;	

+        }

+    }

+    NotificationService.$inject = ['$q', '$log', '$http', 'conf', 'uuid4','utilsService'];

+    angular.module('ecompApp').service('notificationService', NotificationService)

+})();

diff --git a/ecomp-portal-FE-common/client/app/services/portal-admins/portal-admins.service.js b/ecomp-portal-FE-common/client/app/services/portal-admins/portal-admins.service.js
new file mode 100644
index 0000000..12518ce
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/services/portal-admins/portal-admins.service.js
@@ -0,0 +1,112 @@
+/*-
+ * ================================================================================
+ * eCOMP Portal
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property
+ * ================================================================================
+ * Licensed 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';
+
+(function () {
+    class PortalAdminsService {
+        constructor($q, $log, $http, conf, uuid, utilsService) {
+            this.$q = $q;
+            this.$log = $log;
+            this.$http = $http;
+            this.conf = conf;
+            this.uuid = uuid;
+            this.utilsService = utilsService;
+        }
+
+        getPortalAdmins() {
+            let deferred = this.$q.defer();
+            this.$log.info('PortalAdminsService::get all portal admins list');
+            this.$http({
+                    url: this.conf.api.portalAdmins,
+                    method: 'GET',
+                    cache: false,
+                    headers: {
+                        'X-ECOMP-RequestID':this.uuid.generate()
+                    }
+                }).then(res => {
+                    // If response comes back as a redirected HTML page which IS NOT a success
+                if (this.utilsService.isValidJSON(res)== false) {
+                        deferred.reject('PortalAdminsService::getPortalAdmins Failed');
+                    } else {
+                        deferred.resolve(res.data);
+                    }
+                })
+                .catch(status => {
+                    deferred.reject(status);
+                });
+            return deferred.promise;
+        }
+
+        addPortalAdmin(userData) {
+            let deferred = this.$q.defer();
+            this.$log.info('PortalAdminsService::addPortalAdmin: ' + JSON.stringify(userData));
+            this.$http({
+                url: this.conf.api.portalAdmin,
+                method: 'POST',
+                cache: false,
+                headers: {
+                    'X-ECOMP-RequestID':this.uuid.generate()
+                },
+                data: userData
+            }).then(res => {
+                // If response comes back as a redirected HTML page which IS NOT a success
+                this.$log.debug('PortalAdminsService:: this.conf.api.portalAdmin: ' + JSON.stringify(res));
+                if (this.utilsService.isValidJSON(res)== false) {
+                    deferred.reject('PortalAdminsService::addPortalAdmin Failed');
+                } else {
+                    deferred.resolve(res.data);
+                }
+            })
+                .catch(errRes => {
+                    this.$log.debug('PortalAdminsService:: this.conf.api.portalAdmin: ' + JSON.stringify(errRes));
+                    deferred.reject(errRes);
+                });
+            return deferred.promise;
+        }
+
+        removePortalAdmin(userId,orUserId) {
+            let deferred = this.$q.defer();
+            let userInfo = userId+"-"+orUserId;
+            let url = this.conf.api.portalAdmin + '/' + userInfo;
+            this.$log.info('PortalAdminsService:: remove Portal Admin');
+            this.$http({
+                url: url,
+                method: 'DELETE',
+                cache: false,
+                headers: {
+                    'X-ECOMP-RequestID':this.uuid.generate()
+                }
+            }).then(res => {
+                // If response comes back as a redirected HTML page which IS NOT a success
+                if (this.utilsService.isValidJSON(res)== false) {
+                    deferred.reject('PortalAdminsService::removePortalAdmin Failed');
+                } else {
+                    deferred.resolve(res.data);
+                }
+            }).catch(errRes => {
+                deferred.reject(errRes);
+            });
+
+            return deferred.promise;
+        }
+    }
+    PortalAdminsService.$inject = ['$q', '$log', '$http', 'conf', 'uuid4', 'utilsService'];
+    angular.module('ecompApp').service('portalAdminsService', PortalAdminsService)
+})();
diff --git a/ecomp-portal-FE-common/client/app/services/role/role.service.js b/ecomp-portal-FE-common/client/app/services/role/role.service.js
new file mode 100644
index 0000000..f8fee13
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/services/role/role.service.js
@@ -0,0 +1,190 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+app.factory('RoleService', function ($http, $q, conf,uuid4) {

+	return {

+		getRoles: function() {

+			return $http.get(conf.api.getRoles,{

+                cache: false,

+                headers: {

+                    'X-ECOMP-RequestID':uuid4.generate()

+                }

+            })

+			.then(function(response) {

+				if (typeof response.data === 'object') {

+					return response.data;

+				} else {

+					return $q.reject(response.data);

+				}

+

+			}, function(response) {

+				// something went wrong

+				return $q.reject(response.data);

+			});

+		},

+		

+		saveRoleFunction: function() {

+			return $http.post(conf.api.saveRoleFuncion)

+			.then(function(response) {

+				if (typeof response.data === 'object') {

+					return response.data;

+				} else {

+					return $q.reject(response.data);

+				}

+

+			}, function(response) {

+				// something went wrong

+				return $q.reject(response.data);

+			});

+		},

+		

+		getRoleFunctionList: function() {

+			return $http.get(conf.api.getRoleFunctions,{

+                cache: false,

+                headers: {

+                    'X-ECOMP-RequestID':uuid4.generate()

+                }

+            })

+			.then(function(response) {

+				if (typeof response.data === 'object') {

+					return response.data;

+				} else {

+					return $q.reject(response.data);

+				}

+

+			}, function(response) {

+				// something went wrong

+				return $q.reject(response.data);

+			});

+		},

+		

+		getFnMenuItems: function(){

+			

+			return $http.get('admin_fn_menu')

+			.then(function(response) {

+				if (typeof response.data === 'object') {

+					

+					return response.data;

+				} else {

+					return $q.reject(response.data);

+				}

+

+			}, function(response) {

+				// something went wrong

+				return $q.reject(response.data);

+			});			

+		},

+

+		getCacheRegions: function() {

+			return $http.get('get_regions')

+			.then(function(response) {

+				if (typeof response.data === 'object') {

+					return response.data;

+				} else {

+					return $q.reject(response.data);

+				}

+	

+			}, function(response) {

+				// something went wrong

+				return $q.reject(response.data);

+			});

+		},

+		

+		getUsageList: function() {

+			return $http.get('get_usage_list')

+			.then(function(response) {

+				if (typeof response.data === 'object') {

+					return response.data;

+				} else {

+					return $q.reject(response.data);

+				}

+	

+			}, function(response) {

+				// something went wrong

+				return $q.reject(response.data);

+			});

+		},

+		

+		getBroadcastList: function() {

+			return $http.get('get_broadcast_list')

+			.then(function(response) {

+				if (typeof response.data === 'object') {

+					return response.data;

+				} else {

+					return $q.reject(response.data);

+				}

+	

+			}, function(response) {

+				// something went wrong

+				return $q.reject(response.data);

+			});

+		},

+		

+		getBroadcast: function(messageLocationId, messageLocation, messageId) {

+			return $http.get('get_broadcast?message_location_id='+messageLocationId + '&message_location=' + messageLocation + ((messageId != null) ? '&message_id=' + messageId : ''))

+			.then(function(response) {

+				if (typeof response.data === 'object') {

+					return response.data;

+				} else {

+					return $q.reject(response.data);

+				}

+	

+			}, function(response) {

+				// something went wrong

+				return $q.reject(response.data);

+			});

+		},

+		

+		getCollaborateList: function() {

+			return $http.get('get_collaborate_list')

+			.then(function(response) {

+				if (typeof response.data === 'object') {

+					return response.data;

+				} else {

+					return $q.reject(response.data);

+				}

+	

+			}, function(response) {

+				// something went wrong

+				return $q.reject(response.data);

+			});

+		},

+		

+		getRole: function(roleId) {

+			

+			return $http.get(conf.api.getRole + '?role_id=' + roleId,{

+                cache: false,

+                headers: {

+                    'X-ECOMP-RequestID':uuid4.generate()

+                }

+            })

+			.then(function(response) {

+				if (typeof response.data === 'object') {

+					return response.data;

+				} else {

+					return $q.reject(response.data);

+				}

+	

+			}, function(response) {

+				// something went wrong

+				return $q.reject(response.data);

+			});

+		}

+	};

+});

diff --git a/ecomp-portal-FE-common/client/app/services/support/getAccess/get-access.service.js b/ecomp-portal-FE-common/client/app/services/support/getAccess/get-access.service.js
new file mode 100644
index 0000000..4b2ac50
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/services/support/getAccess/get-access.service.js
@@ -0,0 +1,62 @@
+/*-
+ * ================================================================================
+ * eCOMP Portal
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property
+ * ================================================================================
+ * Licensed 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';
+
+(function () {
+    class GetAccessService {
+        constructor($q, $log, $http, conf, uuid, utilsService) {
+            this.$q = $q;
+            this.$log = $log;
+            this.$http = $http;
+            this.conf = conf;
+            this.uuid = uuid;
+            this.utilsService = utilsService;
+        }
+        getListOfApp(searchStr) {
+            //this.$log.info('GetAccessService::getListOfApp: get all app list');
+            let deferred = this.$q.defer();
+            //this.$log.info('GetAccessService::getListOfApp: searchStr', searchStr);
+            //this.$log.info('GetAccessService::getListOfApp: ', this.conf.api.listOfApp);
+            this.$http({
+                method: 'GET',
+                url: this.conf.api.listOfApp,
+                params: {search:searchStr},
+                cache: false
+            }).then( res => {
+                // If response comes back as a redirected HTML page which IS NOT a success
+//            	this.$log.info('GetAccessService::getListOfApp response: ', res);
+                if (this.utilsService.isValidJSON(res)== false) {
+                    deferred.reject('GetAccessService::getListOfApp: Failed');
+                } else {
+                    // this.$log.debug('GetAccessService::getListOfApp: query results: ', res);
+                    // this.$log.info('GetAccessService::getListOfApp Succeeded');
+                    deferred.resolve(res);
+                }
+            }).catch( status => {
+            	this.$log.error('GetAccessService::getListOfApp: query error: ',status);
+                deferred.reject(status);
+            });
+            return deferred.promise;           
+        }
+
+    }
+    GetAccessService.$inject = ['$q', '$log', '$http', 'conf','uuid4', 'utilsService'];
+    angular.module('ecompApp').service('getAccessService', GetAccessService)
+})();
diff --git a/ecomp-portal-FE-common/client/app/services/userbar/userbar.update.service.js b/ecomp-portal-FE-common/client/app/services/userbar/userbar.update.service.js
new file mode 100644
index 0000000..70899f2
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/services/userbar/userbar.update.service.js
@@ -0,0 +1,97 @@
+/*-
+ * ================================================================================
+ * eCOMP Portal
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property
+ * ================================================================================
+ * Licensed 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';
+
+(function () {
+    class userbarUpdateService {
+        constructor($q, $log, $http, conf, uuid) {
+            this.$q = $q;
+            this.$log = $log;
+            this.$http = $http;
+            this.conf = conf;
+            this.uuid = uuid;
+            this.refreshCount = 0;
+            this.maxCount = 0;
+        }
+    
+        getRefreshCount() {
+            return this.refreshCount;
+        }
+        setRefreshCount(count){
+            this.refreshCount = count;
+        }
+        setMaxRefreshCount(count){
+            this.maxCount = count;
+        }
+        decrementRefreshCount(){
+            this.refreshCount = this.refreshCount - 1;
+        }
+
+        getWidthThresholdLeftMenu() {
+            let deferred = this.$q.defer();
+            this.$log.info('userbarUpdateService::getWidthThresholdLeftMenu');
+            this.$http({
+                    url: this.conf.api.getWidthThresholdLeftMenu,
+                    method: 'GET',
+                    cache: false,
+                    headers: {
+                        'X-ECOMP-RequestID':this.uuid.generate()
+                    }
+                }).then(res => {
+                    if (Object.keys(res.data).length == 0) {
+                        deferred.reject("userbarUpdateService::getWidthThresholdLeftMenu Failed");
+                    } else {
+                        deferred.resolve(res.data);
+                    }
+                })
+                .catch(status => {
+                    deferred.reject(status);
+                });
+            return deferred.promise;
+        }
+        
+        getWidthThresholdRightMenu() {
+            let deferred = this.$q.defer();
+            this.$log.info('userbarUpdateService::getWidthThresholdRightMenu');
+            this.$http({
+                    url: this.conf.api.getWidthThresholdRightMenu,
+                    method: 'GET',
+                    cache: false,
+                    headers: {
+                        'X-ECOMP-RequestID':this.uuid.generate()
+                    }
+                }).then(res => {
+                    if (Object.keys(res.data).length == 0) {
+                        deferred.reject("userbarUpdateService::getWidthThresholdRightMenu Failed");
+                    } else {
+                        deferred.resolve(res.data);
+                    }
+                })
+                .catch(status => {
+                    deferred.reject(status);
+                });
+            return deferred.promise;
+        }
+
+
+    }
+    userbarUpdateService.$inject = ['$q', '$log', '$http', 'conf', 'uuid4'];
+    angular.module('ecompApp').service('userbarUpdateService', userbarUpdateService)
+})();
diff --git a/ecomp-portal-FE-common/client/app/services/users/users.service.js b/ecomp-portal-FE-common/client/app/services/users/users.service.js
new file mode 100644
index 0000000..894aac0
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/services/users/users.service.js
@@ -0,0 +1,215 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+/**

+ * Created by doritrieur on 12/8/15.

+ */

+'use strict';

+

+(function () {

+    class UsersService {

+        constructor($q, $log, $http, conf, uuid, utilsService) {

+            this.$q = $q;

+            this.$log = $log;

+            this.$http = $http;

+            this.conf = conf;

+            this.uuid = uuid;

+            this.utilsService = utilsService;

+        }

+

+

+        searchUsers(queryString) {

+            let canceller = this.$q.defer();

+            let isActive = false;

+

+            let cancel = () => {

+                if(isActive){

+                    this.$log.debug('UsersService::searchUsers: canceling the request');

+                    canceller.resolve();

+                }

+            };

+

+            let promise = () => {

+                let deferred = this.$q.defer();

+                if(!queryString){

+                    return deferred.reject(new Error('query string is mandatory'));

+                }

+                isActive = true;

+                this.$http({

+                    method: 'GET',

+                    url: this.conf.api.queryUsers,

+                    params: {search: queryString},

+                    cache: false,

+                    timeout: canceller.promise,

+                    headers: {

+                        'X-ECOMP-RequestID':this.uuid.generate()

+                    }

+                }).then( res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success

+                    if (this.utilsService.isValidJSON(res)== false) {

+                        deferred.reject('UsersService::queryUsers Failed');

+                    } else {

+                        //this.$log.info('UsersService::queryUsers Succeeded');

+                        isActive = false;

+                        deferred.resolve(res.data);

+                    }

+                }).catch( status => {

+                    isActive = false;

+                    deferred.reject('UsersService::searchUsers:: API Failed with status: ' + status);

+                });

+                return deferred.promise;

+            };

+

+            return {

+                cancel: cancel,

+                promise: promise

+            };

+

+        }

+

+        getAccountUsers(appId) {

+            let deferred = this.$q.defer();

+            let log = this.$log;

+            // this.$log.debug('UsersService::getAccountUsers for appId: ' + appId);

+

+            let url = this.conf.api.accountUsers.replace(':appId', appId);

+            this.$http({

+                method: 'GET',

+                url: url,

+                cache: false,

+                headers: {

+                    'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then( res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success

+                if (this.utilsService.isValidJSON(res)== false) {

+                        deferred.reject('UsersService::getAccountUsers for appId Failed');

+                    } else {

+                        // log.info('UsersService::getAccountUsers(appId) Succeeded');

+                        deferred.resolve(res.data);

+                    }

+                })

+                .catch( status => {

+                    log.error('getAccountUsers(appId) $http error = ', status);

+                    deferred.reject(status);

+                });

+

+            return deferred.promise;

+        }

+

+        getUserAppRoles(appid, orgUserId){

+//            let deferred = this.$q.defer();

+        	let canceller = this.$q.defer();

+            let isActive = false;

+

+            let cancel = () => {

+                if(isActive){

+                    this.$log.debug('UsersService::getUserAppRoles: canceling the request');

+                    canceller.resolve();

+                }

+            };

+

+            // this.$log.info('UsersService::getUserAppRoles');

+

+            let promise = () => {

+                let deferred = this.$q.defer();

+                isActive = false;

+            this.$http({

+                method: 'GET',

+                url: this.conf.api.userAppRoles,

+                params: {user: orgUserId, app: appid},

+                cache: false,

+                headers: {

+                    'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then( res => {

+                    //this.$log.debug('getUserAppRoles response: ', JSON.stringify(res))

+                    // If response comes back as a redirected HTML page which IS NOT a success

+                    if (this.utilsService.isValidJSON(res)== false) {

+                        deferred.reject('UsersService::getUserAppRoles: Failed');

+                    } else {

+                    	isActive = false;

+                        //this.$log.info('UsersService::getUserAppRoles: Succeeded');

+                        deferred.resolve(res.data);

+                    }

+                })

+                .catch( status => {

+                	isActive = false;

+                    deferred.reject(status);

+                });

+

+            return deferred.promise;

+            };

+            

+            return {

+                cancel: cancel,

+                promise: promise

+            };

+        }

+

+        updateUserAppRoles(newUserAppRoles) {

+//            let deferred = this.$q.defer();

+            let canceller = this.$q.defer();

+            let isActive = false;

+

+            let cancel = () => {

+                if(isActive){

+                    this.$log.debug('UsersService::updateUserAppRoles: canceling the request');

+                    canceller.resolve();

+                }

+            };

+

+            // this.$log.info('UsersService::updateUserAppRoles');

+            let promise = () => {

+                let deferred = this.$q.defer();

+            this.$http({

+                method: 'PUT',

+                url: this.conf.api.userAppRoles,

+                data: newUserAppRoles,

+                headers: {

+                    'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then( res => {

+                // this.$log.debug('getUserAppRoles response: ', JSON.stringify(res))

+                // If response comes back as a redirected HTML page which IS NOT a success

+                if (this.utilsService.isValidJSON(res)== false) {

+                    deferred.reject('UsersService::updateUserAppRoles: Failed');

+                } else {

+                    // this.$log.info('UsersService::updateUserAppRoles: Succeeded');

+                    deferred.resolve(res.data);

+                }

+            })

+            .catch( status => {

+                deferred.reject(status);

+            });

+

+            return deferred.promise;

+            };

+            

+            return {

+                cancel: cancel,

+                promise: promise

+            };

+

+        }

+

+    }

+    UsersService.$inject = ['$q', '$log', '$http', 'conf','uuid4', 'utilsService'];

+    angular.module('ecompApp').service('usersService', UsersService)

+})();

diff --git a/ecomp-portal-FE-common/client/app/services/utils/utils.service.js b/ecomp-portal-FE-common/client/app/services/utils/utils.service.js
new file mode 100644
index 0000000..1cbb3ca
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/services/utils/utils.service.js
@@ -0,0 +1,55 @@
+/*-
+ * ================================================================================
+ * eCOMP Portal
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property
+ * ================================================================================
+ * Licensed 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';
+
+(function () {
+    class UtilsService {
+        constructor($log) {
+            this.$log = $log;
+        }
+
+        isRunningInLocalDevEnv() {
+            var myHostName;
+            myHostName = location.host;
+
+            if (myHostName.includes('localhost')) {
+                return true;
+            } else {
+                return false;
+            }
+        }
+
+        isValidJSON(json) {
+            try {
+                var checkJSON = JSON.parse(JSON.stringify(json));
+                if (checkJSON && typeof checkJSON === 'object' && checkJSON !== null) {
+                    // this.$log.debug('UtilsService::isValidJSON: JSON is valid!');
+                    return true;
+                }
+            } catch (err) {
+                this.$log.debug('UtilsService::isValidJSON: json passed is not valid: ' + JSON.stringify(err));
+            }
+            return false;
+        }
+
+    }
+    UtilsService.$inject = ['$log'];
+    angular.module('ecompApp').service('utilsService', UtilsService)
+})();
diff --git a/ecomp-portal-FE-common/client/app/services/widgets-catalog/widgets-catalog.service.js b/ecomp-portal-FE-common/client/app/services/widgets-catalog/widgets-catalog.service.js
new file mode 100644
index 0000000..99ace1c
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/services/widgets-catalog/widgets-catalog.service.js
@@ -0,0 +1,358 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+

+(function () {

+	class WidgetsCatalogService {

+        constructor($q, $log, $http, conf,uuid,base64Service, beReaderService, utilsService) {

+            this.$q = $q;

+            this.$log = $log;

+            this.$http = $http;

+            this.conf = conf;

+            this.uuid = uuid;

+            this.base64Service = base64Service;

+            this.beReaderService = beReaderService;

+            this.utilsService = utilsService;

+        }

+        

+        getUserWidgets(loginName) {

+        	 let deferred = this.$q.defer();

+  	         this.$http({

+  	        	 method: "GET",

+	             url: this.conf.api.widgetCommon + '/widgetCatalog' + '/' + loginName,

+	             cache: false,

+	             headers: {

+	            	 'X-Widgets-Type': 'all',

+	                 'X-ECOMP-RequestID':this.uuid.generate()

+	             }

+             }).then(res => {

+        	 	if (res == null || res.data == null) {

+                     deferred.reject("WidgetsCatalogService::getUserWidgets Failed");

+                 } else {

+                     deferred.resolve(res.data);

+                 }

+             })

+             .catch(status => {

+            	 deferred.reject(status);

+	         });

+             return deferred.promise;

+        }

+

+        getManagedWidgets() {          	

+            let deferred = this.$q.defer();

+            let url = this.conf.api.widgetCommon + '/widgetCatalog';

+            this.$http({

+            	method: "GET",

+            	url: url,

+            	cache: false,

+            	headers: {

+            		'X-Widgets-Type': 'all',

+            		'X-ECOMP-RequestID':this.uuid.generate()

+            	}

+            }).then(res => {

+        	 	if (res == null || res.data == null) {

+                     deferred.reject("WidgetsCatalogService::getManagedWidgets Failed");

+                 } else {

+                     deferred.resolve(res.data);

+                 }

+             })

+             .catch(status => {

+                 deferred.reject(status);

+             });  

+            return deferred.promise;

+        }

+        

+        createWidget(newWidget, file) {

+        	console.log(newWidget);

+        	let deferred = this.$q.defer();

+        	var formData = new FormData();

+        	formData.append('file', file);

+        	this.$http({

+                method: "POST",

+                url: this.conf.api.widgetCommon + '/widgetCatalog',

+                transformRequest: angular.identity,  

+                data: formData,

+                headers:{  

+                	'Content-Type': undefined,

+                	'X-Widgets-Type': 'all',

+	                'X-ECOMP-RequestID':this.uuid.generate()

+                },

+                params:{

+                	'newWidget': newWidget

+                }

+            }).then(res => {

+            	if (res == null || res.data == null) {

+                    deferred.reject("WidgetsCatalogService::getManagedWidgets Failed");

+                } else {

+                    deferred.resolve(res.data);

+                }

+            })

+            .catch(errRes => {

+                deferred.reject(errRes);

+            });

+            return deferred.promise;

+        }

+        

+        updateWidgetWithFile(file, widgetId, newWidget){

+        	console.log(widgetId);

+        	let deferred = this.$q.defer();

+            var formData = new FormData();

+        	formData.append('file', file);

+    		let url = this.conf.api.widgetCommon + '/widgetCatalog/' + widgetId;

+        	this.$http({

+                method: 'POST',

+                url: url, 

+                transformRequest: angular.identity,  

+                data: formData,

+                headers: {        

+                	'Content-Type': undefined,

+                	'X-Widgets-Type': 'all',

+	                'X-ECOMP-RequestID':this.uuid.generate()

+                },

+        	 	params:{

+        	 		'newWidget': newWidget

+        	 	}

+            })

+            .then(res => {

+               if (res == null || res.data == null)

+            	   deferred.reject("WidgetsCatalogService::saveWidgetFile Failed");

+               else 

+            	   deferred.resolve(res.data);

+            })

+            .catch(status => {

+                 deferred.reject(status);

+             });  

+        	return deferred.promise;

+        }

+        

+        updateWidget(widgetId, widgetData) {

+            let deferred = this.$q.defer();

+            let url = this.conf.api.widgetCommon  + '/widgetCatalog' + '/' + widgetId;

+            this.$http({

+            	method: 'PUT',

+            	url: url,

+            	cache: false,

+            	data: widgetData,

+            	headers: {

+            		'X-Widgets-Type': 'all',

+	                'X-ECOMP-RequestID':this.uuid.generate()

+            	}

+            })

+            .then(res => {

+            	deferred.resolve(res.data);

+            })

+            .catch(errRes => {

+            	deferred.reject(errRes);

+            });

+           

+            return deferred.promise;

+        }

+        

+        

+        deleteWidgetFile(widgetName) {

+        	let deferred = this.$q.defer();

+            this.$log.info('WidgetsCatalogService::deleteWidgetCatalog');

+            let url = this.conf.api.widgetCommon + '/doUpload' + '/' + widgetName;

+            this.$http({

+                method: "DELETE",

+                url: url,

+                cache: false,

+                headers:{  

+                	'X-Widgets-Type': 'all',

+	                'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then(res => {

+                    deferred.resolve(res.data); 

+                })

+                .catch(status => {

+                	deferred.reject(status);

+                });

+           

+            return deferred.promise;

+        }

+        

+        deleteWidget(widgetId) {

+        	let deferred = this.$q.defer();

+            this.$log.info('WidgetsCatalogService::deleteWidgetCatalog');

+            let url = this.conf.api.widgetCommon + '/widgetCatalog'  + '/' + widgetId;

+            this.$http({

+                method: "DELETE",

+                url: url,

+                cache: false,

+                headers:{  

+                	'X-Widgets-Type': 'all',

+	                'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then(res => {

+                    deferred.resolve(res.data); 

+                })

+                .catch(status => {

+                	deferred.reject(status);

+                });

+           

+            return deferred.promise;

+        }

+        

+        downloadWidgetFile(widgetId) {

+        	let deferred = this.$q.defer();

+            this.$log.info('WidgetsCatalogService::downloadWidgetFile');

+            let url = this.conf.api.widgetCommon + '/download/' + widgetId;

+            console.log(url);

+            this.$http({

+                method: "GET",

+                url: url,

+                cache: false,

+                responseType: "arraybuffer",

+                headers:{  

+                	'X-Widgets-Type': 'all',

+	                'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then(res => {

+                    deferred.resolve(res.data); 

+                })

+                .catch(status => {

+                	deferred.reject(status);

+                });

+           

+            return deferred.promise;

+        }

+        

+        updateWidgetCatalog(appData){

+        	let deferred = this.$q.defer();

+            // Validate the request, maybe this is overkill

+            if (appData == null || appData.widgetId == null || appData.select == null) {

+            	var msg = 'WidgetCatalogService::updateAppCatalog: field appId and/or select not found';

+                this.$log.error(msg);

+                return deferred.reject(msg);

+            }

+            this.$http({

+                method: "PUT",

+                url: this.conf.api.widgetCatalogSelection,

+                data: appData,

+                headers: {

+                	 'X-Widgets-Type': 'all',

+	                 'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then( res => {

+                    // Detect non-JSON

+                    if (res == null || res.data == null) {

+                        deferred.reject("WidgetCatalogService::updateAppCatalog Failed");

+                    } else {

+                        deferred.resolve(res.data);

+                    }

+                })

+                .catch( status => {

+                	this.$log.error('WidgetCatalogService:updateAppCatalog failed: ' + status);

+                    deferred.reject(status);

+                });

+            return deferred.promise;

+        }

+        

+        getServiceJSON(serviceId){

+        	let deferred = this.$q.defer();

+            this.$log.info('WidgetsCatalogService::getServiceJSON');

+            let url = this.conf.api.microserviceProxy + "/" + serviceId;

+            console.log(url);

+            this.$http({

+                method: "GET",

+                url: url,

+                cache: false,

+                headers:{  

+                	'X-Widgets-Type': 'all',

+	                'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then(res => {

+	            	if (res == null || res == null) {

+	        	 		this.$log.error('WidgetCatalogService::getServiceJSON Failed: Result or result.data is null');

+	                    deferred.reject("WidgetCatalogService::getServiceJSON Failed: Result or result.data is null");

+	                } else{

+	                	deferred.resolve(res.data);

+	                }

+	                

+                })

+                .catch(status => {

+                	deferred.reject(status);

+                });

+           

+            return deferred.promise;

+        }

+        

+        getWidgetCatalogParameters(widgetId){

+        	let deferred = this.$q.defer();

+            this.$log.info('WidgetsCatalogService::getWidgetCatalogParameters');

+            let url = this.conf.api.widgetCommon + "/parameters/" + widgetId;

+            console.log(url);

+            this.$http({

+                method: "GET",

+                url: url,

+                cache: false,

+                headers:{  

+                	'X-Widgets-Type': 'all',

+	                'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then(res => {

+	            	if (res == null || res.data == null) {

+	        	 		this.$log.error('WidgetCatalogService::getWidgetCatalogParameters Failed: Result or result.data is null');

+	                    deferred.reject("WidgetCatalogService::getWidgetCatalogParameters Failed: Result or result.data is null");

+	                } else {

+	                    deferred.resolve(res.data);

+	                }

+                })

+                .catch(status => {

+                	deferred.reject(status);

+                });

+           

+            return deferred.promise;

+        }

+        

+        

+        saveWidgetParameter(widgetParamObject){

+        	let deferred = this.$q.defer();

+            this.$log.info('WidgetsCatalogService::saveWidgetParameter');

+            let url = this.conf.api.widgetCommon + "/parameters";

+            this.$http({

+                method: "POST",

+                url: url,

+                cache: false,

+                data: widgetParamObject,

+                headers:{  

+                	'X-Widgets-Type': 'all',

+	                'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then(res => {

+	            	if (res == null || res.data == null) {

+	        	 		this.$log.error('WidgetCatalogService::getWidgetCatalogParameters Failed: Result or result.data is null');

+	                    deferred.reject("WidgetCatalogService::getWidgetCatalogParameters Failed: Result or result.data is null");

+	                } else {

+	                    deferred.resolve(res.data);

+	                }

+                })

+                .catch(status => {

+                	deferred.reject(status);

+                });

+           

+            return deferred.promise;

+        }

+        

+    }

+    

+    WidgetsCatalogService.$inject = ['$q', '$log', '$http', 'conf','uuid4','base64Service', 'beReaderService', 'utilsService'];

+    angular.module('ecompApp').service('widgetsCatalogService', WidgetsCatalogService)

+})();

diff --git a/ecomp-portal-FE-common/client/app/services/widgets/widgets.service.js b/ecomp-portal-FE-common/client/app/services/widgets/widgets.service.js
new file mode 100644
index 0000000..735a731
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/services/widgets/widgets.service.js
@@ -0,0 +1,206 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+/**

+ * Created by doritrieur on 12/3/15.

+ */

+'use strict';

+

+(function () {

+    class WidgetsService {

+        constructor($q, $log, $http, conf, uuid, utilsService) {

+            this.$q = $q;

+            this.$log = $log;

+            this.$http = $http;

+            this.conf = conf;

+            this.uuid = uuid;

+            this.utilsService = utilsService;

+        }

+

+        getUserWidgets() {

+            let deferred = this.$q.defer();

+            this.$log.info('WidgetsService::getUserWidgets');

+            this.$http({

+                method: 'GET',

+                url: this.conf.api.widgets,

+                cache: false,

+                headers: {

+                    'X-Widgets-Type': 'all',

+                    'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then(res => {

+                // If response comes back as a redirected HTML page which IS NOT a success

+                if (this.utilsService.isValidJSON(res) == false) {

+                	var msg = 'WidgetsService::getUserWidgets Failed';

+                	this.$log.error(msg);

+                	deferred.reject(msg);

+                } else {

+                	// this.$log.info('WidgetsService::getUserWidgets Succeeded');

+                	deferred.resolve(res.data);

+                }

+            })

+            .catch(status => {

+                deferred.reject(status);

+            });

+            return deferred.promise;

+        }

+

+        getManagedWidgets() {

+            let deferred = this.$q.defer();

+            this.$log.info('WidgetsService::getManagedWidgets');

+            this.$http({

+                method: 'GET',

+                url: this.conf.api.widgets,

+                cache: false,

+                headers: {

+                    'X-Widgets-Type': 'managed',

+                    'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then(res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success

+                    if (this.utilsService.isValidJSON(res)== false) {

+                        deferred.reject('WidgetsService::getManagedWidgets Failed');

+                    } else if(Object.keys(res).length == 0 && this.utilsService.isValidJSON(res)) {

+                        deferred.resolve(null);

+                    } else {

+                        this.$log.info('WidgetsService::getManagedWidgets Succeeded');

+                        deferred.resolve(res.data);

+                    }

+                })

+                .catch(status => {

+                    deferred.reject(status);

+                });

+            return deferred.promise;

+        }

+

+        createWidget(widgetData) {

+            let deferred = this.$q.defer();

+            this.$log.info('WidgetsService::createWidget');

+            this.$http({

+                method: 'POST',

+                url: this.conf.api.widgets,

+                cache: false,

+                data: widgetData,

+                headers: {

+                    'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then(res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success

+                    if (this.utilsService.isValidJSON(res)== false) {

+                        deferred.reject('WidgetsService::createWidget Failed');

+                    } else {

+                        this.$log.info('WidgetsService::createWidget Succeeded');

+                        deferred.resolve(res.data);

+                    }

+                })

+                .catch(errRes => {

+                    deferred.reject(errRes);

+                });

+            return deferred.promise;

+        }

+

+        updateWidget(widgetId, widgetData) {

+            let deferred = this.$q.defer();

+            this.$log.info('WidgetsService::updateWidget');

+            let url = this.conf.api.widgets + '/' + widgetId;

+            this.$http({

+                method: 'PUT',

+                url: url,

+                cache: false,

+                data: widgetData,

+                headers: {

+                    'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then(res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success

+                    if (this.utilsService.isValidJSON(res)== false) {

+                        deferred.reject('WidgetsService::updateWidget Failed');

+                    } else {

+                        this.$log.info('WidgetsService::updateWidget Succeeded');

+                        deferred.resolve(res.data);

+                    }

+                })

+                .catch(errRes => {

+                    deferred.reject(errRes);

+                });

+            return deferred.promise;

+        }

+

+        deleteWidget(widgetId) {

+            let deferred = this.$q.defer();

+            this.$log.info('WidgetsService::deleteWidget');

+            let url = this.conf.api.widgets + '/' + widgetId;

+            this.$http({

+                method: 'DELETE',

+                url: url,

+                cache: false,

+                headers: {

+                    'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then(res => {

+                    // If response comes back as a redirected HTML page which IS NOT a success

+                    if (this.utilsService.isValidJSON(res)== false) {

+                        deferred.reject('WidgetsService::deleteWidget Failed');

+                    } else {

+                        this.$log.info('WidgetsService::deleteWidget Succeeded');

+                        deferred.resolve(res.data);

+                    }

+                })

+                .catch(status => {

+                    deferred.reject(status);

+                });

+            return deferred.promise;

+        }

+

+        checkIfWidgetUrlExists(urlToValidate) {

+            let deferred = this.$q.defer();

+            this.$log.info('WidgetsService::checkIfWidgetUrlExists:' + urlToValidate);

+            let reqBody = {

+                url: urlToValidate

+            };

+            this.$http({

+                method: 'POST',

+                url: this.conf.api.widgetsValidation,

+                cache: false,

+                data: reqBody,

+                headers: {

+                    'X-ECOMP-RequestID':this.uuid.generate()

+                }

+            }).then(() => {

+                //if exists return true

+                    deferred.resolve(true);

+                })

+                .catch(response => {

+                    if (this.utilsService.isValidJSON(res)== false) {

+                        deferred.reject('WidgetsService::checkIfWidgetUrlExists Failed');

+                    } else {

+                        if (response.data.status === 404) {

+                            deferred.resolve(false);

+                        } else {

+                            deferred.reject(response.data);

+                        }

+                    }

+                });

+            return deferred.promise;

+        }

+

+    }

+    WidgetsService.$inject = ['$q', '$log', '$http', 'conf','uuid4', 'utilsService'];

+    angular.module('ecompApp').service('widgetsService', WidgetsService)

+})();

diff --git a/ecomp-portal-FE-common/client/app/styles/buttons.less b/ecomp-portal-FE-common/client/app/styles/buttons.less
new file mode 100644
index 0000000..dd11101
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/styles/buttons.less
@@ -0,0 +1,54 @@
+.ecomp-btn {
+    .white14m;
+    border-style: solid;
+    border-width: 1px;
+    border-radius: 6px;
+    box-shadow: 0px 1px 0.99px 0.01px rgba(2, 60, 89, 0.004);
+    height: 29px;
+    line-height: 29px;
+    cursor: pointer;
+    text-align: center;
+
+    &:disabled {
+        opacity: .5;
+        cursor: default;
+    }
+    &:focus {
+        outline: 0;
+        border: 1px solid @funcRed;
+    }
+}
+.btn-green {
+    .ecomp-btn;
+    border-color: @green-border;
+    background: @green-active;
+    &:hover {
+        &:disabled {
+            background: @green-active;
+        }
+        background: @green-hover;
+    }
+}
+.btn-blue {
+    .ecomp-btn;
+    //border-color: @blue-border;
+    background: @blue-active;
+
+    &:hover {
+        -webkit-transition: background-color .3s ease-out;
+        -moz-transition: background-color .3s ease-out;
+        transition: background-color .3s ease-out;
+        background: @blue-hover;
+        color : @funcBkgGray;
+        &.disabled {
+            background: @blue-active;
+            opacity: 0.5;
+            cursor: default;
+        }
+    }
+
+    &.disabled {
+        opacity: 0.5;
+        cursor: default;
+    }
+}
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/styles/ecomp-general.less b/ecomp-portal-FE-common/client/app/styles/ecomp-general.less
new file mode 100644
index 0000000..79003c0
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/styles/ecomp-general.less
@@ -0,0 +1,98 @@
+.w-ecomp-main-view-title {
+    //.a24r;
+    .blackText24m;
+    //margin-left: @table-margin-left;
+    width: @table-width;
+    padding-bottom: 15px;
+    //.content_justify;
+    //padding-left: 20px;
+    margin:auto;
+}
+
+.override_background {
+    .bg_portalGray; // 1610
+}
+
+input.table-search {
+  font-style: italic;
+  padding: 7px 10px;
+  width: 440px;
+  display: inline-block;
+  position: relative;
+  margin-bottom: 10px;
+  border-radius: 6px;
+  border: 1px solid @portalLGray;
+  height: 32px;
+  .form-field, .form-field-input-container {
+    width: 440px;
+  }
+  &:focus{
+    border-color: @blue-hover;
+  }
+  &:active{
+    border-color: @portalLGray;
+  }
+}
+
+.add-button {
+  cursor: pointer;
+  width: 120px;
+  float: right;
+
+  .btn-blue;
+
+  line-height: 32px;
+  height: 32px;
+
+  &::before {
+    .ico_add_user;
+    content: '';
+    vertical-align: middle;
+    display: inline-block;
+    margin-right: 10px;
+  }
+}
+
+.edit-button {
+  cursor: pointer;
+  width: 132px;
+  float:right;
+  .btn-blue;
+
+  line-height: 32px;
+  height: 32px;
+
+  &::before {
+  font-family: portalicons;
+    content: '\e6bc';
+    vertical-align: middle;
+    display: inline-block;
+    margin-right: 10px;
+  }
+}
+
+.errors-text {
+  .dGray14r;
+}
+
+.general-errors-text {
+  .dGray14r;
+  color: @err;
+}
+
+.ecomp-table-repeat{
+	margin-bottom:5px;
+}
+
+.table-control-fields{
+	display:inline-flex
+}
+
+.table-control-buttons{
+	float:right;
+}
+
+.table-control-fields input, .table-dropdown{
+	width:400px;
+	margin-right:10px;
+}
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/styles/form.less b/ecomp-portal-FE-common/client/app/styles/form.less
new file mode 100644
index 0000000..0caaf48
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/styles/form.less
@@ -0,0 +1,143 @@
+.form {
+    input, textarea, label , .multiple {
+        .blue14r;
+    }
+    input, textarea , .multiple {
+        &:focus {
+            outline: none;
+            border: 1px solid @portalDBlue;
+        }
+        border: 1px solid @portalDBlue;
+        padding: 8px;
+        &:disabled {
+            border: 1px solid @funcBkgGray;
+            .bg_portalDarkPurple;
+        }
+        &.ng-invalid.ng-touched {
+            border: 1px solid @err;
+            box-shadow: 0 0 2.55px 0.45px rgba(218,31,61,.5);
+            &:focus {
+                border: 1px solid @portalDBlue;
+            }
+        }
+    }
+    input[type=number] {
+        -moz-appearance:textfield;
+    }
+    textarea {
+        min-height: 148px;
+        max-width: @input-max-width;
+    }
+    .flex-group {
+        display: flex;
+        justify-content: space-between;
+    }
+    .form-group {
+        margin: 0 0 18px;
+        position: relative;
+    }
+    .checkbox {
+        label {
+            cursor: pointer;
+        }
+    }
+    label {
+        margin: 0 0 3px;
+        display: block;
+    }
+    .text {
+        width: 100%;
+        display: block;
+    }
+    .short {
+        width: 104px;
+    }
+    .multiple{
+        padding: 0;
+    }
+    .error {
+        color: @portalGreen;
+        background: @err;
+        border-radius: 0 5px 5px 0;
+        font-size: 12px;
+        line-height: 20px;
+        text-align: center;
+        width: 100%;
+        position: absolute;
+        top: 18px;
+        margin-left: 75%;
+        z-index: 100;
+        height: 33px;
+        padding: 8px;
+        box-shadow: 0 0 2.55px 0.45px rgba(218,31,61,.5);
+        &:before {
+            content: '';
+            width: 0;
+            height: 0;
+            position: absolute;
+            left: -9px;
+            top: 7px;
+            border-top: 9px solid transparent;
+            border-bottom: 9px solid transparent;
+            border-right: 9px solid @err;
+        }
+    }
+}
+
+.required:before {
+    color: @funcRed;
+    margin-right: 2px;
+    content:  "* ";
+    position: absolute;
+    top: 28px;
+    left: -10px;
+}
+
+.custom-input-field{
+    padding-left: 6px;
+    padding-top: 0;
+
+    height: 30px;
+    line-height: 30px;
+    width: 100%;
+    border: 1px solid @portalLGray;
+    border-radius: 0;
+    position: relative;
+    padding-bottom: 0px;
+
+}
+.custom-select-field{
+    padding-left: 6px;
+    padding-top: 0;
+
+    height: 30px;
+    line-height: 30px;
+    width: 100%;
+    border: 1px solid @portalLGray;
+    border-radius: 0;
+    position: relative;
+}
+
+.custom-select-wrap{
+    width: 100%;
+    position: relative;
+    display: inline-block;
+    vertical-align: middle;
+}
+.custom-select-wrap::after{
+    content: '';
+    .arrow_down;
+    display: block;
+    position: absolute;
+    top: 12px;
+    right: 10px;
+    z-index: 999;
+}
+
+select{
+    -webkit-appearance:none;
+    -moz-appearance:none;
+    appearance:none;
+    text-indent: 0.01px;
+    text-overflow: '';
+}
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/styles/mixins.less b/ecomp-portal-FE-common/client/app/styles/mixins.less
new file mode 100644
index 0000000..9c598c0
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/styles/mixins.less
@@ -0,0 +1,366 @@
+/*------------------------------- Backgrounds -------------------------------*/
+//.bg_a  {background-color:  @a;}
+//.bg_b  {background-color:  @b;}
+//.bg_c  {background-color:  @c;}
+//.bg_d  {background-color:  @d;}
+//.bg_e  {background-color:  @e;}
+//.bg_f  {background-color:  @f;}
+//.bg_g  {background-color:  @g;}
+//.bg_h  {background-color:  @h;}
+//.bg_i  {background-color:  @i;}
+//.bg_j  {background-color:  @j;}
+//.bg_k  {background-color:  @k;}
+//.bg_l  {background-color:  @l;}
+//.bg_m  {background-color:  @m;}
+//.bg_n  {background-color:  @n;}
+//.bg_o  {background-color:  @o;}
+//.bg_p  {background-color:  @p;}
+//.bg_q  {background-color:  @q;}
+//.bg_r  {background-color:  @r;}
+//.bg_s  {background-color:  @s;}
+//.bg_t  {background-color:  @t;}
+//.bg_u  {background-color:  @u;}
+//.bg_v  {background-color:  @v;}
+//.bg_w  {background-color:  @bg;}
+.bg_portalGray {background-color: @funcBkgGray;}
+.bg_portalWhite {background-color: @portalWhite;}
+.bg_portalDarkPurple {background-color: @portalDPurple;}
+.bg_portalDBlue {background-color: @portalDBlue;}
+
+
+/*------------------------------- Fonts -------------------------------*/
+
+/* font family */
+.clearview {
+    .regular {
+        font-family: @font-clearview-regular;
+    }
+    .medium {
+      font-family: @font-omnes-medium;
+    }
+    .light {
+        font-family: @font-clearview-light;
+    }
+    .regular-italic {
+        font-family: @font-omnes-regular-italic;
+    }
+    .bold {
+        font-family: @font-clearview-bold;
+    }
+}
+.omnes {
+  .regular {
+    font-family: @font-omnes-regular;
+  }
+  .medium {
+    font-family: @font-omnes-medium;
+  }
+  .light {
+    font-family: @font-omnes-light;
+  }
+  .regular-italic {
+    font-family: @font-omnes-regular-italic;
+  }
+  .bold {
+    font-family: @font-omnes-bold;
+  }
+}
+
+//.a {color:  @a;}
+.portalBlue {color:  @portalBlue;}
+//.b {color:  @b;}
+//.c {color:  @c;}
+.portalDBlue {color:  @portalDBlue;}
+.portalTxtBlack {color:  @funcTextBlack;}
+//.d {color:  @d;}
+//.e {color:  @e;}
+.portalGreen {color:  @portalGreen;}
+//.f {color:  @f;}
+//.g {color:  @g;}
+//.h {color:  @h;}
+//.i {color:  @i;}
+//.j {color:  @j;}
+//.k {color:  @k;}
+.portalRed {color:  @funcRed;}
+//.l {color:  @l;}
+//.m {color:  @m;}
+//.n {color:  @n;}
+.portalDGray {color:  @portalDGray;}
+//.o {color:  @o;}    //  @portalDGray
+//.p {color:  @p;}
+//.q {color: @q;}
+//.r {color: @r;}
+//.s {color: @s;}
+//.t {color: @t;}
+//.u {color: @u;}
+.portalWhite {color:  @portalWhite;}
+//.v {color: @v;}
+//.z {color: @z;}
+
+
+
+/* font type and size */
+//._14rc {
+//  .omnes > .regular;
+//  font-size: 14px;
+//}
+._12ic {
+  .omnes > .regular-italic;
+  font-size: 12px;
+}
+._16rc {
+  .omnes > .regular;
+  font-size: 16px;
+}
+._18rc {
+  .omnes > .regular;
+  font-size: 18px;
+}
+._18bc {
+  .omnes > .bold;
+  font-size: 18px;
+}
+._12r {
+    .omnes > .regular;
+    font-size: 12px;
+}
+._14m {
+    .omnes > .medium;
+    font-size: 14px;
+}
+._14r{
+    .omnes > .regular;
+    font-size: 14px;
+}
+._16m {
+    .omnes > .medium;
+    font-size: 16px;
+}
+._16r{
+    .omnes > .regular;
+    font-size: 16px;
+}
+._21r{
+    .omnes > .regular;
+    font-size: 21px;
+}
+._21m{
+    .omnes > .medium;
+    font-size: 21px;
+}
+._24r{
+    .omnes > .regular;
+    font-size: 24px;
+}
+._24m{
+    .omnes > .medium;
+    font-size: 24px;
+}
+._24b{
+    .omnes > .bold;
+    font-size: 24px;
+}
+._40r{
+    .omnes > .regular;
+    font-size: 40px;
+}
+._16m{
+    .omnes > .medium;
+    font-size: 16px;
+}
+._18r{
+    .omnes > .regular;
+    font-size: 18px;
+}
+._13r{
+    .omnes > .regular;
+    font-size: 13px;
+}
+
+/* font mixins */
+//.a14m{
+//    .a;
+//    ._14m;
+//}
+//.b14r{
+//    .b;
+//    ._14r;
+//}
+//.d14m{
+//    .d;
+//    ._14m;
+//}
+//.a14r{
+//    .a;
+//    ._14r;
+//}
+.blue14r{
+    .portalBlue;
+    ._14r;
+}
+//.b14m{
+//    .b;
+//    ._14m;
+//}
+//.c16r{
+//    .c;
+//    ._16r;
+//}
+.portalDBlue16r{
+    .portalBlue;
+    ._16r;
+}
+//.c18b{
+//  .c;
+//  ._18bc;
+//}
+.portalBlue24r{
+    .portalBlue;
+    ._24r;
+}
+.dBlue24r{
+    .portalDBlue;
+    ._24r;
+}
+.blackText21m{
+    .portalTxtBlack;
+    ._21m;
+}
+.blackText24m{
+    .portalTxtBlack;
+    ._24m;
+}
+//.a24b{
+//  .a;
+//  ._24b;
+//}
+//.d40r{
+//    .d;
+//    ._40r;
+//}
+//.d16r{
+//    .d;
+//    ._16r;
+//}
+//.b16r{
+//    .b;
+//    ._16m;
+//}
+//.u21r{
+//    .u;
+//    ._21r;
+//}
+//.u40r{
+//    .u;
+//    ._40r;
+//}
+//.u16r{
+//    .u;
+//    ._16r;
+//}
+.portalWhite16r {
+    .portalWhite;
+    ._16r;
+}
+//.u16m{
+//    .u;
+//    ._16m;
+//}
+//.u18r{
+//    .u;
+//    ._18r;
+//}
+//.o14r{
+//    .o;
+//    ._14r;
+//}
+.dGray14r{
+    .portalDGray;
+    ._14r;
+}
+//.o16m{
+//  .o;
+//  ._16m;
+//}
+//.o12i{
+//  .o;
+//  ._12ic
+//}
+.portalDGray12i{
+    .portalDGray;
+    ._12ic
+}
+//.u14m{
+//    .u;
+//    ._14m;
+//}
+.white14m{
+    .portalWhite;
+    ._14m;
+}
+.white12r{
+    .portalWhite;
+    ._12r;
+}
+//.u13r{
+//    .u;
+//    ._13r;
+//}
+//.n18r{
+//    .n;
+//    ._18r;
+//}
+.dGray18r {
+    .portalDGray;
+    ._18r;
+}
+//.n14r{
+//    .n;
+//    ._14r;
+//}
+.dGray14r {
+    .portalDGray;
+    ._14r;
+}
+//.n16r{
+//    .n;
+//    ._16r;
+//}
+//.n16m{
+//    .n;
+//    ._16m;
+//}
+//.n14r{
+//    .n;
+//    ._14r;
+//}
+//.n14m{
+//    .n;
+//    ._14m;
+//}
+//.n16m{
+//  .n;
+//  ._16m;
+//}
+.dGray16m {
+    .portalDGray;
+    ._16m;
+}
+//.p13r{
+//    .p;
+//    ._13r;
+//}
+.gray13r {
+    .portalDGray;
+    ._13r;
+}
+
+/* Content justify */
+
+.content_justify {
+    margin-top: 0;
+    margin-right: 0;
+    margin-left: @page-main-left;
+    margin-bottom: 0;
+    width: @table-width;
+}
diff --git a/ecomp-portal-FE-common/client/app/styles/ng-dialog.less b/ecomp-portal-FE-common/client/app/styles/ng-dialog.less
new file mode 100644
index 0000000..d5ecff6
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/styles/ng-dialog.less
@@ -0,0 +1,71 @@
+.ngdialog.ngdialog-theme-default.ng-scope {
+.ngdialog-content {
+        background-color: @portalWhite;
+        padding: 0;
+        box-shadow: 0px 0px 6px 0px rgba(0, 0, 0, 0.5);
+        border-radius: 4px;
+        top: -100px;
+    .title {
+            margin: 0 16px;
+            height: 48px;
+            line-height: 48px;
+        }
+
+    .ngdialog-close {
+            top: 6px;
+            right: 6px;
+        }
+    .app-roles-main {
+            margin: 20px 16px 0;
+        }
+    }
+}
+
+//#need fix!!!
+.ngdialog.ngdialog-theme-default .ngdialog-content {
+    width: 580px;
+    padding: 16px;
+}
+
+.ngdialog.ngdialog-theme-default.confirm-box .ngdialog-content{
+  @height: 160px;
+  @width: 400px;
+
+  width: @width;
+  height: @height;
+  padding: 20px;
+  position: absolute;
+  top: 300px;
+  left: ~"calc(50% - @{width}/2)";
+}
+
+
+.dialog-control {
+  position: absolute;
+  bottom: 16px;
+  right: 16px;
+  .next-button {
+    display: inline-block;
+    .btn-blue;
+    width: 90px;
+    margin-right: 10px;
+  }
+  .cancel-button {
+    display: inline-block;
+    .btn-blue;
+    width: 90px;
+  }
+  .save-button {
+    display: inline-block;
+    .btn-blue;
+    width: 90px;
+  }
+  .back-button {
+    display: inline-block;
+    .btn-blue;
+    width: 90px;
+    margin-right: 10px;
+  }
+
+}
+
diff --git a/ecomp-portal-FE-common/client/app/styles/reset.less b/ecomp-portal-FE-common/client/app/styles/reset.less
new file mode 100644
index 0000000..2c5fbd5
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/styles/reset.less
@@ -0,0 +1,60 @@
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+	margin: 0;
+	padding: 0;
+	border: 0;
+	font-size: 100%;
+	font: inherit;
+	vertical-align: baseline;
+}
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+	display: block;
+}
+body {
+	line-height: 1;
+}
+ol, ul {
+	list-style: none;
+}
+blockquote, q {
+	quotes: none;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+	content: '';
+	content: none;
+}
+table {
+	border-collapse: collapse;
+	border-spacing: 0;
+}
+.clearfix:before,
+.clearfix:after { content: ""; display: table; }
+.clearfix:after { clear: both; }
+.clearfix { zoom: 1;}
+
+* {
+    box-sizing: border-box;
+}
+
+input[type=number]::-webkit-inner-spin-button,
+input[type=number]::-webkit-outer-spin-button {
+	-webkit-appearance: none;
+	margin: 0;
+}
+input[type=number] {
+	-moz-appearance:textfield;
+}
diff --git a/ecomp-portal-FE-common/client/app/styles/select2.less b/ecomp-portal-FE-common/client/app/styles/select2.less
new file mode 100644
index 0000000..835b336
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/styles/select2.less
@@ -0,0 +1,230 @@
+.select2-drop {
+    //position: absolute;
+    border-radius: 0 0 2px 2px;
+
+    outline: none;
+}
+
+.select2-container {
+    margin-right: 8px;
+
+
+    .select2-choice {
+        border-color: @portalLGray;
+        border-radius: 2px;
+        background-image: none;
+        height: 32px;
+        width: 100%;
+    }
+
+    &.select2-dropdown-open {
+        .select2-choice {
+            border-color: @portalBlue;
+        }
+        .select2-choice .select2-arrow b:after,
+        &.select2-container-disabled .select2-choice .select2-arrow b:after,
+        .select2-choice .select2-arrow b:after {
+            .arrow_up;
+        }
+    }
+
+    .select2-choice .select2-arrow b:after,
+    &.select2-container-disabled .select2-choice .select2-arrow b:after,
+    .select2-choice .select2-arrow b:after {
+        .arrow_down;
+        height: 10px;
+        width: 10px;
+        top: 12px;
+        right: 10px;
+        content: '';
+    }
+    .select2-chosen, .select2-container input {
+        line-height: 32px;
+    }
+}
+
+.select2-results {
+    border-radius: 0 0 2px 2px;
+    margin: -13px 0 0 0;
+    .select2-result {
+        &:first-child {
+            margin-top: 14px;
+        }
+        &:hover {
+            background-color: @funcBkgGray;
+        }
+    }
+
+    .select2-result-label {
+        outline: none;
+    }
+}
+
+.select2-drop-above {
+    &.select2-drop {
+        border-radius: 2px 2px 0 0;
+    }
+
+    &.select2-dropdown-open .select2-choice {
+        border-radius: 0 0 2px 2px !important;
+        background: @portalWhite;
+    }
+}
+
+.select2-dropdown-open {
+    .select2-choice {
+        border-color: @portalLGray;
+
+        .select2-arrow b:after {
+
+        }
+    }
+}
+
+.select2-drop-active {
+    border-color: @portalBlue;
+}
+//.select2-container {
+//    margin: 0;
+//    position: relative;
+//    display: block;
+//    .a14r;
+//
+//    &.ng-invalid.ng-touched {
+//        .select2-choice {
+//            border: 1px solid @err;
+//        }
+//    }
+//
+//    &.select2-container-active.ng-invalid.ng-touched {
+//        .select2-choice {
+//            border: 1px solid @c;
+//        }
+//    }
+//
+//    .select2-choice {
+//        display: block;
+//        padding: 10px 0 6px 8px;
+//        overflow: hidden;
+//        position: relative;
+//        border: 1px solid @o;
+//        text-decoration: none;
+//        cursor: pointer;
+//
+//        & > .select2-chosen {
+//            margin-right: 33px;
+//            display: block;
+//            overflow: hidden;
+//            white-space: nowrap;
+//            text-overflow: ellipsis;
+//        }
+//
+//        .select2-arrow {
+//            display: inline-block;
+//            width: 33px;
+//            height: 100%;
+//            position: absolute;
+//            right: 0;
+//            top: 0;
+//            //border-left: 1px solid @s;;
+//            //border-radius: 0 4px 4px 0;
+//            display: flex;
+//            align-items: center;
+//            justify-content: center;
+//
+//            b {
+//                display: block;
+//                //.spr_dropdown_arrow; //fix this
+//            }
+//        }
+//    }
+//
+//    .select2-drop-active {
+//        border: 1px solid @c;
+//        border-top: none;
+//        position: absolute;
+//        top: 32px;
+//        left: 0;
+//        width: 100%;
+//        .bg_f;
+//        z-index: 100;
+//    }
+//
+//    .select2-search {
+//        display: inline-block;
+//        width: 100%;
+//        min-height: 30px;
+//        margin: 0;
+//        padding-left: 4px;
+//        padding-right: 4px;
+//        position: relative;
+//        z-index: 10000;
+//        white-space: nowrap;
+//
+//        input {
+//            width: 100%;
+//            height: auto !important;
+//            min-height: 26px;
+//            padding: 4px 20px 4px 5px;
+//            margin: 0;
+//            outline: 0;
+//            border: 1px solid @o;
+//            &:focus {
+//                border: 1px solid @o;
+//            }
+//            border-radius: 2px;
+//            box-shadow: none;
+//        }
+//    }
+//
+//}
+/////
+//
+//.select2-container-active .select2-choice,
+//.select2-container-active .select2-choices {
+//    border: 1px solid @c;
+//    outline: none;
+//    box-shadow: 0 0 5px rgba(0, 0, 0, .3);
+//}
+//
+//.select2-dropdown-open {
+//    .select2-choice {
+//        border-bottom-color: transparent;
+//        box-shadow: 0 1px 0 #fff inset;
+//        border-bottom-left-radius: 0;
+//        border-bottom-right-radius: 0;
+//
+//        .select2-arrow {
+//            background: transparent;
+//            border-left: none;
+//            filter: none;
+//        }
+//    }
+//    .select2-results {
+//        max-height: 200px;
+//        padding: 0 0 0 4px;
+//        margin: 4px 4px 4px 0;
+//        position: relative;
+//        overflow-x: hidden;
+//        overflow-y: auto;
+//        -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+//
+//        li.select2-result-with-children > .select2-result-label {
+//            font-weight: bold;
+//        }
+//
+//        .select2-result-label {
+//            padding: 3px 7px 4px;
+//            margin: 0;
+//            cursor: pointer;
+//        }
+//
+//        .select2-highlighted {
+//            background: @c;
+//            color: @f;
+//        }
+//    }
+//}
+//.select2-display-none {
+//    display: none;
+//}
diff --git a/ecomp-portal-FE-common/client/app/styles/spinner.less b/ecomp-portal-FE-common/client/app/styles/spinner.less
new file mode 100644
index 0000000..b1bf6a1
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/styles/spinner.less
@@ -0,0 +1,37 @@
+.ecomp-spinner{
+  background: url('../assets/images/spinner.gif') top left no-repeat;
+  @height: 32px;
+  @width: 32px;
+
+  width: @width;
+  height: @height;
+  position: absolute;
+  top: 35%;
+  left: ~"calc(50% - @{width}/2)";
+}
+
+.ecomp-save-spinner{
+  background: url('../assets/images/spinner.gif') top left no-repeat;
+  @resize: 25px;
+  width: @resize;
+  height: @resize;
+  background-size: @resize;
+  position: absolute;
+  left: -50px;
+  top: 2px;
+}
+
+.ecomp-small-spinner{
+  background: url('../assets/images/spinner.gif') top left no-repeat;
+  @resize: 25px;
+  width: @resize;
+  height: @resize;
+  background-size: @resize;
+  position: relative;
+  display: inline-block;
+  vertical-align: 2px;
+  cursor: pointer;
+  top: 6px;
+  color: transparent;
+  margin-left: 8px;
+}
diff --git a/ecomp-portal-FE-common/client/app/styles/sprites.less b/ecomp-portal-FE-common/client/app/styles/sprites.less
new file mode 100644
index 0000000..a241851
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/styles/sprites.less
@@ -0,0 +1,101 @@
+.sprite-base {
+  background: url('../assets/images/sprite.png') top left no-repeat;
+}
+
+.portal-logo {
+  .sprite-base;
+  width: 28px;
+  height: 28px;
+  background-position: -10px -10px;
+}
+
+.ico_user {
+  .sprite-base;
+  width: 16px;
+  height: 19px;
+  background-position: -10px -48px;
+}
+
+.ico_add_user {
+  .sprite-base;
+  width: 19px;
+  height: 19px;
+  background-position: -10px -78px;
+}
+
+.ico_dropdown {
+  .sprite-base;
+  width: 12px;
+  height: 7px;
+  background-position: -10px -107px;
+}
+
+.ico_search {
+  .sprite-base;
+  width: 14px;
+  height: 14px;
+  background-position: -10px -124px;
+}
+
+.ico_trash_default {
+  .sprite-base;
+  width: 12px;
+  height: 15px;
+  background-position: -10px -148px;
+}
+
+.ico_trash_disabled {
+  .sprite-base;
+  width: 12px;
+  height: 15px;
+  background-position: -10px -173px;
+}
+
+.arrow_down {
+  .sprite-base;
+  width: 12px;
+  height: 5px;
+  background-position: -10px -198px;
+}
+
+.arrow_up {
+  .sprite-base;
+  width: 12px;
+  height: 5px;
+  background-position: -10px -213px;
+}
+
+.checkbox_checked {
+  .sprite-base;
+  width: 14px;
+  height: 14px;
+  background-position: -10px -228px;
+}
+
+.checkbox_disabled {
+  .sprite-base;
+  width: 14px;
+  height: 14px;
+  background-position: -10px -252px;
+}
+
+.checkbox_unchecked {
+  .sprite-base;
+  width: 14px;
+  height: 14px;
+  background-position: -10px -276px;
+}
+
+.checkbox_unchecked_hover {
+  .sprite-base;
+  width: 14px;
+  height: 14px;
+  background-position: -10px -300px;
+}
+
+.default_upload_image{
+  .sprite-base;
+  width:150px;
+  height:100px;
+  background-position:-10px -324px;
+}
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/styles/variables.less b/ecomp-portal-FE-common/client/app/styles/variables.less
new file mode 100644
index 0000000..7a0a75e
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/styles/variables.less
@@ -0,0 +1,97 @@
+/*----------------------------------    General    ----------------------------------------------------------------*/
+@font-omnes-regular: Omnes-ECOMP-W02,Arial;
+@font-omnes-medium: Omnes-ECOMP-W02-Medium,Arial;
+@font-omnes-light: Omnes-ECOMP-W02-Light,Arial;
+@font-omnes-regular-italic: Omnes-ECOMP-W02-Italic,Arial;
+@font-omnes-bold: Omnes-ECOMP-W02-Bold,Arial;
+@font-clearview-regular: Omnes-ECOMP-W02,Arial;
+@font-clearview-bold: Omnes-ECOMP-W02-Bold,Arial;
+@font-clearview-light: Omnes-ECOMP-W02-Light,Arial;
+@font-clearview-regular-italic: Omnes-ECOMP-W02-Italic,Arial;
+
+@select-width: 440px;
+@header-height: 55px;
+@padding-left-side: 0px;
+@footer-height: 75px;
+
+@container-bottom:  32px;
+/* Top of Pages */
+@padding-top: 10px;
+@page-main-top: 105px;
+@page-main-left: 0;
+@page-main-right: 0;
+@page-main-bottom: @footer-height;
+@page-main-position: fixed;
+@page-main-overflow-y: scroll;
+
+/* tables */
+@table-width: 1170px;
+@table-margin: auto;
+@table-dropdown-filter-width: 460px;
+@table-dropdown-filter-display: inline-block;
+@table-margin-left: 230px;
+
+@second-level-top: 62px;
+@err: @funcRed;
+@input-max-width: 234px;
+/*------------------------------- Colors -------------------------------*/
+@a : #067ab4;   // @portalBlue
+@b : #156b97;
+@c : #0c2577;   // @portalDBlue
+@d : #44c8f5;
+@e : #4ca90c;   // @portalGreen
+@f : #27a337;
+@g : #c4d82d;
+@h : #ef6f00;
+@i : #fbb313;
+@j : #b30a3c;
+@k : #e00000;
+@l : #81017e;
+@m : #da0081;
+@n : #333333;   //@funcTextBlack
+@o : #666666;   // @portalDGray
+@p : #999999;   //@portalGray
+@q : #d8d8d8;   //@portalLGray
+@r : #eaeaea;
+//@s : #f2f2f2;
+@t : #f8f8f8;   // @funcBkgGray
+@u : #ffffff;   //  @portalWhite
+@v : #e6f6fb;   // @funcBkgGray
+@z : #199ddf;
+@bg : #F2F2F2;
+
+// Signature color
+@portalBlue    :   #009FDB;
+//  Secondary Colors
+@portalBlack   :   #000000;
+@portalWhite   :   #ffffff;
+@portalOrange   :   #EA7400;
+//  Functional Colors
+@funcBlueLink :   #0568AE;
+@funcRed  :   #CF2A2A;
+@funcYellow  :   #FFB81C;
+@funcGreen  :   #007A3E;
+@funcTextBlack  :   #191919;
+@funcBkgGray   :   #F2F2F2;
+//  Accent Colors
+@portalLBlue   :   #71C5E8;
+@portalDBlue   :   #0568AE;
+@portalLGray   :   #D2D2D2;
+@portalLightGray:   #eaeaea;
+@portalGray   :   #959595;
+@portalDGray   :   #5A5A5A;
+@portalYellow   :   #FFB81C;
+@portalLGreen   :   #B5BD00;
+@portalGreen   :   #4CA90C;
+@portalDGreen   :   #007A3E;
+@portalLPurple   :   #CAA2DD;
+@portalPurple   :   #9063CD;
+@portalDPurple   :   #702F8A;
+
+/*------------------------------- Buttons colors -------------------------------*/
+@green-active: #4CA90C;
+@green-hover: #209657;
+@green-border: #45a006;
+@blue-active: #067ab4;
+@blue-hover: #39b1ec;
+@blue-border: #036698;
diff --git a/ecomp-portal-FE-common/client/app/views/account-onboarding/account-add-details/account-add-details.html b/ecomp-portal-FE-common/client/app/views/account-onboarding/account-add-details/account-add-details.html
new file mode 100644
index 0000000..91027f9
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/account-onboarding/account-add-details/account-add-details.html
@@ -0,0 +1,125 @@
+<div class="account-add-details-modal">
+	<div id="account-add-details-title" class="account-details-title">Account
+		Details</div>
+
+	<div id="accounts">
+		<div class="account-properties-main"
+			scroll-top="accountAddDetails.scrollApi">
+			<form id="accounts-details-form" name="serviceForm" novalidate
+				autocomplete="off">
+				<div class="item required">
+					<div class="item-label">Account Name</div>
+					<input id="account-details-input-name"
+						type="text" name="name" ng-pattern="/^[\w -]*$/" maxlength="100"
+						ng-model="accountAddDetails.account.applicationName"
+						ng-change="accountAddDetails.updateAccountName()" required />
+
+					<div class="error-container"
+						ng-show="(accountAddDetails.emptyAccountName || serviceForm.name.$dirty)">
+						<div ng-messages="serviceForm.name.$error" class="error-container">
+							<small id="accounts-details-input-name-required"
+								class="err-message" ng-message="required">account Name
+								is required</small> <small id="accounts-details-input-name-pattern"
+								class="err-message" ng-message="pattern">account Name
+								must be letters, numbers, or underscore</small>
+						</div>
+					</div>
+
+					<div class="error-container"
+						ng-show="accountAddDetails.dupliateName == true">
+						<small id="accounts-details-input-name-dupliated"
+							class="err-message">Name not available - choose different
+							name </small>
+					</div>
+				</div>
+
+				<div class="item required">
+					<div class="item-label">Username</div>
+					<input id="account-details-input-username" 
+						type="text" name="username" maxlength="100"
+						ng-model="accountAddDetails.account.username"
+						ng-change="accountAddDetails.updateUsername()" />
+
+					<div class="error-container"
+						ng-show="(accountAddDetails.emptyAccountUsername 
+					|| (serviceForm.username.$dirty && accountAddDetails.account.username == ''))">
+						<small id="account-details-input-username-required"
+							class="err-message">Username is required</small>
+					</div>
+
+				</div>
+
+				<div class="item">
+					<div class="item-label">Password</div>
+					<input id="account-details-input-password"
+						type="password" name="password" maxlength="100"
+						ng-model="accountAddDetails.account.password" />
+				</div>
+
+				<div class="item">
+					<div class="item-label">Retype Password</div>
+					<input id="account-details-input-repassword"
+						type="password" name="repassword" maxlength="100"
+						ng-model="accountAddDetails.account.repassword"
+						ng-change="accountAddDetails.confirmPassword()" />
+
+					<div class="error-container"
+						ng-show="accountAddDetails.passwordMatched == false">
+						<small id="accounts-details-input-name-dupliated"
+							class="err-message">Password does not match the confirm
+							password</small>
+					</div>
+				</div>
+
+
+				<div class="add-endpoint-item">
+					<div class="item-label add-label-left">Add Endpoint</div>
+					<div class="icon-add add-label-right"
+						ng-click="accountAddDetails.addEndpoint()"></div>
+				</div>
+				<div class="item">
+					<div class="item-label"
+						ng-show="accountAddDetails.account.endpointList.length > 0">
+						Account endpoint</div>
+
+					<div id="account-details-user-paramters"
+						ng-repeat="endpoint in accountAddDetails.account.endpointList">
+
+						<div class="endpoint-item-left">
+							<input id="account-details-input-endpoint-name"
+								type="text" name="endpointName"
+								maxlength="200" ng-model="endpoint.name" 
+								ng-change="accountAddDetails.updateAccountEndpoint(endpoint)"/>
+						</div>
+
+						<div class="icon-circle-action-remove endpoint-item-middle"
+							ng-click="accountAddDetails.removeEndpointItem(endpoint)"></div>
+							
+						<div class="error-container endpoint-item-right"
+								ng-show="endpoint.valid == false">
+								<small id="accounts-details-input-invalid-endpoint"
+									class="err-message">Invalid end point format</small>
+						</div>
+							
+					</div>
+
+					<div class="account-property">
+						<input id="accounts-checkbox-app-is-enabled" type="checkbox"
+							class="checkbox-field"
+							ng-model="accountAddDetails.account.active" />
+						<div class="property-label checkbox-label">Active</div>
+					</div>
+					<div class="item">
+						<div id="account-details-next-button" class="submit-button"
+							ng-click="accountAddDetails.closeThisDialog()">close</div>
+						<div id="account-details-next-button" class="submit-button"
+							ng-click="accountAddDetails.saveChanges()">Save</div>
+					</div>
+				</div>
+
+			</form>
+		</div>
+
+
+	</div>
+</div>
diff --git a/ecomp-portal-FE-common/client/app/views/account-onboarding/account-add-details/account-add-details.js b/ecomp-portal-FE-common/client/app/views/account-onboarding/account-add-details/account-add-details.js
new file mode 100644
index 0000000..ff89dce
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/account-onboarding/account-add-details/account-add-details.js
@@ -0,0 +1,171 @@
+'use strict';
+(function () {
+    class AccountAddDetailsCtrl {
+        constructor($scope, $log, $interval, basicAuthAccountSerivce, errorMessageByCode, ECOMP_URL_REGEX, $window, confirmBoxService, $cookies) {
+     	    
+          
+           this.addEndpoint = () => {
+           	  this.account.endpointList.push({
+           		  valid: true
+           	  }); 
+           }
+        	
+            let init = () => {
+            	this.account = [];
+                this.account.endpointList = [];
+                this.passwordMatched = true;
+                this.dupliateName = false;
+                this.emptyAccountName = false;
+                this.emptyAccountUsername = false;
+                this.accountList = $scope.ngDialogData.list;
+                
+                if ($scope.ngDialogData && $scope.ngDialogData.account) {
+                    this.isEditMode = true;
+                    this.account = _.clone($scope.ngDialogData.account);
+                    this.account.repassword = this.account.password;
+                    this.account.endpointList = this.account.endpoints;
+                    if(this.account.isActive == 'Y')
+                    	this.account.active = true;
+                    else
+                    	this.account.active = false;
+                } else {
+                    this.isEditMode = false;
+                    this.account.active = true;
+                } 
+                console.log(this.account);
+            };
+            
+            let resetConflict = fieldName => {
+                delete this.conflictMessages[fieldName];
+                if($scope.widgetForm[fieldName]){
+                    $scope.widgetForm[fieldName].$setValidity('conflict', true);
+                }
+            };
+
+            
+            this.closeThisDialog = () => {
+            	$scope.closeThisDialog(true);
+            }
+            
+            this.removeEndpointItem = (endpoint) => {
+        		for(var i = 0; i < this.account.endpointList.length; i++){
+        			if(this.account.endpointList[i].name == endpoint.name){
+        				this.account.endpointList.splice(i, 1);
+        				return; 
+        			}
+        		}
+            }
+            
+            this.confirmPassword = () => {
+            	this.passwordMatched =  true;
+            }
+            
+            this.updateUsername = () => {
+            	this.emptyAccountUsername = false;
+            }
+            
+            this.updateAccountName = () => {
+            	this.dupliateName = false;
+            	for(var i = 0; i < this.accountList.length; i++){
+            		if(this.accountList[i].applicationName == this.account.applicationName){
+            			this.dupliateName = true;
+            			return;
+            		}
+            	}
+            }
+            
+            this.updateAccountEndpoint = (endpoint) => {
+            	endpoint.valid = true;
+            }
+           
+            this.saveChanges = () => {
+
+            	var isValid = true;
+            	var r = /\/[^ "]+$/;
+            	
+            	for(var i = 0; i < this.account.endpointList.length; i++){
+               		if(this.account.endpointList[i].name == undefined
+            		|| this.account.endpointList[i].name == null
+            		|| this.account.endpointList[i].name == ""){
+            			this.account.endpointList.splice(i, 1);
+            			i--;
+            		}else{
+            			if(!this.account.endpointList[i].name.startsWith("/")){
+            				this.account.endpointList[i].name = "/" + this.account.endpointList[i].name;
+            			}
+            			if(!r.test(this.account.endpointList[i].name)){
+            				this.account.endpointList[i].valid = false;
+            				isValid = false;
+            			}
+            			
+            		}
+            	}
+            	
+            	if(this.account.applicationName == ''
+                || this.account.applicationName == undefined){
+            		this.emptyAccountName = true;
+                	isValid = false;
+                }
+            	
+            	if(this.account.username == ''
+                || this.account.username == undefined){
+            		this.emptyAccountUsername = true;
+                    isValid = false;
+                }
+            	
+            	if(this.dupliateName == true){
+                   isValid = false;
+               	}
+            	
+            	if(this.account.password != this.account.repassword){
+            		this.passwordMatched =  false;
+            		isValid = false;
+            	}
+            	
+            	if(!isValid)
+            		return;
+            	
+               	
+            	
+            	var active = 'N';
+            	if(this.account.active == true)
+            		active = 'Y';
+            	
+            	var newAccount = {
+            			applicationName: this.account.applicationName,
+            			username: this.account.username,
+            			password: this.account.password,
+            			endpoints: this.account.endpointList,
+            			isActive: active
+            	};
+            	
+            	
+            	if(this.isEditMode){
+            		var message = "Are you sure you want to change '" + this.account.applicationName + "'?"
+            		confirmBoxService.editItem(message).then(isConfirmed => {
+	            		if(isConfirmed){
+	            			basicAuthAccountSerivce.updateAccount(this.account.id, newAccount).then(() => {
+	                			$scope.closeThisDialog(true);
+	                		});
+	           			}
+            		});
+            	}else{
+            		basicAuthAccountSerivce.createAccount(newAccount).then(() => {
+            			$scope.closeThisDialog(true);
+            		});
+            	}
+            	
+            	
+            
+            }
+            
+            
+            init();
+            $scope.$on('$stateChangeStart', e => {
+                e.preventDefault();
+            });
+        }
+    }
+    AccountAddDetailsCtrl.$inject = ['$scope', '$log', '$interval', 'basicAuthAccountSerivce', 'errorMessageByCode', 'ECOMP_URL_REGEX', '$window', 'confirmBoxService', '$cookies'];
+    angular.module('ecompApp').controller('AccountAddDetailsCtrl', AccountAddDetailsCtrl);
+})(); 
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/account-onboarding/account-add-details/account-add-details.less b/ecomp-portal-FE-common/client/app/views/account-onboarding/account-add-details/account-add-details.less
new file mode 100644
index 0000000..6a916d6
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/account-onboarding/account-add-details/account-add-details.less
@@ -0,0 +1,195 @@
+.account-add-details-modal{
+	height: 550px;
+	
+	overflow-y: auto;
+
+	.account-details-title {
+	    color: #191919;
+	    font-family: "Omnes-ECOMP-W02", Arial;;
+	    font-size: 24px;
+	    padding-bottom: 15px;
+	    padding-top:30px;
+	    margin-left: 25px;
+	}
+	.account-properties-main {
+	    padding-top: 20px;
+	    padding-left: 20px;
+	    padding-right: 20px;
+	    margin-bottom: 50px;
+	    
+	    height: 100%;
+	    width:100%;
+	    
+	    .account-property{
+	      margin-top: 10px;
+	      position: relative;
+	      .property-label{
+	        .dGray14r;
+	      }
+	      .checkbox-label{
+	        display: inline-block;
+	        padding-left: 3px;
+	      }
+	      .checkbox-field{
+	        padding: 0;
+	        margin: 0;
+	        vertical-align: middle;
+	        position: relative;
+	        top: -1px;
+	      } 
+	    }
+		.add-endpoint-item{
+			position: relative;
+	        .add-label-left{
+	  	  	  line-height: 25px;
+	          height: 30px;
+	          vertical-align: middle;
+	          display:inline-block;
+	          margin-right: 10px;
+			  .dGray14r;
+	  	   }
+	  	   .add-label-right{
+	  	   	  height: 14px;
+	  	   	  width: 14px;
+	          display:inline-block;
+	  	   }
+		}
+		.item{
+	      position: relative;
+	      margin-bottom: 15px;
+	      width: 400px;
+	      .service-select{
+	 		  select{
+	 		  	cursor: pointer;
+			    position: relative;
+			    border: 1px solid #5a5a5a;
+			    white-space: nowrap;
+			    overflow: hidden;
+			    text-overflow: ellipsis;
+			    line-height: 15px;
+			    height: 32px;
+			    padding-left: 10px;
+			    padding-right: 10px;
+		      	border-radius: 0px;
+	 		  }
+	          display:inline-block;
+	          width: 100%;
+    	  }
+    	  .error-container{
+	        position: absolute;
+	        width: 280px;
+	        display: block;
+	        height: 12px;
+	        line-height: 12px;
+	
+	        .err-message{
+	          color: @funcRed;
+	          font-size: 9px;
+	        }
+	        .valid-message{
+	          color: @funcGreen;
+	          font-size: 9px;
+	        }
+	      }
+	      .auth-item-left{
+	          padding-top: 0;
+	          line-height: 30px;
+	          height: 30px;
+	          vertical-align: middle;
+	          display:inline-block;
+	          width: 15%;
+	          //border-radius: 2px;
+	          //border: 1px solid @attLGray;
+	          margin-right: 10px;
+	          //background: @attWhite;
+	          white-space: nowrap;
+			  .dGray14r;
+       	  }
+          .auth-item-right{
+	          display:inline-block;
+	          width: 45%;
+	          border-radius: 2px;
+	          border: 1px solid;
+	          //background: @attWhite;
+	          vertical-align: middle;
+          }
+          
+          .endpoint-item-left{
+	          line-height: 30px;
+	          height: 30px;
+	          vertical-align: middle;
+	          display:inline-block;
+	          width: 80%;
+	          //border-radius: 2px;
+	          //border: 1px solid;
+	          margin-right: 10px;
+	          //background: @attWhite;
+	          white-space: nowrap;
+			  .dGray14r;
+       	  }
+       	  .endpoint-item-middle{
+          	  margin-top: 10px;
+	          height: 14px;
+	          width: 14px;
+	          display:inline-block;
+          }
+          
+          .endpoint-item-right{
+          	  margin-left: 5px; 
+	          width: 15%;
+	          height: 30px;
+	          display:inline-block;
+          }
+          
+          .endpoint-label-item-left{
+	          line-height: 30px;
+	          height: 30px;
+	          vertical-align: middle;
+	          display:inline-block;
+	          width: 80%;
+	          margin-right: 10px;
+	          //background: @attWhite;
+	          white-space: nowrap;
+			  .dGray14r;
+       	  }
+          .endpoint-label-item-right{
+	          line-height: 30px;
+	          height: 30px;
+	          display:inline-block;
+	          width: 20%;
+	          //background: @attWhite;
+	          vertical-align: middle;
+	          white-space: nowrap;
+	          .dGray14r;
+          }
+
+	      
+	       .textarea-field{
+	      	position: relative;
+	      	.custom-input-field;
+	      	height:50px;
+      	    }
+	       .submit-button {
+	       	 margin-top: 25px;
+	  	 	 position: relative;
+	  	 	 float: right;
+	  	 	 width: 20%;
+	   		.btn-blue;
+	  	  }
+	  	  
+	  	 
+	  	  
+	  	  .add-endpoint-button{
+			height: 14px;
+	  	  	width: 14px;
+	  	  	float: right;
+	  	  }
+	  	  .item-label{
+        	.dGray14r;
+      	  }
+	    }
+	}
+}
+#account-scroll-end{
+	height: 20px;
+} 
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/account-onboarding/account-onboarding.controller.js b/ecomp-portal-FE-common/client/app/views/account-onboarding/account-onboarding.controller.js
new file mode 100644
index 0000000..deed9dc
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/account-onboarding/account-onboarding.controller.js
@@ -0,0 +1,72 @@
+'use strict';
+(function () {
+    class AccountOnboardingCtrl {
+        constructor($log, ngDialog, confirmBoxService, basicAuthAccountService, $cookies, $scope) {
+        	
+        	
+            let init = () => {
+            	 $scope.accountList = [];
+            	 getOnboardingAccounts();
+            	 
+            	 this.accoutTableHeaders = [
+                 	{name: 'Account Name', value: 'applicationName', isSortable: true},
+                    {name: 'Username', value: 'username', isSortable: false}
+                 ];
+            };
+            
+            let getOnboardingAccounts = () => {
+            	basicAuthAccountService.getAccountList().then(res => {            		
+                    $scope.accountList = res;
+                }).catch(err => {
+                    $log.error('AccountOnboardingCtrl::getOnboardingAccounts caught error', err);
+                });
+            };
+            
+            this.openAddNewAccountModal = (selectedAccount) => {
+            	let data = null; 
+				if(selectedAccount){
+					data = { 
+						account:selectedAccount,
+						list: $scope.accountList
+					}
+				}else{
+					data = {
+						list: $scope.accountList	
+					}
+				}
+                ngDialog.open({
+                    templateUrl: 'app/views/account-onboarding/account-add-details/account-add-details.html',
+                    controller: 'AccountAddDetailsCtrl',
+                    controllerAs: 'accountAddDetails',
+                    data: data
+                }).closePromise.then(needUpdate => {
+                	if(needUpdate.value === true){
+                		if(needUpdate.value === true){
+                			 getOnboardingAccounts();
+                        }
+                    }
+                });
+            };
+            
+            
+            this.deleteAccount = account => { 
+            	console.log(account);
+     		    confirmBoxService.deleteItem(account.applicationName).then(isConfirmed => {   
+                	if(isConfirmed){
+                		basicAuthAccountService.deleteAccount(account.id).then(() => {
+                        	$scope.accountList.splice($scope.accountList.indexOf(account), 1);
+                        }).catch(err => {
+                            $log.error('AccountOnboardingCtrl::deleteAccount error:',err);
+                        });
+                    }
+                }).catch(err => {
+                    $log.error('AccountOnboardingCtrl::deleteAccount error:',err);
+                });
+             };
+            
+            init();
+        }
+    }
+    AccountOnboardingCtrl.$inject = ['$log', 'ngDialog', 'confirmBoxService', 'basicAuthAccountService', '$cookies', '$scope'];
+    angular.module('ecompApp').controller('AccountOnboardingCtrl', AccountOnboardingCtrl);
+})();
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/account-onboarding/account-onboarding.less b/ecomp-portal-FE-common/client/app/views/account-onboarding/account-onboarding.less
new file mode 100644
index 0000000..8d3663c
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/account-onboarding/account-onboarding.less
@@ -0,0 +1,24 @@
+.account-onboarding{
+  position: @page-main-position;
+  top: @page-main-top;
+  left: @page-main-left;
+  right: @page-main-right;
+  bottom: @page-main-bottom;
+  padding-top: @padding-top;
+  overflow-y: @page-main-overflow-y;
+  padding-left: @padding-left-side;
+
+    .account-table {
+  	  width: @table-width;
+ 	  margin: 0 auto;
+ 	  
+ 	  .add-button{
+ 	  	width: 180px;
+ 	  }
+    }
+
+	.delete-account{
+      .ico_trash_default;
+    }
+
+}
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/account-onboarding/account-onboarding.tpl.html b/ecomp-portal-FE-common/client/app/views/account-onboarding/account-onboarding.tpl.html
new file mode 100644
index 0000000..5f46a1c
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/account-onboarding/account-onboarding.tpl.html
@@ -0,0 +1,76 @@
+<div class="w-ecomp-main">
+	<div class="w-ecomp-main-container">
+		<div class="account-onboarding" id="page-content">
+			<div id="account-onboarding-title" class="w-ecomp-main-view-title">Account
+				Onboarding</div>
+			<div class="account-table">
+				<div class="table-control">
+
+					<div class="table-control-buttons">
+						<button class="btn btn-alt btn-small"
+							id="account-onboarding-button-add"
+							ng-click="accountOnboarding.openAddNewAccountModal()">
+							<i class="icon-people-userbookmark" aria-hidden="true"></i>&nbsp;Add
+							Account
+						</button>
+					</div>
+
+
+					<div>
+						<div class="c-ecomp-b2b-abs-table default">
+							<table b2b-table table-data="serviceList"
+								view-per-page="accountOnboardingviewPerPageIgnored"
+								current-page="accountOnboarding.currentPageIgnored"
+								total-page="accountOnboarding.totalPageIgnored">
+								<thead b2b-table-row type="header">
+									<tr>
+										<th id="account-catalog-th-header-name"
+											ng-repeat="header in accountOnboarding.accoutTableHeaders"
+											b2b-table-header key="{{header.value}}"
+											sortable="{{header.isSortable}}">{{header.name}}</th>
+
+										<th id="account-catalog-th-header-endpoints" b2b-table-header
+											key="endpoints" sortable="false">Endpoints</th>
+
+										<th id="account-catalog-th-header-delete" b2b-table-header
+											sortable="false">Delete</th>
+									</tr>
+								</thead>
+								<tbody b2b-table-row type="body" class="table-body"
+									row-repeat="rowData in accountList">
+
+									<tr>
+										<td b2b-table-body
+											ng-repeat="header in accountOnboarding.accoutTableHeaders"
+											ng-click="accountOnboarding.openAddNewAccountModal(rowData)">
+											<div id="account-catalog-name-{{rowData.id}}"
+												ng-bind="rowData[header.value]"></div>
+										</td>
+
+										<td b2b-table-body
+											ng-click="accountOnboarding.openAddNewAccountModal(rowData)">
+											<div ng-show="rowData.endpoints.length > 0">
+												<div ng-repeat="row in rowData.endpoints">
+													<div id="account-catalog-account-endpoint"
+														ng-bind="row.name"></div>
+												</div>
+											</div>
+											<div ng-hide="rowData.endpoints.length > 0">All
+												endpoints</div>
+										</td>
+
+										<td b2b-table-body>
+											<div id="account-onboarding-div-delete-{{$index}}"
+												class="delete-account"
+												ng-click="accountOnboarding.deleteAccount(rowData)"></div>
+										</td>
+									</tr>
+								</tbody>
+							</table>
+						</div>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</div>
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/admins/add-admin-dialogs/new-admin.controller.js b/ecomp-portal-FE-common/client/app/views/admins/add-admin-dialogs/new-admin.controller.js
new file mode 100644
index 0000000..5a164e1
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/admins/add-admin-dialogs/new-admin.controller.js
@@ -0,0 +1,252 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+(function () {

+    class NewAdminModalCtrl {

+        constructor($log, adminsService, $scope, confirmBoxService, utilsService, $location) {

+

+            let init = () => {

+                this.isSaving = false;

+                this.originalApps = [];

+                /* istanbul ignore if */

+                if ($scope.ngDialogData && $scope.ngDialogData.selectedUser && $scope.ngDialogData.dialogState) {

+                    this.selectedUser = $scope.ngDialogData.selectedUser;

+                    this.dialogState = $scope.ngDialogData.dialogState;

+                    this.isShowBack = false;

+                    if (this.dialogState === 2) {

+                        this.getAdminAppsRoles();

+                    }

+                } else {

+                    this.isShowBack = true;

+                    this.selectedUser = null;

+                    this.dialogState = 1;

+                }

+

+                //this.searchUsersInProgress = false;

+                //this.showNewAdminAppDropdown = false;

+                $log.info('NewAdminModalCtrl::initiated');

+                this.appsOrder = [];

+            };

+

+            let orderList = (apps) => {

+                this.appsOrder = [];

+                for (var i = 0; i < apps.length; i++) {

+                    if (apps[i].isAdmin) {

+                        this.appsOrder.push(apps[i].id);

+                    }

+                }

+            };

+

+            this.orderFilter = app => {

+                if (!app || !app.id || !this.appsOrder.length) {

+                    return;

+                }

+                return this.appsOrder.indexOf(app.id);

+            };

+

+            /**

+             * this function get the selected admin apps roles

+             */

+            this.getAdminAppsRoles = () => {

+                if (!this.selectedUser || !this.selectedUser.orgUserId) {

+                    $log.error('No user is selected / searchUsers is InProgress');

+                    this.dialogState = 1;

+                    return;

+                }

+                adminsService.getAdminAppsRoles(this.selectedUser.orgUserId).then(roles => {

+                    $log.debug('apps roles res: ', JSON.stringify(roles));

+                    if (!roles.appsRoles) {

+                        return;

+                    }

+

+                    this.adminAppsRoles = [];

+                    for (var i = 0; i < roles.appsRoles.length; i++) {

+                        if (!roles.appsRoles[i].restrictedApp) {

+                            $log.debug('pushing: {id: ', roles.appsRoles[i].id,

+                                'name: ', roles.appsRoles[i].appName,

+                                'restrictedApp: ', roles.appsRoles[i].restrictedApp,

+                                'isAdmin: ', roles.appsRoles[i].isAdmin, '}');

+                            this.adminAppsRoles.push({

+                                id: roles.appsRoles[i].id,

+                                appName: roles.appsRoles[i].appName,

+                                isAdmin: roles.appsRoles[i].isAdmin,

+                                restrictedApp: roles.appsRoles[i].restrictedApp

+                            });

+                        }

+                    }

+                    this.dialogState = 2;

+                    this.adminAppsRoles = this.adminAppsRoles.sort(getSortOrder("appName"));

+

+                    orderList(roles.appsRoles);

+                    if (this.appsOrder != null) {

+                        for (var j = 0; j < this.appsOrder.length; j++) {

+                            this.originalApps.push(this.appsOrder[j]);

+                        }

+                    }

+                }).catch(err => {

+                    $log.error(err);

+                });

+            };

+

+            // Refactor this into a directive

+            let getSortOrder = (prop) => {

+                return function (a, b) {

+                    if (a[prop].toLowerCase() > b[prop].toLowerCase()) {

+                        return 1;

+                    } else if (a[prop].toLowerCase() < b[prop].toLowerCase()) {

+                        return -1;

+                    }

+                    return 0;

+                }

+            }

+

+            /**

+             * this function set the selected user

+             * @param user: selected user object

+             */

+            this.setSelectedUser = (user) => {

+                $log.debug('selected user: ', user);

+                this.selectedUser = user;

+            };

+

+            /**

+             * Mark the user as not admin of the selected app

+             * @param app: selected app object

+             */

+            this.unadminApp = (app) => {

+                confirmBoxService.deleteItem(app.appName).then(confirmed => {

+                    if (confirmed === true) {

+                        app.isAdmin = false;

+                        this.appsOrder.splice(this.appsOrder.indexOf(app.id), 1);

+                    }

+                }).catch(err => {

+                    $log(err);

+                });

+            };

+

+            /**

+             * update the selected admin app with the new roles

+             */

+            this.updateAdminAppsRoles = () => {

+                if (!this.selectedUser || !this.selectedUser.orgUserId || !this.adminAppsRoles) {

+                    return;

+                }

+                this.isSaving = true;

+                $log.debug('going to update user: ' + this.selectedUser.orgUserId + ' with app roles: ' + JSON.stringify(this.adminAppsRoles));

+                confirmBoxService.makeAdminChanges('Are you sure you want to make these admin changes?')

+                    .then(confirmed => {

+                    	if(confirmed === true){

+                        adminsService.updateAdminAppsRoles({

+                                orgUserId: this.selectedUser.orgUserId,

+                                appsRoles: this.adminAppsRoles

+                            })

+                            .then(res => {

+                                $log.debug('Admin apps roles updated successfully!', res);

+                                //close and resolve dialog promise with true (to update the table)

+                                this.remindToAddUserIfNecessary();

+                                $scope.closeThisDialog(true);

+                            }).catch(err => {

+                            $log.error('NewAdminModalCtrl.updateAdminAppsRoles:: Failed - ' + err);

+                        }).finally(()=> {

+                            this.isSaving = false;

+                        })

+                    	}else{

+                        	this.isSaving = false;

+                        }

+                    });

+            };

+

+            /**

+             * Navigate between dialog screens using step number: 1,2,...

+             */

+            this.navigateBack = () => {

+                if (this.dialogState === 1) {

+                    //back from 1st screen?

+                }

+                if (this.dialogState === 2) {

+                    this.dialogState = 1;

+                }

+            };

+

+            init();

+

+            /**

+             * each time new app is selected in the drop down,

+             * add it to the user administrated apps list

+             */

+            $scope.$watch('newAdmin.selectedNewApp', (newVal) => {

+                if (!newVal || newVal.isAdmin === undefined) {

+                    return;

+                }

+                //newVal.isAdmin = true; - track by ruined this, here is the workaround:

+                let app = _.find(this.adminAppsRoles, {id: newVal.id});

+                if (app) {

+                    app.isAdmin = true;

+                    this.appsOrder.push(app.id);

+                }

+                this.selectedNewApp = null;

+                //this.showNewAdminAppDropdown = false;

+            });

+

+            $scope.$on('$stateChangeStart', e => {

+                //Disable navigation when modal is opened

+                //**Nabil - note: this will cause the history back state to be replaced with current state

+                e.preventDefault();

+            });

+

+            /**

+             * If an Admin was added for an application remind the portal admin to add the admin as a user

+             */

+            this.remindToAddUserIfNecessary = () => {

+

+                var adminAddedToNewApp = false;

+                if ((this.originalApps != null) && (this.originalApps.length > 0)) {

+                    for (var i = 0; i < this.appsOrder.length; i++) {

+                        var foundApp = false;

+                        for (var j = 0; j < this.originalApps.length; j++) {

+                            if (this.originalApps[j] === this.appsOrder[i]) {

+                                foundApp = true;

+                            }

+                        }

+                        if (foundApp === false) {

+                            adminAddedToNewApp = true;

+                            break;

+                        }

+                    }

+                } else {

+                    adminAddedToNewApp = true;

+                }

+

+                if (adminAddedToNewApp === true) {

+                    confirmBoxService.confirm('Add this person as an application user? This allows them to access the application from ECOMP Portal. Press OK to go to the Add Users page.')

+                        .then(confirmed => {

+                            if (confirmed === true) {

+                                $location.path('/users');

+                            }

+                        });

+                }

+            }

+

+        }

+    }

+    NewAdminModalCtrl.$inject = ['$log', 'adminsService', '$scope', 'confirmBoxService', 'utilsService', '$location'];

+    angular.module('ecompApp').controller('NewAdminModalCtrl', NewAdminModalCtrl);

+})();

diff --git a/ecomp-portal-FE-common/client/app/views/admins/add-admin-dialogs/new-admin.controller.spec.js b/ecomp-portal-FE-common/client/app/views/admins/add-admin-dialogs/new-admin.controller.spec.js
new file mode 100644
index 0000000..540459e
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/admins/add-admin-dialogs/new-admin.controller.spec.js
@@ -0,0 +1,134 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+/**

+ * Created by nnaffar on 12/8/15.

+ */

+'use strict';

+

+describe('Controller: NewAdminCtrl ', () => {

+    beforeEach(module('ecompApp'));

+

+    //destroy $http default cache before starting to prevent the error 'default cache already exists'

+    beforeEach(inject((_CacheFactory_)=> {

+        _CacheFactory_.destroyAll();

+    }));

+

+

+    let newCtrl, $controller, $q, $rootScope, $log;

+

+    beforeEach(inject((_$controller_, _$q_, _$rootScope_, _$log_)=> {

+        [$controller, $q, $rootScope, $log] = [_$controller_, _$q_, _$rootScope_, _$log_];

+    }));

+

+    let deferredAdminAppsRoles, deferredUpdateRolesRes;

+    let adminsServiceMock;

+    beforeEach(()=> {

+        [deferredAdminAppsRoles, deferredUpdateRolesRes] = [$q.defer(), $q.defer()];

+

+        adminsServiceMock = jasmine.createSpyObj('adminsServiceMock', ['getAdminAppsRoles', 'updateAdminAppsRoles']);

+

+        adminsServiceMock.getAdminAppsRoles.and.returnValue(deferredAdminAppsRoles.promise);

+        adminsServiceMock.updateAdminAppsRoles.and.returnValue(deferredUpdateRolesRes.promise);

+

+        newCtrl = $controller('NewAdminModalCtrl', {

+            $log: $log,

+            adminsService: adminsServiceMock,

+            $scope: $rootScope

+        });

+    });

+

+    it('should init default values when loading the controller', ()=> {

+        expect(newCtrl.dialogState).toBe(1);

+        expect(newCtrl.selectedUser).toBe(null);

+    });

+

+    it('should populate admin apps roles and move to the next screen when adminsService.getAdminAppsRoles succeeded', ()=> {

+        let userApps = {appsRoles: [{id: 1, isAdmin: false}, {id: 2, isAdmin: true}]};

+        deferredAdminAppsRoles.resolve(userApps);

+

+        newCtrl.selectedUser = {orgUserId: 'orgUserId'};

+

+        newCtrl.getAdminAppsRoles();

+        $rootScope.$apply();

+

+        expect(adminsServiceMock.getAdminAppsRoles).toHaveBeenCalledWith(newCtrl.selectedUser.orgUserId);

+        expect(newCtrl.adminAppsRoles).toEqual(userApps.appsRoles);

+        expect(newCtrl.dialogState).toBe(2);

+    });

+

+    it('should  log the error when adminsService.getAdminAppsRoles fails', ()=> {

+        spyOn($log, 'error');

+        deferredAdminAppsRoles.reject('some error');

+

+        newCtrl.searchUsersInProgress = false;

+        newCtrl.selectedUser = {orgUserId: 'orgUserId'};

+

+        newCtrl.getAdminAppsRoles();

+        $rootScope.$apply();

+

+        expect($log.error).toHaveBeenCalled();

+    });

+    it('should  log the error when trying to getAdminAppsRoles without selecting user ', ()=> {

+        spyOn($log, 'error');

+

+        newCtrl.searchUsersInProgress = false;

+        newCtrl.selectedUser = null;

+

+        newCtrl.getAdminAppsRoles();

+        $rootScope.$apply();

+

+        expect($log.error).toHaveBeenCalled();

+    });

+

+    it('should set isAdmin as true when adding app via the dropdown menu', ()=> {

+        newCtrl.adminAppsRoles = [{id: 1, isAdmin: false},{id: 2, isAdmin: true}];

+        //simulate UI change

+        $rootScope.$apply('newAdmin.selectedNewApp = null');

+        $rootScope.$apply('newAdmin.selectedNewApp = {id: 1, isAdmin: true}');

+

+        expect(newCtrl.adminAppsRoles[0].isAdmin).toBe(true);

+        expect(newCtrl.selectedNewApp).toBe(null);

+    });

+

+    it('should close the modal when updating apps roles succeeded', ()=> {

+        $rootScope.closeThisDialog = () => {};

+        spyOn($rootScope,'closeThisDialog');

+

+        newCtrl.selectedUser = {orgUserId: 'orgUserId'};

+        newCtrl.adminAppsRoles = [{id: 1}];

+

+        deferredUpdateRolesRes.resolve();

+        newCtrl.updateAdminAppsRoles();

+        $rootScope.$apply();

+

+        expect(adminsServiceMock.updateAdminAppsRoles).toHaveBeenCalledWith({orgUserId: newCtrl.selectedUser.orgUserId, appsRoles: newCtrl.adminAppsRoles});

+        expect($rootScope.closeThisDialog).toHaveBeenCalled();

+    });

+    it('should log the error when updating apps roles fails', ()=> {

+        newCtrl.selectedUser = {orgUserId: 'orgUserId'};

+        newCtrl.adminAppsRoles = [{id: 1}];

+

+        spyOn($log,'error');

+        deferredUpdateRolesRes.reject();

+        newCtrl.updateAdminAppsRoles();

+        $rootScope.$apply();

+        expect($log.error).toHaveBeenCalled();

+    });

+});

diff --git a/ecomp-portal-FE-common/client/app/views/admins/add-admin-dialogs/new-admin.modal.html b/ecomp-portal-FE-common/client/app/views/admins/add-admin-dialogs/new-admin.modal.html
new file mode 100644
index 0000000..1b1b9de
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/admins/add-admin-dialogs/new-admin.modal.html
@@ -0,0 +1,77 @@
+<!--
+  ================================================================================
+  eCOMP Portal
+  ================================================================================
+  Copyright (C) 2017 AT&T Intellectual Property
+  ================================================================================
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+  
+       http://www.apache.org/licenses/LICENSE-2.0
+  
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ================================================================================
+  -->
+<div class="new-admin-modal">
+
+    <div class="search-users" ng-show="newAdmin.dialogState===1">
+
+        <search-users search-title="New Admin"
+                      selected-user="newAdmin.selectedUser"></search-users>
+
+        <div class="dialog-control">
+              <button id="search-users-button-next" class="btn btn-alt btn-small" ng-click="newAdmin.selectedUser && newAdmin.getAdminAppsRoles()"
+                 ng-class="{disabled: !newAdmin.selectedUser}">Next
+            </button>
+            <button id="search-users-button-cancel" class="btn btn-alt btn-small" ng-click="closeThisDialog()">Cancel</button>
+        </div>
+    </div>
+
+
+    <div id="div-admin-app-roles" class="admin-app-roles" ng-show="newAdmin.dialogState===2">
+        <div class="title" id="title"
+             ng-bind="newAdmin.selectedUser.firstName + ' ' + newAdmin.selectedUser.lastName + ' (' + newAdmin.selectedUser.orgUserId + ')'"></div>
+        <div class="app-roles-main">
+            <div id="div-app-roles-main-title" class="app-roles-main-title">
+                <span class="left">Administrates:</span>
+                <!--<span class="right" ng-click="newAdmin.addAdministratedApp()">+Add</span>-->
+            </div>
+
+
+            <div class="select-input custom-select-wrap">
+                <select class="new-administrated-app" id="dropdown-select-app"
+                        ui-select2 ng-model="newAdmin.selectedNewApp"
+                        data-placeholder="Select application"
+                        ng-options="app as app.appName for app in (filteredApps = (newAdmin.adminAppsRoles | filter:{isAdmin:'false'})) track by app.id "
+                        ng-disabled="!filteredApps.length">
+                    <option id="option-select-app" value="" disabled style="display: none;">Select application</option>
+                </select>
+            </div>
+
+
+            <div class="admin-roles-list">
+                <div ng-repeat="app in (newAdmin.adminAppsRoles | orderBy:newAdmin.orderFilter) track by app.id" ng-show="app.isAdmin">
+                    <div id="select-app-{{app.appName.split(' ').join('-')}}" class="administrated-application" ng-bind="app.appName | elipsis: 57"></div>
+                    <i id="i-delete-application" class="icon-misc-trash" ng-click="newAdmin.unadminApp(app)"> </i>
+                </div>
+            </div>
+
+            <div class="dialog-control">
+                <span class="ecomp-save-spinner" ng-show="newAdmin.isSaving"></span>              
+                 <button id="button-back" ng-show="newAdmin.isShowBack" class="btn btn-alt btn-small" ng-click="newAdmin.navigateBack()">Back</button>
+                <button id="div-updateAdminAppsRoles" class="btn btn-alt btn-small" ng-click="newAdmin.updateAdminAppsRoles()"
+                     ng-class="{disabled: false}">Save
+                </button>
+                <button id="div-cancel-button" class="btn btn-alt btn-small" ng-click="closeThisDialog()">Cancel</button>
+            </div>
+
+        </div>
+
+    </div>
+
+</div>
diff --git a/ecomp-portal-FE-common/client/app/views/admins/add-admin-dialogs/new-admin.modal.less b/ecomp-portal-FE-common/client/app/views/admins/add-admin-dialogs/new-admin.modal.less
new file mode 100644
index 0000000..8b304b4
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/admins/add-admin-dialogs/new-admin.modal.less
@@ -0,0 +1,81 @@
+.new-admin-modal {
+  height: 430px;
+  margin-left: 20px;
+
+  .search-users {
+  }
+
+  .admin-app-roles {
+    .title {
+      //.n18r;
+      .dGray18r;  //AT&T Dark Gray
+      border-bottom: @blue-active 3px solid;
+
+    }
+
+    .app-roles-main {
+      margin-top: 16px;
+      .app-roles-main-title {
+        .dGray14r;
+        margin-bottom: 8px;
+        .left {
+          display: inline-block;
+        }
+        .right {
+          display: inline-block;
+          color: @blue-active;
+          float: right;
+          cursor: pointer;
+        }
+      }
+
+      .select-input{
+        width: 460px;
+      }
+
+      .new-administrated-app {
+        height: 30px;
+        line-height: 30px;
+
+        border: 1px solid @portalGray;
+        margin-bottom: 8px;
+        border-radius: 2px;
+        padding-left: 6px;
+        padding-top: 0;
+        width: 100%;
+        .dGray14r;
+      }
+
+      .admin-roles-list {
+        height: 240px;
+        overflow-y: auto;
+      }
+
+      .administrated-application {
+        width: 460px;
+        height: 30px;
+        border: 1px solid @portalGray;
+        margin-bottom: 8px;
+        border-radius: 2px;
+        padding: 6px;
+        .dGray14r;
+        display: inline-block;
+
+      }
+
+      .delete-application {
+        .ico_trash_default;
+        display: inline-block;
+        vertical-align: 4px;
+        cursor: pointer;
+        position: relative;
+        top: 6px;
+        color: transparent;
+        margin-left: 8px;
+      }
+
+    }
+
+  }
+}
+
diff --git a/ecomp-portal-FE-common/client/app/views/admins/admins.controller.js b/ecomp-portal-FE-common/client/app/views/admins/admins.controller.js
new file mode 100644
index 0000000..05f9db3
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/admins/admins.controller.js
@@ -0,0 +1,161 @@
+/*-
+ * ================================================================================
+ * eCOMP Portal
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property
+ * ================================================================================
+ * Licensed 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';
+(function () {
+    class AdminsCtrl {
+        constructor($log, adminsService, applicationsService, ngDialog) {
+
+            let allPortalsFilterObject = {index: 0, title: 'All applications', value: ''};
+
+            let updateTableData = () => {
+                this.isLoadingTable = true;
+                adminsService.getAccountAdmins().then(res=> {
+                    if (!res || !res.length) {
+                        $log.error('AdminsCtrl::updateTableData: no admins err handling');
+                        this.adminsTableData = [];
+                        return;
+                    }
+                    this.adminsTableData = res;
+                }).catch(err=> {
+                    $log.error('AdminsCtrl::updateTableData error: ', err);
+                }).finally(() => {
+                    this.isLoadingTable = false;
+                });
+            };
+
+            let init = () => {
+                //$log.info('AdminsCtrl:: ::initializing...');
+                this.isLoadingTable = false;
+                this.availableApps = [allPortalsFilterObject];
+                this.filterByApp = this.availableApps[0];
+
+                /*Table general configuration params*/
+                this.searchString= '';
+                /*Table data*/
+                this.adminsTableHeaders = ['First Name', 'Last Name', 'User ID', 'Applications'];
+                this.adminsTableData = [];
+                updateTableData();
+            };
+
+            applicationsService.getAvailableApps().then(res=> {
+                //if(!res || Object.prototype.toString.call(res) !== '[object Array]'){
+                //    this.availableApps = [allPortalsFilterObject];
+                //    return;
+                //}
+                //this part overrides index param to fix ABS select bug
+                // (index has to be the same as location in array)
+                // todo:change BE object to contain only id and name
+                this.availableApps = [allPortalsFilterObject];
+                var res1 = res.sort(getSortOrder("title"));
+                var realAppIndex = 1;
+                for(let i=1; i<=res1.length; i++){
+                    if (!res1[i-1].restrictedApp) {
+                        //$log.debug('AdminsCtrl:getAvailableApps:: pushing: {index: ', realAppIndex, 'title: ', res1[i - 1].title,
+                         //   '| value: ', res1[i -1].value, '}');
+                        this.availableApps.push({
+                            index: realAppIndex,
+                            title: res1[i - 1].title,
+                            value: res1[i - 1].value
+                        });
+                        realAppIndex = realAppIndex + 1;
+                    } else {
+                        // $log.debug('AdminsCtrl:getAvailableApps:: Restricted/URL only App will not be used = ' + res1[i - 1].title);
+                    }
+                }
+            }).catch(err=> {
+                $log.error('AdminsCtrl::ctor', err);
+                this.availableApps = [allPortalsFilterObject];
+            });
+
+            // Refactor this into a directive
+            let getSortOrder = (prop) => {
+                return function(a, b) {
+                    // $log.debug('a = ' + JSON.stringify(a) + "| b = " + JSON.stringify(b));
+                    if (a[prop].toLowerCase() > b[prop].toLowerCase()) {
+                        return 1;
+                    } else if (a[prop].toLowerCase() < b[prop].toLowerCase()) {
+                        return -1;
+                    }
+                    return 0;
+                }
+            }
+
+            init();
+
+            //Filter function
+            this.portalsRowFilter = (input) => {
+                if (this.filterByApp.value === '') {
+                    return true;
+                }
+                return _.find(input.apps, {appName: this.filterByApp.value}) !== undefined;
+            };
+
+            this.openAddNewAdminModal = (user) => {
+                let data = null;
+                if(user){
+                    data = {
+                        dialogState: 2,
+                        selectedUser:{
+                            orgUserId: user.orgUserId,
+                            firstName: user.firstName,
+                            lastName: user.lastName
+                        }
+                    }
+                }
+                ngDialog.open({
+                    templateUrl: 'app/views/admins/add-admin-dialogs/new-admin.modal.html',
+                    controller: 'NewAdminModalCtrl',
+                    controllerAs: 'newAdmin',
+                    data: data
+                }).closePromise.then(needUpdate => {
+                    if(needUpdate.value === true){
+                        // $log.debug('AdminsCtrl:openAddNewAdminModal:: updating table data...');
+                        updateTableData();
+                    }
+                });
+            };
+            
+            this.openEditUserModal = (loginId) => {
+            	var data = {
+            			loginId : loginId,
+            	        updateRemoteApp : false,
+            	        appId : this.selectedApp!=null?this.selectedApp.id:''
+            	}
+            	var modalInstance = ngDialog.open({
+                    templateUrl: 'app/views/header/user-edit/edit-user.tpl.html',
+                    controller: 'editUserController',
+                    data: data,
+                    resolve: {
+                        message: function message() {
+                            var message = {
+                                type: 'Contact',
+                            };
+                            return message;
+                        }
+                    }
+                }).closePromise.then(needUpdate => {	              	 
+                	updateTableData();
+	            });       
+            }
+        }
+    }
+    AdminsCtrl.$inject = ['$log', 'adminsService', 'applicationsService', 'ngDialog'];
+    angular.module('ecompApp').controller('AdminsCtrl', AdminsCtrl);
+})();
diff --git a/ecomp-portal-FE-common/client/app/views/admins/admins.controller.spec.js b/ecomp-portal-FE-common/client/app/views/admins/admins.controller.spec.js
new file mode 100644
index 0000000..3841a2b
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/admins/admins.controller.spec.js
@@ -0,0 +1,19 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

diff --git a/ecomp-portal-FE-common/client/app/views/admins/admins.less b/ecomp-portal-FE-common/client/app/views/admins/admins.less
new file mode 100644
index 0000000..729ef69
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/admins/admins.less
@@ -0,0 +1,45 @@
+.w-ecomp-admins-page-main {
+    .bg_portalWhite;//white for 1702
+      position: @page-main-position;
+    top: @page-main-top;
+    left: @page-main-left;
+    right: @page-main-right;
+    bottom: @page-main-bottom;
+    padding-top: @padding-top;
+    overflow-y: @page-main-overflow-y;
+    padding-left: @padding-left-side;
+#input-table-search::-webkit-input-placeholder,
+{
+font-style: italic;
+  color:   #D7D7D7;
+
+}
+	.ecomp-spinner{
+	top: 255px;
+	}
+
+    .admins-home-container {
+        position: relative;
+        padding-right: 0;
+        padding-left: 0;
+        padding-bottom: @container-bottom;
+
+        .admins-table {
+            width: @table-width;
+            //margin-left: @table-margin-left;
+            margin: 0 auto;
+
+            .table-control {
+                .table-dropdown-filter {
+                    width: @table-dropdown-filter-width;
+                    display: @table-dropdown-filter-display;
+                }
+            }
+
+            .table-body {
+                cursor: pointer;
+            }
+        }
+    }
+}
+
diff --git a/ecomp-portal-FE-common/client/app/views/admins/admins.tpl.html b/ecomp-portal-FE-common/client/app/views/admins/admins.tpl.html
new file mode 100644
index 0000000..907bf32
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/admins/admins.tpl.html
@@ -0,0 +1,66 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="w-ecomp-admins-page-main">

+    <div class="admins-home-container" id="page-content">

+            <div id="title" class="w-ecomp-main-view-title">

+	 			<h1 class="heading-page" >Admins</h1>

+			</div>

+            <div class="admins-table">

+                <div class="table-control">

+                	 <div class="table-control-fields">		

+						<div class="table-dropdown">

+							<select id="dropdown1" name="dropdown1" b2b-dropdown placeholder-text="All application" ng-model="admins.filterByApp.value">

+				                <option b2b-dropdown-list option-repeat="d in admins.availableApps" value="{{d.value}}">{{d.title}}</option>

+				            </select>

+						</div>

+						<div >

+							<input id="input-table-search" placeholder="Search in entire table" class="table-search-field" type="text" data-ng-model="admins.searchString">

+						</div>

+						<button class="btn btn-alt btn-small" ng-click="admins.openAddNewAdminModal()"><i class="icon-people-userbookmark" aria-hidden="true"></i>&nbsp;Add Admin</button> 

+					</div>                  

+            	</div>

+                <span class="ecomp-spinner" ng-show="admins.isLoadingTable"></span>

+                <div b2b-table table-data="admins.adminsTableData"  ng-hide="admins.isLoadingTable"	search-string="admins.searchString" class="b2b-table-div">

+					<table>

+						<thead b2b-table-row type="header">

+							<tr >

+								<th b2b-table-header key="firstName" sortable="true" id="col1">First Name</th>

+								<th b2b-table-header key="lastName" sortable="true" id="col2">Last Name</th>

+								<th b2b-table-header key="orgUserId" sortable="true" id="col3">User ID</th>

+								<th b2b-table-header key="" sortable="falses" id="col4">Applications</th>

+							</tr>

+						</thead>

+						<tbody b2b-table-row type="body" 	row-repeat="rowData in admins.adminsTableData | filter: admins.portalsRowFilter">

+							<tr ng-click="admins.openAddNewAdminModal(rowData)">

+								<td b2b-table-body id="rowheader_t1_{{$index}}" headers="col1" ng-bind="rowData.firstName"></td>

+								<td b2b-table-body headers="rowheader_t1_{{$index}} col2" ng-bind="rowData.lastName"></td>

+								<td b2b-table-body headers="rowheader_t1_{{$index}} col3" ng-bind="rowData.orgUserId"></td>

+								<td b2b-table-body headers="rowheader_t1_{{$index}} col4">

+									<div class="ecomp-table-repeat" ng-repeat="app in rowData.apps" ng-bind="app.appName"></div>

+								</td>							

+							</tr>

+						</tbody>

+					</table>

+				</div>

+            </div>

+        </div>

+    </div>

+

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/applications/application-details-dialog/application-details.modal.less b/ecomp-portal-FE-common/client/app/views/applications/application-details-dialog/application-details.modal.less
new file mode 100644
index 0000000..1c8ae29
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/applications/application-details-dialog/application-details.modal.less
@@ -0,0 +1,106 @@
+.application-details-modal {
+  height: 700px;
+
+  .title {
+    //.n18r;
+    .dGray18r;  //AT&T Dark Gray
+    border-bottom: @blue-active 3px solid;
+
+  }
+  .app-properties-main{
+    padding-left: 16px;
+    padding-top: 16px;
+    padding-bottom: 16px;
+    height: 630px;
+    overflow-y: auto;
+
+
+    .left-container{
+      display: inline-block;
+      width: 48%;
+
+    }
+    .right-container{
+      display: inline-block;
+      width: 48%;
+      float: right;
+	  margin-right:10px;
+
+    }
+
+    .property{
+      position: relative;
+      margin-bottom: 18px;
+      .property-label{
+        .dGray14r;
+      }
+      .checkbox-label{
+        display: inline-block;
+        padding-left: 3px;
+      }
+      .checkbox-field{
+        padding: 0;
+        margin: 0;
+        vertical-align: middle;
+        position: relative;
+        top: -1px;
+      }
+      .preview{
+        width: 220px;
+        margin-top: 22px;
+        display: block;
+
+        .left-label{
+          display:inline-block;
+          float: left;
+        }
+        .remove{
+          cursor: pointer;
+          display: inline-block;
+          float: right;
+          .blue14r;
+        }
+      }
+
+      .input-field{
+        .custom-input-field;
+        width: 220px;
+      }
+
+      .input-file-field{
+        width: 220px;
+        border: 0px solid #d2d2d2;
+		box-shadow: 0px 0px 2px -2px rgba(0, 0, 0, 0.08) inset;
+		padding-left: 2px;
+      }
+      .select-field {
+        .custom-select-field;
+      }
+
+      .image-preview{
+        background: @funcBkgGray;
+        background-size: cover;
+        width: 220px;
+        height: 184px;
+        margin-top: 10px;
+        border: 2px solid #e8e8e8;
+        border-radius: 4px;
+      }
+
+      .error-container{
+        position: absolute;
+        width: 220px;
+        display: block;
+        height: 12px;
+        line-height: 12px;
+
+        .err-message{
+          color: @funcRed;
+          font-size: 10px;
+        }
+      }
+    }
+
+  }
+
+}
diff --git a/ecomp-portal-FE-common/client/app/views/applications/applications.less b/ecomp-portal-FE-common/client/app/views/applications/applications.less
new file mode 100644
index 0000000..60b9d81
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/applications/applications.less
@@ -0,0 +1,31 @@
+.applications-page-main{
+ .bg_portalWhite;//white for 1702
+    position: @page-main-position;
+    top: @page-main-top;
+    left: @page-main-left;
+    right: @page-main-right;
+    bottom: @page-main-bottom;
+    padding-top: @padding-top;
+    overflow-y: @page-main-overflow-y;
+    padding-left: @padding-left-side;
+	#input-table-search::-webkit-input-placeholder,
+	{
+	font-style: italic;
+	  color:    #D7D7D7;
+	}
+    .apps-table {
+        width: @table-width;
+        //margin-left: @table-margin-left;
+        //margin: @table-margin;
+		margin:auto;
+        .delete-app{
+            .ico_trash_default;
+        }
+        .small-thumbnail{
+            width: 72px;
+            height: 60px;
+            border: 1px solid @portalLGray;
+            border-radius: 2px;
+        }
+    }
+}
diff --git a/ecomp-portal-FE-common/client/app/views/catalog/add-catalog-dialogs/new-catalog.modal.less b/ecomp-portal-FE-common/client/app/views/catalog/add-catalog-dialogs/new-catalog.modal.less
new file mode 100644
index 0000000..b98289e
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/catalog/add-catalog-dialogs/new-catalog.modal.less
@@ -0,0 +1,125 @@
+.new-catalog-modal {

+  height: 430px;

+

+  .user-catalog-roles{

+    .title{

+      //.n18r;

+      .dGray18r;  //AT&T Dark Gray

+      border-bottom: @portalDBlue 3px solid;

+    }

+    

+       input:not([type="button"]) {

+    height: 13px;

+} 

+    .display-userApp-Catalog-Roles

+    {

+    padding-left: 16px;

+    padding-top: 10px;

+    font-family: Omnes-ECOMP-W02,Arial;

+    font-size: 18px;

+    color: #5a5a5a;

+    }

+	

+	#pending-checkbox{	

+	font-family: Omnes-ECOMP-W02,Arial;

+	font-size: 15px; 

+	color: #5a5a5a;

+	}

+    .app-catalog-roles-list{

+      height: 286px;

+      

+      

+      .app-catalog-item{

+        border: 1px solid @portalLGray;

+        border-radius: 2px;

+        background-color: @funcBkgGray;

+

+        padding: 10px;

+        margin-top: 8px;

+        //margin-right: 6px;

+        //margin-left: 6px;

+

+        .app-catalog-item-left{

+          padding-top: 0;

+          line-height: 30px;

+          height: 30px;

+          vertical-align: middle;

+          display:inline-block;

+          width: 45%;

+          border-radius: 2px;

+          border: 1px solid @portalLGray;

+          margin-right: 10px;

+          padding-left: 4px;

+          background: @portalWhite;

+          white-space: nowrap;

+

+        }

+        .app-catalog-item-right{

+          display:inline-block;

+          width: 45%;

+          border-radius: 2px;

+          border: 1px solid @portalLGray;

+          background: @portalWhite;

+          vertical-align: middle;

+        }

+

+        .app-catalog-item-right-error{

+          .portalRed;

+          padding: 7px 7px 7px 7px;

+          display:inline-block;

+          width: 45%;

+          border-radius: 2px;

+          border: 1px solid @portalLGray;

+          background: @portalWhite;

+          vertical-align: middle;

+        }

+

+        .app-catalog-item-right-contacting{

+          .portalGreen;

+          padding: 7px 7px 7px 7px;

+          display:inline-block;

+          width: 45%;

+          border-radius: 2px;

+          border: 1px solid @portalLGray;

+          background: @portalWhite;

+          vertical-align: middle;

+        }

+

+        .app-select-left{

+          width: 45%;

+          margin-right: 10px;

+          vertical-align: middle;

+

+

+          .select-field{

+            padding-top: 0;

+            line-height: 30px;

+            height: 30px;

+            vertical-align: middle;

+            border-radius: 2px;

+            border: 1px solid @portalLGray;

+            margin-right: 10px;

+            padding-left: 4px;

+            background: @portalWhite;

+            display:inline-block;

+          }

+        }

+

+

+        .app-item-delete{

+          .ico_trash_default;

+          display: inline-block;

+          vertical-align: 2px;

+          cursor: pointer;

+          position: relative;

+          top: 6px;

+          color: transparent;

+          margin-left: 8px;

+

+        }

+

+      }

+    }

+

+  }

+}

diff --git a/ecomp-portal-FE-common/client/app/views/catalog/catalog.controller.js b/ecomp-portal-FE-common/client/app/views/catalog/catalog.controller.js
new file mode 100644
index 0000000..ad34b08
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/catalog/catalog.controller.js
@@ -0,0 +1,238 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+

+function _classCallCheck(instance, Constructor) {

+	if (!(instance instanceof Constructor)) {

+		throw new TypeError('Cannot call a class as a function');

+	}

+}

+

+(function() {

+	var HTTP_PROTOCOL_RGX = /https?:\/\//;

+

+	var CatalogCtrl = function CatalogCtrl(conf, catalogService, confirmBoxService, ExternalRequestAccessService,

+			$log, $window, userProfileService, applicationsService, $scope, $state, 

+			$timeout, $interval, $modal, ngDialog) {

+

+		this.conf = conf;	

+		var _this = this;

+		_classCallCheck(this, CatalogCtrl);

+

+		// activate spinner

+		this.isLoading = true;

+		$scope.getAppCatalogIsDone = false;

+		$scope.radioValue = 'All';

+		$scope.$watch('radioValue', function(newValue, oldValue) {

+			var appCatalog = $scope.appCatalog;

+			$scope.appCatalog = [];

+			$scope.appCatalog = appCatalog;

+

+			

+		});

+		

+		this.catalogService = catalogService;

+		this.userProfileService = userProfileService;

+		this.applicationsService = applicationsService;

+		var resultAccessValue = null;

+		$scope.demoNum = 1;

+		$scope.appRoles = [];

+		

+        let init = () => {

+            getExternalAccess();

+        };

+        

+        var getExternalAccess = () => {

+    		ExternalRequestAccessService.getExternalRequestAccessServiceInfo().then(

+    				function(property) {

+    		resultAccessValue = property.accessValue;

+    					// $log.info(res);

+    				}).catch(err => {

+                        $log.error('CatalogCtrl: failed getExternalRequestAccessServiceInfo: ' + JSON.stringify(err));

+                    });    

+        };

+        

+

+	

+		$scope.applyPresentationDetailsToAppsCatalog = function(res, value) {

+			

+			_this.apps = res;

+			var rowNo = 0;

+

+			// defend against error string result -

+			// a huge list that should never happen.

+			var maxItems = 333;

+			if (_this.apps.length < maxItems)

+				maxItems = _this.apps.length;

+			for (var i = 0; i < maxItems; i++) {												

+				let imgLnk = '';

+				if (_this.apps[i].imageUrl)

+					imgLnk = conf.api.appThumbnail.replace(':appId', _this.apps[i].id);

+				//$log.debug('CatalogCtlr::applyPresn: imgLink = ' + imgLnk);

+				$scope.appCatalog[i] = {

+					sizeX : 2,

+					sizeY : 2,

+					id : _this.apps[i].id,

+					headerText : _this.apps[i].name,

+					imageLink : imgLnk,

+					restricted : _this.apps[i].restricted,  

+					select : _this.apps[i].select,

+					access : _this.apps[i].access,

+					pending: _this.apps[i].pending,

+					mlproperty: value

+				};

+			}

+			//$log.debug('CatalogCtrl: getAppCatalog count : '

+			//				+ $scope.appCatalog.length);

+			_this.isLoading = false;

+			$scope.getAppCatalogIsDone = true;

+		}

+		

+        let getAppsCatalog = () => {

+            catalogService.getAppCatalog()

+                .then(appsList => {

+                	$scope.applyPresentationDetailsToAppsCatalog(appsList);

+                }).catch(err => {

+                    confirmBoxService.showInformation('There was a problem retrieving the Applications. ' +

+                        'Please try again later. Error Status: '+ err.status).then(isConfirmed => {});

+                    $log.error('CatalogCtrl:openAddRoleModal: Error: ', err);

+                });

+        };

+        

+        this.openAddRoleModal = (item) => { 

+        	let data = null;

+

+            if((!item.restricted) && (item.mlproperty)){

+                data = {

+                    dialogState: 2,

+                    selectedUser:{

+                        attuid: $scope.attuid,

+                        firstName: $scope.firstName,

+                        lastName: $scope.lastName,

+                        headerText: item.headerText,

+                    	item: item

+                        

+                    }

+                }

+            ngDialog.open({

+                templateUrl: 'app/views/catalog/add-catalog-dialogs/new-catalog.modal.html',

+                controller: 'NewCatalogModalCtrl',

+                controllerAs: 'userInfo',

+                data: data

+            }).closePromise.then(needUpdate => {

+                if(needUpdate.value === true){

+                	getAppsCatalog();

+                }

+            	

+            });

+          }

+        };

+

+		// Run this function when user clicks on checkbox.

+		this.storeSelection = function(item) {

+			// $log.debug('CatalogCtrl:storeSelection: item.id is ' + item.id + ', select is ' + item.select);

+			var pendingFlag = false;

+			

+			if(item.access) 

+				pendingFlag = false;

+			else

+				pendingFlag =  item.pending;

+							

+			var appData = { 

+					appId   : item.id,

+					select  : item.select,

+					pending : pendingFlag	// TODO

+			};

+			

+			catalogService.updateManualAppSort(appData).then(

+					function(result) {

+						// $log.debug('CatalogCtrl:storeSelection result is ', result);

+					})['catch'](function(err) {

+						$log.error('CatalogCtrl:storeSelection: exception: ', err);

+					});

+			catalogService.updateAppCatalog(appData).then(

+				function(result) {

+					// $log.debug('CatalogCtrl:storeSelection result is ', result);

+				})['catch'](function(err) {

+					$log.error('CatalogCtrl:storeSelection: exception: ', err);

+				});

+		};

+		

+		userProfileService

+				.getUserProfile()

+				.then(

+						function(profile) {

+							$scope.attuid = profile.orgUserId;

+							$scope.firstName = profile.firstName;

+							$scope.lastName = profile.lastName;

+							$scope.appCatalog = [];

+							

+							catalogService.getAppCatalog().then(

+											function(res) {	

+									$scope.applyPresentationDetailsToAppsCatalog(res, resultAccessValue);

+											})['catch'](function(err) {

+									$log.error('CatalogCtrl: failed getAppCatalog: ', JSON.stringify(err));

+									_this.isLoading = false;

+									$scope.getAppCatalogIsDone = true;

+						  });

+						});

+

+							// applicationsService.getUserApps()

+

+

+		this.gridsterOpts = {

+			columns : 12,

+			colWidth : 95,

+			rowHeight : 95,

+			margins : [ 20, 20 ],

+			outerMargin : true,

+			pushing : true,

+			floating : true,

+			swapping : true,

+		};

+

+		if (getParameterByName('noUserError') != null) {

+			if (getParameterByName('noUserError') == "Show") {

+				$("#errorInfo").show();

+			}

+

+		}

+		

+		init();

+	};

+

+	CatalogCtrl.$inject = [ 'conf', 'catalogService', 'confirmBoxService', 'ExternalRequestAccessService', '$log',

+			'$window', 'userProfileService', 'applicationsService', '$scope', 

+			'$state', '$timeout', '$interval', '$modal', 'ngDialog' ];

+	angular.module('ecompApp').controller('CatalogCtrl', CatalogCtrl);

+})();

+

+function getParameterByName(name, url) {

+	if (!url)

+		url = window.location.href;

+	name = name.replace(/[\[\]]/g, "\\$&");

+	var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), results = regex

+			.exec(url);

+	if (!results)

+		return '';

+	if (!results[2])

+		return '';

+	return results[2].replace(/\+/g, " ");

+}

diff --git a/ecomp-portal-FE-common/client/app/views/catalog/catalog.less b/ecomp-portal-FE-common/client/app/views/catalog/catalog.less
new file mode 100644
index 0000000..dc5e467
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/catalog/catalog.less
@@ -0,0 +1,413 @@
+.w-ecomp-app-catalog {
+  .bg_portalWhite;//white for 1702
+  position: @page-main-position;
+  top: @page-main-top;
+  left: @page-main-left;
+  right: @page-main-right;
+  bottom: @page-main-bottom;
+  padding-top: @padding-top;
+  overflow-y: @page-main-overflow-y;
+  padding-left: @padding-left-side;
+   input:not([type="button"]) {
+    height: 13px;
+} 
+
+  .gridster-item-container .gridster-item-body {
+    overflow-y:auto;
+    overflow-x:hidden;
+  }
+	
+  .appCatalogue-boarder{
+  background-color: #eee;
+  border: 1px dashed white;
+  overflow-y: auto;
+  overflow-x: hidden;
+  }
+  
+  .app-catalog-container {
+    .content_justify;
+    position: relative;
+    padding: 0 0 32px 0;
+    width: 100%;
+
+    .app-catalog-title {
+      //.a24r;
+      margin: auto;
+     // margin-left: 230px;
+      .content_justify;
+    }
+    
+    .app-catalog-list {
+      min-height: 70vh;
+      //display: flex;
+      justify-content: center;
+      flex-flow: row wrap;
+      width: @table-width;
+      //margin-left: 230px;
+      margin-bottom: 63px;
+	  margin:auto;
+      .app-gridster-header {
+        background-color: @u;
+        font-size:12px;
+        overflow:hidden
+      }
+
+      .app-gridster-footer {
+        background-color: @u;
+      }
+
+    }
+  }
+}
+
+@keyframes fadein {
+  from {
+    opacity: 0;
+  }
+  to {
+    opacity: 1;
+  }
+}
+
+.slide.ng-hide-add, 
+.slide.ng-hide-remove,
+.slide.ng-enter,
+.slide.ng-leave {
+  transition: all 0.5s ease;
+}
+.slide.ng-hide,
+.slide.ng-enter {
+  transform: translate(-100%, 0);
+}
+.slide.ng-enter-active {
+  transform: translate(0, 0);
+}
+.slide.ng-leave {
+  transform: translate(0, 0);
+}
+.slide.ng-leave-active {
+  transform: translate(+100%, 0);
+}
+.appCatalogue-boarder{
+	// border: 2px solid #ccc!important;    
+	// border-radius: 16px!important; 
+	  height: 800px;
+    overflow: auto;
+}
+.app-visibility.false {
+	opacity: 0.5;  
+} 
+
+.dock ul{
+display: inline-block;
+
+width: auto;
+margin: 0px;
+padding: 0px;
+list-style: none;
+
+}
+.dock ul li {
+width: auto;
+height: auto;
+display: inline-block;
+bottom: 0;
+vertical-align: bottom;
+margin-top: -43px;
+}
+.dock ul li a {
+display: block;
+height: 150px;
+width: 150px;
+position: relative;
+-webkit-transition-property: width, height,margin-top;
+-webkit-transition-duration: 0.5s;
+-o-transition-property: width, height,margin-top;
+-o-transition-duration: 0.5s;
+-moz-transition-property: width, height,margin-top;
+-moz-transition-duration: 0.5s;
+}
+.dock ul li a:hover {
+width: 200px;
+height: 200px;
+margin-top: -50px;
+}
+.dock ul li a img {
+width: 100%;
+position: absolute;
+bottom: 0;
+left: 0;
+border: none;
+padding: 0px 0px 0px 30px;
+}
+.dock_left{
+width: 31px;
+-webkit-transform: rotate(33deg);
+-moz-transform: rotate(33deg);
+-o-transform: rotate(33deg);
+position: relative;
+background: #EEE;
+overflow: hidden;
+float: left;
+height: 100px;
+z-index: 2;
+margin: -18px;
+}
+.dock_right{
+width: 36px;
+-webkit-transform: rotate(-33deg);
+-moz-transform: rotate(-33deg);
+-o-transform: rotate(-33deg);
+position: relative;
+background: #EEE;
+overflow: hidden;
+float: left;
+height: 100px;
+z-index: 2;
+margin: -18px;
+}
+
+
+
+
+/*Time for the CSS*/
+* {margin: 0; padding: 0;}
+body {background: #ccc;}
+
+.caption {
+	background: rgba(0, 0, 0, 0.5);
+	position: absolute;
+  bottom: 0;	
+	width: 640px
+}
+
+.caption a {
+	display: block;
+	color: #fff;
+	text-decoration: none;
+  font: normal 16px 'Lato', Helvetica, Arial, sans-serif;
+  -webkit-font-smoothing: antialiased;
+	padding: 15px;
+}
+
+
+/*widgets*/
+.singleBtnBorder {
+    border-radius: 6px 6px 6px 6px;  
+}
+    
+.icon-anchor {
+    color: #888;
+}
+   
+.widgetHeaderBtn:hover, .widgetHeaderBtn:focus {
+    background: rgba(0, 0, 0, 0);
+    color: #3a7999;
+    box-shadow: inset 0 0 0 2px #3a7999;
+}
+
+.box-one {
+  -webkit-transition:all linear 0.4s;
+  transition:all linear 0.4s;
+  height:100px; width:200px; background:white;    border: 2px solid #ccc!important;
+    border-radius: 16px!important;
+}
+.box-one.ng-hide {
+display: block!important;
+  opacity:0;
+}
+
+.gridsterContent{
+	height:120px;
+}
+.gridsterAppContent{
+	height:120px;
+}
+ .gridsterImage{
+ 	height:84px; 
+ 	width:168px;
+ }
+
+/*application empty div*/
+.app-error-block {
+    padding-top: 10px;
+}
+
+.text-center { text-align: center; }
+
+.text-right { text-align: right; }
+
+.text-justify { text-align: justify; }
+
+/*widget header*/
+.optionsMenu{
+	position: absolute;
+    list-style: none;
+    top: 25px;
+    right: 10px;
+    border: 1px solid #067ab4;
+    display: none;
+    z-index: 2;
+    border-radius: 6px 0px 6px 6px;
+    background: #fff;
+    width: 130px;
+}
+
+.optionsMenuLink {
+    position: relative;
+    padding-left: 8px;
+    padding-right: 2px;
+    font-size: 12px;
+    line-height: 30px;
+    color: #444444;
+}
+.optionsMenu > li:hover a {
+    color: #ffffff !important;
+}
+.optionsMenu > li {
+    width: 100%;
+    text-align: left;
+}
+.optionsMenu > li:hover {
+    background-color: #0faaf7;
+    border-color: none !important;
+    cursor: pointer;
+}
+
+.simulateCatGridHeader{
+	position: relative;
+    height: 70px !important;
+    border: 1px solid #d3d3d3;
+    border-bottom: 0;
+    background-color: #E5E5E5;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    z-index: 1;
+}
+
+.simulateCatGridHeaderTitle{
+	line-height: 20px;
+	margin-top: 10px;
+	 margin-left: 26px;
+    font-family: "Omnes-ECOMP-W02", Arial;
+    font-size: 18px; 
+    color: #444444;
+    float: left;
+}
+
+.simulateCatGridHeaderRadio{
+    line-height: 20px;
+    margin-top: 10px; 
+    margin-left: 10px;
+    font-family: "Omnes-ECOMP-W02", Arial;
+    font-size: 12px;
+    color: #444444;
+    float: left; 
+}
+
+.simulateCatGridHeaderDetails{
+	line-height: 20px;
+    margin-left: 10px;
+    font-family: "Omnes-ECOMP-W02", Arial;
+    font-size: 12px; 
+    color: #444444;
+    float: left;
+}
+
+.simulateGridHeaderHandle{
+	cursor: move;
+    margin: 12px;
+    position: absolute;
+    top: 0;
+    left: 0;
+    border: 0;
+    vertical-align: middle;
+    -ms-interpolation-mode: bicubic;
+    display: block;
+}
+
+.portal-checkbox--on .portal-checkbox__indicator, .portal-radio--on .portal-radio__indicator {
+    opacity: 1;
+}
+
+.animate-enter, 
+.animate-leave
+{ 
+    -webkit-transition: 400ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all;
+    -moz-transition: 400ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all;
+    -ms-transition: 400ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all;
+    -o-transition: 400ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all;
+    transition: 400ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all;
+    position: relative;
+    display: block;
+} 
+
+.animate-enter.animate-enter-active, 
+.animate-leave {
+    opacity: 1;
+    top: 0;
+    height: 30px;
+}
+
+.animate-leave.animate-leave-active,
+.animate-enter {
+    opacity: 0;
+    top: -50px;
+    height: 0px;
+}
+
+/** gridster style**/
+ul {
+    list-style: none;
+}
+.gridster-box {
+    height: 100%;
+    border: 1px solid #ccc;
+    background-color: #fff;
+ 	transition: transform 0.5s ease-out;
+}
+.gridster-box-header {
+    background-color: #fff;
+    padding: 0 0px 0 10px;
+    border-bottom: 1px solid #ccc;
+    position: relative;
+    height: 50px !important;
+}
+.gridster-box-header h3 {
+    margin-top: 15px;
+    display: inline-block;
+    font-family: "Omnes-ECOMP-W02", Arial;
+}
+.gridster-box-content {
+    padding: 59px;
+}
+.gridster-box:hover{
+    transform: scale(1.1);
+}
+.gridster-box-header-btns {
+    top: 15px;
+    right: 10px;
+    position: absolute;
+}
+.checkbox .skin {
+    left: 125px;
+    top: -24px;
+}
+.form-row {
+    margin-top: -14px;
+}
+
+.icon-content-gridguide{
+cursor:move;
+font-size: 22px; 
+}
+.icon-tickets-contested{
+font-size: 20px;
+margin-left: 126px;
+}
+
+.checkbox, .radio {
+    min-height: 0px; 
+    padding-left: 0px; 
+
+}
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/catalog/catalog.tpl.html b/ecomp-portal-FE-common/client/app/views/catalog/catalog.tpl.html
new file mode 100644
index 0000000..68bf9ef
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/catalog/catalog.tpl.html
@@ -0,0 +1,90 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="w-ecomp-app-catalog">

+	<div class="app-catalog-container" id="page-content">

+	        <div id="title" class="w-ecomp-main-view-title">

+			<h1 class="heading-page">Application Catalog</h1>

+		</div>

+		<div class="app-catalog-list">

+			<div class="simulateCatGridHeader">

+				<span class="simulateCatGridHeaderDetails"> Click the check

+					boxes below to choose which applications are shown on the <a

+					href="applicationsHome">home page</a>.

+				</span> <br> <br> <label id="label-all"

+					class="simulateCatGridHeaderRadio"> Filter: <input

+					id="radio-button-all" type="radio" ng-model="radioValue"

+					value="All"> All

+				</label> <label id="label-accessible" class="simulateCatGridHeaderRadio">

+					<input id="radio-button-accessible" type="radio"

+					ng-model="radioValue" value="Accessible"> Accessible

+				</label> <label id="label-homepage" class="simulateCatGridHeaderRadio">

+					<input id="radio-button-homepage" type="radio"

+					ng-model="radioValue" value="Selected"> On Home Page

+				</label>

+

+			</div>

+			<div class="appCatalogue-boarder" >

+				<div gridster="catalog.gridsterOpts">	

+					<ul>	

+						<li gridster-item="item" ng-repeat="item in appCatalog"

+							ng-if="(item.select && radioValue=='Selected') || (item.access && radioValue=='Accessible') || (radioValue == 'All')">

+							<div class="gridster-box" style="{{!item.access?' color:lightgray !important':''}}">

+								<div class="gridster-box-header"

+									ng-show="(item.select && radioValue=='Selected') || (item.access && radioValue=='Accessible') || (radioValue == 'All')">

+									<i class="icon-content-gridguide"></i>

+									<h3 style="cursor: context-menu">{{item.headerText |

+										elipsis: 13}}</h3>

+								            <div class="form-row" ng-show="item.access || isAdminPortalAdmin">

+								                <label class="checkbox">

+								                    <input type="checkbox" ng-model="item.select" ng-change="catalog.storeSelection(item)">

+								                    <i class="skin"></i> 

+								                </label> 

+								                <i ng-show="item.pending" class="icon-tickets-contested"></i>							                      								                

+									</div>

+								</div>

+								<div class="gridster-box-content"

+									ng-style="{'cursor':'pointer',

+										'background-image': 'url('+(item.imageLink)+')',

+										'order': item.order, 

+										'background-color':'white',

+										'background-repeat': 'no-repeat',

+										'background-size': '170px 130px'}"

+									ng-click="catalog.openAddRoleModal(item)"

+									ng-hide="users.isLoadingTable && !users.getAppCatalogIsDone"

+									>

+								</div>

+							</div>

+						</li>

+					</ul>

+				</div>

+			</div>

+			

+			<br>

+

+			<div style="text-align: right;">

+				To request access to an application, please visit the <a

+					ng-href="getAccess">Get Access</a> page.

+			</div>

+

+			<br>

+		</div>

+

+	</div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/catalog/catalogconfirmation.controller.js b/ecomp-portal-FE-common/client/app/views/catalog/catalogconfirmation.controller.js
new file mode 100644
index 0000000..83438e9
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/catalog/catalogconfirmation.controller.js
@@ -0,0 +1,62 @@
+/*-
+ * ================================================================================
+ * eCOMP Portal
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property
+ * ================================================================================
+ * Licensed 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';
+(function () {
+    class CatalogConfirmationBoxCtrl {
+        constructor($scope,$state,catalogService) {
+
+            let init = () => {
+                let item = ($scope.ngDialogData && $scope.ngDialogData.item) || 'this';
+                this.message = $scope.ngDialogData.message ? $scope.ngDialogData.message : `Are you sure you want to delete "${item}"?`;
+                this.title = $scope.ngDialogData.title ? $scope.ngDialogData.title : '';
+            };
+
+            this.closeBox = isConfirmed => {
+                $scope.closeThisDialog(isConfirmed);
+            };
+            
+            this.goTo = (state, params) => {
+            	$scope.closeThisDialog(false);
+            	$state.go(state,params);
+            	
+            };
+            
+            this.storeSelection = function(item) {
+    			// $log.debug('CatalogCtrl:storeSelection: item.id is ' + item.id + ', select is ' + item.select);
+    			var appData = { 
+    					appId   : item.id,
+    					select  : item.select,
+    					pending : item.pending	
+    			};
+    			catalogService.updateAppCatalog(appData).then(
+    				function(result) {
+    					// $log.debug('CatalogCtrl:storeSelection result is ', result);
+    				})['catch'](function(err) {
+    					$log.error('CatalogCtrl:storeSelection: exception: ', err);
+    				});
+    		};
+
+            init();
+        }
+    }
+    CatalogConfirmationBoxCtrl.$inject = ['$scope','$state','catalogService'];
+    angular.module('ecompApp').controller('CatalogConfirmationBoxCtrl', CatalogConfirmationBoxCtrl);
+})();
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/catalog/information-box.tpl.html b/ecomp-portal-FE-common/client/app/views/catalog/information-box.tpl.html
new file mode 100644
index 0000000..a5b754f
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/catalog/information-box.tpl.html
@@ -0,0 +1,42 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="confirmation-box-container">

+    <div class="confirmation-message-wrap">

+        <div class="confirmation-message" >

+			<div>

+				You do not have access to this application.  

+				See the 

+				<a href="" ng-click="confirmBox.goTo('root.getAccess', {appName: confirmBox.message.headerText})">

+					Get Access page</a> and request access at MyLogins.

+					<br><br>

+					You may check this box if access is pending: 

+				<input type="checkbox" 

+					att-checkbox 

+					ng-model="confirmBox.message.pending"

+					ng-change="confirmBox.storeSelection(confirmBox.message)"

+				>

+			</div>

+

+		</div>

+    </div>

+    <div class="dialog-control">

+        <div class="cancel-button" ng-click="confirmBox.closeBox(false)">Close</div>

+    </div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/confirmation-box/admin-confirmation-box.tpl.html b/ecomp-portal-FE-common/client/app/views/confirmation-box/admin-confirmation-box.tpl.html
new file mode 100644
index 0000000..01b48de
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/confirmation-box/admin-confirmation-box.tpl.html
@@ -0,0 +1,31 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="confirmation-box-container">

+    <div class="confirmation-title-wrap">

+        <div class="confirmation-title" ng-bind="confirmBox.title"></div>

+    </div>

+    <div class="confirmation-message-wrap">

+        <div class="confirmation-message" ng-bind="confirmBox.message"></div>

+    </div>

+    <div class="dialog-control">

+        <div id="confirm-admin-update" class="btn btn-alt btn-small"  ng-click="confirmBox.closeBox(true)">Confirm</div>

+        <div id="cancel-admin-update" class="btn btn-alt btn-small" ng-click="confirmBox.closeBox(false)">Cancel</div>

+    </div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/confirmation-box/confirmation-box.controller.js b/ecomp-portal-FE-common/client/app/views/confirmation-box/confirmation-box.controller.js
new file mode 100644
index 0000000..26d39a7
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/confirmation-box/confirmation-box.controller.js
@@ -0,0 +1,49 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+/**

+ * Created by nnaffar on 1/18/16.

+ */

+'use strict';

+(function () {

+    class ConfirmationBoxCtrl {

+        constructor($scope,$state) {

+

+            let init = () => {

+                let item = ($scope.ngDialogData && $scope.ngDialogData.item) || 'this';

+                this.message = $scope.ngDialogData.message ? $scope.ngDialogData.message : `Are you sure you want to delete "${item}"?`;

+                this.title = $scope.ngDialogData.title ? $scope.ngDialogData.title : '';

+            };

+

+            this.closeBox = isConfirmed => {

+                $scope.closeThisDialog(isConfirmed);

+            };

+            

+            this.goTo = (state, params) => {

+            	$scope.closeThisDialog(false);

+            	$state.go(state,params);

+            	

+            };

+

+            init();

+        }

+    }

+    ConfirmationBoxCtrl.$inject = ['$scope','$state'];

+    angular.module('ecompApp').controller('ConfirmationBoxCtrl', ConfirmationBoxCtrl);

+})();

diff --git a/ecomp-portal-FE-common/client/app/views/confirmation-box/confirmation-box.less b/ecomp-portal-FE-common/client/app/views/confirmation-box/confirmation-box.less
new file mode 100644
index 0000000..8beee1d
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/confirmation-box/confirmation-box.less
@@ -0,0 +1,35 @@
+.confirmation-box-container {
+  .confirmation-message-wrap {
+    display: table;
+    height: 60px;
+    width: 100%;
+
+    .confirmation-message {
+      .dGray14r;
+      display: table-cell;
+      vertical-align: middle;
+      max-width: 100px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      //line-height: 60px;
+    }
+  }
+
+  .confirmation-title-wrap {
+    display: table;
+    height: 20px;
+    width: 100%;
+
+    .confirmation-title {
+      .dGray14r;
+      display: table-cell;
+      vertical-align: middle;
+      text-align: center;
+      font-weight: bold;
+      max-width: 100px;
+      overflow: hidden;
+      padding-bottom: 2px;
+      text-overflow: ellipsis;
+    }
+  }
+}
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/confirmation-box/confirmation-box.tpl.html b/ecomp-portal-FE-common/client/app/views/confirmation-box/confirmation-box.tpl.html
new file mode 100644
index 0000000..0131307
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/confirmation-box/confirmation-box.tpl.html
@@ -0,0 +1,28 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="confirmation-box-container">

+    <div class="confirmation-message-wrap">

+        <div id="confirmation-message" class="confirmation-message" ng-bind="confirmBox.message"></div>

+    </div>

+    <div class="dialog-control">

+        <div id="confirmation-button-next" class="btn btn-alt btn-small" ng-click="confirmBox.closeBox(true)">OK</div>

+        <div id="confirmation-button-cancel" class="btn btn-alt btn-small" ng-click="confirmBox.closeBox(false)">Cancel</div>

+    </div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/confirmation-box/dragdrop-confirmation-box.tpl.html b/ecomp-portal-FE-common/client/app/views/confirmation-box/dragdrop-confirmation-box.tpl.html
new file mode 100644
index 0000000..a7a3fd4
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/confirmation-box/dragdrop-confirmation-box.tpl.html
@@ -0,0 +1,31 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="confirmation-box-container">

+    <div class="confirmation-title-wrap">

+        <div class="confirmation-title" ng-bind="confirmBox.title"></div>

+    </div>

+    <div class="confirmation-message-wrap">

+        <div class="confirmation-message" ng-bind="confirmBox.message"></div>

+    </div>

+    <div class="dialog-control">

+        <div class="next-button" ng-click="confirmBox.closeBox(true)">Confirm</div>

+        <div class="cancel-button" ng-click="confirmBox.closeBox(false)">Cancel</div>

+    </div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/confirmation-box/information-box.tpl.html b/ecomp-portal-FE-common/client/app/views/confirmation-box/information-box.tpl.html
new file mode 100644
index 0000000..912b971
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/confirmation-box/information-box.tpl.html
@@ -0,0 +1,27 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="confirmation-box-container">

+    <div class="confirmation-message-wrap">

+        <div class="confirmation-message" >{{confirmBox.message}}</div>

+    </div>

+    <div class="dialog-control">

+        <div class="btn btn-alt btn-small" ng-click="confirmBox.closeBox(false)">Close</div>

+    </div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/dashboard/dashboard-widget-manage.html b/ecomp-portal-FE-common/client/app/views/dashboard/dashboard-widget-manage.html
new file mode 100644
index 0000000..9883bd1
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/dashboard/dashboard-widget-manage.html
@@ -0,0 +1,152 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div style="margin-top: 10px; margin-bottom: 10psx;">

+	<h1 class="heading1"></h1>

+</div>

+<br />

+

+<div style="margin-top: 25px;">

+	<div id="title" class="manage-contactUs-home-manageWidgets-title">Manage {{widgetTypeDisplay}} Widgets</div>

+		<div class="widgets-popup-margin">         

+            <div class="get-access-table">

+                <div class="table-control" style="margin:30px;">

+				  	<div class="c-ecomp-portal-abs-table" style="height:300px"">

+                    <table b2b-table id="table-main" 

+                    	table-data="ignoredTableData"

+                        current-page="ignoredCurrentPage">

+                        <thead b2b-table-row type="header">

+	                        <tr>

+	                            <th id="th-users-1" b2b-table-header key="title" sortable="true" >Title</th>

+	                            <th id="th-users-2" b2b-table-header key="href" sortable="true" >URL</th>

+	                            <th ng-show="{{widgetType=='EVENTS'}}" id="th-users-3" b2b-table-header key="event_date" sortable="true" >Date</th>

+	                            <th ng-show="{{widgetType=='EVENTS'}}" id="th-users-4" b2b-table-header key="content" sortable="true" >Content</th>

+	                            <th id="th-users-5" b2b-table-header key="sortOrder" sortable="true" >Order</th>

+	                            <th id="th-users-6" b2b-table-header key="edit" sortable="false" >Edit</th>

+	                             <th id="th-users-7" b2b-table-header key="delete" sortable="false" >Delete</th>

+	                        </tr>

+                        </thead>

+                        <!-- Use track-by="UNIQUE KEY HERE" or leave out if no unique keys in data -->

+                        <tbody b2b-table-row type="body" class="table-body" track-by="$index" row-repeat="rowData in widgetData">

+	                        <tr id="tr-rowData" ng-click="">

+	                            <td b2b-table-body>

+	                                <div id="widget-td-title" ng-hide="rowData.showEdit" ng-bind="rowData.title"></div>

+	                               <input id="widget-input-title"  type="text" ng-show="rowData.showEdit" ng-model="widgetData[$index].title"/>

+	                            </td>

+	                            <td b2b-table-body>

+	                                <div id="widget-td-href" ng-hide="rowData.showEdit" ng-bind="rowData.href"></div>

+	                                <input id="widget-input-href" type="text" ng-show="rowData.showEdit" ng-model="widgetData[$index].href"/>

+	                            </td>

+	                            <td  ng-show="{{widgetType=='EVENTS'}}" b2b-table-body>

+	                                <div id="widget-td-date" ng-hide="rowData.showEdit" ng-bind="rowData.eventDate"></div>

+	                                <input id="widget-input-date" type="text" ng-show="rowData.showEdit" ng-model="widgetData[$index].eventDate"/>

+	                            </td>

+	                            <td  ng-show="{{widgetType=='EVENTS'}}" b2b-table-body>

+	                                <div id="widget-td-content" ng-hide="rowData.showEdit">{{rowData.content | cut:true:20:' ...'}}</div>

+	                                <input id="widget-input-content"  type="text" ng-show="rowData.showEdit" ng-model="widgetData[$index].content"/>

+	                            </td>

+	                            <td b2b-table-body>

+	                                <div id="widget-td-order" ng-hide="rowData.showEdit" ng-bind="rowData.sortOrder"></div>

+	                                <input id="widget-input-order"  type="text" ng-show="rowData.showEdit" ng-model="widgetData[$index].sortOrder"/>      

+	                            </td>                            

+	                            <td b2b-table-body>

+                                	<div class="delete-contact-us" ng-hide="rowData.showEdit" ng-click="setEdit($index);">

+                                		<span class="icon-edit"></span>

+                                	</div>

+                                	<span ng-show="rowData.showEdit">

+                                		<a att-button btn-type="primary" ng-click="modify($index);" class="button button--primary" size="small" >Save</a>

+                               		</span>

+	                            </td>

+	                            <td b2b-table-body>

+                                	<div class="delete-contact-us" ng-click="remove($index);">

+                                		<span class="icon-misc-trash"></span>

+                                	</div>

+	                            </td>

+	                        </tr>

+                        </tbody>

+                    </table>

+                </div>

+                

+                <div att-divider-lines></div>

+					<div style="margin-top: 15px; margin-left: -80px;" >

+					<form name = "widgetForm" novalidate>				

+						<div id="addWidgetHeader" class="contact-us-margin">				

+							<h1 style="font-size:18px;">Add</h1>

+							<div class="errMsg">{{errMsg}}</div>

+							<div class="sucessMsg" ng-bind="successMsg"></div>

+						</div>								  

+						<div id="addWidget" class="contact-us-margin">

+							<div>

+								<div>

+									<div class="add-widget-field">

+							           <div id="mots-property-label" class="property-label">Title</div>

+							            <input id="widget-input-add-title" class="input-text-area" type="text"  ng-model="newWidgetObject.title" name="title" ng-required="true"/>							          

+							        </div>

+							        <div class="add-widget-field">

+							            <div  class="property-label">URL</div>

+							            <input id="widget-input-add-url" class="input-text-area" type="text"  ng-model="newWidgetObject.href" type="url" size="3" name = "url"  ng-required = "true"/>

+							        </div>

+							        <div class="add-widget-field" >

+							            <div class="property-label">Sort Order</div>

+							            <input id="widget-input-add-order" class="input-text-area" type="text" ng-model="newWidgetObject.sortOrder"/>

+							        </div>

+						        </div>

+						         <div  class="add-widget-field"  ng-controller="DatePickerController" ng-show="{{widgetType=='EVENTS'}}">

+								    <h4>Event Date</h4>

+								    <div>

+								      <div>

+								        <p class="input-group">

+								          <input id="widget-input-add-date" type="text" name = "eventDate" datetime="yyyy-MM-dd" class="input-text-area" uib-datepicker-popup="{{format}}" 

+								          		ng-model="newWidgetObject.eventDate" is-open="popup1.opened" datepicker-options="dateOptions" close-text="Close" alt-input-formats="altInputFormats" 

+								           ng-focus="open1()"

+								           ng-required = "widgetType =='EVENTS'" />

+								        </p>

+								      </div>

+									</div>

+								</div>	

+								

+						        <div class="add-contact-us-field-des"  ng-show="{{widgetType=='EVENTS'}}">

+						            <div  class="property-label">Content</div>

+						            <textarea style="margin-top: 0px; margin-bottom: 0px; height: 150px" ng-model="newWidgetObject.content" name="content" ng-required = "widgetType =='EVENTS'">

+						            </textarea>						            

+						        </div>

+					        </div>

+							<div style="height:50px;">

+								        <a style="float:right; margin-top:20px;" att-button btn-type="primary" class="button button--primary" size="small" ng-click="saveNew()">Add New</a>

+					

+							</div>

+							

+							<div att-divider-lines></div>

+					

+						</div>

+					</form>

+					</div>

+				    <div style="height:50px;">

+						<a style="float:right;" att-button btn-type="primary" class="button button--primary" size="small" ng-click="closeDialog()">Close</a>

+					</div>           

+            </div>

+        </div>

+    </div>

+</div>

+	

+<script type="application/javascript">

+    $(document).ready(function(){

+        $(".ngdialog-content").css("width","85%")

+    });

+</script>

diff --git a/ecomp-portal-FE-common/client/app/views/dashboard/dashboard-widget-parameter-manage.html b/ecomp-portal-FE-common/client/app/views/dashboard/dashboard-widget-parameter-manage.html
new file mode 100644
index 0000000..59e6f4f
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/dashboard/dashboard-widget-parameter-manage.html
@@ -0,0 +1,86 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="manage-widget-parameters-header"></div>

+<div id="title" class="manage-widget-parameters-title">Manage

+	Widgets Parameters Page</div>

+<div class="widgets-popup-margin">

+	<div class="get-access-table">

+		<div class="table-control manage-widget-parameters-table">

+			<div class="c-ecomp-att-abs-table manage-widget-parameters-table-details">

+				<table b2b-table id="table-main" table-data="ignoredTableData"

+					current-page="ignoredCurrentPage"  ng-show="isLoadingTable">

+					<thead b2b-table-row type="header">

+						<tr>

+							<th id="th-users-2" b2b-table-header key="href" sortable="true">Parameter

+								Key</th>

+							<th id="th-users-3" b2b-table-header key="event_date"

+								sortable="true">My Value</th>

+							<th id="th-users-4" b2b-table-header key="content"

+								sortable="true">Default Value</th>

+							<th id="th-users-5" b2b-table-header key="edit" sortable="false">Edit</th>

+							<th id="th-users-5" b2b-table-header key="edit" sortable="false">Reset to Default</th>

+						</tr>

+					</thead>

+					<tbody b2b-table-row type="body" class="table-body"

+						track-by="$index" row-repeat="rowData in widgetParam">

+						<tr id="tr-rowData">

+							<td b2b-table-body>

+								<div id="widget-parameter-td-parameter-key"

+									ng-bind="rowData.param_key"></div>

+							</td>

+							<td b2b-table-body>

+								<div id="widget-parameter-td-parameter-user-value"

+									ng-hide="rowData.showEdit" ng-bind="rowData.user_value"></div>

+								<input id="widget-input-user-value" type="text"

+								ng-show="rowData.showEdit"

+								ng-model="widgetParam[$index].user_value" />

+							</td>

+

+							<td b2b-table-body>

+								<div id="widget-parameter-td-parameter-default-value"

+									ng-bind="rowData.default_value"></div>

+							</td>

+							<td b2b-table-body>

+								<div ng-hide="rowData.showEdit" ng-click="setEdit($index);">

+									<span class="icon-edit"></span>

+								</div> <span ng-show="rowData.showEdit"> <a att-button

+									btn-type="primary" ng-click="modify($index);"

+									class="button button--primary" size="small">Save</a>

+							</span>

+							</td>

+							<td b2b-table-body>

+								<div ng-click="resetDefault($index);">

+									<span class="icon-edit"></span>

+								</div> 

+							</span>

+							</td>

+						</tr>

+					</tbody>

+

+				</table>

+				<div ng-show = "messageInfo">

+					There is no parameter assoicated with this widget!

+				</div>

+				

+			</div>

+

+		</div>

+	</div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/dashboard/dashboard-widget-parameters.controller.js b/ecomp-portal-FE-common/client/app/views/dashboard/dashboard-widget-parameters.controller.js
new file mode 100644
index 0000000..460618e
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/dashboard/dashboard-widget-parameters.controller.js
@@ -0,0 +1,81 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+

+(function () {

+    class WidgetParameterController {

+        constructor($scope, widgetsCatalogService, userProfileService) {

+        	let widgetId = $scope.ngDialogData.widgetId;

+        	$scope.modflag = false;

+        	$scope.isLoadingTable = false;

+        	$scope.messageInfo = false;

+        	

+        	widgetsCatalogService.getWidgetCatalogParameters(widgetId).then(res => {

+        		if(res.status == 'OK'){

+        			$scope.isLoadingTable = true;

+        			$scope.widgetParam = res.response;

+        		}else if(res.status == 'WARN'){

+        			$scope.messageInfo = true;

+        		}

+             }).catch(err => {

+                 $log.error('WidgetParameterController::init error: ' + err);

+             })

+            	

+        	 $scope.setEdit = function(index) {

+ 	        	if($scope.modflag === false){

+ 	        		$scope.widgetParam[index].showEdit = true;

+ 		        	$scope.modflag = true;

+ 	        	}

+ 	         }	        

+        	

+        	 $scope.resetDefault = function(index) {

+        		var widgetParamObject = {};

+             	widgetParamObject.user_value = $scope.widgetParam[parseInt(index)].default_value;

+             	widgetParamObject.paramId = $scope.widgetParam[parseInt(index)].param_id;

+             	widgetParamObject.widgetId = widgetId;

+  	        	widgetsCatalogService.saveWidgetParameter(widgetParamObject)

+         		.then(function(res){

+         			if(res.status == 'OK'){

+         				$scope.widgetParam[index].user_value = $scope.widgetParam[index].default_value;

+         			}

+         		});

+  	         }	     

+        	

+        	 $scope.modify = function(index) {

+         		

+         		var widgetParamObject = {};

+         		widgetParamObject.user_value = $scope.widgetParam[parseInt(index)].user_value;

+         		widgetParamObject.paramId = $scope.widgetParam[parseInt(index)].param_id;

+         		widgetParamObject.widgetId = widgetId;

+         		

+         		widgetsCatalogService.saveWidgetParameter(widgetParamObject)

+         		.then(function(res){

+         			if(res.status == 'OK'){

+         				$scope.modflag = false;

+         				$scope.widgetParam[index].showEdit = false;

+         			}

+         		});

+         		

+         	};	

+        }           

+    }

+    WidgetParameterController.$inject = ['$scope', 'widgetsCatalogService', 'userProfileService'];

+    angular.module('ecompApp').controller('WidgetParameterController', WidgetParameterController); 

+})();

diff --git a/ecomp-portal-FE-common/client/app/views/dashboard/dashboard-widget-parameters.controller.less b/ecomp-portal-FE-common/client/app/views/dashboard/dashboard-widget-parameters.controller.less
new file mode 100644
index 0000000..ce63acd
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/dashboard/dashboard-widget-parameters.controller.less
@@ -0,0 +1,28 @@
+.manage-widget-parameters-title{

+ 	color: #067ab4;

+    font-family: "Omnes-ECOMP-W02", Arial;;

+    font-size: 24px;

+    margin-left: 30px;

+    width: 1170px;

+    margin-top: 20px; 

+    margin-bottom: 10px;

+}

+

+.manage-widget-parameters-table{

+	margin: 30px;

+}

+

+.manage-widget-parameters-table-details{

+	height: 300px;

+	background-color: #ffffff;

+}

+

+.manage-widget-parameters-header{

+	margin-top: 20px;

+	margin-bottom: 20px;

+	height:20px;

+}

+

+.ngdialog-content{

+	width: 40%;

+}
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/dashboard/dashboard-widget.controller.js b/ecomp-portal-FE-common/client/app/views/dashboard/dashboard-widget.controller.js
new file mode 100644
index 0000000..fe0bf45
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/dashboard/dashboard-widget.controller.js
@@ -0,0 +1,422 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+

+(function () {

+    class CommonWidgetController {

+        constructor(dashboardService, $scope, message, $q, $http, conf, $filter,confirmBoxService,$log) {

+        	$scope.widgetType = message.type;

+        	$scope.widgetTypeDisplay = message.type;

+        	$scope.modflag = false;

+        	

+        	switch (message.type) {

+            case 'EVENTS':

+            	$scope.widgetTypeDisplay = 'Events'

+                break;

+            case 'NEWS':

+            	$scope.widgetTypeDisplay = 'News'

+                break;

+            case 'IMPORTANTRESOURCES':

+            	$scope.widgetTypeDisplay = 'Resources'

+                break;

+        	}

+ 

+        	$scope.widgetData = [];

+	        dashboardService.getCommonWidgetData(message.type)

+	        .then(function (res) {

+	        	 // console.log('CommonWidgetController: result is ' + res);

+	        	 $scope.widgetData = res.response.items;

+	         });

+                 

+	         

+	         

+	        $scope.setEdit = function(index) {

+	        	

+	        	//for(var i=0; i<$scope.widgetData )

+	        	

+	        	if($scope.modflag === false){

+	        		$scope.widgetData[index].showEdit = true;

+		        	$scope.modflag = true;

+	        	}

+	         }	         

+        	

+	         $scope.modify = function(index) {

+        		

+        		$scope.widgetObject = {};

+        		$scope.widgetObject.id = $scope.widgetData[parseInt(index)].id;

+        		$scope.widgetObject.category = $scope.widgetData[parseInt(index)].category;

+        		$scope.widgetObject.title = $scope.widgetData[parseInt(index)].title;

+        		$scope.widgetObject.href = $scope.widgetData[parseInt(index)].href;

+        		$scope.widgetObject.eventDate = $scope.widgetData[parseInt(index)].eventDate;

+        		$scope.widgetObject.content = $scope.widgetData[parseInt(index)].content;

+        		$scope.widgetObject.sortOrder = $scope.widgetData[parseInt(index)].sortOrder;

+        		

+        		var validateMsg = $scope.validateWidgetObject($scope.widgetObject);

+    			if (validateMsg) {

+    				confirmBoxService.showInformation(validateMsg).then(isConfirmed => {return;});

+    				return;

+    			}

+    			

+        		dashboardService.saveCommonWidgetData($scope.widgetObject)

+        		.then(function(res){

+        			if (res.status == 'OK') {

+        			dashboardService.getCommonWidgetData(message.type)

+        			.then(function(res){

+        				$scope.widgetData = res.response.items;

+        	        	$scope.modflag = false;

+        				$scope.cancelEdit(index);

+        				});

+        			}

+        			else {

+        				// Save failed

+        				confirmBoxService.showInformation("Save failed").then(isConfirmed => {return;});

+        				return;

+        			}

+        		});   

+

+        	};	

+        	

+        	$scope.newWidgetObject = {};

+        	

+        	// Answers string error if validation fails; 

+        	// answers null if all is well.

+        	$scope.validateWidgetObject = function(wo) {

+        		if (wo.title == null || wo.title == '')

+        			return "Please enter a title.";

+        		if (wo.href == null || wo.href == '' || !validateUrl(wo.href))

+        			return "Please enter a valid URL that starts with http.";

+        		if (wo.sortOrder == null || wo.sortOrder == '' || isNaN(parseInt(wo.sortOrder)))

+        			return "Please enter a number for the sort order.";

+    			if (wo.category=='EVENTS') {

+    				if (wo.eventDate == null || wo.eventDate == '')

+    					return "Please enter a date for the event.";

+    				// Parses and normalizes the date with rollover.

+    				var filteredDate = $filter('date')(wo.eventDate, "yyyy-MM-dd");

+    				// $log.debug('dashboard-widget-controller: date filter yields ' + filteredDate);

+    				// The date picker shows mm/dd/yy.

+    				if (filteredDate == null || filteredDate.length != 10)

+    					return "Please enter a valid date as YYYY-MM-DD";

+    				if (wo.content==null || wo.content=='')

+    					return "Please enter content for the event.";

+    			}

+    			return null;

+    		};

+        	

+        	$scope.saveNew = function() {

+        		$scope.newWidgetObject.category = message.type;

+        		// $log.info($scope.newWidgetObject);

+        		var validateMsg = $scope.validateWidgetObject($scope.newWidgetObject);

+    			if (validateMsg) {

+    				confirmBoxService.showInformation(validateMsg).then(isConfirmed => {return;});

+    				return;

+    			}

+    			// Transform date into expected storage format

+    			$scope.newWidgetObject.eventDate = $filter('date')($scope.newWidgetObject.eventDate, "yyyy-MM-dd");

+

+    			dashboardService.saveCommonWidgetData($scope.newWidgetObject)

+        		.then(function(res){

+        			if (res.status == 'OK') {

+        				$scope.widgetForm.$setPristine();  

+        				confirmBoxService.showInformation('You have added a new item').then(isConfirmed => {

+        				});

+        				dashboardService.getCommonWidgetData(message.type)

+        				.then(function(res){

+        					$scope.widgetData = res.response.items;

+        					$scope.newWidgetObject = {};

+        			});  

+        			}

+        			else {

+        				confirmBoxService.showInformation("Save failed").then(isConfirmed => {return;});

+        				return;

+        			}        			

+        		});        		

+        	};

+        	

+        	$scope.remove = function(index) {

+        		var confirmMsg = 'Are you sure you want to delete this item from the list?' + ' Press OK to delete.';

+        		confirmBoxService.confirm(confirmMsg).then(function (confirmed) {

+                    if (confirmed == true) { 

+                    	$scope.widgetObject = {};

+                		$scope.widgetObject.id = $scope.widgetData[parseInt(index)].id;

+                		$scope.widgetObject.category = $scope.widgetData[parseInt(index)].category;

+                		$scope.widgetObject.title = $scope.widgetData[parseInt(index)].title;

+                		$scope.widgetObject.href = $scope.widgetData[parseInt(index)].href;

+                		$scope.widgetObject.sortOrder = $scope.widgetData[parseInt(index)].sortOrder;

+                		

+                		

+                		dashboardService.removeCommonWidgetData($scope.widgetObject)

+                		.then(function(res){

+                			dashboardService.getCommonWidgetData(message.type)

+                			.then(function(res){

+                				$scope.widgetData = res.response.items;

+                				$scope.cancelEdit(index);

+                			});        			

+                		});                           	

+                    }

+                });

+        				

+        	};	

+        	$scope.closeDialog = function(){

+        		$scope.closeThisDialog( $scope.widgetData);

+        	}

+        	$scope.deleteSpeedDial = function(widget){

+        		

+        	 }

+        	 

+        	 $scope.manage = function(index) {

+        		 	$scope.modflag = true;

+        		 	

+        		 	var thisdata = $scope.widgetData[index];

+        	      	$scope.editMode=true;

+        	      	var category = "#category"+index;

+        	      	var categoryEdit = "#categoryEdit"+index;

+        	      	

+        	      	var title = "#title"+index;

+        	      	var titleEdit = "#titleEdit"+index;

+

+        	      	var url = "#href"+index;

+        	      	var urlEdit = "#hrefEdit"+index;

+        	      	

+        	      	var order = "#order"+index;

+        	      	var orderEdit = "#orderEdit"+index;

+        	  

+        	    	var eventDate = "#eventDate"+index;

+        	      	var eventDateEdit = "#eventDateEdit"+index;

+        	  

+        	      	

+        	      	var dsavebutton  ="#savebutton"+index;

+        	      	var dremovebutton="#removebutton"+index;

+        	      	var dmanagebutton="#managebutton"+index;

+        	      	var deditRbutton="#editRbutton"+index;

+        	      	

+        	      	$(title).css('display', 'none');

+        	        $(titleEdit).css('display', 'inherit'); 

+        	        $(url).css('display', 'none');

+        	        $(urlEdit).css('display', 'inherit'); 

+        	        $(order).css('display', 'none');

+        	        $(orderEdit).css('display', 'inherit');

+        	        $(eventDate).css('display', 'none');

+        	        $(eventDateEdit).css('display', 'inherit');

+        	        

+        	        $(dsavebutton).css('display', 'inherit');

+        	      	$(dremovebutton).css('display', 'inherit');

+        	      	$(dmanagebutton).css('display', 'none');

+        	      	$(deditRbutton).css('display', 'inherit');

+        	    	

+        	      };

+        	      

+        	   $scope.cancelEdit = function(index) {

+        	    	

+	       	    	$scope.editMode=false;

+	    	    	var category = "#category"+index;

+	    	      	var categoryEdit = "#categoryEdit"+index;

+	    	      	

+	    	      	var title = "#title"+index;

+	    	      	var titleEdit = "#titleEdit"+index;

+	

+	    	      	var url = "#href"+index;

+	    	      	var urlEdit = "#hrefEdit"+index;

+	    	      	

+	    	      	var order = "#order"+index;

+	    	      	var orderEdit = "#orderEdit"+index;

+

+	    	      	var eventDate = "#eventDate"+index;

+	    	      	var eventDateEdit = "#eventDateEdit"+index;

+

+	    	    	var dsavebutton  ="#savebutton"+index;

+	    	    	var dremovebutton  ="#removebutton"+index;

+	    	    	var dmanagebutton="#managebutton"+index;

+	    	    	var deditRbutton="#editRbutton"+index;

+	    	

+	    	    	$(title).css('display', 'inherit');

+	    	        $(titleEdit).css('display', 'none'); 

+	    	        

+	    	        $(url).css('display', 'inherit');

+	    	        $(urlEdit).css('display', 'none'); 

+	    	        

+	    	        $(order).css('display', 'inherit');

+	    	        $(orderEdit).css('display', 'none');

+

+	    	        $(eventDate).css('display', 'inherit');

+	    	        $(eventDateEdit).css('display', 'none');

+

+	    	        $(dsavebutton).css('display', 'none');

+        	       	$(dremovebutton).css('display', 'none');

+        	    	$(dmanagebutton).css('display', 'inherit');

+        	    	$(deditRbutton).css('display', 'none');

+        	      	

+        	     };

+        	

+        	$scope.popupConfirmWin = function(title, msgBody, callback){

+            	modalService.popupConfirmWin.apply(null, arguments);

+            };

+        }           

+    }

+    CommonWidgetController.$inject = ['dashboardService', '$scope', 'message', '$q', '$http', 'conf', '$filter','confirmBoxService','$log'];

+    angular.module('ecompApp').controller('CommonWidgetController', CommonWidgetController); 

+})();

+

+angular.module('ecompApp').filter('cut', function () {

+    return function (value, wordwise, max, tail) {

+        if (!value) return '';

+

+        max = parseInt(max, 10);

+        if (!max) return value;

+        if (value.length <= max) return value;

+

+        value = value.substr(0, max);

+        if (wordwise) {

+            var lastspace = value.lastIndexOf(' ');

+            if (lastspace != -1) {

+              //Also remove . and , so its gives a cleaner result.

+              if (value.charAt(lastspace-1) == '.' || value.charAt(lastspace-1) == ',') {

+                lastspace = lastspace - 1;

+              }

+              value = value.substr(0, lastspace);

+            }

+        }

+

+        return value + (tail || ' …');

+    };

+});

+

+angular.module('ecompApp').controller('DatePickerController', function ($scope, uibDateParser) {

+	 	  $scope.today = function() {

+		    $scope.dt = new Date();

+		  };

+		  $scope.today();

+

+		  $scope.clear = function() {

+		    $scope.dt = null;

+		  };

+

+		  $scope.inlineOptions = {

+		    customClass: getDayClass,

+		    minDate: new Date(),

+		    showWeeks: true

+		  };

+

+		  $scope.dateOptions = {

+		    dateDisabled: disabled,

+		    formatYear: 'yy',

+		    minDate: new Date(),

+		    startingDay: 1

+		  };

+

+		  // Disable weekend selection

+		  function disabled(data) {

+		    var date = data.date,

+		      mode = data.mode;

+		    return mode === 'day' && (date.getDay() === 0 || date.getDay() === 6);

+		  }

+

+		  $scope.toggleMin = function() {

+		    $scope.inlineOptions.minDate = $scope.inlineOptions.minDate ? null : new Date();

+		    $scope.dateOptions.minDate = $scope.inlineOptions.minDate;

+		  };

+

+		  $scope.toggleMin();

+

+		  $scope.open1 = function() {

+			// console.log('In open1');

+		    $scope.popup1.opened = true;

+		  };

+

+		  $scope.open2 = function() {

+			// console.log('In open2');

+			$scope.popup2.opened = true;

+		  };

+

+		  

+		  $scope.setDate = function(year, month, day) {

+		    $scope.dt = new Date(year, month, day);

+		  };

+

+		  $scope.formats = ['dd-MMMM-yyyy', 'yyyy/MM/dd', 'dd.MM.yyyy', 'shortDate'];

+		  $scope.format = $scope.formats[3];

+		  $scope.altInputFormats = ['M!/d!/yyyy'];

+

+		  $scope.popup1 = {

+		    opened: false

+		  };

+

+		  $scope.popup2 = {

+		    opened: false

+		  };

+

+		  var tomorrow = new Date();

+		  tomorrow.setDate(tomorrow.getDate() + 1);

+		  var afterTomorrow = new Date();

+		  afterTomorrow.setDate(tomorrow.getDate() + 1);

+		  $scope.events = [

+		    {

+		      date: tomorrow,

+		      status: 'full'

+		    },

+		    {

+		      date: afterTomorrow,

+		      status: 'partially'

+		    }

+		  ];

+

+		  function getDayClass(data) {

+		    var date = data.date,

+		      mode = data.mode;

+		    if (mode === 'day') {

+		      var dayToCheck = new Date(date).setHours(0,0,0,0);

+

+		      for (var i = 0; i < $scope.events.length; i++) {

+		        var currentDay = new Date($scope.events[i].date).setHours(0,0,0,0);

+

+		        if (dayToCheck === currentDay) {

+		          return $scope.events[i].status;

+		        }

+		      }

+		    }

+

+		    return '';

+		  }

+});

+

+

+

+function toggleCollapsible(id, clickedIconId, subtitutingIconId){

+	$("#"+id).toggle();

+	$("#"+clickedIconId).toggle();

+	$("#"+subtitutingIconId).toggle();

+	setTimeout(function(){focusFirstEle(id);}, 1000);

+}

+

+function focusFirstEle(id){

+	var focusItems = $("#"+id).find(":focusable");

+	for(var i=0; i<focusItems.length; i++){

+		if(!isHidden(focusItems[i])){

+			var targetClassName = focusItems[i].className;

+			if(targetClassName!='collapsibleArrow'){

+				focusItems[i].focus();

+				return;

+			}

+		}

+	}

+}     

+

+function validateUrl(value){

+    return /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value);

+  }

diff --git a/ecomp-portal-FE-common/client/app/views/dashboard/dashboard-widget.controller.less b/ecomp-portal-FE-common/client/app/views/dashboard/dashboard-widget.controller.less
new file mode 100644
index 0000000..8f326c4
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/dashboard/dashboard-widget.controller.less
@@ -0,0 +1,82 @@
+.searchLiHeader {
+font-weight: bold; 
+color: #0574ac; 
+font-size: 16px;
+padding-bottom: 10px;
+line-height: 1.5;
+font-family: Omnes-ECOMP-W02,Arial;
+
+}
+
+.searchLiItems{
+cursor: pointer;
+font-weight: normal; 
+font-size: 12px;
+color: #444444;
+font-family: Omnes-ECOMP-W02,Arial;
+}
+
+.searchUl {
+list-style: none; 
+border-bottom: 1px solid #bbb; 
+padding-bottom: 20px;
+}
+
+#editWidgetsContent {
+    height: 300px;
+    width: 1770px;
+}
+
+#editWidgetsContent .scroll-viewport {
+    height: 300px;
+    width: 1770px;
+}
+
+.full button span {
+    background-color: limegreen;
+    border-radius: 32px;
+    color: black;
+}
+
+.partially button span {
+    background-color: orange;
+    border-radius: 32px;
+    color: black;
+  }
+  
+  .add-widget-field{
+  	width:250px;
+	display: inline-table;
+	margin:10px;
+  	
+  }
+  .uib-datepicker-popup {
+  	th {
+  		vertical-align: middle !important;
+  	}
+  }
+  
+  .input-text-area {
+	  font-style: italic;
+	  padding: 7px 10px;
+	  width: 250px !important;
+	  display: inline-block;
+	  position: relative;
+	  margin-bottom: 10px;
+	  border-radius: 6px;
+	  border: 1px solid #d8d8d8;
+	  height: 32px;
+	  border-color: slategrey !important;
+  }
+  .input-inline-edit-text {
+	  font-style: italic;
+	  padding: 7px 10px;
+	  display: inline-block;
+	  position: relative;
+	  margin-bottom: 10px;
+	  border-radius: 6px;
+	  border: 1px solid #d8d8d8;
+	  height: 32px;
+	  border-color: slategrey !important;
+	  width:100%;
+  }
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/dashboard/dashboard.controller.js b/ecomp-portal-FE-common/client/app/views/dashboard/dashboard.controller.js
new file mode 100644
index 0000000..5a763e2
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/dashboard/dashboard.controller.js
@@ -0,0 +1,504 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+

+function _classCallCheck(instance, Constructor) {

+	if (!(instance instanceof Constructor)) {

+		throw new TypeError('Cannot call a class as a function');

+	}

+}

+

+(function() {

+	var HTTP_PROTOCOL_RGX = /https?:\/\//;

+

+	var DashboardCtrl = function DashboardCtrl(conf, applicationsService, $log,

+			$window, userProfileService, $scope, $cookies, $timeout, $interval, 

+			$modal, $state, beReaderService, dashboardService, confirmBoxService, 

+			auditLogService,ngDialog, $compile, widgetsCatalogService) {

+

+		this.conf = conf;	

+		var _this = this;

+

+		_classCallCheck(this, DashboardCtrl);

+

+		// activate spinner

+		this.isLoading = true;

+		this.isCommError = false;

+		$scope.getUserAppsIsDone = false;

+		this.userProfileService = userProfileService;

+		$scope.demoNum = 1;

+		$scope.event_content_show = false;

+		$scope.widgetData = [];

+		$scope.activateThis = function(ele){

+			 $compile(ele.contents())($scope);

+			 $scope.$apply();

+		};

+

+		$scope.editWidgetModalPopup = function(availableData, resourceType) {

+			$scope.editData = JSON.stringify(availableData);

+			$scope.availableDataTemp = $scope.availableData;

+			ngDialog.open({

+					templateUrl : 'app/views/dashboard/dashboard-widget-manage.html',

+					controller : 'CommonWidgetController',

+					resolve : {

+						message : function message() {

+							var message = {

+								type : resourceType,

+								availableData : $scope.editData

+							};

+							return message;

+						}

+					}

+				}).closePromise.then(needUpdate => {	

+					if(resourceType=='NEWS'){

+						$scope.updateNews();

+					}else if(resourceType=='EVENTS'){

+						$scope.updateEvents();

+					}else if(resourceType=='IMPORTANTRESOURCES'){

+						$scope.updateImportRes();

+					}

+	          });		

+		};

+		

+		$scope.editWidgetParameters = function(widgetId) {

+			let data = {

+            	widgetId: widgetId

+            }

+			ngDialog.open({

+					templateUrl : 'app/views/dashboard/dashboard-widget-parameter-manage.html',

+					controller : 'WidgetParameterController',

+					data: data

+				}).closePromise.then(needUpdate => {	

+						

+	          });		

+		};

+		

+		$scope.sort_options = [        

+		                       {index: 0, value: 'N', title: 'Name'},

+		                       {index: 1, value: 'L', title: 'Last used'},

+		                       {index: 2, value: 'F', title: 'Most used'},

+		                       {index: 3, value: 'M', title: 'Manual'}		                       

+		                   ]; 

+		

+		$scope.selectedSortTypeChanged = function(userAppSortTypePref) {

+			$scope.appsViewData = [];

+			$scope.appsView = [];

+			

+			$scope.sort_type = userAppSortTypePref;

+			

+			applicationsService

+			.getAppsOrderBySortPref(userAppSortTypePref)

+				.then(function(res) {

+					_this.apps = res;			

+					$scope.applyPresentationDetailsToApps(_this.apps); 				

+			})	

+			applicationsService

+			.saveAppsSortTypePreference($scope.selectedSortType)

+				.then(function(res) {

+					// Nothing to do

+				})

+		

+		}

+		$scope.$watch('selectedSortType.value', (newVal, oldVal) => {

+			for(var i=0;i<$scope.sort_options.length;i++){

+			if($scope.sort_options[i].value==newVal){

+			$scope.selectedSortType=angular.copy($scope.sort_options[i]);;

+			}

+			}

+			});

+		

+		$scope.restoreSortSelected = function(){

+			confirmBoxService.confirm("Restore the default size and position of all widgets?").then(

+	    			function(confirmed){

+	    				var checkConfirm = confirmed;

+	    				if(checkConfirm === true){

+	    				applicationsService

+	    				.delWidgetsSortPref($scope.widgetsViewData).then(function(){

+	    					$state.reload();

+	    				});

+	    				}

+	    			});

+		/*	if(confirm('Restore the default size and position of all widgets?') == true)

+				{

+			applicationsService

+			.delWidgetsSortPref($scope.widgetsViewData).then(function(){

+				$state.reload();

+			})

+				}*/

+	    			

+		}

+		

+		$scope.applyPresentationDetailsToApps = function(appsReturned) {

+			var rowNo = 0;

+			for (var i = 0; i < _this.apps.length; i++) {

+				$scope.appsView[i] = {

+					sizeX : 1,

+					sizeY : 1,

+					headerText : '',

+					subHeaderText : '',

+					imageLink : '',

+					order : '',

+					url : '',

+					appid: '',

+				};

+				$scope.appsView[i].headerText = appsReturned[i].name;

+				$scope.appsView[i].subHeaderText = appsReturned[i].notes;

+				let imgLnk = '';

+				if (appsReturned[i].imageUrl)

+					imgLnk = conf.api.appThumbnail.replace(':appId', appsReturned[i].id);

+				//$log.debug('DashboardCtlr::applyPresn: imgLink = ' + imgLnk);

+				$scope.appsView[i].imageLink = imgLnk;

+				$scope.appsView[i].order = appsReturned[i].order;

+				$scope.appsView[i].url = appsReturned[i].url;

+				$scope.appsView[i].restrictedApp = appsReturned[i].restrictedApp;

+				$scope.appsView[i].appid = appsReturned[i].id;

+			}

+			$scope.appsView[_this.apps.length] = {

+					addRemoveApps : true,

+					sizeX : 1,

+					sizeY : 1,

+					headerText : 'Add/Remove Applications',

+					subHeaderText : '',

+					imageLink : 'assets/images/cloud.png',

+					order : '',

+					restrictedApp : false,

+					url : '',

+			};

+			if($scope.appsView.length>6){													

+				$(".dashboard-boarder").css({

+					"height" : "400px"

+				});

+			}else{

+				$(".dashboard-boarder").css({

+					"height" : "210px"

+				});

+			}

+			

+			if ($scope.appsView != undefined

+					&& $scope.appsView != null

+					&& $scope.appsView.length > 0) {

+				$scope.appsViewData = $scope.appsView;

+			}

+		}

+		

+$scope.widgetsView = [];

+		

+		$scope.applyPresentationDetailsToWidgets = function(widgetsReturned){

+		 var rowNo = 0;

+		 for (var i = 0; i < widgetsReturned.length; i++) {

+				$scope.widgetsView[i] = {

+					sizeX : '',

+					sizeY :'',

+					headerText:'',

+					widgetText:'',

+					widgetIdentifier : '',

+					url : '',

+					widgetid: '',

+					attrb:'',

+					row:'',

+					col:'',

+				};

+				$scope.widgetsView[i].widgetid = widgetsReturned[i].id;

+				$scope.widgetsView[i].headerText = widgetsReturned[i].headerName;

+				$scope.widgetsView[i].widgetText = widgetsReturned[i].name;

+				

+				if(widgetsReturned[i].headerName.toLowerCase() === 'news'){

+					$scope.widgetsView[i].widgetIdentifier = 'NEWS';

+				}

+				else

+				if(widgetsReturned[i].headerName.toLowerCase() === 'resources'){

+					$scope.widgetsView[i].widgetIdentifier = 'IMPORTANTRESOURCES';

+				}

+				else

+				if(widgetsReturned[i].headerName.toLowerCase() === 'events'){

+					$scope.widgetsView[i].widgetIdentifier = 'EVENTS';

+				}

+

+				$scope.widgetsView[i].url = widgetsReturned[i].url;

+				$scope.widgetsView[i].attrb = widgetsReturned[i].attrs;

+				if(widgetsReturned[i].width === null){

+					$scope.widgetsView[i].sizeX = 2;

+				}else{

+					$scope.widgetsView[i].sizeX = widgetsReturned[i].width;

+				}

+				if(widgetsReturned[i].height === null){

+					$scope.widgetsView[i].sizeY = 2;

+				}else{

+					$scope.widgetsView[i].sizeY = widgetsReturned[i].height;

+				}

+				$scope.widgetsView[i].row = widgetsReturned[i].x;

+				$scope.widgetsView[i].col = widgetsReturned[i].y;

+			}

+		if ($scope.widgetsView != undefined

+				&& $scope.widgetsView != null

+				&& $scope.widgetsView.length > 0) {

+			$scope.widgetsViewData = $scope.widgetsView;

+		}

+		}

+

+	applicationsService

+	.getUserAppsSortTypePreference().then(function(res) {

+			var resJson = {};

+		    resJson.value = res;

+			if (resJson.value==="N" || resJson.value==="") {

+				resJson.index = 0;

+				resJson.title = 'Name';

+			}else if (resJson.value==="L") {

+				resJson.index = 1;

+				resJson.title = 'Last used';

+			}else if(resJson.value==="F"){

+				resJson.index = 2;

+				resJson.title = 'Most used';

+			}else {

+				resJson.index = 3;

+				resJson.title = 'Manual';

+			}	

+			$scope.selectedSortType = resJson;

+			$scope.selectedSortTypeChanged(res);

+

+		

+		});

+		

+	$scope.widgetsList = [];

+	

+	let getUserWidgets = (loginName) => {

+   	 var conf = this.conf;

+		 widgetsCatalogService.getUserWidgets(loginName).then(res => {

+			 if(!(res instanceof Array)){

+				 this.isCommError = true;

+				 return;

+			 }

+			 for(var i = 0; i < res.length; i++){	 

+	   			 var widget_id = res[i][0];

+	   			 var widget_name = res[i][1];

+	   			 let url = this.conf.api.widgetCommon + "/" + widget_id + "/framework.js";

+	   			 var header_name = widget_name;

+	   			 if(res[i][7] == 1){

+	   				header_name = (widget_name.length > 9) ?widget_name.substring(0, 8) + '...' : widget_name;

+	   			 }

+	   			 if(res[i][4] === "S" || res[i][4] === null ){

+	   			 $scope.widgetsList.push({

+		    			   id: widget_id,

+		    			   headerName: header_name,

+		                   name: widget_name,

+		                   url: url,

+		                   attrs: [{attr: 'data-' + res[i][0], value: ''}],

+		                   x: res[i][3], 

+		                   y: res[i][5],

+		                   height: res[i][6],

+		                   width: res[i][7]

+		    		 });

+	   			 }

+	   			 var script = document

+					 .createElement('script');

+					 script.src = url;

+					 script.async = false;

+					 var entry = document

+					 	.getElementsByTagName('script')[0];

+					 entry.parentNode

+					 	.insertBefore(script, entry);

+			 } 

+   		$scope.applyPresentationDetailsToWidgets($scope.widgetsList);

+        }).catch(err => {

+            $log.error('WidgetsHomeCtrl::getUserWidgets error: ' + err);

+        }).finally(()=> {

+       	 

+        });

+   };

+

+      userProfileService.getUserProfile().then(

+			function(profile) {

+				$scope.orgUserId = profile.orgUserId;

+				getUserWidgets($scope.orgUserId);

+	  });

+		

+		/* Widget Gridster Section */

+		$scope.newsGridsterItem = {

+			sizeX : 1,

+			sizeY : 1,

+			headerText : 'News',

+			subHeaderText : '',

+			imageLink : '',

+			order : '',

+			url : ''

+		};

+

+		$scope.eventsGridsterItem = {

+			sizeX : 1,

+			sizeY : 1,

+			headerText : 'Events',

+			subHeaderText : '',

+			imageLink : '',

+			order : '',

+			url : ''

+		};

+

+		$scope.impoResGridsterItem = {

+			sizeX : 1,

+			sizeY : 1,

+			headerText : 'Resources',

+			subHeaderText : '',

+			imageLink : '',

+			order : '',

+			url : ''

+		};

+		

+		this.gridsterAppOpts = {

+				columns : 6,

+				colWidth : 190,

+				rowHeight : 190,

+				margins : [ 20, 20 ],

+				outerMargin : true,

+				pushing : true,

+				floating : true,

+				swapping : true,

+				resizable: {

+					enabled: false,

+				},

+				draggable : {

+					handle:'.icon-content-gridguide',

+					stop: function stop() {	

+						$scope.defaultSortBy = function() {

+							var resJson = {};

+							resJson.value = 'M';

+							resJson.index = 3;

+							resJson.title = 'Manual';

+							$scope.selectedSortType = resJson;

+							applicationsService.saveAppsSortTypeManual($scope.appsViewData)	

+							applicationsService.saveAppsSortTypePreference($scope.selectedSortType)

+						}

+						$scope.defaultSortBy();			

+					}

+				}

+			};

+			

+			this.gridsterWidgetOpts = {

+					columns : 6,

+					colWidth : 190,

+					rowHeight : 190,

+					margins : [ 20, 20 ],

+					outerMargin : true,

+					pushing : true,

+					floating : true,

+					swapping : true,

+					resizable: {

+						enabled: true,

+						stop: function stop(event, uiWidget, $element){

+							if($element.sizeX == 1)

+								$element.headerText = ($element.widgetText.length > 9) ? $element.widgetText.substring(0, 8) + '...' : $element.widgetText;

+							if($element.sizeX >= 2)

+								$element.headerText = $element.widgetText;

+							

+							applicationsService

+							.saveWidgetsSortManual($scope.widgetsViewData)

+					

+						}

+					},

+					draggable : {

+						handle:'.icon-content-gridguide',

+						stop: function stop(){

+							applicationsService

+							.saveWidgetsSortManual($scope.widgetsViewData)

+					

+						}

+					}

+			};

+		

+		this.goToCatalog = function(item) {

+			$state.go('root.appCatalog');

+		} 

+		

+		this.goToWidgetCatLog = function(item) {

+			$state.go('root.widgetCatalog');

+		} 

+		

+		// navigate to application url in new tab

+		this.goToPortal = function(item) {

+			if (!item.url) {

+				$log.error('No URL found for this application, doing nothing!');

+				return;

+			}

+			if (item.restrictedApp) {

+				// Link-based apps open in their own browser tab

+				$window.open(item.url, '_blank');

+			} else {

+				// cache control so browsers load app page every time

+				var ccParam = 'cc=' + new Date().getTime();

+				var urlParts = item.url.split('#');

+				var appUrl = null;

+				if (urlParts.length < 2) {

+					// no #

+					let urlLastChar = item.url.charAt(item.url.length - 1);

+					if (item.url.includes("?"))

+						appUrl = (urlLastChar === '&' ? item.url + ccParam : item.url + '&' + ccParam);

+					else 

+						appUrl = item.url + '?' + ccParam;

+				} else {

+					// has #

+					let urlLastChar = urlParts[0].charAt(urlParts[0].length - 1);

+					if (item.url.includes("?"))

+						appUrl = (urlLastChar === '&' ? urlParts[0] + ccParam + '#' + urlParts[1]  :  urlParts[0] + '&' + ccParam + '#' + urlParts[1]);

+					else

+						appUrl = urlParts[0] + '?' + ccParam + "#" + urlParts[1];

+				}

+				// $log.debug('DashboardCtrlr::goToPortal: opening tab with URL

+				// ' + appUrl);

+				var tabContent = {

+					id: new Date(),

+					title: item.headerText,

+					url: appUrl,

+					appId: item.appId

+				};

+				$cookies.putObject('addTab', tabContent);

+			}

+

+		};

+		

+		this.auditLog = function(app) {

+			console.log(app);

+			auditLogService.storeAudit(app.appid,'app',app.url);

+		};

+		

+		if (getParameterByName('noUserError') != null) {

+			if (getParameterByName('noUserError') == "Show") {

+				$("#errorInfo").show();

+			}

+		}		

+	};

+

+	DashboardCtrl.$inject = [ 'conf', 'applicationsService', '$log', '$window',

+			'userProfileService', '$scope', '$cookies', '$timeout',	'$interval', 

+			'$modal',  '$state', 'beReaderService', 'dashboardService', 'confirmBoxService', 

+			'auditLogService', 'ngDialog', '$compile', 'widgetsCatalogService' ];

+	angular.module('ecompApp').controller('DashboardCtrl', DashboardCtrl);

+})();

+

+function getParameterByName(name, url) {

+	if (!url)

+		url = window.location.href;

+	name = name.replace(/[\[\]]/g, "\\$&");

+	var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), results = regex

+			.exec(url);

+	if (!results)

+		return '';

+	if (!results[2])

+		return '';

+	return results[2].replace(/\+/g, " ");

+}

diff --git a/ecomp-portal-FE-common/client/app/views/dashboard/dashboard.controller.spec.js b/ecomp-portal-FE-common/client/app/views/dashboard/dashboard.controller.spec.js
new file mode 100644
index 0000000..0c05a81
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/dashboard/dashboard.controller.spec.js
@@ -0,0 +1,78 @@
+/*-
+ * ================================================================================
+ * eCOMP Portal
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property
+ * ================================================================================
+ * Licensed 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';
+
+describe('Controller: DashboardCtrl ', function() {
+    beforeEach(module('ecompApp'));
+
+    let DashboardCtrl, $controller, $q, rootScope, $log, $window, $cookies, scope;
+    let deferredApps, deferredUserProfile, applicationsServiceMock, userProfileServiceMock;
+
+
+    beforeEach(inject( (_$controller_, _$q_, _$rootScope_, _$log_, _$window_, _$cookies_, _CacheFactory_)=>{
+        rootScope = _$rootScope_;
+        scope = rootScope.$new();
+        $q = _$q_;
+        $controller = _$controller_;
+        $log = _$log_;
+        $window = _$window_;
+        $cookies = _$cookies_;
+
+        _CacheFactory_.destroyAll();
+
+        deferredApps = $q.defer();
+        deferredUserProfile = $q.defer();
+        applicationsServiceMock = jasmine.createSpyObj('applicationsServiceMock', ['getUserApps']);
+        applicationsServiceMock.getUserApps.and.returnValue(deferredApps.promise);
+
+        userProfileServiceMock = jasmine.createSpyObj('userProfileServiceMock',['getUserProfile']);
+        userProfileServiceMock.getUserProfile.and.returnValue(deferredUserProfile.promise);
+
+        DashboardCtrl = $controller('DashboardCtrl', {
+            applicationsService: applicationsServiceMock,
+            $log: $log,
+            $window: $window,
+            userProfileService: userProfileServiceMock,
+            $scope: scope,
+            $cookies: $cookies
+        });
+        scope.$digest();
+    }));
+
+    it('should populate this.apps with data from portals service getUserApps', inject(function ( _$q_) {
+        $q = _$q_;
+
+        let profile = {roles: 'superAdmin', orgUserId: 'userid'};
+
+        deferredUserProfile.resolve(profile)
+        deferredApps.resolve([{name: 'portal1'},{name: 'portal2'},{name: 'portal3'}]);
+        scope.$digest();
+        expect(scope.appsViewData.length).toBe(3);
+    }));
+
+    it('should call $log error when getAllPortals fail', inject(function ( _$q_) {
+        $q = _$q_;
+        spyOn($log, 'error');
+        deferredUserProfile.reject('something happened!');
+        scope.$digest();
+        expect($log.error).toHaveBeenCalled();
+    }));
+
+});
diff --git a/ecomp-portal-FE-common/client/app/views/dashboard/dashboard.less b/ecomp-portal-FE-common/client/app/views/dashboard/dashboard.less
new file mode 100644
index 0000000..bf6530b
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/dashboard/dashboard.less
@@ -0,0 +1,901 @@
+.w-ecomp-dashboard-home {

+ .bg_portalWhite;//white for 1702

+   position: @page-main-position;

+  top: @page-main-top;

+  left: @page-main-left;

+  right: @page-main-right;

+  bottom: @page-main-bottom;

+  padding-top: @padding-top;

+  overflow-y: @page-main-overflow-y;

+  padding-left: @padding-left-side;

+

+  .gridster-item-container .gridster-item-body {

+    overflow-y:auto;

+    overflow-x:hidden;

+  }

+

+  .dashboard-home-container {

+      position: relative;

+      padding-right: 0;

+      padding-left: 0;

+      padding-bottom: @container-bottom;

+

+    .dashboard-home-title {

+      .blackText24m;

+      margin: auto;

+      .content_justify;

+    }

+    

+    .portals-list {

+      min-height: 70vh;

+      //display: flex;

+      justify-content: center;

+      flex-flow: row wrap;

+      width: @table-width;

+      //margin-left: 230px;

+      margin-bottom: 63px;

+	  margin:auto;

+      .app-gridster-header {

+        background-color: @u;

+        font-size:12px;

+        overflow:hidden

+      }

+

+      .app-gridster-footer {

+        background-color: @u;

+      }

+

+      .portals-list-item {

+        background-color: @u;

+        border-radius: 2px;

+        box-shadow: 0px -1px 2px 0px rgba(0, 0, 0, 0.1);

+        display: inline-block;

+        width: 360px;

+        height: 300px;

+        background-size: cover;

+        cursor: pointer;

+        margin: 15px;

+        overflow: hidden;

+

+        .portals-item-info {

+          background-color: @u;

+          height: 120px;

+          top: 180px;

+          position: relative;

+          box-shadow: 0px -1px 2px 0px rgba(0, 0, 0, 0.1);

+          padding: 16px;

+

+          .info-title {

+            .blackText24m;

+            margin-bottom: 4px;

+            

+            text-overflow: ellipsis;

+            overflow: hidden;

+          }

+          .info-description {

+            .portalDBlue16r;

+            text-overflow: ellipsis;

+            white-space: nowrap;

+            overflow: hidden;

+          }

+          .info-button {

+            .btn-green;

+            width: 96px;

+            position: absolute;

+            bottom: 16px;

+            left: 16px;

+          }

+

+          &:hover {

+            opacity: .93;

+            z-index: 3;

+          }

+        }

+      }

+    }

+  }

+}

+.w-ecomp-main-error{	  

+  .portalRed;

+  position: absolute;

+  width: 100%;

+  line-height: 1.5em;	

+}

+.w-ecomp-main-disclaimer {

+  text-align: center;

+  .dGray14r;

+  //position: absolute;

+  bottom: -75px;

+  line-height: 1.5em;

+  margin: 0 auto;

+  width:1170px;

+  position: relative;

+

+}

+

+@keyframes fadein {

+  from {

+    opacity: 0;

+  }

+  to {

+    opacity: 1;

+  }

+}

+

+.slide.ng-hide-add, 

+.slide.ng-hide-remove,

+.slide.ng-enter,

+.slide.ng-leave {

+  transition: all 0.5s ease;

+}

+.slide.ng-hide,

+.slide.ng-enter {

+  transform: translate(-100%, 0);

+}

+.slide.ng-enter-active {

+  transform: translate(0, 0);

+}

+.slide.ng-leave {

+  transform: translate(0, 0);

+}

+.slide.ng-leave-active {

+  transform: translate(+100%, 0);

+}

+.dashboard-boarder{

+	// border: 2px solid #ccc!important;    

+	// border-radius: 16px!important; 

+	  height: 210px;

+    overflow: auto;

+}

+.dashboard-information-boarder{

+	border: 2px solid #ccc!important; 

+	border-radius: 16px!important;    

+}

+

+#information-section {

+   margin-top:50px;

+}

+

+.information-section-title{

+    color: #0574ac;

+    font-family: Omnes-ECOMP-W02-Bold,Arial;

+    font-size: 25px;

+    margin: 20px;

+    width: 1170px;

+}

+#left {

+    float: left;

+    width: 33%;

+    height:300px;

+    background-color: white;

+}

+

+#center {

+    float: right;

+    width: 33%;

+    height:300px;

+        background: white;

+} 

+#right {

+    margin-left:34%;

+	margin-right:34%;

+	height:300px;

+	background:white;

+}  

+

+

+/*hover*/

+

+

+.dock ul{

+display: inline-block;

+

+width: auto;

+margin: 0px;

+padding: 0px;

+list-style: none;

+

+}

+.dock ul li {

+width: auto;

+height: auto;

+display: inline-block;

+bottom: 0;

+vertical-align: bottom;

+margin-top: -43px;

+}

+.dock ul li a {

+display: block;

+height: 150px;

+width: 150px;

+position: relative;

+-webkit-transition-property: width, height,margin-top;

+-webkit-transition-duration: 0.5s;

+-o-transition-property: width, height,margin-top;

+-o-transition-duration: 0.5s;

+-moz-transition-property: width, height,margin-top;

+-moz-transition-duration: 0.5s;

+}

+.dock ul li a:hover {

+width: 200px;

+height: 200px;

+margin-top: -50px;

+}

+.dock ul li a img {

+width: 100%;

+position: absolute;

+bottom: 0;

+left: 0;

+border: none;

+padding: 0px 0px 0px 30px;

+}

+.dock_left{

+width: 31px;

+-webkit-transform: rotate(33deg);

+-moz-transform: rotate(33deg);

+-o-transform: rotate(33deg);

+position: relative;

+background: #EEE;

+overflow: hidden;

+float: left;

+height: 100px;

+z-index: 2;

+margin: -18px;

+}

+.dock_right{

+width: 36px;

+-webkit-transform: rotate(-33deg);

+-moz-transform: rotate(-33deg);

+-o-transform: rotate(-33deg);

+position: relative;

+background: #EEE;

+overflow: hidden;

+float: left;

+height: 100px;

+z-index: 2;

+margin: -18px;

+}

+

+

+

+

+/*Time for the CSS*/

+* {margin: 0; padding: 0;}

+body {background: #ccc;}

+

+.slider{

+	width: 640px; /*Same as width of the large image*/

+	position: relative;

+	/*Instead of height we will use padding*/

+	padding-top: 320px; /*That helps bring the labels down*/

+	

+	margin: 50px auto;

+	

+	/*Lets add a shadow*/

+	box-shadow: 0 10px 20px -5px rgba(0, 0, 0, 0.75);

+}

+

+

+/*Last thing remaining is to add transitions*/

+.slider>img{

+	position: absolute;

+	left: 0; top: 0;

+	transition: all 0.5s;

+}

+

+.slider input[name='slide_switch'] {

+	display: none;

+}

+

+.slider label {

+	/*Lets add some spacing for the thumbnails*/

+	margin: 18px 0 0 18px;

+	border: 3px solid #999;

+	

+	float: left;

+	cursor: pointer;

+	transition: all 0.5s;

+	

+	/*Default style = low opacity*/

+	opacity: 0.6;

+}

+

+.slider label img{

+	display: block;

+}

+

+/*Time to add the click effects*/

+.slider input[name='slide_switch']:checked+label {

+	border-color: #666;

+	opacity: 1;

+}

+/*Clicking any thumbnail now should change its opacity(style)*/

+/*Time to work on the main images*/

+.slider input[name='slide_switch'] ~ img {

+	opacity: 0;

+	transform: scale(1.1);

+}

+/*That hides all main images at a 110% size

+On click the images will be displayed at normal size to complete the effect

+*/

+.slider input[name='slide_switch']:checked+label+img {

+	opacity: 1;

+	transform: scale(1);

+}

+/*Clicking on any thumbnail now should activate the image related to it*/

+

+/*We are done :)*/

+

+

+

+.accordion {

+	width: 895px; height: 320px;

+	overflow: hidden;

+  box-shadow: 0 10px 6px -6px #111;

+  margin: 20px auto

+}

+.accordion ul { width: 200% }

+

+.accordion li {

+	position: relative;

+	display: block;

+	width: 160px;

+	float: left;

+  box-shadow: 0 0 30px 8px #222;

+	transition: all 0.4s ease .300ms;

+}

+

+.accordion ul:hover li {width: 40px }

+.accordion ul li:hover {width: 640px }

+

+.caption {

+	background: rgba(0, 0, 0, 0.5);

+	position: absolute;

+  bottom: 0;	

+	width: 640px

+}

+

+.caption a {

+	display: block;

+	color: #fff;

+	text-decoration: none;

+  font: normal 16px 'Lato', Helvetica, Arial, sans-serif;

+  -webkit-font-smoothing: antialiased;

+	padding: 15px;

+}

+

+

+/*events*/

+.events-date{

+	margin-left: 10px; 

+	float:left; 

+	white-space: normal; 

+	display: inline-block; 

+	vertical-align: middle;

+    width: 55px;

+}

+.event-title-div{

+	float: left;

+	width: 235px;

+	line-height: 20px;

+    padding: 5px;

+   	font-size: 14px; 

+   	margin-left: 4px;

+}

+.events-date{

+	height: 20px;

+	margin-left: 4px; 

+	font-size: 14px; 

+}

+.events-content{

+	font-size: 14px; 

+	color: #444;

+    margin-right: 10px;

+}

+.events-content-body{

+	margin-top:5px;

+	color:#444;

+	margin-left:12px;

+	line-height:1.5;

+}

+.events {

+	border-radius: 4px;

+	padding: 3px;

+	-webkit-user-select: none;   

+}

+.events ul {

+	float: left;

+	width:100%;

+	-webkit-user-select: none

+}

+.events ul li {

+	line-height: 30px; 

+	list-style: none;

+    border-bottom: 2px solid grey;

+    height: 100%;

+    min-height: 42px;

+}

+.events ul li:hover {

+    background: #ddd;

+    cursor: pointer;

+}

+.events ul li a {

+	color: black;

+	text-decoration: none;

+	font: 14px Helvetica, Arial, sans-serif;

+	-webkit-font-smoothing: antialiased;

+	-webkit-user-select: none;

+	font-family: "Omnes-ECOMP-W02", Arial;

+}

+.events-link{

+	color: #067ab4 !important; 

+}

+

+

+/*widgets*/

+

+.handle-e{

+    width: 5px;

+}

+

+.singleBtnBorder {

+    border-radius: 6px 6px 6px 6px;  

+}

+

+.widgetHeaderBtn{

+    

+    height: 30px;

+    background-color: #FFFFFF;

+    position: relative;

+    display: inline-block;

+    -moz-background-clip: padding-box;

+    -webkit-background-clip: padding-box;

+    background-clip: padding-box;

+    -webkit-box-sizing: border-box;

+    -moz-box-sizing: border-box;

+    box-sizing: border-box;

+    padding: 8px 20px;

+    font-size: 14px;

+    line-height: 14px;

+    min-width: 60px;

+    border: none;

+    border-radius: 6px;

+    background-color: #ffffff;

+    background-image: none;

+    color: #ffffff;

+    vertical-align: middle;

+    text-align: center;

+    text-decoration: none;

+    text-transform: capitalize;

+    text-shadow: none !important;

+    white-space: nowrap;

+    cursor: pointer;

+    -webkit-user-select: none;

+    -moz-user-select: none;

+    -ms-user-select: none;

+    -o-user-select: none;

+    user-select: none;

+    -webkit-transition: background-color 0.3s ease-out;

+    -moz-transition: background-color 0.3s ease-out;

+    transition: background-color 0.3s ease-out;

+    }

+  

+.widgetHeaderBtnPosition {

+	width: 30px;

+    min-width: 0px;

+    border: 1px solid #AAAAAA;

+    padding-left: 3px;

+    padding-right: 5px;

+    }

+    

+.icon-anchor {

+    color: #888;

+}

+   

+.widgetHeaderBtn:hover, .widgetHeaderBtn:focus {

+    background: rgba(0, 0, 0, 0);

+    color: #3a7999;

+    box-shadow: inset 0 0 0 2px #3a7999;

+}

+/*news*/

+

+@keyframes ticker {

+	0%   {margin-top: 0}

+	25%  {margin-top: -55px}

+	50%  {margin-top: -110px}

+	75%  {margin-top: -165px}

+	100% {margin-top: 0}

+}

+

+body { background: #333; width: 100%; height: 100% }

+

+.news {

+ 

+  width: 350px;

+  height: 250px;

+  margin: 0px auto;

+  // overflow: auto;

+  border-radius: 4px;

+  padding: 3px;

+  -webkit-user-select: none;

+     

+} 

+

+

+

+.news ul {

+  float: left;

+  width:100%;

+  // animation: ticker 25s cubic-bezier(1, 0, .5, 0) infinite;

+  -webkit-user-select: none

+}

+

+.news ul li {line-height: 30px; list-style: none;

+    border-bottom: 2px solid grey;

+    min-height: 42px;

+ }

+ 

+.news ul li:hover {

+    background: #ddd;

+    cursor: pointer;

+}

+

+.news ul li a {

+  color: black;

+  text-decoration: none;

+  font-size: 14px;

+  line-height: 1.5;

+  display: inline-block;

+  width:100%;

+  min-height:40px;

+  padding-top: 5px;

+  padding-bottom: 5px;

+  -webkit-font-smoothing: antialiased;

+  -webkit-user-select: none;

+}

+

+.news ul:hover { animation-play-state: paused }

+.news span:hover+ul { animation-play-state: paused }

+/*resources*/

+.resources {

+ 

+  width: 100%;

+  height: 200px;

+  margin-left: 5px;

+  border-radius: 4px;

+  padding: 3px;

+  -webkit-user-select: none;

+     

+} 

+

+

+

+.resources ul {

+  float: left;

+  width:100%;

+ 

+  -webkit-user-select: none

+}

+

+.resources ul li {line-height: 30px; list-style: none;

+    border-bottom: 2px solid grey;

+    min-height: 42px;

+ }

+ 

+.resources ul li:hover {

+    background: #ddd;

+    cursor: pointer;

+}

+

+.resources ul li a {

+  color: black;

+  text-decoration: none;

+  font-size: 14px;

+  line-height: 1.5;

+  width:100%;

+  min-height:40px;

+  display: inline-block;

+  padding-top: 5px;

+  padding-bottom: 5px;

+  -webkit-font-smoothing: antialiased;

+  -webkit-user-select: none;

+}

+

+

+/* OTHER COLORS */

+.blue { background: #347fd0 }

+.blue span { background: #2c66be }

+.red { background: #d23435 }

+.red span { background: #c22b2c }

+.green { background: #699B67 }

+.green span { background: #547d52 }

+.magenta { background: #b63ace }

+.magenta span { background: #842696 }

+

+

+/*broadcast*/

+

+.box-one {

+  -webkit-transition:all linear 0.4s;

+  transition:all linear 0.4s;

+  height:100px; width:200px; background:white;    border: 2px solid #ccc!important;

+    border-radius: 16px!important;

+}

+.box-one.ng-hide {

+display: block!important;

+  opacity:0;

+}

+

+.gridsterContent{

+	height:120px;

+}

+

+.gridster-item{

+    z-index:0 !important;

+}

+.gridsterAppContent{

+	height:120px;

+	

+}

+

+ .gridsterImage{

+ 	height:84px; 

+ 	width:168px;

+ }

+ .grider-content-ecomp{

+ 	transition: transform 0.5s ease-out;

+ }

+  .grider-content-ecomp:hover{

+    transform: scale(1.1);

+}

+

+

+/*information section*/

+

+.information-section{

+	/*margin-top:25px;*/

+}

+.information-section-gridsterContent{

+	/*height:300px;*/

+}

+.information-sections-gridster-header{

+	color: #0574ac;

+    font-family: Omnes-ECOMP-W02-Bold,Arial;

+    font-size: 25px;

+   

+}

+/*application empty div*/

+.app-error-block {

+    padding-top: 10px;

+   

+}

+/*news empty div*/

+.activity-error-block {

+    padding-top: 60px;

+   

+}

+

+.activity-error-msg1{

+	text-align: center;

+    margin-top: 20px;

+    font-family: "Omnes-ECOMP-W02", Arial;

+    color: #444;

+    font-size: 20px;

+    

+}

+

+.newstape {

+  background: white;

+  color: black;

+  height: 400px;

+  overflow: hidden;

+}

+

+.newstape-content {

+  position: relative;

+  padding: 15px;

+}

+

+.newstape-drag { cursor: ns-resize; }

+

+.text-center { text-align: center; }

+

+.text-right { text-align: right; }

+

+.text-justify { text-align: justify; }

+

+// #newsContainer{

+//   overflow:auto;

+//   height: 100%;

+// }

+

+/*widget header*/

+.optionsMenu{

+	    position: absolute;

+    list-style: none;

+    top: 25px;

+    right: 10px;

+    border: 1px solid #067ab4;

+    display: none;

+    z-index: 2;

+    border-radius: 6px 0px 6px 6px;

+    background: #fff;

+    width: 130px;

+}

+

+.optionsMenuLink {

+    position: relative;

+    padding-left: 8px;

+    padding-right: 2px;

+    font-size: 12px;

+    line-height: 30px;

+    color: #444444;

+}

+.optionsMenu > li:hover a {

+    color: #ffffff !important;

+}

+.optionsMenu > li {

+    width: 100%;

+    text-align: left;

+}

+.optionsMenu > li:hover {

+    background-color: #0faaf7;

+    border-color: none !important;

+    cursor: pointer;

+}

+

+.dashboardSortHeader{

+    margin-left: 756px;

+}

+

+#dashboardAddWidgetPreference{

+    display: inline-block; 

+    font-size: 14px; 

+    color: #3e3e3e; 

+    width: 69%; 

+    text-align: center; 

+    padding: 15px 0px 15px 0px;

+    font-family: "Omnes-ECOMP-W02", Arial;

+}

+

+#dashboardAddWidgetPreference:hover{

+    background-color: #0568ae;

+    color:white !important; 

+}

+

+#dashboardDefaultPreference{

+    display: inline-block; 

+    font-size: 14px; 

+    color: #3e3e3e; 

+    width: 30%; 

+    text-align: center; 

+    padding: 15px 0px 15px 0px; 

+    font-family: "Omnes-ECOMP-W02", Arial;

+}

+

+#dashboardDefaultPreference:hover{

+    background-color: #0568ae;

+    color:white !important;    

+}

+

+.simulateGridHeader{

+	position: relative;

+    height: 50px !important;

+    border: 1px solid #d3d3d3;

+    border-bottom: 0;

+    background-color: #E5E5E5;

+    white-space: nowrap;

+    text-overflow: ellipsis;

+    z-index: 1;

+}

+

+.simulateGridHeaderTitle{

+	line-height: 44px;

+    margin-left: 26px;

+    font-family: "Omnes-ECOMP-W02", Arial;

+    font-size: 18px; 

+    color: #444444;

+    float: left;

+}

+

+.simulateGridHeaderHandle{

+	cursor: move;

+    margin: 12px;

+    position: absolute;

+    top: 0;

+    left: 0;

+    border: 0;

+    vertical-align: middle;

+    -ms-interpolation-mode: bicubic;

+    display: block;

+}

+

+/* apps gridsters */

+ul {

+    list-style: none;

+}

+.gridster-box {

+    height: 100%;

+    border: 1px solid #ccc;

+    background-color: #fff;

+ 	transition: transform 0.5s ease-out;

+}

+.gridster-box-header {

+    background-color: #fff;

+    padding: 0 0px 0 10px;

+    border-bottom: 1px solid #ccc;

+    position: relative;

+    height: 50px !important;

+}

+.gridster-box-header h3 {

+    margin-top: 15px;

+    display: inline-block;

+    font-family: "Omnes-ECOMP-W02", Arial;

+}

+

+.gridster-box-header i {

+font-size: 22px; 

+}

+

+.gridster-box-content {

+    padding: 59px;

+}

+.gridster-box:hover{

+    transform: scale(1.1);

+}

+.gridster-box-header-btns {

+    top: 15px;

+    right: 10px;

+    position: absolute;

+}

+

+/*** widgets ***/

+ul {

+    list-style: none;

+}

+.box {

+    height: 100%;

+    border: 1px solid #ccc;

+    background-color: #fff;

+    font-family: "Omnes-ECOMP-W02", Arial;

+}

+.box-header {

+   	height : 50px;

+    background-color: #fff;

+    padding: 0 30px 0 10px;

+    border-bottom: 1px solid #ccc;

+    position: relative;

+}

+.box-header h3 {

+    margin-top: 15px;

+    display: inline-block;

+    font-size: 16px;

+}

+.box-content {

+    position: absolute;

+    width: 100%;

+    top: 50px;

+    left: 0;

+    right: 0;

+    bottom: 29px;

+    border: 1px solid #d3d3d3;

+    box-sizing: border-box;

+    overflow-y: auto;

+    overflow-x: hidden;

+    color: #444444;

+    bottom: 0px;

+}

+.box-header-btns {

+    top: 10px;

+    right: 10px;

+    cursor: pointer;

+    position: absolute;

+}

+

+#widget-boarder{

+  background-color: #eee;

+  border: 1px dashed white;

+}

+.icon-content-gridguide{

+cursor:move;

+}
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/dashboard/dashboard.tpl.html b/ecomp-portal-FE-common/client/app/views/dashboard/dashboard.tpl.html
new file mode 100644
index 0000000..5e67526
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/dashboard/dashboard.tpl.html
@@ -0,0 +1,179 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="w-ecomp-dashboard-home" id="widgets">

+	<div class="dashboard-home-container" id="page-content">

+		<div align="center" id="errorInfo"

+			style="display: none; font-size: 12px; margin-left: 5px">

+			<span style="color: red">You don't have a user account in that

+				application. Please ask the system administrator. </span>

+		</div>

+		<div class="portals-list" style="margin-bottom: 0px;">

+			<div>

+				<div class="simulateGridHeader">

+					<span class="simulateGridHeaderTitle">Applications <span

+						class="dashboardSortHeader">Sort by:</span></span>

+					<div style="float: right; margin: 7px 7px 7px 0px;"

+						class="form-field" att-select="sort_options"

+						ng-model="selectedSortType"

+						id="sort-by-{{selectedSortType.value}}"

+						ng-change="selectedSortTypeChanged(selectedSortType.value)"></div>

+				</div>

+				<div class="dashboard-boarder">

+					<div class="gridster-container override_background">

+						<div ng-if="appsViewData.length==0">

+							<div class="app-error-block">

+								<i class="icon-information full-linear-icon-information"

+									style="margin-left: 50%; font-size: 90px; color: black"></i> <br>

+								<div class="activity-error-msg1">

+									You do not have access to any application or function in ECOMP

+									Portal. <br> Please request access via <a

+										href="https://mylogins.cso.att.com/index.cfm"

+										target="_mylogins">MyLogins</a>.

+								</div>

+							</div>

+						</div>

+

+						<div ng-if="appsViewData.length>0" id="page-content"

+							class="content" gridster="dashboard.gridsterAppOpts">

+							<ul>

+								<li gridster-item="item" ng-repeat="item in appsViewData">

+									<div class="gridster-box" ng-if="item.addRemoveApps == null">

+										<div class="gridster-box-header">

+											<i class="icon-content-gridguide"></i>

+											<h3 style="cursor: context-menu">{{item.headerText |

+												elipsis: 14}}</h3>

+										</div>

+										<div class="gridster-box-content"

+											ng-style="{'cursor':'pointer',

+											'background-image': 'url('+(item.imageLink)+')',

+											'background-color':'white',

+											'background-repeat': 'no-repeat',

+											'background-size': '170px 130px'}"

+											ng-click="dashboard.goToPortal(item);dashboard.auditLog(item)"

+											ng-hide="users.isLoadingTable && !users.getUserAppsIsDone">

+										</div>

+									</div>

+									<div class="gridster-box" ng-if="item.addRemoveApps">

+										<div class="gridster-box-header" style="cursor: pointer;"

+											ng-click="dashboard.goToCatalog()">

+											<i class="icon-content-gridguide"></i>

+											<h3>Select applications...</h3>

+

+										</div>

+										<div class="gridster-box-content"

+											ng-style="{'cursor':'pointer',

+										'order': item.order, 

+										'text-align': 'center',

+										'background-color':'white',

+										'background-repeat': 'no-repeat',

+										'background-size': '170px 130px',

+										'padding': '4px',

+										'opacity': '1'}"

+											ng-click="dashboard.goToCatalog()" class="gridsterContent">

+											<label style="font-size: 12px;">Click

+												here to personalize <br> this applications page

+											</label> <i class="icon-controls-add-maximize"

+												style="font-size: 80px; color: #067ab4"></i>

+										</div>

+									</div>

+								</li>

+							</ul>

+						</div>

+

+					</div>

+

+				</div>

+			</div>

+

+			<br> <br>

+

+

+			<div style="font-family: Omnes-ECOMP-W02;"

+				ng-show=dashboard.isCommError>Failed to communicate with the

+				widget microservice.</div>

+			

+			<div id="widget-boarder" class="content" gridster="dashboard.gridsterWidgetOpts">

+				<ul>

+					<li gridster-item="widget" ng-repeat="widget in widgetsViewData">

+						<div class="box">

+							<div class="box-header">

+							<i style="cursor:move;" class="icon-content-gridguide"></i>

+								<h3>{{ widget.headerText}}</h3>

+								<div class="box-header-btns pull-right"

+									ng-if="isAdminPortalAdmin == true" class="ng-scope">

+										<a id="widgetHeaderBtns"

+											class="widgetHeaderBtn widgetHeaderBtnPosition singleBtnBorder"

+											ng-class="singleBtnBorder" alt="Settings"

+											ng-mousedown="showImpResOption=!showImpResOption"

+											ng-mouseleave="showImpResOption=false" title="Settings"

+											ddh-accessibility-click="13,32" tabindex="0"><span

+											style="color: #888;"><img

+											ng-src="assets/images/generic.png"

+											style="margin-bottom: 3px; margin-left: 3px;" alt="..."

+											aria-label="Tap or Click to move"

+											src="assets/images/generic.png"></span></a>

+

+										<ul class="optionsMenu"

+											style="display: block"

+											ng-show="showImpResOption"

+											ng-mouseenter="showImpResOption=true"

+											ng-mouseleave="showImpResOption=false">

+											<!-- ngIf: widget.duplicateAllowed -->

+											<li ng-show="widget.widgetIdentifier != ''"

+												ng-click="editWidgetModalPopup(importResData, widget.widgetIdentifier);">

+												<a class="optionsMenuLink" href="javascript:void(0)"

+												ddh-accessibility-click="13,32">Edit</a>

+											</li>

+											

+											<li

+												ng-click="editWidgetParameters(widget.widgetid);">

+												<a class="optionsMenuLink" href="javascript:void(0)"

+												ddh-accessibility-click="13,32">Parameters</a>

+											</li>

+											

+											<!-- end ngIf: widget.duplicateAllowed  -->

+										</ul>

+

+								</div>

+							</div>

+							<div class="box-content">

+							<div  dyn-attr="widget.attrb"></div>

+							</div>

+						</div>

+					</li>

+				</ul>

+			</div>

+			<div></div>

+			<div class="gridster-container override_background">

+				<div id="dashboardAddWidgetPreference"

+					ng-style="{'cursor':'pointer'}"

+					ng-click="dashboard.goToWidgetCatLog()">

+					<i class="icon-controls-add-maximize"></i> Add Widget

+				</div>

+				&nbsp;

+				<div id="dashboardDefaultPreference" ng-style="{'cursor':'pointer'}"

+					ng-click="restoreSortSelected()">

+					<i class="icon-arrows-replay-restart"></i> Reset Widget Layout

+				</div>

+			</div>

+		</div>

+

+	</div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/dashboard/newsticker.controller.js b/ecomp-portal-FE-common/client/app/views/dashboard/newsticker.controller.js
new file mode 100644
index 0000000..0efbaac
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/dashboard/newsticker.controller.js
@@ -0,0 +1,52 @@
+/*-
+ * ================================================================================
+ * eCOMP Portal
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property
+ * ================================================================================
+ * Licensed 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';
+(function () {
+    class NewsTickerController {
+        constructor($scope) {
+        	$scope.newstape = function(){
+            	jQuery('.newstape').newstape({
+                	
+            		// timer period
+            		period: 30, 
+            	
+            		// offset pixel count
+            		offset: 1, 
+            	
+            		// mousewheel scrolling
+            		mousewheel: true, 
+            	
+            		// mousewheel offset pixel count
+            		mousewheelRate: 30, 
+            	
+            		// dragging tape content
+            		dragable: true,
+            		
+            		heightSpy: true
+            	
+            		});
+            };
+            $scope.newstape();
+        	
+        }
+    }
+    NewsTickerController.$inject = ['$scope'];
+    angular.module('ecompApp').controller('NewsTickerController', NewsTickerController);
+})();
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/functionalMenu/functionalMenu-dialog/modal-details.modal.less b/ecomp-portal-FE-common/client/app/views/functionalMenu/functionalMenu-dialog/modal-details.modal.less
new file mode 100644
index 0000000..0d5e98f
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/functionalMenu/functionalMenu-dialog/modal-details.modal.less
@@ -0,0 +1,97 @@
+.functionalMenu-details-modal {
+  height: 430px;
+  
+  input:not([type="button"]) {
+    height: 13px;
+	} 
+
+  .title {
+    //.n18r;
+    .dGray18r;  //AT&T Dark Gray
+    border-bottom: @blue-active 3px solid;
+
+  }
+.btn-group {
+    display: table-cell;
+    max-width: 250px;
+  min-width: 290px;
+   background-color: #f2f2f2;
+  }
+  .btn-medium{
+    width:168px;
+  }
+  
+
+    
+  .dropdown-menu-medium{
+    width:236px;
+    max-height: 200px;
+    overflow-y: auto;
+  }
+
+  .functionalMenu-properties-main {
+    padding: 16px;
+    height: 306px;
+    overflow-y: visible;
+
+    .item{
+      position: relative;
+      margin-bottom: 18px;
+
+      .input-field{
+        .custom-input-field;
+        width: 100%;
+        &.url{
+          width: 78%;
+          display: inline-block;
+        }
+      }
+
+      .select-field {
+        .custom-select-field;
+      }
+
+      .item-label{
+        .dGray14r;
+      }
+
+      .right-item{
+        position: relative;
+        display: inline-block;
+        width: 48%;
+        float: right;
+      }
+      .left-item{
+        display: inline-block;
+        width: 48%;
+      }
+
+      .url-validation-button{
+        .btn-blue;
+        width: 20%;
+        display: inline-block;
+        float: right;
+      }
+
+      .error-container{
+        position: absolute;
+        width: 220px;
+        display: block;
+        height: 12px;
+        line-height: 12px;
+
+        .err-message{
+          color: @funcRed;
+          font-size: 9px;
+        }
+        .valid-message{
+          color: @funcGreen;
+          font-size: 9px;
+        }
+      }
+
+    }
+
+  }
+
+}
diff --git a/ecomp-portal-FE-common/client/app/views/functionalMenu/functionalMenu.less b/ecomp-portal-FE-common/client/app/views/functionalMenu/functionalMenu.less
new file mode 100644
index 0000000..8452590
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/functionalMenu/functionalMenu.less
@@ -0,0 +1,60 @@
+.functional-menu-main{
+ .bg_portalWhite;//white for 1702
+   position: @page-main-position;
+  top: @page-main-top;
+  left: @page-main-left;
+  right: @page-main-right;
+  bottom: @page-main-bottom;
+  padding-top: @padding-top;
+  overflow-y: @page-main-overflow-y;
+  padding-left: @padding-left-side;
+
+  .functional-menu-container {
+    // .portalWhite16r;  // font color and size
+    position: relative;
+    padding-right: 0;
+    padding-left: 0;
+    padding-bottom: @container-bottom;
+    background-color:#ffffff;
+    
+    //width: @table-width;
+	.btn-blue{
+		margin-left:0px;
+	}
+	
+    .tree {
+   	  margin:auto;
+      width: @table-width;
+      //margin-left: @table-margin-left;
+      //margin: 0 auto 60px;
+       font-size:16px;
+      
+    }
+    
+    .functional-admin-button-container {
+      padding-top: 10px;
+      width: @table-width;
+      margin:auto;
+    }
+    
+    .error-text {
+      width: @table-width;
+      margin: auto;
+      padding: 20px;
+      left: 20px;
+      font-weight: bold;
+      font-size: 16px;
+      text-align: left;
+      color: @err;
+      background-color: @portalWhite;
+
+      .error-help {
+        color: @portalDGray;
+        font-weight: normal;
+      }
+    }
+    
+    
+  }
+  
+}
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/header/header.controller.js b/ecomp-portal-FE-common/client/app/views/header/header.controller.js
new file mode 100644
index 0000000..961362a
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/header/header.controller.js
@@ -0,0 +1,449 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+(function () {

+	class HeaderCtrl {

+        constructor($log, $window, userProfileService, menusService, $scope, ECOMP_URL_REGEX, $cookies, $state,auditLogService,notificationService) {

+            this.firstName = '';

+            this.lastName = '';

+            this.$log = $log;

+            this.menusService = menusService;

+            this.$scope = $scope;

+            this.favoritesMenuItems = '';

+            $scope.favoriteItemsCount = 0;

+            $scope.favoritesMenuItems = '';

+            $scope.showFavorites = false;

+            $scope.emptyFavorites = false;

+            $scope.favoritesWindow = false;

+            $scope.notificationCount=0;

+            $scope.showNotification = true;

+

+            $scope.hideMenus = false;

+

+            $scope.menuItems = [];

+            $scope.activeClickSubMenu = {

+                x: ''

+            }; 

+            $scope.activeClickMenu = {

+                x: ''

+            };

+            $scope.megaMenuDataObject =[];

+            $scope.notificationCount= notificationService.notificationCount;

+            this.isLoading = true;

+            this.ECOMP_URL_REGEX = ECOMP_URL_REGEX;

+            

+            var unflatten = function( array, parent, tree ){

+            

+                tree = typeof tree !== 'undefined' ? tree : [];

+                parent = typeof parent !== 'undefined' ? parent : { menuId: null };

+                var children = _.filter( array, function(child){ return child.parentMenuId == parent.menuId; });

+                

+                if( !_.isEmpty( children )  ){

+                  if( parent.menuId === null ){

+                    tree = children;

+                  }else{

+                    parent['children'] = children

+                  }

+                  _.each( children, function( child ){ unflatten( array, child ) } );

+                }

+            

+                return tree;

+            }

+            

+            userProfileService.getFunctionalMenuStaticInfo()

+            .then(res=> {

+            	// $log.debug('HeaderCtrl::getFunctionalMenuStaticInfo: getting Functional Menu Static Info init');

+            	if(res==null || res.firstName==null || res.firstName=='' || res.lastName==null || res.lastName=='' ){

+            		// $log.info('HeaderCtrl::getFunctionalMenuStaticInfo: failed getting userinfo from shared context.. ');

+            		$log.info('HeaderCtrl: failed to get all required data, trying user profile');

+            		userProfileService.getUserProfile()

+                    .then(profile=> {

+                    	// $log.debug('HeaderCtrl:: getting userinfo from session success');

+                        this.firstName = profile.firstName;

+                        this.lastName = profile.lastName;                     

+                        // $log.debug('HeaderCtrl::getFunctionalMenuStaticInfo: user has the following roles: ' + profile.roles);

+                    }).catch(err=> {

+                        $log.error('Header Controller:: getUserProfile() failed: ' + err);

+                    });

+            	} else {

+            		// $log.debug('HeaderCtrl: fetched Functional Menu Static Info successfully',res);

+            		this.firstName = res.firstName;

+            		this.lastName = res.lastName;           	   	  	  

+            	}

+

+            	menusService.GetFunctionalMenuForUser()

+            	.then(jsonHeaderMenu=> {

+            		$scope.menuItems = unflatten( jsonHeaderMenu );

+            		$scope.megaMenuDataObject = $scope.menuItems;

+            	}).catch(err=> {

+            		$log.error('HeaderCtrl::GetFunctionalMenuForUser: HeaderCtrl json returned: ' + err);

+            	});      

+            	

+            }).catch(err=> {

+            	$log.error('HeaderCtrl::getFunctionalMenuStaticInfo failed: ' + err);

+            });

+            

+          //store audit log

+            $scope.auditLog = function(app,type) {

+            	var comment = 'type: '+type+ ',title: '+app.text+",url: "+app.url;

+        		auditLogService.storeAudit(app.appid,'functional',comment);

+        	};

+

+            $scope.loadFavorites = function () {

+                $scope.hideMenus = false;

+                // $log.debug('HeaderCtrl::loadFavorites: loadFavorites has happened.');

+                if ($scope.favoritesMenuItems == '') {

+                    generateFavoriteItems();

+                    // $log.debug('HeaderCtrl::loadFavorites: loadFavorites is calling generateFavoriteItems()');

+                } else {

+                    // $log.debug('HeaderCtrl::loadFavorites: loadFavorites is NOT calling generateFavoriteItems()');

+                }

+            }

+

+            $scope.goToUrl = (item) =>  {

+               //  $log.error('HeaderCtrl::goToUrl has started',item);

+                let url = item.url;

+                let restrictedApp = item.restrictedApp;

+                if (!url) {

+                    $log.warn('HeaderCtrl::goToUrl: No url found for this application, doing nothing..');

+                    return;

+                }

+                if (restrictedApp) {

+                    $window.open(url, '_blank');

+                } else {

+                	if(item.url=="getAccess" || item.url=="contactUs"){

+                	    // if (url = window.location.href)

+                		$state.go("root."+url);

+                	} else {

+                    	var tabContent = { id: new Date(), title: item.text, url: item.url,appId:item.appid };

+                    	$cookies.putObject('addTab', tabContent );

+                    }

+                    // $log.debug('HeaderCtrl::goToUrl: url = ', url);

+                }

+                $scope.hideMenus = true;

+            }

+            

+            

+            

+            $scope.submenuLevelAction = function(index, column) {

+                if ($scope.favoritesMenuItems == '') {

+                    generateFavoriteItems();

+                    // $log.debug('HeaderCtrl::submenuLevelAction: submenuLevelAction is calling generateFavoriteItems()');

+                } else {

+                    // $log.debug('submenuLevelAction is NOT calling generateFavoriteItems()');

+                }

+                // $log.debug('item hovered: ' + index + '; column = ' + column);

+                // if (column == 2) {  // 2 is Design

+                //     // This is an admitted hack. See aw3218 for reasons why

+                //     $log.debug('submenuLevelAction column == 2');

+                //     $scope.favoritesWindow = false;

+                //     $scope.showFavorites = false;

+                //     $scope.emptyFavorites = false;

+                // }

+                if (index=='Favorites' && $scope.favoriteItemsCount != 0) {

+                    // $log.debug('HeaderCtrl::submenuLevelAction: Showing Favorites window');

+                    // generateFavoriteItems();

+                    $scope.favoritesWindow = true;

+                    $scope.showFavorites = true;

+                    $scope.emptyFavorites = false;

+                }

+                if (index=='Favorites' && $scope.favoriteItemsCount == 0) {

+                    // $log.debug('HeaderCtrl::submenuLevelAction: Hiding Favorites window in favor of No Favorites Window');

+                    // generateFavoriteItems();

+                    $scope.favoritesWindow = true;

+                    $scope.showFavorites = false;

+                    $scope.emptyFavorites = true;

+                }

+                if (index!='Favorites' ) {

+                    $scope.favoritesWindow = false;

+                    $scope.showFavorites = false;

+                    $scope.emptyFavorites = false;

+                }

+

+            };

+            

+            $scope.hideFavoritesWindow = function() {

+                $scope.showFavorites = false;

+                $scope.emptyFavorites = false;

+                // $scope.thirdFourthMenus = true;

+            }

+            

+            $scope.isUrlFavorite = function (menuId) {

+                // $log.debug('array objects in menu favorites = ' + $scope.favoriteItemsCount + '; menuId=' + menuId);

+                var jsonMenu =  JSON.stringify($scope.favoritesMenuItems);

+                var isMenuFavorite =  jsonMenu.indexOf('menuId\":' + menuId);

+                // $log.debug('jsonMenu.indexOf(menuId:' + jsonMenu.indexOf('menuId\":'+menuId));

+                // $log.debug('isMenuFavorite= ' + isMenuFavorite);

+                if (isMenuFavorite==-1) {

+                    return false;

+                } else {

+                    return true;

+                }

+

+            }

+            

+            let generateFavoriteItems  = () => {

+                menusService.getFavoriteItems()

+                    .then(favorites=> {

+                        // $log.debug('HeaderCtrl.getFavoriteItems:: ' + JSON.stringify(favorites));

+                        $scope.favoritesMenuItems = favorites;

+                        $scope.favoriteItemsCount = Object.keys(favorites).length;

+                        // $log.info('HeaderCtrl.getFavoriteItems:: number of favorite menus: ' + $scope.favoriteItemsCount);

+                    }).catch(err=> {

+                        $log.error('HeaderCtrl.getFavoriteItems:: Error retrieving Favorites menus: ' + err);

+                });

+            }

+

+           $scope.setAsFavoriteItem =  function(event, menuId){

+               var jsonMenuID = angular.toJson({'menuId': + menuId });

+               // $log.debug('HeaderCtrl::setFavoriteItems: ' + jsonMenuID  + " - " +  event.target.id);

+

+               menusService.setFavoriteItem(jsonMenuID)

+               .then(() => {

+                   // var elementId = '#'+ event.currentTarget.id;

+                   angular.element('#' + event.target.id).css('color', '#fbb313');

+                   generateFavoriteItems();

+               }).catch(err=> {

+                   $log.error('HeaderCtrl::setFavoriteItems:: API setFavoriteItem error: ' + err);

+               });

+           };

+

+            $scope.removeAsFavoriteItem =  function(event, menuId){

+                // $log.debug('-----------------------------removeAsFavoriteItem: ' + menuId + " - " +  event.target.id);

+                menusService.removeFavoriteItem(menuId)

+                .then(() => {

+                    angular.element('#' + event.target.id).css('color', '#666666');

+                    generateFavoriteItems();

+                }).catch(err=> {

+                    $log.error('HeaderCtrl::removeAsFavoriteItem: API removeFavoriteItem error: ' + err);

+                });

+            };

+

+            $scope.goToPortal = (headerText, url) => {

+                if (!url) {

+                    $log.warn('HeaderCtrl::goToPortal: No url found for this application, doing nothing..');

+                    return;

+                }

+                if (!ECOMP_URL_REGEX.test(url)) {

+                    url = 'http://' + url;

+                }

+

+                if(headerText.startsWith("vUSP")) {

+                	window.open(url, '_blank');//, '_self'

+                }

+                else {

+                	var tabContent = { id: new Date(), title: headerText, url: url };

+                	$cookies.putObject('addTab', tabContent );

+                }

+            };

+

+        }

+    }

+    class LoginSnippetCtrl {

+        constructor($log, $scope, $cookies, $timeout, userProfileService, sessionService) {

+            $scope.firstName="";

+            $scope.lastName="";

+            $scope.displayUserAppRoles=false; 

+            $scope.allAppsLogout = function(){

+            	

+            	var cookieTabs = $cookies.getObject('visInVisCookieTabs');

+             	if(cookieTabs!=null){

+             		for(var t in cookieTabs){

+             		

+             			var url = cookieTabs[t].content;

+             			if(url != "") {

+             				sessionService.logout(url);

+             	      	}

+             		}

+             	}

+             	// wait for individual applications to log out before the portal logout

+             	$timeout(function() {

+             		window.location = "logout.htm";

+             	}, 2000);

+            }

+            

+            

+            try {

+            	userProfileService.getFunctionalMenuStaticInfo()

+                .then(res=> {

+              	  // $log.info('HeaderCtrl::LoginSnippetCtrl: Login information: ' + JSON.stringify(res));

+              	  $scope.firstName = res.firstName;

+              	  $scope.lastName = res.lastName;

+              	  $scope.loginSnippetEmail = res.email;

+              	  $scope.loginSnippetUserid = res.userId;

+              	  $scope.lastLogin = res.last_login;

+                }).catch(err=> {

+              	  $log.error('HeaderCtrl::LoginSnippetCtrl: failed in getFunctionalMenuStaticInfo: ' + err);

+                });

+            } catch (err) {

+                $log.error('HeaderCtrl::LoginSnippetCtrl caught exception: ' + err);

+            }

+            

+            $scope.getUserApplicationRoles= function(){

+          	  $scope.userapproles = [];

+          	  if($scope.displayUserAppRoles)

+	         		$scope.displayUserAppRoles = false;

+	         		 else

+	         			$scope.displayUserAppRoles = true;

+          	  

+        	        userProfileService.getUserAppRoles($scope.loginSnippetUserid)

+        	          .then(res=>{

+        			

+       		 for(var i=0;i<res.length;i++){              			

+	            	var userapprole ={

+	            		App:res[i].appName,

+	            		Roles:res[i].roleNames,	

+	            	};

+	            	

+	            	$scope.userapproles.push(userapprole); 

+       		}

+       		 

+        	});

+        	

+          }

+        }        

+    }

+    class NotificationCtrl{

+    	constructor($log, $scope, $cookies, $timeout, sessionService,notificationService,$interval,ngDialog) {

+    		 $scope.notifications=[];   

+    		 var intervalPromise = null;

+             $scope.notificationCount= notificationService.notificationCount;

+             

+             $scope.getNotification = function(){            	 

+            	 notificationService.getNotification()

+                 .then(res=> {

+                	notificationService.decrementRefreshCount();

+                	var count = notificationService.getRefreshCount();

+                 	if (res==null || res.data==null || res.data.message!='success') {

+                 		$log.error('NotificationCtrl::updateNotifications: failed to get notifications');

+                 		if (intervalPromise != null)

+                 			$interval.cancel(intervalPromise);

+                 	} else if(count<=0){

+                 		if (intervalPromise != null)

+                 			$interval.cancel(intervalPromise);

+                 	} else {

+                 		$scope.notifications = [];

+                 		notificationService.setNotificationCount(res.data.response.length);

+                 		for(var i=0;i<res.data.response.length;i++){

+                 			var data = res.data.response[i];                			

+			            	var notification ={

+			            		id:data.notificationId,

+			            		title:data.msgHeader,

+			            		message:data.msgDescription,

+			            		source:data.msgSource,

+			            		time:data.createdDate,

+			            		priority:data.priority

+			            	};

+			            	$scope.notifications.push(notification);       

+			             }  

+                 	}   	

+                 }).catch(err=> {

+                 	$log.error('NotificationCtrl::getNotification: caught exception: ' + err);

+                 	if (intervalPromise != null)

+                 		$interval.cancel(intervalPromise);

+                 });      

+             }

+             $scope.getNotification();

+             function updateNotifications() {

+            	 $scope.getNotification();

+             }

+             $scope.start = function(rate) {

+ 				// stops any running interval to avoid two intervals running at the same time

+ 				$scope.stop(); 	

+ 				// store the interval promise

+ 				intervalPromise = $interval(updateNotifications, rate);

+ 			 };

+

+ 			 $scope.stop = function() {

+ 				$interval.cancel(intervalPromise);

+ 			 };

+ 			 

+   			

+  			 $scope.showDetailedJsonMessage=function (selectedAdminNotification) {

+   				if (selectedAdminNotification.source!=='EP'){

+				 var messageObject=JSON.parse(selectedAdminNotification.message);

+				 var html="";

+				 html+='<p>'+'Message Source'+' : '+selectedAdminNotification.source+'</p>';

+				 html+='<p>'+'Message Title'+' : '+selectedAdminNotification.title+'</p>';

+				 for(var field in  messageObject){

+					 if(field=='eventDate'||field=='lastModifiedDate'){

+						 html+='<p>'+field+' : '+new Date(+messageObject[field])+'</p>';

+						  

+					 }else{

+					 html+='<p>'+field+' : '+messageObject[field]+'</p>';

+					 

+					 }

+				 }

+ 			

+ 		     var modalInstance = ngDialog.open({

+ 				    templateUrl: 'app/views/user-notifications-admin/user.notifications.Json.details.modal.page.html',

+ 				    controller: 'userNotificationCtrl',

+ 				    resolve: {

+ 				    	message: function () {

+ 				    		var message = {

+ 				    			   title:    '',

+ 		                       		text:    html

+ 		                       		

+ 		                           	};

+ 				          return message;

+ 				        },

+ 				     

+ 				      }

+ 				  }); 

+ 			

+   				} 

+ 			 };

+ 			 

+ 			notificationService.getNotificationRate().then(res=> {

+            	if (res == null || res.response == null) {

+            		$log.error('NotificationCtrl: failed to notification update rate or duration, check system.properties file.');

+            	} else {

+            		var rate = parseInt(res.response.updateRate);

+					var duration = parseInt(res.response.updateDuration);

+					notificationService.setMaxRefreshCount(parseInt(duration/rate)+1);

+					notificationService.setRefreshCount(notificationService.maxCount);

+           			if (rate != NaN && duration != NaN) {

+						$scope.updateRate=rate;

+						$scope.start($scope.updateRate);

+           			}            			

+            	}

+            }).catch(err=> {

+            	$log.error('NotificationCtrl: getNotificationRate() failed: ' + err);

+            });

+             

+             $scope.deleteNotification = function(index){

+            	 if ($scope.notifications[index].id == null || $scope.notifications[index].id == '') {

+             		$log.error('NotificationCtrl: failed to delete Notification.');

+             		return;

+            	 }

+            	 notificationService.setNotificationRead($scope.notifications[index].id);

+            	 $scope.notifications.splice(index,1);

+            	 notificationService.setNotificationCount($scope.notifications.length);     	 

+             }

+    	}

+    }

+    NotificationCtrl.$inject = ['$log', '$scope', '$cookies', '$timeout', 'sessionService','notificationService','$interval','ngDialog'];

+    LoginSnippetCtrl.$inject = ['$log', '$scope', '$cookies', '$timeout','userProfileService', 'sessionService'];

+    HeaderCtrl.$inject = ['$log', '$window', 'userProfileService', 'menusService', '$scope', 'ECOMP_URL_REGEX','$cookies','$state','auditLogService','notificationService'];

+    angular.module('ecompApp').controller('HeaderCtrl', HeaderCtrl);

+    angular.module('ecompApp').controller('loginSnippetCtrl', LoginSnippetCtrl);

+    angular.module('ecompApp').controller('notificationCtrl', NotificationCtrl);

+

+})();

diff --git a/ecomp-portal-FE-common/client/app/views/header/header.controller.spec.js b/ecomp-portal-FE-common/client/app/views/header/header.controller.spec.js
new file mode 100644
index 0000000..3841a2b
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/header/header.controller.spec.js
@@ -0,0 +1,19 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

diff --git a/ecomp-portal-FE-common/client/app/views/header/header.less b/ecomp-portal-FE-common/client/app/views/header/header.less
new file mode 100644
index 0000000..ec57812
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/header/header.less
@@ -0,0 +1,496 @@
+.header-section {
+  position: relative;
+  z-index: 999;
+}
+
+.logo-image {
+          .portal-logo;
+          display: inline-block;
+          vertical-align: middle;
+          margin-top: -3px;
+      }
+
+.portal-title {
+  font-weight: 400;
+  font-family: "Omnes-ECOMP-W02",Arial !important;
+  font-size: 18px;
+  //.c18b;
+}
+
+.parentmenu-tabs {
+  height: 55px;
+}
+
+.menu-section {
+  float: left;
+}
+
+/* Logout control*/
+.controlCls{
+  font-size: .975rem;
+  color: @portalDGray;
+  display: inline-block;
+  cursor: pointer;
+  height: 37px;
+  line-height: 37px;
+  padding-bottom: 10px;
+  vertical-align: middle;
+  width: 100%;
+}
+
+
+.controlCls:hover{
+  color:@portalLBlue !important;
+}
+
+.login-section {
+  float:  right;
+  margin-top:15px;
+  min-width:150px;
+}
+
+.login-snippet-text {
+  display: inline-block;
+  font-size:  12px;
+  font-weight: bold;
+  margin-left:  5px;
+  overflow: hidden;
+  max-height: 31px;
+  max-width:  120px;
+  padding-top: 0;
+  margin-top: 0;
+  font-family: "Omnes-ECOMP-W02",Arial;
+  white-space: nowrap;
+}
+
+
+.newrow {
+  clear: left;
+}
+
+.header-columns{
+  -webkit-column-count:4;
+  -moz-column-rule: 1px outset @portalLGray;
+  -moz-column-count:4;
+  column-count: 4;
+  line-height: 12px;
+  max-height: 500px;
+  overflow-x: hidden;
+  overflow-y:hidden;
+  column-gap: 13px;
+  column-rule: 1px outset @portalLGray;
+
+
+}
+
+.header-columns li{
+  -webkit-column-break-inside: avoid;
+  break-inside: avoid;
+  //-webkit-page-break-inside: avoid;
+  page-break-inside: avoid;
+
+  margin-top: 0px !important;
+
+}
+
+.header-columns-div{
+  width:100%;
+  margin-left: 12px;
+  margin-top: 12px
+}
+
+
+//.favorites-icon li [class*=icon]:hover {
+.favorites-icon-active {
+  position: relative;
+  margin-top: 5px;
+  margin-left: 5px;
+  top: 3px;
+  color: @funcYellow;
+}
+
+.favorites-icon-inactive {
+  position: relative;
+  margin-top: 5px;
+  margin-left: 5px;
+  top: 3px;
+  color: @portalDGray;
+}
+
+.favorites-window-empty {
+  width: 100%;
+  height: auto;
+  justify-content:center;
+  align-items:center;
+  margin: auto;
+  text-align: center;
+
+  .no-fav-icon{
+  	font-weight: 400;
+    font-size: 50px;
+    text-align: center;
+    color: rgb(255, 155, 0);
+  }
+  .largeText {
+    font-weight: 400;
+    font-family: Omnes-ECOMP-W02-Bold,Arial !important;
+    font-size: 18px;
+    text-align: center;
+    color: @portalDGray;
+  }
+
+  .normal {
+    color: @portalDGray;
+    font-size: 14px;
+    text-align: center;
+  }
+
+
+}
+.favorites-window {
+  width: 100%;
+  height: auto;
+  //margin: auto;
+  font-size: 14px !important;
+  display: flex;
+  margin-top: 25px;
+  margin-left: 25px;
+  z-index: 1000;
+
+  .fav-links {
+    margin-right: 25px;
+  }
+
+  .largeText {
+    font-weight: 400;
+    font-family: Omnes-ECOMP-W02-Bold,Arial !important;
+    font-size: 18px;
+    text-align: center;
+    color: @portalDGray;
+  }
+
+  a:link, a:active, a:hover {
+    margin-left: 3px;
+    margin-right: 20px;
+    text-decoration: none;
+  }
+
+  a:hover {
+    color: @portalLBlue;
+  }
+}
+
+
+
+ .notifications-count
+  {
+    .border-radius(50%);
+    background:#db3434;
+    color: @colorWhite;
+    font-size: 10px;
+    padding-top: 2px;
+    height: 16px;   
+    position: absolute;
+    right: -7px;
+    text-align: center;
+    top: -8px;
+    width: 16px;
+  }
+  
+  .notification-header{
+    border-bottom: 1px solid #b4b4b4;
+    padding: 10px 40px 0px 40px;
+  }
+  .notification-heading{
+  font-family: Omnes-ECOMP-W02, Arial;
+    font-size: 24px;
+    padding-top: 15px;
+    margin-bottom: 1rem;
+  }
+  .notificationBox{
+      border-bottom: 1px solid #b4b4b4;
+   
+  }
+  
+  .notification-info-icon{
+      padding-top: 41px;
+    font-size: 47px;
+  }
+  .notification-text {
+    line-height: 15px;
+    margin: 0;
+    padding: 0 0 24px 0;
+    text-align: center;
+        font-family: Omnes-ECOMP-W02, Arial;
+    font-size: 16px;
+    }
+    
+    
+
+	.notificationBox .icon-circle-action-close {
+	    cursor: pointer;
+	    font-size: 16px;
+	    font-family: "Omnes-ECOMP-W02",Arial
+    }
+    
+	.notification-close {
+	    padding: 2px 2px 0px 0px;
+	    float: right;
+    }
+  #notification-flag{
+ 	font-size: 20px; 
+ 	color: white;  
+ 	vertical-align: middle;
+  }
+  #notification-flag:hover{
+  	color:silver
+  }
+ .notifications-list
+  {
+    list-style: none;
+    margin: 0;
+    overflow: auto;
+    height: 250px;
+    width: 270px;
+    padding: 0;
+    
+    
+    .item:hover{
+    	background-color:#eee;
+    }
+    .item
+    {
+      .transition-transform(@transitionDefault);
+      border-bottom: 1px solid @colorSilver;
+      color: @colorAsbestos;
+      cursor: default;
+      display: block;
+      padding: 10px;
+      position: relative;
+      white-space: nowrap;
+      width: 250px;
+      font-family: "Omnes-ECOMP-W02",Arial;
+	 &:before,
+      	.details,
+      	.button-dismiss
+      	{
+       		display: inline-block;
+        	vertical-align: middle;
+      	}
+	 .icon{
+	 	display:inline-block;
+      	.important
+      	{
+        	.border-radius(50%);
+        	background: red;
+        	content: '';
+        	height: 8px;
+        	width: 8px;
+        	display:block;
+      	}
+      	.normal
+      	{
+        	.border-radius(50%);
+        	background: @colorPeterRiver;
+        	content: '';
+        	height: 8px;
+        	width: 8px;
+        	display:block;
+      	}
+	  }
+      .details
+      {
+        margin-left: 10px;
+        white-space: normal;
+        width: 200px;
+        font-size:12px;
+
+        .title,
+        .date
+        {
+          display: block;
+          font-weight:bold;
+        }
+		.message-body{
+			display: block;
+		}
+        .date
+        {
+          color: @colorConcrete;
+          font-size: .85em;
+          margin-top: 3px;
+        }
+      }
+      
+      .button-dismiss
+      {
+        color: @colorSilver;
+        font-size: 15px;
+        
+        &:hover,
+        &:focus
+        {
+          color: @colorConcrete;
+        }
+      }
+      
+      &.no-data
+      {
+        display: none;
+        text-align: center;
+        
+        &:before
+        {
+          display: none;
+        }
+      }
+
+      &.expired
+      {
+        color: @colorSilver;
+
+        &:before
+        {
+          background: @colorSilver;
+        }
+
+        .details
+        {
+          .date
+          {
+            color: @colorSilver;
+          }
+        }
+      }
+      
+      &.dismissed
+      {
+        .transform(translateX(100%));
+      }
+    }
+  }
+    
+#header-user-div{	
+    vertical-align: middle;
+    margin-top:20px;
+}
+
+#header-user-icon{
+	display: inline-block;
+    vertical-align: middle;
+    width: 20px;
+    cursor: pointer; 
+    font-size:22px;
+}
+.b2b-header-tabs .header__item.b2b-headermenu a.menu__item{
+	font-family:"Omnes-ECOMP-W02", Arial;
+}
+.b2b-header-tabs .header-tertiary li a{
+	display:inline;
+	padding-left: 3px;
+}
+.b2b-header-tabs .header-secondary .header-subitem.active .header-tertiary{
+	padding:20px;
+	font-size:15px;
+}
+
+.b2b-header-tabs .header__item.notification{
+	float:right;
+}
+
+.b2b-header-tabs .header__items{
+	width:90%;
+}
+
+.b2b-header-tabs .header-secondary, .b2b-header-tabs .header-tertiary{
+	width:100%;
+	
+}
+
+.third-level-menu{
+column-count: 4;
+    line-height: 12px;
+    max-height: 500px;
+    overflow-x: hidden;
+    overflow-y: hidden;
+    column-gap: 13px;
+    column-rule: 1px outset #d2d2d2;
+    margin-left:20px;
+}
+
+
+
+.third-level-menu a{
+ color:black;
+}
+
+.b2b-header-tabs .third-level-menu li a {
+    color: #333;
+    display: inline-grid;
+    padding: 7px 15px;
+    max-width: 228px;
+    font-family:"Omnes-ECOMP-W02", Arial;
+}
+
+.b2b-header-tabs .third-level-menu li{
+    display: inline-block;
+    width:100%;
+    border-bottom: 1px solid #d2d2d2;
+}
+
+
+.b2b-header-tabs .header-secondary .header-subitem a.menu__item{
+	font-size:16px;
+}
+
+.third-level-title{
+	font-size:15px;
+	font-weight: 700;
+}
+
+.notification-div{
+	width:15px;
+	font-size:23px;
+	cursor: pointer;
+}
+
+.notification-content{
+	line-height: normal;
+	right: 167px;
+	min-height: 122px;
+	height: auto;
+	width: auto; 
+}
+
+.header-user-icon{
+	color:white;
+	font-size:20px;
+	display:inline-block;
+}
+#reg-header-snippet .reg-profileDetails .reg-userEmail-value .reg-userEmail-value-spn,
+#reg-header-snippet .reg-profileDetails .reg-userRole-value .reg-userRole-value-spn,
+#reg-header-snippet .reg-profileDetails .reg-userLastLogin-value .reg-userLastLogin-value-spn,
+#reg-header-snippet .reg-profileDetails  .reg-userAppRoles-value .reg-userAppRoles-value-spn{
+	font-family: "Omnes-ECOMP-W02",Arial !important;
+}
+
+.reg-Details-table{
+	list-style: none;
+	border-bottom: 1px solid #bbb;
+	padding-bottom: 20px;
+}
+
+.reg-userName-table-cell{
+	font-weight:bold;
+	font-size:15px; 
+	line-height:1.6 !important;
+	
+}
+
+#header-favorites ul li{
+	width:100%;
+}
+
+.display-userAppRoles-label span{
+	font-family:"Omnes-ECOMP-W02", Arial;
+}
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/header/header.tpl.html b/ecomp-portal-FE-common/client/app/views/header/header.tpl.html
new file mode 100644
index 0000000..65a34ed
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/header/header.tpl.html
@@ -0,0 +1,266 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div style="position:fixed;width: 100%;top: 0px;left: 0;background-color: #222;z-index:9999">

+    <header class="b2b-header-tabs" b2b-header-responsive>

+        <ul class="header__items" role="navigation">

+<!-- Menu Icon and name -->

+            <li class="header__item icon__item" onclick="window.location = 'applicationsHome'">

+                <span id="logo-image"  class="icon-primary-att-globe"></span>

+                <span id="portal-title" class="portal-title" >OpenECOMP Portal</span>           	

+            </li>

+<!-- First Level menu -->

+            <li b2b-header-menu 

+            	id="megaMenu-{{item.text.split(' ').join('-')}}" 

+            	class="header__item b2b-headermenu" 

+            	ng-repeat="item in megaMenuDataObject" 

+            	ng-mousedown="loadFavorites(item.text)"  

+            	role="presentation">

+            	

+                <a href="javascript:void(0);" 

+                id="parentmenu-tabs"

+                class="menu__item" 

+                role="menuitem">{{item.text}}</a>

+                

+                <div class="header-secondary-wrapper" ng-if="item.active_yn=='Y'">

+                    <ul class="header-secondary" role="menu">

+<!-- Second Level menu -->

+                        <li class="header-subitem" 

+                        id="subItem-{{subItem.text.split(' ').join('-')}}"

+                        b2b-header-submenu 

+                        ng-repeat="i in item.children | orderBy : 'column'" 

+                        ng-mousemove="submenuLevelAction(i.text,i.column)" 

+                        role="presentation">

+           <!-- Favorites -->

+                        	<div ng-if="i.text=='Favorites'" >                       		

+                        		<a href="javascript:void(0);" class="menu__item" role="menuitem">{{i.text}}</a>                

+                        		<i id="favorite-star" data-size="large"  class="icon-star favorites-icon-active"></i>      		

+                        		<div class="header-columns-div" ng-show='favoritesWindow' ng-mouseleave="hideFavoritesWindow()" >

+                        			<div class="header-tertiary-wrapper" id="header-favorites">

+			                            <ul class="header-tertiary" role="menu">

+			                                <li role="presentation">

+			                                	<div                                                   

+                                                     ng-repeat="subItem in favoritesMenuItems"

+                                                     ng-show="showFavorites"

+                                                     ng-hide="hideMenus"

+                                                     id="favoritesMenuItems-{{subItem.text.split(' ').join('-')}}">

+                                                    <div class="fav-links">

+                                                        <i id="favorite-selector-favorites-list"

+                                                           class="icon-star favorites-icon-active"                                                       

+                                                           data-ng-click="removeAsFavoriteItem($event, subItem.menuId)"

+                                                           ng-mousedown="removeAsFavoriteItem($event, subItem.menuId)">

+                                                        </i>

+                                                        <a id="favorites-list" aria-label="{{subItem.text}}" ng-click="goToUrl(subItem)">{{subItem.text}}</a>

+                                                    </div>

+                                                </div>	

+                                                	                                	

+			                                    <div id="favorites-empty"  class="favorites-window-empty"   ng-show="emptyFavorites">

+			                                        <p id="p-no-favs-icon" class="no-fav-icon">

+			                                        	<span class="icon-star" ></span>

+			                                        </p>

+			                                        <p id="p-no-favs" class="largeText">No Favorites</p>

+			                                        <p id="p-no-favs-desc"  class="normal">Add your favorite items for quick access.</p>

+				                                </div>

+			                                </li>

+			                            </ul>

+		                            </div>

+                        		</div>             	

+                        	</div>

+            <!-- Support or Help -->

+                        	<div ng-if="item.text=='Support' || item.text=='Help'" id="second-level-menus-help">                      	

+                        		<a href="javascript:void(0);" ng-click="goToUrl(i);auditLog(i,'Support')" class="menu__item" role="menuitem">{{i.text| elipsis: 50}}</a>         	

+                        	</div>

+       		<!-- Others -->

+                        	<div ng-if="i.text!='Favorites' && (item.text!='Support' && item.text!='Help')" >

+                        		<a href="javascript:void(0);" class="menu__item" role="menuitem">{{i.text| elipsis: 50}}</a>

+	                            <div class="header-tertiary-wrapper" >

+		                            <ul class="third-level-menu"  role="menu" id="third-level-menus">

+<!-- Third Level menu -->	                 

+										

+			                                <li b2b-header-tertiarymenu ng-repeat="link in i.children | orderBy : 'column'" role="presentation" >

+			                                    <i id="level3-star-inactive-{{link.menuId}}" ng-cloak

+                                                    class="icon-star favorites-icon-inactive"  data-size="large"

+                                                    data-ng-click="setAsFavoriteItem($event, link.menuId)"

+                                                    ng-if="link.url.length > 1 && isUrlFavorite(link.menuId)==false">

+                                                </i>

+                                                <i id="level3-star-active-{{link.menuId}}" ng-cloak

+                                                   ng-if="link.url.length > 1 && isUrlFavorite(link.menuId)"

+                                                   class="icon-star favorites-icon-active ng-cloak"  data-size="large"

+                                                   data-ng-click="removeAsFavoriteItem($event, link.menuId)">

+                                                </i>                                              

+                                                

+			                                    <a class="third-level-title"

+                                                          aria-label="{{link.text | elipsis: 50}}"

+                                                          ng-click="goToUrl(link);auditLog(link,'application')">{{link.text| elipsis: 50}}</a>

+<!-- Fourth Level menu -->

+			                                    <div b2b-tertiary-link ng-repeat="title in link.children"  >

+			                                    	<i id="level4-star-inactive-{{title.menuId}}" ng-cloak

+		                                               class="icon-star favorites-icon-inactive"

+		                                               data-ng-click="setAsFavoriteItem($event, title.menuId)"

+		                                               ng-if="title.url.length > 1 && isUrlFavorite(title.menuId)==false">

+		                                            </i>

+		                                            <i id="level4-star-active-{{title.menuId}}" ng-cloak

+		                                               class="icon-star favorites-icon-active"

+		                                               data-ng-click="removeAsFavoriteItem($event, title.menuId)"

+		                                               ng-if="title.url.length > 1 && isUrlFavorite(title.menuId)">

+		                                            </i>

+			                                    	<a href="javascript:void(0);" class="header-tertiaryitem"  ng-class="{'disabled': title.disabled}" role="menuitem" ng-click="goToUrl(title);auditLog(title,'functional')">{{title.text | elipsis: 50}}</a>                                 	

+			                                    </div>

+			                                </li>	

+			                                

+			                                   

+		                              

+		                                                 

+		                            </ul>

+	                            </div>

+                        	</div>

+           

+                        </li>                        

+                    </ul>

+                </div>

+            </li>

+<!-- Right side of the Menu - User Icon and Notification flag -->

+            <div class="login-section">

+     <!-- User Icon -->

+            <li class="header__item profile" aria-haspopup="true">

+				<b2b-flyout>

+					<div b2b-flyout-toggler >

+						<div class="icon-people-oneperson" id="header-user-icon" tabindex="0" b2b-accessibility-click="13,32" aria-label="notifications" aria-haspopup="true" aria-expanded="{{flyoutOpened}}" role="button"></div>

+		          		<div id="login-snippet-text" class="login-snippet-text">{{header.isGuest ? 'Guest' : header.firstName}}</div>                  

+					</div>					

+		            <b2b-flyout-content horizontal-placement="center" vertical-placement="below">

+						<div  ng-controller="loginSnippetCtrl" >

+							<div id="reg-header-snippet">

+								<div tabindex="0" class="reg-profileDetails" id="reg-profiledetails-id">

+									<ul class="reg-Details-table">

+										<li>

+					                        <div class="reg-userName-table">

+					                            <div id="reg-userName-table-row">

+					                                <div id="reg-userName-table-cell">

+					                                    <h3 >

+					                                    {{firstName}} {{lastName}}&nbsp;</h3>

+					                                    <span>&nbsp;</span>

+					                                </div>

+					                            </div>

+					                        </div>

+					                    </li>

+										<li><div class="reg-userEmail-label"><span class="reg-userEmail-label-spn" style=font-weight:bold>Email<span class="visuallyhidden">:

+					                    </span></span></div></li>

+										<li><div class="reg-userEmail-value"><span class="reg-userEmail-value-spn">

+					                        {{loginSnippetEmail}}</span></div></li>

+					                    <li>&nbsp;</li>

+										<li><div class="reg-userRole-label"><span class="reg-userRole-label-spn" style=font-weight:bold>

+					                        User Id<span class="visuallyhidden">:</span></span></div></li>

+										<li><div class="reg-userRole-value"><span class="reg-userRole-value-spn">

+					                        {{loginSnippetUserid}}<span class="visuallyhidden"></span></span></div></li>

+					                    <li>&nbsp;</li>

+										<li><div class="reg-userLastLogin-label"><span class="reg-userLastLogin-label-spn" style=font-weight:bold>

+					                        Last login<span class="visuallyhidden">:</span></span></div></li>

+										<li><div class="reg-userLastLogin-value"><span class="reg-userLastLogin-value-spn">

+					                        {{lastLogin}}<span class="visuallyhidden"></span></span></div></li>

+						                <li>&nbsp;</li>

+					 					<li>

+					               			<div class="display-userAppRoles-label">

+					                			<a href="javascript:void(0);"  ng-click="getUserApplicationRoles()"  class="icon-controls-add-maximize" ><span>Applications and Roles</span></a>

+					                 		</div>

+					                        

+					                        <div class="display-userAppRoles-label" ng-show="displayUserAppRoles" style="height:200px; overflow-y:auto;">

+					                             <div ng-repeat="ua in userapproles track by  $index">

+				                                    <div class="reg-userApp-value">

+				                                    	<span class="reg-userApp-value-spn" style=font-weight:bold>{{ua.App}}<span class="visuallyhidden">:</span></span>

+													</div>							           

+					                                <div ng-repeat="role in ua.Roles track by  $index" class="reg-userAppRoles-value" >

+					                                	<span class="reg-userAppRoles-value-spn">{{role}}</span>

+					                                </div>

+					                        	</div>

+					                     	</div>

+					                	</li>

+									</ul>

+					                <div id="reg-logout-div" style="padding-top: 8px">

+					                      <button href="javascript:void(0)" id="allLogout" ng-click="allAppsLogout()" class="btn btn-alt btn-small">

+											Log out

+										</button>

+					                </div>

+						        </div>

+							</div>

+						</div>		

+		            </b2b-flyout-content>

+	       		</b2b-flyout>

+            </li> 

+    <!-- Notification flag -->

+            <li class="header__item notification" aria-haspopup="true" class="notification-li">

+            	<b2b-flyout>

+            		<div b2b-flyout-toggler class="notification-div">

+	            		<div class="notifications-count" ng-hide="notificationCount.count==0" ng-bind="notificationCount.count"></div>      	

+						<div  class="icon-content-flag megamenu-notification-overrides" class="b2b-flyout-icon" tabindex="0" b2b-accessibility-click="13,32" aria-label="notifications" aria-haspopup="true" aria-expanded="{{flyoutOpened}}" role="button"></div>

+		            </div>

+		            <b2b-flyout-content horizontal-placement="center" vertical-placement="below">

+						<div class="notification-content" ng-controller="notificationCtrl" >

+							<div class="ng-scope">

+								<div id="notification" class="notificationBox ">

+									<div align ="right">

+					 					 <a ui-sref="root.notificationHistory" style="font-size: 14px"> View All Recent Notifications </a>

+									</div>

+									<div class="notification-header">

+										<div style="float:left;">

+											<p class="notification-heading">Notifications</p>

+										</div>

+										<div style="clear:both;"></div>

+									</div>

+									<div ng-show="notifications.length==0">

+										<div class="notification-main">

+											<div style="height:113px;">

+												<div align="center" class="icon-information notification-info-icon"></div>

+											</div>

+										<div>

+										<p class="notification-text">No New Notifications.</p>

+									</div>

+									

+								</div>

+							</div>

+							<div class="notification-main" ng-show="notifications.length>0">

+								<ul class="notifications-list">

+									<li class="item" data-id="5" ng-repeat="item in notifications">

+										<div class="icon">

+											<span class="important" ng-show="item.priority==2"/>

+											<span class="normal" ng-show="item.priority==1"/>

+										</div>

+										<div class="details">

+											<span class="title" ng-bind="item.title"></span>

+											<span class="message-body" ng-bind="item.message"></span>

+											<!-- <span class="date" ng-bind="item.time" ></span> -->

+											<mydate>{{item.time | date:'MM/dd/yyyy hh:mm:ss a Z'}}</mydate>			

+										</div>

+										<button type="button" ng-click="deleteNotification($index)" class="button-default button-dismiss js-dismiss">x</button>

+									</li>

+								</ul>

+							</div>

+							<div class="notification-footer">

+								<div class="notification-links">

+									<div style="clear:both;"></div>

+								</div>

+							</div>

+						</div>					

+		            </b2b-flyout-content>

+		        </b2b-flyout>

+            </li>

+            </div>

+        </ul>

+	</header>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/microservice-onboarding/microservice-add-details/microservice-add-details.html b/ecomp-portal-FE-common/client/app/views/microservice-onboarding/microservice-add-details/microservice-add-details.html
new file mode 100644
index 0000000..a8b2073
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/microservice-onboarding/microservice-add-details/microservice-add-details.html
@@ -0,0 +1,205 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="microservice-add-details-model">

+	<div id="microservice-add-details-title" class="title">microservice Details</div>

+

+	<div id="microservices" class="microservice-scrolling-table">

+		<div class="microservice-properties-main"

+			scroll-top="microserviceAddDetails.scrollApi">

+			<form id="microservices-details-form" name="serviceForm" novalidate

+				autocomplete="off">

+				<div class="item required">

+					<div class="item-label">microservice Name</div>

+					<input id="microservice-details-input-name" class="table-search-field"

+						type="text" name="name" ng-pattern="/^[\w -]*$/" maxlength="100"

+						ng-model="microserviceAddDetails.service.name"

+						ng-change="microserviceAddDetails.updateServiceName()"

+						ng-disabled="microserviceAddDetails.isEditMode" required />

+

+					<div class="error-container"

+						ng-show="(microserviceAddDetails.emptyServiceName || serviceForm.name.$dirty)">

+						<div ng-messages="serviceForm.name.$error" class="error-container">

+							<small id="microservices-details-input-name-required"

+								class="err-message" ng-message="required">Microservice

+								Name is required</small> <small

+								id="microservices-details-input-name-pattern"

+								class="err-message" ng-message="pattern">Microservice

+								Name must be letters, numbers, or underscore</small>

+						</div>

+					</div>

+

+					<div class="error-container"

+						ng-show="microserviceAddDetails.isEditMode == false && microserviceAddDetails.dupliateName == true">

+						<small id="microservices-details-input-name-dupliated"

+							class="err-message">Name not available - choose different

+							name </small>

+					</div>

+				</div>

+

+				<div class="item required">

+					<div class="item-label">microservice Description</div>

+					<textarea b2b-reset b2b-reset-textarea id="widgets-details-input-desc"

+						name="desc" ng-model="microserviceAddDetails.service.desc"

+						ng-change="microserviceAddDetails.updateDesc()"></textarea>

+					<div class="error-container"

+						ng-show="(microserviceAddDetails.emptyServiceDesc 

+					|| (serviceForm.desc.$dirty && microserviceAddDetails.service.desc == ''))">

+						<small id="microservices-details-input-desc-required"

+							class="err-message">Microservice Description is required</small>

+					</div>

+				</div>

+

+

+

+				<div class="item"

+					ng-show="microserviceAddDetails.isEditMode && microserviceAddDetails.availableWidgets.length > 0">

+					<div class="item-label">Client Widgets</div>

+					<div ng-repeat="widget in microserviceAddDetails.availableWidgets">{{widget.name}}</div>

+				</div>

+

+				<div class="item required">

+					<div class="item-label">Application Name</div>

+					<div class="service-select">					

+						<div class="table-dropdown">

+							<select id="microservice-details-input-app" name="app" b2b-dropdown placeholder-text="Select Application" ng-model="microserviceAddDetails.service.application.name" ng-change="microserviceAddDetails.updateApp()">

+				            	<option b2b-dropdown-list option-repeat="d in microserviceAddDetails.availableApps" value="{{d.name}}">{{d.name}}</option>

+				            </select>

+						</div>

+					</div>

+

+					<div class="error-container"

+						ng-show="(microserviceAddDetails.emptyServiceApp 

+					|| (serviceForm.app.$dirty && microserviceAddDetails.service.application == null))">

+						<small id="microservices-details-input-url-required"

+							class="err-message">Please select microservice

+							Application</small>

+					</div>

+				</div>

+

+				<div class="item required">

+					<div class="item-label">microservice Endpoint URL</div>

+					<input id="microservice-details-input-endpoint-url"

+						class="table-search-field" ng-model="microserviceAddDetails.service.url"

+						ng-change="microserviceAddDetails.updateURL()" type="text"

+						name="url" maxlength="200" />

+

+					<div class="error-container"

+						ng-show="(microserviceAddDetails.emptyServiceURL 

+					|| (serviceForm.url.$dirty && microserviceAddDetails.service.url == ''))">

+						<small id="microservices-details-input-url-required"

+							class="err-message">Microservice Endpoint URL is required</small>

+					</div>

+				</div>

+

+				<div class="item required">

+					<div class="item-label">Security Type</div>

+					<div class="service-select">					

+						<div class="table-dropdown">

+							<select id="microservice-details-input-security-type" name="microservice-details-input-security-type" b2b-dropdown placeholder-text="Select Application" ng-model="microserviceAddDetails.service.security.name" ng-change="microserviceAddDetails.updateApp()">

+				            	<option b2b-dropdown-list option-repeat="d in microserviceAddDetails.availableSecurityTypes" value="{{d.name}}">{{d.name}}</option>				            	

+				            </select>

+						</div>

+					</div>				

+				</div>

+

+				<div class="item"

+					ng-show="microserviceAddDetails.service.security.id == 1">

+					<div class="item-label">Username</div>

+					<input id="microservice-details-input-username" class="table-search-field"

+						type="text" name="username" maxlength="100"

+						ng-model="microserviceAddDetails.service.username" />

+				</div>

+

+				<div class="item"

+					ng-show="microserviceAddDetails.service.security.id == 1">

+					<div class="item-label">Password</div>

+					<input id="microservice-details-input-password" class="table-search-field"

+						type="password" name="password" maxlength="100"

+						ng-model="microserviceAddDetails.service.password" />

+				</div>

+

+

+				<div class="item" ng-show="microserviceAddDetails.isEditMode">

+					<div class="left-test-item">

+						<div class="item-label">Test Microservice</div>

+					</div>

+					<div class="right-test-item">

+						<div id="microservice-details-test-button" class="test-button"

+							ng-click="microserviceAddDetails.testServiceURL()">Test</div>

+					</div>

+				</div>

+

+				<div class="item" ng-show="microserviceAddDetails.isEditMode">

+					<div class="item-label">JSON output</div>

+					<textarea id="microservice-details-input-json" class="json-field"

+						name="json"></textarea>

+				</div>

+

+				<div class="add-para-item">

+					<div class="item-label add-label-left">Add User Parameter</div>

+					<div class="icon-primary-accordion-plus"

+						ng-click="microserviceAddDetails.addParameter()"></div>

+				</div>

+				<div class="item">

+					<div class="para-label-item-left"

+						ng-show="microserviceAddDetails.service.parameterList.length > 0">

+						Parameter Key</div>

+					<div class="para-label-item-right"

+						ng-show="microserviceAddDetails.service.parameterList.length > 0">

+						Parameter Default Value</div>

+

+					<div id="microservice-details-user-paramters"

+						ng-repeat="parameter in microserviceAddDetails.service.parameterList">

+

+						<div class="para-item-left">

+							<input id="microservice-details-input-user-parameter-key"

+								class="table-search-field" type="text" name="param-key" maxlength="200"

+								ng-model="parameter.para_key" />

+						</div>

+						<div class="para-item-middle">

+							<input id="microservice-details-input-user-parameter-value"

+								class="table-search-field" type="text" name="param-value"

+								maxlength="200" ng-model="parameter.para_value" />

+						</div>

+

+						<div class="icon-primary-accordion-minus para-item-right"

+							ng-click="microserviceAddDetails.removeParamItem(parameter)"></div>

+					</div>

+

+					<div class="microservice-property">

+						<input id="microservices-checkbox-app-is-enabled" type="checkbox"

+							class="checkbox-field"

+							ng-model="microserviceAddDetails.service.active" />

+						<div class="property-label checkbox-label">Active</div>

+					</div>

+					

+				</div>

+					

+				<div id="microservice-scroll-end"></div>

+				<div class="dialog-control">

+					<button class="btn btn-alt btn-small" id="microservice-details-save-button" ng-click="microserviceAddDetails.saveChanges()">Save</button>				

+					<button class="btn btn-alt btn-small" id="microservice-details-close-button" ng-click="microserviceAddDetails.closeThisDialog()">Close</button>	

+				</div>

+			</form>

+		</div>

+

+

+	</div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/microservice-onboarding/microservice-add-details/microservice-add-details.js b/ecomp-portal-FE-common/client/app/views/microservice-onboarding/microservice-add-details/microservice-add-details.js
new file mode 100644
index 0000000..f612334
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/microservice-onboarding/microservice-add-details/microservice-add-details.js
@@ -0,0 +1,336 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+(function () {

+    class MicroserviceAddDetailsCtrl {

+        constructor($scope, $log, $interval, widgetsCatalogService, applicationsService, adminsService, microserviceService, errorMessageByCode, ECOMP_URL_REGEX, $window,userProfileService, confirmBoxService, $cookies) {

+     	    

+          

+            let getAvailableApps = () => {       

+            	applicationsService.getAppsForSuperAdminAndAccountAdmin().then(apps => {

+            		this.availableApps=[];

+            		apps.unshift({

+                        id: 1,

+                        name: "ECOMP Portal"

+                    });

+            		for(var i = 0; i < apps.length; i++) {

+                        this.availableApps.push({

+                            id: apps[i].id,

+                            name: apps[i].name

+                        });

+                        if(this.isEditMode == true && this.service.appId == apps[i].id){

+                        	this.service.application = this.availableApps[i];

+                        }

+                    }

+            	}).catch(err => {

+                    $log.error(err);

+                });

+            };

+            

+            let getAvailableWidgets = () => {       

+            	microserviceService.getWidgetListByService(this.service.id).then(widgets =>{

+            		this.availableWidgets = [];

+            		for(var i = 0; i < widgets.length; i++){

+            			this.availableWidgets.push({

+            				name: widgets[i]

+            			})

+            		}

+            	}).catch(err => {

+                    $log.error(err);

+                });

+            };

+            

+            

+            let getAvailableSecurityTypes = () => {   

+            	this.availableSecurityTypes = [];

+            	this.availableSecurityTypes.push({

+            		id: 0,

+            		name: 'No Authentication'

+            	});

+            	this.availableSecurityTypes.push({

+            		id: 1,

+            		name: 'Basic Authentication'

+            	});

+            	this.availableSecurityTypes.push({

+            		id: 2,

+            		name: 'Cookie based Authentication'

+            	});

+            }

+

+            let init = () => {

+            	$log.info('MicroserviceAddDetailsCtrl::init');

+                this.service = [];

+                this.service.parameterList = [];

+                this.service.active = true;

+                this.emptyServiceName = false;

+                this.emptyServiceDesc = false;

+                this.emptyServiceURL = false;

+                this.emptyServiceApp = false;

+                this.dupliateName = false;

+                this.serviceList = $scope.ngDialogData.list;

+                

+                if ($scope.ngDialogData && $scope.ngDialogData.service) {

+                	

+                    this.isEditMode = true;

+                    this.service = _.clone($scope.ngDialogData.service);

+                    

+                    console.log(this.service);

+                    if(this.service.active == 'Y')

+                    	this.service.active = true;

+                    else

+                    	this.service.active = false;

+                } else {

+                    this.isEditMode = false;

+                } 

+                getAvailableApps();

+                getAvailableSecurityTypes();

+                //getAvailableWidgets();

+                

+                /**

+				 * 0: Basic Authentication

+				 * 

+				 * TODO: change the structure

+				 */

+                if(this.service.securityType == "No Authentication"){

+                	this.service.security = this.availableSecurityTypes[0];

+                }else if(this.service.securityType == "Basic Authentication"){

+                	this.service.security = this.availableSecurityTypes[1];

+                }else if(this.service.securityType == "Cookie based Authentication"){

+                	this.service.security = this.availableSecurityTypes[2];

+                }

+            };

+             

+            this.ECOMP_URL_REGEX = ECOMP_URL_REGEX;

+            this.conflictMessages = {};

+            this.scrollApi = {};

+

+            let resetConflict = fieldName => {

+                delete this.conflictMessages[fieldName];

+                if($scope.widgetForm[fieldName]){

+                    $scope.widgetForm[fieldName].$setValidity('conflict', true);

+                }

+            };

+

+            this.addParameter = () => {

+            	document.getElementById('microservice-scroll-end').scrollIntoView();

+            	this.service.parameterList.push({}); 

+            }

+            

+            this.closeThisDialog = () => {

+            	$scope.closeThisDialog(true);

+            }

+            

+            this.removeParamItem = (parameter) => {

+            	microserviceService.getUserParameterById(parameter.id).then((res) => {

+            		if(res.length > 0){

+            			var message = res.length + " users have their own widget parameters. Are you sure you want to delete?";

+            			confirmBoxService.editItem(message).then(isConfirmed => {

+            				if(isConfirmed){

+            					microserviceService.deleteUserParameterById(parameter.id).then((res) => {

+	            					for(var i = 0; i < this.service.parameterList.length; i++){

+	            	            		if(this.service.parameterList[i].para_key == parameter.para_key

+	            	            		&& this.service.parameterList[i].para_value == parameter.para_value){

+	            	            			this.service.parameterList.splice(i, 1);

+	            	            			return;

+	            	            		}

+	            	            	}

+            					});

+            				}

+            			});

+            		}

+            		else{

+            			for(var i = 0; i < this.service.parameterList.length; i++){

+    	            		if(this.service.parameterList[i].para_key == parameter.para_key

+    	            		&& this.service.parameterList[i].para_value == parameter.para_value){

+    	            			this.service.parameterList.splice(i, 1);

+    	            			return;

+    	            		}

+    	            	}

+            		}

+            	});

+            	

+            	

+            }

+            

+            this.updateServiceName = () => {

+            	this.dupliateName = false;

+            	for(var i = 0; i < this.serviceList.length; i++){

+            		if(this.serviceList[i].name == this.service.name){

+            			this.dupliateName = true;

+            			return;

+            		}

+            	}

+            }

+            

+            this.updateDesc = () => {

+            	this.emptyServiceDesc = false;

+            }

+            

+            this.updateURL = () => {

+            	this.emptyServiceURL  = false;

+            }

+            

+            this.updateApp = () => {

+            	this.emptyServiceApp = false;

+            }

+            

+          //This is a fix for dropdown selection, due to b2b dropdown only update value field

+    		$scope.$watch('microserviceAddDetails.service.application.name', (newVal, oldVal) => {

+    			for(var i=0;i<$scope.microserviceAddDetails.availableApps.length;i++){ 			

+    				if($scope.microserviceAddDetails.availableApps[i].name==newVal){

+    					$scope.microserviceAddDetails.service.application=angular.copy($scope.microserviceAddDetails.availableApps[i]);

+    				}

+    			}

+    		});

+    		$scope.$watch('microserviceAddDetails.service.security.name', (newVal, oldVal) => {

+    			for(var i=0;i<$scope.microserviceAddDetails.availableSecurityTypes.length;i++){ 			

+    				if($scope.microserviceAddDetails.availableSecurityTypes[i].name==newVal){

+    					$scope.microserviceAddDetails.service.security=angular.copy($scope.microserviceAddDetails.availableSecurityTypes[i]);

+    				}

+    			}

+    		});

+            

+            let emptyCookies = () => {

+                userProfileService.getUserProfile()

+                .then(profile=> {

+                    $log.info('AppDetailsModalCtrl::emptyCookies profile: ', profile);

+                    $scope.attuid = profile.attuid;

+                    $log.info('user has the following attuid: ' + profile.attuid);

+                    if ($cookies.getObject($scope.attuid + '_widget') != undefined && $cookies.getObject($scope.attuid + '_widget') != null) {

+                        $cookies.remove($scope.attuid + '_widget');

+                    }

+                });

+            };

+            

+            this.testServiceURL = () =>{

+            	//console.log(this.service.id);

+            	widgetsCatalogService.getServiceJSON(this.service.id).then(res => {

+            		document.getElementById("microservice-details-input-json").innerHTML = (JSON.stringify(res));

+				});

+            }

+            

+            this.saveChanges = () => {     

+            	/* TODO: add form validation */

+            	

+            	var isValid = true;

+            	this.updateServiceName();

+            	

+            	if(this.service.name == ''

+                || this.service.name == undefined){

+            		this.emptyServiceName = true;

+            		isValid = false;

+            	}

+            	

+            	if(this.dupliateName == true

+            	&& this.isEditMode == false){

+            		isValid = false;

+            	}

+            		

+            	

+            	if(this.service.desc == ''

+            	|| this.service.desc == undefined){

+            		this.emptyServiceDesc = true;

+            		isValid = false;

+            	}

+            	

+            	if(this.service.url == ''

+               	|| this.service.url == undefined){

+              		this.emptyServiceURL = true;

+               		isValid = false;

+               	}

+            		

+            	if(this.service.application == undefined

+             	|| this.service.application == null){

+            		this.emptyServiceApp = true;

+            		isValid = false;

+            	}

+            	

+            	if(!isValid)

+            		return;

+            	

+            	/*

+				 * Check the parameter list, delete those parameters that don't

+				 * have key

+				 */

+            	for(var i = 0; i < this.service.parameterList.length; i++){

+            		if(this.service.parameterList[i].para_key == undefined

+            		|| this.service.parameterList[i].para_key == null

+            		|| this.service.parameterList[i].para_key == ""){

+            			this.service.parameterList.splice(i, 1);

+            			i--;

+            		}

+            	}

+      

+            	var securityType;

+            	var username;

+            	var password;

+            	if(this.service.security == undefined ||

+            	this.service.security == null)

+            		securityType = "No Authentication";

+            	else{

+            		securityType = this.service.security.name;

+            		username = this.service.username;

+            		password = this.service.password;

+            	}

+            	

+            	var active = 'N';

+            	if(this.service.active == true)

+            		active = 'Y';

+            	

+            	var newService = {

+            			name: this.service.name,

+            			desc: this.service.desc,

+            			appId: this.service.application.id,

+            			url: this.service.url,

+            			securityType: securityType,

+            			username: username,

+            			password: password,

+            			active: active,

+            			parameterList: this.service.parameterList

+            	};

+            	

+            	if(this.isEditMode){

+            		// console.log(this.service.parameterList);

+            		var message = "Are you sure you want to change '" + this.service.name + "'?"

+            		confirmBoxService.editItem(message).then(isConfirmed => {

+	            		if(isConfirmed){

+		            		microserviceService.updateService(this.service.id, newService).then(() => {

+		            			// TODO: result validation check

+		            			this.closeThisDialog(); 

+		            		});

+            			}

+            		});

+            	}else{

+            		microserviceService.createService(newService).then(() => {

+            			// TODO: result validation check

+            			$scope.closeThisDialog(true);

+            		});

+            	}

+             	

+            };

+            init();

+            $scope.$on('$stateChangeStart', e => {

+                e.preventDefault();

+            });

+        }

+    }

+    MicroserviceAddDetailsCtrl.$inject = ['$scope', '$log', '$interval', 'widgetsCatalogService', 'applicationsService', 'adminsService', 'microserviceService', 'errorMessageByCode', 'ECOMP_URL_REGEX', '$window','userProfileService', 'confirmBoxService', '$cookies'];

+    angular.module('ecompApp').controller('MicroserviceAddDetailsCtrl', MicroserviceAddDetailsCtrl);

+})(); 

diff --git a/ecomp-portal-FE-common/client/app/views/microservice-onboarding/microservice-add-details/microservice-add-details.less b/ecomp-portal-FE-common/client/app/views/microservice-onboarding/microservice-add-details/microservice-add-details.less
new file mode 100644
index 0000000..fa607fe
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/microservice-onboarding/microservice-add-details/microservice-add-details.less
@@ -0,0 +1,234 @@
+.microservice-add-details-model{
+	height: 550px;
+	overflow-y: auto;
+	.title {
+		.dGray18r;  //AT&T Dark Gray
+		border-bottom: @blue-active 3px solid;
+	}
+	.microservice-details-title {
+	    color: #191919;
+	    font-family: "Omnes-ECOMP-W02", Arial;;
+	    font-size: 24px;
+	    padding-bottom: 15px;
+	    padding-top:30px;
+	    margin-left: 25px;
+	}
+	.microservice-properties-main {
+	    padding-top: 20px;
+	    padding-left: 20px;
+	    padding-right: 20px;
+	    margin-bottom: 50px;
+	    
+	    height: 100%;
+	    width:100%;
+	    
+	    .microservice-property{
+	      margin-top: 10px;
+	      position: relative;
+	      .property-label{
+	        .dGray14r;
+	      }
+	      .checkbox-label{
+	        display: inline-block;
+	        padding-left: 3px;
+	      }
+	      .checkbox-field{
+	        padding: 0;
+	        margin: 0;
+	        vertical-align: middle;
+	        position: relative;
+	        top: -1px;
+	      } 
+	    }
+		.add-para-item{
+			position: relative;
+	        .add-label-left{
+	  	  	  line-height: 25px;
+	          height: 30px;
+	          vertical-align: middle;
+	          display:inline-block;
+	         
+	          margin-right: 10px;
+	          background: @portalWhite;
+			  .dGray14r;
+	  	   }
+	  	   .add-label-right{
+	  	   	  height: 14px;
+	  	   	  width: 14px;
+	          display:inline-block;
+	  	   }
+		}
+		.item{
+	      position: relative;
+	      margin-bottom: 15px;
+	      width:400px;
+	      .service-select{
+	 		  select{
+	 		  	cursor: pointer;
+			    position: relative;
+			    border: 1px solid #5a5a5a;
+			    white-space: nowrap;
+			    overflow: hidden;
+			    text-overflow: ellipsis;
+			    line-height: 15px;
+			    height: 32px;
+			    padding-left: 10px;
+			    padding-right: 10px;
+		      	border-radius: 0px;
+	 		  }
+	          display:inline-block;
+	          width: 100%;
+    	  }
+    	  .error-container{
+	        position: absolute;
+	        width: 280px;
+	        display: block;
+	        height: 12px;
+	        line-height: 12px;
+	
+	        .err-message{
+	          color: @funcRed;
+	          font-size: 9px;
+	        }
+	        .valid-message{
+	          color: @funcGreen;
+	          font-size: 9px;
+	        }
+	      }
+	      .auth-item-left{
+	          padding-top: 0;
+	          line-height: 30px;
+	          height: 30px;
+	          vertical-align: middle;
+	          display:inline-block;
+	          width: 15%;
+	          //border-radius: 2px;
+	          //border: 1px solid @portalLGray;
+	          margin-right: 10px;
+	          background: @portalWhite;
+	          white-space: nowrap;
+			  .dGray14r;
+       	  }
+          .auth-item-right{
+	          display:inline-block;
+	          width: 45%;
+	          border-radius: 2px;
+	          border: 1px solid @portalLGray;
+	          background: @portalWhite;
+	          vertical-align: middle;
+          }
+          .para-label-item-left{
+	          line-height: 30px;
+	          height: 30px;
+	          vertical-align: middle;
+	          display:inline-block;
+	          width: 45%;
+	          margin-right: 10px;
+	          background: @portalWhite;
+	          white-space: nowrap;
+			  .dGray14r;
+       	  }
+          .para-label-item-right{
+	          line-height: 30px;
+	          height: 30px;
+	          display:inline-block;
+	          width: 45%;
+	          background: @portalWhite;
+	          vertical-align: middle;
+	          white-space: nowrap;
+	          .dGray14r;
+          }
+           .para-item-left{
+	          line-height: 30px;
+	          height: 36px;
+	          margin:3px;
+	          vertical-align: middle;
+	          display:inline-block;
+	          width: 43%;
+	          border-radius: 2px;
+	          border: 1px solid @portalLGray;
+	          margin-right: 10px;
+	          background: @portalWhite;
+	          white-space: nowrap;
+			  .dGray14r;
+       	  }
+          .para-item-middle{
+	          line-height: 30px;
+	          height: 36px;
+	          margin:3px;
+	          display:inline-block;
+	          width: 43%;
+	          border-radius: 2px;
+	          border: 1px solid @portalLGray;
+	          background: @portalWhite;
+	          vertical-align: middle;
+	          white-space: nowrap;
+	          .dGray14r;
+          }
+          .json-field{
+		  	position: relative;
+	      	.custom-input-field;
+	      	height:80px;
+		  }
+           .left-test-item{
+	        display: inline-block;
+	        width: 85%;
+	        background: @portalWhite;
+	      }
+          
+          .right-test-item{
+	        position: relative;
+	        display: inline-block;
+	        width: 15%;
+	        float: right;
+	      }
+	      
+          .test-button {
+   			  .btn-blue;
+  	  	  }
+          .para-item-right{
+          	  margin-top: 10px;
+	          height: 14px;
+	          width: 14px;
+	          float: right;
+	          display:inline-block;
+          }
+	      .input-field{
+	        .custom-input-field;
+	        width: 100%; 
+	       }
+	       .textarea-field{
+	      	position: relative;
+	      	.custom-input-field;
+	      	height:50px;
+      	    }
+	       .submit-button {
+	       	 margin-top: 25px;
+	  	 	 position: relative;
+	  	 	 float: right;
+	  	 	 width: 20%;
+	   		.btn-blue;
+	  	  }
+	  	  
+	  	 
+	  	  
+	  	  .add-para-button{
+			height: 14px;
+	  	  	width: 14px;
+	  	  	float: right;
+	  	  }
+	  	  .item-label{
+        	.dGray14r;
+      	  }
+	    }
+	}
+}
+.microservice-scrolling-table{
+	width: 548px;
+    margin-left: 16px;
+    height: 430px;
+    overflow: auto;
+}
+#microservice-scroll-end{
+	height: 20px;
+} 
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/microservice-onboarding/microservice-onboarding.controller.js b/ecomp-portal-FE-common/client/app/views/microservice-onboarding/microservice-onboarding.controller.js
new file mode 100644
index 0000000..6e711b7
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/microservice-onboarding/microservice-onboarding.controller.js
@@ -0,0 +1,87 @@
+'use strict';
+(function () {
+    class MicroserviceOnboardingCtrl {
+        constructor($log, applicationsService, microserviceService, ngDialog, confirmBoxService,
+                    userProfileService, $cookies, $scope) {
+        	
+        	
+            let getOnboardingServices = () => {
+            	microserviceService.getServiceList().then(res => {
+                     $scope.serviceList = res;
+                }).catch(err => {
+                    $log.error('MicroserviceOnboardingCtrl::getOnboardingServices caught error', err);
+                });
+            };
+            
+            
+            
+            let init = () => {
+                $scope.serviceList = [];
+                getOnboardingServices();
+                this.serviceTableHeaders = [
+                    {name: 'Microservice Name', value: 'name', isSortable: false},
+                    {name: 'Service Endpoint URL', value: 'url', isSortable: false},
+                    {name: 'Security Type', value: 'securityType', isSortable: false}
+                ];
+            };
+            
+            this.openAddNewMicroserviceModal = (selectedService) => {
+            	let data = null; 
+				if(selectedService){
+					if(!selectedService.id){
+						$log.error('MicroserviceOnboardingCtrl:openAddNewMicroserviceModal:service id not found');
+						return; 
+					} 
+					data = { 
+						service:selectedService,
+						list: $scope.serviceList
+					}
+				}else{
+					data = {
+						list: $scope.serviceList	
+					}
+				}
+				
+                ngDialog.open({
+                    templateUrl: 'app/views/microservice-onboarding/microservice-add-details/microservice-add-details.html',
+                    controller: 'MicroserviceAddDetailsCtrl',
+                    controllerAs: 'microserviceAddDetails',
+                    data: data
+                }).closePromise.then(needUpdate => {
+                	if(needUpdate.value === true){
+                		getOnboardingServices();
+                    }
+                });
+            };
+                        
+            this.deleteService = service => { 
+    		   confirmBoxService.deleteItem(service.name).then(isConfirmed => {   
+               	if(isConfirmed){
+               			if(!service || !service.id){
+                           $log.error('MicroserviceOnboardingCtrl::deleteService: No service or ID... cannot delete');
+                           return;
+                       }
+                       microserviceService.deleteService(service.id).then((res) => {                    	   
+                    	   if(res.status == "WARN"){
+                    		   confirmBoxService.showInformation("Failed: widgets " +  res.response + " are assoicated with this microservice!");
+                    	   }else{
+                    		   $scope.serviceList.splice($scope.serviceList.indexOf(service), 1);
+                    	   }
+                       }).catch(err => {
+                           $log.error('MicroserviceOnboardingCtrl::deleteService error:',err);
+                       });
+                   }
+               }).catch(err => {
+                   $log.error('MicroserviceOnboardingCtrl::deleteService error:',err);
+               });
+
+                
+            };
+            
+            init();
+        }
+    }
+    MicroserviceOnboardingCtrl.$inject = ['$log', 'applicationsService', 'microserviceService', 'ngDialog', 'confirmBoxService',
+        'userProfileService','$cookies', '$scope'];
+    angular.module('ecompApp').controller('MicroserviceOnboardingCtrl', MicroserviceOnboardingCtrl);
+})();
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/microservice-onboarding/microservice-onboarding.less b/ecomp-portal-FE-common/client/app/views/microservice-onboarding/microservice-onboarding.less
new file mode 100644
index 0000000..7565b43
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/microservice-onboarding/microservice-onboarding.less
@@ -0,0 +1,26 @@
+.microservice-onboarding{
+  //.bg_portalWhite;//white for 1702
+  .bg_portalGray;  // gray for 1610
+  position: @page-main-position;
+  top: @page-main-top;
+  left: @page-main-left;
+  right: @page-main-right;
+  bottom: @page-main-bottom;
+  padding-top: @padding-top;
+  overflow-y: @page-main-overflow-y;
+  padding-left: @padding-left-side;
+
+    .microservices-table {
+  	  width: @table-width;
+ 	  margin: 0 auto;
+ 	  
+ 	  .add-button{
+ 	  	width: 180px;
+ 	  }
+    }
+
+	.delete-microservice{
+      .ico_trash_default;
+    }
+
+}
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/microservice-onboarding/microservice-onboarding.tpl.html b/ecomp-portal-FE-common/client/app/views/microservice-onboarding/microservice-onboarding.tpl.html
new file mode 100644
index 0000000..e3b3128
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/microservice-onboarding/microservice-onboarding.tpl.html
@@ -0,0 +1,75 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="w-ecomp-main">

+	<div class="w-ecomp-main-container">

+		<div class="microservice-onboarding" id="page-content">

+			<div id="microservice-onboarding-title"

+				class="w-ecomp-main-view-title">Microservice Onboarding</div>

+			<div class="microservices-table">

+				<div class="table-control">

+	                <div class="table-control-buttons">

+						<button class="btn btn-alt btn-small"  id="microservice-onboarding-button-add" ng-click="microserviceOnboarding.openAddNewMicroserviceModal()">

+		                	<i class="icon-people-userbookmark" aria-hidden="true"></i>&nbsp;Add Microservice

+		                </button> 

+					</div>

+					<div>

+						<div class="c-ecomp-b2b-abs-table default">

+							<table b2b-table table-data="serviceList"

+								search-string="microserviceOnboarding.searchString"

+								view-per-page="microserviceOnboarding.viewPerPageIgnored"

+								current-page="microserviceOnboarding.currentPageIgnored"

+								total-page="microserviceOnboarding.totalPageIgnored">

+								<thead b2b-table-row type="header">

+									<tr>

+										<th id="microservice-catalog-th-header-name"

+											ng-repeat="header in microserviceOnboarding.serviceTableHeaders"

+											b2b-table-header key="{{header.value}}"

+											sortable="{{header.isSortable}}">{{header.name}}</th>

+										<th id="microservices-catalog-th-header-delete"

+											b2b-table-header sortable="false">Delete</th>

+									</tr>

+								</thead>

+								<tbody b2b-table-row type="body" class="table-body"

+									row-repeat="rowData in serviceList">

+

+									<tr>

+										<td b2b-table-body

+											ng-repeat="header in microserviceOnboarding.serviceTableHeaders"

+											ng-click="microserviceOnboarding.openAddNewMicroserviceModal(rowData)">

+											<div

+												id="microservices-catalog-microservice-name-{{rowData.id}}"

+												ng-bind="rowData[header.value]"></div>

+										</td>

+

+										<td b2b-table-body>

+											<div id="microservice-onboarding-div-delete-{{$index}}"

+												class="icon-misc-trash"

+												ng-click="microserviceOnboarding.deleteService(rowData)"></div>

+										</td>

+									</tr>

+								</tbody>

+							</table>

+						</div>

+					</div>

+				</div>

+			</div>

+		</div>

+	</div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/notification-history/notificationhistory.controller.js b/ecomp-portal-FE-common/client/app/views/notification-history/notificationhistory.controller.js
new file mode 100644
index 0000000..609dba3
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/notification-history/notificationhistory.controller.js
@@ -0,0 +1,83 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+

+(function () {

+

+    class notificationHistoryCtrl {

+        constructor( $scope, $log,notificationService, confirmBoxService, $modal, ngDialog, $state) {

+        	

+        	var priorityItems={"1":"Normal","2":"Important"};

+        	$scope.priorityItems=priorityItems;

+            $scope.externalNotification="External System";

+        	$scope.isLoadingTable = false;

+            $scope.searchString = '';

+            $scope.notificationHistory = [];

+            let getNotificationHistory = () => {

+            	$scope.isLoadingTable = true;

+                notificationService.getNotificationHistory().then(res => {

+                    $scope.notificationHistory = res.data;

+                    $scope.isLoadingTable = false;

+                }).catch(err => {

+                    $log.error('notificationHistoryCtlr:notifSvc.getNotifHist failed: ', err);

+                    $scope.isLoadingTable = false;

+                });

+            }

+

+            getNotificationHistory();           

+        	

+            $scope.showDetailedJsonMessage=function (selectedAdminNotification) {

+				 var messageObject=JSON.parse(selectedAdminNotification.msgDescription);

+				 var html="";

+				 html+='<p>'+'Message Source'+' : '+selectedAdminNotification.msgSource+'</p>';

+				 html+='<p>'+'Message Title'+' : '+selectedAdminNotification.msgHeader+'</p>';

+				  for(var field in  messageObject){

+					 if(field=='eventDate'||field=='lastModifiedDate'){

+						 html+='<p>'+field+' : '+new Date(+messageObject[field])+'</p>';

+						  

+					 }else{

+					 html+='<p>'+field+' : '+messageObject[field]+'</p>';

+					 

+					 }

+				 }

+			

+		     var modalInstance = ngDialog.open({

+				    templateUrl: 'app/views/user-notifications-admin/user.notifications.Json.details.modal.page.html',

+				    controller: 'userNotificationCtrl',

+				    resolve: {

+				    	message: function () {

+				    		var message = {

+				    			   title:    '',

+		                       		text:    html

+		                       		

+		                           	};

+				          return message;

+				        },

+				     

+				      }

+				  }); 

+			

+				 

+			 };

+         }

+    }

+    notificationHistoryCtrl.$inject = ['$scope', '$log', 'notificationService', 'confirmBoxService', '$modal', 'ngDialog', '$state'];

+    angular.module('ecompApp').controller('notificationHistoryCtrl', notificationHistoryCtrl);

+})();

diff --git a/ecomp-portal-FE-common/client/app/views/notification-history/notificationhistory.less b/ecomp-portal-FE-common/client/app/views/notification-history/notificationhistory.less
new file mode 100644
index 0000000..9e1de6f
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/notification-history/notificationhistory.less
@@ -0,0 +1,41 @@
+.w-ecomp-user-notification-history {
+      .bg_portalWhite;//white for 1702
+    //.bg_portalGray;  // gray for 1610
+    position: @page-main-position;
+    top: @page-main-top;
+    left: @page-main-left;
+    right: @page-main-right;
+    bottom: @page-main-bottom;
+    padding-top: @padding-top;
+    overflow-y: @page-main-overflow-y;
+    padding-left: @padding-left-side;
+#input-search::-webkit-input-placeholder,
+{
+font-style: italic;
+  color:   #999999;
+
+}
+    .tab-bottom {
+        bottom: 0;
+    }
+    
+    .tablesorter-default .tablesorter-header .tablesorter-header-inner {
+	    background-position: center right;
+	    background-repeat: no-repeat;
+	    cursor: pointer;
+	    white-space: normal;
+	    display: inline-block;
+	    vertical-align: baseline;
+	    zoom: 1;
+	    padding: 12px 50px;
+    }
+    
+   .notifHistTable {
+        width: @table-width;
+        //margin-left: @table-margin-left;
+        //margin: @table-margin;
+		margin:auto;
+       }
+
+
+}
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/notification-history/notificationhistory.tpl.html b/ecomp-portal-FE-common/client/app/views/notification-history/notificationhistory.tpl.html
new file mode 100644
index 0000000..5cd56e9
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/notification-history/notificationhistory.tpl.html
@@ -0,0 +1,100 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="w-ecomp-user-notification-history"

+	ng-style="{bottom: tabBottom}">

+	<div class="user-notification-history" id="page-content">

+		<div id='app-title' class="w-ecomp-main-view-title">

+			<h1 class="heading-page" >Recent Notifications</h1>

+		</div>

+		<div class="notifHistTable">

+

+			<div class="table-control">

+				<div class="simulateCatGridHeaderDetails">This table shows

+					notifications published in the last 30 days.</div>

+				<div align="right">

+					<input class="table-search" type="text" id="input-search"

+						placeholder="Search in entire table" ng-model="searchString" />

+				</div>

+			</div>

+

+            <span class="ecomp-spinner" ng-show="isLoadingTable"></span>

+

+			<div id="table-main" class="b2b-table-div" ng-hide="isLoadingTable">

+				<table b2b-table id="notification-history-table"

+					table-data="notificationHistory"

+					search-string="searchString"

+					view-per-page="viewPerPagerIgnored"

+					current-page="ignoredCurrentPage"

+					total-page="totalPageIgnored">

+					<thead b2b-table-row type="header">

+						<tr>

+							<th id="th-notif-0" b2b-table-header key="msgSource"

+								sortable="true" style=" width: 10px;">Message Source</th>

+							<th id="th-notif-1" b2b-table-header key="msgHeader"

+								sortable="true">Message</th>

+							<th id="th-notif-2" b2b-table-header key="startTime"

+								sortable="true">Start Date (Local Time)</th>

+							<th id="th-notif-3" b2b-table-header key="endTime"

+								sortable="true">End Date  (Local Time)</th>

+							<th id="th-notif-4" b2b-table-header key="priority"

+								sortable="true">Priority</th>

+							<th id="th-notif-5" b2b-table-header key="loginId"

+								sortable="true">Created By</th>

+							<th id="th-notif-6" b2b-table-header key="createdDate"

+								sortable="true">Created Time</th>

+						</tr>

+					</thead>

+

+					<tbody b2b-table-row 

+						type="body"

+						class="table-body"

+						track-by="$index"

+						row-repeat="rowData in notificationHistory">

+						<tr id="row-{{$index}}">

+								<td b2b-table-body style="{{rowData.expired?'color:lightgray !important':''}}">

+								<div id="{{$index}}-msgSource">{{rowData.msgSource}}</div>

+							</td>

+								<td class="td-first" b2b-table-body style="{{rowData.expired?'color:lightgray !important':''}}"  ng-click="rowData.msgSource=='EP'||showDetailedJsonMessage(rowData)">

+								<div id="{{$index}}-title" style="font-weight: bold;" ng-bind="rowData.msgHeader"></div>

+								<div id="{{$index}}-message" ng-if="rowData.msgSource==='EP'"  style="width:500px" ng-bind="rowData.msgDescription"></div>

+								<div id="{{$index}}-message" ng-if="rowData.msgSource!=='EP'"  ng-bind="rowData.msgDescription| elipsis: 27"></div>

+							</td>

+							<td b2b-table-body style="{{rowData.expired?'color:lightgray !important':''}}">

+								<div id="{{$index}}-startTime" ng-bind="rowData.startTime |	date:'medium'"></div>

+							</td>

+							<td b2b-table-body style="{{rowData.expired?'color:lightgray !important':''}}">

+								<div id="{{$index}}-endTime" ng-bind="rowData.endTime |	date:'medium'"></div>

+							</td>

+							<td b2b-table-body style="{{rowData.expired?'color:lightgray !important':''}}">

+								<div id="{{$index}}-priority" ng-bind="priorityItems[rowData.priority]"></div>

+							</td>

+							<td b2b-table-body style="{{rowData.expired?'color:lightgray !important':''}}">

+								<div id="{{$index}}-loginId" ng-bind="!rowData.loginId ? externalNotification : rowData.loginId"></div>

+							</td>

+							<td b2b-table-body style="{{rowData.expired?'color:lightgray !important':''}}">

+								<div id="{{$index}}-createdDate" ng-bind="rowData.createdDate | date:'medium'"></div>

+							</td>

+						</tr>

+					</tbody>

+				</table>

+			</div>

+		</div>

+	</div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/portal-admin/new-portal-admin/new-portal-admin.controller.js b/ecomp-portal-FE-common/client/app/views/portal-admin/new-portal-admin/new-portal-admin.controller.js
new file mode 100644
index 0000000..54575d3
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/portal-admin/new-portal-admin/new-portal-admin.controller.js
@@ -0,0 +1,95 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+/**

+ * Created by nnaffar on 12/8/15.

+ */

+'use strict';

+(function () {

+    class NewPortalAdminModalCtrl {

+        constructor($log, portalAdminsService, $scope, confirmBoxService) {

+

+            let init = () => {

+                this.isSaving = false;

+                /* istanbul ignore if */

+                if($scope.ngDialogData && $scope.ngDialogData.selectedUser && $scope.ngDialogData.dialogState){

+                    this.selectedUser = $scope.ngDialogData.selectedUser;

+                    this.dialogState = $scope.ngDialogData.dialogState;

+                }else{

+                    this.selectedUser = null;

+                    this.dialogState = 1;

+                }

+                //this.searchUsersInProgress = false;

+                //this.showNewAdminAppDropdown = false;

+                $log.info('NewPortalAdminModalCtrl:: initiated');

+            };

+

+            this.addNewPortalAdmin = () => {

+                confirmBoxService.makeAdminChanges('Are you sure you want to add "' + this.selectedUser.firstName + ' ' + this.selectedUser.lastName + '" as a Portal Admin?')

+                    .then(isConfirmed => {

+                        if(isConfirmed) {

+                            if (!this.selectedUser || !this.selectedUser.orgUserId) {

+                                $log.error('NewPortalAdminModalCtrl::makeAdminChanges: No portal admin or ID... cannot add');

+                                return;

+                            }

+                            portalAdminsService.addPortalAdmin(this.selectedUser.orgUserId)

+                                .then(() => {

+                                    $log.debug("NewPortalAdminModalCtrl::addNewPortalAdmin: portal admin added successfully");

+                                    $scope.closeThisDialog(true);

+                                }).catch(err => {

+                                    if(err.status === 409) {    //Conflict

+                                        confirmBoxService.showInformation('This user already exists as a portal admin!').then(function (isConfirmed) {

+                                            $scope.closeThisDialog(true);

+                                        });

+                                    } else {

+                                        confirmBoxService.showInformation('There was a unknown problem adding the portal admin. ' + 'Please try again later. Error Status: '+ err.status).then(function (isConfirmed)  {

+                                            $scope.closeThisDialog(true);

+                                        });

+                                    }

+                            });

+                        }

+                    }).catch(err => {

+                        confirmBoxService.showInformation('There was a unknown problem adding the portal admin. ' + 'Please try again later. Error Status: '+ err.status).then(function (isConfirmed)  {

+                            $scope.closeThisDialog(true);

+                        });

+                        $log.error('portalAdminsService.addPortalAdmin error status: '+ err.status);

+                });

+            };

+

+            /**

+             * this function set the selected user

+             * @param user: selected user object

+             */

+            this.setSelectedUser = (user) => {

+                $log.debug('NewPortalAdminModalCtrl::setSelectedUser: selected user: ', user);

+                this.selectedUser = user;

+            };

+

+            init();

+

+            $scope.$on('$stateChangeStart', e => {

+                //Disable navigation when modal is opened

+                //**Nabil - note: this will cause the history back state to be replaced with current state

+                e.preventDefault();

+            });

+        }

+    }

+    NewPortalAdminModalCtrl.$inject = ['$log', 'portalAdminsService', '$scope', 'confirmBoxService'];

+    angular.module('ecompApp').controller('NewPortalAdminModalCtrl', NewPortalAdminModalCtrl);

+})();

diff --git a/ecomp-portal-FE-common/client/app/views/portal-admin/new-portal-admin/new-portal-admin.controller.spec.js b/ecomp-portal-FE-common/client/app/views/portal-admin/new-portal-admin/new-portal-admin.controller.spec.js
new file mode 100644
index 0000000..3841a2b
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/portal-admin/new-portal-admin/new-portal-admin.controller.spec.js
@@ -0,0 +1,19 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

diff --git a/ecomp-portal-FE-common/client/app/views/portal-admin/new-portal-admin/new-portal-admin.modal.html b/ecomp-portal-FE-common/client/app/views/portal-admin/new-portal-admin/new-portal-admin.modal.html
new file mode 100644
index 0000000..d328db8
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/portal-admin/new-portal-admin/new-portal-admin.modal.html
@@ -0,0 +1,36 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="new-admin-modal">

+

+    <div class="search-users">

+

+        <search-users search-title="Add New Portal Admin"

+                      selected-user="newPortalAdmin.selectedUser"></search-users>

+

+        <div class="dialog-control">

+            

+               <button id="pa-search-users-button-save" class="btn btn-alt btn-small"

+                 ng-click="newPortalAdmin.selectedUser && newPortalAdmin.addNewPortalAdmin()"

+                 ng-class="{disabled: !newPortalAdmin.selectedUser}">Save

+            </button>

+            <button id="pa-search-users-button-cancel" class="btn btn-alt btn-small" ng-click="closeThisDialog()">Cancel</button>

+        </div>

+    </div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/portal-admin/new-portal-admin/new-portal-admin.modal.less b/ecomp-portal-FE-common/client/app/views/portal-admin/new-portal-admin/new-portal-admin.modal.less
new file mode 100644
index 0000000..f8e1960
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/portal-admin/new-portal-admin/new-portal-admin.modal.less
@@ -0,0 +1,80 @@
+.new-portal-admin-modal {
+  height: 430px;
+
+  .search-users {
+  }
+
+  .admin-app-roles {
+    .title {
+      //.n18r;
+      .dGray18r;  //AT&T Dark Gray
+      border-bottom: @blue-active 3px solid;
+
+    }
+
+    .app-roles-main {
+      margin-top: 16px;
+      .app-roles-main-title {
+        .dGray14r;
+        margin-bottom: 8px;
+        .left {
+          display: inline-block;
+        }
+        .right {
+          display: inline-block;
+          color: @portalDBlue;
+          float: right;
+          cursor: pointer;
+        }
+      }
+
+      .select-input{
+        width: 460px;
+      }
+
+      .new-administrated-app {
+        height: 30px;
+        line-height: 30px;
+
+        border: 1px solid @portalGray;
+        margin-bottom: 8px;
+        border-radius: 2px;
+        padding-left: 6px;
+        padding-top: 0;
+         width: 100%;
+        .dGray14r;
+      }
+
+      .admin-roles-list {
+        height: 240px;
+        overflow-y: auto;
+      }
+
+      .administrated-application {
+        width: 460px;
+        height: 30px;
+        border: 1px solid @portalGray;
+        margin-bottom: 8px;
+        border-radius: 2px;
+        padding: 6px;
+        .dGray14r;
+        display: inline-block;
+
+      }
+
+      .delete-application {
+        .ico_trash_default;
+        display: inline-block;
+        vertical-align: 4px;
+        cursor: pointer;
+        position: relative;
+        top: 6px;
+        color: transparent;
+        margin-left: 8px;
+      }
+
+    }
+
+  }
+}
+
diff --git a/ecomp-portal-FE-common/client/app/views/portal-admin/portal-admin-controller.js b/ecomp-portal-FE-common/client/app/views/portal-admin/portal-admin-controller.js
new file mode 100644
index 0000000..0051e70
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/portal-admin/portal-admin-controller.js
@@ -0,0 +1,110 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+(function () {

+    class PortalAdminsCtrl {

+        constructor($log, portalAdminsService, ngDialog, confirmBoxService) {

+

+            let updateTableData = () => {

+                this.isLoadingTable = true;

+                portalAdminsService.getPortalAdmins().then(result=> {

+                    $log.debug('PortalAdminsCtrl::updateTableData: result: ' + JSON.stringify(result));

+                    if (!result || !result.length) {

+                        $log.info('PortalAdminsCtrl::updateTableData: no Portal Admins err handling');

+                        this.portalAdminsTableData = [];

+                        return;

+                    }

+                    this.portalAdminsTableData = result;

+                }).catch(err=> {

+                    $log.error('PortalAdminsCtrl::updateTableData error :',err);

+                    confirmBoxService.showInformation('There was a problem retrieving the portal admins. ' +

+                        'Please try again later. Error: ' + err.status).then(isConfirmed => {});

+

+                }).finally(() => {

+                    this.isLoadingTable = false;

+                });

+            };

+

+            let init = () => {

+                $log.info('portalAdminsService.getPortalAdmins::initializing...');

+                this.isLoadingTable = false;

+

+                /*Table general configuration params*/

+                this.searchString= '';

+                /*Table data*/

+                this.portalAdminsTableHeaders = ['First Name', 'Last Name', 'User ID', 'Delete'];

+                this.portalAdminsTableData = [];

+                updateTableData();

+            };

+

+            init();

+

+            this.removePortalAdmin = pAdmin => {

+                $log.debug('PortalAdminsCtrl::removePortalAdmin: pAdmin = ' + JSON.stringify(pAdmin));

+                confirmBoxService.deleteItem(pAdmin.firstName + ' ' + pAdmin.lastName )

+                    .then(isConfirmed => {

+                    if(isConfirmed){

+                        if(!pAdmin || !pAdmin.userId){

+                            $log.error('PortalAdminsCtrl::removePortalAdmin No portal admin or ID... cannot delete');

+                            return;

+                        }

+                        portalAdminsService.removePortalAdmin(pAdmin.userId,pAdmin.loginId).then(() => {

+                            $log.info("PortalAdminsCtrl::removePortalAdmin removed admin");

+                            init();

+                        }).catch(err => {

+                            $log.error('PortalAdminsCtrl::removePortalAdmin.deleteItem error: '+ err);

+                            confirmBoxService.showInformation('There was a problem deleting this portal admins. ' +

+                                'Please try again later. Error: ' + err.status).then(isConfirmed => {});

+                        });

+                    }

+                }).catch(err => {

+                    $log.error('PortalAdminsCtrl::removePortalAdmin.deleteItem error: '+ err);

+                });

+            };

+            

+            this.openAddNewPortalAdminModal = (user) => {

+                let data = null;

+                if(user){

+                    data = {

+                        dialogState: 2,

+                        selectedUser:{

+                            orgUserId: user.orgUserId,

+                            firstName: user.firstName,

+                            lastName: user.lastName

+                        }

+                    }

+                }

+                ngDialog.open({

+                    templateUrl: 'app/views/portal-admin/new-portal-admin/new-portal-admin.modal.html',

+                    controller: 'NewPortalAdminModalCtrl',

+                    controllerAs: 'newPortalAdmin',

+                    data: data

+                }).closePromise.then(needUpdate => {

+                    if(needUpdate.value === true){

+                        $log.debug('PortalAdminsCtrl::openAddNewPortalAdminModal: updating Portal Admin table data...');

+                        updateTableData();

+                    }

+                });

+            };

+        }

+    }

+    PortalAdminsCtrl.$inject = ['$log', 'portalAdminsService', 'ngDialog', 'confirmBoxService'];

+    angular.module('ecompApp').controller('PortalAdminsCtrl', PortalAdminsCtrl);

+})();

diff --git a/ecomp-portal-FE-common/client/app/views/portal-admin/portal-admin.tpl.html b/ecomp-portal-FE-common/client/app/views/portal-admin/portal-admin.tpl.html
new file mode 100644
index 0000000..43f830c
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/portal-admin/portal-admin.tpl.html
@@ -0,0 +1,64 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="w-ecomp-main">

+    <div class="w-ecomp-main-container" >

+        <div class="portal-admins-page-main" id="page-content" >

+            <div id="title" class="w-ecomp-main-view-title">

+            	 <h1 class="heading-page" >Portal Admins</h1>

+            </div>

+            <div class="portal-admins-table">

+                <div class="table-control">

+	                <div class="table-control-fields">

+						<input id="input-table-search" placeholder="Search in entire table" class="table-search-field" type="text" data-ng-model="portalAdmin.searchString">

+					</div>

+					<div class="table-control-buttons">

+						<button class="btn btn-alt btn-small" ng-click="portalAdmin.openAddNewPortalAdminModal()"><i class="icon-people-userbookmark" aria-hidden="true"></i>&nbsp;Add Portal Admin</button> 		

+					</div>      

+                </div>

+

+                <span class="ecomp-spinner" ng-show="portalAdmin.isLoadingTable"></span>

+                

+                <div b2b-table table-data="portalAdmin.portalAdminsTableData"  ng-hide="portalAdmin.isLoadingTable"	search-string="portalAdmin.searchString" class="b2b-table-div">

+					<table>

+						<thead b2b-table-row type="header">

+							<tr >

+								<th b2b-table-header key="firstName" sortable="true" id="col1">First Name</th>

+								<th b2b-table-header key="lastName" sortable="true" id="col2">Last Name</th>

+								<th b2b-table-header key="loginId" sortable="true" id="col3">User ID</th>

+								<th b2b-table-header key="" sortable="falses" id="col4">Delete</th>

+							</tr>

+						</thead>

+						<tbody b2b-table-row type="body" 	row-repeat="rowData in portalAdmin.portalAdminsTableData">

+							<tr>

+								<td b2b-table-body id="rowheader_t1_{{$index}}" headers="col1" ng-bind="rowData.firstName"></td>

+								<td b2b-table-body headers="rowheader_t1_{{$index}} col2" ng-bind="rowData.lastName"></td>

+								<td b2b-table-body headers="rowheader_t1_{{$index}} col3" ng-bind="rowData.loginId"></td>

+								<td b2b-table-body headers="rowheader_t1_{{$index}} col4">

+									<span class="icon-misc-trash" ng-click="portalAdmin.removePortalAdmin(rowData)"></span>

+								</td>							

+							</tr>

+						</tbody>

+					</table>

+				</div>

+            </div>

+        </div>

+    </div>

+

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/portal-admin/portal-admins.less b/ecomp-portal-FE-common/client/app/views/portal-admin/portal-admins.less
new file mode 100644
index 0000000..0471aee
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/portal-admin/portal-admins.less
@@ -0,0 +1,42 @@
+.portal-admins-page-main {
+  .bg_portalWhite;//white for 1702
+  position: @page-main-position;
+  top: @page-main-top;
+  left: @page-main-left;
+  right: @page-main-right;
+  bottom: @page-main-bottom;
+  padding-top: @padding-top;
+  overflow-y: @page-main-overflow-y;
+  padding-left: @padding-left-side;
+
+#input-table-search::-webkit-input-placeholder,
+{
+font-style: italic;
+  color:   #D7D7D7;
+
+}
+  .portal-admins-table {
+    width: @table-width;
+    //margin-left: @table-margin-left;
+	margin:auto;
+    .table-control {
+      .table-dropdown-filter{
+        width: @table-dropdown-filter-width;
+        display: @table-dropdown-filter-display;
+      }
+    }
+
+    .table-body {
+      cursor: pointer;
+    }
+  }
+
+  .delete-user{
+    .ico_trash_default;
+  }
+
+  .portal-add-button {
+    width: 160px;
+  }
+}
+
diff --git a/ecomp-portal-FE-common/client/app/views/role/popup_modal_rolefunction.html b/ecomp-portal-FE-common/client/app/views/role/popup_modal_rolefunction.html
new file mode 100644
index 0000000..3970960
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/role/popup_modal_rolefunction.html
@@ -0,0 +1,55 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+

+<div class="new-admin-modal" style="height: 350px;">

+	<form name="adminForm" novalidate>

+		<div class="title" ng-bind="label" style="color: #5a5a5a;font-size: 18px;border-bottom: #067ab4 3px solid;margin-left:0px"></div>

+

+		<div id="name-property-title" class="property required">

+			<div id="name-property-label" class="property-label" style="margin-bottom: 5px;    color: #5a5a5a;       font-size: 14px;    font-weight: bold;    padding-top: 20px;">

+				<sup><b style="color: Red;">*</b></sup>Name:</div>

+			<input id="name-property-input" type="text" class="input-field" style=" display: inline-block; width: 396px; height: 40px;"ng-model="editRoleFunction.name" maxlength="30" />

+			<div id="name-property-required" ng-show="!editRoleFunction.name||editRoleFunction.name.length==0" style="color: #cf2a2a; font-size: 10px;"><small style="position: absolute;">Name is Required</small>

+				</div>

+		</div>

+		<br />

+		<div id="code-property-title" class="property required">

+			<div id="code-property-label" class="property-label" style="    margin-bottom: 5px;    color: #5a5a5a;       font-size: 14px;    font-weight: bold;    padding-top: 20px;">

+				<sup><b style="color: Red;">*</b></sup>Code:

+			</div>

+		   <input id="code-property-input" type="text" class="input-field" style=" display: inline-block; width: 396px; height: 40px;"

+				ng-model="editRoleFunction.code" ng-disabled="disableCd"

+				maxlength="30" />

+				<div id="code-property-required" ng-show="!editRoleFunction.code||editRoleFunction.code.length==0" style="color: #cf2a2a; font-size: 10px;"><small style="position: absolute;">Code is Required</small>

+				</div>

+		</div>

+		

+

+

+	<div class="dialog-control">

+				

+					<button id="button-app-save" class="save-button" size="small" ng-disabled="(!editRoleFunction.name||editRoleFunction.name.length==0)||(!editRoleFunction.code||editRoleFunction.code.length==0 )" ng-click="!saveRoleFunction(editRoleFunction)";herf="javascript:void(0)">Save</button>

+						

+						<div id="button-app-cancel" class="cancel-button" ng-click="closeThisDialog()" role="button" tabindex="0" herf="javascript:void(0)">Cancel</div>

+		</div>

+	</form>

+</div>

+

+

diff --git a/ecomp-portal-FE-common/client/app/views/role/role-controller.js b/ecomp-portal-FE-common/client/app/views/role/role-controller.js
new file mode 100644
index 0000000..c5c7c97
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/role/role-controller.js
@@ -0,0 +1,219 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+

+app.controller('roleController', function ($scope, $http, confirmBoxService, ngDialog, RoleService, conf, $stateParams){

+	//$scope.role=${role};

+		

+	$( "#dialogRoleFunction" ).hide();

+	$( "#dialogChildRole" ).hide();

+	

+	//$scope.ociavailableRoleFunctions=${availableRoleFunctions};

+	$scope.fetchRoles = function() {

+	

+	RoleService.getRole($stateParams.roleId).then(function(data){

+		

+		var j = data;

+  		$scope.data = JSON.parse(j.data);

+  		

+  		$scope.role =JSON.parse($scope.data.role);

+  		

+  		$scope.ociavailableRoleFunctions =JSON.parse($scope.data.availableRoleFunctions);

+  		$scope.availableRoleFunctions=[];

+  		

+  		if($scope.ociavailableRoleFunctions)

+  			$.each($scope.ociavailableRoleFunctions, function(i, a){ 

+  				var availableRoleFunction = a;

+  				availableRoleFunction.selected = false;

+  			    $.each($scope.role.roleFunctions, function(j, b){ 

+  			    	if(a.code === b.code) {

+  			    		availableRoleFunction.selected = true;

+  			    	}

+  			    });

+  			    $scope.availableRoleFunctions.push(availableRoleFunction);	    

+  		});	

+  		

+

+  		$scope.ociavailableRoles=JSON.parse($scope.data.availableRoles);

+  		$scope.availableRoles=[];

+  		

+  		if($scope.ociavailableRoles)

+  			$.each($scope.ociavailableRoles, function(i, a){ 

+  				var availableRole = a;

+  				availableRole.selected = false;

+  				if($scope.role.childRoles){

+  			    $.each($scope.role.childRoles, function(j, b){ 

+  			    	if(a.id === b.id) {

+  			    		availableRole.selected = true;

+  			    	}

+  			    });

+  				};

+  			    $scope.availableRoles.push(availableRole);	    

+  		});

+  			

+	

+	},function(error){

+		console.log("RoleService.getRole failed", error);

+		//reloadPageOnce();

+	});

+	}

+	

+	$scope.fetchRoles();

+

+	$scope.saveRole = function() {

+				var exists = false,x;	

+				for(x in $scope.availableRoles){

+					if($scope.availableRoles[x].name==$scope.role.name){

+						exists = true;

+						//$modalInstance.close({availableRoleFunctions:message.availableRoleFunctions});

+					}

+				}

+				if (exists) {

+					confirmBoxService.showInformation( "Role already exists.");

+				}

+				else {

+					var uuu = conf.api.saveRole + "?role_id="+$stateParams.roleId;

+					var postData = {

+							role: $scope.role, 

+							childRoles: $scope.role.childRoles,

+							roleFunctions : $scope.role.roleFunctions

+					};

+					$http.post(uuu, JSON.stringify(postData)).then(function(res) {

+						// console.log('roleController::saveRole: ' + JSON.stringify(res));

+						if (res && res.data && res.data.role)

+							confirmBoxService.showInformation("Update Successful.");

+						else

+							confirmBoxService.showInformation('Failed to create role: ' + res.data.error)

+					},

+					function(res){

+						console.log('post failed', res.data);

+						confirmBoxService.showInformation("Error while saving.");

+					}

+					);

+				}

+			};

+		

+	$scope.addNewRoleFunctionModalPopup = function() {

+			var modalInstance = ngDialog.open({

+			    templateUrl: 'app/views/role/role_functions_popup.html',

+			    controller: 'rolepopupController',

+			    

+			    resolve: {

+			    	roleId: function () {

+				          return $stateParams.roleId;

+				        },

+			    	role: function () {

+			          return $scope.role;

+			        },

+			        availableRoles: function () {

+				          return $scope.ociavailableRoles;

+				    },

+				    availableRoleFunctions: function () {

+				          return $scope.ociavailableRoleFunctions;

+				    },

+			      }

+			  });

+			 modalInstance.closePromise.then(response => {

+					if($stateParams.roleId === '0'){

+						return $scope.role;

+					}else{

+						$scope.fetchRoles();

+					}

+	           // $scope.role=response.role;

+	        });

+	};

+		

+	 $scope.addNewChildRoleModalPopup = function() {

+			var modalInstance = ngDialog.open({

+			    templateUrl: 'app/views/role/role_childrole_popup.html',

+			    controller: 'rolepopupController',

+			  

+			    resolve: {

+			    	roleId: function () {

+				          return $stateParams.roleId;

+				        },

+			    	role: function () {

+			          return $scope.role;

+			        },

+			        availableRoles: function () {

+				          return $scope.ociavailableRoles;

+				    },

+				    availableRoleFunctions: function () {

+				          return $scope.ociavailableRoleFunctions;

+				    },

+			      }

+			  }).closePromise.then(function(response){

+					if($stateParams.roleId === '0'){

+						 return $scope.role;

+					}else{

+			            $scope.fetchRoles();

+					}

+	            //$scope.role=response.role;

+	        });

+		};

+		

+		$scope.removeRoleFunction = function(roleFunction) {

+			confirmBoxService.confirm("You are about to remove the role function "+roleFunction.name+" from the role for "+$scope.role.name+". Do you want to continue?").then(

+	    			function(confirmed){

+						var uuu = conf.api.toggleRoleRoleFunction + "?role_id=" + $stateParams.roleId;

+						  var postData={roleFunction:roleFunction};

+						  	if(confirmed) {	

+								$http.post(uuu, postData).then(

+										function(response) {

+											$scope.role= response.data.role;

+											$.each($scope.availableRoleFunctions, function(k, c){ 

+							  			    	if(c.code === roleFunction.code) {

+							  			    		c.selected = false;

+							  			    	}

+							  			    });

+										}, 

+										function(response) {

+											confirmBoxService.showInformation("Error while saving.");

+										}

+								);									

+								}

+				

+	    	});

+		

+		};

+		

+		$scope.removeChildRole = function(childRole) {

+			confirmBoxService.confirm("You are about to remove the child role "+childRole.name+" from the role for "+$scope.role.name+". Do you want to continue?").then(

+	    			function(confirmed){

+					var uuu = conf.api.toggleRoleChildRole + "?role_id=" + $stateParams.roleId;

+					  var postData={childRole:childRole};

+					  if(confirmed) {

+							  $http.post(uuu,postData).then( function(response) {

+								  $scope.role=response.data.role;

+								  $.each($scope.availableRoles, function(k, c){ 

+					  			    	if(c.id === childRole.id) {

+					  			    		c.selected = false;

+					  			    	}

+					  			    });

+								  },

+								  

+								  function(data) {

+									  confirmBoxService.showInformation("Error while saving.");

+								  });

+						}				

+	    	});

+			

+		};

+		

+});

diff --git a/ecomp-portal-FE-common/client/app/views/role/role-function-list-controller.js b/ecomp-portal-FE-common/client/app/views/role/role-function-list-controller.js
new file mode 100644
index 0000000..c7a1bfd
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/role/role-function-list-controller.js
@@ -0,0 +1,160 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+app.controller('roleFunctionListController', function ($scope,RoleService,$http,$state,conf,confirmBoxService, ngDialog){

+	$( "#dialog" ).hide();

+	

+	RoleService.getRoleFunctionList().then(function(data){

+		

+		var j = data;

+  		$scope.data = JSON.parse(j.data);

+  		$scope.availableRoleFunctions =JSON.parse($scope.data.availableRoleFunctions);

+  		

+  		//$scope.resetMenu();

+	

+	},function(error){

+		console.log("failed");

+		//reloadPageOnce();

+	});

+	

+	$scope.editRoleFunction = null;

+	var dialog = null;

+	$scope.editRoleFunctionPopup = function(availableRoleFunction) {

+		$scope.editRoleFunction = availableRoleFunction;

+		$( "#dialog" ).dialog({

+		      modal: true

+	    });

+	};

+	

+	$scope.editRoleFunctionModalPopup = function(availableRoleFunction) {

+		$scope.editRoleFunction = availableRoleFunction;

+		$scope.availableRoleFunctionsTemp=$scope.availableRoleFunctions;

+		//$scope.availableRoleFunctions={};

+		var modalInstance = ngDialog.open({

+		    templateUrl: 'app/views/role/popup_modal_rolefunction.html',

+		    controller: 'rolefunctionpopupController',

+		    resolve: {

+		    	message: function () {

+		    		var message = {

+		    				availableRoleFunction:  $scope.editRoleFunction,

+		    				availableRoleFunctions: $scope.availableRoleFunctionsTemp

+                           	};

+		          return message;

+		        },

+		        isEditing: function () {

+		        	return true;

+		        }

+		      }

+		  }); 

+		modalInstance.closePromise.then(response =>{

+			if(response.value!=null){

+            	if(response.value.result){

+            		$scope.availableRoleFunctions=response.value.availableRoleFunctions;	

+            	}

+            }            	

+            /*else

+            	$scope.availableRoleFunctions=$scope.availableRoleFunctionsTemp;

+            */

+        });

+	};

+	

+	$scope.addNewRoleFunctionModalPopup = function(availableRoleFunction) {

+		

+		$scope.editRoleFunction = null;

+		$scope.availableRoleFunctionsTemp=$scope.availableRoleFunctions;

+		//$scope.availableRoleFunctions={};

+		var modalInstance = ngDialog.open({

+		    templateUrl: 'app/views/role/popup_modal_rolefunction.html',

+		    controller: 'rolefunctionpopupController',

+		    resolve: {

+		    	message: function () {

+		    		var message = {

+		    				availableRoleFunction: $scope.editRoleFunction,

+		    				availableRoleFunctions: $scope.availableRoleFunctionsTemp

+                           	};

+		          return message;

+		        },

+		        isEditing: function () {

+		        	return false;

+		        }

+		      }

+		  });

+		modalInstance.closePromise.then(response => {

+            if(response.value!=null){

+            	if(response.value.result){

+            		$scope.availableRoleFunctions=response.value.availableRoleFunctions;	

+            	}

+            }

+           /* if(response.availableRoleFunctions != undefined)

+            	$scope.availableRoleFunctions=response.availableRoleFunctions;

+            else

+            	$scope.availableRoleFunctions=$scope.availableRoleFunctionsTemp;

+            	*/

+        });

+	};

+	

+	$scope.addNewRoleFunctionPopup = function() {

+		$scope.editRoleFunction = null;

+		$( "#dialog" ).dialog({

+		      modal: true

+	    });

+	};

+	

+	$scope.saveRoleFunction = function(availableRoleFunction) {

+		  var uuu = conf.api.saveRoleFuncion;

+		  var postData={availableRoleFunction: availableRoleFunction};

+		  $http.post(uuu,postData).then(function(response) {

+			  var data = response.data;

+	  		$scope.availableRoleFunctions=data.availableRoleFunctions; 

+	  		$scope.editRoleFunction = null;

+	  	  },	  	  

+	  	function() {

+	  		

+	  		confirmBoxService.showInformation("Error while saving");

+	  	  }

+	  	  );

+		};

+	

+		

+		$scope.removeRole = function(availableRoleFunction) {

+			confirmBoxService.confirm("You are about to delete the role function "+availableRoleFunction.name+". Do you want to continue?").then(

+	    			function(confirmed){

+	    				if(confirmed){

+							$scope.availableRoleFunctionsTemp=$scope.availableRoleFunctions;

+							//$scope.availableRoleFunctions={};

+						  var uuu = conf.api.removeRoleFunction;

+						  var postData={availableRoleFunction: availableRoleFunction};

+					  	  $http.post(uuu,postData).then(function(response) {

+					  		  var data = response.data;

+					  		  if(data.availableRoleFunctions == undefined)

+					  			confirmBoxService.showInformation("Error while deleting: "+ data);

+					  		  else 

+					  			  $scope.availableRoleFunctions=data.availableRoleFunctions; 

+					  	  },

+					  	function() {

+					  		$scope.availableRoleFunctions=$scope.availableRoleFunctionsTemp;

+					  		confirmBoxService.showInformation("Error while deleting: "+ data.responseText);

+					  	  }

+					  	  );

+	    				}			

+	    			});

+			

+		};

+

+});

diff --git a/ecomp-portal-FE-common/client/app/views/role/role-list-controller.js b/ecomp-portal-FE-common/client/app/views/role/role-list-controller.js
new file mode 100644
index 0000000..5ef3716
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/role/role-list-controller.js
@@ -0,0 +1,147 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+app.controller('roleListController', function ($scope,RoleService,confirmBoxService,conf,$state,$http){

+	$scope.showSpinner = true;

+	

+	RoleService.getRoles().then(function(data){

+		$scope.showSpinner = true;

+		var j = data;

+  		$scope.data = JSON.parse(j.data);

+  		$scope.availableRoles =JSON.parse($scope.data.availableRoles);

+  		$scope.showSpinner = false;

+  		//$scope.resetMenu();

+	

+	},function(error){

+		console.log("failed");

+		//reloadPageOnce();

+	});

+	

+	

+		$scope.goToUrl = function(roleIdVal) {

+			$state.go("root.role", {"roleId":roleIdVal});

+		}	

+	//console.log($scope.availableRoles);

+		$scope.toggleRole = function(selected,availableRole) {

+				//alert('toggleRole: '+selected);

+				var toggleType = null;

+				if(selected) {

+					toggleType = "activate";

+				} else {

+					toggleType = "inactivate";

+				}

+				

+				confirmBoxService.confirm("You are about to "+toggleType+" the role "+availableRole.name+". Do you want to continue?").then(

+		    			function(confirmed){

+		    				

+		    				 if(confirmed) {

+			                    var uuu = conf.api.toggleRole;

+								

+								var postData={role:availableRole};

+								$http.post(uuu, postData).then(function(response) {

+									var data = response.data;

+									if (typeof data === 'object') {

+										console.log(data);

+							  			$scope.availableRoles=data.availableRoles; 

+							  			console.log($scope.availableRoles);

+									} else {

+										//

+									}

+

+								}, function(response) {

+									console.log(response.data);

+									availableRole.active=!availableRole.active;

+									confirmBoxService.showInformation("Error while saving.");

+								});

+								

+								/*

+							  	  $.ajax({

+							  		 type : 'POST',

+							  		 url : uuu,

+							  		 dataType: 'json',

+							  		 contentType: 'application/json',

+							  		 data: JSON.stringify(postData),

+							  		 success : function(data){

+							  			console.log(data);

+							  			$scope.$apply(function(){$scope.availableRoles=data.availableRoles;}); 

+							  			console.log($scope.availableRoles);

+									 },

+									 error : function(data){

+										 console.log(data);

+										 availableRole.active=!availableRole.active;

+										 confirmBoxService.showInformation("Error while saving.");

+									 }

+							  	  });

+							  	  */

+		    				 }

+		    				 else {

+		    					 availableRole.active=!availableRole.active;

+		    				 }

+					

+		    	});

+		    	//,

+		    	//function(){

+		    //		availableRole.active=!availableRole.active;

+		    	//})

+				

+				  

+		};

+

+		$scope.removeRole = function(role) {

+			

+			confirmBoxService.confirm("You are about to delete the role "+role.name+". Do you want to continue?").then(

+	    			function(confirmed){

+							var uuu = conf.api.removeRole;

+							  var postData={role:role};

+						  /*	  $.ajax({

+						  		 type : 'POST',

+						  		 url : uuu,

+						  		 dataType: 'json',

+						  		 contentType: 'application/json',

+						  		 data: JSON.stringify(postData),

+						  		 success : function(data){

+						  			$scope.$apply(function(){$scope.availableRoles=data.availableRoles;});  

+								 },

+								 error : function(data){

+									 console.log(data);

+									 confirmBoxService.showInformation("Error while deleting: "+ data.responseText);

+								 }

+						  	  }); */

+							  

+							  

+							  $http.post(uuu, postData).then(function(response) {

+									var data = response.data;

+									if (typeof data === 'object') {

+							  			$scope.availableRoles=data.availableRoles; 

+									} else {

+										//

+									}

+

+								}, function(response) {

+									console.log(response.data);

+									confirmBoxService.showInformation("Error while deleting: "+ data.responseText);

+								});

+				

+	    	});

+	    	

+			

+		};

+		

+

+});

diff --git a/ecomp-portal-FE-common/client/app/views/role/role.html b/ecomp-portal-FE-common/client/app/views/role/role.html
new file mode 100644
index 0000000..42793ee
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/role/role.html
@@ -0,0 +1,96 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="w-ecomp-admins-page-main">

+	<div class="admins-home-container" id="page-content">

+	 	<div id="title" class="w-ecomp-main-view-title">

+	    	<h3 class="heading-page" >Role</h3>

+	    </div>	

+	    <div  class="admins-table" >

+			<div>

+				<br>

+			    <p class="p-info">Please edit the role details below:&nbsp;</p><br>

+			     

+				<div id="role-text" >

+					<label id="role-text-name-label" >*Name:</label>

+					<input id="input-name"  class="input-field" maxlength="30"  type="text" data-ng-model="role.name">					 

+					<br>

+					<label id="role-text-priority-label">Priority:</label>

+					<input id="input-priority" class="input-field" maxlength="30"  type="text" data-ng-model="role.priority">					 	

+				</div>

+				<br>

+				<div align="left" >

+						<button id="button-role-save" type="submit" ng-click="saveRole();" class="btn btn-alt btn-small">Save</button>

+				</div>

+				

+				<br>

+				<div id="page-title"  class="pageTitle">

+					<label>Role Functions</label>

+					<a id="add-new-role" ng-click="addNewRoleFunctionModalPopup();" ng-style="{'cursor':'pointer'}" class="icon-primary-accordion-plus" size="small"></a>

+				</div>

+			

+				<div b2b-table table-data="role.roleFunctions"  ng-hide="users.isLoadingTable"	search-string="users.searchString" class="b2b-table-div">

+					<table>

+						<thead b2b-table-row type="header">

+							<tr >

+								<th id="table-header-name" b2b-table-header key="name" sortable="true" >Name</th>

+								<th id="table-header-remove" b2b-table-header sortable="false" >Remove</th>

+							</tr>

+						</thead>

+						<tbody b2b-table-row type="body" 	row-repeat="roleFunction in role.roleFunctions">

+							<tr >

+								<td id="role-function-{{roleFunction.name}}" b2b-table-body id="rowheader_t1_{{$index}}" headers="col1" ng-bind="roleFunction.name"></td>

+								<td b2b-table-body headers="rowheader_t1_{{$index}} col4">

+					    			<div ng-click="removeRoleFunction(roleFunction);" ><a href="javascript:void(0)" class="icon-misc-trash"></a></div>

+								</td>							

+							</tr>

+						</tbody>

+					</table>

+				</div>		

+				

+				<a id="manage-role"  href="roleFunctions">Manage Role Functions</a><br><br>

+				

+				<div id="page-title-child" class="pageTitle">

+					<label>Child Roles</label>

+					<a id="add-child-role"  ng-click="addNewChildRoleModalPopup();" ng-style="{'cursor':'pointer'}" class="icon-primary-accordion-plus" size="small"></a>

+				</div>

+				

+				<div b2b-table table-data="role.childRoles"  ng-hide="users.isLoadingTable"	search-string="users.searchString" class="b2b-table-div">

+					<table>

+						<thead b2b-table-row type="header">

+							<tr >

+								<th id="table-header-name" b2b-table-header key="firstName" sortable="true" >Name</th>

+								<th id="table-header-remove" b2b-table-header key="lastName" sortable="true" >Remove</th>

+							</tr>

+						</thead>

+						<tbody b2b-table-row type="body" 	row-repeat="role in role.childRoles">

+							<tr >

+								<td id="role-function-{{role.name}}" b2b-table-body id="rowheader_t1_{{$index}}" headers="col1" ng-bind="role.name"></td>

+								<td b2b-table-body headers="rowheader_t1_{{$index}} col4">

+					    			<div ng-click="removeChildRole(role);" ><a href="javascript:void(0)" class="icon-misc-trash"></a></div>

+								</td>							

+							</tr>

+						</tbody>

+					</table>

+				</div>					

+			</div>

+	

+		</div>

+	</div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/role/role.less b/ecomp-portal-FE-common/client/app/views/role/role.less
new file mode 100644
index 0000000..42311e6
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/role/role.less
@@ -0,0 +1,50 @@
+.p-info {

+	font-size: 18px;

+}

+

+.input-field {

+	width: 250px !important;

+}

+

+.scrolling-table {

+	width: 548px;

+	margin-left: 16px;

+	height:575px;

+	overflow:auto;

+}

+

+.scrolling-table .scroll-viewport {

+	height: 200px !important;

+	width: 99.5% !important;

+	background-color: white;

+}

+

+.scrolling-table .scroll-overview {

+	margin-top: -14px !important;

+}

+

+.scrolling-table .scroll-viewport:hover {

+	background-color: white;

+}

+

+.scrolling-table #portal-scroll-table-content {

+	height: 200px;

+	position: absolute !important;

+	width: 548px;

+	padding-left: 0px;

+	padding-top: 0px;

+	padding-bottom: 0px;

+	padding-right: 5px;

+}

+

+.vertical .scroll-thumb {

+	width: 13px !important;

+}

+

+.vertical .scroll-bar {

+	width: 15px !important;

+}

+

+.scroll-bar {

+	border-radius: 0px !important;

+}
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/role/role_childrole_popup.html b/ecomp-portal-FE-common/client/app/views/role/role_childrole_popup.html
new file mode 100644
index 0000000..278ef2c
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/role/role_childrole_popup.html
@@ -0,0 +1,51 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed 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.

+  ================================================================================

+  -->

+<link rel="stylesheet" href="role.css">

+<div class="application-details-modal">

+    <div id="title" class="title">Select Role Functions</div>

+<div class="scrolling-table">

+

+	<div b2b-table table-data="availableRoles"   class="b2b-table-div">

+		<table>

+			<thead b2b-table-row type="header">

+				<tr >

+					<th b2b-table-header key="firstName" sortable="true" id="col1"></th>

+					<th b2b-table-header key="name" sortable="true" id="col2">Name</th>

+				</tr>

+			</thead>

+			<tbody b2b-table-row type="body" 	row-repeat="rowData in availableRoles">

+				<tr>

+					<td b2b-table-body id="rowheader_t1_{{$index}}" headers="col1">

+						<label class="btn-switch-label" tabindex="0" role="option">

+							<input type="checkbox" b2b-switches ng-model="rowData.selected" ng-click="toggleChildRole(rowData.selected,rowData);">

+					    </label> 

+					</td>

+					<td b2b-table-body headers="rowheader_t1_{{$index}} col2" ng-bind="rowData.name"></td>

+											

+				</tr>

+			</tbody>

+		</table>

+	</div>

+</div>

+    <div class="dialog-control">

+        <button id="button-app-cancel" class="btn btn-alt btn-small" ng-click="closeThisDialog()">Close</button>      

+    </div>

+</div>

+

diff --git a/ecomp-portal-FE-common/client/app/views/role/role_function_list.html b/ecomp-portal-FE-common/client/app/views/role/role_function_list.html
new file mode 100644
index 0000000..c015ee5
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/role/role_function_list.html
@@ -0,0 +1,107 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed 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.

+  ================================================================================

+  -->

+<style>

+.c-ecomp-abs-table .tablesorter-default tbody tr td div {

+  line-height: 20px;

+}

+

+.c-ecomp-abs-table{

+    width: 100%;

+    height: 600px;

+    overflow-x: auto;

+    overflow-y: auto;

+	background-color: white;

+}

+</style>

+<div class="w-ecomp-admins-page-main">

+	<div class="admins-home-container" id="page-content">	

+    <div id="title" class="w-ecomp-main-view-title">

+    <h3 class="heading-page" >Role Functions</h3>

+    </div>

+	        

+	

+	<br>

+	

+	<div class="admins-table">

+	

+	 <!-- <a ng-click="addNewRoleFunctionModalPopup();" class="icon-add" size="small" ></a> -->

+	        <div id="create-new-admin" align="left" style="marin-bottom: 50px;">

+				<button id="button-admin-create" type="submit" ng-click="addNewRoleFunctionModalPopup();" 

+					 class="btn btn-alt btn-small">Create</button>

+			</div>

+			

+			<div style="text-align: justify;text-align-last:auto;">

+			<!-- 	Click on the edit icon to update a role function, the plus icon to add additional role functions, or the delete icon to remove them.  -->

+			</div>

+	

+	<div id="rolesTable" class="c-ecomp-abs-table default"  title="Role Functions">

+	  <table b2b-table table-data="availableRoleFunctions" current-page="1">

+	  	<thead b2b-table-row type="header">

+	  		<tr>

+	  			<th id="table-header-name" b2b-table-header width="70%" key="name" sortable="true">Name</th>

+	  			<th id="table-header-code" b2b-table-header width="10%" key="code" sortable="true">Code</th>

+	  			<th id="table-header-edit" b2b-table-header width="10%" sortable="false">Edit?</th>

+	  			<th id="table-header-delete" b2b-table-header width="10%" sortable="false">Delete?</th>

+	  		</tr>

+	  	</thead>

+	  	<tbody b2b-table-row type="body" row-repeat="availableRoleFunction in availableRoleFunctions" style="max-height: 980px;" ><!-- background colors will alternate not properly with multiple tbody-->

+		  <tr>

+		    <td id="table-body-role-{{availableRoleFunction['name'].split(' ').join('-')}}" b2b-table-body width="70%" ng-bind="availableRoleFunction['name']"></td>

+		    <td id="table-body-role-{{availableRoleFunction['code']}}" b2b-table-body width="10%" ng-bind="availableRoleFunction['code']"></td>

+		    <td id="table-body" b2b-table-body width="10%">

+		    <!-- <a ng-click="editRoleFunctionPopup(availableRoleFunction);" >

+		    <img src="static/fusion/images/editicon.gif">

+		    </a> -->

+		    <div ng-click="editRoleFunctionModalPopup(availableRoleFunction);" style="font-size:20px;"><a id="role-{{availableRoleFunction['name'].split(' ').join('-')}}-edit" href="javascript:void(0)" class="icon-edit"></a></div>

+		    </td>

+		     <td b2b-table-body width="10%">

+		    	<!-- <a ng-click="removeRole(availableRoleFunction);" ><img src="static/fusion/images/deleteicon.gif"></a> -->

+		    	<div ng-click="removeRole(availableRoleFunction);" style="font-size:20px;"><a id="role-{{availableRoleFunction['name'].split(' ').join('-')}}-trash" href="javascript:void(0)" class="icon-misc-trash"></a></div>

+		     </td>

+		  </tr>

+		</tbody>

+		</table>

+	</div>

+	

+		   

+	

+	</div>

+	

+	

+	<div id="dialog" title="Add Role Function">

+	   	

+	   	<div id="fn-ebz-container-name" class="fn-ebz-container" >

+		<label id="fn-ebz-label-name" class="fn-ebz-text-label"><sup><b>*</b></sup>Name:</label><br>

+		<input id="fn-ebz-input-{{editRoleFunction.name}}" type="text" class="fn-ebz-text" ng-model="editRoleFunction.name"

+			maxlength="30" /> 

+		</div>

+	   	<br/>

+	   	<div id="fn-ebz-container-code" class="fn-ebz-container" >

+		<label id="fn-ebz-label-code" class="fn-ebz-text-label"><sup><b>*</b></sup>Code:</label><br>

+		<input id="fn-ebz-input-{{editRoleFunction.code}}" type="text" class="fn-ebz-text" ng-model="editRoleFunction.code" ng-disabled="editRoleFunction.code!=null"

+			maxlength="30" /> 

+		</div>

+		<br/>

+		<button id="button-admin-save" type="submit" ng-click="saveRoleFunction(editRoleFunction);"  class="btn btn-alt btn-small">Save</button>

+		

+	</div>

+	

+</div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/role/role_functions_popup.html b/ecomp-portal-FE-common/client/app/views/role/role_functions_popup.html
new file mode 100644
index 0000000..7d80d81
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/role/role_functions_popup.html
@@ -0,0 +1,49 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed 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.

+  ================================================================================

+  -->

+<link rel="stylesheet" href="role.css">

+<div class="application-details-modal">

+    <div id="title" class="title">Select Role Functions</div>

+<div class="scrolling-table">

+	<div b2b-table table-data="availableRoleFunctions"   class="b2b-table-div">

+		<table>

+			<thead b2b-table-row type="header">

+				<tr >

+					<th b2b-table-header key="firstName" sortable="true" id="col1"></th>

+					<th b2b-table-header key="name" sortable="true" id="col2">Name</th>

+				</tr>

+			</thead>

+			<tbody b2b-table-row type="body" 	row-repeat="rowData in availableRoleFunctions">

+				<tr>

+					<td b2b-table-body id="rowheader_t1_{{$index}}" headers="col1">

+						<label class="btn-switch-label" tabindex="0" role="option">

+							<input type="checkbox" b2b-switches ng-model="rowData.selected" ng-click="toggleRoleFunction(rowData.selected,rowData);">

+					    </label> 

+					</td>

+					<td b2b-table-body headers="rowheader_t1_{{$index}} col2" ng-bind="rowData.name"></td>

+											

+				</tr>

+			</tbody>

+		</table>

+	</div>

+</div>

+    <div class="dialog-control">

+        <button id="button-app-cancel" class="btn btn-alt btn-small" ng-click="closeThisDialog()">Close</button>

+    </div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/role/role_list.html b/ecomp-portal-FE-common/client/app/views/role/role_list.html
new file mode 100644
index 0000000..e8d56ca
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/role/role_list.html
@@ -0,0 +1,55 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="w-ecomp-admins-page-main">

+	<div class="admins-home-container" id="page-content">

+		<div id="title" class="w-ecomp-main-view-title">

+			<h1 class="heading-page" >Roles</h1>

+		</div>

+		<div class="admins-table">

+			<div id="button-create-role" align="left" style="marin-bottom: 50px;" >

+				<button ng-click="goToUrl(0)" class = "btn btn-alt btn-small">Create</button>

+			</div>

+			<div b2b-table table-data="availableRoles"  search-string="searchString" class="b2b-table-div">

+		        <table>

+		            <thead b2b-table-row type="header">

+		                <tr>

+		                    <th b2b-table-header key="name" sortable="true" default-sort="a" id="col1">Name</th>

+		                    <th b2b-table-header key="priority" sortable="true" id="col2">Priority</th>

+		                    <th b2b-table-header id="col3" sortable="false">Active</th>

+		                </tr>

+		            </thead>

+		            <tbody b2b-table-row type="body" row-repeat="rowData in availableRoles">

+		                <tr>

+		                    <td b2b-table-body id="rowheader_t1_{{$index}}" headers="col1" ng-bind="rowData['name']"></td>

+		                    <td b2b-table-body ng-bind="rowData['priority']"></td>

+		                    <td b2b-table-body headers="rowheader_t1_{{$index}} col3" >

+		                    	<div >

+						    		<label class="btn-switch-label" tabindex="0" role="option">

+										<input type="checkbox" b2b-switches ng-model="rowData.active" ng-click="toggleRole(rowData.active,rowData);">

+								    </label> 

+						    	</div>

+		                    </td>

+		                </tr>

+		            </tbody>

+		        </table>

+		    </div>

+		</div>

+	</div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/role/rolefunctionpopupController.js b/ecomp-portal-FE-common/client/app/views/role/rolefunctionpopupController.js
new file mode 100644
index 0000000..6275c76
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/role/rolefunctionpopupController.js
@@ -0,0 +1,95 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+app.controller('rolefunctionpopupController', function ($scope, confirmBoxService, message, $http,RoleService, conf, isEditing){

+				if(message.availableRoleFunction==null) {

+					$scope.label='Add Role Function';

+					var tempText = "";

+				}

+				else{

+					$scope.label='Edit Role Function'

+					$scope.disableCd=true;

+					var tempText = new String(message.availableRoleFunction.name);

+					$scope.editRoleFunction = angular.copy(message.availableRoleFunction);

+				}

+				

+				$scope.tempText = tempText;

+				$scope.isEditing = isEditing;

+				

+				$scope.saveRoleFunction = function(availableRoleFunction) {

+					  var uuu = conf.api.saveRoleFunction;

+					  var postData={availableRoleFunction: availableRoleFunction};

+

+					  if(availableRoleFunction==null){

+						  confirmBoxService.showInformation("Please enter valid role function details.");

+					  }

+					  var exists = false,x;

+					  for(x in message.availableRoleFunctions){

+						  console.log(message.availableRoleFunctions[x].name);

+							if(message.availableRoleFunctions[x].name==availableRoleFunction.name){

+								confirmBoxService.showInformation("Role Function already exists.");

+								exists = true;

+								availableRoleFunction.name = $scope.tempText;

+								break;

+							} 

+							if(!isEditing){

+								if (message.availableRoleFunctions[x].code == availableRoleFunction.code) {

+									confirmBoxService.showInformation("Code already exists. Please create a role function with a different code to proceed.");

+									exists = true;

+									availableRoleFunction.name = $scope.tempText;

+									break;

+								}

+							}

+					  }

+					  

+					  if(!exists && availableRoleFunction.name.trim() != '' && availableRoleFunction.code.trim() != ''){

+			              $http.post(uuu, JSON.stringify(postData)).then(function(res){

+			            	  console.log("data");

+//			            	  console.log(res.data);

+//			            	  $scope.availableRoleFunctionsTemp = res.data.availableRoleFunctions;

+			            	  RoleService.getRoleFunctionList().then(function(data){

+			            			

+			            			var j = data;

+			            	  		$scope.data = JSON.parse(j.data);

+			            	  		$scope.availableRoleFunctions =JSON.parse($scope.data.availableRoleFunctions);

+			            	  		

+			            	  		//$scope.resetMenu();

+			            	  		$scope.closeThisDialog({result: true, availableRoleFunctions: $scope.availableRoleFunctions });

+			            		},function(error){

+			            			console.log("failed");

+			            			//reloadPageOnce();

+			            			$scope.closeThisDialog(true);		            			

+			            		});

+			            	  

+			            	  

+			              });						  

+						  

+						  

+						  

+						  

+						}

+				};

+					  

+				  	  

+					

+				$scope.close = function() { 

+					this.closeThisDialog(true);

+				};

+}

+);

diff --git a/ecomp-portal-FE-common/client/app/views/role/rolepopupmodelController.js b/ecomp-portal-FE-common/client/app/views/role/rolepopupmodelController.js
new file mode 100644
index 0000000..7feb7f2
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/role/rolepopupmodelController.js
@@ -0,0 +1,251 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+app.controller('rolepopupController',  function ($scope, role, roleId, confirmBoxService, $http, $state, availableRoles, conf, availableRoleFunctions,ngDialog){

+	

+	  		$scope.role = role;

+	  		console.log($scope.role);

+	  		if($scope.role.childRoles==null){

+	  			$scope.role.childRoles=[];

+	  		}

+	  		

+	  		$scope.ociavailableRoles=availableRoles;

+	  		console.log($scope.ociavailableRoles);

+	  		

+	  		$scope.availableRoles=[];

+	  		if($scope.ociavailableRoles)

+	  			$.each($scope.ociavailableRoles, function(i, a){ 

+	  				var availableRole = a;

+	  				availableRole.selected = false;

+	  				if($scope.role.childRoles){

+	  			    $.each($scope.role.childRoles, function(j, b){ 

+	  			    	if(a.id === b.id) {

+	  			    		availableRole.selected = true;

+	  			    	}

+	  			    });

+	  				};

+	  			    $scope.availableRoles.push(availableRole);	    

+	  			});	

+	  			

+	  		$scope.ociavailableRoleFunctions = availableRoleFunctions; 

+	  		console.log($scope.ociavailableRoleFunctions);

+	  		$scope.availableRoleFunctions = []; 

+	  		if($scope.ociavailableRoleFunctions)

+	  			$.each($scope.ociavailableRoleFunctions, function(i, a){ 

+	  				var availableRoleFunction = a;

+	  				availableRoleFunction.selected = false;

+	  			    $.each($scope.role.roleFunctions, function(j, b){ 

+	  			    	if(a.code === b.code) {

+	  			    		availableRoleFunction.selected = true;

+	  			    	}

+	  			    });

+	  			    $scope.availableRoleFunctions.push(availableRoleFunction);	    

+	  		});

+	  		//$scope.resetMenu();

+		

+		$scope.toggleRoleFunction = function(selected,availableRoleFunction) {

+			//alert('toggleRole: '+selected);

+			 

+			if(!selected) {

+				//remove role function

+				if(role.id==null){

+					var index = $scope.role.roleFunctions.indexOf(availableRoleFunction);

+				 	if(index>=0)

+						$scope.role.roleFunctions.splice(index, 1);

+					return;

+				}

+				var uuu = conf.api.toggleRoleRoleFunction + "?role_id=" + roleId;

+				confirmBoxService.confirm("You are about to remove the role function "+availableRoleFunction.name+" from the role for "+$scope.role.name+". Do you want to continue?").then(

+		    			function(confirmed){

+		    					if(confirmed) {	

+								var postData={roleFunction:availableRoleFunction};

+								$http.post(uuu, postData).then(

+										function(response) {

+											$scope.role= response.data.role;

+										}, 

+										function(response) {

+											confirmBoxService.showInformation("Error while saving.");

+										}

+								);

+									

+									

+									

+									

+								}

+		    					 else {

+		    						 availableRoleFunction.selected=!availableRoleFunction.selected;

+			    				 }

+								/*

+							  	  $.ajax({

+							  		 type : 'POST',

+							  		 url : uuu,

+							  		 dataType: 'json',

+							  		 contentType: 'application/json',

+							  		 data: JSON.stringify(postData),

+							  		 success : function(data){

+							  			$scope.$apply(function(){$scope.role=data.role;}); 

+									 },

+									 error : function(data){

+										 modalService.showFailure("Fail","Error while saving.");

+									 }

+							  	  });

+							  	  */

+		    			});

+	

+			} else {

+				//add role function

+				if(role.id==null){

+					$scope.role.roleFunctions.push(availableRoleFunction);

+					return;

+				}

+				var uuu = conf.api.addRoleRoleFunction + "?role_id=" + roleId;

+				

+				confirmBoxService.confirm("You are about to add the role function "+availableRoleFunction.name+" to the role for "+$scope.role.name+". Do you want to continue?").then(

+		    			function(confirmed){

+		    				if(confirmed) {

+								  var postData={roleFunction:availableRoleFunction};

+								  $http.post(uuu,postData).then( function(response) {

+									  $scope.role=response.data.role;

+									  },

+									  

+									  function(data) {

+										  confirmBoxService.showInformation("Error while saving.");

+									  });

+							} else {

+									  availableRoleFunction.selected=!availableRoleFunction.selected;

+							}

+									/*		  

+								  	  $.ajax({

+								  		 type : 'POST',

+								  		 url : uuu,

+								  		 dataType: 'json',

+								  		 contentType: 'application/json',

+								  		 data: JSON.stringify(postData),

+								  		 success : function(data){

+								  			$scope.$apply(function(){$scope.role=data.role;}); 

+										 },

+										 error : function(data){

+											 modalService.showFailure("Fail","Error while saving.");

+										 }

+								  	  });

+								  	  

+									

+						    	},

+						    	function(){

+						    		availableRoleFunction.selected=!availableRoleFunction.selected;

+						    	})*/

+			});

+			

+			  

+	}

+		};

+	

+	$scope.toggleChildRole = function(selected,availableRole) {

+		//alert('toggleRole: '+selected);

+

+		if(!selected) {

+			//remove role

+			if(role.id==null){

+				var index = $scope.role.childRoles.indexOf(availableRole);

+			 	if(index>=0)

+					$scope.role.childRoles.splice(index, 1);

+				return;

+			}

+			var uuu = conf.api.toggleRoleChildRole +"?role_id=" + roleId;

+			

+			confirmBoxService.confirm("You are about to remove the child role "+availableRole.name+" from the role for "+$scope.role.name+". Do you want to continue?").then(

+	    			function(confirmed){

+	    				if(confirmed) {

+	    			

+						var postData={childRole:availableRole};

+						 $http.post(uuu,postData).then(function(response) {

+							  $scope.role=response.data.role;

+							  },

+							  function(data) {

+								  confirmBoxService.showInformation("Error while saving.");

+							  });

+						  } else {

+							  availableRole.selected=false;

+						  }

+	    			});

+					  	 /* $.ajax({

+					  		 type : 'POST',

+					  		 url : uuu,

+					  		 dataType: 'json',

+					  		 contentType: 'application/json',

+					  		 data: JSON.stringify(postData),

+					  		 success : function(data){

+					  			 console.log('role',data.role);

+					  			 $scope.$apply(function(){$scope.role=data.role;}); 

+							 },

+							 error : function(data){

+								 modalService.showFailure("Fail","Error while saving.");

+							 }

+					  	  });

+					  	  */

+				

+	    	

+		} else {

+			//add role

+			if(role.id==null){

+				$scope.role.childRoles.push(availableRole);

+				return;

+			}

+			var uuu = conf.api.addRoleChildRole +"?role_id=" + roleId;

+			

+			confirmBoxService.confirm("You are about to add the child role "+availableRole.name+" to the role for "+$scope.role.name+". Do you want to continue?").then(

+	    			function(confirmed){

+	    				if(confirmed) {

+						  var postData={childRole:availableRole};

+						  $http.post(uuu,postData).then(function(response) {

+							  $scope.role=response.data.role;

+							  },

+							  function(data) {

+								  confirmBoxService.showInformation("Error while saving.");

+							  });

+						  } else {

+							  availableRole.selected=false;

+						  }

+			  	 /* $.ajax({

+			  		 type : 'POST',

+			  		 url : uuu,

+			  		 dataType: 'json',

+			  		 contentType: 'application/json',

+			  		 data: JSON.stringify(postData),

+			  		 success : function(data){

+			  			$scope.$apply(function(){$scope.role=data.role;}); 

+					 },

+					 error : function(data){

+						 modalService.showFailure("Fail","Error while saving.");

+					 }

+			  	});*/

+				

+	    	});

+		

+		  

+	}

+	};

+	

+	$scope.close = function() {

+		console.log('role', $scope.role);

+		//$modalInstance.close({role:$scope.role});

+		this.closeThisDialog(true);

+	};

+	

+});

diff --git a/ecomp-portal-FE-common/client/app/views/search/search.less b/ecomp-portal-FE-common/client/app/views/search/search.less
new file mode 100644
index 0000000..11c71f9
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/search/search.less
@@ -0,0 +1,66 @@
+.searchLiHeader {
+font-weight: bold; 
+color: #0574ac; 
+font-size: 16px;
+padding-bottom: 10px;
+line-height: 1.5;
+font-family: Omnes-ECOMP-W02,Arial;
+
+}
+
+.searchLiItems{
+cursor: pointer;
+font-weight: normal; 
+font-size: 12px;
+color: #444444;
+font-family: Omnes-ECOMP-W02,Arial;
+}
+
+.searchUl {
+list-style: none; 
+border-bottom: 1px solid #bbb; 
+padding-bottom: 20px;
+}
+
+#contentVertical {
+        height: 300px;
+        width: 250px;
+        overflow:auto;
+    }
+
+#contentVertical .scroll-viewport {
+        height: 300px;
+        width: 250px;
+    }
+#mainSearchText::-ms-clear{
+	display:none;
+}
+.search-input input{
+	height: 30px; 
+	width:265px;
+	border-radius: 10px;
+	border: 2px solid rgb(250, 250, 250);
+	font-size: 12px !important;
+	font-family: Omnes-ECOMP-W02,Arial;
+}
+
+.search-div{
+	left: -270px; 
+	position: relative;
+}
+.icon-search-span i{
+	line-height: 10px !important;
+}
+
+.search-res-dialog{
+	position: fixed;
+    background: white;
+    box-shadow: rgba(0, 0, 0, 0.247059) 0px 5px 6px 0px;
+    padding: 20px;
+    top: 90px;
+    right: 10px;
+}
+
+.search-popup-content{
+	line-height: normal; right: 167px; min-height: 122px; height: auto; width: auto;
+}
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/sidebar/sidebar.controller.js b/ecomp-portal-FE-common/client/app/views/sidebar/sidebar.controller.js
new file mode 100644
index 0000000..6d0f2d1
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/sidebar/sidebar.controller.js
@@ -0,0 +1,63 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+(function () {

+    class SidebarCtrl {

+        constructor(applicationsService,userProfileService, $log, $rootScope) {

+            this.$log = $log;

+            this.userProfileService = userProfileService;

+            this.$rootScope = $rootScope;

+            $rootScope.isAdminPortalAdmin = false;

+

+

+            //if (bowser.msie || bowser.msedge)

+            //    $log.debug('SidebarCtrl::init: Browser is: Internet Explorer or Edge');

+            // else

+            //    $log.debug('SidebarCtrl::init: Browser is: ' + bowser.name + ': ' + bowser.version);

+

+            //note: this model should be retrieved from BE via sidebar specific service

+            	userProfileService.getUserProfile()

+                .then(profile=> {

+                	

+                	if (profile.roles.indexOf('System Administrator') > -1) {

+                        $rootScope.isAdminPortalAdmin = true;

+                	} else {

+                		 this.$log.debug('SidebarCtrl::getUserProfile: user is not superAdmin nor admin');

+                	}

+                });

+                // $log.debug('SidebarCtrl::getUserProfile: profile.roles.indexOf(superAdmin) = ' + profile.roles.indexOf('superAdmin'));

+                // $log.debug('SidebarCtrl::getUserProfile: profile.roles.indexOf(admin) = ' + profile.roles.indexOf('admin'));

+            	applicationsService

+    			.getLeftMenuItems()

+    			.then(res=>

+    				 {

+    						//console.log("Menu items is "+JSON.stringify(res));

+    						this.sidebarModel = res;	 

+    				 }).catch(err => {

+            		//confirmBoxService.showInformation('There was a problem creating the menu. ' +

+                    // 'Please try again later. Error Status: '+ err.status).then(isConfirmed => {});

+            		$log.error('SidebarCtrl::getUserProfile: User Profile error occurred: ' + err);

+    				});

+    

+        }

+    }

+    SidebarCtrl.$inject = ['applicationsService','userProfileService', '$log', '$rootScope'];

+    angular.module('ecompApp').controller('SidebarCtrl', SidebarCtrl);

+})();

diff --git a/ecomp-portal-FE-common/client/app/views/sidebar/sidebar.less b/ecomp-portal-FE-common/client/app/views/sidebar/sidebar.less
new file mode 100644
index 0000000..f86b5af
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/sidebar/sidebar.less
@@ -0,0 +1,37 @@
+/*-

+ * ================================================================================

+ * eCOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+ .w-ecomp-sidebar {

+    position: relative;

+    left: 0;

+    right: 0;

+    top: 5px;

+    padding-left: 0;

+}

+  

+  @media screen and (-webkit-min-device-pixel-ratio:0)

+{ 

+    .w-ecomp-sidebar {

+	  position: relative;

+	  left: 0;

+	  right: 0;

+	  top: -5px;

+	  padding-left: 0;

+    }

+}
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/sidebar/sidebar.tpl.html b/ecomp-portal-FE-common/client/app/views/sidebar/sidebar.tpl.html
new file mode 100644
index 0000000..95492ed
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/sidebar/sidebar.tpl.html
@@ -0,0 +1,20 @@
+<!--

+  ================================================================================

+  eCOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed 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.

+  ================================================================================

+  -->

+<left-menu id="sidebar.sidebarModel" sidebar-model="sidebar.sidebarModel" class="w-ecomp-sidebar"></left-menu>

diff --git a/ecomp-portal-FE-common/client/app/views/support/contact-us/contact-us-manage/contact-us-manage.controller.js b/ecomp-portal-FE-common/client/app/views/support/contact-us/contact-us-manage/contact-us-manage.controller.js
new file mode 100644
index 0000000..8413382
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/support/contact-us/contact-us-manage/contact-us-manage.controller.js
@@ -0,0 +1,202 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+

+(function () {

+    class ContactUsManageController {

+        constructor($scope,$log, message, $q, $http, conf,contactUsService,confirmBoxService) {

+        	$scope.contactUsList=[];

+        	$scope.contactUsAllAppList=[];

+        	$scope.errMsg='';

+        	$scope.newContactUs ={

+        			app:'',

+        			name:'',

+        			email:'',

+        			url:'',

+        			desc:''       			

+        	};

+        	/*Get the existing contact us first, then based on the existing, filter from all apps*/

+        	$scope.getContactUsList = function(){

+        		contactUsService.getContactUs().then(res=> {

+            		// $log.info('getting getContactUs',res.response);

+            		if(res!=null && res.response!=null){

+            			for(var i=0; i<res.response.length;i++){

+            				if(res.response[i].appId!=1)

+            					$scope.contactUsList.push(res.response[i]);

+            			}

+            		}

+            		/*get all the apps*/

+            		contactUsService.getListOfApp().then(res=> {

+                    	var tableData=[];

+                    	$scope.contactUsAllAppListTemp=[];

+                    	// $log.info('contactUsService::getListOfApp: getting res');

+                    	var result = (typeof(res.data) != "undefined" && res.data!=null)?res.data:null;

+                    	// $log.info('contactUsService::getListOfApp: result',result);  	

+                    	var res1 = result;

+                        var realAppIndex = 0;

+                        $scope.contactUsAllAppList.length=0;

+                        for (var i = 1; i <= res1.length; i++) {

+                            if (!res1[i - 1].restrictedApp) {

+                            	var okToAdd = true;

+                            	for(var j =0; j<$scope.contactUsList.length;j++){

+                            		if(res1[i - 1].title==$scope.contactUsList[j].appName)

+                            			okToAdd=false;

+                            	}

+                            	// not allowed to add(duplicate) another entry if the app is already available in the table

+                            	if(okToAdd){

+                            		if(res1[i - 1].title){

+                            			$scope.contactUsAllAppList.push({

+                                            index: realAppIndex,

+                                            title: res1[i - 1].title,

+                                            value: res1[i - 1].index

+                                        });

+                            		}       

+                            		realAppIndex = realAppIndex + 1;

+                            	}         

+                            } else {

+                                // $log.debug('contactUsService:getAvailableApps:: Restricted/URL only App will not be used = ' + res1[i - 1].title);

+                            }

+                        }       

+                    }).catch(err=> {

+                        $log.error('contactUsService:error:: ', err);

+                        

+                       

+                    }).finally(() => {

+                        //this.isLoadingTable = false;

+                    });

+            	});

+        	}

+        	

+        	$scope.getContactUsList();

+        	

+        	$scope.closeDialog = function(){

+        		$scope.closeThisDialog( $scope.widgetData);

+        	}

+        	

+        	/*Add new Contact Us*/

+        	$scope.newContactUsFun = function(){ 

+        		if($scope.newContactUs.app.title==null || $scope.newContactUs.app.title=='' ){

+        			confirmBoxService.showInformation('Please select an App to add').then(isConfirmed => {

+        				return;

+        			});	

+        		}

+        					

+    			if($scope.newContactUs.url !=null && $scope.newContactUs.url != '' && !validateUrl($scope.newContactUs.url)){

+    				var warningMsg = "Please enter a valid URL";

+    				confirmBoxService.showInformation(warningMsg).then(isConfirmed => {return;});

+    				return;

+    			}

+    			

+        		contactUsService.addContactUs($scope.newContactUs).then(res=> {

+        			// $log.info('contactUsService: add ContactUs successfully');

+        			$scope.contactUsList.length=0;

+        			// $log.info('contactUsService: refreshing the Contact US table');

+        			$scope.getContactUsList();

+        			$scope.errMsg='';

+        			/*	confirmBoxService.showInformation('You have added a new Contact Us item').then(isConfirmed => {	});*/

+        			var defaultSel={

+    						index: 0,

+                            title: '',

+                            value: ''

+    				}

+    				$scope.newContactUs ={

+    	        			app:defaultSel,       			

+    	        			name:'',

+    	        			email:'',

+    	        			url:'',

+    	        			desc:''       			

+    	        	};

+

+                }).catch(err=> {

+                    $log.error('contactUsService: addContactUs error:: ', err);

+                   // $scope.errMsg=err;

+                    confirmBoxService.showInformation('Add Contact Us list failed: ' + err);

+                   

+                }).finally(() => {

+                    //this.isLoadingTable = false;

+                });

+        	}

+        	/* Edit Contact Us*/

+        	$scope.editContactUsFun = function(contactObj){

+    			// $log.info('contactUsService: edit ContactUs save successfully', contactObj);    			

+    			var contactUsObj={

+                		appId:contactObj.appId,

+                		appName:contactObj.appName,

+                		description:contactObj.description,

+                		contactName:contactObj.contactName,

+                		contactEmail:contactObj.contactEmail,

+                		url:contactObj.url,       		

+                };

+    			

+    			contactUsService.modifyContactUs(contactUsObj).then(res=> {

+        			// $log.info('contactUsService: edit ContactUs successfully');

+    				//	confirmBoxService.showInformation('You have saved the changes').then(isConfirmed => {});

+        			$scope.errMsg='';

+        			

+                }).catch(err=> {

+                    $log.error('contactUsService: editContactUs error:: ', err);

+                    confirmBoxService.showInformation('Edit Contact Us list failed: ' + err);

+                   // $scope.errMsg=err;

+                }).finally(() => {

+                    //this.isLoadingTable = false;

+                });

+        		

+        	}

+        	

+        	$scope.$watch('newContactUs.app.value', (newVal, oldVal) => {

+        		for(var i=0;i<$scope.contactUsAllAppList.length;i++){

+        		if($scope.contactUsAllAppList[i].value==newVal){

+        		$scope.newContactUs.app=angular.copy($scope.contactUsAllAppList[i]);;

+        		}

+        		}

+        		});

+        	/*del Contact Us*/

+        	$scope.delContactUsFun = function(appObj){

+        		var confirmMsg = 'Are you sure you want to delete '+appObj.appName +' from the list?' + ' Press OK to delete.';

+        		confirmBoxService.confirm(confirmMsg).then(function (confirmed) {

+                    if (confirmed == true) {                     	

+                    	contactUsService.removeContactUs(appObj.appId).then(res=> {   

+                    		// $log.info('delContactUsFun: delete ContactUs successfully',res);

+                			$scope.errMsg='';

+                			$scope.contactUsList.length=0;

+                			$scope.getContactUsList();

+                			confirmBoxService.showInformation('Item has been deleted').then(isConfirmed => {});

+                        }).catch(err=> {

+                            $log.error('contactUsService: addContactUs error:: ', err);

+                            confirmBoxService.showInformation('Deletion failed: ' + err);

+                           // $scope.errMsg=err;

+                        }).finally(() => {

+                            //this.isLoadingTable = false;

+                        });

+                    }

+                });

+        		

+        	}	

+        	

+        }        

+    }

+    ContactUsManageController.$inject = ['$scope','$log',  'message', '$q', '$http', 'conf','contactUsService','confirmBoxService'];

+    angular.module('ecompApp').controller('ContactUsManageController', ContactUsManageController);

+

+ 

+})();

+function validateUrl(value){

+    return /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value);

+  }

diff --git a/ecomp-portal-FE-common/client/app/views/support/contact-us/contact-us-manage/contact-us-manage.controller.less b/ecomp-portal-FE-common/client/app/views/support/contact-us/contact-us-manage/contact-us-manage.controller.less
new file mode 100644
index 0000000..905f9fa
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/support/contact-us/contact-us-manage/contact-us-manage.controller.less
@@ -0,0 +1,152 @@
+.searchLiHeader {
+font-weight: bold; 
+color: #0574ac; 
+font-size: 16px;
+padding-bottom: 10px;
+line-height: 1.5;
+font-family: Omnes-ECOMP-W02,Arial;
+
+}
+
+.searchLiItems{
+cursor: pointer;
+font-weight: normal; 
+font-size: 12px;
+color: #444444;
+font-family: Omnes-ECOMP-W02,Arial;
+}
+
+.searchUl {
+list-style: none; 
+border-bottom: 1px solid #bbb; 
+padding-bottom: 20px;
+}
+
+.manage-contactUs-home-title{
+    color: #067ab4;
+    font-family: "Omnes-ECOMP-W02", Arial;;
+    font-size: 24px;
+    margin-left: 78px;
+    width: 1170px;
+	}
+
+.manage-contactUs-home-manageWidgets-title{
+    color: #067ab4;
+    font-family: "Omnes-ECOMP-W02", Arial;;
+    font-size: 24px;
+    margin-left: 30px;
+    width: 1170px;
+	}	
+	
+#editWidgetsContent {
+        height: 300px;
+        width: 1250px;
+    }
+
+#editWidgetsContent .scroll-viewport {
+        height: 300px;
+        width: 1250px;
+    }
+    
+ .add-contact-us-field{
+	width:250px;
+	display: inline-block;
+	margin:10px;
+  }
+  .add-contact-us-field-des{
+	width:800px;
+	margin:10px;
+  }
+
+.errMsg{
+	color:red;
+}
+.sucessMsg{
+	color:green;
+}
+.contact-us-margin{
+	margin-left: 80px; 
+	margin-right: 120px;
+}
+.contact-us-manage-table {
+   
+
+  .delete-contact-us{
+    font-size:20px;
+  }
+
+  .portal-add-button {
+    width: 160px;
+  }
+}
+
+.select2-container .select2-chosen, .select2-container .select2-container input{
+	line-height:32px;
+}
+.select2-container .select2-choice{
+    -webkit-transition: border-color .2s ease-out;
+    -moz-transition: border-color .2s ease-out;
+    transition: border-color .2s ease-out;
+    background: #fff;
+    border-radius: 6px;
+    display: block;
+    border: 1px solid #888;
+    box-shadow: none;
+    filter: none;
+    height: 32px !important;
+    padding: 0;
+    position: relative;
+    z-index: 5;
+    cursor: pointer;
+}
+
+.c-ecomp-portal-abs-table .contactUs{
+
+		height:300px;
+	
+}
+
+.c-ecomp-portal-abs-table 
+{
+background-color: transparent;
+}
+.ngdialog-overlay {
+	pointer-events: none !important;
+}
+
+.input-text-area {
+  font-style: italic;
+  padding: 7px 10px;
+  width: 250px;
+  display: inline-block;
+  position: relative;
+  margin-bottom: 10px;
+  border-radius: 6px;
+  border: 1px solid #d8d8d8;
+  height: 32px !important;
+  border-color: slategrey !important;
+}
+.input-inline-edit-text {
+	  font-style: italic;
+	  padding: 7px 10px;
+	  display: inline-block;
+	  position: relative;
+	  margin-bottom: 10px;
+	  border-radius: 6px;
+	  border: 1px solid #d8d8d8;
+	  height: 32px;
+	  border-color: slategrey !important;
+	  width:100%;
+  }
+.glyphicon {
+      position: relative !important;
+      line-height: 0 !important;
+}
+
+#divider-line-bottom , #divider-line
+{
+ border-bottom-style: solid;
+ border-bottom-width: 1px;
+ padding-bottom: 11px;
+
+}
diff --git a/ecomp-portal-FE-common/client/app/views/support/contact-us/contact-us-manage/contact-us-manage.html b/ecomp-portal-FE-common/client/app/views/support/contact-us/contact-us-manage/contact-us-manage.html
new file mode 100644
index 0000000..c619a18
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/support/contact-us/contact-us-manage/contact-us-manage.html
@@ -0,0 +1,180 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div style="margin-top: 10px; margin-bottom: 10px;"></div>

+<br />

+

+<div style="margin-top: 25px;">

+	<div id="title" class="manage-contactUs-home-title">Manage

+		Contact Us</div>

+	<div class="contact-us-margin">

+		<div class="get-access-table">

+			<div class="table-control">

+				<div class="c-ecomp-portal-abs-table" style="height: 300px">

+					<table b2b-table id="table-main" table-data="contactUsList"

+						current-page="ignoredCurrentPage">

+						<thead b2b-table-row type="header">

+							<tr>

+								<th id="th-users-0" b2b-table-header key="ecomp_function"

+									default-sort="a">App Name</th>

+								<th id="th-users-1" b2b-table-header key="app_name"

+									sortable="true">Contact Name</th>

+								<th id="th-users-2" b2b-table-header key="app_name"

+									sortable="true">Contact Email</th>

+								<th id="th-users-3" b2b-table-header key="role_name"

+									sortable="true">Contact URL</th>

+								<th id="th-users-4" b2b-table-header key="role_name"

+									sortable="true">Description</th>

+								<th id="th-users-5" b2b-table-header key="role_name"

+									sortable="true">Edit</th>

+								<th id="th-users-6" b2b-table-header key="role_name"

+									sortable="true">Delete</th>

+							</tr>

+						</thead>

+						<!-- Use track-by="UNIQUE KEY HERE" or leave out if no unique keys in data -->

+						<tbody b2b-table-row type="body" class="table-body"

+							track-by="$index" row-repeat="rowData in contactUsList">

+							<tr id="tr-rowData" ng-click="">

+								<td b2b-table-body>

+									<div id="users-page-td-appName" ng-bind="rowData.appName"></div>

+								</td>

+								<td b2b-table-body>

+									<div id="users-page-td-name" ng-hide="rowData.showEdit"

+										ng-bind="rowData.contactName"></div> <input

+									class="input-inline-edit-text" type="text"

+									ng-show="rowData.showEdit"

+									ng-model="contactUsList[$index].contactName" />

+								</td>

+								<td b2b-table-body>

+									<div id="users-page-td-email" ng-hide="rowData.showEdit"

+										ng-bind="rowData.contactEmail"></div> <input

+									class="input-inline-edit-text" type="text"

+									ng-show="rowData.showEdit"

+									ng-model="contactUsList[$index].contactEmail" />

+								</td>

+								<td b2b-table-body>

+									<div id="users-page-td-url" ng-hide="rowData.showEdit"

+										ng-bind="rowData.url"></div> <input

+									class="input-inline-edit-text" type="text"

+									ng-show="rowData.showEdit" ng-model="contactUsList[$index].url" />

+								</td>

+								<td b2b-table-body>

+									<div id="users-page-td-descr" ng-hide="rowData.showEdit"

+										ng-bind=" rowData.description"></div> <input

+									class="input-inline-edit-text" type="text"

+									ng-show="rowData.showEdit"

+									ng-model="contactUsList[$index].description" />

+								</td>

+								<td b2b-table-body>

+									<div class="delete-contact-us" ng-hide="rowData.showEdit"

+										ng-click="rowData.showEdit=true">

+										<span class="icon-edit"></span>

+									</div> <span ng-show="rowData.showEdit"> <a

+										btn-type="primary"

+										ng-click="editContactUsFun(rowData); rowData.showEdit=false"

+										class="btn btn-alt btn-small" size="small">Save</a>

+								</span>

+								</td>

+								<td b2b-table-body>

+									<div class="delete-contact-us"

+										ng-click="delContactUsFun(rowData)">

+										<span class="icon-misc-trash"></span>

+									</div>

+								</td>

+							</tr>

+						</tbody>

+					</table>

+				</div>

+

+				<div id="divider-line"></div>

+				<div style="margin-top: 15px; margin-left: -78px;">

+					<div id="addWidgetHeader" class="contact-us-margin">

+						<h1 style="font-size: 18px;">Add Application Contact

+							Information</h1>

+						<!-- <div class="errMsg">{{errMsg}}</div> -->

+					</div>

+					<div id="addWidget" class="contact-us-margin">

+						<div>

+							<div id="add-contact-us-field-appname"

+								class="add-contact-us-field">

+								<div id="mots-property-label" class="property-label">App

+									Name</div>

+								<select id="dropdown1" name="dropdown1" b2b-dropdown

+									placeholder-text="Select an App"

+									ng-model="newContactUs.app.value">

+									<option b2b-dropdown-list

+										option-repeat="d in contactUsAllAppList" value="{{d.value}}">{{d.title}}</option>

+								</select>

+							</div>

+							<br>

+							<div id="add-contact-us-field-contactname"

+								class="add-contact-us-field">

+								<div id="property-label-name" class="property-label">Contact

+									Name</div>

+								<input id="property-input-name" class="input-text-area"

+									type="text" ng-model="newContactUs.name" />

+							</div>

+							<div id="add-contact-us-field-email" class="add-contact-us-field">

+								<div id="property-label-email" class="property-label">Contact

+									Email</div>

+								<input id="property-input-email" class="input-text-area"

+									type="text" ng-model="newContactUs.email" />

+							</div>

+							<div id="add-contact-us-field-url" class="add-contact-us-field">

+								<div id="property-label-url" class="property-label">Contact

+									URL</div>

+								<input id="property-input-url" class="input-text-area"

+									type="text" ng-model="newContactUs.url" />

+							</div>

+							<div id="add-contact-us-field-desc"

+								class="add-contact-us-field-des">

+								<div id="property-label-desc" class="property-label">Description</div>

+								<textarea id="property-input-desc"

+									style="margin-top: 0px; margin-bottom: 0px; height: 100px"

+									ng-model="newContactUs.desc"></textarea>

+							</div>

+						</div>

+						<div style="height: 50px;">

+							<a style="float: right; margin-top: 20px"

+								class="btn btn-alt btn-small" ng-click="newContactUsFun()">Add

+								New</a>

+						</div>

+						<div id="divider-line-bottom"></div>

+

+						<div style="height: 50px;">

+							<a style="float: right; margin-right: -230px !important;"

+								class="btn btn-alt btn-small" ng-click="closeDialog()">Close</a>

+						</div>

+

+					</div>

+

+				</div>

+

+			</div>

+		</div>

+	</div>

+</div>

+

+<script type="application/javascript">

+	

+    $(document).ready(function(){

+        $(".ngdialog-content").css("width","85%")

+    });

+

+</script>

diff --git a/ecomp-portal-FE-common/client/app/views/support/contact-us/contact-us.controller.js b/ecomp-portal-FE-common/client/app/views/support/contact-us/contact-us.controller.js
new file mode 100644
index 0000000..0d39a65
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/support/contact-us/contact-us.controller.js
@@ -0,0 +1,171 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+

+(function () {	

+		

+    class ContactUsCtrl {

+    	constructor($log, contactUsService, applicationsService, $modal, ngDialog, $state,$anchorScroll,$location) {  

+        	

+        	contactUsService.getContactUSPortalDetails().then(res => {

+        		// $log.info('ContactUsCtrl:: contactUsService getContactUSPortalDetails res',res);

+                // $log.info('getting res',res);

+                var result = (typeof(res.response) != "undefined" && res.response!=null)?res.response:null;

+                //	$log.info('result',result);

+                //	$log.info('done');

+                var source = JSON.parse(result);

+                //	$log.info(source); 

+                this.ush_TicketInfoUrl = source.ush_ticket_url; 

+                this.portalInfo_Address = source.feedback_email_address; 

+                this.feedback_Url = source.portal_info_url;                	

+        	}).catch(err=> {

+           	 	$log.error('ContactUsCtrl:error:: ', err);

+            }).finally(() => {

+           });

+        	

+        	let init = () => {

+                // $log.info('ecomp app::contact-us-controller::initializing...');

+                this.appTable=[];

+                this.functionalTableData=[];

+            };

+            init();     

+            

+            let updateContactUsTable = () => {

+            	contactUsService.getAppsAndContacts().then(res=> {

+         			// $log.info('ContactUsCtrl:: contactUsService getAppsAndContacts res',res);

+                 	var tableData=[];

+                 	// $log.info('getting res',res);

+                 	var result = (typeof(res.response) != "undefined" && res.response!=null)?res.response:null;

+                 	// $log.info('result',result);

+                 	// $log.info('done');

+                 	var source = result;

+                 	// $log.info(source);

+                 	// Drop Portal app, empty name entries

+                 	for(var i=0;i<source.length; i++) {

+                 		var dataArr = source[i];

+                 		if ( !dataArr.appName  || dataArr.appId == 1) {

+                 			continue;

+                 		}

+                     	var dataTemp={

+                     		app_name: dataArr.appName,

+                     		contact_name: dataArr.contactName,

+                     		contact_email: dataArr.contactEmail,

+                     		desc: dataArr.description,

+                     		url_Info: dataArr.url,

+                     		app_Id: dataArr.appId,

+                     	}

+                     	tableData.push(dataTemp);

+                 	}              	

+                 	this.appTable=tableData;

+                 }).catch(err=> {

+          		 	 $log.error('ContactUsCtrl.updateContactUsTable:error:: ', err);

+          		 })

+            };

+            

+            contactUsService.getAppCategoryFunctions().then(res=> {

+     			// $log.info('ContactUsCtrl:: contactUsService getAppCategoryFunctionsthen res',res);

+             	var tablefunctionalData=[];

+             	// $log.info('getting res',res);

+             	var result = (typeof(res.response) != "undefined" && res.response!=null)?res.response:null;

+             	// $log.info('result',result);

+             	// $log.info('done');

+             	var source = result;

+             	// $log.info(source);                	

+             	for(var i=0;i<source.length; i++) {

+             		var datafunctionalArr = source[i];

+                 	var datafuntionalTemp={

+                 		category: datafunctionalArr.category,

+                 		app_Name: datafunctionalArr.application,

+                 		functions: datafunctionalArr.functions,

+                 		app_Id: datafunctionalArr.appId,

+                 	}

+                 	tablefunctionalData.push(datafuntionalTemp);

+             	}              	

+             	this.functionalTableData=tablefunctionalData;

+             }).catch(err=> {

+      		 	 $log.error('ContactUsCtrl:error:: ', err);

+      		 })

+            

+            updateContactUsTable();

+            this.editContactUsModalPopup = () => {

+                 // $log.debug('ContactUsCtrl::editContactUsModalPopup updating table data...');               

+                 var modalInstance = ngDialog.open({

+                     templateUrl: 'app/views/support/contact-us/contact-us-manage/contact-us-manage.html',

+                     controller: 'ContactUsManageController',

+                     resolve: {

+                         message: function message() {

+                             var message = {

+                                 type: 'Contact',

+                             };

+                             return message;

+                         }

+                     }

+                 }).closePromise.then(needUpdate => {	              	 

+                	 updateContactUsTable();

+	             });       

+             };

+             

+             this.goToSection = (id) => {

+            	

+            	 var targetDiv = document.getElementById(id);  

+            	 

+            	 var offSetHeight = 0;

+            	 for(var i=0;i<this.appTable.length;i++){  

+            		 if(this.appTable[i].app_Id==id)

+            			 break;

+            		 if(this.appTable[i].showFlag==true){

+            			 offSetHeight+=document.getElementById('collapse'+i).clientHeight;         			

+            		 }

+            	 }

+            	 console.log(offSetHeight);

+            	 this.appTable.forEach(d => d.showFlag = false);

+            	// let index = this.appTable.findIndex(a => a.app_Id == id);

+            	 var index =-1;

+            	 for(var i=0; i<this.appTable.length;i++){

+            		 if(this.appTable[i].app_Id==id){

+            			 index = i;

+            			 break;

+            		 }

+            	 }

+            	 console.log(index);

+            	 if (index > -1) {

+            		 // setting the showFlag to true based on index comparing with the app_Id 

+            		 this.appTable[index].showFlag = true;

+            		 $location.hash('appId'+index);

+            	      $anchorScroll();

+            	/*	 $('#contentId').animate({

+          	            scrollTop: targetDiv.offsetTop-offSetHeight

+          	        }, 'fast');*/

+            	 }            	             	 

+            	 

+             };

+             

+             // Take the user to the application on the get access page.

+             this.goGetAccess = (appName) => {

+            	 // $log.debug('ContactUsCtrl::goGetAccess received name ' + appName);

+            	 applicationsService.goGetAccessAppName = appName;

+            	 $state.go('root.getAccess');

+             };

+             

+        }

+    }

+    ContactUsCtrl.$inject = ['$log','contactUsService', 'applicationsService', '$modal', 'ngDialog', '$state','$anchorScroll','$location'];

+    angular.module('ecompApp').controller('ContactUsCtrl', ContactUsCtrl);

+})();

diff --git a/ecomp-portal-FE-common/client/app/views/support/contact-us/contact-us.controller.spec.js b/ecomp-portal-FE-common/client/app/views/support/contact-us/contact-us.controller.spec.js
new file mode 100644
index 0000000..3841a2b
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/support/contact-us/contact-us.controller.spec.js
@@ -0,0 +1,19 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

diff --git a/ecomp-portal-FE-common/client/app/views/support/contact-us/contact-us.less b/ecomp-portal-FE-common/client/app/views/support/contact-us/contact-us.less
new file mode 100644
index 0000000..6fd0a42
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/support/contact-us/contact-us.less
@@ -0,0 +1,93 @@
+.w-ecomp-contactUs-home {
+  //.bg_portalWhite;//white for 1702
+  .bg_portalGray;  // gray for 1610
+  position: @page-main-position;
+  top: @page-main-top;
+  left: @page-main-left;
+  right: @page-main-right;
+  bottom: @page-main-bottom;
+  padding-top: @padding-top;
+  padding-left: @padding-left-side;
+  background-color: white;
+
+  .w-ecomp-main-view-title{
+  	 font-family: Omnes-ECOMP-W02,Arial;
+  }
+  
+  #title{
+  font-family: Omnes-ECOMP-W02-Light,Arial;
+  }
+  
+  #edit-button-contact-us{
+    float: right;
+  }
+  
+  .contactUs-home-container {
+    .content_justify;
+    position: relative;
+    //padding-top: 15px;
+    padding-right: 0;
+    padding-left: 0;
+    padding-bottom: 32px;
+    width: 100%;
+   
+
+    .contactUs-general-div {
+      margin: auto !important;
+      padding-top: 15px;
+      padding-bottom: 15px;
+      .content_justify;
+    }
+  }
+  
+.collapsibleArrow {
+	width: 30px;
+	height: 30px;
+	margin-left: 10px; 
+	}
+
+.contactUs-collapsible-panel {
+	margin: auto;
+	//width: 1170px;
+	
+	}
+
+ .contactUs-collapsible-table {
+	margin: auto;
+	width: 1170px;
+	}
+	
+ .contactUs-txt { 
+ 	font-weight: normal;
+	color: #666666;
+	font-size: 15px;
+	font-family: Omnes-ECOMP-W02-Medium,Arial;
+	margin:10px;
+	}
+	
+.contactUs-panel-header { 
+	font-weight: normal;
+	color: #666666;
+	font-size: 18px;
+	font-family: Omnes-ECOMP-W02-Medium,Arial;
+	border-top: 1px solid #ccc;
+	outline : none;
+	height: 35px;
+	
+	}
+	
+ .contactUs-panel-header:hover{
+ 	color: #428bca;
+ 	background: #eae8e8;
+ }	
+ 
+.contactUs-panel-labels { 
+	font-weight: normal;
+	color: #666666;
+	font-size: 15px;
+	font-family: Omnes-ECOMP-W02-Medium,Arial;
+	padding-top: 5px;
+	padding-bottom: 5px;
+}
+	
+}
diff --git a/ecomp-portal-FE-common/client/app/views/support/contact-us/contact-us.tpl.html b/ecomp-portal-FE-common/client/app/views/support/contact-us/contact-us.tpl.html
new file mode 100644
index 0000000..9e96b44
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/support/contact-us/contact-us.tpl.html
@@ -0,0 +1,126 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="w-ecomp-contactUs-home" style=" overflow:auto;''">

+	<div class="contactUs-home-container" id="page-content">

+		<div class="admins-page-main">

+			<div id="title" class="w-ecomp-main-view-title">

+			<h1 class="heading-page">Contact Us</h1>

+			<button id="edit-button-contact-us" ng-if="isAdminPortalAdmin == true" class="btn btn-alt btn-small" ng-click="contact.editContactUsModalPopup()"><i class="icon-people-userbookmark" aria-hidden="true"></i>&nbsp;Edit Contact Us</button>

+			

+			<div ng-include src="'app/views/support/contact-us/contact-us.aux.html'"></div>

+			

+		<br/> 

+		<!-- Function Categories -->		

+		<div id="appFunctionInfo" style="font-size: 20px;" class="w-ecomp-main-view-title"> Application Functions

+				<div b2b-table table-data="contact.functionalTableData" class="b2b-table-div">

+						<table>

+							<thead b2b-table-row type="header">

+								<tr>

+								  <th id="th-functionalItem-0" b2b-table-header sortable="false">Category</th>	                     

+			                      <th id="th-functionalItem-1" b2b-table-header sortable="false">ECOMP Functions</th>

+			                      <th id="th-functionalItem-2" b2b-table-header sortable="false">ECOMP Application</th>

+			                 	  <!-- <th id="th-functionalItem-3" b2b-table-header >Contact</th> --> 

+							  </tr>

+							</thead>

+							<!--  Use track-by="UNIQUE KEY HERE" or leave out if no unique keys in data  -->

+							<tbody b2b-table-row type="body"

+							type="body"  

+		                 	class="table-body" 

+		                 	track-by="$index"

+		                 	row-repeat="rowData in contact.functionalTableData">

+							<tr id="tr-rowData" ng-click="">

+			                 <td b2b-table-body>

+			                    <div id="functional-item-td-category" 

+	                                ng-show="$index == 0 || contact.functionalTableData[$index-1].category != rowData.category"

+			                    	ng-bind="rowData.category">

+			                    </div>

+			                 </td>

+			                 <td b2b-table-body>

+			                    <div id="functional-item-td-functions"  ng-bind="rowData.functions"></div>

+			                 </td>		                 

+			                 <td b2b-table-body>

+			                   <a ng-click="contact.goToSection(rowData.app_Id, $index);">{{rowData.app_Name}}</a>	

+			                 </td>			                 

+			               </tr>

+							</tbody>

+						</table>

+					</div>

+	</div>

+		<br/>

+		<!-- Display applicationInfo panels -->

+		<div id="allAppInfo">

+			<div style="font-size: 20px;" class="w-ecomp-main-view-title"> All Applications		

+			</div>		

+			<div class="contactUs-general-div">

+				<div class="contact-us-table">

+				<div style="height:auto;" class="c-ecomp-portal-abs-table default">

+					<div ng-repeat="rowData in contact.appTable"

+						class="contactUs-collapsible-panel">

+						<div id="{{rowData.app_Id}}" style="height:60px; " class="contactUs-panel-header" ng-click="rowData.showFlag = !rowData.showFlag" >

+							<span id="appId{{$index}}" ng-bind="rowData.app_name" style="position:relative; top:20px"></span>

+							

+							<span style="float: right; margin-right: 30px; margin-top:20px;">

+						 		<img ng-hide="rowData.showFlag"	src="assets/images/chevron_down.png"

+									alt="Expand app contact section"	title="Expand app contact section" /> 

+								<img ng-show="rowData.showFlag" src="assets/images/chevron_up.png"

+									alt="App contact section" title="Collapse app contact section" />

+							</span>

+						</div>

+				

+						<div id="collapse{{$index}}" ng-show="rowData.showFlag"

+							class="contactUs-collapsible-panel">

+							<div class="contactUs-txt" ng-hide="rowData.contact_name || rowData.contact_email || rowData.url_Info || rowData.desc"> 

+								<span style="margin-bottom: 20px;"> No application information is available. Please use the links above to contact the ECOMP Portal team. </span>

+							</div>

+		 					<div class="contactUs-txt" ng-show="rowData.contact_name || rowData.contact_email || rowData.url_Info || rowData.desc"> 

+							  <table style="border-spacing: 10px 5px; max-width:950px; margin:10px;">

+								<tr>

+									<td class="contactUs-panel-labels" style="width: 120px;">Contact:</td>

+									<td class="contactUs-panel-labels" style="width: 830px;">{{rowData.contact_name}}</td>

+								</tr>

+								<tr>

+									<td class="contactUs-panel-labels">Email:</td>

+									<td class="contactUs-panel-labels"><a ng-href="mailto:{{rowData.contact_email}}" target="_top">{{rowData.contact_email}}</a></td>

+								</tr>

+								<tr>

+									<td class="contactUs-panel-labels">Info URL:</td>

+									<td class="contactUs-panel-labels"><a ng-href="{{rowData.url_Info}}" target="_blank">{{rowData.url_Info}}</a></td>

+								</tr>

+								<tr>

+									<td class="contactUs-panel-labels">Description:</td>

+									<td class="contactUs-panel-labels">{{rowData.desc}}</td>

+								</tr>

+								<tr>

+									<td class="contactUs-panel-labels">Get access:</td>

+									<td class="contactUs-panel-labels"><a href="" ng-click="contact.goGetAccess(rowData.app_name);">Click for application and role information</a></td>

+								</tr>

+							  </table>

+							</div>				

+						</div>

+				

+					</div>

+				</div>

+				</div>

+			</div>

+		</div>

+

+	</div>

+</div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/support/get-access/get-access.controller.js b/ecomp-portal-FE-common/client/app/views/support/get-access/get-access.controller.js
new file mode 100644
index 0000000..74b5ba4
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/support/get-access/get-access.controller.js
@@ -0,0 +1,123 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+(function () {

+    class GetAccessCtrl {

+        constructor($log, $scope,  $stateParams, getAccessService, userProfileService, ExternalRequestAccessService, applicationsService, ngDialog) {

+        	// $log.debug('GetAccessCtrl: appService param is: ' + applicationsService.goGetAccessAppName);

+        	var resultAccessValue = null;

+        	

+        	$scope.openAppRoleModal = (itemData) => {    	

+        		if(resultAccessValue){

+        		let data = null;

+                    data = {

+                        dialogState: 2,

+                        selectedUser:{

+                            attuid: $scope.attuid,

+                            firstName: $scope.firstName,

+                            lastName: $scope.lastName,

+                            headerText: itemData.app_name

+                        }

+                    }

+                ngDialog.open({

+                    templateUrl: 'app/views/catalog/add-catalog-dialogs/new-catalog.modal.html',

+                    controller: 'NewCatalogModalCtrl',

+                    controllerAs: 'userInfo',

+                    data: data

+                });

+        		}

+            }

+        	

+            userProfileService.getUserProfile().then(

+        			function(profile) {

+        				$scope.attuid = profile.orgUserId;

+        				$scope.firstName = profile.firstName;

+        				$scope.lastName = profile.lastName;

+        	  });

+            

+        	this.updateAppsList = () => {

+        		ExternalRequestAccessService.getExternalRequestAccessServiceInfo().then(

+			  			function(property) {

+			  				resultAccessValue = property.accessValue;

+				}).catch(err => {

+                    $log.error('GetAccessCtrl: failed getExternalRequestAccessServiceInfo: ' + JSON.Stringify(err));

+                });

+                getAccessService.getListOfApp().then(res=> {

+                	var tableData=[];

+                	// $log.info('GetAccessCtrl::updateAppsList: getting res');

+                	var result = (typeof(res.data) != "undefined" && res.data!=null)?res.data:null;

+                	// $log.info('GetAccessCtrl::updateAppsList: result',result);

+                	// $log.info('GetAccessCtrl::updateAppsList: done');

+                	var source = result;

+                	// $log.info('GetAccessCtrl::updateAppsList source: ', source);

+                	for(var i=0;i<source.length; i++){

+                		var dataArr = source[i];

+                		var checkEcompFuncAvail = 'Ecomp Function Not Available' ; 

+                		var reqStatus = 'Pending'; 

+                		dataArr.ecompFunction = (dataArr.ecompFunction === null) ? checkEcompFuncAvail : dataArr.ecompFunction;

+                		dataArr.reqType = (dataArr.reqType === 'P') ? reqStatus : dataArr.reqType;

+                    	var dataTemp={

+                    		ecomp_function: dataArr.ecompFunction,

+                    		app_name:dataArr.appName,

+                    		role_name:dataArr.roleName,

+                    		current_role:dataArr.roleActive,

+                    		request_type:dataArr.reqType

+                    	}

+                    	tableData.push(dataTemp);

+                	} 

+                	this.appTable=tableData;

+                	if(tableData!=null){

+                		var len = tableData.length;

+                		this.totalPage =  Math.ceil(len/this.viewPerPage);     

+                	}

+                	if( $stateParams.appName != null)

+                		this.searchString = $stateParams.appName;

+                	else

+                		this.searchString = applicationsService.goGetAccessAppName;

+                	// the parameter has been used; clear the value.

+                	applicationsService.goGetAccessAppName = '';

+                }).catch(err=> {

+                    $log.error('GetAccessCtrl:error:: ', err);

+                }).finally(() => {

+                    this.isLoadingTable = false;

+                });

+            };

+            

+            this.updateTable = (num) => {

+                this.startIndex=this.viewPerPage*(num-1);

+                this.currentPage = num;

+            };

+        	let init = () => {

+                // $log.info('GetAccessCtrl:: initializing...');

+                this.searchString = '';

+                this.getAccessTableHeaders = ['ECOMP Function', 'Application Name', 'Role Name', 'Current Role', 'Request Status'];

+                this.appTable=[];

+                this.updateAppsList();             

+                this.viewPerPage=20;

+                this.startIndex=0;

+                this.currentPage = 1;

+                this.totalPage=0;

+            };

+            init();

+        }

+    }

+    GetAccessCtrl.$inject = ['$log', '$scope', '$stateParams', 'getAccessService', 'userProfileService', 'ExternalRequestAccessService','applicationsService', 'ngDialog'];

+    angular.module('ecompApp').controller('GetAccessCtrl', GetAccessCtrl);

+})();

diff --git a/ecomp-portal-FE-common/client/app/views/support/get-access/get-access.controller.spec.js b/ecomp-portal-FE-common/client/app/views/support/get-access/get-access.controller.spec.js
new file mode 100644
index 0000000..3841a2b
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/support/get-access/get-access.controller.spec.js
@@ -0,0 +1,19 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

diff --git a/ecomp-portal-FE-common/client/app/views/support/get-access/get-access.less b/ecomp-portal-FE-common/client/app/views/support/get-access/get-access.less
new file mode 100644
index 0000000..7ac0d18
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/support/get-access/get-access.less
@@ -0,0 +1,55 @@
+.w-ecomp-get-access-home {
+    //.bg_portalWhite;//white for 1702
+    .bg_portalGray;  // gray for 1610
+    position: @page-main-position;
+    top: @page-main-top;
+    left: @page-main-left;
+    right: @page-main-right;
+    bottom: @page-main-bottom;
+    padding-top: @padding-top;
+    overflow-y: @page-main-overflow-y;
+    padding-left: @padding-left-side;
+    background-color: white;
+	
+	.w-ecomp-main-view-title{
+	    font-family: Omnes-ECOMP-W02-Light,Arial;
+	}
+
+    .get-access-home-container {
+        position: relative;
+        padding-right: 0;
+        padding-left: 0;
+        padding-bottom: @container-bottom;
+
+        .get-access-general-div {
+            width: @table-width;
+            //margin-left: @table-margin-left;
+            margin:auto;
+        }
+
+        .get-access-table {
+            margin-top: 15px;
+            width: @table-width;
+            //margin-left: @table-margin-left;
+			margin:0 auto;
+            .table-control {
+                .table-dropdown-filter {
+                      width: @table-dropdown-filter-width;
+                      display: @table-dropdown-filter-display;
+                }
+            }
+
+            .table-body {
+                cursor: pointer;
+            }
+        }
+    }
+}
+
+table th, table td {
+    padding: 15px 14px 10px;
+}
+	
+#access-page-currentRole{
+    text-align: center;
+}
diff --git a/ecomp-portal-FE-common/client/app/views/support/get-access/get-access.tpl.html b/ecomp-portal-FE-common/client/app/views/support/get-access/get-access.tpl.html
new file mode 100644
index 0000000..7f6770e
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/support/get-access/get-access.tpl.html
@@ -0,0 +1,102 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="w-ecomp-get-access-home">

+	<div class="get-access-home-container" id="page-content">

+		<div class="admins-page-main">

+			<div id="title" class="w-ecomp-main-view-title">

+			<h1 class="heading-page">Get Access</h1>

+			</div>

+			<div ng-include

+				src="'app/views/support/get-access/get-accessinfo.html'"></div>

+			<div class="get-access-table">

+				<div class="table-control">

+					<input id="input-table-search" class="table-search" type="text"

+						placeholder="Search" ng-model="access.searchString" /> <span

+						class="ecomp-spinner" ng-show="access.isLoadingTable"></span>

+					<div b2b-table table-data="access.appTable"

+						ng-hide="access.isLoadingTable"

+						search-string="access.searchString"

+						class="b2b-table-div">

+						<table>

+							<thead b2b-table-row type="header">

+								<tr>

+									<th id="th-access-0" b2b-table-header key="ecomp_function"

+										sortable="false" >{{access.getAccessTableHeaders[0]}}</th>

+									<th id="th-access-1" b2b-table-header key="app_name"

+										sortable="false">{{access.getAccessTableHeaders[1]}}</th>

+									<th id="th-access-2" b2b-table-header key="role_name"

+										sortable="false">{{access.getAccessTableHeaders[2]}}</th>

+									<th id="th-access-3" b2b-table-header key="current_role"

+										sortable="false">{{access.getAccessTableHeaders[3]}}</th>

+									<th id="th-access-4" b2b-table-header key="request_access"

+										sortable="false">{{access.getAccessTableHeaders[4]}}</th>

+								</tr>

+							</thead>

+							<tbody b2b-table-row type="body"

+								row-repeat="rowData in access.appTable | limitTo:access.viewPerPage:access.startIndex | orderBy:'ecomp_function'"  track-by="$index">

+								<tr id="tr-rowData" ng-click="openAppRoleModal(rowData)">

+									<td b2b-table-body>

+										<div id="access-page-function"

+											ng-if="rowData.ecomp_function !== 'Ecomp Function Not Available'"

+											ng-show="$index == 0 || access.appTable[$index-1].ecomp_function != rowData.ecomp_function"

+											ng-bind="rowData.ecomp_function"></div>

+										<div id="access-page-function"

+											ng-if="rowData.ecomp_function === 'Ecomp Function Not Available'"

+											ng-bind="rowData.ecomp_function"></div>

+									</td>

+									<td b2b-table-body>

+										<div id="access-page-appName"

+											ng-show="$index == 0 || access.appTable[$index-1].app_name != rowData.app_name"

+											ng-bind="rowData.app_name"></div>

+									</td>

+									<td b2b-table-body>

+										<div id="access-page-roleName" ng-bind="rowData.role_name"></div>

+									</td>

+									<td b2b-table-body>

+										<div id="access-page-currentRole"

+											ng-if="rowData.current_role === 'Y'">

+											<i class="icon-included-checkmark"></i>

+										</div>

+									</td>

+									<td b2b-table-body>

+										<div id="access-page-RequestAccess"

+											ng-if="rowData.request_type !== null"

+											ng-bind="rowData.request_type"></div>

+									</td>

+								</tr>

+							</tbody>

+						</table>

+					</div>

+				</div>

+			</div>

+			<div b2b-pagination="" input-id="goto-page-1" total-pages="access.totalPage" current-page="access.currentPage" click-handler="access.updateTable" role="navigation" aria-label="Customer Data Pages"></div>	

+		</div>

+	</div>

+</div>

+

+<style>

+.tablesorter-default {

+	cursor: default;

+}

+

+.admins-page-main .admins-table .table-body {

+	cursor: default;

+}

+</style>

diff --git a/ecomp-portal-FE-common/client/app/views/tabs/tabframe.html b/ecomp-portal-FE-common/client/app/views/tabs/tabframe.html
new file mode 100644
index 0000000..0155592
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/tabs/tabframe.html
@@ -0,0 +1,22 @@
+<!--
+  ================================================================================
+  eCOMP Portal
+  ================================================================================
+  Copyright (C) 2017 AT&T Intellectual Property
+  ================================================================================
+  Licensed 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.
+  ================================================================================
+  -->
+<iframe id= "tabframe-{{tab.title.split(' ').join('-')}}" scrolling='yes' frameBorder='0' width='100%' style='height: 90vh;' 
+src='{{tab.content | trusted}}'
+></iframe>
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/tabs/tabs.controller.js b/ecomp-portal-FE-common/client/app/views/tabs/tabs.controller.js
new file mode 100644
index 0000000..5a5b7e4
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/tabs/tabs.controller.js
@@ -0,0 +1,314 @@
+/*-
+ * ================================================================================
+ * eCOMP Portal
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property
+ * ================================================================================
+ * Licensed 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';
+(function () {
+    const HTTP_PROTOCOL_RGX = /https?:\/\//;
+    class  TabsCtrl {
+        constructor(applicationsService, $log, $window, conf, userProfileService, userbarUpdateService, $scope,$cookies,$rootScope,confirmBoxService,auditLogService) {        	
+        	// Tab counter
+            var counter = 1;
+            var tabLimit = 6;
+            this.conf = conf;
+            var cookieDomain = this.conf.cookieDomain;
+            // Array to store the tabs
+            $scope.tabs = [];
+            $scope.notificationShow=true;
+            $rootScope.showFooter = "";
+            $cookies.putObject('show_app_header', false,{domain: cookieDomain, path : '/'});
+
+
+            let noRefresh = function () {
+                    window.onbeforeunload = function(e) {
+
+                    	var isQtoHref = false;
+                    	try{
+                    		isQtoHref = e.srcElement.activeElement.href.includes("to");
+                    	} catch(err) {
+
+                    	}
+
+                        if ($scope.tabs.length > 1 && isQtoHref == false) {
+                            return "Changes you made may not be saved. Are you sure you want to refresh?";
+                        } else {
+                            return null;
+                        }
+                    }
+            }
+            // Add tab to the end of the array
+            var addTab = function (title, content) {
+            	if($scope.tabs.length===tabLimit){
+            		//alert
+            		confirmBoxService.showInformation('You have reached your maximum limit of tabs allowed.').then(isConfirmed => {});
+            	} else {
+            		// console.log(window.performance.memory);
+            		// var usedperc = (window.performance.memory.usedJSHeapSize/window.performance.memory.jsHeapSizeLimit)*100;
+            		// console.log('Current memory usage: '+usedperc+'%');
+            		if(title!=='Home' && content.indexOf('https') == -1){
+            			console.log('App URL: '+content+'. The application URL you are trying to open is not HTTPS. We recommend to use secured HTTPS URL while on-boarding the application.');
+            			//confirmBoxService.showInformation('The application URL you are trying to open is not HTTPS. We recommend to use secured HTTPS URL while on-boarding the application.').then(isConfirmed => {});
+            		}
+            		
+                    $scope.tabs.push({ title: title, content: content });
+                    counter++;
+                    //$scope.tabs[$scope.tabs.length - 1].disabled = false;
+                    $scope.selectedIndex = $scope.tabs.length - 1;
+                    if ($scope.tabs.length > 1) {
+                        noRefresh();
+                    }
+                    $cookies.putObject('cookieTabs', $scope.tabs,{domain: cookieDomain, path : '/'});
+                    $cookies.putObject('visInVisCookieTabs', $scope.tabs,{domain: cookieDomain, path : '/'});
+            	}
+            };
+            
+            //with APP ID
+            var addTab = function (title, content, appId) {
+            	if($scope.tabs.length===tabLimit){
+            		//alert
+            		confirmBoxService.showInformation('You have reached your maximum limit of tabs allowed.').then(isConfirmed => {});
+            	} else {     		
+            		if(title!=='Home' && content.indexOf('https') === -1){
+            			console.log('App URL: '+content+'. The application URL you are trying to open is not HTTPS. We recommend to use secured HTTPS URL while on-boarding the application.');
+            		}
+                    $scope.tabs.push({ title: title, content: content, appId:appId });
+                    counter++;
+                    $scope.selectedIndex = $scope.tabs.length - 1;
+                    if ($scope.tabs.length > 1) {
+                        noRefresh();
+                    }
+                    $cookies.putObject('cookieTabs', $scope.tabs,{domain: cookieDomain, path : '/'});
+                    $cookies.putObject('visInVisCookieTabs', $scope.tabs,{domain: cookieDomain, path : '/'});
+            	}
+            };
+            
+            // adjust title - trim the title and append ...
+            var adjustTitle = function (title) {
+            	var index = 15;
+            	var nonEmptyCharPattern = /(\s|\w)/;
+            	var adjustedTitle = title.substring(0,index);
+            	var ext = title.charAt(index).replace(nonEmptyCharPattern,'...');
+            	return adjustedTitle.concat(ext);
+            	
+            	
+            };
+            
+            //store audit log
+            $scope.auditLog = function(app) {
+                $log.debug('auditLog::auditLog: auditLog.ping() = ' + app);
+                var comment = '';
+                if(app.content==null || app.content=='')
+                	comment= app.title;
+                else
+                	comment = app.content;
+                auditLogService.storeAudit(app.appId, 'tab', comment);
+        	};
+            
+            // Remove tab by index
+            var removeTab = function (event, index) {
+              event.preventDefault();
+              event.stopPropagation();
+              $scope.tabs.splice(index, 1);
+              $cookies.putObject('cookieTabs', $scope.tabs,{domain: cookieDomain, path : '/'});
+            };
+            
+          //adjust height of the tab due to the search popup being hidden 
+        	$scope.adjustTabStyle = function(title){
+        		if(title=='Home'){
+        			$(".w-ecomp-tabs").css('height',"50px");
+        		}else{
+        			$(".w-ecomp-tabs").css('height',"100%");
+        		}
+        	}
+        	
+           // select tab 
+            var selectTab = function (title) {
+            	$scope.adjustTabStyle(title);
+                if(title==='Home') {
+                    $rootScope.ContentModel.IsVisible=true;
+                    $rootScope.showFooter = true;
+                    $rootScope.tabBottom = 75;
+                    userbarUpdateService.setRefreshCount(userbarUpdateService.maxCount);
+                }
+                else {
+                    $rootScope.ContentModel.IsVisible=false;
+                    $rootScope.showFooter = false;
+                    $rootScope.tabBottom = 0;
+                }
+            };
+
+            // Initialize the scope functions
+            $scope.addTab    = addTab;
+            $scope.removeTab = removeTab;
+            $scope.selectTab = selectTab;
+            $scope.adjustTitle = adjustTitle;
+            
+
+            $rootScope.ContentModel = {
+            	    IsVisible : false,
+            	    ViewUrl : null,
+            	};
+            
+            
+        	var sessionActive = applicationsService.ping()
+        	.then(sessionActive => {
+            // $log.debug('TabsCtrl::addTab: applicationsService.ping() = ' + JSON.stringify(sessionActive));
+        	// For demonstration add 5 tabs
+               
+            var cookieTabs = $cookies.getObject('cookieTabs');
+        	if(cookieTabs!=null){
+        		for(var t in cookieTabs){
+        			// console.log('TabsCtrl::addTab: cookieTabs title: '+cookieTabs[t].title);
+        			if(cookieTabs[t].title!=null && cookieTabs[t].title==='Home'){
+        				cookieTabs[t].content = "";
+        				$rootScope.ContentModel.IsVisible=true;
+            			addTab( cookieTabs[t].title, cookieTabs[t].content,1) ;
+        			}else{
+            			addTab( cookieTabs[t].title, cookieTabs[t].content,cookieTabs[t].appId) ;
+        			}
+        				
+        		}
+        	} else {
+            for (var i = 0; i < 1; i++) {
+            	var content="";
+            	var title="";
+            	var appId=""
+            	if(i===0){
+            		title="Home";
+            		$rootScope.ContentModel.IsVisible=true;
+                    addTab(title, content,1);
+            	}else{
+                    addTab(title, content,appId);
+            	} 
+            }
+        	}
+            
+
+            
+            //$scope.tabs[$scope.tabs.length - 1].active = false;
+            //$scope.tabs[0].disabled = false;
+            $scope.selectedIndex = 0;
+        	});
+        	
+            $scope.$watchCollection(function() { return $cookies.getObject('addTab'); }, function(newValue) {
+            	// $log.log('Cookie string: ' + $cookies.get('test'))
+            	var tabContent = $cookies.getObject('addTab');
+            	if(tabContent!=null && tabContent.url!=null){
+            		var tabExists = false;
+            		for(var x in $scope.tabs){
+            			// console.log($scope.tabs[x].content);
+            			if($scope.tabs[x].title==tabContent.title){
+            				tabExists = true;
+            				//$scope.tabs[x].disabled = false;
+            				$scope.selectedIndex = x;
+                            // added dummy variable to get iframe reloded if tab is already opened.
+                            if(tabContent.url.indexOf('?')===-1){
+                                $scope.tabs[x].content=tabContent.url+'?dummyVar='+(new Date()).getTime();
+                            }
+                            else{
+                                $scope.tabs[x].content=tabContent.url+'&dummyVar='+(new Date()).getTime();
+                            }
+            			}
+            		}
+            		if(!tabExists){
+            	         addTab( tabContent.title, tabContent.url,tabContent.appId) ;
+            		}
+            		$cookies.remove('addTab');
+            	}
+            });
+        }
+    }
+    TabsCtrl.$inject = ['applicationsService', '$log', '$window', 'conf', 'userProfileService', 'userbarUpdateService', '$scope','$cookies','$rootScope','confirmBoxService','auditLogService'];
+    angular.module('ecompApp').controller('TabsCtrl', TabsCtrl);
+    
+    angular.module('ecompApp').directive('mainArea', function() {
+        return {
+            restrict: "E",
+            templateUrl: 'app/views/tabs/tabframe.html',
+            link: function(scope, element) {
+            	           	
+            	//var iframeId = "#tabframe-" + scope.$parent.tab.title.split(' ').join('-');
+            	// jQuery(iframeId).load(function() {
+            	//        alert("hello");
+            	//    }); //.attr("src",'{{tab.content | trusted}}' ); //src='{{tab.content | trusted}}'
+            	// jQuery(iframeId).attr('src', '{{tab.content | trusted}}');
+            	 
+            	//element.childNodes[0].on('load', function() {
+            	//	alert('hello');
+            	//});
+            }
+        }
+    });
+    
+   
+    
+    angular.module('ecompApp').directive('tabHighlight', [function () {
+        return {
+          restrict: 'A',
+          link: function (scope, element) {
+            // Here is the major jQuery usage where we add the event
+            // listeners mousemove and mouseout on the tabs to initalize
+            // the moving highlight for the inactive tabs
+            var x, y, initial_background = '#c3d5e6';
+
+            element
+              .removeAttr('style')
+              .mousemove(function (e) {
+                // Add highlight effect on inactive tabs
+                if(!element.hasClass('md-active'))
+                {
+                  x = e.pageX - this.offsetLeft;
+                  y = e.pageY - this.offsetTop;
+
+                  // Set the background when mouse moves over inactive tabs
+                  element
+                    .css({ background: '-moz-radial-gradient(circle at ' + x + 'px ' + y + 'px, rgba(255,255,255,0.4) 0px, rgba(255,255,255,0.0) 45px), ' + initial_background })
+                    .css({ background: '-webkit-radial-gradient(circle at ' + x + 'px ' + y + 'px, rgba(255,255,255,0.4) 0px, rgba(255,255,255,0.0) 45px), ' + initial_background })
+                    .css({ background: 'radial-gradient(circle at ' + x + 'px ' + y + 'px, rgba(255,255,255,0.4) 0px, rgba(255,255,255,0.0) 45px), ' + initial_background });
+                }
+              })
+              .mouseout(function () {
+                // Return the inital background color of the tab
+                element.removeAttr('style');
+              });
+          }
+        };
+      }]);
+
+
+    
+})();
+
+function getParameterByName(name, url) {
+    if (!url) url = window.location.href;
+    name = name.replace(/[\[\]]/g, "\\$&");
+    var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
+        results = regex.exec(url);
+    if (!results) return '';
+    if (!results[2]) return '';
+    return (results[2].replace(/\+/g, " "));
+}
+
+function isCascadeFrame(ref) {
+	  // alert(ref.id);
+	   if (self != top) {
+		   var e = document.body;
+		   e.parentNode.removeChild(e);
+		   window.location = "unKnownError";
+		   }
+}
diff --git a/ecomp-portal-FE-common/client/app/views/tabs/tabs.controller.spec.js b/ecomp-portal-FE-common/client/app/views/tabs/tabs.controller.spec.js
new file mode 100644
index 0000000..b9fd0ac
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/tabs/tabs.controller.spec.js
@@ -0,0 +1,81 @@
+/*-
+ * ================================================================================
+ * eCOMP Portal
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property
+ * ================================================================================
+ * Licensed 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';
+
+describe('Controller: TabsCtrl ',() => {
+    beforeEach(module('ecompApp'));
+
+    //destroy $http default cache before starting to prevent the error 'default cache already exists'
+    beforeEach(inject((_CacheFactory_)=>{
+        _CacheFactory_.destroyAll();
+    }));
+
+    let TabsCtrl, $controller, $q, $rootScope, $log, $window, $cookies,$scope;
+
+    beforeEach(inject((_$controller_, _$q_, _$rootScope_, _$log_, _$window_, _$cookies_)=>{
+        [$controller, $q, $rootScope, $log, $window, $cookies] = [_$controller_, _$q_, _$rootScope_, _$log_, _$window_, _$cookies_];
+    }));
+
+    var deferredApps, deferredUserProfile;
+    beforeEach(()=>{
+        deferredApps = $q.defer();
+        deferredUserProfile = $q.defer();
+        let applicationsServiceMock = jasmine.createSpyObj('applicationsServiceMock', ['getUserApps']);
+        applicationsServiceMock.getUserApps.and.returnValue(deferredApps.promise);
+
+        let userProfileServiceMock = jasmine.createSpyObj('userProfileServiceMock',['getUserProfile']);
+        userProfileServiceMock.getUserProfile.and.returnValue(deferredUserProfile.promise);
+
+        $scope = $rootScope.$new();
+        TabsCtrl = $controller('TabsCtrl', {
+            applicationsService: applicationsServiceMock,
+            $log: $log,
+            $window: $window,
+            userProfileService: userProfileServiceMock,
+            $scope: $scope,
+            $cookies: $cookies,
+            $rootScope: $rootScope
+        });
+    });
+
+    it('should populate this.apps with data from portals service getUserApps', ()=>{
+        var profile = {roles:'superAdmin',userId:'userid'};
+       deferredUserProfile.resolve(profile);
+       deferredApps.resolve([{name: 'portal1'},{name: 'portal2'},{name: 'portal3'}]);
+        $rootScope.$apply();
+        expect($scope.appsViewData.length).toBe(3);
+    });
+
+    it('should call $log error when getAllPortals fail', ()=>{
+        spyOn($log, 'error');
+        deferredUserProfile.reject('something happened!');
+        $rootScope.$apply();
+        expect($log.error).toHaveBeenCalled();
+    });
+
+    it('should open link in a new window when clicking app thumbnail', () => {
+        spyOn($window, 'open');
+        let someUrl = 'http://some/url/';
+        TabsCtrl.goToPortal(someUrl);
+        expect($window.open).toHaveBeenCalledWith(someUrl, '_self');
+    });
+    
+ 
+});
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/tabs/tabs.less b/ecomp-portal-FE-common/client/app/views/tabs/tabs.less
new file mode 100644
index 0000000..9f90d61
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/tabs/tabs.less
@@ -0,0 +1,660 @@
+.w-ecomp-tabs {
+  //.bg_portalWhite;//white for 1702
+  .bg_portalGray;  // gray for 1610
+
+  position: fixed;
+  left: 0;
+  right: 0;
+  bottom: @page-main-bottom;
+  overflow-y: hidden;
+  top: @header-height;
+  padding-left: 0;
+  height:50px;
+  z-index:101;
+  .go-button {
+    .btn-green;
+    width: 96px;
+    position: absolute;
+    border-radius: 0px;
+  }
+
+  .tabs-container {
+    .content_justify;
+    position: relative;
+    padding: 15px 0 32px 0;
+    width: 100%;
+
+    .tabs-title {
+      //.a24r;
+      .dBlue24r;  // AT&T Dark Blue
+      margin: auto;
+      .content_justify;
+    }
+    .portals-list {
+      min-height: 70vh;
+      //display: flex;
+      justify-content: center;
+      flex-flow: row wrap;
+      width: 1170px;
+
+      margin: auto;
+      margin-bottom: 63px;
+
+      .app-gridster-header {
+        background-color: @portalWhite;
+      }
+
+      .app-gridster-footer {
+        background-color: @portalWhite;
+      }
+
+      .portals-list-item {
+        background-color: @portalWhite;
+        border-radius: 2px;
+        box-shadow: 0px -1px 2px 0px rgba(0, 0, 0, 0.1);
+        display: inline-block;
+        width: 360px;
+        height: 300px;
+        background-size: cover;
+        cursor: pointer;
+        margin: 15px;
+        overflow: hidden;
+
+        .portals-item-info {
+          background-color: @portalWhite;
+          height: 120px;
+          top: 180px;
+          position: relative;
+          box-shadow: 0px -1px 2px 0px rgba(0, 0, 0, 0.1);
+          padding: 16px;
+
+          .info-title {
+            //.a24r;
+            .dBlue24r;  // AT&T Dark Blue
+            margin-bottom: 4px;
+            
+            text-overflow: ellipsis;
+            overflow: hidden;
+          }
+          .info-description {
+            .portalDBlue16r;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+            overflow: hidden;
+          }
+          .info-button {
+            .btn-green;
+            width: 96px;
+            position: absolute;
+            bottom: 16px;
+            left: 16px;
+          }
+
+          &:hover {
+            opacity: .93;
+            z-index: 3;
+          }
+        }
+      }
+    }
+  }
+}
+
+.w-ecomp-main-disclaimer {
+  text-align: center;
+  .dGray14r;
+  bottom: -37px;
+  width: 100%;
+  line-height: 1.5em;
+
+}
+//.build-number {
+//  .o12i;
+//}
+
+@keyframes fadein {
+  from {
+    opacity: 0;
+  }
+  to {
+    opacity: 1;
+  }
+}
+
+
+
+.tab-container {
+  background: @portalLGray;
+  margin: 0;
+  padding: 0;
+  max-height: 40px;
+}
+.tab-container ul.nav-tabs {
+  margin: 0;
+  list-style-type: none;
+  line-height: 40px;
+  max-height: 40px;
+  overflow: hidden;
+  display: inline-block;
+  display: -webkit-flex;
+  display: flex;
+  padding-right: 20px;
+  border-bottom: 5px solid @funcBkgGray;
+}
+.tab-container ul.nav-tabs > li {
+  margin: 5px -14px 0;
+  -moz-border-radius-topleft: 28px 145px;
+  -webkit-border-top-left-radius: 28px 145px;
+  border-top-left-radius: 28px 145px;
+  -moz-border-radius-topright: 28px 145px;
+  -webkit-border-top-right-radius: 28px 145px;
+  border-top-right-radius: 28px 145px;
+  padding: 0 30px 0 25px;
+  height: 170px;
+  background: #c3d5e6;
+  position: relative;
+  -moz-box-shadow: 0 4px 6px rgba(0, 0, 0, 0.5);
+  -webkit-box-shadow: 0 4px 6px rgba(0, 0, 0, 0.5);
+  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.5);
+  width: 200px;
+  max-width: 200px;
+  min-width: 20px;
+  border: 1px solid @portalGray;
+}
+.tab-container ul.nav-tabs > li:first-child {
+  margin-left: 0;
+}
+.tab-container ul.nav-tabs > li:last-of-type {
+  margin-right: 0;
+}
+.tab-container ul.nav-tabs > li > a {
+  display: block;
+  max-width: 100%;
+  text-decoration: none;
+  color: @portalBlack;
+  padding: 3px 7px;
+}
+.tab-container ul.nav-tabs > li > a span {
+  overflow: hidden;
+  white-space: nowrap;
+  display: block;
+}
+.tab-container ul.nav-tabs > li > a:focus, .tab-container ul.nav-tabs > li > a:hover {
+  background-color: transparent;
+  border-color: transparent;
+}
+.tab-container ul.nav-tabs > li > a .glyphicon-remove {
+  color: @portalDGray;
+  display: inline-block;
+  padding: 3px;
+  font-size: 10px;
+  position: absolute;
+  z-index: 10;
+  top: 7px;
+  right: -10px;
+  -moz-border-radius: 50%;
+  -webkit-border-radius: 50%;
+  border-radius: 50%;
+}
+.tab-container ul.nav-tabs > li > a .glyphicon-remove:hover {
+  background: @portalLPurple;
+  color: @portalWhite;
+  -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.25);
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.25);
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.25);
+  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25);
+}
+.tab-container ul.nav-tabs > li.active {
+  z-index: 4;
+  background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2Y3ZjdmNyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA==');
+  background-size: 100%;
+  background-image: -webkit-gradient(linear, 50% 0%, 50% 30, color-stop(0%, @portalWhite), color-stop(100%, @funcBkgGray));
+  background-image: -moz-linear-gradient(@portalWhite, @funcBkgGray 30px);
+  background-image: -webkit-linear-gradient(@portalWhite, @funcBkgGray 30px);
+  background-image: linear-gradient(@portalWhite, @funcBkgGray 30px);
+}
+.tab-container ul.nav-tabs > li.active > a {
+  background-color: transparent;
+  border-color: transparent;
+  border-bottom-color: transparent;
+}
+.tab-container ul.nav-tabs > li.active > a:focus, .tab-container ul.nav-tabs > li.active > a:hover {
+  background-color: transparent;
+  border-color: transparent;
+  border-bottom-color: transparent;
+}
+.tab-container ul.nav-tabs .btn {
+  float: left;
+  height: 20px;
+  width: 35px;
+  min-width: 35px;
+  max-width: 35px;
+  margin: 10px 0 0 0;
+  border-color: @portalLBlue;
+  outline: none;
+  -moz-transform: skew(30deg);
+  -ms-transform: skew(30deg);
+  -webkit-transform: skew(30deg);
+  transform: skew(30deg);
+}
+.tab-container ul.nav-tabs .btn.btn-default {
+  background: #c3d5e6;
+}
+.tab-container ul.nav-tabs .btn.btn-default:hover {
+  background: #d2deeb;
+}
+.tab-container ul.nav-tabs .btn.btn-default:active {
+  background: #9cb5cc;
+}
+.tab-container .tab-pane {
+  padding: 0px 0px;
+  text-align: center;
+}
+.tab-container .tab-pane.active {
+  border-top: 1px solid #ddd;
+}
+
+.tab-container md-content {
+  background-color: transparent !important; }
+  .tab-container md-content md-tabs {
+    border: 1px solid #e1e1e1; }
+    .tab-container md-content md-tabs md-tab-content {
+      background: #f6f6f6; }
+    .tab-container md-content md-tabs md-tabs-canvas {
+      background: white; }
+  .tab-container md-content h1:first-child {
+    margin-top: 0; }
+.tab-container md-input-container {
+  padding-bottom: 0; }
+.tab-container .remove-tab {
+  margin-bottom: 40px; }
+.tab-container .demo-tab > div > div {
+  padding: 25px;
+  box-sizing: border-box; }
+.tab-container .edit-form input {
+  width: 100%; }
+.tab-container md-tabs {
+  border-bottom: 1px solid rgba(0, 0, 0, 0.12); }
+.tab-container md-tab[disabled] {
+  opacity: 0.5; }
+.tab-container label {
+  text-align: left; }
+.tab-container .long > input {
+  width: 264px; }
+.tab-container .md-button.add-tab {
+  transform: translateY(5px); }
+
+.md-tab{
+	font-size: 13px;
+    line-height: 30px;
+    margin: 5px -3px 0;
+    border-top-left-radius: 88px 205px;
+    border-top-right-radius: 88px 205px;
+    padding: 0 30px 0 25px;
+    height: 40px;
+    background: @portalLGray;
+    position: relative;
+    box-shadow: 0 4px 6px rgba(0,0,0,.5);
+    width: 180px;
+    max-width: 200px;
+    min-width: 20px;
+    border: 1px solid #aaa;
+    text-transform: capitalize;
+    text-align: left;
+}
+
+md-tabs .md-tab {
+	color: #222;
+}
+
+md-tabs-canvas {
+    border-bottom: 5px solid #f7f7f7;
+    height: 40px;
+}
+.md-tab.md-active {
+  	z-index: 4;
+  	background-color: #f5f5f5 !important;
+}
+.md-tab:first-child{
+	margin-left: 10px;
+}
+md-ink-bar{
+	z-index: 5 !important;
+}
+
+.glyphicon {
+      position: fixed;
+      line-height: 4;
+}
+.close_button {
+	font-size: x-small;width: 10px;
+    margin-left: 130px;
+}
+@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
+	.close_button {
+		font-size: x-small;width: 10px;margin-left: 130px;margin-top: 55px;
+	}
+}
+
+*,
+*:after,
+*:before
+{
+  -moz-box-sizing: border-box;
+  -webkit-box-sizing: border-box;
+  box-sizing: border-box;
+}
+
+
+.button-default
+{
+  .transition(@transitionDefault color);
+  background: transparent;
+  border: none;
+  cursor: pointer;
+  margin: 0;
+  outline: none;
+  position: relative;
+}
+
+.show-notifications
+{
+  position: relative;
+
+  &:hover,
+  &:focus,
+  &.active
+  {
+    #icon-bell
+    {
+      fill: @colorWetAsphalt;
+    }
+  }
+
+  #icon-bell
+  {
+    fill: @colorAsbestos;
+  }
+
+
+
+  &.active ~ .notifications
+  {
+    opacity: 1;
+    top: 35px;
+  }
+}
+.notifications_li{
+    border-top: 1px solid #bdc3c7;
+    color: #7f8c8d;
+    cursor: default;
+    display: block;
+    padding: 10px;
+    position: relative;
+    white-space: nowrap;
+    width: 350px;
+}
+.notifications_li:hover{
+	background:#eee;
+}
+.notifications_detail{
+	margin-left: 10px;
+    white-space: normal;
+    width: 280px;
+    display: inline-block;
+    vertical-align: middle;
+}
+
+.notifications_empty{
+    display: none;
+    text-align: center;
+}
+.notifications_title{
+    display: block;
+}
+
+.notifications_date{
+    color: #95a5a6;
+    font-size: .85em;
+    margin-top: 3px;
+}
+.notifications
+{
+  .border-radius(@borderRadius);
+  .transition(@transitionDefault opacity);
+  background: @colorClouds;
+  border: 1px solid @colorSilver;
+  left: 10px;
+  opacity: 0;
+ // position: absolute;
+  //top: -999px;
+
+  &:after
+  {
+    border: 10px solid transparent;
+    border-bottom-color: @colorPeterRiver;
+    content: '';
+    display: block;
+    height: 0;
+    left: 10px;
+    position: absolute;
+    top: -20px;
+    width: 0;
+  }
+
+  h3,
+  .show-all
+  {
+    background: @colorPeterRiver;
+    color: @colorWhite;
+    margin: 0;
+    padding: 10px;
+    width: 350px;
+  }
+
+  h3
+  {
+    cursor: default;
+    font-size: 1.05em;
+    font-weight: normal;
+  }
+
+  .show-all
+  {
+    display: block;
+    text-align: center;
+    text-decoration: none;
+
+    &:hover,
+    &:focus
+    {
+      text-decoration: underline;
+    }
+  }
+
+  .notifications-list
+  {
+    list-style: none;
+    margin: 0;
+    overflow: hidden;
+    padding: 0;
+
+    .item
+    {
+      .transition-transform(@transitionDefault);
+      border-top: 1px solid @colorSilver;
+      color: @colorAsbestos;
+      cursor: default;
+      display: block;
+      padding: 10px;
+      position: relative;
+      white-space: nowrap;
+      width: 350px;
+
+      &:before,
+      .details,
+      .button-dismiss
+      {
+        display: inline-block;
+        vertical-align: middle;
+      }
+
+      &:before
+      {
+        .border-radius(50%);
+        background: @colorPeterRiver;
+        content: '';
+        height: 8px;
+        width: 8px;
+      }
+
+      .details
+      {
+        margin-left: 10px;
+        white-space: normal;
+        width: 280px;
+
+        .title,
+        .date
+        {
+          display: block;
+        }
+
+        .date
+        {
+          color: @colorConcrete;
+          font-size: .85em;
+          margin-top: 3px;
+        }
+      }
+
+      .button-dismiss
+      {
+        color: @colorSilver;
+        font-size: 2.25em;
+
+        &:hover,
+        &:focus
+        {
+          color: @colorConcrete;
+        }
+      }
+
+      &.no-data
+      {
+        display: none;
+        text-align: center;
+
+        &:before
+        {
+          display: none;
+        }
+      }
+
+      &.expired
+      {
+        color: @colorSilver;
+
+        &:before
+        {
+          background: @colorSilver;
+        }
+
+        .details
+        {
+          .date
+          {
+            color: @colorSilver;
+          }
+        }
+      }
+
+      &.dismissed
+      {
+        .transform(translateX(100%));
+      }
+    }
+  }
+
+  &.empty
+  {
+    .notifications-list
+    {
+      .no-data
+      {
+        display: block;
+        padding: 10px;
+      }
+    }
+  }
+}
+
+/* variables */
+@colorClouds: #ecf0f1;
+@colorSilver: #bdc3c7;
+@colorWhite: #fefefe;
+@colorPeterRiver: #3498db;
+@colorConcrete: #95a5a6;
+@colorAsbestos: #7f8c8d;
+@colorWetAsphalt: #34495e;
+
+@borderRadius: 2px;
+
+@transitionDefault: 0.25s ease-out 0.10s;
+
+/* mixins */
+.background-clip(@value: border-box)
+{
+    -moz-background-clip: @value;
+    -webkit-background-clip: @value;
+    background-clip: @value;
+}
+
+.border-radius(@value: 5px)
+{
+    -moz-border-radius: @value;
+    -webkit-border-radius: @value;
+    border-radius: @value;
+    .background-clip(padding-box);
+}
+
+.transform(@value)
+{
+  -webkit-transform: @value;
+  -moz-transform: @value;
+  -ms-transform: @value;
+  -o-transform: @value;
+  transform: @value;
+}
+
+.transition(@value: all 0.25s ease-out)
+{
+  -webkit-transition: @value;
+  -moz-transition: @value;
+  -o-transition: @value;
+  transition: @value;
+}
+
+.transition-transform(@transition: 0.25s ease-out)
+{
+  -webkit-transition: -webkit-transform @transition;
+  -moz-transition: -moz-transform @transition;
+  -o-transition: -o-transform @transition;
+  transition: transform @transition;
+}
+
+.tab-cross-sign{
+	position:relative;
+    margin-left: 126px;
+	top:-70px;
+	 @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
+ 	 top:-70px;
+	}    
+}
+
+#tab-cross-sign-icon{
+    margin: 48px 0px 0px 0px; 
+    width: 16.1px;
+    border-bottom-width:0px;
+    padding: 6px 6px 6px 3px;   
+}
diff --git a/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.Json.details.controller.js b/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.Json.details.controller.js
new file mode 100644
index 0000000..dd11c60
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.Json.details.controller.js
@@ -0,0 +1,36 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+

+(function () {

+

+    class userNotificationCtrl {

+        constructor($scope,   message,  ngDialog) {

+    		$scope.messageData=message.text;

+   			//alert("message.text" + $scope.messageData);

+         }

+    }

+    userNotificationCtrl.$inject = ['$scope',  'message', 'ngDialog'];

+    angular.module('ecompApp').controller('userNotificationCtrl', userNotificationCtrl);

+})();

+

+

+

+ 

diff --git a/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.Json.details.modal.page.html b/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.Json.details.modal.page.html
new file mode 100644
index 0000000..2c22751
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.Json.details.modal.page.html
@@ -0,0 +1,48 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="w-ecomp-user-json-notification-details-admin"

+	ng-style="{bottom: tabBottom}">

+	 <div class="w-ecomp-main-container" >

+	<div class="user-notification" >

+		<div id="'widgets-details-title" class="w-ecomp-main-json-view-title"> Message Notification Details </div>

+	

+	

+<div class="notifications-properties-main">

+		

+		<div ng-bind-html="messageData"></div>

+		</div>

+		

+            <div class="dialog-control-close">

+               

+                <button id="div-cancel-button" class="btn btn-alt btn-small" ng-click="closeThisDialog()">Close</button>

+            </div>

+		

+			</div>

+

+</div>

+</div>

+

+<script type="application/javascript">

+    $(document).ready(function(){

+        $(".ngdialog-content").css("width","40%")

+        $(".ngdialog-content").css("height","450px")

+

+    });

+</script>

diff --git a/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.controller.js b/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.controller.js
new file mode 100644
index 0000000..51c7bb7
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.controller.js
@@ -0,0 +1,196 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+

+(function () {

+

+    class userNotificationsCtrl {

+        constructor($scope, $log, notificationService, confirmBoxService, $modal, ngDialog, $state) {

+

+        	var priorityItems={"1":"Normal","2":"Important"};

+        	$scope.priorityItems=priorityItems;

+        	$scope.searchString='';

+            $scope.externalNotification="External System";

+        	$scope.itemExpired={"background-color":"silver "};

+        	$scope.showInput = true;

+        	$scope.totalPages1 = 0;

+        	$scope.viewPerPage1 = 15;

+        	$scope.currentPage1 = 1;

+        	$scope.showLoader = false;

+        	$scope.firstPlay = true;

+        	// Start with empty list to silence error in console

+        	$scope.tableData = [];

+        	$scope.tableAdminNotifItems = [];

+        	 let getAdminNotifications = () => {

+               $scope.isLoadingTable = true;

+        	notificationService.getAdminNotification().then(res => {

+                $scope.adminNotifications = res.data;

+                $scope.isLoadingTable = false;

+                $scope.tableData = res.data;

+    			var totalItems = $scope.tableData.length;

+    			$scope.totalPages1  = Math.ceil(totalItems / $scope.viewPerPage1);

+    			$scope.showLoader = false;

+    			$scope.currentPage1=1;

+    			var endIndex = 1 * $scope.viewPerPage1;

+    			var startIndex = endIndex - $scope.viewPerPage1;

+    			$scope.tableAdminNotifItems = $scope.tableData.slice(startIndex, endIndex); 

+            	}).catch(err => {

+                $log.error('userNotificationsCtrl:getAdminNotifications:: error ', err);

+                $scope.isLoadingTable = false;

+            });

+             }

+        	 

+      	   getAdminNotifications();           

+

+        	$scope.customPageHandler = function(num) {

+        		$scope.currentPage1=num;

+        		var endIndex = num * $scope.viewPerPage1;

+        		var startIndex = endIndex - $scope.viewPerPage1;

+        		$scope.tableAdminNotifItems = $scope.tableData.slice(startIndex, endIndex);

+        	};

+        	  

+

+          

+

+           	

+           $scope.removeUserNotification = function (selectedAdminNotification) {

+                selectedAdminNotification.activeYn = 'N';

+                confirmBoxService.deleteItem(selectedAdminNotification.msgHeader)

+                    .then(isConfirmed => {

+                        if (isConfirmed) {

+                            notificationService.updateAdminNotification(selectedAdminNotification)

+                                .then(() => {

+                                    getAdminNotifications();

+                                }).catch(err => {

+                                    switch (err.status) {

+                                        case '409':         // Conflict

+                                            // handleConflictErrors(err);

+                                            break;

+                                        case '500':         // Internal Server

+															// Error

+                                            confirmBoxService.showInformation('There was a problem updating the notification. ' +

+                                                'Please try again later. Error: ' + err.status).then(isConfirmed => { });

+                                            break;

+                                        case '403':         // Forbidden...

+															// possible

+															// webjunction error

+															// to try again

+                                            confirmBoxService.showInformation('There was a problem updating the notification. ' +

+                                                'Please try again. If the problem persists, then try again later. Error: ' + err.status).then(isConfirmed => { });

+                                            break;

+                                        default:

+                                            confirmBoxService.showInformation('There was a problem updating the notification. ' +

+                                                'Please try again. If the problem persists, then try again later. Error: ' + err.status).then(isConfirmed => { });

+                                    }

+                                    $log.error('UserNotifCtlr::updateAdminNOtif failed: ' + JSON.stringify(err));

+                                }).finally(() => {

+                                    var objOffsetVersion = objAgent.indexOf("MSIE");

+                                    if (objOffsetVersion != -1) {

+                                        $window.location.reload();       

+                                    }

+                                });

+                        }

+                    }).catch(err => {

+                        $log.error('UserNotifCtlr::deleteItem error: ' + err);

+                    });

+            }

+           

+

+          

+        	 $scope.showDetailedJsonMessage=function (selectedAdminNotification) {

+        		 notificationService.getMessageRecipients(selectedAdminNotification.notificationId).then(res =>{

+                     $scope.messageRecipients = res;

+				 var messageObject=JSON.parse(selectedAdminNotification.msgDescription);

+				 var html="";

+				 html+='<p>'+'Message Source'+' : '+selectedAdminNotification.msgSource+'</p>';

+				 html+='<p>'+'Message Title'+' : '+selectedAdminNotification.msgHeader+'</p>';

+				 html+='<p>'+'Message Recipient'+' : '+$scope.messageRecipients+'</p>';

+

+				 for(var field in  messageObject){

+					 if(field=='eventDate'||field=='lastModifiedDate'){

+						 html+='<p>'+field+' : '+new Date(+messageObject[field])+'</p>';

+						  

+					 }else{

+					 html+='<p>'+field+' : '+messageObject[field]+'</p>';

+					 

+					 }

+				 }

+

+ 		     var modalInstance = ngDialog.open({

+ 				    templateUrl: 'app/views/user-notifications-admin/user.notifications.Json.details.modal.page.html',

+ 				    controller: 'userNotificationCtrl',

+ 				    resolve: {

+ 				    	message: function () {

+ 				    		var message = {

+ 				    			   title:    '',

+ 		                       		text:    html

+ 		                       		

+ 		                           	};

+ 				          return message;

+ 				        },

+ 				     

+ 				      }

+ 				  }); 

+ 		     

+        	 }).catch(err => {

+                 $log.error('userNotificationsCtrl:getMessageRecipients:: error ', err);

+                 $scope.isLoadingTable = false;

+             });

+

+ 			 };

+    

+			 

+            $scope.editUserNotificationModal = function (selectedAdminNotification) {

+

+                // retrieve roleIds here

+                selectedAdminNotification.roleIds = null;

+                notificationService.getNotificationRoles(selectedAdminNotification.notificationId)

+                    .then(res => {

+                        selectedAdminNotification.roleIds = res.data;

+

+                        $scope.openUserNotificationModal(selectedAdminNotification);

+                    }).catch(err => {

+                        $log.error('UserNotifCtlr:getNotificationRoles:: error ', err);

+

+                    });

+            }

+

+            $scope.openUserNotificationModal = function (selectedAdminNotification) {

+                let data = null;

+                if (selectedAdminNotification) {

+                    data = {

+                        notif: selectedAdminNotification

+                    }

+                }

+                ngDialog.open({

+                    templateUrl: 'app/views/user-notifications-admin/user.notifications.modal.page.html',

+                    controller: 'userNotificationsModalCtrl',

+                    controllerAs: 'userNotifModal',

+                    data: data

+                }).closePromise.then(function (needUpdate) {

+                    getAdminNotifications();

+                });

+            }

+

+        }

+    }

+    userNotificationsCtrl.$inject = ['$scope', '$log', 'notificationService', 'confirmBoxService', '$modal', 'ngDialog', '$state'];

+    angular.module('ecompApp').controller('userNotificationsCtrl', userNotificationsCtrl);

+})();

diff --git a/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.json.details.modal.page.less b/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.json.details.modal.page.less
new file mode 100644
index 0000000..ebf4807
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.json.details.modal.page.less
@@ -0,0 +1,48 @@
+

+.w-ecomp-user-json-notification-details-admin {

+ .bg_portalWhite;//white for 1702

+    //.bg_portalGray;  // gray for 1610

+    position: relative;

+    top: 20px;

+    left: -200px;

+    right: @page-main-right;

+    bottom: @page-main-bottom;

+    padding-top: @padding-top;

+    padding-left: @padding-left-side;

+    height: 345px;

+    width: 96%;

+    margin-left: 220px;

+  .w-ecomp-main-json-view-title {

+    //.n18r;

+    .dGray18r;  //AT&T Dark Gray

+    border-bottom: @portalDBlue 3px solid;

+    width:100%;

+  }

+  

+  .notifications-properties-main{

+     padding-top: 16px;

+    font-size: 14px;

+    overflow-y: scroll;

+    max-height: 285px;

+      

+  }

+input:not([type="button"]) {

+  height: 22px; }

+  .widget-properties-main {

+    padding: 16px;

+    height: 460px;

+    overflow-y: auto;

+

+

+    

+

+  }

+

+.dialog-control-close {

+    position: absolute;

+    bottom: -60px;

+    right: 16px;

+}

+

+

+}
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.less b/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.less
new file mode 100644
index 0000000..ee5f9e4
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.less
@@ -0,0 +1,128 @@
+.w-ecomp-user-notification-admin{
+ .bg_portalWhite;//white for 1702
+    //.bg_portalGray;  // gray for 1610
+    position: @page-main-position;
+    top: @page-main-top;
+    left: @page-main-left;
+    right: @page-main-right;
+    bottom: @page-main-bottom;
+    padding-top: @padding-top;
+    overflow-y: @page-main-overflow-y;
+    padding-left: @padding-left-side;
+
+    .tab-bottom {
+        bottom: 0;
+    }
+   
+    .tablesorter-default .tablesorter-header .tablesorter-header-inner {
+    //background-image: url(images/upanddown.png);
+    background-position: center right;
+    background-repeat: no-repeat;
+    cursor: pointer;
+    white-space: normal;
+    display: inline-block;
+    vertical-align: baseline;
+    zoom: 1;
+    padding: -1px 50px;
+}
+
+
+p {
+    display: inline;
+}
+.userNotifTable {
+        width: @table-width;
+        //margin-left: @table-margin-left;
+        //margin: @table-margin;
+		margin:auto;
+        .delete-app{
+            .ico_trash_default;
+        }
+        }
+  .user-notification-container {
+    .content_justify;
+      position: relative;
+    padding: 15px 0 32px 0;
+    width: 100%;
+
+    .user-notification-list {
+        min-height: 70vh;
+        //display: flex;
+        justify-content: center;
+        flex-flow: row wrap;
+        width: @table-width;
+        margin: auto;
+        margin-bottom: 63px;
+
+        .app-gridster-header {
+        background-color: @portalWhite;
+               
+      }
+
+      .app-gridster-footer {
+        background-color: @portalWhite;
+      }
+
+      .user-notification-list-item {
+        background-color: @portalWhite;
+        border-radius: 2px;
+        box-shadow: 0px -1px 2px 0px rgba(0, 0, 0, 0.1);
+        display: inline-block;
+        width: 360px;
+        height: 300px;
+        background-size: cover;
+        cursor: pointer;
+        margin: 15px;
+        overflow: hidden;
+
+        .notification-item-info {
+          background-color: @portalWhite;
+          height: 120px;
+          top: 180px;
+          position: relative;
+          box-shadow: 0px -1px 2px 0px rgba(0, 0, 0, 0.1);
+          padding: 50px;
+
+          .info-title {
+            //.a24r;
+            .dBlue24r;  // AT&T Dark Blue
+            margin-bottom: 4px;
+            
+            text-overflow: ellipsis;
+            overflow: hidden;
+          }
+          .info-description {
+            .portalDBlue16r;  // omnes 16 regular
+            text-overflow: ellipsis;
+            white-space: nowrap;
+            overflow: hidden;
+          }
+          .info-button {
+            .btn-green;
+            width: 96px;
+            position: absolute;
+            bottom: 16px;
+            left: 16px;
+          }
+
+          &:hover {
+            opacity: .93;
+            z-index: 3;
+          }
+        }
+      }
+    }
+  }
+
+    .go-button {
+        .btn-green;
+        width: 96px;
+        position: absolute;
+        border-radius: 0px;
+    }
+    .user-notification-add-button {
+    width: 160px;
+    margin-bottom: 11px;
+  }
+
+}
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.modal.controller.js b/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.modal.controller.js
new file mode 100644
index 0000000..ebd2f93
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.modal.controller.js
@@ -0,0 +1,747 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+

+(function () {

+

+    class userNotificationsModalCtrl {

+        constructor($scope, $log, functionalMenuService, confirmBoxService, notificationService, $modal, ngDialog, $state, $filter) {

+

+            let newNotifModel = {

+                'isOnlineUsersOnly': null,

+                'isForAllRolesOptions': null,

+                'selectedPriority': null,

+                'isActive': null,

+                'startTime': null,

+                'endTime': null,

+                'msgHeader': null,

+                'msgDescription': null,

+                'roleIds': null,

+                'roleObj': {notificationRoleIds:null}

+            };

+            

+            $scope.notificationId = null;

+            $scope.selectedCat = null;

+            $scope.selectedEcompFunc = null;

+            this.YN_index_mapping = {

+                "Y": 0,

+                "N": 1

+            }

+            

+            $scope.onlineAllUsersOptions = [

+                { "index": 0, "value": "Y", "title": "Online Users Only" },

+                { "index": 1, "value": "N", "title": "Online & Offline Users" }

+                

+            ];

+

+            $scope.isForAllRolesOptions = [

+                { "index": 0, "value": "Y", "title": "Yes" },

+                { "index": 1, "value": "N", "title": "No" }

+            ];

+

+            $scope.priorityOptions  = [

+                { "index": 0, "value": 1, "title": "Normal" },

+                { "index": 1, "value": 2, "title": "Important" }

+            ];

+

+            $scope.isActiveOptions = [

+                { "index": 0, "value": "Y", "title": "Yes" },

+                { "index": 1, "value": "N", "title": "No" }

+            ];

+            $scope.isActive = $scope.isActiveOptions[0];

+            $scope.selectPriority = $scope.priorityOptions[0];

+            $scope.isOnlineUsersOnly = $scope.onlineAllUsersOptions[1];

+          $scope.isForAllRoles=$scope.isForAllRolesOptions[0].value;

+          $scope.isFunctionalMenu ="Y";

+

+          $scope.selectedPriority=$scope.priorityOptions[0].value;

+

+            // $scope.notificationRoleIds = [];

+            $scope.msgHeader = '';

+            $scope.msgDescription = '';

+            $scope.treeTitle="Functional Menu";

+            $scope.notifObj= {isCategoriesFunctionalMenu:true};

+

+            let init = () => {

+                // $log.info('userNotificationsModalCtrl::init');

+                this.isSaving = false;

+                var today = new Date();

+                $scope.minDate = today.toISOString().substring(0, 10);

+                var threeMonthsFromNow = new Date();

+                threeMonthsFromNow.setMonth(threeMonthsFromNow.getMonth() + 3);

+                $scope.maxDate = threeMonthsFromNow.toISOString().substring(0, 10);

+                if ($scope.ngDialogData && $scope.ngDialogData.notif) {

+                    // $log.debug('userNotificationsModalCtrl:init:: Edit

+					// notification mode for', $scope.ngDialogData.notif);

+                    $scope.isEditMode = true;

+                    $scope.editModeObj = {isEditMode: true};

+                    this.notif = _.clone($scope.ngDialogData.notif);

+                    $scope.modalPgTitle = 'View Notification'

+                    $scope.isOnlineUsersOnly = $scope.onlineAllUsersOptions[this.YN_index_mapping[this.notif.isForOnlineUsers]];

+                    $scope.isForAllRoles = $scope.isForAllRolesOptions[this.YN_index_mapping[this.notif.isForAllRoles]].value;

+                    $scope.isActive = $scope.isActiveOptions[this.YN_index_mapping[this.notif.activeYn]];

+                    $scope.selectedPriority = $scope.priorityOptions[this.notif.priority - 1].value;

+                    $scope.startTime = new Date(this.notif.startTime);

+                    $scope.endTime = new Date(this.notif.endTime);

+                    $scope.msgHeader = this.notif.msgHeader;

+                    $scope.msgDescription = this.notif.msgDescription;

+                    $scope.notificationId = this.notif.notificationId;

+                    $scope.notificationRoleIds = this.notif.roleIds;

+                    $scope.roleObj = {notificationRoleIds:this.notif.roleIds};

+                } else {

+                	// $log.debug('AppDetailsModalCtrl:init:: New app mode');

+                    $scope.isEditMode = false;

+                    $scope.editModeObj = {isEditMode: false};

+                    $scope.modalPgTitle = 'Add a New Notification'

+                    this.notif = _.clone(newNotifModel);

+                    $scope.roleObj = {notificationRoleIds:null};

+                }

+            };

+            this.conflictMessages = {};

+            this.scrollApi = {};

+            let handleConflictErrors = err => {

+                if(!err.data){

+                    return;

+                }

+                if(!err.data.length){ // support objects

+                    err.data = [err.data]

+                }

+                _.forEach(err.data, item => {

+                    _.forEach(item.fields, field => {

+                        // set conflict message

+                        this.conflictMessages[field.name] = errorMessageByCode[item.errorCode];

+                        // set field as invalid

+                        $scope.appForm[field.name].$setValidity('conflict', false);

+                        // set watch once to clear error after user correction

+                        watchOnce[field.name]();

+                    });

+                });

+                this.scrollApi.scrollTop();

+            };

+

+            let resetConflict = fieldName => {

+                delete this.conflictMessages[fieldName];

+                if($scope.appForm[fieldName]){

+                    $scope.appForm[fieldName].$setValidity('conflict', true);

+                }

+            };

+            $scope.addUserNotificationValidation = function () {

+                // // pre-processing

+                if (!($scope.isEditMode)) {                    

+                    var validation=false;

+

+               if($scope.startTime && $scope.endTime && $scope.msgHeader != '' && $scope.msgDescription != '' && ($scope.startTime<$scope.endTime)){

+            	   validation=true;

+            	   if( $scope.isForAllRoles=='N'){

+                       validation =  $scope.checkBoxObj.isAnyRoleSelected;

+                   }

+               }

+               else{

+            	   validation=false;

+            	   }

+                    

+                    	

+                   return !validation; 

+                }

+            }

+            

+            /* format the value for viewing a notification */

+            $scope.formatStartDate = function () {

+            	if ($scope.startTime) {

+            		$scope.startTime = $filter('date')($scope.startTime, 'medium'); 

+           	}            	

+            }

+            

+            /* format the value for viewing a notification */

+            $scope.formatEndDate = function () {

+            	if($scope.endTime){

+            		$scope.endTime = $filter('date')($scope.endTime, 'medium');

+            	}

+            }

+            

+            $scope.addUserNotification = function () {

+                $scope.notificationRoleIds = [];

+                // pre-processing

+                for (var key in $scope.checkboxIdDict) {

+                    if ($scope.checkboxIdDict[key].is_box_checked && ($scope.checkboxIdDict[key].role_id != null)) {

+                        var role_ids = $scope.checkboxIdDict[key].role_id;

+                        for (var i in role_ids) {

+                            if (!($scope.notificationRoleIds.indexOf(role_ids[i]) > -1)) {

+                                $scope.notificationRoleIds.push(role_ids[i]);

+                            }

+                        }

+                    }

+                }

+

+                $scope.notificationRoleIds.sort();

+                if (($scope.isOnlineUsersOnly) && ($scope.isForAllRoles) && ($scope.selectedPriority) && ($scope.isActive)

+                    && ($scope.startTime) && ($scope.endTime) && ($scope.msgHeader != '') && ($scope.msgDescription != '')) {

+                    this.newUserNotification =

+                        {

+                            'notificationId':$scope.notificationId,

+                            'isForOnlineUsers': $scope.isOnlineUsersOnly.value,

+                            'isForAllRoles': $scope.isForAllRoles,

+                            'priority': $scope.selectedPriority,

+                            'activeYn': $scope.isActive.value,

+                            'startTime': $scope.startTime,

+                            'endTime': $scope.endTime,

+                            'msgHeader': $scope.msgHeader,

+                            'msgDescription': $scope.msgDescription,

+                            'roleIds': $scope.notificationRoleIds,

+                            'createdDate': new Date()

+                        };

+

+                    // POST ajax call here;

+                    if ($scope.isEditMode) {

+                        notificationService.updateAdminNotification(this.newUserNotification)

+                            .then(() => {

+                                //$log.debug('NotificationService:updateAdminNotification:: Admin notification update succeeded!');

+                                $scope.closeThisDialog(true);

+                                // emptyCookies();

+                            }).catch(err => {

+                                $log.error('notificationService.updateAdminNotfication failed: ' + JSON.stringify(err));

+                                switch (err.status) {

+                                    case '409':         // Conflict

+                                        // handleConflictErrors(err);

+                                        break;

+                                    case '500':         // Internal Server Error

+                                        confirmBoxService.showInformation('There was a problem updating the notification. ' +

+                                            'Please try again later. Error: ' + err.status).then(isConfirmed => { });

+                                        break;

+                                    case '403':         // Forbidden... possible

+														// webjunction error to

+														// try again

+                                        confirmBoxService.showInformation('There was a problem updating the notification. ' +

+                                            'Please try again. If the problem persists, then try again later. Error: ' + err.status).then(isConfirmed => { });

+                                        break;

+                                    default:

+                                        confirmBoxService.showInformation('There was a problem updating the notification. ' +

+                                            'Please try again. If the problem persists, then try again later. Error: ' + err.status).then(isConfirmed => { });

+                                }

+                            }).finally(() => {

+                                // for bug in IE 11

+                                var objOffsetVersion = objAgent.indexOf("MSIE");

+                                if (objOffsetVersion != -1) {

+                                    $log.debug('AppDetailsModalCtrl:updateOnboardingApp:: Browser is IE, forcing Refresh');

+                                    $window.location.reload();   

+                                }

+                                // for bug in IE 11

+                            });

+

+                    } else {

+                        notificationService.addAdminNotification(this.newUserNotification)

+                            .then((res) => {

+                                $log.debug('notificationService:addAdminNotification:: Admin notification creation succeeded!,',res);

+                                if(res.status=='ERROR'){

+                                	 confirmBoxService.showInformation('There was a problem adding the notification. ' +

+                                             ' Error: ' + res.response).then(isConfirmed => { });

+

+                                	

+                                }

+                                else{

+                                	 $scope.closeThisDialog(true);

+                                }

+                               

+                                // emptyCookies();

+                            }).catch(err => {

+                                switch (err.status) {

+                                    case '409':         // Conflict

+                                        // handleConflictErrors(err);

+                                        break;

+                                    case '500':         // Internal Server Error

+                                        confirmBoxService.showInformation('There was a problem adding the notification. ' +

+                                            'Please try again later. Error: ' + err.status).then(isConfirmed => { });

+                                        break;

+                                    default:

+                                        confirmBoxService.showInformation('There was a problem adding the notification. ' +

+                                            'Please try again. If the problem persists, then try again later. Error: ' +

+                                            err.status).then(isConfirmed => { });

+                                }

+                                $log.error('notificationService:addAdminNotification error:: ' + JSON.stringify(err));

+                            })

+                    }

+

+                   

+                } else {

+                    $log.warn('please fill in all required fields');

+                	confirmBoxService.showInformation('Please fill in all required fields').then(isConfirmed => { });

+                }

+            }

+            // Populate the category list for category dropdown list

+            let getFunctionalMenu = () => {

+                this.isLoadingTable = true;

+                $scope.notifObj= {isCategoriesFunctionalMenu:true};

+                functionalMenuService.getFunctionalMenuRole().then(role_res => {

+                    var menu_role_dict = {};

+                    for (var i in role_res) {

+                        // if first time appear in menu_role_dict

+                        if (!(role_res[i].menuId in menu_role_dict)) {

+                            menu_role_dict[role_res[i].menuId] = [role_res[i].roleId];

+                        } else {

+                            menu_role_dict[role_res[i].menuId].push(role_res[i].roleId);

+                        }

+                    }

+                    functionalMenuService.getManagedFunctionalMenuForNotificationTree().then(res => {

+                        let actualData = [];

+                        var exclude_list = ['Favorites']

+                        // Adding children and label attribute to all objects in

+                        $scope.checkboxIdDict = {};

+                        $scope.checkBoxObj = {isAnyRoleSelected:false};

+                        for (let i = 0; i < res.length; i++) {

+                            res[i].children = [];

+                            res[i].label = res[i].text;

+                            res[i].id = res[i].text;

+                            // res[i].is_box_checked = false;

+                            res[i].can_check = true;

+                            res[i].roleId = menu_role_dict[res[i].menuId];

+                            $scope.checkboxIdDict[res[i].id] = { 'is_box_checked': false, 'role_id': res[i].roleId };

+                        }

+

+                        // Adding actual child items to children array in res

+						// objects

+                        $scope.parentChildDict ={};

+                        $scope.parentChildRoleIdDict ={};

+                        for (let i = 0; i < res.length; i++) {

+                            let parentId = res[i].menuId;

+                            $scope.parentChildDict[parentId] = [];

+                            $scope.parentChildRoleIdDict[parentId]=[];

+                            for (let j = 0; j < res.length; j++) {

+                                let childId = res[j].parentMenuId;

+                                if (parentId === childId) {

+                                    res[i].children.push(res[j]);

+                                    $scope.parentChildDict[parentId].push(res[j].menuId);

+                                    //if res[j].roleId is defined

+                                    if (res[j].roleId) {

+                                    	for (let k in res[j].roleId) {

+                                            $scope.parentChildRoleIdDict[parentId].push(res[j].roleId[k]);

+                                    	}

+                                    	

+                                    }

+                                }

+                            }

+                        }

+                        

+                        //check if grand children exist

+                            for (var key in $scope.parentChildDict){

+                            	var children = $scope.parentChildDict[key];

+                            	var isGrandParent = false;

+                            	if (children.length>0) {

+                            		for (var i in children) {

+                            			if ($scope.parentChildDict[children[i]].length>0){

+                            				isGrandParent = true;

+                            				break;

+                            			}

+                            		}

+                            	}

+                            	if (isGrandParent) {

+                                	for (var i in children) {

+                                		// if the child has children

+                                		if ($scope.parentChildDict[children[i]].length>0) {

+                                			for (var j in $scope.parentChildRoleIdDict[children[i]]) {

+                                				

+                                				if ($scope.parentChildRoleIdDict[key].indexOf($scope.parentChildRoleIdDict[children[i]][j]) === -1) {

+                                					$scope.parentChildRoleIdDict[key].push($scope.parentChildRoleIdDict[children[i]][j]);

+                                				}

+                                			}

+                                		} else {

+                                			 

+                                		}

+                                	}

+                            	}

+

+                            };                            

+                                                        

+

+                                var ListMenuIdToRemove = [];

+                        //$scope.parentObj = {ListMenuIdToRemove : []};

+                        //get the list of menuId that needs to be removed 

+                        for (let i = 0; i < res.length; i++) {

+                            if ((res[i].children.length==0)&&(!res[i].roleId)) {

+                                var menuIdToRemove = res[i].menuId;

+                                if (ListMenuIdToRemove.indexOf(menuIdToRemove) === -1){

+                                    ListMenuIdToRemove.push(menuIdToRemove);

+                                }

+                            }

+                        }

+

+                        // a scope variable that marks whether each functional menu item should be displayed.

+                        $scope.toShowItemDict = {};                        

+                        for (let i = 0; i < res.length; i++) {

+                            if (res[i].roleId==null) {

+                                if (res[i].children.length==0) {

+                                    $scope.toShowItemDict[res[i].menuId]=false;                                

+                                } else if(res[i].children.length>0){

+                                    if($scope.parentChildDict[res[i].menuId].length === _.intersection($scope.parentChildDict[res[i].menuId], ListMenuIdToRemove).length){

+                                        $scope.toShowItemDict[res[i].menuId]=false;                                

+                                    } else {

+                                        $scope.toShowItemDict[res[i].menuId]=true;                                

+                                    }

+                                }

+                            } else {

+                                $scope.toShowItemDict[res[i].menuId]=true;

+                            }

+                        }

+ 

+                        // Sort the top-level menu items in order based on the

+						// column

+                        res.sort(function (a, b) {

+                            return a.column - b.column;

+                        });

+

+                        // Sort all the children in order based on the column

+                        for (let i = 0; i < res.length; i++) {

+                            res[i].children.sort(function (a, b) {

+                                return a.column - b.column;

+                            });

+                        }

+

+                        // Forming actual parent items

+                        for (let i = 0; i < res.length; i++) {

+                            let parentId = res[i].parentMenuId;

+                            if (parentId === null) {

+                                actualData.push(res[i]);

+                            }

+                        }

+

+                        // $scope.treedata = actualData;

+                        var treedata = actualData[0].children;

+                        $scope.treedata = [];

+                        for (var i in treedata) {

+                            if (!(treedata[i].label.indexOf(exclude_list) > -1)) {

+                                $scope.treedata.push(treedata[i])

+                            }

+                        }

+

+                    }).catch(err => {

+                        $log.error('FunctionalMenuCtrl:getFunctionalMenu:: error ', err);

+                    }).finally(() => {

+                        this.isLoadingTable = false;

+                    })

+

+                }).catch(err => {

+                    $log.error('FunctionalMenuCtrl:getFunctionalMenu:: error ', err);

+                })

+                    ;

+            }

+     

+            

+            let getAppRoleIds = () => {

+                $scope.notifObj= {isCategoriesFunctionalMenu:false};

+                notificationService.getAppRoleIds().then(res => {

+                	

+                    res = res.data;

+                    let actualData = [];

+                    // var exclude_list = ['Favorites']

+                    var app_id_name_list = {};

+                        $scope.checkboxIdDict = {};

+                        $scope.checkBoxObj = {isAnyRoleSelected:false};

+

+                    for (let i = 0; i < res.length; i++) {

+                        if (!(res[i].appId in app_id_name_list)) {

+                            app_id_name_list[res[i].appId] = res[i].appName;

+                        }

+

+                        res[i].children = [];

+                        res[i].label = res[i].roleName;

+                        res[i].id = res[i].roleId;

+                        res[i].menuId = res[i].roleId;

+                        res[i].parentMenuId = res[i].appId;

+                        res[i].can_check = true;

+                        res[i].roleId = [res[i].roleId];

+                        $scope.checkboxIdDict[res[i].id] = { 'is_box_checked': false, 'role_id': res[i].roleId};   

+                    }

+                    

+                    for (var app_id in app_id_name_list) {

+                        var new_res = {};

+                        new_res.children = [];

+                        new_res.label = app_id_name_list[app_id];

+                        new_res.id = app_id;

+                        new_res.menuId = app_id;

+                        new_res.parentMenuId = null;

+                        new_res.appId = null;

+                        new_res.can_check = true;

+                        new_res.roleId = null;

+                        $scope.checkboxIdDict[new_res.id]= { 'is_box_checked': false, 'role_id': new_res.roleId };

+                        res.push(new_res);

+                    }

+                    $scope.parentChildRoleIdDict ={};

+                    //Adding actual child items to children array in res objects

+                    for (let i = 0; i < res.length; i++) {

+                        let parentId = res[i].menuId;

+                        $scope.parentChildRoleIdDict[parentId]=[];

+                        for (let j = 0; j < res.length; j++) {

+                            let childId = res[j].parentMenuId;

+                            if (parentId == childId) {

+                                res[i].children.push(res[j]);

+                                if (res[j].roleId) {

+                                	for (let k in res[j].roleId) {

+                                        $scope.parentChildRoleIdDict[parentId].push(res[j].roleId[k]);

+                                	}

+                                	

+                                }

+                            }

+                        }

+                    }

+                    //Forming actual parent items

+                    for (let i = 0; i < res.length; i++) {

+                        let parentId = res[i].parentMenuId;

+                        if (parentId === null) {

+                            actualData.push(res[i]);

+                        }

+                    }

+

+                    $scope.treedata = actualData;

+                }).catch(err => {

+                    $log.error('FunctionalMenuCtrl:getFunctionalMenu:: error ', err);

+                }).finally(() => {

+                    this.isLoadingTable = false;

+                })

+            }

+            $scope.getFunctionalMenu= function() {

+            $scope.treeTitle="Functional Menu";

+            getFunctionalMenu();

+            }

+            $scope.getAppRoleIds = function() {

+            $scope.treeTitle="Applications/Roles";

+                getAppRoleIds();

+            }

+             

+            init();

+            getFunctionalMenu();

+

+        }

+        

+    }

+    

+    userNotificationsModalCtrl.$inject = ['$scope', '$log', 'functionalMenuService', 'confirmBoxService', 'notificationService', '$modal', 'ngDialog', '$state', '$filter'];

+    angular.module('ecompApp').controller('userNotificationsModalCtrl', userNotificationsModalCtrl);

+    

+    angular.module('ecompApp').directive('attDatepickerCustom', ['$log', function($log) {

+        return {

+            restrict: 'A',

+          require: 'ngModel',

+            scope: {},

+            

+            controller: ['$scope', '$element', '$attrs', '$compile', 'datepickerConfig', 'datepickerService', function($scope, $element, $attrs, $compile, datepickerConfig, datepickerService) {

+               var dateFormatString = angular.isDefined($attrs.dateFormat) ? $scope.$parent.$eval($attrs.dateFormat) : datepickerConfig.dateFormat;

+               var selectedDateMessage = '<div class="sr-focus hidden-spoken" tabindex="-1">the date you selected is {{$parent.current | date : \'' + dateFormatString + '\'}}</div>';

+               $element.removeAttr('att-datepicker-custom');

+                $element.removeAttr('ng-model');

+                $element.attr('ng-value', '$parent.current |  date:"EEEE, MMMM d, y"');

+                $element.attr('aria-describedby', 'datepicker');

+               

+                $element.attr('maxlength', 10);

+

+                var wrapperElement = angular.element('<div></div>');

+                wrapperElement.attr('datepicker-popup', '');

+                wrapperElement.attr('current', 'current');

+

+                datepickerService.setAttributes($attrs, wrapperElement);

+                datepickerService.bindScope($attrs, $scope);

+

+                wrapperElement.html('');

+                wrapperElement.append($element.prop('outerHTML'));

+                if (navigator.userAgent.match(/MSIE 8/) === null) {

+                    wrapperElement.append(selectedDateMessage);

+                }

+                var elm = wrapperElement.prop('outerHTML');

+                elm = $compile(elm)($scope);

+                $element.replaceWith(elm);

+            }],

+            link: function(scope, elem, attr, ctrl) {

+                if (!ctrl) {

+                    // do nothing if no ng-model

+                    $log.error("ng-model is required.");

+                    return;

+                }

+

+                scope.$watch('current', function(value) {

+                    ctrl.$setViewValue(value);

+                });

+                ctrl.$render = function() {

+                    scope.current = ctrl.$viewValue;

+                };

+              

+            }

+        };

+    }]);

+

+    angular.module('ecompApp').directive('jqTreeUserNotif', ['functionalMenuService', '$log', 'confirmBoxService', '$compile', function (functionalMenuService, $log, confirmBoxService, $compile) {

+        return {

+            scope: true,

+            templateUrl: 'jq-tree-tmpl-user-notif.html',

+            link: function (scope, el, attrs) {

+

+                var $jqTree = el.find('#jqTreeUserNotif').tree({

+                    data: scope.treedata,

+                    autoOpen: scope.editModeObj.isEditMode,

+                    dragAndDrop: false,

+                    onCreateLi: function (node, $li) {

+                        node.is_checked = false;

+                        if (node.roleId&&scope.roleObj.notificationRoleIds) {

+                            node.is_checked = (node.roleId.length === _.intersection(node.roleId, scope.roleObj.notificationRoleIds).length);

+                        }                        

+                        if (typeof node.id =="string"){

+                            $li.attr('id', node.id.replace(/\s+/g, '_'));

+                        }

+                        var isChecked = '';

+                        if (node.is_checked) {

+                            isChecked = 'checked="checked"';

+                        }

+                        if (node.can_check) {

+                            var toShow = true;

+                            if (scope.notifObj.isCategoriesFunctionalMenu) {

+                                toShow = scope.toShowItemDict[node.menuId];

+                            }

+                            var isDisabled = "";

+                            if (scope.editModeObj.isEditMode) {

+                                isDisabled = " disabled"

+                                	

+                                	//if node is a parent/grandparent node

+                                    if (node.children.length>0){

+                                             	//whether to show node first

+                                    	if (_.intersection(scope.parentChildRoleIdDict[node.menuId], scope.roleObj.notificationRoleIds).length) {

+                                    		toShow=true;

+                                    		if (scope.parentChildRoleIdDict[node.menuId].length==_.intersection(scope.parentChildRoleIdDict[node.menuId], scope.roleObj.notificationRoleIds).length) {

+                                                isChecked = 'checked="checked"';                                			

+                                    		}

+                                    	} else {

+                                    		toShow=false;

+                                    	}

+                                    } 

+                                    //if node is a child node

+                                    else {

+                                    	if (node.is_checked) {

+                                        	toShow=true;                                	

+                                    	} else {

+                                    		toShow=false;

+                                    	}

+                                    }

+

+                              }

+

+                            

+

+                            var template = '<input ng-click="thisCheckboxClicked($event)" type="checkbox" class="edit js-node-check" data-node-menu-id="' + node.menuId + '"  data-node-id="' + node.id + '" ' + isChecked + ' ng-show="' + toShow + '"' + isDisabled+ '/>'

+

+                            var templateEl = angular.element(template);

+                            var $jqCheckbox = $compile(templateEl)(scope);

+							if (toShow){

+									$li.find('.jqtree-element').prepend($jqCheckbox);

+								} else {

+									$li.find('.jqtree-element').remove();

+									}

+                        }

+                    }

+                });

+

+                scope.thisCheckboxClicked = function (e) {

+                

+                	var nodeId = e.target.attributes[4].value;

+                	

+             

+               

+                	var sBrowser, sUsrAg = window.navigator.userAgent;

+                	//if (sUsrAg.indexOf("Firefox") > -1) {

+                	

+                	if (sUsrAg.indexOf("Trident") > -1) {

+                		nodeId = e.target.attributes[5].value;

+                	}

+                    

+//                	if (sUsrAg.indexOf("MSIE") > 1) {

+//                		alert("hELLO tHIS IS IE10");

+//                		nodeId = e.target.attributes[3].value;

+//                		alert('nodeId 26 of IE 45 : '+nodeId);

+//                	}

+//                	

+                	var version = navigator.userAgent.match(/Firefox\/(.*)$/);

+                	

+                	if(version && version.length > 1){

+                	if(parseInt(version[1]) >= 50){

+                		nodeId = e.target.attributes[3].value;

+                	} else if(parseInt(version[1]) >= 45){

+                		

+                		nodeId = e.target.attributes[2].value;

+                	}

+                }

+                	var thisNode = el.find('#jqTreeUserNotif').tree('getNodeById', nodeId);

+                    var isChecked = e.target.checked;

+                    scope.checkboxIdDict[nodeId]['is_box_checked'] = isChecked;

+

+                    thisNode = angular.element(thisNode);

+                    if (thisNode[0].hasOwnProperty('children') && thisNode[0].children.length > 0) {

+                        var jsNodeCheckList = angular.element(e.target).parent().next().find('.js-node-check')

+                        // check/uncheck children items

+                        jsNodeCheckList.prop('checked', isChecked);

+

+                        for (var i in jsNodeCheckList) {

+                            var singlediv = jsNodeCheckList[i];

+                            if (typeof singlediv == 'object' & (!singlediv.length)) {

+                            	

+                            	var tempNodeId = angular.element(singlediv)[0].attributes[4].value;

+                            

+                                

+                                

+                                if (sUsrAg.indexOf("Trident") > -1) {

+                                	

+                                	var tempNodeId = angular.element(singlediv)[0].attributes[5].value;

+                                	

+                               

+                                }

+                                

+//                                if (sUsrAg.indexOf("MSIE") > 0) {

+//                                    var tempNodeId = angular.element(singlediv)[0].attributes[3].value;

+//                                    alert('tempNodeId 2 FF 45 : '+tempNodeId);

+//                                    }

+                                if(version && version.length > 1){

+                                	if(parseInt(version[1]) >= 50){

+                                		tempNodeId = angular.element(singlediv)[0].attributes[3].value;

+                                	} 

+                                	else if(parseInt(version[1]) >= 45){

+                                		tempNodeId = angular.element(singlediv)[0].attributes[2].value;

+                                	}

+                                }

+                                scope.checkboxIdDict[tempNodeId]['is_box_checked'] = isChecked;

+                            }

+                        }

+                    }

+

+                    scope.checkBoxObj.isAnyRoleSelected = false;

+                    for (var key in scope.checkboxIdDict) {

+                        if (scope.checkboxIdDict[key]['is_box_checked']&&scope.checkboxIdDict[key]['role_id']) {

+                            scope.checkBoxObj.isAnyRoleSelected = true;

+                            break;

+                        }

+                    }

+                }

+

+

+

+                scope.$watch('treedata', function (oldValue, newValue) {

+                    if (oldValue !== newValue) {

+                        $jqTree.tree('loadData', scope.treedata);

+                        $jqTree.tree('reload', function () {

+                        });

+                    }

+                });

+            }

+        };

+    }]);

+})();

diff --git a/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.modal.less b/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.modal.less
new file mode 100644
index 0000000..b712ed2
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.modal.less
@@ -0,0 +1,157 @@
+
+.user-notification-details-modal {
+    width: 595px;
+    margin:auto;
+    background-color:white;
+  .title {
+    .dGray18r;  //AT&T Dark Gray
+    border-bottom: @blue-active 3px solid;
+
+  }    
+    input:not([type="button"]) {
+    height: 13px;
+} 
+  .user-notification-details-contents {
+  padding-left: 0px;
+    padding-top: 16px;
+    padding-bottom: 16px;
+    height: 630px;
+    overflow-y: auto;
+    margin-left:10px;
+    padding-right:100px;
+    
+          .notif-input-calendar{
+    width: 301px; 
+    border-radius: 6px ; 
+    border: 1px solid #888;
+     height: 36px; 
+     position: relative; 
+     z-index: 1;
+    }
+    
+.simulateCatGridHeaderRadioButton{
+	line-height: 20px;
+	margin-top: 5px;
+	margin-left: 10px;
+    font-family: Omnes-ECOMP-W02, Arial;
+    color: #444444;
+    float: left;
+    font-size: 13px; 
+    margin-left: 0px;
+    width: 80px;
+}
+
+    .left-container{
+      display: inline-block;
+      width: 48%;
+      
+
+    }
+    
+   
+    .right-container{
+      display: inline-block;
+      width: 48%;
+      float: right;
+          .mandatory-categories{
+	color: #cf2a2a;
+	font-size: 10px;
+	position:absolute;
+	
+	}
+      
+      .notif-input{
+      width:302px;
+      height:50px;
+    
+      }
+    }
+   
+  
+   
+    .ngdialog.ngdialog-theme-plain .ngdialog-content {
+    // max-width: 100%;
+    // width: 2000px;
+    }
+    .user-notif-label {
+    
+    .checkbox-categories{
+	color: #cf2a2a;
+	font-size: 10px;
+	position:absolute;
+	margin-top: -6px;
+	
+	}
+    
+    
+ .tree{
+   margin-left: 0px; 
+   margin-right: 30px; 
+   overflow-y: auto; 
+   padding-top: 15px; 
+   width:100%;
+    max-height: 250px;
+    margin-top:10px;
+   }
+   
+   
+   }
+   
+ 
+    
+        margin-bottom: 5px;
+        color: #5a5a5a;
+        font-family: Omnes-ECOMP-W02, Arial;
+        font-size: 14px;
+        font-weight: bold;
+       // padding-left: 10px;
+    
+         .property{
+      position: relative;
+      margin-bottom: 18px;
+      
+     
+      .property-label{
+        .dGray14r;
+        
+        
+      }
+       .input-field{
+        .custom-input-field;
+        width: 220px;
+      }
+
+      .input-file-field{
+        width: 220px;
+      }
+      .select-field {
+        .custom-select-field;
+      }
+        .error-container{
+        position: absolute;
+        width: 220px;
+        display: block;
+        height: 12px;
+        line-height: 12px;
+
+        .err-message{
+          color: @funcRed;
+          font-size: 10px;
+        }
+    }    
+    .js-node-check{ 
+        width: 16px;
+        height: 16px;
+        vertical-align:middle; 
+        }
+    }
+     }
+      }
+      
+   #datepicker{
+      z-index: 10000 !important;
+      width: 302px;
+      padding-left: 6px;
+  }     
+        
+      
diff --git a/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.modal.page.html b/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.modal.page.html
new file mode 100644
index 0000000..e342692
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.modal.page.html
@@ -0,0 +1,161 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="user-notification-details-modal">

+	<!--<div class="functional-menu-container">-->

+	<div id="app-title" class="title">{{modalPgTitle}}</div>

+	<div class="user-notification-details-contents">

+		<div class="left-container">

+			<div class="add-widget-field" style="{{isEditMode? 'opacity : 0.6; pointer-events: none;':' '}} ">

+				<div class="user-notif-label">Broadcast to All Categories</div>

+				<label id="label-yes" class="simulateCatGridHeaderRadioButton"> <input

+					id="radio-button-yes" type="radio" ng-model="isForAllRoles" value="Y"> Yes

+				</label> <label id="label-no" class="simulateCatGridHeaderRadioButton"> <input

+					id="radio-button-no" type="radio" ng-model="isForAllRoles" value="N"> No

+				</label>

+			</div>

+			<div 

+				style="padding-left: 10px; {{(! isForAllRoles ||isForAllRoles=='Y')? 'opacity : 0.6; pointer-events: none;':' '}} {{isEditMode? 'opacity : 0.6;':' '}} "

+				class="user-notif-label">

+				<div id="notifcation-label-user" class="user-notif-label">

+					<span ng-show="isForAllRoles=='N'" runat="server" ID="required" style="color: Red;">*</span>

+					Categories

+				</div>

+				<div>

+

+				<div id="notifcation-label-user-div" class="user-notif-label"></div>

+				<label id="funcMenu-label-yes" > <input id="radio-button-funcMenu"

+				type="radio" ng-model="isFunctionalMenu" ng-click="getFunctionalMenu()" value="Y"> Functional Menu

+				<br/>

+				</label> <label id="approles-label-no" > <input id="radio-button-approles"

+				type="radio" ng-model="isFunctionalMenu" ng-click="getAppRoleIds()" value="N"> Application Roles

+				</label>

+

+				<div id="approles-checkbox" ng-show="!checkBoxObj.isAnyRoleSelected&&(isForAllRoles=='N')&&(!isEditMode)">

+					

+					<div id="approles-checkbox-required" ng-show="!checkBoxObj.isAnyRoleSelected" >

+						

+

+						<small class="checkbox-categories">At least

+							one category is required</small>

+

+					</div>

+				</div>

+				

+				</div>

+				

+				<div  id="tree1">

+					<div id="Service_Creation"></div>

+				</div>

+				<div 

+					

+					id="jq-tree-div" jq-tree-user-notif ></div>

+

+					

+

+				<script type="text/ng-template" id="jq-tree-tmpl-user-notif.html">

+            	<div  id="jqTreeUserNotif" class="tree"></div>

+        	</script>

+			</div>

+			<div>

+

+				<div id="add-user-notif-priority" class="add-widget-field" style="{{isEditMode? 'opacity : 0.6; pointer-events: none;':' '}} ">

+					<div id="user-notification-priority-label" class="user-notif-label">Priority</div>

+					<label id="label-important" class="simulateCatGridHeaderRadioButton"> <input

+						id="radio-button-normal" type="radio" ng-model="selectedPriority" value="1"> Normal

+					</label> <label id="label-normal" class="simulateCatGridHeaderRadioButton"> <input

+						id="radio-button-important" type="radio" ng-model="selectedPriority" value="2">

+						Important

+					</label>

+				</div>

+			</div>

+		</div>

+		<div id="app-conatiner-right" class="right-container">

+

+			<div id="add-user-notif-startdate" class="add-widget-field"

+				style="padding-bottom: 12px; width: 301px !important;{{( isEditMode )? 'opacity : 0.6; pointer-events: none;':' '}}">

+				<div id="user-notification-startdate-label" class="user-notif-label">

+					<div class="user-notif-label">

+						<span runat="server" ID="required" style="color: Red;">*</span>

+						Start Date (Local Time)

+					</div>

+					<input class="notif-input-calendar" id="datepicker-start" type="text" 

+						ng-model="startTime" b2b-datepicker min="minDate" max="maxDate" 

+						required />

+					<div id="user-startdate-required" ng-show="!startTime">

+						<small class="mandatory-categories">Start Date is Required</small>

+					</div>

+				</div>

+				<div ng-show="!isEditMode" ng-init="formatStartDate()"></div> 

+			</div>			

+

+			<div id="add-user-notif-enddate" class="add-widget-field"

+				style="padding-bottom: 12px;     width: 301px !important; {{( isEditMode )? 'opacity : 0.6; pointer-events: none;':' '}}">

+				<div id="user-notification-enddate-label" class="user-notif-label">

+					<span runat="server" ID="required" style="color: Red;">*</span> End	Date (Local Time)

+				</div>

+				<input class="notif-input-calendar" type="text" id="datepicker-end" 

+					ng-model="endTime" b2b-datepicker min="minDate" max="maxDate" 

+					required />

+				<div id="user-enddate-required" ng-show="!endTime" >

+					<small class="mandatory-categories">End Date is Required</small>

+				</div>

+				<div id="user-enddate-error" ng-show="endTime&&startTime&&startTime.getTime()>=endTime.getTime()" style="color: #cf2a2a; font-size: 10px;">

+					<small style="position: absolute;">End Date must be greater than start Date</small>

+				</div>

+				<div ng-show="!isEditMode" ng-init="formatEndDate()" ></div> 

+			</div>

+

+			<div id="add-user-notif-title" class="add-widget-field"

+				style="padding-bottom: 12px; {{( isEditMode )? 'opacity : 0.6; pointer-events: none;':' '}}">

+				<div id="user-notification-title-label" class="user-notif-label">

+					<span runat="server" ID="required" style="color: Red;"

+						visible="false"> *</span> Title

+				</div>

+				<textarea id="add-notification-input-title" class="notif-input" ng-model="msgHeader" name="content" style="height: 50px;">

+                </textarea>

+				<div id="user-title-required" ng-show="msgHeader.length == 0">

+					<small class="mandatory-categories">Title is Required</small>

+				</div>

+			</div>

+			

+			<div id="add-user-notif-message" class="add-widget-field"

+				style="padding-bottom: 12px; {{( isEditMode )? 'opacity : 0.6; pointer-events: none;':' '}}">

+				<div id="user-notif-message-label" class="user-notif-label">

+					<span runat="server" ID="required" style="color: Red;"

+						visible="false"> *</span> Message

+				</div>

+				<textarea id="user-notif-input-message" class="notif-input" style="height: 150px"

+					ng-model="msgDescription" name="content">

+				</textarea>

+				<div id="user-notif-message-required" ng-show="msgDescription.length ==0 ">

+				<small class="mandatory-categories">Message is Required</small>

+				</div>

+			</div>

+

+			<div class="dialog-control">

+				<a ng-show="!isEditMode">

+				<button id="button-notification-save" class="btn btn-alt btn-small" size="small" ng-disabled="addUserNotificationValidation()"

+					ng-click="addUserNotification()">Save</button></a> 						

+				<button id="button-notification-cancel" class="btn btn-alt btn-small" ng-click="closeThisDialog()" role="button" tabindex="0">Cancel</button>

+			</div>

+			

+		</div>

+	</div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.tpl.html b/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.tpl.html
new file mode 100644
index 0000000..742946f
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/user-notifications-admin/user.notifications.tpl.html
@@ -0,0 +1,117 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="w-ecomp-user-notification-admin"

+	ng-style="{bottom: tabBottom}">

+	 <div class="w-ecomp-main-container" >

+	<div class="user-notification" id="page-content">

+		<div id="title" class="w-ecomp-main-view-title">

+			<h1 class="heading-page" >User Notifications</h1>

+		</div>

+		<div class="userNotifTable">

+		

+		   <div class="table-control-fields">

+		   <input class="input-table-search" type="text" id="table-search-field"

+						placeholder="Search in entire table" ng-model="searchString" />

+					</div>

+			  <div class="table-control-buttons" ng-controller="userNotificationsCtrl">

+						<button class="btn btn-alt btn-small"id="button-openAddNewApp"   ng-click="openUserNotificationModal()" >

+		                	<i class="icon-people-userbookmark" aria-hidden="true"></i>&nbsp;Add Notification

+		                </button> 

+					</div>

+		

+			<span class="ecomp-spinner" ng-show="isLoadingTable"></span>

+			<div class="b2b-table-div"

+				ng-hide="isLoadingTable" id="table-main">

+				<table b2b-table id="table-main" table-data="adminNotifications" search-string="searchString"

+					current-page="ignoredCurrentPage">

+					<thead b2b-table-row type="header">

+						<tr>

+							<th id="th-notif-0" b2b-table-header key="msgSource"

+								sortable="true" style=" width: 10px;">Message Source</th>

+							<th id="th-notif-1" b2b-table-header key="msgHeader"

+								sortable="true" style=" width: 10px;">Message</th>

+							<th id="th-notif-2"  b2b-table-header key="startTime"

+								sortable="true">Start Date (Local Time)</th>

+							<th id="th-notif-3" b2b-table-header key="endTime"

+								sortable="true">End Date  (Local Time)</th>

+							<th id="th-notif-4" b2b-table-header key="priority"

+								sortable="true">Priority</th>

+							<th id="th-notif-5" b2b-table-header key="loginId"

+								sortable="true">Created By</th>

+							<th id="th-notif-6" b2b-table-header key="createdDate"

+								sortable="true">Created Time</th>

+							<th id="th-notif-7" b2b-table-header key="isForAllRoles"

+								sortable="true">All Users (Roles)?</th>

+							<th id="th-notif-8" b2b-table-header key="edit" sortable="false">View/Delete</th>

+						</tr>

+					</thead>

+					<tbody b2b-table-row type="body" class="table-body"

+						row-repeat="rowData in tableAdminNotifItems">

+						<tr>

+							<td b2b-table-body style="{{rowData.expired?'color:lightgray !important':''}}">

+								<div  id="{{$index}}-msgSource">{{rowData.msgSource}}</div>

+							</td>

+							<td b2b-table-body style="{{rowData.expired?' color:lightgray !important':''}}">

+								<div id="{{$index}}-msgHeader" style="font-weight: bold;">{{rowData.msgHeader}}</div>

+								<div id="{{$index}}-message" ng-if="rowData.msgSource==='EP'" style="width:500px"  ng-bind="rowData.msgDescription"></div>

+								<div id="{{$index}}-message" ng-if="rowData.msgSource!=='EP'"  ng-bind="rowData.msgDescription| elipsis: 27"></div>

+							</td>

+							<td b2b-table-body style="{{rowData.expired?'color:lightgray !important':''}}">

+								<div id="{{$index}}-startTime">{{rowData.startTime |

+									date:'medium'}}</div>

+							</td >

+							<td b2b-table-body style="{{rowData.expired?'color:lightgray !important':''}}">

+								<div id="{{$index}}-endTime">{{rowData.endTime |

+									date:'medium'}}</div>

+							</td>

+							<td b2b-table-body style="{{rowData.expired?'color:lightgray !important':''}}">

+								<div id="{{$index}}-priority">{{priorityItems[rowData.priority]}}</div>

+							</td>

+							<td b2b-table-body style="{{rowData.expired?'color:lightgray !important':''}}">

+								<div id="{{$index}}-loginId">{{!rowData.loginId ?externalNotification: rowData.loginId}}</div>

+							</td>

+							<td b2b-table-body style="{{rowData.expired?'color:lightgray !important':''}}">

+								<div id="{{$index}}-createdDate">{{rowData.createdDate |

+									date:'medium'}}</div>

+							</td>

+							<td b2b-table-body style="{{rowData.expired?'color:lightgray !important':''}}">

+								<div id="{{$index}}-isForAllRoles">{{rowData.isForAllRoles}}</div>

+							</td>

+							<td b2b-table-body style="{{rowData.expired?' color:lightgray !important':''}}">

+							

+							<p id="{{$index}}-notification-edit" ng-if="rowData.msgSource==='EP'" ng-click="editUserNotificationModal(rowData)"class="icon-overview"  >  /</p> 

+							<p id="{{$index}}-notification-edit" ng-if="rowData.msgSource!=='EP'" ng-click="showDetailedJsonMessage(rowData)"class="icon-overview"  >/ </p> 

+							<p id="{{$index}}-notification-delete" ng-click="removeUserNotification(rowData)" class="icon-misc-trash"></p> 

+							

+						

+							</td>

+						</tr>

+					</tbody>

+				</table>

+				

+			</div>

+					<div b2b-pagination="" total-pages="totalPages1"

+						current-page="currentPage1" click-handler="customPageHandler"

+						role="navigation" aria-label="Customer Data Pages"></div>

+				

+						

+		</div>

+	</div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/userbar/userbar.less b/ecomp-portal-FE-common/client/app/views/userbar/userbar.less
new file mode 100644
index 0000000..20248a9
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/userbar/userbar.less
@@ -0,0 +1,22 @@
+.w-ecomp-sidebar {
+
+  position: relative;
+  left: 0;
+  right: 0;
+  top: 5px;
+  padding-left: 0;
+
+  }
+  
+  @media screen and (-webkit-min-device-pixel-ratio:0)
+{ 
+    .w-ecomp-sidebar {
+
+	  position: relative;
+	  left: 0;
+	  right: 0;
+	  top: -5px;
+	  padding-left: 0;
+	
+	  }
+}
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.ack.html b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.ack.html
new file mode 100644
index 0000000..9527c75
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.ack.html
@@ -0,0 +1,32 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="new-user-modal">

+	<div class="search-users-directive">

+		<div class="title">Bulk User Upload Acknowledgement</div>

+		<div class="main">

+			<h1>The valid entries have been uploaded.</h1>

+

+			<div class="dialog-control">

+				<div id="search-user-cancel-button" class="cancel-button"

+					ng-click="closeDialog()">OK</div>

+			</div>

+		</div>

+	</div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.confirm.html b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.confirm.html
new file mode 100644
index 0000000..a3c0b53
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.confirm.html
@@ -0,0 +1,83 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="bulk-user-modal">

+	<div class="title">Bulk User Upload Confirmation</div>

+		<div class="main">

+

+			<!-- progress indicator -->

+			<div class="upload-instructions"

+				ng-show="isProcessing">

+				{{progressMsg}}

+				<br>

+				<br>

+				<span class="ecomp-spinner"></span>

+			</div>

+

+			<div ng-hide="isProcessing">

+				<div class="upload-instructions">

+				Click OK to upload the valid requests. 

+				Invalid requests will be ignored.</div>

+				<div class="c-ecomp-portal-abs-table default"

+					style="height: 250px !important">

+					<table b2b-table table-data="uploadFile"

+						search-string="bulkUser.searchString"

+						view-per-page="bulkUser.viewPerPageIgnored"

+						current-page="bulkUser.currentPageIgnored"

+						total-page="bulkUser.totalPageIgnored">

+						<thead b2b-table-row type="header">

+							<tr>

+								<th id="th-line" b2b-table-header sortable="false">Line</th>

+								<th id="th-orgUserId" b2b-table-header sortable="false">Org User ID

+									</th>

+								<th id="th-approle" b2b-table-header sortable="false">App

+									Role</th>

+								<th id="th-status" b2b-table-header sortable="false">Status</th>

+							</tr>

+						</thead>

+						<!-- Use track-by="UNIQUE KEY HERE" or leave out if no unique keys in data -->

+						<tbody b2b-table-row type="body" class="table-body"

+							row-repeat="rowData in uploadFile">

+							<tr id="tr-rowData">

+								<td class="td-first" b2b-table-body>

+									<div ng-bind="rowData.line"></div>

+								</td>

+								<td b2b-table-body>

+									<div ng-bind="rowData.orgUserId"></div>

+								</td>

+								<td b2b-table-body>

+									<div ng-bind="rowData.role"></div>

+								</td>

+								<td b2b-table-body>

+									<div ng-bind="rowData.status"></div>

+								</td>

+							</tr>

+						</tbody>

+					</table>

+				</div>

+

+			</div>

+			<div class="dialog-control">		

+					<button id="bulk-user-ok-button" class="btn btn-alt btn-small" ng-class="{disabled: isValidating}"

+					ng-click="updateDB()">Ok</button>

+					<button id="bulk-user-cancel-button" class="btn btn-alt btn-small" ng-click="cancelUpload()">Cancel</button>

+					

+			</div>

+		</div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.controller.js b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.controller.js
new file mode 100644
index 0000000..e3046b8
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.controller.js
@@ -0,0 +1,577 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+/**

+ * bulk user upload controller

+ */

+'use strict';

+(function () {

+    class BulkUserModalCtrl {

+    	constructor($scope, $log, $filter, $q, usersService, applicationsService, confirmBoxService, functionalMenuService, ngDialog) {

+    		

+    		// Set to true for copious console output

+    		var debug = false;

+    		// Roles fetched from app service

+    		var appRolesResult = [];

+    		// Users fetched from user service

+    		var	userCheckResult = [];

+    		// Requests for user-role assignment built by validator

+    		var appUserRolesRequest = [];

+    		

+    		let init = () => {

+    			if (debug)

+    				$log.debug('BulkUserModalCtrl::init');

+    			// Angular insists on this.

+    			$scope.fileModel = {};

+    			// Model for drop-down

+    			$scope.adminApps = [];

+    			// Enable modal controls

+    			this.step1 = true;

+    			this.fileSelected = false;

+

+    			// Flag that indicates background work is proceeding

+    			$scope.isProcessing = true;

+

+    			// Load user's admin applications

+    			applicationsService.getAdminApps().promise().then(apps => {

+    				if (debug)

+    					$log.debug('BulkUserModalCtrl::init: getAdminApps returned' + JSON.stringify(apps));

+                    if (!apps || typeof(apps) != 'object') {

+                        $log.error('BulkUserModalCtrl::init: getAdminApps returned unexpected data');

+                    }

+                    else {

+                    	if (debug)

+                    		$log.debug('BulkUserModalCtrl::init:  admin apps length is ', apps.length);

+                    	

+                    	// Sort app names and populate the drop-down model

+                        let sortedApps = apps.sort(getSortOrder('name', true));

+                        for (let i = 0; i < sortedApps.length; ++i) {

+                            $scope.adminApps.push({

+                                index: i,

+                                id: sortedApps[i].id,

+                                value: sortedApps[i].name,

+                                title: sortedApps[i].name

+                            });

+                        }

+                        // Pick the first one in the list

+                        $scope.selectedApplication = $scope.adminApps[0];

+                    }

+    				$scope.isProcessing = false;

+                }).catch(err => {

+                    $log.error('BulkUserModalCtrl::init: getAdminApps threw', err);

+                	$scope.isProcessing = false;

+                });

+    			

+    		}; // init

+    		

+    		// Answers a function that compares properties with the specified name.

+    		let getSortOrder = (prop, foldCase) => {

+                return function(a, b) {

+                	let aProp = foldCase ? a[prop].toLowerCase() : a[prop];

+                	let bProp = foldCase ? b[prop].toLowerCase() : b[prop];

+                    if (aProp > bProp)

+                        return 1;

+                    else if (aProp < bProp) 

+                        return -1;

+                    else

+                    	return 0;

+                }

+            }

+    		

+    		//This is a fix for dropdown selection, due to b2b dropdown only update value field

+    		$scope.$watch('selectedApplication.value', (newVal, oldVal) => {

+    			for(var i=0;i<$scope.adminApps.length;i++){ 			

+    				if($scope.adminApps[i].value==newVal){

+    					$scope.selectedApplication=angular.copy($scope.adminApps[i]);;

+    				}

+    			}

+    		});

+

+    		// Invoked when user picks an app on the drop-down.

+    		$scope.appSelected = () => {

+    			if (debug)

+    				$log.debug('BulkUserModalCtrl::appSelected: selectedApplication.id is ' + $scope.selectedApplication.id);

+    			this.appSelected = true;

+    		}

+    		

+    		// Caches the file name supplied by the event handler.

+    		$scope.fileChangeHandler = (event, files) => {

+    			this.fileSelected = true;

+    			this.fileToRead = files[0];

+    			if (debug)

+    				$log.debug("BulkUserModalCtrl::fileChangeHandler: file is ", this.fileToRead);

+    		}; // file change handler

+    		

+    		/**

+    		 * Reads the contents of the file, calls portal endpoints

+    		 * to validate roles, userIds and existing role assignments;

+    		 * ultimately builds array of requests to be sent.

+    		 * Creates scope variable with input file contents for

+    		 * communication with functions.

+    		 * 

+    		 * This function performs a synchronous step-by-step process

+    		 * using asynchronous promises. The code could all be inline

+    		 * here but the nesting becomes unwieldy.

+    		 */

+    		$scope.readValidateFile = () => {

+    			$scope.isProcessing = true;

+    			$scope.progressMsg = 'Reading upload file..';

+    			var reader = new FileReader();

+    			reader.onload = function(event) {

+    				$scope.uploadFile = $filter('csvToObj')(reader.result);

+    				if (debug)

+    					$log.debug('BulkUserModalCtrl::readValidateFile onload: data length is ' + $scope.uploadFile.length);

+    				// sort input by orgUserId

+    				$scope.uploadFile.sort(getSortOrder('orgUserId', true));

+    				

+    				let appid = $scope.selectedApplication.id;

+    				$scope.progressMsg = 'Fetching application roles..';

+                    functionalMenuService.getManagedRolesMenu(appid).then(function (rolesObj) {

+                    	if (debug)

+                    		$log.debug("BulkUserModalCtrl::readValidateFile: managedRolesMenu returned " + JSON.stringify(rolesObj));

+           				appRolesResult = rolesObj;

+           				$scope.progressMsg = 'Validating application roles..';

+                    	$scope.verifyRoles();

+    				

+                    	let userPromises = $scope.buildUserChecks();

+                    	if (debug)

+                    		$log.debug('BulkUserModalCtrl::readValidateFile: userPromises length is ' + userPromises.length);

+                    	$scope.progressMsg = 'Validating Org Users..';

+                    	$q.all(userPromises).then(function() {

+                    		if (debug)

+                    			$log.debug('BulkUserModalCtrl::readValidateFile: userCheckResult length is ' + userCheckResult.length);

+                    		$scope.evalUserCheckResults();

+    					

+                    		let appPromises = $scope.buildAppRoleChecks();

+                    		if (debug)

+                    			$log.debug('BulkUserModalCtrl::readValidateFile: appPromises length is ' + appPromises.length);

+                    		$scope.progressMsg = 'Querying application for user roles..';

+                    		$q.all(appPromises).then( function() {

+                    			if (debug)

+                    				$log.debug('BulkUserModalCtrl::readValidateFile: appUserRolesRequest length is ' + appUserRolesRequest.length);

+                    			$scope.evalAppRoleCheckResults();

+                    			

+	    						// Re sort by line for the confirmation dialog

+	    						$scope.uploadFile.sort(getSortOrder('line', false));

+	    						// We're done, confirm box may show the table

+	    						if (debug)

+	    							$log.debug('BulkUserModalCtrl::readValidateFile inner-then ends');

+	    						$scope.progressMsg = 'Done.';

+	    						$scope.isProcessing = false;

+                    		},

+                    		function(error) {

+                    			$log.error('BulkUserModalCtrl::readValidateFile: failed retrieving user-app roles');

+	    						$scope.isProcessing = false;

+                    		}

+                    		); // then of app promises

+                    	},

+                    	function(error) {

+                    		$log.error('BulkUserModalCtrl::readValidateFile: failed retrieving user info');

+                    		$scope.isProcessing = false;

+                    	}

+                    	); // then of user promises

+                    },

+                    function(error) {

+                    	$log.error('BulkUserModalCtrl::readValidateFile: failed retrieving app role info');

+                    	$scope.isProcessing = false;

+                    }

+                    ); // then of role promise

+           

+    			} // onload

+    			

+    			// Invoke the reader on the selected file

+    			reader.readAsText(this.fileToRead);

+    		}; 

+    		

+    		/**

+    		 * Evaluates the result set returned by the app role service.

+    		 * Sets an uploadFile array element status if a role is not defined.

+    		 * Reads and writes scope variable uploadFile.

+    		 * Reads closure variable appRolesResult.

+    		 */

+    		$scope.verifyRoles = () => {

+    			if (debug)

+    				$log.debug('BulkUserModalCtrl::verifyRoles: appRoles is ' + JSON.stringify(appRolesResult));

+    			// check roles in upload file against defined app roles

+    			$scope.uploadFile.forEach( function (uploadRow) {

+    				// skip rows that already have a defined status: headers etc.

+    				if (uploadRow.status) {

+    					if (debug)

+    						$log.debug('BulkUserModalCtrl::verifyRoles: skip row ' + uploadRow.line);

+    					return;

+    				}

+    				uploadRow.role = uploadRow.role.trim();

+    				var foundRole=false;

+    				for (var i=0; i < appRolesResult.length; i++) {

+    					if (uploadRow.role.toUpperCase() === appRolesResult[i].rolename.trim().toUpperCase()) {

+    						if (debug)

+    							$log.debug('BulkUserModalCtrl::verifyRoles: match on role ' + uploadRow.role);

+    						foundRole=true;

+    						break;

+    					}

+    				};

+    				if (!foundRole) {

+    					if (debug)

+    						$log.debug('BulkUserModalCtrl::verifyRoles: NO match on role ' + uploadRow.role);

+    					uploadRow.status = 'Invalid role';

+    				};

+    			}); // foreach

+    		}; // verifyRoles

+    		

+    		/**

+    		 * Builds and returns an array of promises to invoke the 

+    		 * searchUsers service for each unique Org User UID in the input.

+    		 * Reads and writes scope variable uploadFile, which must be sorted by Org User UID.

+    		 * The promise function writes to closure variable userCheckResult

+    		 */

+    		$scope.buildUserChecks = () => {

+    			if (debug)

+    				$log.debug('BulkUserModalCtrl::buildUserChecks: uploadFile length is ' + $scope.uploadFile.length);

+    			userCheckResult = [];

+    			let promises = [];

+    			let prevRow = null;

+    			$scope.uploadFile.forEach(function (uploadRow) {

+    				if (uploadRow.status) {

+    					if (debug)

+    						$log.debug('BulkUserModalCtrl::buildUserChecks: skip row ' + uploadRow.line);

+    					return;

+    				};

+    				// detect repeated UIDs

+    				if (prevRow == null || prevRow.orgUserId.toLowerCase() !== uploadRow.orgUserId.toLowerCase()) {

+    					if (debug)

+    						$log.debug('BulkUserModalCtrl::buildUserChecks: create request for orgUserId ' + uploadRow.orgUserId);

+    					let userPromise = usersService.searchUsers(uploadRow.orgUserId).promise().then( (usersList) => {

+    						if (typeof usersList[0] !== "undefined") {

+    							userCheckResult.push({ 

+    								orgUserId:    usersList[0].orgUserId,

+    								firstName: usersList[0].firstName,

+    								lastName:  usersList[0].lastName,

+    								jobTitle:  usersList[0].jobTitle

+    							});

+    						}

+    						else {

+    							// User not found.

+    							if (debug)

+    								$log.debug('BulkUserModalCtrl::buildUserChecks: searchUsers returned null');

+    						}

+    					}, function(error){

+    						$log.error('BulkUserModalCtrl::buildUserChecks: searchUsers failed ' + JSON.stringify(error));

+    					}); 

+    					promises.push(userPromise);

+    				}

+    				else {

+    					if (debug)

+    						$log.debug('BulkUserModalCtrl::buildUserChecks: skip repeated orgUserId ' + uploadRow.orgUserId);    					

+    				}

+    				prevRow = uploadRow;

+    			}); // foreach

+    			return promises;

+    		}; // buildUserChecks

+    		

+    		/**

+    		 * Evaluates the result set returned by the user service to set

+    		 * the uploadFile array element status if the user was not found.

+    		 * Reads and writes scope variable uploadFile.

+    		 * Reads closure variable userCheckResult.

+    		 */

+    		$scope.evalUserCheckResults = () => {

+    			if (debug)

+    				$log.debug('BulkUserModalCtrl::evalUserCheckResult: uploadFile length is ' + $scope.uploadFile.length);

+    			$scope.uploadFile.forEach(function (uploadRow) {

+    				if (uploadRow.status) {

+    					if (debug)

+    						$log.debug('BulkUserModalCtrl::evalUserCheckResults: skip row ' + uploadRow.line);

+    					return;

+    				};

+    				let foundorgUserId = false;

+    				userCheckResult.forEach(function(userItem) {

+    					if (uploadRow.orgUserId.toLowerCase() === userItem.orgUserId.toLowerCase()) {

+    						if (debug)

+    							$log.debug('BulkUserModalCtrl::evalUserCheckResults: found orgUserId ' + uploadRow.orgUserId);

+    						foundorgUserId=true;

+    					};

+    				});

+    				if (!foundorgUserId) {

+    					if (debug)

+    						$log.debug('BulkUserModalCtrl::evalUserCheckResults: NO match on orgUserId ' + uploadRow.orgUserId);

+    					uploadRow.status = 'Invalid orgUserId';

+    				}

+    			}); // foreach

+    		}; // evalUserCheckResults

+

+            /**

+    		 * Builds and returns an array of promises to invoke the getUserAppRoles

+    		 * service for each unique Org User in the input file.

+    		 * Each promise creates an update to be sent to the remote application

+    		 * with all role names.

+    		 * Reads scope variable uploadFile, which must be sorted by Org User.

+    		 * The promise function writes to closure variable appUserRolesRequest

+    		 */

+    		$scope.buildAppRoleChecks = () => {

+    			if (debug)

+    				$log.debug('BulkUserModalCtrl::buildAppRoleChecks: uploadFile length is ' + $scope.uploadFile.length); 

+    			appUserRolesRequest = [];

+    			let appId = $scope.selectedApplication.id;

+    			let promises = [];

+    			let prevRow = null;

+    			$scope.uploadFile.forEach( function (uploadRow) {

+    				if (uploadRow.status) {

+    					if (debug)

+    						$log.debug('BulkUserModalCtrl::buildAppRoleChecks: skip row ' + uploadRow.line);

+    					return;

+    				}

+    				// Because the input is sorted, generate only one request for each Org User

+    				if (prevRow == null || prevRow.orgUserId.toLowerCase() !== uploadRow.orgUserId.toLowerCase()) {

+            			 if (debug)

+            				 $log.debug('BulkUserModalCtrl::buildAppRoleChecks: create request for orgUserId ' + uploadRow.orgUserId);

+            			 let appPromise = usersService.getUserAppRoles(appId, uploadRow.orgUserId).promise().then( (userAppRolesResult) => {

+            				 // Reply for unknown user has all defined roles with isApplied=false on each.  

+            				 if (typeof userAppRolesResult[0] !== "undefined") {

+            					 if (debug)

+            						 $log.debug('BulkUserModalCtrl::buildAppRoleChecks: adding result ' 

+            								 + JSON.stringify(userAppRolesResult));

+            					 appUserRolesRequest.push({

+            						 orgUserId: uploadRow.orgUserId,

+            						 userAppRoles: userAppRolesResult                    					 

+            					 });

+            				 } else {

+            					 $log.error('BulkUserModalCtrl::buildAppRoleChecks: getUserAppRoles returned ' + JSON.stringify(userAppRolesResult));

+            				 };

+            			 }, function(error){

+            				 $log.error('BulkUserModalCtrl::buildAppRoleChecks: getUserAppRoles failed ', error);

+            			 });

+            			 promises.push(appPromise);

+    				} else {

+            			 if (debug)

+            				 $log.debug('BulkUserModalCtrl::buildAppRoleChecks: duplicate orgUserId, skip: '+ uploadRow.orgUserId);

+            		 }

+            		 prevRow = uploadRow;

+            	 }); // foreach

+    			return promises;

+    		}; // buildAppRoleChecks

+    		

+    		/**

+    		 * Evaluates the result set returned by the app service and adjusts 

+    		 * the list of updates to be sent to the remote application by setting

+    		 * isApplied=true for each role name found in the upload file.

+    		 * Reads and writes scope variable uploadFile.

+    		 * Reads closure variable appUserRolesRequest.

+    		 */

+    		$scope.evalAppRoleCheckResults = () => {

+    			if (debug)

+    				$log.debug('BulkUserModalCtrl::evalAppRoleCheckResults: uploadFile length is ' + $scope.uploadFile.length);

+    			$scope.uploadFile.forEach(function (uploadRow) {

+    				if (uploadRow.status) {

+    					if (debug)

+    						$log.debug('BulkUserModalCtrl::evalAppRoleCheckResults: skip row ' + uploadRow.line);

+    					return;

+    				}

+    				// Search for the match in the app-user-roles array

+    				appUserRolesRequest.forEach( function (appUserRoleObj) {

+    					if (uploadRow.orgUserId.toLowerCase() === appUserRoleObj.orgUserId.toLowerCase()) {

+    						if (debug)

+    							$log.debug('BulkUserModalCtrl::evalAppRoleCheckResults: match on orgUserId ' + uploadRow.orgUserId);

+    						let roles = appUserRoleObj.userAppRoles;

+    						roles.forEach(function (appRoleItem) {

+    							//if (debug)

+    							//	$log.debug('BulkUserModalCtrl::evalAppRoleCheckResults: checking uploadRow.role='

+    							//			+ uploadRow.role + ', appRoleItem.roleName= ' + appRoleItem.roleName);

+    							if (uploadRow.role === appRoleItem.roleName) {

+    								if (appRoleItem.isApplied) {

+    									if (debug)

+    										$log.debug('BulkUserModalCtrl::evalAppRoleCheckResults: existing role ' 

+    											+ appRoleItem.roleName);

+    									uploadRow.status = 'Role exists';

+    								}

+    								else {

+    									if (debug)

+    										$log.debug('BulkUserModalCtrl::evalAppRoleCheckResults: new role ' 

+    											+ appRoleItem.roleName);

+    									// After much back-and-forth I decided a clear indicator

+    									// is better than blank in the table status column.

+    									uploadRow.status = 'OK';

+    									appRoleItem.isApplied = true;

+    								}

+    								// This count is not especially interesting.

+    								// numberUserRolesSucceeded++;

+    							}

+    						}); // for each role

+    					}

+    				}); // for each result		

+    			}); // for each row

+    		}; // evalAppRoleCheckResults

+             

+    		/**

+    		 * Sends requests to Portal requesting user role assignment.

+    		 * That endpoint handles creation of the user at the remote app if necessary.

+    		 * Reads closure variable appUserRolesRequest.

+    		 * Invoked by the Next button on the confirmation dialog.

+    		 */

+    		$scope.updateDB = () => {

+    			$scope.isProcessing = true;

+    			$scope.progressMsg = 'Sending requests to application..';

+    			if (debug)

+    				$log.debug('BulkUserModalCtrl::updateDB: request length is ' + appUserRolesRequest.length);

+    			var numberUsersSucceeded = 0;

+    			let promises = [];

+    			appUserRolesRequest.forEach(function(appUserRoleObj) {

+    				if (debug) 

+    					$log.debug('BulkUserModalCtrl::updateDB: appUserRoleObj is ' + JSON.stringify(appUserRoleObj));

+                     let updateRequest = {

+                    		 orgUserId: appUserRoleObj.orgUserId, 

+                    		 appId: $scope.selectedApplication.id, 

+                    		 appRoles: appUserRoleObj.userAppRoles

+                     };

+                     if (debug)

+                    	 $log.debug('BulkUserModalCtrl::updateDB: updateRequest is ' + JSON.stringify(updateRequest));

+                     let updatePromise = usersService.updateUserAppRoles(updateRequest).promise().then(res => {

+                    	 if (debug)

+                    		 $log.debug('BulkUserModalCtrl::updateDB: updated successfully: ' + JSON.stringify(res));

+                    	 numberUsersSucceeded++;

+                     }).catch(err => {

+                    	 // What to do if one of many fails??

+                    	 $log.error('BulkUserModalCtrl::updateDB failed: ', err);

+                    	 confirmBoxService.showInformation(

+                    			 'Failed to update the user application roles. ' +

+                    			 'Error: ' + err.status).then(isConfirmed => { });

+                     }).finally( () => {

+                    	 // $log.debug('BulkUserModalCtrl::updateDB: finally()');

+                     });

+                     promises.push(updatePromise);

+            	 }); // for each

+    			

+            	 // Run all the promises

+            	 $q.all(promises).then(function(){

+            		 $scope.isProcessing = false;

+            		 confirmBoxService.showInformation('Processed ' + numberUsersSucceeded + ' users.').then(isConfirmed => {

+            			 // Close the upload-confirm dialog

+            			 ngDialog.close();

+            		 });

+            	 });

+             }; // updateDb

+             

+    		// Sets the variable that hides/reveals the user controls

+    		$scope.step2 = () => {

+    			this.fileSelected = false;

+    			$scope.selectedFile = null;

+    			$scope.fileModel = null;

+    			this.step1 = false;  			

+    		}

+    		

+             // Navigate between dialog screens using step number: 1,2,...

+             $scope.navigateBack = () => {

+            	 this.step1 = true;

+                 this.fileSelected = false;

+             };

+             

+             // Opens a dialog to show the data to be uploaded.

+             // Invoked by the upload button on the bulk user dialog.

+             $scope.confirmUpload = () => {

+            	// Start the process

+            	$scope.readValidateFile();

+            	// Dialog shows progress

+            	ngDialog.open({

+            		templateUrl: 'app/views/users/new-user-dialogs/bulk-user.confirm.html',

+            		scope: $scope

+            	});

+             };

+

+             // Invoked by the Cancel button on the confirmation dialog.

+             $scope.cancelUpload = () => {

+            	 ngDialog.close();

+             };

+             

+             init();

+    	} // constructor

+    } // class

+    BulkUserModalCtrl.$inject = ['$scope', '$log', '$filter', '$q', 'usersService', 'applicationsService', 'confirmBoxService', 'functionalMenuService', 'ngDialog'];    

+    angular.module('ecompApp').controller('BulkUserModalCtrl', BulkUserModalCtrl);

+

+    angular.module('ecompApp').directive('fileChange', ['$parse', function($parse){

+    	return {

+    		require: 'ngModel',

+    	    restrict: 'A',

+    	    link : function($scope, element, attrs, ngModel) {

+    	    	var attrHandler = $parse(attrs['fileChange']);

+    	    	var handler=function(e) {

+    	    		$scope.$apply(function() {

+    	    			attrHandler($scope, { $event:e, files:e.target.files } );

+    	    			$scope.selectedFile = e.target.files[0].name;

+    	    		});

+    	    	};

+    	    	element[0].addEventListener('change',handler,false);

+    	   }

+    	}

+    }]);

+

+    angular.module('ecompApp').filter('csvToObj',function() {

+    	return function(input) {

+    	    var result = [];

+    	    var len, i, line, o;

+    		var lines = input.split('\n');

+    	    // Need 1-based index below

+    	    for (len = lines.length, i = 1; i <= len; ++i) {

+    	    	// Use 0-based index for array

+    	    	line = lines[i - 1].trim();

+   	    		if (line.length == 0) {

+   	    			// console.log("Skipping blank line");

+   	    			result.push({

+   	    				line: i,

+   	    				orgUserId: '',

+   	    				role: '',

+   	    				status: 'Blank line'

+   	    			});

+   	    			continue;

+   	    		}

+   	    		o = line.split(',');

+   	    		if (o.length !== 2) {

+   	    			// other lengths not valid for upload

+   	    			result.push({

+   	    				line: i,

+   	    				orgUserId: line,   

+   	    				role: '',

+   	    				status: 'Failed to find 2 comma-separated values'

+   	    			});

+   	    		}

+   	    		else {

+   	    			// console.log("Valid line: ", val);

+   	    			let entry = {

+   	    					line: i,

+   	    					orgUserId: o[0],

+   	    					role: o[1]

+   	    					// leave status undefined, this could be valid.

+   	    			};

+   	    			if (o[0].toLowerCase() === 'orgUserId') {

+   	    				// not valid for upload, so set status

+   	    				entry.status = 'Header';

+   	    			}

+   	    			else if (o[0].trim() == '' || o[1].trim() == '') {

+   	    				// defend against line with only a single comma etc.

+   	    				entry.status = 'Failed to find 2 non-empty values';   	    				

+   	    			}

+   	    			result.push(entry);

+   	    		} // len 2

+    	    } // for

+    	    return result;

+    	};

+    });

+    

+  

+        

+})();

diff --git a/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.modal.html b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.modal.html
new file mode 100644
index 0000000..3d479cb
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.modal.html
@@ -0,0 +1,70 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="bulk-user-modal">

+	<div class="title">Bulk User Upload</div>

+	<div class="main">

+		<div ng-show="bulkUser.step1">

+			<div class="upload-instructions">Select Application:</div>

+			<div class="c-ecomp-portal-abs-select default">

+

+			      <select id="bulk-user-dropdown-apps" name="dropdown1" b2b-dropdown  ng-model="selectedApplication.value" ng-disabled="isProcessing" ng-class="{disabled: isProcessing}">  

+				        <option b2b-dropdown-list option-repeat="d in adminApps" value="{{d.value}}">{{d.title}}</option>

+				                </select>

+

+			</div>

+		</div>

+

+		<div ng-hide="bulkUser.step1">

+			<div class="upload-instructions">Select Upload File:</div>

+

+			<!-- input type=file is difficult to style.

+				 Instead use a label styled as a button. -->

+			<label class="file-label"> 

+				<input type="file"

+					file-change="fileChangeHandler($event,files)" 

+					ng-model="fileModel" />

+					<span>Browse...</span>

+			</label>{{selectedFile}}

+			<div class="upload-instructions">File must have one entry per line with this format:

+			<pre>orgUserId, role name</pre>

+			</div>

+		</div>

+

+		<!-- progress indicator in middle -->

+		<div ng-show="isProcessing">

+			<span class="ecomp-spinner"></span>

+		</div>

+

+		<div class="dialog-control">

+				

+				<button id="bulk-user-back-button" class="btn btn-alt btn-small"

+				ng-hide="bulkUser.step1" ng-click="navigateBack()">Back</button>

+			<button id="bulk-user-next-button" class="btn btn-alt btn-small"

+				ng-hide="!bulkUser.step1" ng-click="!isProcessing && step2()"

+				ng-class="{disabled: isProcessing}">Next</button>

+			<button id="bulk-user-upload-button" class="btn btn-alt btn-small"

+				ng-hide="bulkUser.step1"

+				ng-click="bulkUser.fileSelected && confirmUpload()"

+				ng-class="{disabled: !bulkUser.fileSelected}">Upload</button>

+			<button id="bulk-user-cancel-button" class="btn btn-alt btn-small"

+				ng-click="closeThisDialog()">Cancel</button>

+		</div>

+	</div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.modal.less b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.modal.less
new file mode 100644
index 0000000..b6ee63f
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.modal.less
@@ -0,0 +1,60 @@
+.bulk-user-modal {
+	height: 430px;
+
+	.title {
+		.dGray18r;  //AT&T Dark Gray
+		border-bottom: @blue-active 3px solid;
+	}
+	
+	.main {
+		margin: 16px;
+    
+		.upload-instructions {
+			.dGray14r;
+		}
+		
+		// http://stackoverflow.com/questions/572768/styling-an-input-type-file-button
+
+		.file-label {
+			border: 1px solid #ffffff;
+			border-radius: 6px;
+			margin-top: 4px;
+			margin-bottom: 4px;
+			margin-left: 0px;
+			margin-right: 8px;
+			color: #ffffff;
+			background: #067ab4;
+			display: inline-block;
+			text-align: center;
+			font-family: Omnes-ECOMP-W02-Medium,Arial;
+			font-size: 14px;
+			height: 29px;
+			line-height: 29px;
+			width: 90px;
+
+			input[type="file"] {
+				// Hide the browser's control
+				display: none;
+			}
+		
+		}
+
+		.file-label:hover {
+			background: #009fdb;
+		}
+		
+		.file-label:active {
+			background: #009fdb;
+		}
+		
+		.file-label:invalid+span {
+			color: #ffffff;
+		}
+		
+		.file-label:valid+span {
+			color: #ffffff;
+		}
+
+	}
+    
+}
diff --git a/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.controller.js b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.controller.js
new file mode 100644
index 0000000..882f1e8
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.controller.js
@@ -0,0 +1,216 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+/**

+ * Created by nnaffar on 12/20/15.

+ */

+'use strict';

+(function () {

+    class NewUserModalCtrl {

+        constructor($scope, $log, usersService, applicationsService, confirmBoxService) {

+            let init = () => {

+                //$log.info('NewUserModalCtrl::init');

+                this.isSaving = false;

+                this.anyChanges = false;

+                this.adminApps = [];

+                this.isGettingAdminApps = false;

+                if($scope.ngDialogData && $scope.ngDialogData.selectedUser && $scope.ngDialogData.dialogState){

+                    this.selectedUser = $scope.ngDialogData.selectedUser;

+                    this.dialogState = $scope.ngDialogData.dialogState;

+                    this.isShowBack = false;

+                    if(this.dialogState === 3){

+                        this.getUserAppsRoles();

+                    }

+                }else{

+                    this.isShowBack = true;

+                    this.selectedUser = null;

+                    this.dialogState = 1;

+                }

+            };

+

+            this.appChanged = (index) => {

+                let myApp = this.adminApps[index];

+                //$log.debug('NewUserModalCtrl::appChanged: index: ', index, '; app id: ', myApp.id, 'app name: ',myApp.name);

+                myApp.isChanged = true;

+                this.anyChanges = true;

+            }

+

+            this.deleteApp = (app) => {

+                let appMessage = this.selectedUser.firstName + ' ' + this.selectedUser.lastName;

+                confirmBoxService.deleteItem(appMessage).then(isConfirmed => {

+                    if(isConfirmed){

+                        this.anyChanges = true;

+                        app.isChanged = true;

+                        app.isDeleted = true; // use this to hide the app in the display

+                        app.appRoles.forEach(function(role){

+                            role.isApplied = false;

+                        });

+                    }

+                }).catch(err => {

+                    $log.error('NewUserModalCtrl::deleteApp error: ',err);

+                    confirmBoxService.showInformation('There was a problem deleting the the applications. ' +

+                        'Please try again later. Error: ' + err.status).then(isConfirmed => {});

+                });

+            };

+

+            this.getUserAppsRoles = () => {

+                if (!this.selectedUser || !this.selectedUser.orgUserId) {

+                    $log.error('NewUserModalCtrl::getUserAppsRoles error: No user is selected');

+                    this.dialogState = 1;

+                    return;

+                }

+                //$log.debug('NewUserModalCtrl::getUserAppsRoles: about to call getAdminAppsSimpler');

+                this.isGettingAdminApps = true;

+                applicationsService.getAdminAppsSimpler().then((apps) => {

+                    //$log.debug('NewUserModalCtrl::getUserAppsRoles: beginning of then for getAdminAppsSimpler');

+                    this.isGettingAdminApps = false;

+                    if (!apps || !apps.length) {

+                        $log.error('NewUserModalCtrl::getUserAppsRoles error: no admin apps found');

+                        return null;

+                    }

+                    //$log.debug('NewUserModalCtrl::getUserAppsRoles: then for getAdminAppsSimpler: step 2');

+                    //$log.debug('NewUserModalCtrl::getUserAppsRoles: admin apps: ', apps);

+                    this.adminApps = apps;

+                    this.dialogState = 3;

+                    this.userAppRoles = {};

+                    this.numberAppsProcessed = 0;

+                    this.isLoading = true;

+                    apps.forEach(app => {

+                        //$log.debug('NewUserModalCtrl::getUserAppsRoles: app: id: ', app.id, 'name: ',app.name);

+                        // Keep track of which app has changed, so we know which apps to update using a BE API

+                        app.isChanged = false;

+                        // Each of these specifies a state, which corresponds to a different message and style that gets displayed

+                        app.isLoading = true;

+                        app.isError = false;

+                        app.isDeleted = false;

+                        app.printNoChanges = false;

+                        app.isUpdating = false;

+                        app.isErrorUpdating = false;

+                        app.isDoneUpdating = false;

+                        app.errorMessage = "";

+                        usersService.getUserAppRoles(app.id, this.selectedUser.orgUserId).promise().then((userAppRolesResult) => {

+                            //$log.debug('NewUserModalCtrl::getUserAppsRoles: got a result for app: ',app.id,': ',app.name,': ',userAppRolesResult);

+                            app.appRoles = userAppRolesResult;

+                            app.isLoading = false;

+

+                        }).catch(err => {

+                            $log.error(err);

+                            app.isError = true;

+                            app.isLoading = false;

+                            app.errorMessage = err.headers('FEErrorString');

+                            //$log.debug('NewUserModalCtrl::getUserAppsRoles: in new-user.controller: response header: '+err.headers('FEErrorString'));

+                        }).finally(()=>{

+                            this.numberAppsProcessed++;

+                            if (this.numberAppsProcessed === this.adminApps.length) {

+                                this.isLoading = false;

+                            }

+                        });

+                    })

+                    return;

+                }).catch(err => {

+                    $log.error(err);

+                })

+

+            }

+

+            /**

+             * Update the selected user apps with the new roles.

+             * If no roles remain, set the user to inactive.

+             */

+            this.updateUserAppsRoles = () => {

+                // $log.debug('NewUserModalCtrl::updateUserAppsRoles: entering updateUserAppsRoles');

+                if(!this.selectedUser || !this.selectedUser.orgUserId || !this.adminApps){

+                    $log.error('NewUserModalCtrl::updateUserAppsRoles: mmissing arguments');

+                    return;

+                }

+                this.isSaving = true;

+                //$log.debug('NewUserModalCtrl::updateUserAppsRoles: going to update user: ' + this.selectedUser.orgUserId);

+                this.numberAppsProcessed = 0;

+                this.numberAppsSucceeded = 0;

+                this.adminApps.forEach(app => {

+                    if (app.isChanged) {

+                        //$log.debug('NewUserModalCtrl::updateUserAppsRoles: app roles have changed; going to update: id: ', app.id, '; name: ', app.name);

+                        app.isUpdating = true;

+                        var newUserAppRoles = {

+                        	orgUserId: this.selectedUser.orgUserId,

+                        	appId: app.id, 

+                        	appRoles: app.appRoles,

+                        	appName: app.name

+                        }; 

+                        usersService.updateUserAppRoles(newUserAppRoles).promise()

+                        .then(res => {

+                            //$log.debug('NewUserModalCtrl::updateUserAppsRoles: User app roles updated successfully on app: ',app.id);

+                            app.isUpdating = false;

+                            app.isDoneUpdating = true;

+                            this.numberAppsSucceeded++;

+                        }).catch(err => {

+                            $log.error(err);

+                            app.isErrorUpdating = true;

+                            confirmBoxService.showInformation(

+                            		'Failed to update the user application roles: ' + err.status)

+                            		.then(isConfirmed => {});

+                        }).finally(()=>{

+                            this.numberAppsProcessed++;

+                            if (this.numberAppsProcessed === this.adminApps.length) {

+                                this.isSaving = false; // hide the spinner

+                            }

+                            if (this.numberAppsSucceeded === this.adminApps.length) {

+                                $scope.closeThisDialog(true);//close and resolve dialog promise with true (to update the table)

+                            }

+                        })

+                    } else {

+                        //$log.debug('NewUserModalCtrl::updateUserAppsRoles: app roles have NOT changed; NOT going to update: id: ', app.id, '; name: ', app.name);

+                        app.noChanges = true;

+                        app.isError = false; //remove the error message; just show the No Changes messages

+                        this.numberAppsProcessed++;

+                        this.numberAppsSucceeded++;

+                        if (this.numberAppsProcessed === this.adminApps.length) {

+                            this.isSaving = false; // hide the spinner

+                        }

+                        if (this.numberAppsSucceeded === this.adminApps.length) {

+                            $scope.closeThisDialog(true);//close and resolve dialog promise with true (to update the table)

+                        }

+                    }

+                });

+            };

+

+            /**

+             * Navigate between dialog screens using step number: 1,2,...

+             */

+            this.navigateBack = () => {

+                if (this.dialogState === 1) {

+                    //back from 1st screen?

+                }

+                if (this.dialogState === 3) {

+                    this.dialogState = 1;

+                }

+            };

+

+            init();

+

+            $scope.$on('$stateChangeStart', e => {

+                //Disable navigation when modal is opened

+                //**Nabil - note: this will cause the history back state to be replaced with current state

+                e.preventDefault();

+            });

+        }

+    }

+    NewUserModalCtrl.$inject = ['$scope', '$log', 'usersService', 'applicationsService', 'confirmBoxService'];

+    angular.module('ecompApp').controller('NewUserModalCtrl', NewUserModalCtrl);

+})();

diff --git a/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.controller.spec.js b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.controller.spec.js
new file mode 100644
index 0000000..8d5ac74
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.controller.spec.js
@@ -0,0 +1,255 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+/**

+ * Created by nnaffar on 12/20/15.

+ */

+'use strict';

+

+describe('Controller: NewUserModalCtrl ', () => {

+    beforeEach(module('testUtils'));

+    beforeEach(module('ecompApp'));

+

+        let promisesTestUtils;

+        //destroy $http default cache before starting to prevent the error 'default cache already exists'

+        //_promisesTestUtils_ comes from testUtils for promises resolve/reject

+        beforeEach(inject((_CacheFactory_, _promisesTestUtils_)=> {

+            _CacheFactory_.destroyAll();

+            promisesTestUtils = _promisesTestUtils_;

+        }));

+

+        let newUser, $controller, $q, $rootScope, $log, $scope;

+

+        let applicationsServiceMock, usersServiceMock, confirmBoxServiceMock;

+        let deferredAdminApps, deferredUsersAccounts, deferredUsersAppRoles, deferredUsersAppRoleUpdate;

+

+        beforeEach(inject((_$controller_, _$q_, _$rootScope_, _$log_)=> {

+            $rootScope = _$rootScope_;

+            $q = _$q_;

+            $controller = _$controller_;

+            $log = _$log_;

+        }));

+

+    beforeEach(()=> {

+            [deferredAdminApps, deferredUsersAccounts, deferredUsersAppRoles, deferredUsersAppRoleUpdate] = [$q.defer(),$q.defer(), $q.defer(), $q.defer()];

+

+            /*applicationsServiceMock = {

+                getAdminApps: () => {

+                    var promise = () => {return deferredAdminApps.promise};

+                    var cancel = jasmine.createSpy();

+                    return {

+                        promise: promise,

+                        cancel: cancel

+                    }

+                }

+            };*/

+

+            confirmBoxServiceMock = {

+                deleteItem: () => {

+                    var promise = () => {return deferredAdminApps.promise};

+                    var cancel = jasmine.createSpy();

+                    return {

+                        promise: promise,

+                        cancel: cancel

+                    }

+                }

+            };

+

+            applicationsServiceMock = jasmine.createSpyObj('applicationsServiceMock', ['getAdminAppsSimpler']);

+            applicationsServiceMock.getAdminAppsSimpler.and.returnValue(deferredAdminApps.promise);

+

+            usersServiceMock = jasmine.createSpyObj('usersServiceMock', ['getAccountUsers','getUserAppRoles','updateUserAppsRoles']);

+

+            //applicationsServiceMock.getAdminApps().promise().and.returnValue(deferredAdminApps.promise);

+            usersServiceMock.getAccountUsers.and.returnValue(deferredUsersAccounts.promise);

+            usersServiceMock.getUserAppRoles.and.returnValue(deferredUsersAppRoles.promise);

+            usersServiceMock.updateUserAppsRoles.and.returnValue(deferredUsersAppRoleUpdate.promise);

+

+            $scope = $rootScope.$new();

+            newUser = $controller('NewUserModalCtrl', {

+                $scope: $scope,

+                $log: $log,

+                usersService: usersServiceMock,

+                applicationsService: applicationsServiceMock,

+                confirmBoxService: confirmBoxServiceMock

+            });

+            //$scope.users = users;

+        });

+

+        /*beforeEach(()=> {

+            scope = $rootScope.$new();

+            newUser = $controller('NewUserModalCtrl', {

+                $scope: scope,

+                $log: $log,

+                usersService: usersService,

+                applicationsService: applicationsService,

+                confirmBoxService: confirmBoxService

+            });

+        });*/

+

+

+        it('should open modal window without user when no user is selected', ()=> {

+            expect(newUser.selectedUser).toBe(null);

+        });

+

+        it('should open modal window with selectedUser apps roles when user is selected', ()=> {

+            let roles = {apps: [{id: 1, appRoles: [{id: 3, isApplied: true}]}]};

+            let someUser = {orgUserId: 'asdfjl'};

+

+           deferredUsersAppRoles.resolve(roles);

+           deferredAdminApps.resolve(roles.apps);

+

+            $scope.ngDialogData = {

+                selectedUser: someUser,

+                dialogState: 2

+            };

+

+            //inject ngDialogData to the scope controller

+            newUser = $controller('NewUserModalCtrl', {

+                $scope: $scope,

+                $log: $log,

+                usersService: usersServiceMock,

+                applicationsService: applicationsServiceMock,

+                confirmBoxService: confirmBoxServiceMock

+            });

+

+            newUser.getUserAppsRoles();

+            $scope.$apply();

+

+            expect(newUser.selectedUser).toBe(someUser);

+            expect(newUser.adminApps).toEqual(roles.apps);

+        });

+

+        it('should push to apps order list only apps that has applied roles when initializing', () => {

+            let roles = {apps: [{appId: 13, appRoles: [{id: 3, isApplied: true}]},{appId: 20, appRoles: [{id: 3, isApplied: false}]}]};

+            let someUser = {orgUserId: 'asdfjl'};

+

+            deferredUsersAppRoles.resolve(roles);

+            //deferredAdminApps.resolve(roles.apps);

+

+            $scope.ngDialogData = {

+                selectedUser: someUser,

+                dialogState: 2

+            };

+

+            //inject ngDialogData to the scope controller

+            newUser = $controller('NewUserModalCtrl', {

+                $scope: $scope,

+                $log: $log,

+                usersService: usersServiceMock,

+                applicationsService: applicationsServiceMock,

+                confirmBoxService: confirmBoxServiceMock

+            });

+

+            $scope.$apply();

+

+           // expect(newUser.appsOrder).toEqual([13]);

+        });

+

+        it('should push app to apps order list when applying at least one role to user from app', () => {

+            let roles = {apps: [{appId: 13, appRoles: [{id: 3, isApplied: true}]},{appId: 20, appRoles: [{id: 3, isApplied: false}]}]};

+            let someUser = {orgUserId: 'asdfjl'};

+

+            // promisesTestUtils.resolvePromise(usersService, 'getUserAppsRoles', roles);

+            deferredUsersAppRoles.resolve(roles);

+

+            $scope.ngDialogData = {

+                selectedUser: someUser,

+                dialogState: 2

+            };

+

+            //inject ngDialogData to the scope controller

+            newUser = $controller('NewUserModalCtrl', {

+                $scope: $scope,

+                $log: $log,

+                usersService: usersServiceMock,

+                applicationsService: applicationsServiceMock,

+                confirmBoxService: confirmBoxServiceMock

+            });

+

+            //$scope.$apply();

+            //newUser.updateAppsOrder({appId: 39, appRoles: [{id: 13, isApplied: true}]});

+            $scope.$apply();

+

+          //  expect(newUser.appsOrder).toEqual([13, 39]);

+        });

+

+

+        it('should remove app from list when removing all user roles in it', () => {

+            let roles = {apps: [{appName: 'aaa', appId: 13, appRoles: [{id: 3, isApplied: true}]},{appName: 'vvv', appId: 20, appRoles: [{id: 3, isApplied: true}]}]};

+            let someUser = {orgUserId: 'asdfjl'};

+

+           // promisesTestUtils.resolvePromise(usersService, 'getUserAppsRoles', roles);

+            promisesTestUtils.resolvePromise(confirmBoxServiceMock, 'deleteItem', true);

+

+            deferredUsersAppRoles.resolve(roles);

+

+            $scope.ngDialogData = {

+                selectedUser: someUser,

+                dialogState: 2

+            };

+

+            //inject ngDialogData to the scope controller

+            newUser = $controller('NewUserModalCtrl', {

+                $scope: $scope,

+                $log: $log,

+                usersService: usersServiceMock,

+                applicationsService: applicationsServiceMock,

+                confirmBoxService: confirmBoxServiceMock

+            });

+

+            $scope.$apply();

+            newUser.deleteApp(roles.apps[0]);

+            $scope.$apply();

+

+           // expect(newUser.appsOrder).toEqual([20]);

+        });

+

+        it('should close the modal when update changes succeeded', () => {

+            let roles = {apps: [{appName: 'aaa', appId: 13, appRoles: [{id: 3, isApplied: true}]},{appName: 'vvv', appId: 20, appRoles: [{id: 3, isApplied: true}]}]};

+            let someUser = {orgUserId: 'asdfjl'};

+            //promisesTestUtils.resolvePromise(usersServiceMock, 'getUserAppsRoles', roles);

+            //promisesTestUtils.resolvePromise(usersServiceMock, 'updateUserAppsRoles');

+            deferredUsersAppRoles.resolve(roles);

+            deferredUsersAppRoleUpdate.resolve();

+            deferredAdminApps.resolve(roles.apps);

+

+            $scope.ngDialogData = {

+                selectedUser: someUser,

+                dialogState: 2

+            };

+

+            //inject ngDialogData to the scope controller

+            newUser = $controller('NewUserModalCtrl', {

+                $scope: $scope,

+                $log: $log,

+                usersService: usersServiceMock,

+                applicationsService: applicationsServiceMock,

+                confirmBoxService: confirmBoxServiceMock

+            });

+            $scope.closeThisDialog = function(){};

+            spyOn($scope, 'closeThisDialog');

+

+            newUser.getUserAppsRoles();

+            $scope.$apply();

+            newUser.updateUserAppsRoles();

+            $scope.$apply();

+            expect($scope.closeThisDialog).toHaveBeenCalledWith(true);

+        });

+    });

diff --git a/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.modal.html b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.modal.html
new file mode 100644
index 0000000..5f26152
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.modal.html
@@ -0,0 +1,84 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="new-user-modal">

+

+    <div class="search-users" ng-show="newUser.dialogState===1">

+

+        <search-users search-title="New User"

+                      selected-user="newUser.selectedUser"></search-users>

+

+        <div class="dialog-control">   

+            <button class="btn btn-alt btn-small" id="next-button"  ng-click="newUser.selectedUser && newUser.getUserAppsRoles()"

+                 ng-class="{disabled: !newUser.selectedUser}">Next

+            </button>

+             <button class="btn btn-alt btn-small" id="cancel-button" ng-click="closeThisDialog()">Cancel</button>

+            

+        </div>

+    </div>

+

+    <div class="user-apps-roles" ng-show="newUser.dialogState===3">

+        <div class="title"

+             ng-bind="newUser.selectedUser.firstName + ' ' + newUser.selectedUser.lastName + ' (' + newUser.selectedUser.orgUserId + ')'"></div>

+

+

+        <div class="app-roles-main">

+            <div class="app-roles-main-title">

+                <span class="left">Access and roles:</span>

+            </div>

+

+            <div class="app-roles-list">

+                <div class="app-item" ng-repeat="app in (newUser.adminApps) track by app.id"  id="app-name-{{app.name.split(' ').join('-')}}" ng-show="!app.isDeleted">

+                    <div class="app-item-left" id="div-app-name-{{app.name.split(' ').join('-')}}">{{app.name | elipsis: 27}}</div>

+                    <div class="app-item-right" id="div-app-name-dropdown-{{app.name.split(' ').join('-')}}" ng-show="!app.isError && !app.isLoading && !app.noChanges && !app.isUpdating && !app.isDoneUpdating && !app.isErrorUpdating">

+                        <multiple-select id="app-roles"

+                                         unique-data="{{$index}}"

+                                         placeholder="Select roles"

+                                         ng-model="app.appRoles"

+                                         on-change="newUser.appChanged($index)"

+                                         name-attr="roleName"

+                                         value-attr="isApplied"></multiple-select>

+                    </div>

+                    <div id="app-item-no-contact" class="app-item-right-error" ng-show="app.isError">{{app.errorMessage}}</div>

+                    <div id="app-item-contacting" class="app-item-right-contacting" ng-show="app.isLoading">Contacting application...</div>

+                    <div id="app-item-no-changes" class="app-item-right-contacting" ng-show="app.noChanges">No changes</div>

+                    <div id="app-item-no-updating" class="app-item-right-contacting" ng-show="app.isUpdating">Updating application...</div>

+                    <div id="app-item-done-updating" class="app-item-right-contacting" ng-show="app.isDoneUpdating">Finished updating application</div>

+                    <div id="app-item-cannot-update" class="app-item-right-error" ng-show="app.isErrorUpdating">Could not update application...</div>

+                    <div id="app-item-delete" class="app-item-delete" ng-click="newUser.deleteApp(app)" ng-show="!app.isLoading && !app.isError"></div>

+                    <div id='ecomp-small-spinner' class="ecomp-small-spinner" ng-show="app.isLoading"></div>

+                </div>

+            </div>

+            

+             <div class="dialog-control">

+                <span id="ecomp-save-spinner" class="ecomp-save-spinner" ng-show="newUser.isSaving || newUser.isGettingAdminApps"></span>

+                <button id="new-user-back-button" class="btn btn-alt btn-small"  ng-show="newUser.isShowBack" ng-click="newUser.navigateBack()">Back</button>

+                <button id="new-user-save-button" class="btn btn-alt btn-small" ng-click="newUser.updateUserAppsRoles()"

+                      ng-disabled="(newUser.anyChanges == false)">Save

+                </button>

+                <button id="new-user-cancel-button" class="btn btn-alt btn-small" ng-click="closeThisDialog()">Cancel</button>

+            </div>

+

+        </div>

+

+    </div>

+

+

+

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.modal.less b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.modal.less
new file mode 100644
index 0000000..68c23e5
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.modal.less
@@ -0,0 +1,112 @@
+.new-user-modal {
+  height: 430px;
+
+  .user-apps-roles{
+    .title{
+      //.n18r;
+      .dGray18r;  //AT&T Dark Gray
+      border-bottom: @portalDBlue 3px solid;
+    }
+	
+	input:not([type="button"]) {
+    height: 13px;
+	} 
+	
+    .app-roles-list{
+      height: 286px;
+      overflow-y: auto;
+      
+      .app-item{
+        border: 1px solid @portalLGray;
+        border-radius: 2px;
+        background-color: @funcBkgGray;
+
+        padding: 10px;
+        margin-top: 8px;
+        //margin-right: 6px;
+        //margin-left: 6px;
+
+        .app-item-left{
+          padding-top: 0;
+          line-height: 30px;
+          height: 30px;
+          vertical-align: middle;
+          display:inline-block;
+          width: 45%;
+          border-radius: 2px;
+          border: 1px solid @portalLGray;
+          margin-right: 10px;
+          padding-left: 4px;
+          background: @portalWhite;
+          white-space: nowrap;
+
+        }
+        .app-item-right{
+          display:inline-block;
+          width: 45%;
+          border-radius: 2px;
+          border: 1px solid @portalLGray;
+          background: @portalWhite;
+          vertical-align: middle;
+        }
+
+        .app-item-right-error{
+          .portalRed;
+          padding: 7px 7px 7px 7px;
+          display:inline-block;
+          width: 45%;
+          border-radius: 2px;
+          border: 1px solid @portalLGray;
+          background: @portalWhite;
+          vertical-align: middle;
+        }
+
+        .app-item-right-contacting{
+          .portalGreen;
+          padding: 7px 7px 7px 7px;
+          display:inline-block;
+          width: 45%;
+          border-radius: 2px;
+          border: 1px solid @portalLGray;
+          background: @portalWhite;
+          vertical-align: middle;
+        }
+
+        .app-select-left{
+          width: 45%;
+          margin-right: 10px;
+          vertical-align: middle;
+
+
+          .select-field{
+            padding-top: 0;
+            line-height: 30px;
+            height: 30px;
+            vertical-align: middle;
+            border-radius: 2px;
+            border: 1px solid @portalLGray;
+            margin-right: 10px;
+            padding-left: 4px;
+            background: @portalWhite;
+            display:inline-block;
+          }
+        }
+
+
+        .app-item-delete{
+          .ico_trash_default;
+          display: inline-block;
+          vertical-align: 2px;
+          cursor: pointer;
+          position: relative;
+          top: 6px;
+          color: transparent;
+          margin-left: 8px;
+
+        }
+
+      }
+    }
+
+  }
+}
diff --git a/ecomp-portal-FE-common/client/app/views/users/users.controller.js b/ecomp-portal-FE-common/client/app/views/users/users.controller.js
new file mode 100644
index 0000000..1aa6760
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/users/users.controller.js
@@ -0,0 +1,243 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+(function () {

+    class UsersCtrl {

+        constructor($log, applicationsService, usersService, confirmBoxService, $scope, ngDialog) {

+            this.$log = $log;

+            $scope.adminAppsIsNull = false;

+            $scope.appsIsDown = false;

+            $scope.noUsersInApp = false;

+            $scope.multiAppAdmin = false;

+

+            $log.info('UsersCtrl:: initializing...');

+            /**

+             * Handle all active HTTP requests

+             * activeRequests @type {Array[requests with cancel option]}

+             */

+            let activeRequests = [];

+            let clearReq = (req) => {

+                activeRequests.splice(activeRequests.indexOf(req), 1);

+            };

+

+            let init = () => {

+                this.isLoadingTable = false;

+                this.selectedApp = null;

+                this.isAppSelectDisabled = false;

+                this.selectApp = 'Select application';

+                this.adminApps = [{index: 0, id: 0, value: this.selectApp, title: this.selectApp}];

+                getAdminApps();

+

+                /*Table general configuration params*/

+                this.searchString = '';

+                /*Table data*/

+                this.usersTableHeaders = ['First Name', 'Last Name', 'User ID', 'Roles'];

+                this.accountUsers = [];

+            };

+

+            let getAdminApps = () => {

+                $log.debug('UsersCtrl::getAdminApps: - Starting getAdminApps');

+                try {

+                    this.isLoadingTable = true;

+                    let adminAppsReq = applicationsService.getAdminApps();

+                    adminAppsReq.promise().then(apps => {

+                        if (!apps || !apps.length) {

+                            $log.error('UsersCtrl::getAdminApps:  - no apps found');

+                            return null;

+                        }

+                        $log.debug('UsersCtrl::getAdminApps: Apps for this user are: ' + JSON.stringify(apps));

+                        if (apps.length >= 2) {

+                            $log.info('UsersCtrl::getAdminApps:  - more than one app for this admin:', apps.length, ' apps');

+                            $scope.multiAppAdmin = true;

+                        } else {

+                            this.adminApps = [] ;

+                        }

+                        let sortedApps = apps.sort(getSortOrder("name"));

+                        let realAppIndex = 1;

+                        for(let i=1; i<=sortedApps.length; i++){

+                            this.adminApps.push({

+                                index: realAppIndex,

+                                id: sortedApps[i - 1].id,

+                                value: sortedApps[i - 1].name,

+                                title: sortedApps[i - 1].name

+                            });

+                            realAppIndex = realAppIndex + 1;

+                        }

+

+                        $log.debug('UsersCtrl::getAdminApps: Apps for this user are: ' + JSON.stringify(this.adminApps));

+

+                        this.selectedApp = this.adminApps[0];

+                        clearReq(adminAppsReq);

+                        $scope.adminAppsIsNull = false;

+                        }).catch(e => {

+                            $scope.adminAppsIsNull = true;

+                            $log.error('UsersCtrl::getAdminApps:  - getAdminApps() failed = '+ e.message);

+                            clearReq(adminAppsReq);

+                            confirmBoxService.showInformation('There was a problem retrieving the applications. ' +

+                                'Please try again later.').then(isConfirmed => {});

+

+                    }).finally(() => {

+                            this.isLoadingTable = false;

+                        });

+                    } catch (e) {

+                        $scope.adminAppsIsNull = true;

+                        $log.error('UsersCtrl::getAdminApps:  - getAdminApps() failed!');

+                        this.isLoadingTable = false;

+                    }

+            };

+

+            let getSortOrder = (prop) => {

+                return function(a, b) {

+                    if (a[prop] > b[prop]) {

+                        return 1;

+                    } else if (a[prop] < b[prop]) {

+                        return -1;

+                    }

+                    return 0;

+                }

+            }

+

+            this.updateUsersList = () => {

+                $scope.appsIsDown = false;

+                $scope.noUsersInApp = false;

+                // $log.debug('UsersCtrl::updateUsersList: Starting updateUsersList');

+                //reset search string

+                this.searchString = '';

+                //should i disable this too in case of moving between tabs?

+                this.isAppSelectDisabled = true;

+                //activate spinner

+                this.isLoadingTable = true;

+                

+                if(this.adminApps!=null && this.selectedApp!=null){

+                	 var tempSelected = null;

+                	 for(let i=0; i<=this.adminApps.length; i++){

+                     	if(typeof this.adminApps[i] != 'undefined' && this.selectedApp.value==this.adminApps[i].value){

+                     		tempSelected=_.clone(this.adminApps[i]);

+                     	}

+                     }

+                     if(tempSelected!=null){

+                     	this.selectedApp= tempSelected;

+                     }

+                }

+               

+                if (this.selectedApp.title != this.selectApp) { // 'Select Application'

+                    usersService.getAccountUsers(this.selectedApp.id)

+                        .then(accountUsers => {

+                            $log.debug('UsersCtrl::updateUsersList accountUsers: '+ accountUsers);

+                            if (angular.isObject(accountUsers)===false) {

+                                $log.error('UsersCtrl::updateUsersList accountUsers: App is down!');

+                                $scope.appsIsDown = true;

+                            }

+                            $log.debug('UsersCtrl::updateUsersList length: '+ Object.keys(accountUsers).length);

+                            this.isAppSelectDisabled = false;

+                            this.accountUsers = accountUsers;

+                            if (angular.isObject(accountUsers) && Object.keys(accountUsers).length === 0) {

+                                $log.debug('UsersCtrl::updateUsersList accountUsers: App has no users.');

+                                $scope.noUsersInApp = true;

+                            }

+                        }).catch(err => {

+                            this.isAppSelectDisabled = false;

+                            $log.error('UsersCtrl::updateUsersList error: ' + err);

+                            confirmBoxService.showInformation('There was a problem updating the users List. ' +

+                                'Please try again later.').then(isConfirmed => {});

+                            $scope.appsIsDown = true;

+                        }).finally(() => {

+                            this.isLoadingTable = false;

+                            $scope.noAppSelected = false;

+                    });

+                } else {

+                    // this.selectedApp = this.adminApps[0];

+                    this.isAppSelectDisabled = false;

+                    this.isLoadingTable = false;

+                    $scope.noUsersInApp = false;

+                    $scope.noAppSelected = true;

+                }

+            };

+

+

+            this.openAddNewUserModal = (user) => {

+                let data = null;

+                if (user) {

+                    data = {

+                        dialogState: 3,

+                        selectedUser: {

+                            orgUserId: user.orgUserId,

+                            firstName: user.firstName,

+                            lastName: user.lastName

+                        }

+                    }

+                }

+                ngDialog.open({

+                    templateUrl: 'app/views/users/new-user-dialogs/new-user.modal.html',

+                    controller: 'NewUserModalCtrl',

+                    controllerAs: 'newUser',

+                    data: data

+                }).closePromise.then(needUpdate => {

+                    if (needUpdate.value === true) {

+                        $log.debug('UsersCtrl::openAddNewUserModal updating table data...');

+                        this.updateUsersList();

+                    }

+                });

+            };

+            

+            this.openBulkUserUploadModal = (adminApps) => {

+                let data = null;

+                if (adminApps) {

+                    data = {

+                        dialogState: 3,

+                        selectedApplication: {

+                            appid: adminApps[0].appid,

+                            appName: adminApps[0].appName

+                        }

+                    }

+                }

+                ngDialog.open({

+                    templateUrl: 'app/views/users/new-user-dialogs/bulk-user.modal.html',

+                    controller: 'BulkUserModalCtrl',

+                    controllerAs: 'bulkUser',

+                    data: data

+                }).closePromise.then(needUpdate => {

+                	this.updateUsersList();

+                });

+            };

+

+

+            $scope.$watch('users.selectedApp.value', (newVal, oldVal) => {

+                if (!newVal || _.isEqual(newVal, oldVal)) {

+                    return;

+                }

+                $log.debug('UsersCtrl::openAddNewUserModal:$watch selectedApp -> Fire with: ', newVal);

+                this.accountUsers = []; //reset table and show swirl here

+                this.updateUsersList();

+            });

+

+            $scope.$on('$destroy', () => {

+                //cancel all active requests when closing the modal

+                activeRequests.forEach(req => {

+                    req.cancel();

+                });

+            });

+

+            init();

+        }

+    }

+    UsersCtrl.$inject = ['$log', 'applicationsService', 'usersService', 'confirmBoxService', '$scope', 'ngDialog'];

+    angular.module('ecompApp').controller('UsersCtrl', UsersCtrl);

+})();

diff --git a/ecomp-portal-FE-common/client/app/views/users/users.controller.spec.js b/ecomp-portal-FE-common/client/app/views/users/users.controller.spec.js
new file mode 100644
index 0000000..9623116
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/users/users.controller.spec.js
@@ -0,0 +1,141 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+// /**

+//  * Created by nnaffar on 12/17/15.

+//  */

+// 'use strict';

+//

+// describe('Controller: UsersCtrl ', () => {

+//     beforeEach(module('ecompApp'));

+//

+//     //destroy $http default cache before starting to prevent the error 'default cache already exists'

+//     beforeEach(inject((_CacheFactory_)=> {

+//         _CacheFactory_.destroyAll();

+//     }));

+//

+//     let users, $controller, $q, $rootScope, $log, $scope;

+//

+//     beforeEach(inject((_$controller_, _$q_, _$rootScope_, _$log_)=> {

+//         [$controller, $q, $rootScope, $log] = [_$controller_, _$q_, _$rootScope_, _$log_];

+//     }));

+//

+//     let applicationsServiceMock, usersServiceMock;

+//     let deferredAdminApps, deferredUsersAccounts;

+//     beforeEach(()=> {

+//         [deferredAdminApps, deferredUsersAccounts] = [$q.defer(), $q.defer()];

+//

+//         applicationsServiceMock = {

+//             getAdminApps: () => {

+//                 var promise = () => {return deferredAdminApps.promise};

+//                 var cancel = jasmine.createSpy();

+//                 return {

+//                     promise: promise,

+//                     cancel: cancel

+//                 }

+//             }

+//         };

+//

+//         usersServiceMock = jasmine.createSpyObj('usersServiceMock', ['getAccountUsers']);

+//

+//         //applicationsServiceMock.getAdminApps().promise().and.returnValue(deferredAdminApps.promise);

+//         usersServiceMock.getAccountUsers.and.returnValue(deferredUsersAccounts.promise);

+//

+//         $scope = $rootScope.$new();

+//         users = $controller('UsersCtrl', {

+//             $log: $log,

+//             applicationsService: applicationsServiceMock,

+//             usersService: usersServiceMock,

+//             $scope: $scope

+//         });

+//         $scope.users = users;

+//     });

+//

+//     //MOCKS

+//     let appsListMock = [

+//         {value: 'SSP', title: 'SSP', id: 3},

+//         {value: 'ASDC', title: 'ASDC', id: 23},

+//         {value: 'Formation', title: 'Formation', id: 223}

+//     ];

+//

+//     let usersListMock = [

+//         {

+//             "orgUserId": "nn605g",

+//             "firstName": "Nabil",

+//             "lastName": "Naffar",

+//             "roles": [

+//                 {

+//                     "roleId": 1,

+//                     "roleName": "Standard user"

+//                 },

+//                 {

+//                     "roleId": 9,

+//                     "roleName": "Super standard user"

+//                 },

+//                 {

+//                     "roleId": 2,

+//                     "roleName": "Super duper standard user"

+//                 }

+//             ]

+//         }];

+//     let secondUsersListMock = [

+//         {

+//             "orgUserId": "sadf7",

+//             "firstName": "John",

+//             "lastName": "Hall",

+//             "roles": [

+//                 {

+//                     "roleId": 1,

+//                     "roleName": "Standard user"

+//                 },

+//                 {

+//                     "roleId": 2,

+//                     "roleName": "Super duper standard user"

+//                 }

+//             ]

+//         }];

+//

+//     it('should get all user\'s administrated applications when initializing the view', ()=> {

+//         deferredAdminApps.resolve(appsListMock);

+//         deferredUsersAccounts.resolve(usersListMock);

+//         $scope.$apply();

+//         expect(users.adminApps).toEqual(appsListMock);

+//         expect(users.selectedApp).toEqual(appsListMock[0]);

+//     });

+//

+//     it('should get first application users by default when initializing the view', () => {

+//         $scope.$apply();

+//         deferredAdminApps.resolve(appsListMock);

+//         deferredUsersAccounts.resolve(usersListMock);

+//         $scope.$apply();

+//         expect(users.accountUsers).toEqual(usersListMock);

+//     });

+//

+//     it('should get application users when changing application', () => {

+//         $scope.$apply();

+//         deferredAdminApps.resolve(appsListMock);

+//         $scope.$apply();

+//

+//         users.selectedApp = appsListMock[1];

+//         deferredUsersAccounts.resolve(secondUsersListMock);

+//         $scope.$apply('users');//change app

+//

+//         expect(users.accountUsers).toEqual(secondUsersListMock);

+//     });

+// });

diff --git a/ecomp-portal-FE-common/client/app/views/users/users.less b/ecomp-portal-FE-common/client/app/views/users/users.less
new file mode 100644
index 0000000..7a0e9eb
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/users/users.less
@@ -0,0 +1,47 @@
+.users-page-main{
+  .bg_portalWhite;//white for 1702
+ //.bg_portalGray;  // gray for 1610
+  position: @page-main-position;
+  top: @page-main-top;
+  left: @page-main-left;
+  right: @page-main-right;
+  bottom: @page-main-bottom;
+  padding-top: @padding-top;
+  overflow-y: @page-main-overflow-y;
+  padding-left: @padding-left-side;
+#input-table-search::-webkit-input-placeholder,
+{
+font-style: italic;
+  color:    #D7D7D7;
+}
+    .users-table {
+      width: @table-width;
+      //margin-left: @table-margin-left;
+      margin: 0 auto;
+
+    }
+
+  .error-text {
+    width: 1170px;
+    margin: auto;
+    padding: 10px;
+    left: 20px;
+    font-weight: bold;
+    font-size: 16px;
+    text-align: left;
+    color: @err;
+    // background-color: @portalWhite;
+    background-color: @u;
+
+    .error-help {
+      color: @o; //@portalDGray;
+      font-weight: normal;
+    }
+
+    .error-help-bold {
+      color: @o; //@portalDGray;
+      font-weight: bold;
+    }
+
+  }
+}
diff --git a/ecomp-portal-FE-common/client/app/views/users/users.tpl.html b/ecomp-portal-FE-common/client/app/views/users/users.tpl.html
new file mode 100644
index 0000000..ff3edde
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/users/users.tpl.html
@@ -0,0 +1,98 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="w-ecomp-main">

+    <div class="w-ecomp-main-container" >

+        <div class="users-page-main" id="page-content">

+            <div id="users-page-title" class="w-ecomp-main-view-title">

+            	 <h1 class="heading-page" >Users</h1>

+            </div>

+            <div class="users-table">

+                <div class="table-control">

+                    <div class="table-control-fields">		

+						<div class="table-dropdown">

+							<select id="dropdown1" name="dropdown1" b2b-dropdown placeholder-text="Select Application" ng-model="users.selectedApp.value">

+				            	<option b2b-dropdown-list option-repeat="d in users.adminApps" value="{{d.value}}">{{d.title}}</option>

+				            </select>

+						</div>

+						<div>

+							<input id="input-table-search" placeholder="Search" class="table-search-field" type="text" data-ng-model="users.searchString">

+						</div>

+						<button class="btn btn-alt btn-small" ng-click="users.openAddNewUserModal()"><i class="icon-people-userbookmark" aria-hidden="true"></i>&nbsp;Add User</button> 

+	                   	<button class="btn btn-alt btn-small" ng-click="users.openBulkUserUploadModal()"><i class="icon-arrows-upload" aria-hidden="true"></i>&nbsp;Bulk Upload</button>

+					</div>

+                </div>

+                <div ng-hide="users.isLoadingTable">

+                    <div class="error-text" id="div-select-app" ng-show="noAppSelected">

+                        <p class="error-help">Use the 'Select application' dropdown to see users.</p>

+                    </div>

+                    <div class="error-text"

+                         id="div-error-no-users"

+                         ng-show="noUsersInApp">

+                        <p>&nbsp;</p>

+                        <p class="error-help">

+                        	No users found. Select "Add User" to add a User to the application.

+                        </p>

+                    </div>

+                    <div class="error-text"

+                         id="div-error-app-down"

+                         ng-show="appsIsDown" >

+                        <p>&nbsp;</p>

+                        <p class="error-help">

+                        	Failed to communicate with the application.

+                            Please try again later or contact a system administrator.

+                        </p>

+                    </div>

+                </div>

+                <span class="ecomp-spinner" ng-show="users.isLoadingTable"></span>

+                <div b2b-table table-data="users.accountUsers"  ng-hide="users.isLoadingTable"	search-string="users.searchString" class="b2b-table-div">

+					<table>

+						<thead b2b-table-row type="header">

+							<tr >

+								<th b2b-table-header key="firstName" sortable="true" id="col1">First Name</th>

+								<th b2b-table-header key="lastName" sortable="true" id="col2">Last Name</th>

+								<th b2b-table-header key="orgUserId" sortable="true" id="col3">User ID</th>

+								<th b2b-table-header key="" sortable="falses" id="col4">Roles</th>

+							</tr>

+						</thead>

+						<tbody b2b-table-row type="body" 	row-repeat="rowData in users.accountUsers">

+							<tr ng-click="users.openAddNewUserModal(rowData)">

+								<td b2b-table-body id="rowheader_t1_{{$index}}" headers="col1" ng-bind="rowData.firstName"></td>

+								<td b2b-table-body headers="rowheader_t1_{{$index}} col2" ng-bind="rowData.lastName"></td>

+								<td b2b-table-body headers="rowheader_t1_{{$index}} col3" ng-bind="rowData.orgUserId"></td>

+								<td b2b-table-body headers="rowheader_t1_{{$index}} col4">

+									<div class="ecomp-table-repeat" ng-repeat="role in rowData.roles" ng-bind="role.name"></div>

+								</td>							

+							</tr>

+						</tbody>

+					</table>

+				</div>

+            </div>

+            <div class="error-text"  id="div-error-403" ng-show="adminAppsIsNull==true">

+                <h1>Attention:</h1>

+                <p>&nbsp;</p>

+                <p class="error-help">It appears that you have not been added as an admin yet to an application.</p>

+                <p>&nbsp;</p>

+                <p class="error-help">Click on the Admins link to the left and check and see if you are listed as an admin for an application.

+                    If not, you can add yourself to the appropriate application.</p>

+            </div>

+        </div>

+    </div>

+

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/widget-catalog/widget-catalog.controller.js b/ecomp-portal-FE-common/client/app/views/widget-catalog/widget-catalog.controller.js
new file mode 100644
index 0000000..2b0cc5c
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/widget-catalog/widget-catalog.controller.js
@@ -0,0 +1,350 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+(function () {

+

+    class WidgetCatalogCtrl {

+        constructor(widgetsService, $log, $cookies, $scope, conf, beReaderService, widgetsCatalogService, userProfileService,dashboardService,$compile, ngDialog) {

+            

+        	$scope.editWidgetModalPopup = function(availableData, resourceType) {

+    			$scope.editData = JSON.stringify(availableData);

+    			$scope.availableDataTemp = $scope.availableData;

+    			ngDialog.open({

+    					templateUrl : 'app/views/dashboard/dashboard-widget-manage.html',

+    					controller : 'CommonWidgetController',

+    					resolve : {

+    						message : function message() {

+    							var message = {

+    								type : resourceType,

+    								availableData : $scope.editData

+    							};

+    							return message;

+    						}

+    					}

+    				}).closePromise.then(needUpdate => {	

+    					if(resourceType=='NEWS'){

+    						$scope.updateNews();

+    					}else if(resourceType=='EVENTS'){

+    						$scope.updateEvents();

+    					}else if(resourceType=='IMPORTANTRESOURCES'){

+    						$scope.updateImportRes();

+    					}

+    	          });		

+    		};

+    		

+        	$scope.WidgetCatView = [];  

+        	$scope.applyPresentationDetailsToWidgetsCatalog = function(widgetsReturned){

+        		var rowNo = 0;

+        		for (var i = 0; i < widgetsReturned.length; i++) {

+        		$scope.WidgetCatView[i] = {

+        		sizeX : 2,

+        		sizeY : 2,

+        		headerText:'',

+        		widgetIdentifier : '',

+        		url : '',

+        		widgetid: '',

+        		attrb:'',

+        		select: false,

+        		};

+        		$scope.WidgetCatView[i].widgetid = widgetsReturned[i].id;

+        		$scope.WidgetCatView[i].headerText = widgetsReturned[i].headerName;

+

+        		if(widgetsReturned[i].headerName === 'widget-news'){

+        		$scope.WidgetCatView[i].widgetIdentifier = 'NEWS';

+        		}

+        		else

+        		if(widgetsReturned[i].headerName === 'widget-resources'){

+        		$scope.WidgetCatView[i].widgetIdentifier = 'IMPORTANTRESOURCES';

+        		}

+        		else

+        		if(widgetsReturned[i].headerName === 'widget-events'){

+        		$scope.WidgetCatView[i].widgetIdentifier = 'EVENTS';

+        		}

+

+        		$scope.WidgetCatView[i].url = widgetsReturned[i].url;

+        		$scope.WidgetCatView[i].attrb = widgetsReturned[i].attrs;

+        		$scope.WidgetCatView[i].select = widgetsReturned[i].select;        		

+        		}

+

+        		$scope.widgetViewData = $scope.WidgetCatView;

+        		

+        		}

+        	

+        	/** Widget code starts */

+            let getUserWidgets = (loginName) => {

+            	 

+            	 this.isCommError = false;

+            	 var conf = this.conf;

+        		 widgetsCatalogService.getUserWidgets(loginName).then(res => {

+        			 

+        			 if(!(res instanceof Array)){

+        				this.isCommError = true;

+        				return;

+        			 }

+            		 for(var i = 0; i < res.length; i++){	

+            			 var widget_id = res[i][0];

+            			 var widget_name = res[i][1];

+            			 let url = this.conf.api.widgetCommon + "/" + widget_id + "/framework.js";

+            			 $scope.widgetsList.push({

+			    			   id: widget_id,

+			                   name: widget_name,

+			                   headerName: widget_name,

+			                   url: url,

+			                   attrs: [{attr: 'data-' + widget_id, value: ''}],

+			                   status: res[i][4],

+			                   select: (res[i][4] == 'S' || res[i][4] === null) ? true : false

+			    		 }); 

+            			 var script = document

+         				 .createElement('script');

+         				 script.src = url;

+         				 script.async = true;

+         				 var entry = document

+         				 	.getElementsByTagName('script')[0];

+         				 entry.parentNode

+         				 	.insertBefore(script, entry);

+            		 }

+            		 $scope.applyPresentationDetailsToWidgetsCatalog($scope.widgetsList);

+                 }).catch(err => {

+                     $log.error('WidgetCatalogCtrl::getUserWidgets caught error', err);

+                 }).finally(()=> {

+                	 

+                 });

+            };

+            

+            let init = () => {

+            	userProfileService.getUserProfile()

+                .then(profile=> {

+                    // $log.info('WidgetCatalogCtrl::getUserProfile: ',

+					// profile);

+                    $scope.orgUserId = profile.orgUserId;

+                    $scope.widgetsViewData = [];

+                    $scope.widgetsView = [];    

+                	getUserWidgets($scope.orgUserId);  

+                });

+                this.conf = conf;

+                $scope.widgetsList = [];

+            };

+            

+            /** Widget code ends */

+          

+            $scope.activateThis = function(ele){

+   			 	$compile(ele.contents())($scope);

+   			 	$scope.$apply();

+            };

+            $scope.setCommonWidget = function() {

+            	/* News Events Resources */

+                var widgetLength = ($scope.widgetsViewData==null || $scope.widgetsViewData.length==0) ? 0:$scope.widgetsViewData.length;

+                $scope.widgetsViewData[widgetLength] = {

+                        sizeX: 2,

+                        sizeY: 2,

+                        headerText: 'News',

+                        width: '',

+                        height: '',

+                        url: '',

+                        selected:true

+                    };

+                $scope.widgetsViewData[widgetLength+1] = {

+                        sizeX: 2,

+                        sizeY: 2,

+                        headerText: 'Calendar Events',

+                        width: '',

+                        height: '',

+                        url: '',

+                        selected:true

+                    };

+                $scope.widgetsViewData[widgetLength+2] = {

+                        sizeX: 2,

+                        sizeY: 2,

+                        headerText: 'Resources',

+                        width: '',

+                        height: '',

+                        url: '',

+                        selected:true

+                    };

+                

+                /* Setting News data */

+        		$scope.newsData = [];

+        		$scope.updateNews = function() {

+        			$scope.newsData.length=0;

+        			dashboardService.getCommonWidgetData('NEWS').then(function(res) {

+        				// $log.info(res.message);

+        				var newsDataJsonArray = res.response.items;

+        				for (var i = 0; i < newsDataJsonArray.length; i++) {

+        					$scope.newsData.push(newsDataJsonArray[i]);

+        				}

+        			})['catch'](function(err) {

+        				$log.error('dashboard controller: failed to get news list', err);        				

+        			});

+        		}

+        		$scope.updateNews();

+        		/* Setting Events data */

+

+        		$scope.eventData = [];

+        		

+        		$scope.updateEvents = function() {

+        			$scope.eventData.length=0;

+        			dashboardService.getCommonWidgetData('EVENTS').then(function(res) {

+        				var eventDataJsonArray = res.response.items;	

+        				for (var i = 0; i < eventDataJsonArray.length; i++) {

+        					if(eventDataJsonArray[i].eventDate !=null) {

+        						// yyyy-mm-dd

+        						eventDataJsonArray[i].year = eventDataJsonArray[i].eventDate.substring(2,4);

+        						eventDataJsonArray[i].mon  = eventDataJsonArray[i].eventDate.substring(5,7);

+        						eventDataJsonArray[i].day  = eventDataJsonArray[i].eventDate.substring(8,10);

+        					}

+        					$scope.eventData.push(eventDataJsonArray[i]);

+        				}

+        			})['catch'](function(err) {

+        				$log.error('dashboard controller: failed to get Events list', err);	

+        			});

+        		}

+        		$scope.updateEvents();

+        		/* Setting Important Resources data */

+

+        		$scope.importResData = [];

+        		$scope.updateImportRes = function() {

+        			$scope.importResData.length=0;

+        			dashboardService.getCommonWidgetData('IMPORTANTRESOURCES').then(

+        					function(res) {

+        						// $log.info(res);

+        						var importResDataJSONArray = res.response.items;

+        						for (var i = 0; i < importResDataJSONArray.length; i++) {

+        							$scope.importResData.push(importResDataJSONArray[i]);

+        						}

+        					})['catch'](function(err) {

+        				$log.error('dashboard controller: failed to get resources list...', err);

+        			});

+        		}

+        		$scope.updateImportRes();

+                

+                /** ******End hardcoded news events and resources*************** */

+        		

+            }

+            

+        	$scope.newsGridsterItem = {

+        			headerText : 'Test',

+        			subHeaderText : ''

+        	};

+        	

+        	$scope.newsGridsterItem = {

+    			headerText : 'News',

+    			subHeaderText : ''

+    		};

+            

+            $scope.eventsGridsterItem = {

+            	headerText : 'Events',

+        		subHeaderText : ''

+        	};

+

+        	$scope.impoResGridsterItem = {

+        		headerText : 'Resources',

+        		subHeaderText : ''

+        	};

+        	

+            this.gridsterOpts = {

+                columns: 6,

+                colWidth: 190,

+                rowHeight: 190,

+                margins: [20, 20],

+                outerMargin: true,

+                pushing: true,

+                floating: true,

+                swapping: true,

+                draggable: {

+                	handle: '.icon-content-gridguide'

+                }

+            };

+            

+            // Run this function when user clicks on checkbox.

+    		$scope.storeSelection = function(widget) {

+    			

+    			// not needed as only 'SHOW' and 'HIDE' status_cd is expected from the micro service now

+    			/*var pendingFlag = false;    			

+    			if(widget.access) 

+    				pendingFlag = false;

+    			else

+    				pendingFlag =  widget.pending;	*/	

+    			

+    			var appData = { 

+    					widgetId: widget.widgetid,

+    					select  : widget.select,

+    				//	pending : pendingFlag	

+    			};

+

+    			widgetsCatalogService.updateWidgetCatalog(appData).then(

+    				function(result) {

+    					// $log.debug('CatalogCtrl:storeSelection result is ', result);

+    				})['catch'](function(err) {

+    					$log.error('CatalogCtrl:storeSelection: exception: ', err);

+    				});

+    		};

+

+            init();

+        }

+    }

+    

+    

+    

+    WidgetCatalogCtrl.$inject = ['widgetsService', '$log', '$cookies', '$scope', 'conf', 'beReaderService', 'widgetsCatalogService', 'userProfileService','dashboardService','$compile','ngDialog'];

+    angular.module('ecompApp').controller('WidgetCatalogCtrl', WidgetCatalogCtrl);

+

+    angular.module('ecompApp').constant('refreshInterval', '30000');

+

+    angular.module('ecompApp').directive('refreshIframe', ['$interval', 'refreshInterval', function ($interval, refreshInterval) {

+

+        function link(scope, element, attrs) {

+            var timeoutId;

+

+            function updateIframe() {

+                if(attrs.isEnlarged == "false")

+                {

+                    element.attr('src', element.attr('src'));

+                }

+            }

+

+            element.on('$destroy', function () {

+                $interval.cancel(timeoutId);

+            });

+

+            // start the UI update process; save the timeoutId for cancelling

+            /*

+			 * timeoutId = $interval(function () { updateIframe(); // update DOM },

+			 * refreshInterval);

+			 */

+        }

+

+        return {

+            link: link

+        };

+    } ]);

+

+})();

+

+

+app.directive('dynAttr', function() {

+    return {

+        scope: { list: '=dynAttr' },

+        link: function(scope, elem, attrs){

+            for(var attr in scope.list){

+                elem.attr(scope.list[attr].attr, scope.list[attr].value);   

+            }

+        }

+    };

+});	 

diff --git a/ecomp-portal-FE-common/client/app/views/widget-catalog/widget-catalog.controller.spec.js b/ecomp-portal-FE-common/client/app/views/widget-catalog/widget-catalog.controller.spec.js
new file mode 100644
index 0000000..77659d9
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/widget-catalog/widget-catalog.controller.spec.js
@@ -0,0 +1,20 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+

diff --git a/ecomp-portal-FE-common/client/app/views/widget-catalog/widget-catalog.less b/ecomp-portal-FE-common/client/app/views/widget-catalog/widget-catalog.less
new file mode 100644
index 0000000..65a74a5
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/widget-catalog/widget-catalog.less
@@ -0,0 +1,156 @@
+.widget-catalog{
+    .bg_portalWhite;//white for 1702
+    //.bg_portalGray;  // gray for 1610
+    position: @page-main-position;
+    top: @page-main-top;
+    left: @page-main-left;
+    right: @page-main-right;
+    bottom: @page-main-bottom;
+    padding-top: @padding-top;
+    overflow-y: @page-main-overflow-y;
+    padding-left: @padding-left-side;
+    margin: auto;
+
+    .widget-title {
+        padding-left: 20px;
+        margin-bottom: -15px;
+    }
+
+  .widget-gridster-header {
+	color: #0574ac;
+    font-family: Omnes-ECOMP-W02-Bold, Arial;
+    font-size: 18px;
+  }
+
+  .widget-gridster-footer {
+    background-color: @portalWhite
+  }
+
+  .widget-iframe {
+    width: 100%;
+    height: 100%;
+  }
+
+  .widget-image {
+    cursor: pointer;
+    width: 20px;
+    height: 25px;
+  }
+
+  .enlarge-content{
+    top: 120px;
+    left: 400px;
+    width:1170px;
+    height:600px;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    background: none repeat scroll 0 0 @funcBkgGray;
+  }
+
+  .enlarge-close-button{
+    .btn-blue;
+    position: relative;
+    top : -20px;
+    left : 1145px;
+    width : 25px;
+  }
+
+  .widgets-home-container {
+    //.content_justify;
+    //position: relative;
+    padding-right: 0;
+    padding-left: 0;
+    padding-bottom: 32px;
+    width: 100%;
+      //margin-left: @table-margin-left;
+
+    .portals-list {
+      min-height: 70vh;
+      //display: flex;
+      //justify-content: center;
+      flex-flow: row wrap;
+      width: @table-width;
+	  margin:auto;
+      //margin-left: @table-margin-left;
+      margin-bottom: 63px;
+
+      .portals-list-item {
+        background-color: @portalWhite;
+        border-radius: 2px;
+        box-shadow: 0px -1px 2px 0px rgba(0, 0, 0, 0.1);
+        display: inline-block;
+        width: 360px;
+        height: 300px;
+        background-size: cover;
+        cursor: pointer;
+        margin: 15px;
+        overflow: hidden;
+
+        .portals-item-info {
+          background-color: @portalWhite;
+          height: 50px;
+          top: 180px;
+         // position: relative;
+          box-shadow: 0px -1px 2px 0px rgba(0, 0, 0, 0.1);
+          padding: 16px;
+
+          .info-title {
+            //.a24r;
+            .dBlue24r;  // AT&T Dark Blue
+            margin-bottom: 4px;
+            
+            text-overflow: ellipsis;
+            overflow: hidden;
+          }
+          .info-description {
+            .portalDBlue16r;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+            overflow: hidden;
+          }
+          .info-button {
+            .btn-green;
+            width: 96px;
+          //  position: absolute;
+            bottom: 16px;
+            left: 16px;
+          }
+
+          &:hover {
+            opacity: .93;
+            z-index: 3;
+          }
+        }
+      }
+    }
+  }
+}
+
+.gridster-item-container .gridster-item-body {
+	bottom:0px;
+	overflow-y:auto;
+    overflow-x:hidden;
+}
+
+@keyframes fadein {
+  from {
+    opacity: 0;
+  }
+  to {
+    opacity: 1;
+  }
+}
+
+#widget-page-content{
+	padding-left:250px;
+}
+
+.widget-centerAlignment{
+margin: auto;
+width: 1170px;
+}
+#widget-checkbox-label{
+margin-left: 190px;
+top: 3px;
+}
diff --git a/ecomp-portal-FE-common/client/app/views/widget-catalog/widget-catalog.tpl.html b/ecomp-portal-FE-common/client/app/views/widget-catalog/widget-catalog.tpl.html
new file mode 100644
index 0000000..1b77a28
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/widget-catalog/widget-catalog.tpl.html
@@ -0,0 +1,76 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="w-ecomp-main">

+	<div class="w-ecomp-main-container">

+		<div class="widget-catalog" id="widget-page-content">

+			<div id="title" class="w-ecomp-main-view-title">

+				<h1 class="heading-page">Widget Catalog</h1>

+			</div>

+			<div class="widget-centerAlignment">

+				<div class="portals-list">

+					<div ng-show="widgetCatalog.isCommError">Failed to

+						communicate with the widget microservice.</div>

+					<div id="widgets" class="information-section"

+						ng-hide="widgetCatalog.isCommError">

+						<div id="news-gridster-container"

+							class="gridster-container override_background">

+							<div class="content" gridster="widgetCatalog.gridsterOpts">

+								<ul>

+									<li gridster-item="widget" ng-repeat="widget in widgetViewData">

+										<div class="box">

+											<div class="box-header">

+												<i style="cursor: move;" class="icon-content-gridguide"></i>

+												<h3>{{ widget.headerText}}</h3>

+												<div class="form-row">

+													<label id="widget-checkbox-label" class="checkbox"> <input type="checkbox"

+														id="{{widget.headerText.split(' ').join('-')}}-checkbox"

+														ng-model="widget.select"

+														ng-change="storeSelection(widget)"> <i

+														class="skin"></i>

+													</label>

+												</div>

+											</div>

+											<div class="box-content">

+												<div dyn-attr="widget.attrb"></div>

+											</div>

+										</div>

+									</li>

+								</ul>

+							</div>

+						</div>

+					</div>

+					<div ng-show="widgetCatalog.isEnlarged">

+						<div id="widgets-button-enlarge-close"

+							class="enlarge-close-button"

+							ng-click="widgetCatalog.isEnlarged=false">X</div>

+						<iframe id="iframe-widget-enlarge-close"

+							ng-src="{{widgetCatalog.enlargeURL | trusted}}"

+							class="enlarge-content"> </iframe>

+					</div>

+				</div>

+			</div>

+

+			<div id="widgets-disclaimer" class="w-ecomp-main-disclaimer">

+				To request access to an application widget, please visit the <a

+					ng-href="getAccess">Get Access</a> page.

+			</div>

+		</div>

+	</div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/widget-onboarding/widget-details-dialog/widget-details.controller.js b/ecomp-portal-FE-common/client/app/views/widget-onboarding/widget-details-dialog/widget-details.controller.js
new file mode 100644
index 0000000..0a9dd12
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/widget-onboarding/widget-details-dialog/widget-details.controller.js
@@ -0,0 +1,381 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+(function () {

+    class WidgetOnboardingDetailsModalCtrl {

+        constructor($scope, $log, $interval, applicationsService, adminsService, microserviceService, widgetsCatalogService, errorMessageByCode, ECOMP_URL_REGEX, $window,userProfileService, confirmBoxService, $cookies) {

+     	    

+    	    this.appUpdate = function(){

+    	    	this.hasSelectedApp = false;

+    	    	this.appCounter = 0;

+    	    	for(var i = 0; i < this.availableApps.length; i++){

+    	    		if(this.availableApps[i].isSelected){

+    	    			this.appCounter++;

+    	    			if(!this.hasSelectedApp)

+    	    				this.hasSelectedApp = true;

+    	    		}

+    	    		if(this.availableApps[i].isSelected

+    	    		&& this.availableApps[i].roles.length == 0){

+    	    			var index = i;

+    	    			this.availableRoles = [];    

+    	    			adminsService.getRolesByApp(this.availableApps[i].id).then(roles => {

+ 				    	   for(var i = 0; i < roles.length; i++){

+ 				    		   this.availableRoles.push({

+ 				    			   id: roles[i].id,

+ 				                   name: roles[i].name,

+ 				                   isSelected: false,

+ 				    		   }); 

+ 				    	   }

+ 				    	   this.availableApps[index].roles = this.availableRoles;

+    	    			});

+    	    		}

+			    }

+    	    	this.allRoleSelected = true;

+    	    	this.checkRoleSelected();

+    	    }

+    	    

+    	    this.roleUpdate = function(app){

+	    		this.allRoleSelected = true;

+    	    	for(var i = 0; i < app.roles.length; i++){

+    	    		if(app.roles[i].isSelected){

+    	    			app.roleSelected = true;

+    	    			this.checkRoleSelected();

+    	    			return;

+    	    		}

+    	    	}

+	    		app.roleSelected = false;

+	    		this.checkRoleSelected();

+    	    }

+    	    

+    	    this.checkRoleSelected = function(){

+    	    	for(var i = 0; i < this.availableApps.length; i++){

+            		if(this.availableApps[i].isSelected

+            		&& !this.availableApps[i].roleSelected){

+            			this.allRoleSelected = false;

+            			return;

+            		}

+            	}

+    	    }

+    	    	   

+    	    this.getAppName = function(appId){

+    	    	 for(var i = 0; i < this.availableApps.length; i++){

+    	    		 if(this.availableApps[i].id == appId){

+    	    			 return this.availableApps[i].name;

+    	    		 }

+    	    	 }

+    	    }

+    	    

+            let newWidgetModel = {

+                name: null,

+                roleId: null,

+                roleName: null,

+                appId: null,

+                appName: null,

+                url: null,

+                showAppOptions: false,

+                showRoleOptions: false,

+                hasSelectedApp: false

+            };

+          

+            let getAvailableApps = () => {           	

+            	

+            	if(this.isEditMode == false){	

+	            	applicationsService.getAppsForSuperAdminAndAccountAdmin().then(apps => {

+	            		this.availableApps=[];

+	            		for(var i=0;i<apps.length;i++) {

+	                        if (!apps[i].restrictedApp) {

+                            this.availableApps.push({

+                                id: apps[i].id,

+                                name: apps[i].name,

+                                roles: [],

+                                roleSelected: false,

+                                isSelected: false,

+                            });

+	                        }

+	                    }

+	                }).catch(err => {

+	                    $log.error(err);

+	                });

+            	}

+            	else if(this.isEditMode == true){

+            		if(this.widget.allowAllUser == "Y")

+            			this.widget.allUser = true;

+            		applicationsService.getAppsForSuperAdminAndAccountAdmin().then(apps => {

+	            		this.availableApps=[];

+	            		let selectedApps = {};

+	            		var availableApps = this.availableApps;  

+	            		this.allRoleSelected = true;

+	            		for(var i=0; i < this.widget.widgetRoles.length; i++){

+	            			if(selectedApps[this.widget.widgetRoles[i].app.appId] != undefined)

+	            				selectedApps[this.widget.widgetRoles[i].app.appId] += this.widget.widgetRoles[i].roleId + ";" + this.widget.widgetRoles[i].roleName + ";"; 

+	            			else{

+	            				selectedApps[this.widget.widgetRoles[i].app.appId] = this.widget.widgetRoles[i].roleId + ";" + this.widget.widgetRoles[i].roleName + ";";        		

+	            				this.appCounter++;

+	            			}

+	            		}         		

+	            		apps.forEach(function(app, index){

+		    				availableApps.push({

+		                       id: app.id,

+		                       name: app.name,

+		                       roles: [],

+		                       roleSelected: false,

+		                       isSelected: false,

+		    				});

+		    				if(selectedApps[app.id] != undefined){

+			    				adminsService.getRolesByApp(app.id).then(roles => {

+		        				var role = selectedApps[app.id].split(';');

+			            		var selectedRoles = [];

+			            		var n = 0;

+			            		while((n+1) < role.length){

+			                		selectedRoles.push({

+						    			   id: role[n++],

+						                   name: role[n++],

+						                   isSelected: true,

+						    		});

+			            		}					

+			    				for(var m = 0; m < roles.length; m++){

+						    		var hasSelected = true;

+			    					for(var n = 0; n < selectedRoles.length; n++){

+			    						if(selectedRoles[n].id == roles[m].id){

+			    							hasSelected = false;

+						    				break;

+						    			}

+						    		}

+						    		if(hasSelected){

+						    		   selectedRoles.push({

+						    			   id: roles[m].id,

+						                   name: roles[m].name,

+						                   isSelected: false,

+						    		   }); 

+						    		}	   

+			    				}  

+			    				availableApps[index].roleSelected = true;

+			    				availableApps[index].isSelected = true;

+			    				availableApps[index].roles = selectedRoles;

+		    					});

+		    				}

+	            		})

+	                })

+            	}

+            };

+            

+            let getAvailableServices = () =>{

+            	microserviceService.getServiceList().then(services => {

+              		this.availableServices = [];

+              		for(var i = 0; i < services.length; i++){

+              			this.availableServices.push({

+              				id: services[i].id,

+              				name: services[i].name,

+              				option: services[i].name + ": " + services[i].url

+              			});

+              			

+              			if(this.widget.service != null && this.widget.service.id == services[i].id){

+              				this.widget.serviceURL = this.availableServices[i];

+              			}

+              		}

+                }).catch(err => {

+                   $log.error(err);

+                });

+            }

+            

+

+            let init = () => {

+            	$log.info('WidgetOnboardingDetailsModalCtrl::init');

+                this.widgetsList = [];

+                this.duplicatedName = true;

+                this.allRoleSelected = false;

+                this.appCounter = 0;

+            	this.isSaving = false;

+            	this.allUser = false;

+            	this.emptyWidgetName = false;

+            	

+                if ($scope.ngDialogData && $scope.ngDialogData.widget) {

+                    this.isEditMode = true;

+            		this.allRoleSelected = true;

+                    this.widget = _.clone($scope.ngDialogData.widget);

+                } else {

+                    this.isEditMode = false;

+                    this.widget = _.clone(newWidgetModel);

+                }

+                

+                widgetsCatalogService.getManagedWidgets().then(res => {

+            		for(var i = 0; i < res.length; i++){

+            			this.widgetsList.push(res[i].name);

+            		}

+                 }).catch(err => {

+                     $log.error('WidgetOnboardingDetailsModalCtrl::init error: ' + err);

+                 }).finally(()=> {

+                     this.isLoadingTable = false;

+                 });

+                 getAvailableApps();

+                 getAvailableServices();

+            };

+            this.ECOMP_URL_REGEX = ECOMP_URL_REGEX;

+            this.conflictMessages = {};

+            this.scrollApi = {};

+

+            let resetConflict = fieldName => {

+                delete this.conflictMessages[fieldName];

+                if($scope.widgetForm[fieldName]){

+                    $scope.widgetForm[fieldName].$setValidity('conflict', true);

+                }

+            };

+            

+            this.updateSelectedRole = () => {

+                if (!this.selectedRole) {

+                    return;

+                }

+                this.widget.RoleId = this.selectedRole.id;

+                this.widget.RoleName = this.selectedRole.name;

+            };

+            

+            let emptyCookies = () => {

+                userProfileService.getUserProfile()

+                .then(profile=> {

+                    $log.info('AppDetailsModalCtrl::emptyCookies profile: ', profile);

+                    $scope.orgUserId = profile.orgUserId;

+                    $log.info('user has the following orgUserId: ' + profile.orgUserId);

+                    if ($cookies.getObject($scope.orgUserId + '_widget') != undefined && $cookies.getObject($scope.orgUserId + '_widget') != null) {

+                        $cookies.remove($scope.orgUserId + '_widget');

+                    }

+                });

+            };

+              

+            this.updateWidgetName = () => {

+            	for(var i = 0; i < this.widgetsList.length; i++){

+            		if(this.widget.name.toUpperCase() == this.widgetsList[i].toUpperCase()){

+            			this.duplicatedName = false;

+            			return;

+            		}

+            	}

+            	this.duplicatedName = true;

+            };

+            

+            this.saveChanges = () => {     

+            	

+            	if(!this.isEditMode)

+            		this.updateWidgetName();

+            	

+            	if(this.duplicatedName == false 

+            	   || this.widget.name == ''

+            	   || this.widget.name == undefined){

+                    	this.emptyWidgetName = true;

+                    	return;

+                }

+            	

+            	if((this.widget.file == undefined && !this.isEditMode) ||

+            	(!this.widget.allUser && this.appCounter == 0) ||

+            	this.widget.name == null ||

+            	(!this.widget.allUser && !this.allRoleSelected) ||

+            	this.widget.saving == true)

+            		return;	

+            	

+            	

+        		this.widget.saving = true;

+            	var selectedRoles = [];

+            	if(!this.widget.allUser){

+	            	for(var i = 0; i < this.availableApps.length; i++){

+	            		if(this.availableApps[i].isSelected){

+		        			for(var n = 0; n < this.availableApps[i].roles.length; n++) {

+			    				if(this.availableApps[i].roles[n].isSelected){

+			    				var role = {

+								app: {

+									appId: this.availableApps[i].id

+								},

+								roleId: this.availableApps[i].roles[n].id,

+								roleName: this.availableApps[i].roles[n].name,

+								};

+			    				selectedRoles.push(role);

+			    				}

+			            	}

+	            		}

+	            	}

+            	}

+            	

+            	var allowAllUser = 0;

+            	if(this.widget.allUser)

+            		allowAllUser = 1;  	

+            	

+            	var serviceId = null;

+            	if(this.widget.serviceURL != null &&

+            	this.widget.serviceURL != undefined){

+            		serviceId = this.widget.serviceURL.id;

+            	}

+            	

+            	

+            	var file_loc = this.widget.name + ".zip";

+            	var newWidget = {

+            			name: this.widget.name,

+            			desc: this.widget.desc,

+            			widgetRoles: selectedRoles,

+            			fileLocation: file_loc,

+            			allowAllUser: allowAllUser,

+            			serviceId: serviceId

+            	};

+            	

+            	if(this.isEditMode){

+            		

+            		if(this.widget.file != undefined){

+            			widgetsCatalogService.updateWidgetWithFile(this.widget.file, this.widget.id, newWidget).then(res => {

+            				if(!res.valid){

+            					if(!res.error){

+                					confirmBoxService.showInformation("Could not save. Please retry.");

+                					this.widget.saving = false;

+                					return;

+                				}

+            					confirmBoxService.showInformation(res.error);

+            					this.widget.saving = false;

+            					return;

+            				}

+            				$scope.closeThisDialog(true);

+    	            		this.widget.saving = false;

+            			});	

+            		}

+            		else{

+            			widgetsCatalogService.updateWidget(this.widget.id, newWidget)

+	            		.then(() => {

+	            			$scope.closeThisDialog(true); 

+	                    });

+            		}

+            	}

+            	else{

+            		widgetsCatalogService.createWidget(newWidget, this.widget.file).then(res => {

+	            		if(!res.valid){

+	        				if(!res.error)

+	        					confirmBoxService.showInformation("Could not save. Please retry.");

+	        				else

+	        					confirmBoxService.showInformation(res.error);

+	        				this.widget.saving = false;

+	        				return;

+	        			}

+	            		$scope.closeThisDialog(true);

+	            		this.widget.saving = false;

+            		});

+            	}

+            };

+            init();

+            $scope.$on('$stateChangeStart', e => {

+                e.preventDefault();

+            });

+        }

+    }

+    WidgetOnboardingDetailsModalCtrl.$inject = ['$scope', '$log', '$interval', 'applicationsService', 'adminsService', 'microserviceService', 'widgetsCatalogService', 'errorMessageByCode', 'ECOMP_URL_REGEX', '$window','userProfileService', 'confirmBoxService', '$cookies'];

+    angular.module('ecompApp').controller('WidgetOnboardingDetailsModalCtrl', WidgetOnboardingDetailsModalCtrl);

+})(); 

diff --git a/ecomp-portal-FE-common/client/app/views/widget-onboarding/widget-details-dialog/widget-details.controller.spec.js b/ecomp-portal-FE-common/client/app/views/widget-onboarding/widget-details-dialog/widget-details.controller.spec.js
new file mode 100644
index 0000000..1762fad
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/widget-onboarding/widget-details-dialog/widget-details.controller.spec.js
@@ -0,0 +1,154 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+// describe('Controller: WidgetDetailsModalCtrl', ()=> {

+//     /**

+//      * INITIALIZATION

+//      */

+//     beforeEach(module('testUtils'));

+//     beforeEach(module('ecompApp'));

+//

+//     let promisesTestUtils;

+//     //destroy $http default cache before starting to prevent the error 'default cache already exists'

+//     //_promisesTestUtils_ comes from testUtils for promises resolve/reject

+//     beforeEach(inject((_CacheFactory_, _promisesTestUtils_)=> {

+//         _CacheFactory_.destroyAll();

+//         promisesTestUtils = _promisesTestUtils_;

+//     }));

+//

+//     let widgetDetails, scope, $controller, $q, $rootScope, $log, widgetsService, errorMessageByCode, ECOMP_URL_REGEX;

+//     let deferredAdminApps, deferredUserProfile;

+//     let applicationsServiceMock, widgetsServiceMock, userProfileServiceMock;

+//     beforeEach(inject((_$controller_, _$q_, _$rootScope_, _$log_)=> {

+//         [$controller, $q, $rootScope, $log] = [_$controller_, _$q_, _$rootScope_, _$log_];

+//

+//         deferredAdminApps = $q.defer();

+//         deferredUserProfile = $q.defer();

+//         /*applicationsServiceMock = {

+//             getAppsForSuperAdminAndAccountAdmin: () => {

+//                 var promise = () => {return deferredAdminApps.promise};

+//                 var cancel = jasmine.createSpy();

+//                 return {

+//                     promise: promise,

+//                     cancel: cancel

+//                 }

+//             }

+//         };*/

+//

+//         widgetsServiceMock = {

+//             updateWidget: () => {

+//                 var promise = () => {return deferredAdminApps.promise};

+//                 var cancel = jasmine.createSpy();

+//                 return {

+//                     promise: promise,

+//                     cancel: cancel

+//                 }

+//             },

+//             createWidget: () => {

+//                 var promise = () => {return deferredAdminApps.promise};

+//                 var cancel = jasmine.createSpy();

+//                 return {

+//                     promise: promise,

+//                     cancel: cancel

+//                 }

+//             }

+//         };

+//

+//         userProfileServiceMock = jasmine.createSpyObj('userProfileServiceMock',['getUserProfile']);

+//         userProfileServiceMock.getUserProfile.and.returnValue(deferredUserProfile.promise);

+//

+//         applicationsServiceMock = jasmine.createSpyObj('applicationsServiceMock',['getAppsForSuperAdminAndAccountAdmin']);

+//         applicationsServiceMock.getAppsForSuperAdminAndAccountAdmin.and.returnValue(deferredAdminApps.promise);

+//

+//     }));

+//

+//     beforeEach(()=> {

+//         errorMessageByCode = [];

+//         ECOMP_URL_REGEX = "";

+//         scope = $rootScope.$new();

+//         createController(scope);

+//     });

+//

+//     let createController = scopeObj => {

+//         widgetDetails = $controller('WidgetDetailsModalCtrl', {

+//             $scope: scope,

+//             $log: $log,

+//             applicationsService: applicationsServiceMock,

+//             widgetsService: widgetsServiceMock,

+//             errorMessageByCode: errorMessageByCode,

+//             ECOMP_URL_REGEX: ECOMP_URL_REGEX,

+//             userProfileService: userProfileServiceMock

+//         });

+//     };

+//

+//     /**

+//      * MOCK DATA

+//      */

+//     let newWidgetModel = {

+//         name: null,

+//         appId: null,

+//         appName: null,

+//         width: 360,

+//         height: 300,

+//         url: null

+//     };

+//     let exsistingWidget = {

+//         name: 'some widget',

+//         appId: 1,

+//         appName: 'APP NAME',

+//         width: 360,

+//         height: 300,

+//         url: 'http://a.com'

+//     };

+//     let adminApps = [{id: 1, name: 'a'}, {id: 2, name: 'b'}];

+//

+//     /**

+//      * TEST CASES

+//      */

+//     it('should initialize controller with new widget mode when opening the modal without selected widget', ()=> {

+//         expect(widgetDetails.widget).toEqual(newWidgetModel);

+//     });

+//

+//     it('should initialize controller with exsisting widget details when opening the modal with selected widget', ()=> {

+//         scope.ngDialogData = {

+//             widget: exsistingWidget

+//         };

+//         createController(scope);

+//         expect(widgetDetails.widget).toEqual(exsistingWidget);

+//     });

+//

+//     it('should populate widget selected app name and id when initializing controller with widget', () =>{

+//         deferredAdminApps.resolve(adminApps);

+//         scope.ngDialogData = {

+//             widget: exsistingWidget

+//         };

+//         createController(scope);

+//         scope.$apply();

+//         expect(widgetDetails.widget.appId).toEqual(adminApps[0].id);

+//         expect(widgetDetails.widget.appName).toEqual(adminApps[0].name);

+//     });

+//

+//     //TODO:

+//     //save changes fail - conflict handling

+//     //save changes success

+//

+//

+//

+// });

diff --git a/ecomp-portal-FE-common/client/app/views/widget-onboarding/widget-details-dialog/widget-details.modal.html b/ecomp-portal-FE-common/client/app/views/widget-onboarding/widget-details-dialog/widget-details.modal.html
new file mode 100644
index 0000000..2fa5644
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/widget-onboarding/widget-details-dialog/widget-details.modal.html
@@ -0,0 +1,156 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="widget-details-modal">

+	<div id="'widgets-details-title" class="title">Widget Details</div>

+

+	<div class="widget-properties-main"

+		scroll-top="widgetOnboardingDetails.scrollApi">

+		<form id="widgets-details-form" name="widgetForm" novalidate

+			autocomplete="off">

+

+			<div class="item required">

+				<div class="item-label">Widget Name</div>

+				<input id="widgets-details-input-name" class="input-field"

+					type="text" ng-model="widgetOnboardingDetails.widget.name"

+					ng-change="widgetOnboardingDetails.updateWidgetName()" name="name"

+					ng-pattern="/^[\w -]*$/" maxlength="100"

+					ng-disabled="widgetOnboardingDetails.isEditMode" required />

+

+				<div class="error-container"

+					ng-show="(widgetOnboardingDetails.emptyWidgetName || widgetForm.name.$dirty) && !widgetOnboardingDetails.duplicatedName">

+					<div id="widgets-details-input-name-conflict" class="err-message">Name

+						not available - choose different name</div>

+				</div>

+

+				<div class="error-container"

+					ng-show="(widgetOnboardingDetails.emptyWidgetName || widgetForm.name.$dirty) && widgetOnboardingDetails.duplicatedName">

+					<div ng-messages="widgetForm.name.$error" class="error-container">

+						<small id="widgets-details-input-name-required"

+							class="err-message" ng-message="required">Widget Name is

+							required</small> <small id="widgets-details-input-name-pattern"

+							class="err-message" ng-message="pattern">Widget Name must

+							be letters, numbers, or underscore</small>

+					</div>

+				</div>

+			</div>

+

+

+			<div class="item">

+				<div class="item-label">Widget Description</div>

+				<input id="widgets-details-input-name" class="input-field"

+					type="text" ng-model="widgetOnboardingDetails.widget.desc"

+					name="desc" maxlength="200" />

+			</div>

+

+			<div class="widget-property">

+				<input id="widgets-checkbox-app-is-enabled" type="checkbox"

+					class="checkbox-field"

+					ng-model="widgetOnboardingDetails.widget.allUser" />

+				<div class="property-label checkbox-label">Allow all user

+					access</div>

+			</div>

+

+

+			<div class="item" ng-show="!widgetOnboardingDetails.isEditMode">

+				<div class="item-label">Service Endpoint</div>

+				<div>

+					<select id="widgets-details-input-endpoint-url" name="url"

+						class="input-field"

+						ng-model="widgetOnboardingDetails.widget.serviceURL"

+						ng-options="service as service.option

+						  for service in widgetOnboardingDetails.availableServices">

+						<option value="" selected="selected">Select Microservice

+							Endpoint</option>

+					</select>

+				</div>

+			</div>

+

+			<div class="item required"

+				ng-show="!widgetOnboardingDetails.widget.allUser">

+				<div class="item-label">Application Name</div>

+				<div>

+					<multiple-select id="widget-applications" unique-data="{{$index}}"

+						placeholder="Select Applications"

+						ng-model="widgetOnboardingDetails.availableApps"

+						on-change="widgetOnboardingDetails.appUpdate()" name-attr="name"

+						value-attr="isSelected"> </multiple-select>

+				</div>

+				<div class="error-container"

+					ng-show="widgetOnboardingDetails.appCounter == 0">

+					<div id="widgets-details-input-name-conflict" class="err-message">Please

+						select at least one Application</div>

+				</div>

+			</div>

+

+			<div class="item" ng-show="!widgetOnboardingDetails.widget.allUser">

+				<div ng-show="widgetOnboardingDetails.hasSelectedApp"

+					class="item-label">User Role Name</div>

+				<div ng-repeat="appRoles in widgetOnboardingDetails.availableApps"

+					id="roles-{{appRoles.roles.split(' ').join('-')}}">

+					<div class="item required">

+						<div class="app-item-left" ng-show="appRoles.isSelected">{{appRoles.name}}</div>

+						<div class="app-item-right" ng-show="appRoles.isSelected">

+							<multiple-select id="widget-roles-by-application"

+								unique-data="{{$index}}" placeholder="Select Roles"

+								ng-model="appRoles.roles" name-attr="name"

+								on-change="widgetOnboardingDetails.roleUpdate(appRoles)"

+								value-attr="isSelected"></multiple-select>

+							<div class="error-container" ng-show="!appRoles.roleSelected">

+								<div id="widgets-details-input-name-conflict"

+									class="err-message">Please select at least one role</div>

+							</div>

+						</div>

+					</div>

+				</div>

+			</div>

+

+			<div class="item required">

+				<div class="item-label">Upload Widget</div>

+				<div>

+					<input id="widget-onboarding-details-upload-file"

+						file-model="widgetOnboardingDetails.widget.file" type="file"

+						style="height: 24px;" />

+

+

+					<div class="error-container"

+						ng-show="widgetOnboardingDetails.widget.file == undefined && !widgetOnboardingDetails.isEditMode">

+						<div class="err-message">Please upload your widget (.zip)</div>

+					</div>

+				</div>

+			</div>

+		</form>

+	</div>

+

+	<div class="dialog-control">

+		<span class="ecomp-save-spinner"

+			ng-show="widgetOnboardingDetails.isSaving"></span>

+		<button id="widgets-details-save-button" class="btn btn-alt btn-small"

+			ng-class="{disabled: widgetOnboardingDetails.widget.name == undefined || !widgetOnboardingDetails.duplicatedName 

+			|| (!widgetOnboardingDetails.widget.allUser && widgetOnboardingDetails.appCounter == 0) || (widgetOnboardingDetails.widget.file == undefined && !widgetOnboardingDetails.isEditMode)

+			|| (!widgetOnboardingDetails.widget.allUser && !widgetOnboardingDetails.allRoleSelected) || (widgetOnboardingDetails.widget.saving)}"

+			ng-click="widgetOnboardingDetails.saveChanges()">Save</button>

+		<button id="widgets-details-cancel-button"

+			class="btn btn-alt btn-small" ng-click="closeThisDialog()">Cancel</button>

+

+	</div>

+

+

+

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/widget-onboarding/widget-details-dialog/widget-details.modal.less b/ecomp-portal-FE-common/client/app/views/widget-onboarding/widget-details-dialog/widget-details.modal.less
new file mode 100644
index 0000000..d7cf267
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/widget-onboarding/widget-details-dialog/widget-details.modal.less
@@ -0,0 +1,102 @@
+.widget-details-modal {
+  height: 580px;
+  .title {
+    //.n18r;
+    .dGray18r;  //AT&T Dark Gray
+    border-bottom: @portalDBlue 3px solid;
+  }
+  .widget-input-field{
+  	width:250px;
+  }
+  .widget-properties-main {
+    padding: 16px;
+    height: 460px;
+    overflow-y: auto;
+
+	.widget-property{
+      position: relative;
+      margin-bottom: 18px;
+      .property-label{
+        .dGray14r;
+      }
+      .checkbox-label{
+        display: inline-block;
+        padding-left: 3px;
+      }
+      .checkbox-field{
+        padding: 0;
+        margin: 0;
+        vertical-align: middle;
+        position: relative;
+        top: -1px;
+        height:15px;
+      } 
+      
+    }
+	.widget-upload-field{
+		height:24px;
+      	border: 0px solid #d2d2d2;
+		box-shadow: 0px 0px 2px -2px rgba(0, 0, 0, 0.08) inset;
+		padding-left: 2px;
+      }
+    .item{
+      position: relative;
+      margin-bottom: 18px;
+
+      .input-field{
+        .custom-input-field;
+        width: 100%;
+        &.url{
+          width: 78%;
+          display: inline-block;
+        }
+      }
+
+      .select-field {
+        .custom-select-field;
+      }
+
+      .item-label{
+        .dGray14r;
+      }
+
+      .right-item{
+        position: relative;
+        display: inline-block;
+        width: 48%;
+        float: right;
+      }
+      .left-item{
+        display: inline-block;
+        width: 48%;
+      }
+
+      .url-validation-button{
+        .btn-blue;
+        width: 20%;
+        display: inline-block;
+        float: right;
+      }
+
+      .error-container{
+        position: absolute;
+        width: 280px;
+        display: block;
+        height: 12px;
+        line-height: 12px;
+
+        .err-message{
+          color: @funcRed;
+          font-size: 9px;
+        }
+        .valid-message{
+          color: @funcGreen;
+          font-size: 9px;
+        }
+      }
+
+    }
+
+  }
+
+}
diff --git a/ecomp-portal-FE-common/client/app/views/widget-onboarding/widget-onboarding.controller.js b/ecomp-portal-FE-common/client/app/views/widget-onboarding/widget-onboarding.controller.js
new file mode 100644
index 0000000..a9e5c41
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/widget-onboarding/widget-onboarding.controller.js
@@ -0,0 +1,204 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+(function () {

+    class WidgetOnboardingCtrl {

+        constructor($log, applicationsService, widgetsCatalogService, ngDialog, confirmBoxService,

+                    userProfileService, $cookies, $scope) {

+            $scope.infoMessage = true;

+

+            let populateAvailableApps = widgets => {

+            	let allPortalsFilterObject = {index: 0, title: 'All applications', value: ''};

+                this.availableApps = [allPortalsFilterObject];

+                this.filterByApp = this.availableApps[0];

+                applicationsService.getAppsForSuperAdminAndAccountAdmin().then(myApps => {

+                	var reSortedApp = myApps.sort(getSortOrder("name"));

+                    var realAppIndex = 1;

+                    for (let i = 1; i <= reSortedApp.length; i++) {

+                        if (!reSortedApp[i-1].restrictedApp) {

+                            this.availableApps.push({

+                                index: realAppIndex,

+                                title: reSortedApp[i - 1].name,

+                                value: reSortedApp[i - 1].name

+                            })

+                            realAppIndex = realAppIndex + 1;

+                        }

+                    }

+                }).catch(err => {

+                    $log.error('WidgetOnboardingCtrl:getAppsForSuperAdmin failed', err);

+                });

+            };

+                 

+            let getOnboardingWidgets = () => {

+                this.isLoadingTable = true;

+                this.isCommError = false;

+                widgetsCatalogService.getManagedWidgets().then(res => {

+                	if(!(res instanceof Array)){

+        				this.isCommError = true;

+        				return;

+        			 }

+                	

+                    var reSortedWidget = res.sort(getSortOrder("name"));

+                    $scope.widgetsList = reSortedWidget;

+                    for(var i = 0; i < $scope.widgetsList.length; i++){

+                    	let set = new Set();

+                    	var info = "";

+                    	var appContent = [];

+                    	var appName = [];	

+                    	for(var n = 0; n < $scope.widgetsList[i].widgetRoles.length; n++){

+                    		set.add($scope.widgetsList[i].widgetRoles[n].app.appName);

+                    	}

+                    	if($scope.widgetsList[i].allowAllUser == "Y"){

+                    		info = "All Applications";

+                    		appContent.push("All Applications");

+                    		appName.push("All Applications");

+                    	}

+                    	

+                    	set.forEach(function (item) {

+                    		info = item.toString() + " - ";

+                    		for(var n = 0; n < $scope.widgetsList[i].widgetRoles.length; n++){

+                    			if(item.toString() == $scope.widgetsList[i].widgetRoles[n].app.appName){

+                        			info += $scope.widgetsList[i].widgetRoles[n].roleName + "; ";

+                        		}

+                    		}

+                    		appContent.push(info);

+                    		appName.push(item.toString());

+                    	});

+                    	$scope.widgetsList[i].appContent = appContent;

+                    	$scope.widgetsList[i].appName = appName;

+                	}

+                    populateAvailableApps(reSortedWidget);

+                }).catch(err => {

+                	// Land here when the micro service is down

+                    $log.error('WidgetOnboardingCtrl::getOnboardingWidgets caught error', err);

+                }).finally(()=> {

+                    this.isLoadingTable = false;

+                });

+             

+            };

+            

+            

+            // Refactor this into a directive

+            let getSortOrder = (prop) => {

+                return function(a, b) {

+                    if (a[prop].toLowerCase() > b[prop].toLowerCase()) {

+                        return 1;

+                    } else if (a[prop].toLowerCase() < b[prop].toLowerCase()) {

+                        return -1;

+                    }

+                    return 0;

+                }

+            }

+

+            $scope.hideMe = function () {

+                $scope.infoMessage = false;

+            }

+

+            let init = () => {

+            	this.isLoadingTable = false;

+                getOnboardingWidgets();

+                this.searchString = '';

+                this.widgetsTableHeaders = [

+                    {name: 'Widget Name', value: 'name', isSortable: false}

+                ];

+                $scope.widgetsList = [];

+            };

+           

+            this.filterByDropdownValue = item => {            	

+                if(this.filterByApp.value === '')

+                    return true;

+                

+             	for(var i = 0; i < item.appName.length; i++){

+                	if(item.appName[i] == this.filterByApp.value

+                	|| item.appName[i] == 'All Applications'){

+                		return true;

+                	}

+            	}

+                return false;

+            };

+

+            this.openWidgetCatalogDetailsModal = (selectedWidget) => {

+            	let data = null;

+                if(selectedWidget){

+                    if(!selectedWidget.id){

+                        $log.error('WidgetOnboardingCtrl:openWidgetCatalogDetailModal: widget id not found');

+                        return;

+                    }

+                    data = {

+                        widget: selectedWidget

+                    }

+                }

+                ngDialog.open({

+                    templateUrl: 'app/views/widget-onboarding/widget-details-dialog/widget-details.modal.html',

+                    controller: 'WidgetOnboardingDetailsModalCtrl',

+                    controllerAs: 'widgetOnboardingDetails',

+                    data: data

+                }).closePromise.then(needUpdate => {

+                	if(needUpdate.value === true){

+                        getOnboardingWidgets();

+                    }

+                });

+            };

+

+            this.deleteWidget = widget => { 

+            	

+               confirmBoxService.deleteItem(widget.name).then(isConfirmed => {

+                	if(isConfirmed){

+                        if(!widget || !widget.id){

+                            $log.error('WidgetOnboardingCtrl::deleteWidget: No widget or ID... cannot delete');

+                            return;

+                        }

+                        widgetsCatalogService.deleteWidget(widget.id).then(() => {

+                        	$scope.widgetsList.splice($scope.widgetsList.indexOf(widget), 1);

+                        }).catch(err => {

+                            $log.error('WidgetOnboardingCtrl::deleteWidget error:',err);

+                        });

+                    }

+                }).catch(err => {

+                    $log.error('WidgetOnboardingCtrl::deleteWidget error:',err);

+                });

+                

+            };

+            

+            

+            this.downloadWidget = widget => {

+	        	widgetsCatalogService.downloadWidgetFile(widget.id).then(res => {

+	        		var data = res;

+	        		var filename = widget.name + ".zip";

+	        		

+	        		if (data == undefined || data == null){

+	        			confirmBoxService.showInformation("Could not download. Please retry.");

+	        			return;         	

+	        		}

+	        		var a = document.createElement('a');

+	        		var blob = new Blob([data], {type: 'application/octet-stream'});

+	        		a.href = URL.createObjectURL(blob);

+	        		a.download = filename;

+	        		a.click();

+	        	});

+            };

+            

+            init();

+        }

+    }

+    WidgetOnboardingCtrl.$inject = ['$log', 'applicationsService', 'widgetsCatalogService', 'ngDialog', 'confirmBoxService',

+        'userProfileService','$cookies', '$scope'];

+    angular.module('ecompApp').controller('WidgetOnboardingCtrl', WidgetOnboardingCtrl);

+})();

diff --git a/ecomp-portal-FE-common/client/app/views/widget-onboarding/widget-onboarding.controller.spec.js b/ecomp-portal-FE-common/client/app/views/widget-onboarding/widget-onboarding.controller.spec.js
new file mode 100644
index 0000000..77659d9
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/widget-onboarding/widget-onboarding.controller.spec.js
@@ -0,0 +1,20 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+

diff --git a/ecomp-portal-FE-common/client/app/views/widget-onboarding/widget-onboarding.less b/ecomp-portal-FE-common/client/app/views/widget-onboarding/widget-onboarding.less
new file mode 100644
index 0000000..f832b8f
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/widget-onboarding/widget-onboarding.less
@@ -0,0 +1,32 @@
+.widget-onboarding{
+  .bg_portalWhite;//white for 1702
+  position: @page-main-position;
+  top: @page-main-top;
+  left: @page-main-left;
+  right: @page-main-right;
+  bottom: @page-main-bottom;
+  padding-top: @padding-top;
+  overflow-y: @page-main-overflow-y;
+  padding-left: @padding-left-side;
+  #widget-onboarding-table-search::-webkit-input-placeholder,
+{
+font-style: italic;
+  color:   #999999;
+
+}
+
+    .widgets-table {
+  	  width: @table-width;
+ 	  margin: 0 auto;
+    }
+
+	.delete-widget{
+      .ico_trash_default;
+    }
+    .c-ecomp-abs-select{
+    width: 440px;
+    display: inline-block;
+    margin-right: 10px;
+    }
+
+}
diff --git a/ecomp-portal-FE-common/client/app/views/widget-onboarding/widget-onboarding.tpl.html b/ecomp-portal-FE-common/client/app/views/widget-onboarding/widget-onboarding.tpl.html
new file mode 100644
index 0000000..808deb8
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/widget-onboarding/widget-onboarding.tpl.html
@@ -0,0 +1,113 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="w-ecomp-main">

+	<div class="w-ecomp-main-container">

+		<div class="widget-onboarding" id="page-content">

+			<div id="title" class="w-ecomp-main-view-title">

+				<h1 class="heading-page">Widget Onboarding</h1>

+			</div>

+			<div class="widgets-table">

+				<div class="table-control">

+					<div class="c-ecomp-abs-select default">

+						<div class="table-dropdown">

+							<select id="dropdown1" name="dropdown1" b2b-dropdown

+								placeholder-text="All application"

+								ng-model="widgetOnboarding.filterByApp.value">

+								<option b2b-dropdown-list

+									option-repeat="d in widgetOnboarding.availableApps"

+									value="{{d.value}}">{{d.title}}</option>

+							</select>

+						</div>

+					</div>

+					<input class="table-search" type="text"

+						id="widget-onboarding-table-search"

+						placeholder="Search in entire table"

+						ng-model="widgetOnboarding.searchString" />

+

+					<button id="widget-onboarding-button-add"

+						class="btn btn-alt btn-small"

+						ng-click="widgetOnboarding.openWidgetCatalogDetailsModal()">

+						<i class="icon-people-userbookmark" aria-hidden="true"></i>&nbsp;Add

+						Widget

+					</button>

+

+					<div id="widget-onboarding-communcation-message"

+						ng-show="widgetOnboarding.isCommError">Failed to communicate

+						with the widget microservice.</div>

+

+					<div ng-hide="widgetOnboarding.isCommError">

+						<div b2b-table table-data="portalAdmin.portalAdminsTableData"

+							ng-hide="widgetOnboarding.isLoadingTable"

+							search-string="portalAdmin.searchString" class="b2b-table-div">

+							<table>

+								<thead b2b-table-row type="header">

+									<tr>

+										<th id="widgets-catalog-th-header-name"

+											ng-repeat="header in widgetOnboarding.widgetsTableHeaders"

+											b2b-table-header key="{{header.value}}"

+											sortable="{{header.isSortable}}">{{header.name}}</th>

+										<th id="widgets-catalog-th-header-url" b2b-table-header

+											key="appName" sortable="false">Application</th>

+

+										<th id="widgets-catalog-th-header-download" b2b-table-header

+											sortable="false">Download</th>

+

+										<th id="widgets-catalog-th-header-delete" b2b-table-header

+											sortable="false">Delete</th>

+									</tr>

+								</thead>

+								<tbody b2b-table-row type="body" class="table-body"

+									row-repeat="rowData in widgetsList | filter:widgetOnboarding.filterByDropdownValue">

+									<tr>

+										<td b2b-table-body

+											ng-repeat="header in widgetOnboarding.widgetsTableHeaders"

+											ng-click="widgetOnboarding.openWidgetCatalogDetailsModal(rowData)">

+											<div id="widgets-catalog-widget-name-{{rowData.id}}"

+												ng-bind="rowData[header.value]"></div>

+										</td>

+

+										<td b2b-table-body

+											ng-click="widgetOnboarding.openWidgetCatalogDetailsModal(rowData)">

+											<div ng-repeat="row in rowData.appContent">

+												<div id="widget-catalog-widget-application-{{rowData.id}}"

+													ng-bind="row"></div>

+											</div>

+										</td>

+

+										<td b2b-table-body>

+											<div class="icon-download"

+												ng-click="widgetOnboarding.downloadWidget(rowData)"></div>

+										</td>

+

+										<td b2b-table-body>

+											<div id="widget-onboarding-div-delete-widget-{{$index}}" class="delete-widget"

+												ng-click="widgetOnboarding.deleteWidget(rowData)"></div>

+										</td>

+									</tr>

+								</tbody>

+							</table>

+						</div>

+					</div>

+				</div>

+			</div>

+		</div>

+	</div>

+</div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/widgets/widget-details-dialog/widget-details.controller.js b/ecomp-portal-FE-common/client/app/views/widgets/widget-details-dialog/widget-details.controller.js
new file mode 100644
index 0000000..eb628b4
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/widgets/widget-details-dialog/widget-details.controller.js
@@ -0,0 +1,226 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

+/**

+ * Created by nnaffar on 12/20/15.

+ */

+'use strict';

+(function () {

+    class WidgetDetailsModalCtrl {

+        constructor($scope, $log, applicationsService, widgetsService, errorMessageByCode,

+                    ECOMP_URL_REGEX, $window, userProfileService, $cookies, $rootScope) {

+

+            let newWidgetModel = {

+                name: null,

+                appId: null,

+                appName: null,

+                width: 360,

+                height: 300,

+                url: null

+            };

+

+            let getAvailableApps = () => {

+                applicationsService.getAppsForSuperAdminAndAccountAdmin().then(apps => {

+                    this.availableApps=[];

+                    for(var i=0;i<apps.length;i++) {

+                        if (!apps[i].restrictedApp) {

+                            $log.debug('WidgetDetailsModalCtrl::getAvailableApps: pushing {id: ', apps[i].id, 'name: ', apps[i].name,

+                                            'restrictedApp: ', apps[i].restrictedApp, '}');

+                            this.availableApps.push({

+                                id: apps[i].id,

+                                name: apps[i].name,

+                                restrictedApp: apps[i].restrictedApp

+                            });

+                        }

+                    }

+                    

+                    if (this.isEditMode) {

+                        this.selectedApp = _.find(apps, {id: this.widget.appId});

+                        if(!this.selectedApp){

+                            //workaround to display validation errors for apps dropdown in case selectedApp isn't valid

+                            $scope.widgetForm.app.$dirty = true;

+                        }

+                    } else {

+                        this.selectedApp = null;

+                    }

+                    //init appId & appName with selectedApp

+                    this.updateSelectedApp();

+                }).catch(err => {

+                    confirmBoxService.showInformation('There was a problem retrieving the Widgets. ' +

+                        'Please try again later.').then(isConfirmed => {});

+                    $log.error('WidgetDetailsModalCtrl::getAvailableApps error: '+ err);

+                });

+            };

+            /**/

+

+            let init = () => {

+                this.isSaving = false;

+                if ($scope.ngDialogData && $scope.ngDialogData.widget) {

+                    $log.debug('WidgetDetailsModalCtrl::getAvailableApps: Edit widget mode for', $scope.ngDialogData.widget);

+                    this.isEditMode = true;

+                    this.widget = _.clone($scope.ngDialogData.widget);

+                } else {

+                    $log.debug('WidgetDetailsModalCtrl::init: New app mode');

+                    this.isEditMode = false;

+                    this.widget = _.clone(newWidgetModel);

+                }

+                getAvailableApps();

+            };

+

+            this.ECOMP_URL_REGEX = ECOMP_URL_REGEX;

+

+            //This part handles conflict errors (409)

+            this.conflictMessages = {};

+            this.scrollApi = {};

+            let handleConflictErrors = err => {

+                if(!err.data){

+                    return;

+                }

+                if(!err.data.length){ //support objects

+                    err.data = [err.data]

+                }

+                _.forEach(err.data, item => {

+                    _.forEach(item.fields, field => {

+                        //set conflict message

+                        this.conflictMessages[field.name] = errorMessageByCode[item.errorCode];

+                        //set field as invalid

+                        $scope.widgetForm[field.name].$setValidity('conflict', false);

+                        //set watch once to clear error after user correction

+                        watchOnce[field.name]();

+                    });

+                });

+                this.scrollApi.scrollTop();

+            };

+

+            let resetConflict = fieldName => {

+                delete this.conflictMessages[fieldName];

+                if($scope.widgetForm[fieldName]){

+                    $scope.widgetForm[fieldName].$setValidity('conflict', true);

+                }

+            };

+

+            let watchOnce = {

+                name: () => {

+                    let unregisterName = $scope.$watchGroup(['widgetDetails.selectedApp','widgetDetails.widget.name'], (newVal, oldVal) => {

+                        if(newVal.toLowerCase() !== oldVal.toLowerCase()){

+                            resetConflict('name');

+                            unregisterName();

+                        }

+                    });

+                },

+                url: () => {

+                    let unregisterUrl = $scope.$watch('widgetDetails.widget.url', (newVal, oldVal) => {

+                        if(newVal.toLowerCase() !== oldVal.toLowerCase()) {

+                            resetConflict('url');

+                            unregisterUrl();

+                        }

+                    });

+                }

+            };

+            //***************************

+

+            this.updateSelectedApp = () => {

+                if (!this.selectedApp) {

+                    return;

+                }

+                this.widget.appId = this.selectedApp.id;

+                this.widget.appName = this.selectedApp.name;

+            };

+

+            let emptyCookies = () => {

+                userProfileService.getUserProfile()

+                    .then(profile=> {

+                        $scope.orgUserId = profile.orgUserId;

+                        if ($cookies.getObject($scope.orgUserId + '_widget') != undefined && $cookies.getObject($scope.orgUserId + '_widget') != null) {

+                            $cookies.remove($scope.orgUserId + '_widget');

+                        }

+                    }).catch(err => {

+                        $log.error('WidgetDetailsModalCtrl::emptyCookies: There was a problem emptying the cookies! No user error presented though.');

+                    });

+            };

+

+            this.saveChanges = () => {

+                if($scope.widgetForm.$invalid){

+                    return;

+                }

+                this.isSaving = true;

+                if(this.isEditMode){

+                    widgetsService.updateWidget(this.widget.id, this.widget)

+                        .then(() => {

+                            $log.debug('WidgetDetailsModalCtrl::saveChanges: Widget update succeeded!');

+                            $scope.closeThisDialog(true);

+                            emptyCookies();

+                        }).catch(err => {

+                            if(err.status === 409){//Conflict

+                                handleConflictErrors(err);

+                            } else {

+                                confirmBoxService.showInformation('There was a problem saving the Widget. ' +

+                                    'Please try again later. Error Status: ' + err.status).then(isConfirmed => {});

+                            }

+                            $log.error('WidgetDetailsModalCtrl::saveChanges error: ', err);

+                        }).finally(()=>{

+                            this.isSaving = false;

+                            // for bug in IE 11

+                            var objOffsetVersion = objAgent.indexOf("MSIE");

+                            if (objOffsetVersion != -1) {

+                                $log.debug('WidgetDetailsModalCtrl::saveChanges: Browser is IE, forcing Refresh');

+                                $window.location.reload();            // for bug in IE 11

+                            }

+                            // for bug in IE 11

+                        });

+                } else {

+                    widgetsService.createWidget(this.widget)

+                        .then(() => {

+                            $log.debug('WidgetDetailsModalCtrl::createWidget: Widget creation succeeded!');

+                            $scope.closeThisDialog(true);

+                            emptyCookies();

+                            $rootScope.noWidgets = false;

+                        }).catch(err => {

+                        if(err.status === 409){//Conflict

+                            handleConflictErrors(err);

+                        } else {

+                            confirmBoxService.showInformation('There was a problem creating the Widget. ' +

+                                'Please try again later. Error Status: ' + err.status).then(isConfirmed => {});

+                        }

+                        $log.error('WidgetDetailsModalCtrl::createWidget error: ',err);

+                    }).finally(()=>{

+                        this.isSaving = false;

+                        // for bug in IE 11

+                        var objOffsetVersion = objAgent.indexOf("MSIE");

+                        if (objOffsetVersion != -1) {

+                            $log.debug('WidgetDetailsModalCtrl::createWidget: Browser is IE, forcing Refresh');

+                            $window.location.reload();            // for bug in IE 11

+                        }

+                        // for bug in IE 11

+                    });

+                }

+            };

+

+            init();

+

+            $scope.$on('$stateChangeStart', e => {

+                //Disable navigation when modal is opened

+                e.preventDefault();

+            });

+        }

+    }

+    WidgetDetailsModalCtrl.$inject = ['$scope', '$log', 'applicationsService', 'widgetsService', 'errorMessageByCode',

+        'ECOMP_URL_REGEX', '$window','userProfileService','$cookies', '$rootScope'];

+    angular.module('ecompApp').controller('WidgetDetailsModalCtrl', WidgetDetailsModalCtrl);

+})();

diff --git a/ecomp-portal-FE-common/client/app/views/widgets/widget-details-dialog/widget-details.controller.spec.js b/ecomp-portal-FE-common/client/app/views/widgets/widget-details-dialog/widget-details.controller.spec.js
new file mode 100644
index 0000000..1762fad
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/widgets/widget-details-dialog/widget-details.controller.spec.js
@@ -0,0 +1,154 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+// describe('Controller: WidgetDetailsModalCtrl', ()=> {

+//     /**

+//      * INITIALIZATION

+//      */

+//     beforeEach(module('testUtils'));

+//     beforeEach(module('ecompApp'));

+//

+//     let promisesTestUtils;

+//     //destroy $http default cache before starting to prevent the error 'default cache already exists'

+//     //_promisesTestUtils_ comes from testUtils for promises resolve/reject

+//     beforeEach(inject((_CacheFactory_, _promisesTestUtils_)=> {

+//         _CacheFactory_.destroyAll();

+//         promisesTestUtils = _promisesTestUtils_;

+//     }));

+//

+//     let widgetDetails, scope, $controller, $q, $rootScope, $log, widgetsService, errorMessageByCode, ECOMP_URL_REGEX;

+//     let deferredAdminApps, deferredUserProfile;

+//     let applicationsServiceMock, widgetsServiceMock, userProfileServiceMock;

+//     beforeEach(inject((_$controller_, _$q_, _$rootScope_, _$log_)=> {

+//         [$controller, $q, $rootScope, $log] = [_$controller_, _$q_, _$rootScope_, _$log_];

+//

+//         deferredAdminApps = $q.defer();

+//         deferredUserProfile = $q.defer();

+//         /*applicationsServiceMock = {

+//             getAppsForSuperAdminAndAccountAdmin: () => {

+//                 var promise = () => {return deferredAdminApps.promise};

+//                 var cancel = jasmine.createSpy();

+//                 return {

+//                     promise: promise,

+//                     cancel: cancel

+//                 }

+//             }

+//         };*/

+//

+//         widgetsServiceMock = {

+//             updateWidget: () => {

+//                 var promise = () => {return deferredAdminApps.promise};

+//                 var cancel = jasmine.createSpy();

+//                 return {

+//                     promise: promise,

+//                     cancel: cancel

+//                 }

+//             },

+//             createWidget: () => {

+//                 var promise = () => {return deferredAdminApps.promise};

+//                 var cancel = jasmine.createSpy();

+//                 return {

+//                     promise: promise,

+//                     cancel: cancel

+//                 }

+//             }

+//         };

+//

+//         userProfileServiceMock = jasmine.createSpyObj('userProfileServiceMock',['getUserProfile']);

+//         userProfileServiceMock.getUserProfile.and.returnValue(deferredUserProfile.promise);

+//

+//         applicationsServiceMock = jasmine.createSpyObj('applicationsServiceMock',['getAppsForSuperAdminAndAccountAdmin']);

+//         applicationsServiceMock.getAppsForSuperAdminAndAccountAdmin.and.returnValue(deferredAdminApps.promise);

+//

+//     }));

+//

+//     beforeEach(()=> {

+//         errorMessageByCode = [];

+//         ECOMP_URL_REGEX = "";

+//         scope = $rootScope.$new();

+//         createController(scope);

+//     });

+//

+//     let createController = scopeObj => {

+//         widgetDetails = $controller('WidgetDetailsModalCtrl', {

+//             $scope: scope,

+//             $log: $log,

+//             applicationsService: applicationsServiceMock,

+//             widgetsService: widgetsServiceMock,

+//             errorMessageByCode: errorMessageByCode,

+//             ECOMP_URL_REGEX: ECOMP_URL_REGEX,

+//             userProfileService: userProfileServiceMock

+//         });

+//     };

+//

+//     /**

+//      * MOCK DATA

+//      */

+//     let newWidgetModel = {

+//         name: null,

+//         appId: null,

+//         appName: null,

+//         width: 360,

+//         height: 300,

+//         url: null

+//     };

+//     let exsistingWidget = {

+//         name: 'some widget',

+//         appId: 1,

+//         appName: 'APP NAME',

+//         width: 360,

+//         height: 300,

+//         url: 'http://a.com'

+//     };

+//     let adminApps = [{id: 1, name: 'a'}, {id: 2, name: 'b'}];

+//

+//     /**

+//      * TEST CASES

+//      */

+//     it('should initialize controller with new widget mode when opening the modal without selected widget', ()=> {

+//         expect(widgetDetails.widget).toEqual(newWidgetModel);

+//     });

+//

+//     it('should initialize controller with exsisting widget details when opening the modal with selected widget', ()=> {

+//         scope.ngDialogData = {

+//             widget: exsistingWidget

+//         };

+//         createController(scope);

+//         expect(widgetDetails.widget).toEqual(exsistingWidget);

+//     });

+//

+//     it('should populate widget selected app name and id when initializing controller with widget', () =>{

+//         deferredAdminApps.resolve(adminApps);

+//         scope.ngDialogData = {

+//             widget: exsistingWidget

+//         };

+//         createController(scope);

+//         scope.$apply();

+//         expect(widgetDetails.widget.appId).toEqual(adminApps[0].id);

+//         expect(widgetDetails.widget.appName).toEqual(adminApps[0].name);

+//     });

+//

+//     //TODO:

+//     //save changes fail - conflict handling

+//     //save changes success

+//

+//

+//

+// });

diff --git a/ecomp-portal-FE-common/client/app/views/widgets/widget-details-dialog/widget-details.modal.html b/ecomp-portal-FE-common/client/app/views/widgets/widget-details-dialog/widget-details.modal.html
new file mode 100644
index 0000000..46a5b1c
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/widgets/widget-details-dialog/widget-details.modal.html
@@ -0,0 +1,152 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="widget-details-modal">

+    <div id="'widgets-details-title" class="title">Widget Details</div>

+

+    <div class="widget-properties-main" scroll-top="widgetDetails.scrollApi">

+        <form id="widgets-details-form" name="widgetForm" novalidate autocomplete="off">

+            <!-- We can remove this script once we get to AT&T Corporate Firefox version 47

+                 autocomplete="off" won't work until v47 -->

+            <script type="text/javascript">

+                document.getElementById("appForm").reset();

+            </script>

+            <div class="item required">

+                <div class="item-label">Application Name</div>

+                <div class="custom-select-wrap">

+                    <select id="widgets-details-select-app"

+                            class="select-field"

+                            ng-model="widgetDetails.selectedApp"

+                            ng-change="widgetDetails.updateSelectedApp();"

+                            ng-options="app.name for app in widgetDetails.availableApps track by app.id"

+                            ng-disabled="!widgetDetails.availableApps || !widgetDetails.availableApps.length"

+                            name="app"

+                            required>

+                        <option id="widgets-details-select-app-disabled" value="" disabled style="display: none;">Select application</option>

+                    </select>

+                </div>

+                <div class="error-container" ng-show="widgetForm.app.$dirty">

+                    <div ng-messages="widgetForm.app.$error" class="error-container">

+                        <small id="widgets-details-select-app-error-required" class="err-message" ng-message="required">Application is required</small>

+                    </div>

+                </div>

+            </div>

+            <div class="item required">

+                <div class="item-label">Widget Name</div>

+                <input id="widgets-details-input-name"

+                       class="input-field"

+                       type="text"

+                       ng-model="widgetDetails.widget.name"

+                       name="name"

+                       maxlength="100"

+                       ng-pattern="/^[a-zA-Z0-9_\s\&]*$/"

+                       required/>

+

+                <div class="error-container" ng-show="widgetDetails.conflictMessages.name">

+                    <small id="widgets-details-input-name-conflict" class="err-message" ng-bind="widgetDetails.conflictMessages.name"></small>

+                </div>

+                <div class="error-container" ng-show="widgetForm.name.$dirty || widgetDetails.isEditMode">

+                    <div ng-messages="widgetForm.name.$error" class="error-container">

+                        <small id="widgets-details-input-name-required" class="err-message" ng-message="required">Widget Name is required</small>

+                        <small id="widgets-details-input-name-pattern" class="err-message" ng-message="pattern">Widget Name must be letters, numbers, or underscore</small>

+                    </div>

+                </div>

+            </div>

+            <div class="item required">

+                <div class="left-item">

+                    <div class="item-label">Width</div>

+                    <input id="widgets-details-input-width"

+                           class="input-field"

+                           type="number"

+                           ng-model="widgetDetails.widget.width"

+                           name="width"

+                           min="300"

+                           required

+                           disabled/>

+

+                    <div class="error-container" ng-show="widgetForm.width.$dirty || widgetDetails.isEditMode">

+                        <div ng-messages="widgetForm.width.$error" class="error-container">

+                            <small id="widgets-details-input-width-required" class="err-message" ng-message="required">Widget width is required</small>

+                            <small id="widgets-details-input-min-width" class="err-message" ng-message="min">Minimum width is 300</small>

+                        </div>

+                    </div>

+                </div>

+                <div class="right-item required">

+                    <div class="item-label">Height</div>

+                    <input id="widgets-details-input-height"

+                           class="input-field"

+                           type="number"

+                           ng-model="widgetDetails.widget.height"

+                           name="height"

+                           min="200"

+                           required

+                           disabled/>

+

+                    <div class="error-container" ng-show="widgetForm.height.$dirty || widgetDetails.isEditMode">

+                        <div ng-messages="widgetForm.height.$error" class="error-container">

+                            <small id="widgets-details-input-height-required" class="err-message" ng-message="required">Widget height is required</small>

+                            <small id="widgets-details-input-height-minimum" class="err-message" ng-message="min">Minimum height is 200</small>

+                        </div>

+                    </div>

+                </div>

+            </div>

+            <div class="item required">

+                <div class="item-label">URL</div>

+                <input id="widgets-details-input-URL"

+                       class="input-field"

+                       type="url"

+                       ng-model="widgetDetails.widget.url"

+                       name="url"

+                       maxlength="256"

+                       ng-pattern="widgetDetails.ECOMP_URL_REGEX"

+                       required/>

+

+                <!--

+                <div class="url-validation-button"

+                     ng-class="{'disabled': widgetForm.url.$invalid}"

+                     ng-click="(widgetForm.url.$invalid) || widgetDetails.validateUrl()">Validate URL

+                </div>

+                -->

+

+                <div class="error-container" ng-show="widgetDetails.conflictMessages.url">

+                    <small id="widgets-details-input-URL-conflict" class="err-message" ng-bind="widgetDetails.conflictMessages.url"></small>

+                </div>

+                <div class="error-container" ng-show="widgetForm.url.$dirty || widgetDetails.isEditMode">

+                    <div ng-messages="widgetForm.url.$error" class="error-container">

+                        <small id="widgets-details-input-URL-required" class="err-message" ng-message="required">Widget URL is required</small>

+                        <small id="widgets-details-input-URL-pattern" class="err-message" ng-message="pattern">Incorrect URL pattern</small>

+                    </div>

+                    <!--

+                    <div class="error-container" ng-show="widgetForm.url.$valid && !widgetDetails.conflictMessages.url">

+                        <small class="err-message" ng-hide="widgetDetails.urlValidity.isValid" ng-bind="widgetDetails.urlValidity.message"></small>

+                        <small class="valid-message" ng-show="widgetDetails.urlValidity.isValid" ng-bind="widgetDetails.urlValidity.message"></small>

+                    </div>

+                    -->

+                </div>

+

+            </div>

+        </form>

+    </div>

+    <div class="dialog-control">

+        <span class="ecomp-save-spinner" ng-show="widgetDetails.isSaving"></span>

+        <div id="widgets-details-next-button" class="next-button"

+             ng-class="{disabled: widgetForm.$invalid}" ng-click="widgetDetails.saveChanges()">Save</div>

+        <div id="widgets-details-cancel-button" class="cancel-button" ng-click="closeThisDialog()">Cancel</div>

+    </div>

+</div>

diff --git a/ecomp-portal-FE-common/client/app/views/widgets/widget-details-dialog/widget-details.modal.less b/ecomp-portal-FE-common/client/app/views/widgets/widget-details-dialog/widget-details.modal.less
new file mode 100644
index 0000000..568b618
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/widgets/widget-details-dialog/widget-details.modal.less
@@ -0,0 +1,75 @@
+.widget-details-modal {
+  height: 580px;
+
+  .title {
+    //.n18r;
+    .dGray18r;  //AT&T Dark Gray
+    border-bottom: @portalDBlue 3px solid;
+  }
+
+  .widget-properties-main {
+    padding: 16px;
+    height: 460px;
+    overflow-y: auto;
+
+    .item{
+      position: relative;
+      margin-bottom: 18px;
+
+      .input-field{
+        .custom-input-field;
+        width: 100%;
+        &.url{
+          width: 78%;
+          display: inline-block;
+        }
+      }
+
+      .select-field {
+        .custom-select-field;
+      }
+
+      .item-label{
+        .dGray14r;
+      }
+
+      .right-item{
+        position: relative;
+        display: inline-block;
+        width: 48%;
+        float: right;
+      }
+      .left-item{
+        display: inline-block;
+        width: 48%;
+      }
+
+      .url-validation-button{
+        .btn-blue;
+        width: 20%;
+        display: inline-block;
+        float: right;
+      }
+
+      .error-container{
+        position: absolute;
+        width: 280px;
+        display: block;
+        height: 12px;
+        line-height: 12px;
+
+        .err-message{
+          color: @funcRed;
+          font-size: 9px;
+        }
+        .valid-message{
+          color: @funcGreen;
+          font-size: 9px;
+        }
+      }
+
+    }
+
+  }
+
+}
diff --git a/ecomp-portal-FE-common/client/app/views/widgets/widgets.controller.js b/ecomp-portal-FE-common/client/app/views/widgets/widgets.controller.js
new file mode 100644
index 0000000..e46f1ce
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/widgets/widgets.controller.js
@@ -0,0 +1,168 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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';

+(function () {

+    class WidgetsCtrl {

+        constructor($log, applicationsService, widgetsService, ngDialog, confirmBoxService,

+                    userProfileService, $cookies, $scope, $rootScope) {

+            //$log.info('WidgetsCtrl::init: Starting Up');

+            $scope.infoMessage = true;

+            $rootScope.noWidgets = false;

+

+            let populateAvailableApps = widgets => {

+                let allPortalsFilterObject = {index: 0, title: 'All applications', value: ''};

+                this.availableApps = [allPortalsFilterObject];

+                this.filterByApp = this.availableApps[0];

+                applicationsService.getAppsForSuperAdminAndAccountAdmin().then(myApps => {

+                    var reSortedApp = myApps.sort(getSortOrder("name"));

+                    var realAppIndex = 1;

+                    for (let i = 1; i <= reSortedApp.length; i++) {

+                        if (!reSortedApp[i-1].restrictedApp) {

+                            $log.debug('WidgetsCtrl::populateAvailableApps: pushing {index: ', realAppIndex, 'title: ', reSortedApp[i - 1].name,

+                                'value: ', reSortedApp[i - 1].name, '}');

+                            this.availableApps.push({

+                                index: realAppIndex,

+                                title: reSortedApp[i - 1].name,

+                                value: reSortedApp[i - 1].name

+                            })

+                            realAppIndex = realAppIndex + 1;

+                        }

+                    }

+                }).catch(err => {

+                    $log.error(err);

+                });

+            };

+

+            let getOnboardingWidgets = () => {

+                this.isLoadingTable = true;

+                widgetsService.getManagedWidgets().then(res => {

+                    $log.debug('WidgetsCtrl.getOnboardingWidgets:: ' + JSON.stringify(res));

+                    // if (JSON.stringify(res) === '[]') {

+                    //     confirmBoxService.showInformation('There are currently no Widgets. ').then(isConfirmed => {});

+                    // }

+                    var reSortedWidget = res.sort(getSortOrder("name"));

+                    this.widgetsList = reSortedWidget;

+                    populateAvailableApps(reSortedWidget);

+                    // $log.info('WidgetsHomeCtrl::getUserWidgets count : ' + $scope.widgetsList.length);

+                    if (Object.keys(res).length === 0 ) {

+                        $rootScope.noWidgets = true;

+                        $scope.isLoadingTable = false;

+                        $log.info('WidgetsHomeCtrl::getUserWidgets: There are no available Widgets');

+                    }

+                }).catch(err => {

+                    confirmBoxService.showInformation('There was a problem retrieving the Widgets. ' +

+                        'Please try again later.').then(isConfirmed => {});

+                    $log.error('WidgetsCtrl::getOnboardingWidgets error: ' + err);

+                }).finally(()=> {

+                    this.isLoadingTable = false;

+                });

+            };

+

+            // Refactor this into a directive

+            let getSortOrder = (prop) => {

+                return function(a, b) {

+                    // $log.debug('a = ' + JSON.stringify(a) + "| b = " + JSON.stringify(b));

+                    if (a[prop].toLowerCase() > b[prop].toLowerCase()) {

+                        return 1;

+                    } else if (a[prop].toLowerCase() < b[prop].toLowerCase()) {

+                        return -1;

+                    }

+                    return 0;

+                }

+            }

+

+            $scope.hideMe = function () {

+                $scope.infoMessage = false;

+            }

+

+            let init = () => {

+                this.isLoadingTable = false;

+                getOnboardingWidgets();

+

+                /*Table general configuration params*/

+                this.searchString = '';

+                /*Table data*/

+                this.widgetsTableHeaders = [

+                    {name: 'Widget Name', value: 'name', isSortable: false},

+                    {name: 'Application', value: 'appName', isSortable: true},

+                    {name: 'Width', value: 'width', isSortable: false},

+                    {name: 'Height', value: 'height', isSortable: false}

+                ];

+                this.widgetsList = [];

+            };

+

+            this.filterByDropdownValue = item => {

+                if(this.filterByApp.value === ''){

+                    return true;

+                }

+                return item.appName === this.filterByApp.value;

+            };

+

+            this.openWidgetDetailsModal = (selectedWidget) => {

+                let data = null;

+                if(selectedWidget){

+                    if(!selectedWidget.id){

+                        $log.error('Widget id not found');

+                        return;

+                    }

+                    data = {

+                        widget: selectedWidget

+                    }

+                }

+                ngDialog.open({

+                    templateUrl: 'app/views/widgets/widget-details-dialog/widget-details.modal.html',

+                    controller: 'WidgetDetailsModalCtrl',

+                    controllerAs: 'widgetDetails',

+                    data: data

+                }).closePromise.then(needUpdate => {

+                    if(needUpdate.value === true){

+                        $log.debug('WidgetsCtrl::openWidgetDetailsModal: updating table data...');

+                        getOnboardingWidgets();

+                    }

+                });

+            };

+

+            this.deleteWidget = widget => {

+                confirmBoxService.deleteItem(widget.name).then(isConfirmed => {

+                    if(isConfirmed){

+                        if(!widget || !widget.id){

+                            $log.error('WidgetsCtrl::deleteWidget: No widget or ID... cannot delete');

+                            return;

+                        }

+                        widgetsService.deleteWidget(widget.id).then(() => {

+                            this.widgetsList.splice(this.widgetsList.indexOf(widget), 1);

+                        }).catch(err => {

+                            $log.error('WidgetsCtrl::deleteWidget error:',err);

+                            confirmBoxService.showInformation('There was a problem deleting the Widget. ' +

+                                'Please try again later.').then(isConfirmed => {});

+                        });

+                    }

+                }).catch(err => {

+                    $log.error('WidgetsCtrl::deleteWidget error:',err);

+                });

+            };

+

+            init();

+        }

+    }

+    WidgetsCtrl.$inject = ['$log', 'applicationsService', 'widgetsService', 'ngDialog', 'confirmBoxService',

+        'userProfileService','$cookies', '$scope', '$rootScope'];

+    angular.module('ecompApp').controller('WidgetsCtrl', WidgetsCtrl);

+})();

diff --git a/ecomp-portal-FE-common/client/app/views/widgets/widgets.controller.spec.js b/ecomp-portal-FE-common/client/app/views/widgets/widgets.controller.spec.js
new file mode 100644
index 0000000..3841a2b
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/widgets/widgets.controller.spec.js
@@ -0,0 +1,19 @@
+/*-

+ * ================================================================================

+ * ECOMP Portal

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property

+ * ================================================================================

+ * Licensed 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.

+ * ================================================================================

+ */

diff --git a/ecomp-portal-FE-common/client/app/views/widgets/widgets.less b/ecomp-portal-FE-common/client/app/views/widgets/widgets.less
new file mode 100644
index 0000000..43bae91
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/widgets/widgets.less
@@ -0,0 +1,48 @@
+.widgets-page-main{
+  .bg_portalWhite;//white for 1702
+  position: @page-main-position;
+  top: @page-main-top;
+  left: @page-main-left;
+  right: @page-main-right;
+  bottom: @page-main-bottom;
+  padding-top: @padding-top;
+  overflow-y: @page-main-overflow-y;
+  padding-left: @padding-left-side;
+
+  .widgets-table{
+    width: @table-width;
+    //margin-left: @table-margin-left;
+    //margin: @table-margin;
+	margin:auto;
+    .table-control{
+
+    }
+
+    .delete-widget{
+      .ico_trash_default;
+    }
+  }
+  .error-text {
+    width: 1170px;
+    margin: auto;
+    padding: 20px;
+    left: 20px;
+    font-weight: bold;
+    font-size: 16px;
+    text-align: left;
+    color: @err;
+    background-color: @u; // @portalWhite;
+
+    .error-help {
+      color: @o; // @portalDGray;
+      font-weight: normal;
+    }
+
+    .informational {
+      color: @o; // @portalDGray;
+      font-weight: normal;
+      font-style: italic;
+    }
+  }
+
+  }
\ No newline at end of file
diff --git a/ecomp-portal-FE-common/client/app/views/widgets/widgets.tpl.html b/ecomp-portal-FE-common/client/app/views/widgets/widgets.tpl.html
new file mode 100644
index 0000000..8afb826
--- /dev/null
+++ b/ecomp-portal-FE-common/client/app/views/widgets/widgets.tpl.html
@@ -0,0 +1,81 @@
+<!--

+  ================================================================================

+  ECOMP Portal

+  ================================================================================

+  Copyright (C) 2017 AT&T Intellectual Property

+  ================================================================================

+  Licensed under the Apache License, Version 2.0 (the "License");

+  you may not use this file except in compliance with the License.

+  You may obtain a copy of the License at

+  

+       http://www.apache.org/licenses/LICENSE-2.0

+  

+  Unless required by applicable law or agreed to in writing, software

+  distributed under the License is distributed on an "AS IS" BASIS,

+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  See the License for the specific language governing permissions and

+  limitations under the License.

+  ================================================================================

+  -->

+<div class="w-ecomp-main">

+    <div class="w-ecomp-main-container">

+        <div class="widgets-page-main" id="page-content">

+            <div id="widget-onboarding-title" class="w-ecomp-main-view-title">Widget Onboarding</div>

+            <div class="widgets-table">

+                <div class="table-control">

+                        <div class="c-ecomp-portal-abs-select default">

+                            <div class="form-field" id="widegets-available-apps"

+                                 att-select="widgets.availableApps"

+                                 ng-model="widgets.filterByApp"></div>

+                        </div>

+                    <input class="table-search" type="text" id="widget-onboarding-table-search"

+                           placeholder="Search in entire table"

+                           ng-model="widgets.searchString"/>

+

+                    <div id="widget-onboarding-button-add" class="add-button" ng-click="widgets.openWidgetDetailsModal()">Add Widget</div>

+                </div>

+                <div class="error-text" ng-show="infoMessage">

+                    <span class="error-help">Only widgets for active applications are displayed.</span>

+                    <button type="button" class="close" ng-click="hideMe()">&times;</button>

+                </div>

+                <div class="error-text" ng-show="noWidgets">

+                    <span class="informational">There are currently no widgets available.</span>

+                </div>

+

+                <span class="ecomp-spinner" ng-show="widgets.isLoadingTable"></span>

+                <div class="c-ecomp-portal-abs-table default" ng-hide="widgets.isLoadingTable">

+                    <table b2b-table

+                           table-data="widgets.widgetsList"

+                           search-string="widgets.searchString"

+                           view-per-page="widgets.viewPerPageIgnored"

+                           current-page="widgets.currentPageIgnored"

+                           total-page="widgets.totalPageIgnored">

+                        <thead b2b-table-row type="header">

+                        <tr>

+                            <th id="widget-onboarding-th-header-name" ng-repeat="header in widgets.widgetsTableHeaders" b2b-table-header key="{{header.value}}" sortable="{{header.isSortable}}">{{header.name}}</th>

+                            <th id="widget-onboarding-th-header-url" b2b-table-header  key="url" sortable="{{false}}">URL</th>

+                            <th id="widget-onboarding-th-header-delete" b2b-table-header  sortable="{{false}}">Delete</th>

+                        </tr>

+                        </thead>

+                        <tbody b2b-table-row type="body"

+                               class="table-body"

+                               row-repeat="rowData in widgets.widgetsList | filter:widgets.filterByDropdownValue">

+                        <tr >

+                            <td b2b-table-body ng-repeat="header in widgets.widgetsTableHeaders" ng-click="widgets.openWidgetDetailsModal(rowData)">

+                                <div id="widget-onboarding-div-{{rowData[header.value].split(' ').join('-')}}" ng-bind="rowData[header.value]"></div>

+                            </td>

+                            <td b2b-table-body ng-click="widgets.openWidgetDetailsModal(rowData)">

+                                <div id="widget-onboarding-div-url-{{rowData[header.value].split(' ').join('-')}}" ng-bind="rowData.url | trusted"></div>

+                            </td>

+                            <td b2b-table-body>

+                                <div id="widget-onboarding-div-delete-widget-{{$index}}" class="delete-widget" ng-click="widgets.deleteWidget(rowData)"></div>

+                            </td>

+                        </tr>

+                        </tbody>

+                    </table>

+                </div>

+            </div>

+        </div>

+    </div>

+

+</div>