summaryrefslogtreecommitdiffstats
path: root/ecomp-portal-FE-common/client/app/views/widgets
diff options
context:
space:
mode:
Diffstat (limited to 'ecomp-portal-FE-common/client/app/views/widgets')
-rw-r--r--ecomp-portal-FE-common/client/app/views/widgets/widget-details-dialog/widget-details.controller.js226
-rw-r--r--ecomp-portal-FE-common/client/app/views/widgets/widget-details-dialog/widget-details.controller.spec.js154
-rw-r--r--ecomp-portal-FE-common/client/app/views/widgets/widget-details-dialog/widget-details.modal.html152
-rw-r--r--ecomp-portal-FE-common/client/app/views/widgets/widget-details-dialog/widget-details.modal.less75
-rw-r--r--ecomp-portal-FE-common/client/app/views/widgets/widgets.controller.js168
-rw-r--r--ecomp-portal-FE-common/client/app/views/widgets/widgets.controller.spec.js19
-rw-r--r--ecomp-portal-FE-common/client/app/views/widgets/widgets.less48
-rw-r--r--ecomp-portal-FE-common/client/app/views/widgets/widgets.tpl.html81
8 files changed, 923 insertions, 0 deletions
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 00000000..eb628b4f
--- /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 00000000..1762fadb
--- /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 00000000..46a5b1c7
--- /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 00000000..568b6180
--- /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 00000000..e46f1ce5
--- /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 00000000..3841a2b3
--- /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 00000000..43bae91f
--- /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 00000000..8afb8267
--- /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>