diff options
Diffstat (limited to 'catalog-ui/app/scripts/view-models/workspace')
70 files changed, 9462 insertions, 0 deletions
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/activity-log/activity-log.html b/catalog-ui/app/scripts/view-models/workspace/tabs/activity-log/activity-log.html new file mode 100644 index 0000000000..23c08f6ec6 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/activity-log/activity-log.html @@ -0,0 +1,85 @@ +<div class="activity-log"> + + <div class="title-wrapper"> + <div class="top-search"> + <input type="text" + class="search-text" + placeholder="Search" + data-ng-model="searchBind" + data-tests-id="main-menu-input-search" + ng-model-options="{ debounce: 500 }" /> + <span class="w-sdc-search-icon magnification"></span> + </div> + </div> + + <div class="table-container-flex"> + <div class="table" data-ng-class="{'view-mode': isViewMode()}"> + + <!-- Table headers --> + <div class="head flex-container"> + <div class="table-header head-row hand flex-item" ng-repeat="header in tableHeadersList track by $index" data-ng-click="sort(header.property)">{{header.title}} + <span data-ng-show="sortBy === header.property" class="table-header-sort-arrow" data-ng-class="{'down': reverse, 'up':!reverse}"> </span> + </div> + </div> + + <!-- Table body --> + <div class="body"> + <perfect-scrollbar suppress-scroll-x="true" scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container"> + + <!-- In case the logs are empty --> + <div data-ng-if="!activityLog || activityLog.length===0" class="no-row-text"> + There are no logs to display + </div> + + <!-- Loop on logs list --> + <div data-ng-repeat="item in activityLog | filter: searchBind | orderBy:sortBy:reverse track by $index" + data-ng-init="item.dateFormat = ( item.TIMESTAMP.replace(' UTC', '') | stringToDateFilter | date: 'MM/dd/yyyy':'UTC')+' | '+(item.TIMESTAMP.replace(' UTC', '') | stringToDateFilter | date: 'shortTime':'UTC' )" + class="flex-container data-row" + data-ng-class="{'selected': component === selectedComponent}" + data-ng-click="doSelectComponent(component);" + > + + <!-- Date --> + <div class="table-col-general flex-item" sdc-smart-tooltip> + {{item.dateFormat}} + </div> + + <!-- Action --> + <div class="table-col-general flex-item" sdc-smart-tooltip> + {{item.ACTION}} + </div> + + <!-- Comment --> + <div class="table-col-general flex-item" sdc-smart-tooltip> + {{item.COMMENT}} + </div> + + <!-- Username --> + <div class="table-col-general flex-item" sdc-smart-tooltip> + {{item.MODIFIER}} + </div> + + <!-- Status --> + <div class="table-col-general flex-item" sdc-smart-tooltip> + {{item.STATUS}} + <span data-ng-class="{'success': item.STATUS>='200' && item.STATUS<='204','error': item.STATUS<'200' || item.STATUS>='300'}"></span> + </div> + + </div> + + </perfect-scrollbar> + </div><!-- End table body --> + </div><!-- End table --> + </div><!-- End table-container-flex --> + +</div> + + + +<!--<div ng-repeat="activityDate in activityDateArray " class="w-sdc-component-viewer-right-activity-log" > + <div class="w-sdc-component-viewer-right-activity-log-date" >{{activityDate | date: 'longDate'}}</div> + <div ng-repeat="activity in activityLog[activityDate] | orderBy: '-TIMESTAMP'"> + <div class="w-sdc-component-viewer-right-activity-log-time">{{activity.TIMESTAMP.replace(" UTC", '') | stringToDateFilter | date: 'mediumTime':'UTC'}}</div> + <div class="w-sdc-component-viewer-right-activity-log-content">{{"Action: " + parseAction(activity.ACTION) + " Performed by: " + activity.MODIFIER + " Status: " + activity.STATUS}}</div> + </div> +</div>--> diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/activity-log/activity-log.less b/catalog-ui/app/scripts/view-models/workspace/tabs/activity-log/activity-log.less new file mode 100644 index 0000000000..61bb3e9f01 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/activity-log/activity-log.less @@ -0,0 +1,83 @@ +.activity-log { + + margin-top: 30px; + + .title-wrapper { + display: flex; + justify-content: flex-end; + } + + .table-container-flex .table .body .scrollbar-container { + max-height: 448px; + } + + .view-mode { + background-color: @main_color_p; + } + + .table{ + height: 490px; + margin-bottom: 0; + } + + .table-container-flex { + margin-top: 10px; + + .flex-item:nth-child(1) { width: 200px; } + .flex-item:nth-child(2) { flex-grow: 20; } + .flex-item:nth-child(3) { flex-grow: 30; } + .flex-item:nth-child(4) { flex-grow: 20; } + .flex-item:nth-child(5) { width: 80px; } + + .success { + position: absolute; + top: 11px; + right: 20px; + .sprite-new; + .sdc-success; + } + + .error { + position: absolute; + top: 11px; + right: 20px; + .sprite-new; + .sdc-error; + } + + } + + .data-row { + position: relative; + } + + .top-search { + float: right; + position: relative; + + input.search-text { + .border-radius(2px); + width: 245px; + height: 32px; + line-height: 32px; + border: 1px solid @main_color_o; + margin: 0; + outline: none; + text-indent: 10px; + + &::-webkit-input-placeholder { font-style: italic; } /* Safari, Chrome and Opera */ + &:-moz-placeholder { font-style: italic; } /* Firefox 18- */ + &::-moz-placeholder { font-style: italic; } /* Firefox 19+ */ + &:-ms-input-placeholder { font-style: italic; } /* IE 10+ */ + &:-ms-input-placeholder { font-style: italic; } /* Edge */ + } + + .magnification { + position: absolute; + top: 10px; + right: 10px; + } + + } + +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/activity-log/activity-log.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/activity-log/activity-log.ts new file mode 100644 index 0000000000..665d0c0ef6 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/activity-log/activity-log.ts @@ -0,0 +1,122 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../../references"/> +module Sdc.ViewModels { + 'use strict'; + + export interface IActivityLogViewModelScope extends IWorkspaceViewModelScope { + activityDateArray: Array<any>; //this is in order to sort the dates + activityLog: Array<Models.Activity>; + preVersion:string; + + tableHeadersList: Array<any>; + reverse: boolean; + sortBy:string; + searchBind:string; + + getActivityLog(uniqueId:string):void; + onVersionChanged(version:any) : void; + parseAction(action:string):string; + sort(sortBy:string): void; + } + + export class ActivityLogViewModel { + + static '$inject' = [ + '$scope', + '$state', + 'Sdc.Services.ActivityLogService' + ]; + + constructor(private $scope:IActivityLogViewModelScope, + private $state:ng.ui.IStateService, + private activityLogService:Services.ActivityLogService + ) { + + this.initScope(); + this.$scope.setValidState(true); + this.initSortedTableScope(); + this.$scope.updateSelectedMenuItem(); + + // Set default sorting + this.$scope.sortBy = 'logDate'; + } + + private initScope():void { + + this.$scope.preVersion = this.$scope.component.version; + + this.$scope.onVersionChanged = (version:any):void => { + if (version.versionNumber != this.$scope.component.version) { + this.$scope.isLoading = true; + this.$scope.getActivityLog(version.versionId); + } + }; + + this.$scope.getActivityLog = (uniqueId:any):void => { + + let onError = (response) => { + this.$scope.isLoading = false; + console.info('onFaild', response); + + }; + + let onSuccess = (response:Array<Models.Activity>) => { + this.$scope.activityLog = _.sortBy(response, function(o) { return o.TIMESTAMP; }); //response; // + this.$scope.isLoading = false; + }; + + this.$scope.isLoading = true; + if (this.$scope.component.isResource()) { + this.activityLogService.getActivityLogService('resources', uniqueId).then(onSuccess, onError); + } + if (this.$scope.component.isService()) { + this.activityLogService.getActivityLogService('services', uniqueId).then(onSuccess, onError); + } + + }; + + if (!this.$scope.activityLog || this.$scope.preVersion != this.$scope.component.version) { + this.$scope.getActivityLog(this.$scope.component.uniqueId); + } + + this.$scope.parseAction = (action:string) => { + return action ? action.split(/(?=[A-Z])/).join(' ') : ''; + }; + + } + + private initSortedTableScope = ():void => { + this.$scope.tableHeadersList = [ + {title: 'Date', property: 'logDate'}, + {title: 'Action', property: 'logAction'}, + {title: 'Comment', property: 'logComment'}, + {title: 'Username', property: 'logUsername'}, + {title: 'Status', property: 'logStatus'} + ]; + + this.$scope.sort = (sortBy:string):void => { + this.$scope.reverse = (this.$scope.sortBy === sortBy) ? !this.$scope.reverse : false; + this.$scope.sortBy = sortBy; + }; + }; + + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/attributes/attributes-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/attributes/attributes-view-model.ts new file mode 100644 index 0000000000..469da6a2e1 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/attributes/attributes-view-model.ts @@ -0,0 +1,107 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../../references"/> +module Sdc.ViewModels { + 'use strict'; + + interface IAttributesViewModelScope extends IWorkspaceViewModelScope { + tableHeadersList: Array<any>; + reverse: boolean; + sortBy:string; + + addOrUpdateAttribute(attribute?:Models.AttributeModel): void; + delete(attribute:Models.AttributeModel): void; + sort(sortBy:string): void; + } + + export class AttributesViewModel { + + static '$inject' = [ + '$scope', + '$filter', + '$modal', + '$templateCache', + 'ModalsHandler' + ]; + + + constructor(private $scope:IAttributesViewModelScope, + private $filter:ng.IFilterService, + private $modal:ng.ui.bootstrap.IModalService, + private $templateCache:ng.ITemplateCacheService, + private ModalsHandler:Utils.ModalsHandler) { + this.initScope(); + this.$scope.updateSelectedMenuItem(); + } + + + private openEditAttributeModal = (attribute:Models.AttributeModel):void => { + let viewModelsHtmlBasePath:string = '/app/scripts/view-models/'; + + let modalOptions:ng.ui.bootstrap.IModalSettings = { + template: this.$templateCache.get(viewModelsHtmlBasePath + 'forms/attribute-form/attribute-form-view.html'), + controller: 'Sdc.ViewModels.AttributeFormViewModel', + size: 'sdc-md', + backdrop: 'static', + keyboard: false, + resolve: { + attribute: ():Models.AttributeModel => { + return attribute; + }, + component: ():Models.Components.Component => { + return <Models.Components.Component> this.$scope.component; + } + } + }; + this.$modal.open(modalOptions); + }; + + private initScope = ():void => { + + //let self = this; + this.$scope.sortBy = 'name'; + this.$scope.reverse = false; + this.$scope.setValidState(true); + this.$scope.tableHeadersList = [ + {title: 'Name', property: 'name'}, + {title: 'Type', property: 'type'}, + {title: 'Default Value', property: 'defaultValue'} + ]; + this.$scope.sort = (sortBy:string):void => { + this.$scope.reverse = (this.$scope.sortBy === sortBy) ? !this.$scope.reverse : false; + this.$scope.sortBy = sortBy; + }; + + this.$scope.addOrUpdateAttribute = (attribute?:Models.AttributeModel):void => { + this.openEditAttributeModal(attribute ? attribute : new Models.AttributeModel()); + }; + + this.$scope.delete = (attribute:Models.AttributeModel):void => { + + let onOk = ():void => { + this.$scope.component.deleteAttribute(attribute.uniqueId); + }; + let title:string = this.$filter('translate')("ATTRIBUTE_VIEW_DELETE_MODAL_TITLE"); + let message:string = this.$filter('translate')("ATTRIBUTE_VIEW_DELETE_MODAL_TEXT", "{'name': '" + attribute.name + "'}"); + this.ModalsHandler.openConfirmationModal(title, message, false).then(onOk); + }; + } + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/attributes/attributes-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/attributes/attributes-view.html new file mode 100644 index 0000000000..59ba933a0a --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/attributes/attributes-view.html @@ -0,0 +1,52 @@ +<div class="workspace-attributes"> + <div class="add-btn" data-tests-id="add-attribute-button" ng-if="!isViewMode()" + data-ng-class="{'disabled': isDisableMode()}" data-ng-click="addOrUpdateAttribute()" data-tests-id="add-attribute-button">Add</div> + <div class="table-container-flex"> + <div class="table" data-ng-class="{'view-mode': isViewMode()}"> + <div class="head flex-container"> + <div class="table-header head-row hand flex-item" data-ng-repeat="header in tableHeadersList track by $index" data-ng-click="sort(header.property)">{{header.title}} + <span data-ng-if="sortBy === header.property" class="table-header-sort-arrow" data-ng-class="{'down': reverse, 'up':!reverse}"> </span> + </div> + <div class="table-no-text-header head-row flex-item" ng-if="!isViewMode()"></div> + <!--div class="table-no-text-header head-row flex-item"></div--> + </div> + + <div class="body"> + <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container"> + <div data-ng-if="component.attributes.length === 0" class="no-row-text" data-ng-class="{'disabled': isDisableMode()}"> + There are no attributes to display <br> + <span ng-if="!isViewMode()"> click <a data-ng-click="addOrUpdateAttribute()">here</a> to add one </span> + + </div> + <div data-ng-repeat-start="attribute in component.attributes | orderBy:sortBy:reverse track by $index" + class="flex-container data-row" data-ng-class="{'selected': attribute.selected}" + data-ng-click="attribute.selected = !attribute.selected" data-tests-id="attributes-table-row"> + + <div class="table-col-general flex-item text"> + <span class="sprite table-arrow" data-ng-class="{'opened': attribute.selected}"></span> + <span data-tests-id="{{attribute.name}}" tooltips tooltip-content="{{attribute.name}}">{{attribute.name}}</span> + + </div> + + <div class="table-col-general flex-item text" data-tests-id="{{attribute.type}}" data-ng-bind="attribute.type"></div> + + <div class="table-col-general flex-item text"> + <span tooltips tooltip-content="{{attribute.defaultValue}}" data-tests-id="{{attribute.defaultValue}}" data-ng-bind="attribute.defaultValue"></span> + </div> + + <div class="table-btn-col flex-item" ng-if="!isViewMode()"> + <button class="table-edit-btn" data-tests-id="edit_{{attribute.name}}" data-ng-show="attribute.parentUniqueId==component.uniqueId" + data-ng-click="addOrUpdateAttribute(attribute); $event.stopPropagation();" data-ng-class="{'disabled': isViewMode()}"> </button> + <button class="table-delete-btn" data-tests-id="delete_{{attribute.name}}" data-ng-show="attribute.parentUniqueId==component.uniqueId" + data-ng-click="delete(attribute); $event.stopPropagation();" data-ng-class="{'disabled': isViewMode()}"> </button> + </div> + </div> + <div data-ng-repeat-end="" data-ng-if="attribute.selected && attribute.description" class="item-opened" data-ng-bind="attribute.description"> + </div> + </perfect-scrollbar> + </div> + + </div> + </div> + +</div> diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/attributes/attributes.less b/catalog-ui/app/scripts/view-models/workspace/tabs/attributes/attributes.less new file mode 100644 index 0000000000..ffd28afce4 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/attributes/attributes.less @@ -0,0 +1,54 @@ +.workspace-attributes { + + width: 93%; + display: inline-block; + .w-sdc-classic-btn { + float: right; + margin-bottom: 10px; + } + + .table{ + height:490px; + margin-bottom: 0; + } + + .table-container-flex { + margin-top: 27px; + + .text{ + overflow: hidden; + text-overflow: ellipsis; + display: inline-block; + white-space: nowrap; + } + + .flex-item:nth-child(1) { + flex-grow: 15; + + .hand; + span.table-arrow { + margin-right: 7px; + } + } + + .flex-item:nth-child(2) { + flex-grow: 6; + } + + .flex-item:nth-child(3) { + flex-grow: 9; + } + + .flex-item:nth-child(4) { + flex-grow: 3; + padding-top: 10px; + } + + .flex-item:nth-child(5) { + flex-grow: 1; + + } + + } + +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/composition-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/composition-view-model.ts new file mode 100644 index 0000000000..f8eeaf7f64 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/composition-view-model.ts @@ -0,0 +1,232 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +/// <reference path="../../../../references"/> +module Sdc.ViewModels { + 'use strict'; + + export interface ICompositionViewModelScope extends IWorkspaceViewModelScope { + + currentComponent: Models.Components.Component; + selectedComponent: Models.Components.Component; + isLoading: boolean; + graphApi:any; + sharingService:Sdc.Services.SharingService; + sdcMenu:Models.IAppMenu; + version:string; + isViewOnly:boolean; + isLoadingRightPanel:boolean; + setComponent(component: Models.Components.Component); + isComponentInstanceSelected():boolean; + updateSelectedComponent(): void + openUpdateModal(); + deleteSelectedComponentInstance():void; + onBackgroundClick():void; + setSelectedInstance(componentInstance: Models.ComponentsInstances.ComponentInstance): void; + printScreen():void; + + cacheComponentsInstancesFullData: Models.Components.Component; + } + + export class CompositionViewModel { + + static '$inject' = [ + '$scope', + '$log', + 'sdcMenu', + 'MenuHandler', + '$modal', + '$templateCache', + '$state', + 'Sdc.Services.SharingService', + '$filter', + 'Sdc.Services.CacheService', + 'ComponentFactory', + 'ChangeLifecycleStateHandler', + 'LeftPaletteLoaderService', + 'ModalsHandler', + 'EventListenerService' + ]; + + constructor(private $scope:ICompositionViewModelScope, + private $log: ng.ILogService, + private sdcMenu:Models.IAppMenu, + private MenuHandler: Utils.MenuHandler, + private $modal:ng.ui.bootstrap.IModalService, + private $templateCache:ng.ITemplateCacheService, + private $state:ng.ui.IStateService, + private sharingService:Services.SharingService, + private $filter:ng.IFilterService, + private cacheService:Services.CacheService, + private ComponentFactory: Utils.ComponentFactory, + private ChangeLifecycleStateHandler: Sdc.Utils.ChangeLifecycleStateHandler, + private LeftPaletteLoaderService: Services.Components.LeftPaletteLoaderService, + private ModalsHandler: Sdc.Utils.ModalsHandler, + private eventListenerService:Services.EventListenerService) { + + this.$scope.setValidState(true); + this.initScope(); + this.$scope.updateSelectedMenuItem(); + this.registerGraphEvents(this.$scope); + } + private cacheComponentsInstancesFullData: Array<Models.Components.Component>; + + private initComponent = ():void => { + + this.$scope.currentComponent = this.$scope.component; + this.$scope.selectedComponent = this.$scope.currentComponent; + this.updateUuidMap(); + this.$scope.isViewOnly = this.$scope.isViewMode(); + }; + private registerGraphEvents = (scope:ICompositionViewModelScope):void => { + + this.eventListenerService.registerObserverCallback(Utils.Constants.GRAPH_EVENTS.ON_NODE_SELECTED, scope.setSelectedInstance); + this.eventListenerService.registerObserverCallback(Utils.Constants.GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED, scope.onBackgroundClick); + + } + private openUpdateComponentInstanceNameModal = ():void => { + + let viewModelsHtmlBasePath:string = '/app/scripts/view-models/'; + let modalOptions:ng.ui.bootstrap.IModalSettings = { + template: this.$templateCache.get(viewModelsHtmlBasePath + 'forms/resource-instance-name-form/resource-instance-name-view.html'), + controller: 'Sdc.ViewModels.ResourceInstanceNameViewModel', + size: 'sdc-sm', + backdrop: 'static', + resolve: { + component: ():Models.Components.Component => { + return this.$scope.currentComponent; + + } + } + }; + + let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$modal.open(modalOptions); + modalInstance.result.then(():void => { + this.eventListenerService.notifyObservers(Utils.Constants.GRAPH_EVENTS.ON_COMPONENT_INSTANCE_NAME_CHANGED, this.$scope.currentComponent.selectedInstance); + //this.$scope.graphApi.updateNodeName(this.$scope.currentComponent.selectedInstance); + }); + }; + + private removeSelectedComponentInstance = ():void => { + this.eventListenerService.notifyObservers(Utils.Constants.GRAPH_EVENTS.ON_DELETE_MULTIPLE_COMPONENTS); + }; + + private updateUuidMap = ():void => { + /** + * In case user press F5, the page is refreshed and this.sharingService.currentEntity will be undefined, + * but after loadService or loadResource this.sharingService.currentEntity will be defined. + * Need to update the uuidMap with the new resource or service. + */ + this.sharingService.addUuidValue(this.$scope.currentComponent.uniqueId,this.$scope.currentComponent.uuid); + }; + + private initScope = ():void => { + + this.$scope.sharingService = this.sharingService; + this.$scope.sdcMenu = this.sdcMenu; + this.$scope.isLoading = false; + this.$scope.isLoadingRightPanel = false; + this.$scope.graphApi = {}; + this.$scope.version = this.cacheService.get('version'); + this.initComponent(); + + this.cacheComponentsInstancesFullData = new Array<Models.Components.Component>(); + + this.$scope.isComponentInstanceSelected = ():boolean => { + return this.$scope.currentComponent && this.$scope.currentComponent.selectedInstance != undefined && this.$scope.currentComponent.selectedInstance != null; + }; + + this.$scope.updateSelectedComponent = (): void => { + if(this.$scope.currentComponent.selectedInstance){ + + let componentParent = _.find(this.cacheComponentsInstancesFullData, (component) => { + return component.uniqueId === this.$scope.currentComponent.selectedInstance.componentUid; + }); + if(componentParent) { + this.$scope.selectedComponent = componentParent; + } + else { + try { + let onSuccess = (component:Models.Components.Component) => { + this.$scope.isLoadingRightPanel = false; + this.$scope.selectedComponent = component; + this.cacheComponentsInstancesFullData.push(component); + }; + let onError = (component:Models.Components.Component) => { + console.log("Error updating selected component"); + this.$scope.isLoadingRightPanel = false; + }; + this.ComponentFactory.getComponentFromServer(this.$scope.currentComponent.selectedInstance.originType, this.$scope.currentComponent.selectedInstance.componentUid).then(onSuccess, onError); + } catch(e){ + console.log("Error updating selected component", e); + this.$scope.isLoadingRightPanel = false; + } + } + } + else { + this.$scope.selectedComponent = this.$scope.currentComponent; + } + }; + + this.$scope.setSelectedInstance = (selectedComponent:Models.ComponentsInstances.ComponentInstance):void => { + + this.$log.debug('composition-view-model::onNodeSelected:: with id: '+ selectedComponent.uniqueId); + this.$scope.currentComponent.setSelectedInstance(selectedComponent); + this.$scope.updateSelectedComponent(); + + if (this.$state.current.name === 'workspace.composition.api') { + this.$state.go('workspace.composition.details'); + } + if (this.$state.current.name === 'workspace.composition.relations' && this.$scope.currentComponent.isProduct()) { + this.$state.go('workspace.composition.details'); + } + }; + + this.$scope.onBackgroundClick = ():void => { + this.$scope.currentComponent.selectedInstance = null; + this.$scope.selectedComponent = this.$scope.currentComponent; + + if (this.$state.current.name === 'workspace.composition.api') { + this.$state.go('workspace.composition.details'); + } + }; + + this.$scope.openUpdateModal = ():void => { + this.openUpdateComponentInstanceNameModal(); + }; + + this.$scope.deleteSelectedComponentInstance = ():void => { + let state = "deleteInstance"; + let onOk = ():void => { + this.removeSelectedComponentInstance(); + //this.$scope.graphApi.deleteSelectedNodes(); + }; + let title:string = this.$scope.sdcMenu.alertMessages[state].title; + let message:string = this.$scope.sdcMenu.alertMessages[state].message.format([this.$scope.currentComponent.selectedInstance.name]); + this.ModalsHandler.openAlertModal(title, message).then(onOk); + }; + + this.$scope.setComponent = (component: Models.Components.Product):void => { + this.$scope.currentComponent = component; + } + + } + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/composition-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/composition-view.html new file mode 100644 index 0000000000..4efc74c31b --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/composition-view.html @@ -0,0 +1,99 @@ +<div class="workspace-composition"> + <loader data-display="isLoading"></loader> + <div class="w-sdc-designer-canvas" data-ng-class="{sidebaractive: displayDesignerRightSidebar}" > + <palette components="leftPanelComponents" + current-component="currentComponent" + is-view-only="isViewOnly" + is-loading="isLoading"></palette> + + <composition-graph component="currentComponent" is-view-only="isViewOnly"></composition-graph> + <!--<graph-creator left-panel-components="leftPanelComponents"--> + <!--data-tests-id="canvas"--> + <!--on-instance-selected="setSelectedInstance(componentInstance)"--> + <!--on-background-click="onBackgroundClick()" current-component="currentComponent"--> + <!--api="graphApi" is-view-only="isViewOnly" is-loading="isLoading"></graph-creator>--> + </div> + + <div class="w-sdc-designer-sidebar-toggle" data-ng-class="{'active': displayDesignerRightSidebar}" + data-ng-init="displayDesignerRightSidebar = true" + data-ng-click="displayDesignerRightSidebar = !displayDesignerRightSidebar"> + <div class="w-sdc-designer-sidebar-toggle-icon sprite-new pointer menu-open-left"></div> + </div> + + <div class="w-sdc-designer-sidebar" data-ng-class="{'view-mode':isViewOnly}"> + + <div class="w-sdc-designer-sidebar-head" data-tests-id="w-sdc-designer-sidebar-head"> + <div class="w-sdc-designer-sidebar-logo-ph"> + <div class="large {{selectedComponent.iconSprite}} {{selectedComponent.icon}}"> + <div ng-if="isComponentInstanceSelected()" + data-ng-class="{'non-certified':'CERTIFIED' !== selectedComponent.lifecycleState, 'smaller-icon': selectedComponent.icon==='vl' || selectedComponent.icon==='cp'}" + tooltips tooltip-side="top" tooltip-content="Not certified"></div> + </div> + </div> + + <div class="w-sdc-designer-sidebar-logo"> + <span class="w-sdc-designer-sidebar-logo-title" data-tests-id="selectedCompTitle" tooltips + tooltip-class="tooltip-custom break-word-tooltip" + tooltip-content="​{{isComponentInstanceSelected() ? currentComponent.selectedInstance.name : currentComponent.name | resourceName}}" + data-ng-bind="isComponentInstanceSelected() ? currentComponent.selectedInstance.name : currentComponent.name | resourceName"></span> + </div> + <div class="sprite e-sdc-small-icon-pencil w-sdc-designer-update-resource-icon" + data-ng-if="!isViewOnly && isComponentInstanceSelected()" + data-ng-click="openUpdateModal()" id="editPencil"></div> + + <div class="sprite e-sdc-small-icon-delete w-sdc-designer-delete-resource-icon" + data-tests-id="e-sdc-small-icon-delete" + data-ng-if="!isViewOnly && isComponentInstanceSelected()" + data-ng-click="!isLoading && deleteSelectedComponentInstance()" title="Delete Resource Instance"></div> + </div> + + <div class="w-sdc-designer-sidebar-tabs"> + <button class="i-sdc-designer-sidebar-tab" data-ui-sref-active="active" + data-ui-sref="workspace.composition.details" + tooltips tooltip-class="tooltip-custom tab-tooltip" tooltip-content="Information"> + <div class="i-sdc-designer-sidebar-tab-icon sprite-new info"></div> + </button> + <button class="i-sdc-designer-sidebar-tab" data-ui-sref-active="active" + ui-sref="workspace.composition.structure" + tooltips tooltip-class="tooltip-custom tab-tooltip" tooltip-content="Composition"> + <div class="i-sdc-designer-sidebar-tab-icon sprite-new structure"></div> + </button> + <button class="i-sdc-designer-sidebar-tab" data-ui-sref-active="active" + data-ui-sref="workspace.composition.deployment" + tooltips tooltip-class="tooltip-custom tab-tooltip" tooltip-content="Deployment Artifacts" + data-tests-id="deployment-artifact-tab"> + <div class="i-sdc-designer-sidebar-tab-icon sprite-new deployment-artifacts"></div> + </button> + <button tooltips tooltip-class="tooltip-custom tab-tooltip" + tooltip-content="{{selectedComponent.isResource() ? 'Properties and Attributes': 'Inputs'}}" + class="i-sdc-designer-sidebar-tab" data-ui-sref-active="active" + data-ui-sref="workspace.composition.properties" + data-tests-id="properties-and-attributes-tab"> + <div class="i-sdc-designer-sidebar-tab-icon sprite-new" + ng-class="selectedComponent.isResource() ? 'properties': 'inputs'"></div> + </button> + <button class="i-sdc-designer-sidebar-tab" data-ui-sref-active="active" + data-ui-sref="workspace.composition.artifacts" + tooltips tooltip-class="tooltip-custom tab-tooltip" tooltip-content="Information Artifacts"> + <div class="i-sdc-designer-sidebar-tab-icon sprite-new information-artifacts"></div> + </button> + <button data-ng-show="!selectedComponent.isService()" class="i-sdc-designer-sidebar-tab" + data-ui-sref-active="active" ui-sref="workspace.composition.relations" + tooltips tooltip-class="tooltip-custom tab-tooltip tooltip-rightside" + tooltip-content="Requirements and Capabilities"> + <div class="i-sdc-designer-sidebar-tab-icon sprite-new relations"></div> + </button> + <button data-ng-show="selectedComponent.isService()" class="i-sdc-designer-sidebar-tab" + data-ui-sref-active="active" ui-sref="workspace.composition.api" data-tests-id="tab-api" + tooltips tooltip-class="tooltip-custom tab-tooltip tooltip-rightside" tooltip-content="API"> + <div class="i-sdc-designer-sidebar-tab-icon sprite-new api"></div> + </button> + + </div> + + <div data-ui-view="" class="w-sdc-designer-sidebar-tab-content-view"></div> + + <loader data-display="isLoadingRightPanel" relative="true" size="medium"></loader> + + </div> +</div> diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/composition.less b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/composition.less new file mode 100644 index 0000000000..4c4c0a87a5 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/composition.less @@ -0,0 +1,864 @@ + +.composition{ + .sdc-workspace-container{ + .w-sdc-main-container{ + .w-sdc-main-right-container{ + left:0; + .sdc-workspace-top-bar { + padding-left: 295px; + .not-latest{ + left: 270px; + } + } + .w-sdc-main-container-body-content{ + padding: 0 0 0 247px; + } + + > div:first-child{ + padding: 0; + } + } + } + } +} + +.workspace-composition { + height:100%; + display: block; + text-align: left; + align-items: left; + padding: 0; + + + + // --------------------------------------------------------------------------------------------------- + // Sidebar + // --------------------------------------------------------------------------------------------------- + + + + .w-sdc-designer-sidebar-toggle { + background-color: @main_color_p; + border-left: 1px solid @main_color_o; + border-bottom: 1px solid @main_color_o; + height: 21px; + position: absolute; + right: 0; + top: 53px; + width: 17px; + transition: right 0.2s; + z-index: 10; + .box-shadow(-1px 1px 3px 0 @main_color_n); + + &.active { + right: 302px; + .w-sdc-designer-sidebar-toggle-icon{ + transform: rotate(180deg); + } + } + + } + + .w-sdc-designer-sidebar-toggle-icon { + margin-left: 6px; + margin-top: 6px; + } + + .w-sdc-designer-sidebar { + background-color:@main_color_p ; + .noselect; + bottom: 0; + position: fixed; + right: -302px; + width: 302px; + top: 102px; + transition: right 0.2s; + z-index: 9; + .box-shadow(-7px -3px 6px -8px @main_color_n); + + } + + .w-sdc-designer-sidebar-toggle.active + .w-sdc-designer-sidebar { + right: 0; + + } + + .w-sdc-designer-sidebar-head { + padding: 36px 30px 30px 30px; + height: 120px; + } + + .w-sdc-designer-sidebar-logo-ph { + display: inline-block; + vertical-align: middle; + line-height: 48px; + height: 48px; + } + + .w-sdc-designer-sidebar-logo { + .g_6; + display: inline-block; + margin-left: 10px; + font-weight: 500; + } + + .w-sdc-designer-sidebar-logo-title { + .s_16_r; + .selectable; + vertical-align: middle; + text-overflow: ellipsis; + max-width: 167px; + display: inline-block; + white-space: nowrap; + overflow: hidden; + } + + .w-sdc-designer-update-resource-icon { + .hand; + position: absolute; + right: 20px; + top: 10px; + } + + .w-sdc-designer-delete-resource-icon { + .hand; + position: absolute; + right: 40px; + top: 10px; + } + + .w-sdc-designer-sidebar-tabs { + .bg_e; + } + + .w-sdc-designer-sidebar-tabs::after { + clear: both; + content: ''; + display: table; + } + + .i-sdc-designer-sidebar-tab { + background-color: @main_color_p; + border: 1px solid @tlv_color_u;; + border-left: none; + display: inline-block; + float: left; + height: 36px; + padding-top: 9px; + text-align: center; + width: 50px; + .hand; + + &:focus { + outline: none; + } + &.tab-disabled { + /* .disabled; */ + } + &.active, &:hover:enabled { + background-color: @tlv_color_u; + .i-sdc-designer-sidebar-tab-icon { + opacity: 1; + + + } + + } + + div& { + padding-top: 0; + } + /*for tooltip on disabled buttons*/ + } + + .i-sdc-designer-sidebar-tab-icon { + margin-top: 5px ; + // opacity: .4; + } + + .w-sdc-designer-sidebar-tab-content { + .perfect-scrollbar; + height: 100%; + } + + .w-sdc-designer-sidebar-tab-content-view { + position: absolute; + top: 156px; + bottom: 0px; + width: 100%; + + } + + .w-sdc-designer-sidebar-section { + } + + .w-sdc-designer-sidebar-section-title { + .m_14_m; + background-color: @tlv_color_u; + .hand; + clear: both; + height: 32px; + line-height: 32px; + margin-top: 1px; + padding: 0 40px 0 20px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + position: relative; + width: 100%; + display: block; + + &.expanded { + .w-sdc-designer-sidebar-section-title-icon { + transform: rotate(180deg); + } + } + } + + .w-sdc-designer-sidebar-section-title-text { + max-width: 240px; + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + position: relative; + } + + .w-sdc-designer-sidebar-section-title-icon { + .hand; + .sprite-new; + .arrow-up; + right: 16px; + top: 10px; + transition: .3s all; + position: absolute; + } + + .w-sdc-designer-sidebar-section-content { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .w-sdc-designer-sidebar-section-title + .w-sdc-designer-sidebar-section-content { + margin: 0 auto; + } + + .w-sdc-designer-sidebar-section-title.expanded + .w-sdc-designer-sidebar-section-content { + margin: 0 auto 1px; + + } + + .i-sdc-designer-sidebar-section-content-item { + .b_7; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + //max-width: 250px; + + &.description { + margin-top: 28px; + white-space: normal; + word-wrap: break-word; + } + } + + .i-sdc-designer-sidebar-section-content-item-tag { + .g_7; + .bg_c; + border-radius: 4px; + display: inline-block; + line-height: 25px; + margin: 0 4px 6px 0; + min-width: 50px; + padding: 0 9px; + text-align: center; + max-width: 280px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .w-sdc-designer-sidebar-section-footer { + margin-top: 10px; + text-align: center; + width: 100%; + } + + + + .w-sdc-designer-sidebar-section-footer-action { + width: 180px; + margin-top: 10px; + } + + //////////////////////Relationship + .w-sdc-designer-sidebar-section-requirements { + border-bottom: 1px solid @color_e; + margin: 0 13px 20px 13px; + padding: 15px 0 0; + } + + .w-sdc-designer-sidebar-section-requirements-item { + margin-bottom: 20px; + } + + .w-sdc-designer-sidebar-section-requirements-label { + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: middle; + white-space: nowrap; + width: 102px; + } + + .w-sdc-designer-sidebar-section-requirements-select { + border: 1px solid @color_e; + min-height: 30px; + padding: 4px 13px; + width: 168px; + } + + //////////////////////Properties + .i-sdc-designer-sidebar-section-content-item-property-and-attribute { + .b_7; + border-bottom: 1px solid @color_e; + min-height: 72px; + padding: 15px 10px 10px 18px; + position: relative; + + &:first-child { + //margin-top: -18px; + } + + &:hover { + // .bg_c_hover; + .bg_c; + transition: all .3s; + + .i-sdc-designer-sidebar-section-content-item-button { + display: block; + } + } + } + + .i-sdc-designer-sidebar-section-content-item-property-and-attribute-label { + overflow: hidden; + text-overflow: ellipsis; + max-width: 200px; + white-space: nowrap; + display: inline-block; + &:hover { + .a_7; + } + } + + .i-sdc-designer-sidebar-section-content-item-property-value { + overflow: hidden; + text-overflow: ellipsis; + max-width: 200px; + display: inline-block; + white-space: nowrap; + + } + + .i-sdc-designer-sidebar-section-content-item-property-label-value { + } + + .i-sdc-designer-sidebar-section-content-item-button { + display: none; + position: absolute; + top: 25px; + + &.update { + background-color: transparent; + border: 0; + right: 60px; + } + + &.delete { + background-color: transparent; + border: 0; + right: 13px; + } + + &.download { + background-color: transparent; + border: 0; + right: 35px; + } + + &.download-env { + background-color: transparent; + border: 0; + right: 35px; + margin-top: 65px; + } + + &.attach { + background-color: transparent; + border: 0; + right: 15px; + } + } + + // --------------------------------------------------------------------------------------------------- + // Canvas + // --------------------------------------------------------------------------------------------------- + .w-sdc-designer-canvas { + height:100%; + .noselect; + .bg_c; + bottom: 0; + // position: fixed; + //right: 0; + //left: 240px; + //top: 94px; + .view-mode{ + background-color: #f8f8f8; + border:0; + } + } + + .w-sdc-designer-canvas.sidebaractive { + //right: 300px; + } + + .w-sdc-designer-element { + .hand; + width: 200px; + height: 100px; + position: absolute; + text-align: center; + top: 50%; + margin-top: -200px; + left: 50%; + margin-left: -50px; + } + + .w-sdc-designer-resource-label { + .q_7; + } + + .w-sdc-designer-resource-label-indicator { + .bg_q; + border-radius: 50%; + display: inline-block; + height: 10px; + margin-right: 6px; + vertical-align: middle; + width: 10px; + + &.valid { + .bg_l; + } + + &.invalid { + .bg_h; + } + } + + // --------------------------------------------------------------------------------------------------- + // Leftbar + // --------------------------------------------------------------------------------------------------- + .w-sdc-designer-leftbar { + background-color: @main_color_p; + bottom: 0; + left: 0; + overflow-y: scroll; + overflow-x: hidden; + position: absolute; + top: 0; + width: 244px; + .box-shadow(7px -3px 6px -8px @main_color_n); + + } + + .w-sdc-designer-leftbar-title { + + .p_16_m; + background-color: @main_color_n; + line-height: 40px; + padding: 0 17px; + } + + .w-sdc-designer-leftbar-title-count { + float: right; + } + + .w-scd-diagram-container { + // left: 240px; + //right: 300px; + } + + .w-sdc-designer-leftbar-search { + background-color: @tlv_color_u; + padding: 10px; + white-space: nowrap; + position: relative; + } + + .w-sdc-designer-leftbar-search-input { + border: 1px solid @color_e; + .border-radius(4px); + height: 30px; + margin: 0; + padding: 0px 28px 3px 10px; + vertical-align: 4px; + width: 100%; + outline: none; + font-style: italic; + } + + .w-sdc-designer-leftbar-search-filter { + + } + + .i-sdc-designer-leftbar-section { + .hand; + } + + .i-sdc-designer-leftbar-section-title { + .m_14_m; + background-color: @tlv_color_u; + .hand; + clear: both; + height: 40px; + line-height: 40px; + margin-top: 1px; + padding: 0 10px; + position: relative; + text-transform: uppercase; + font-weight: bold; + } + + .i-sdc-designer-leftbar-section-title-icon { + .hand; + .sprite-new; + .arrow-up; + width: 15px; + height: 9px; + position: absolute; + right: 13px; + top: 18px; + transition: .3s all; + } + + .i-sdc-designer-leftbar-section.expanded .i-sdc-designer-leftbar-section-title-icon { + transform: rotate(180deg); + margin-right: 2px; + } + + .i-sdc-designer-leftbar-section-content { + background-color: @main_color_o; + } + + .i-sdc-designer-leftbar-section-content-item { + background-color: @main_color_p; + overflow: hidden; + + &:hover { + background-color: @main_color_p; + } + + .cp{ + margin: 6px; + } + + .vl{ + margin: 6px; + } + } + + .i-sdc-designer-leftbar-section-content-subcat { + .m_14_m; + background-color: @tlv_color_t; + line-height: 35px; + padding: 0 10px; + cursor: default; + + + &:hover { + background-color: @func_color_r; + } + + + } + + .i-sdc-designer-leftbar-section .i-sdc-designer-leftbar-section-content .i-sdc-designer-leftbar-section-content-item { + max-height: 0px; + margin: 0 auto; + transition: all .3s; + } + + .i-sdc-designer-leftbar-section.expanded .i-sdc-designer-leftbar-section-content .i-sdc-designer-leftbar-section-content-item { + max-height: 64px; + margin: 0 auto 1px auto; + // padding: 4px 13px; + } + + .i-sdc-designer-leftbar-section.expanded .i-sdc-designer-leftbar-section-content .i-sdc-designer-leftbar-section-content-subcat { + margin: 0; + } + + .i-sdc-designer-leftbar-section-content-item-icon-ph { + display: inline-block; + margin: 12px 0 12px 10px; + pointer-events: auto; + + .non-certified { + position: relative; + left: 27px; + bottom: 6px; + .sprite; + .s-sdc-state-non-certified; + display: block; + + &.smaller-icon { + bottom: 6px; + left: 13px; + } + } + + + + } + + .non-certified { + position: relative; + left: 43px; + bottom: 3px; + .sprite; + .s-sdc-state-non-certified; + display: block; + + &.smaller-icon { + left: 35px; + bottom: -14px; + } + } + /* + .i-sdc-composition-leftbar-section-content-item-icon { + background-image: url('../../../styles/images/resource-icons/default.png'); + // position: absolute; + right: 20px; + top: 10px; + height: 40px; + width: 40px; + background-size: 40px; + } + */ + + .i-sdc-designer-leftbar-section-content-item-info { + display: inline-block; + // margin-left: 10px; + //overflow: hidden; + // vertical-align: middle; + width: 160px; + padding: 0 0 0 10px; + } + + .i-sdc-designer-leftbar-section-content-item-info-title { + .m_14_m; + line-height: 14px; + overflow: hidden; + text-overflow: ellipsis; + text-transform: uppercase; + max-width: 120px; + display: inline-block; + white-space: nowrap; + vertical-align: bottom; + } + + .i-sdc-designer-leftbar-section-content-item-info-text { + .p_3; + line-height: 15px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + //margin: -1px 0 2px 0; + } + + .i-sdc-designer-leftbar-section-content-item-info-text-link { + color: @color_s; + text-decoration: underline; + float: right; + position: absolute; + right: 17px; + // bottom: 5px; + } + + // --------------------------------------------------------------------------------------------------- + // Form actions + // --------------------------------------------------------------------------------------------------- + .w-sdc-form-actions-container.add-property { + text-align: center; + width: 100%; + margin-top: 2px; + margin-bottom: 12px; + + .w-sdc-form-action { + width: 245px; + } + .w-sdc-form-action.add-property-add-another { + .bg_a; + margin-left: 35px; + } + .w-sdc-form-action.add-property-done { + margin-left: 312px; + } + .w-sdc-form-action.save { + margin-left: 327px; + margin-bottom: 30px; + } + + } + + // --------------------------------------------------------------------------------------------------- + // Top menu + // --------------------------------------------------------------------------------------------------- + .w-sdc-header-menu { + padding: 25px 0; + text-align: center; + white-space: nowrap; + } + + .i-sdc-header-menu-item { + cursor: pointer; + display: inline-block; + height: 43px; + min-width: 93px; + padding: 0 38px; + position: relative; + vertical-align: middle; + + &::after { + border-right: 1px solid @color_m; + content: ''; + display: block; + height: 43px; + right: 0; + position: absolute; + top: 0; + width: 2px; + } + + &:first-child { + &::before { + border-right: 1px solid @color_m; + content: ''; + display: block; + height: 43px; + left: 0; + position: absolute; + top: 0; + width: 2px; + } + } + } + + .i-sdc-header-menu-item-icon { + display: inline-block; + height: 20px; + width: 28px; + } + + .i-sdc-header-menu-item-label { + .g_1; + line-height: 18px; + } + + // --------------------------------------------------------------------------------------------------- + // Canvas inline menu + // --------------------------------------------------------------------------------------------------- + .w-sdc-canvas-menu { + position: fixed; + z-index: 100; + + border-style: solid; + border-width: 1px; + border-color: #d8d8d8; + box-sizing: border-box; + background-color: #ffffff; + box-shadow: 0px 2px 2px 0px rgba(24, 24, 25, 0.1); + width: 91px; + +/* &.vl-type-select{ + width: 173px; + } +*/ + + h3 { + color: @func_color_s; + font-size: 14px; + font-weight: bold; + margin: 0; + padding: 7px 11px; + border-bottom: 1px solid #e5e5e5; + } + + .w-sdc-canvas-menu-content { + padding: 5px 5px; + + &.vl-select{ + border-bottom: #d8d8d8 solid 1px; + line-height: 15px; + + .tlv-radio { + padding: 3px 0px; + + .tlv-radio-label { + padding: 3px 0px; + + &::before { + margin-right: 10px; + } + } + } + } + + .w-sdc-canvas-menu-content-update-button { + .sprite; + .sprite.e-sdc-small-icon-delete; + .hand; + position: absolute; + top: 15px; + right: 10px; + } + .w-sdc-canvas-menu-content-delete-button { + .sprite; + .sprite.e-sdc-small-icon-delete; + .hand; + margin: 0 8px 0 6px; + } + } + + .w-sdc-canvas-menu-arrow { + //TODO: Missing image for small blue triangle. + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAYAAAB4ka1VAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMDE0IDc5LjE1Njc5NywgMjAxNC8wOC8yMC0wOTo1MzowMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkE1OTIzNDI1MENFQjExRTU4ODRERTI1MDM2REZCOUYzIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkE1OTIzNDI2MENFQjExRTU4ODRERTI1MDM2REZCOUYzIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QTU5MjM0MjMwQ0VCMTFFNTg4NERFMjUwMzZERkI5RjMiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QTU5MjM0MjQwQ0VCMTFFNTg4NERFMjUwMzZERkI5RjMiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4gBXTlAAAAOElEQVR42mK0rp7NgASMgZgFiE/CBJjQJPcA8U4gNkdXAJMUAGJ+ZEVMaJIwAFfEhEUSRRFAgAEAVtgJyiLAPWAAAAAASUVORK5CYII='); + content: ''; + display: block; + height: 21px; + position: absolute; + right: 12px; + top: -24px; + width: 184px; + background-repeat: no-repeat; + background-position: 175px 16px; + } + + } +} +/*.right-tab-loader { + border: 16px solid #f3f3f3; !* Light grey *! + border-top: 16px solid #3498db; !* Blue *! + border-radius: 50%; + width: 120px; + height: 120px; + animation: spin 2s linear infinite; +}*/ + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view-model.ts new file mode 100644 index 0000000000..5bb5d2cbbd --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view-model.ts @@ -0,0 +1,255 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../../../../references"/> +module Sdc.ViewModels { + 'use strict'; + import Resource = Sdc.Models.Components.Resource; + + export interface IArtifactsViewModelScope extends ICompositionViewModelScope { + artifacts: Array<Models.ArtifactModel>; + artifactType: string; + downloadFile:Models.IFileDownload; + isLoading:boolean; + + getTitle(): string; + addOrUpdate(artifact:Models.ArtifactModel): void; + delete(artifact:Models.ArtifactModel): void; + download(artifact:Models.ArtifactModel): void; + openEditEnvParametersModal(artifact:Models.ArtifactModel):void; + getEnvArtifact(heatArtifact:Models.ArtifactModel):any; + getEnvArtifactName(artifact:Models.ArtifactModel):string; + isLicenseArtifact(artifact:Models.ArtifactModel):boolean; + isVFiArtifact(artifact:Models.ArtifactModel):boolean; + } + + export class ResourceArtifactsViewModel { + + static '$inject' = [ + '$scope', + '$filter', + '$modal', + '$templateCache', + '$state', + 'sdcConfig', + 'ArtifactsUtils', + 'ModalsHandler', + 'Sdc.Services.CacheService' + ]; + + constructor(private $scope:IArtifactsViewModelScope, + private $filter:ng.IFilterService, + private $modal:ng.ui.bootstrap.IModalService, + private $templateCache:ng.ITemplateCacheService, + private $state:any, + private sdcConfig:Models.IAppConfigurtaion, + private artifactsUtils:Sdc.Utils.ArtifactsUtils, + private ModalsHandler: Utils.ModalsHandler, + private cacheService:Services.CacheService) { + + this.initScope(); + } + + + private initArtifactArr = (artifactType:string):void => { + let artifacts:Array<Models.ArtifactModel> = []; + + if (this.$scope.selectedComponent) { + if ('interface' == artifactType) { + let interfaces = this.$scope.selectedComponent.interfaces; + if (interfaces && interfaces.standard && interfaces.standard.operations) { + + angular.forEach(interfaces.standard.operations, (operation:any, interfaceName:string):void => { + let item:Sdc.Models.ArtifactModel = <Sdc.Models.ArtifactModel>{}; + if (operation.implementation) { + item = <Sdc.Models.ArtifactModel> operation.implementation; + } + item.artifactDisplayName = interfaceName; + item.artifactLabel = interfaceName; + item.mandatory = false; + artifacts.push(item); + }); + } + }else { + //init normal artifacts, deployment or api artifacts + let artifactsObj:Models.ArtifactGroupModel; + switch (artifactType) { + case "api": + artifactsObj = (<Models.Components.Service>this.$scope.selectedComponent).serviceApiArtifacts; + break; + case "deployment": + if (!this.$scope.isComponentInstanceSelected()) { + artifactsObj = this.$scope.selectedComponent.deploymentArtifacts; + } else { + artifactsObj = this.$scope.currentComponent.selectedInstance.deploymentArtifacts; + } + break; + default: + artifactsObj = this.$scope.selectedComponent.artifacts; + break; + } + _.forEach(artifactsObj, (artifact:Models.ArtifactModel, key) => { + artifacts.push(artifact); + }); + } + } + this.$scope.artifacts = artifacts; + }; + + private openEditArtifactModal = (artifact:Models.ArtifactModel):void => { + let viewModelsHtmlBasePath:string = '/app/scripts/view-models/'; + + let modalOptions:ng.ui.bootstrap.IModalSettings = { + template: this.$templateCache.get(viewModelsHtmlBasePath + 'forms/artifact-form/artifact-form-view.html'), + controller: 'Sdc.ViewModels.ArtifactResourceFormViewModel', + size: 'sdc-md', + backdrop: 'static', + keyboard: false, + resolve: { + artifact: ():Models.ArtifactModel => { + return artifact; + }, + component: (): Models.Components.Component => { + return this.$scope.currentComponent; + } + } + }; + + let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$modal.open(modalOptions); + modalInstance + .result + .then(():void => { + this.initArtifactArr(this.$scope.artifactType); + }); + }; + + private initScope = ():void => { + let self = this; + this.$scope.isLoading= false; + this.$scope.artifactType = this.artifactsUtils.getArtifactTypeByState(this.$state.current.name); + this.initArtifactArr(this.$scope.artifactType); + + this.$scope.getTitle = ():string => { + return this.artifactsUtils.getTitle(this.$scope.artifactType, this.$scope.selectedComponent); + }; + + let vfiArtifactTypes:any = this.cacheService.get('UIConfiguration').artifacts.deployment.resourceInstanceDeploymentArtifacts; + + this.$scope.isVFiArtifact=(artifact:Models.ArtifactModel):boolean=>{ + return vfiArtifactTypes[artifact.artifactType]; + } + + this.$scope.$watch('selectedComponent', (newResource:Models.Components.Component):void => { + if (newResource) { + this.initArtifactArr(this.$scope.artifactType); + } + }); + + + this.$scope.$watch('currentComponent.selectedInstance', (newInstance:Models.ComponentsInstances.ComponentInstance):void => { + if (newInstance) { + this.initArtifactArr(this.$scope.artifactType); + } + }); + + this.$scope.addOrUpdate = (artifact:Models.ArtifactModel):void => { + this.artifactsUtils.setArtifactType(artifact, this.$scope.artifactType); + let artifactCopy = new Models.ArtifactModel(artifact); + this.openEditArtifactModal(artifactCopy); + }; + + + this.$scope.delete = (artifact:Models.ArtifactModel):void => { + + let onOk = ():void => { + this.$scope.isLoading= true; + this.artifactsUtils.removeArtifact(artifact, this.$scope.artifacts); + + let success = (responseArtifact:Models.ArtifactModel):void => { + this.initArtifactArr(this.$scope.artifactType); + this.$scope.isLoading= false; + }; + + let error =(error:any):void =>{ + console.log('Delete artifact returned error:', error); + this.initArtifactArr(this.$scope.artifactType); + this.$scope.isLoading= false; + }; + if(this.$scope.isComponentInstanceSelected()){ + this.$scope.currentComponent.deleteInstanceArtifact(artifact.uniqueId, artifact.artifactLabel).then(success, error); + }else{ + this.$scope.currentComponent.deleteArtifact(artifact.uniqueId, artifact.artifactLabel).then(success, error);//TODO simulate error (make sure error returns) + } + }; + let title: string = this.$filter('translate')("ARTIFACT_VIEW_DELETE_MODAL_TITLE"); + let message: string = this.$filter('translate')("ARTIFACT_VIEW_DELETE_MODAL_TEXT", "{'name': '" + artifact.artifactDisplayName + "'}"); + this.ModalsHandler.openConfirmationModal(title, message, false).then(onOk); + }; + + + this.$scope.getEnvArtifact = (heatArtifact:Models.ArtifactModel):any=>{ + return _.find(this.$scope.artifacts, (item:Models.ArtifactModel)=>{ + return item.generatedFromId === heatArtifact.uniqueId; + }); + }; + + this.$scope.getEnvArtifactName = (artifact:Models.ArtifactModel):string =>{ + let envArtifact = this.$scope.getEnvArtifact(artifact); + if(envArtifact){ + return envArtifact.artifactDisplayName; + } + }; + + this.$scope.isLicenseArtifact = (artifact:Models.ArtifactModel) :boolean => { + let isLicense:boolean = false; + if(this.$scope.component.isResource() && (<Resource>this.$scope.component).isCsarComponent()) { + isLicense = this.artifactsUtils.isLicenseType(artifact.artifactType); + } + + return isLicense; + }; + + this.$scope.openEditEnvParametersModal = (artifact:Models.ArtifactModel):void => { + + let modalOptions:ng.ui.bootstrap.IModalSettings = { + template: this.$templateCache.get('/app/scripts/view-models/forms/env-parameters-form/env-parameters-form.html'), + controller: 'Sdc.ViewModels.EnvParametersFormViewModel', + size: 'sdc-md', + backdrop: 'static', + resolve: { + artifact: ():Models.ArtifactModel => { + return artifact; + }, + component: (): Models.Components.Component => { + return this.$scope.currentComponent; + } + } + }; + + let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$modal.open(modalOptions); + modalInstance + .result + .then(():void => { + this.initArtifactArr(this.$scope.artifactType); + }); + }; + + } + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view.html new file mode 100644 index 0000000000..8c0138964f --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view.html @@ -0,0 +1,55 @@ +<perfect-scrollbar class="w-sdc-designer-sidebar-tab-content artifacts"> + <div class="w-sdc-designer-sidebar-section"> + <expand-collapse + expanded-selector=".w-sdc-designer-sidebar-section-content" class="w-sdc-designer-sidebar-section-title"> + <span class="w-sdc-designer-sidebar-section-title-text" data-ng-bind="getTitle()" tooltips tooltip-content="{{getTitle()}}"></span> + <div class="w-sdc-designer-sidebar-section-title-icon"></div> + </expand-collapse> + + <div class="w-sdc-designer-sidebar-section-content" data-ng-hide="'deployment' == artifactType && !selectedComponent.isComplex()"> + <div class="i-sdc-designer-sidebar-section-content-item"> + <div class="i-sdc-designer-sidebar-section-content-item-artifact" + data-ng-repeat="artifact in artifacts | orderBy: ['-mandatory', 'artifactDisplayName'] track by $index" data-ng-if="!isComponentInstanceSelected() || (isVFiArtifact(artifact)|| artifact.esId) && 'HEAT_ENV' !== artifact.artifactType"> + <span data-ng-if="isComponentInstanceSelected() && artifact.heatParameters.length" class="i-sdc-designer-sidebar-section-content-item-file-link"></span> + <div class="i-sdc-designer-sidebar-section-content-item-artifact-details" data-ng-class="{'heat':artifact.isHEAT() && artifact.heatParameters.length}"> + <div class="i-sdc-designer-sidebar-section-content-item-artifact-filename" data-tests-id="artifactName" + data-ng-class="{'hand enabled':!isComponentInstanceSelected() && artifact.heatParameters.length && !isViewMode()}" + data-ng-bind="artifact.artifactName" tooltips tooltip-content="{{artifact.artifactName}}" + data-ng-click="!isViewMode() && !isComponentInstanceSelected() && artifact.heatParameters.length && openEditEnvParametersModal(artifact)" data-ng-if="artifact.artifactName"></div> + <div> + <span class="i-sdc-designer-sidebar-section-content-item-artifact-details-name" data-tests-id="artifact_Display_Name-{{artifact.artifactDisplayName}}" + data-ng-class="{'hand enabled': (!isComponentInstanceSelected()||isVFiArtifact(artifact)) && !isViewMode() && !artifact.isHEAT() && !artifact.isThirdParty() && !isLicenseArtifact(artifact)}" + data-ng-bind="artifact.artifactDisplayName" data-ng-click="!isViewMode() && !isLoading && (!isComponentInstanceSelected()||isVFiArtifact(artifact)) && !artifact.isHEAT() && !artifact.isThirdParty() && !isLicenseArtifact(artifact) && addOrUpdate(artifact)" + tooltips tooltip-content="{{artifact.artifactDisplayName}}"></span> + <div class="i-sdc-designer-sidebar-section-content-item-artifact-heat-env" ng-if="isComponentInstanceSelected() && artifact.heatParameters.length"> + <span class="enabled" data-ng-bind="getEnvArtifactName(artifact)" data-ng-click="!isViewMode() && addOrUpdate(getEnvArtifact(artifact))"></span> + <download-artifact class="i-sdc-designer-sidebar-section-content-item-button download-env sprite e-sdc-small-download hand" artifact="getEnvArtifact(artifact)" + component="currentComponent" instance="true" + data-tests-id="download"></download-artifact> + </div> + </div> + + <div class="i-sdc-designer-sidebar-section-content-item-artifact-details-desc"> + <span class="i-sdc-designer-sidebar-section-content-item-artifact-details-desc-label" data-ng-show="artifact.description">Description:</span>{{artifact.description}} + </div> + </div> + <button ng-if="!isViewMode() && artifact.esId && (!isComponentInstanceSelected()||isVFiArtifact(artifact)) && !artifact.isHEAT() && !artifact.isThirdParty() && !isLicenseArtifact(artifact)" class="i-sdc-designer-sidebar-section-content-item-button delete sprite e-sdc-small-icon-delete" + data-tests-id="delete" data-ng-click="delete(artifact)" type="button"></button> + <button ng-if="!isViewMode() && isComponentInstanceSelected() && (getEnvArtifact(artifact)).heatParameters.length" + class="i-sdc-designer-sidebar-section-content-item-button attach sprite e-sdc-small-icon-pad" + data-ng-click="openEditEnvParametersModal(getEnvArtifact(artifact))" type="button"></button> + <download-artifact ng-if="artifact.esId && 'deployment' != artifactType" class="i-sdc-designer-sidebar-section-content-item-button download sprite e-sdc-small-download hand" + artifact="artifact" component="selectedComponent" data-tests-id="download"></download-artifact> + <download-artifact ng-if="artifact.esId && 'deployment' == artifactType" class="i-sdc-designer-sidebar-section-content-item-button download sprite e-sdc-small-download hand" + artifact="artifact" component="currentComponent" instance="isComponentInstanceSelected()" data-tests-id="download"></download-artifact> + <button ng-if="!isViewMode() && !artifact.esId && artifactType==='deployment' && !isComponentInstanceSelected() && !artifact.isThirdParty()" class="i-sdc-designer-sidebar-section-content-item-button attach sprite e-sdc-small-icon-upload" + data-ng-click="addOrUpdate(artifact)" type="button" data-tests-id="add_Artifact"></button> + </div> + </div> + + </div> + <div class="w-sdc-designer-sidebar-section-footer" data-ng-if="!isViewMode() && artifactType!=='api' && (!isComponentInstanceSelected()||selectedComponent.resourceType=='VF') && !currentComponent.isProduct() && ('deployment' != artifactType || selectedComponent.isComplex())"> + <button class="w-sdc-designer-sidebar-section-footer-action tlv-btn blue" data-tests-id="add_Artifact_Button" data-ng-click="addOrUpdate({})" type="button">Add Artifact</button> + </div> + </div> +</perfect-scrollbar> diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/artifacts/artifacts.less b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/artifacts/artifacts.less new file mode 100644 index 0000000000..5726ca66fc --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/artifacts/artifacts.less @@ -0,0 +1,172 @@ +.w-sdc-designer-sidebar-tab-content.artifacts { + + .i-sdc-designer-sidebar-section-content-item-artifact.hand { + .hand; + } + + .w-sdc-designer-sidebar-section-content { + padding: 0; + } + .w-sdc-designer-sidebar-section-title { + &.expanded { + margin-bottom: 0; + } + } + + .i-sdc-designer-sidebar-section-content-item-artifact-details { + display: inline-block; + margin-left: 5px; + vertical-align: middle; + width: 180px; + &.heat { + line-height: 18px; + width: 250px; + } + } + + .i-sdc-designer-sidebar-section-content-item-artifact-details-name { + .g_7; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width:220px; + display: inline-block; + //text-transform: capitalize; + &.enabled { + &:hover { + .a_7; + } + } + + } + + .i-sdc-designer-sidebar-section-content-item-artifact-heat-env { + .g_7; + margin-top: 6px; + line-height: 42px; + padding-top: 10px; + border-top:1px solid #c8cdd1; + .enabled { + &:hover { + .hand; + .a_7; + } + } + } + + .i-sdc-designer-sidebar-section-content-item-artifact-filename { + .g_7; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 225px; + display: inline-block; + .bold; + &.enabled { + &:hover { + .a_7; + } + } + } + + + .i-sdc-designer-sidebar-section-content-item-file-link{ + border-left: 1px #848586 solid; + height: 58px; + margin-left: -11px; + margin-top: 11px; + border-top: 1px #848586 solid; + border-bottom: 1px #848586 solid; + width: 12px; + float: left; + } + + .i-sdc-designer-sidebar-section-content-item-artifact-details-desc { + display: none; + line-height: 16px; + word-wrap: break-word; + white-space: normal; + } + + .i-sdc-designer-sidebar-section-content-item-artifact-details-desc-label { + .b_3; + } + + + .i-sdc-designer-sidebar-section-content-item-artifact { + border-bottom: 1px solid #c8cdd1; + padding: 5px 10px 5px 18px; + position: relative; + // line-height: 36px; + min-height: 61px; + cursor: default; + display: flex; + align-items: center; + + + .i-sdc-designer-sidebar-section-content-item-button { + top: 20px; + line-height: 10px; + } + + &:hover { + //background-color: @color_c; + .bg_c; + transition: all .3s; + + .i-sdc-designer-sidebar-section-content-item-button { + display: block; + + } + + } + } + +} + +///////////////////Lifecycle Management +.i-sdc-designer-sidebar-section-content-item-lm { + .b_7; + border-bottom: 1px solid @color_e; + cursor: pointer; + height: 65px; + padding: 22px 0; + position: relative; + + &:hover { + .bg_c_hover; + margin-left: -10px; + margin-right: -10px; + padding: 22px 10px; + + .i-sdc-designer-sidebar-section-content-item-lm-icon { + right: 16px; + } + } +} + +.i-sdc-designer-sidebar-section-content-item-lm:first-child { + margin-top: -18px; +} + +.i-sdc-designer-sidebar-section-content-item-lm-icon { + position: absolute; + right: 6px; + + //TODO: Replace the icons. + &.icon-view { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAJCAYAAAACTR1pAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMDE0IDc5LjE1Njc5NywgMjAxNC8wOC8yMC0wOTo1MzowMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjdGMDNBRUJDMDkxNjExRTVCMjRBOEI5QzMxQTlBQjY4IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjdGMDNBRUJEMDkxNjExRTVCMjRBOEI5QzMxQTlBQjY4Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6N0YwM0FFQkEwOTE2MTFFNUIyNEE4QjlDMzFBOUFCNjgiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6N0YwM0FFQkIwOTE2MTFFNUIyNEE4QjlDMzFBOUFCNjgiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4U2decAAABRUlEQVR42pyRPUvDUBSG39t2UyyaBgvWQZMqKoFq4lBH0U0UqXSxKPaPieCoWPAPKIgdjG1ohaghOrSR1iaLKMXpmnMhQVfPcLmc8zxwPhjnHFE4rx3eMO/w4rr4+vwQuZHRMcwqClaMVeRnplnEskg8O7/g96YJSZLgBz5+R0bKIAgC6IaBvdKukBP0HJ+cctu2cVCtQsrIUFUVlcp+LE6EItWIIVaIV9c3/PnRRqlcxkJeYV2vi+/hEO/9QSx6b56oEUMsOal6/TacQcXinCpaGE+n0QnljufFIuUoiAlZTk6iWFyD6zpoWi3RwvrmBlLJZCzRn3IUxBBLjlgO9e06T9ja3sGyrqPX66NtWQLWCgVks5Owmg1c1mpQ8vM4OqywP1s1w1PkpnJY0jTIsizy/sDHQ7sFmt0ITxJtlf33jj8CDADhB52tEX6ifAAAAABJRU5ErkJggg=='); + height: 9px; + top: 29px; + width: 14px; + } + + //TODO: Replace the icons. + &.icon-alert { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAANCAYAAAB2HjRBAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMDE0IDc5LjE1Njc5NywgMjAxNC8wOC8yMC0wOTo1MzowMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjhBM0YxQTBCMDkyMDExRTVBNzlCQUYxNEYwMDUwOTQ5IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjhBM0YxQTBDMDkyMDExRTVBNzlCQUYxNEYwMDUwOTQ5Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6OEEzRjFBMDkwOTIwMTFFNUE3OUJBRjE0RjAwNTA5NDkiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6OEEzRjFBMEEwOTIwMTFFNUE3OUJBRjE0RjAwNTA5NDkiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7exgceAAAB5klEQVR42oySy2tTQRTGfzN3cpvbpFTaUlzUjeCuVFyJf4DuCl34wL/CrbpUunLnwpUuFFy48IFiwYWhpYuW+gCLrbWKBQumaRIba5ub3MccJ5WECCV44MDMme873/lmBhGhnZulcn/3/sfrO7J/45R017pT0xUS2bC93vy2LAOLt5DfCeUn14RD4h/ysbHRDii/8pyo7DmFiP7VZ4Rbu9KT3I7i+qJklp7ibQt2J6HeNOhXV/gv8tDcbaLtXYLJKeTcZfxqSPPzvGu6ID3J1Q8z0ny/hIkM5swF8qenUFUfwixHCtd7Kwez04jOgEmc+hq6tuZ8N9E6xG5U+Pn2kRxKLs7eE7tRc0XlToRM2sD+2sGmDiY+kWsSFG528Kr1Xgfjfn0n5uElvFrrzVqVDHqkiTKjxCsV9LBx08QkgcY/eZH+89Oqo+x9eumkwQaQaoPEKWpsHHN8ArItgLt5UdiG6736mFLxixyQt+bviik8QPuaNFRIkjqQRZ2YJDo6QWydjTRtOcGPPdJKgnlxFfN9fVmGNhecR4U3mLrL6nPTRTQHXZO5+4jyyI4Iqs99GJvgBiKjcySlj3897y6/kbreI4w0QS7ASxrsa4/cXkxkIzK5PKmNqRMw7NfdeczA+Fn1R4ABAPnMAeCjkgf5AAAAAElFTkSuQmCC'); + height: 13px; + top: 27px; + width: 15px; + } + +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/details/details-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/details/details-view-model.ts new file mode 100644 index 0000000000..b28de8d331 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/details/details-view-model.ts @@ -0,0 +1,132 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../../../../references"/> +module Sdc.ViewModels { + + 'use strict'; + + export interface IEditResourceVersion { + allVersions: any; + changeVersion: string; + } + + interface IDetailsViewModelScope extends ICompositionViewModelScope { + isLoading: boolean; + $parent: ICompositionViewModelScope; + expandedSection: Array<string>; + editForm:ng.IFormController; + editResourceVersion: IEditResourceVersion; + + changeResourceVersion(): void; + } + + export class DetailsViewModel { + + static '$inject' = [ + '$scope', + 'LeftPaletteLoaderService', + 'EventListenerService' + + ]; + + constructor(private $scope:IDetailsViewModelScope, + private LeftPaletteLoaderService:Services.Components.LeftPaletteLoaderService, + private eventListenerService:Services.EventListenerService) { + this.initScope(); + } + + private clearSelectedVersion = ():void => { + this.$scope.editResourceVersion = { + allVersions: {}, + changeVersion: null + }; + }; + + private versioning:Function = (versionNumber:string):string => { + let version:Array<string> = versionNumber.split('.'); + return '00000000'.slice(version[0].length) + version[0] + '.' + '00000000'.slice(version[1].length) + version[1]; + }; + + private initEditResourceVersion = ():void => { + this.clearSelectedVersion(); + this.$scope.editResourceVersion.allVersions[this.$scope.currentComponent.selectedInstance.componentVersion] = this.$scope.currentComponent.selectedInstance.componentUid; + _.merge(this.$scope.editResourceVersion.allVersions, angular.copy(this.$scope.selectedComponent.allVersions)); + let sorted:any= _.sortBy(_.toPairs(this.$scope.editResourceVersion.allVersions), (item)=>{ + return this.versioning(item[0]); + }); + this.clearSelectedVersion(); + _.forEach(sorted, (item)=> { + this.$scope.editResourceVersion.allVersions[item[0]]= item[1]; + }); + + let highestVersion = _.last(Object.keys(this.$scope.selectedComponent.allVersions)); + + //TODO - ask ronny - what happend if the parent is not in the leftPalette (instance of csar for example) + if (parseFloat(highestVersion) % 1) { //if highest is minor, make sure it is the latest checked in - + let latestVersionComponent:Models.Components.Component = _.find(this.LeftPaletteLoaderService.getFullDataComponentListWithVls(this.$scope.currentComponent.componentType), (component:Models.Components.Component) => { //latest checked in + return (component.systemName === this.$scope.selectedComponent.systemName + || component.uuid === this.$scope.selectedComponent.uuid); + }); + let latestVersion:string = latestVersionComponent ? latestVersionComponent.version : highestVersion; + + if (highestVersion != latestVersion) { //highest is checked out - remove from options + this.$scope.editResourceVersion.allVersions = _.omit(this.$scope.editResourceVersion.allVersions, highestVersion); + } + } + this.$scope.editResourceVersion.changeVersion = this.$scope.currentComponent.selectedInstance.componentVersion; + }; + + private initScope = ():void => { + this.$scope.isLoading = false; + this.$scope.$parent.isLoading = false; + this.$scope.expandedSection = ['general', 'tags']; + //this.clearSelectedVersion(); + + this.$scope.$watch('selectedComponent', (component:Models.Components.Component) => { + if (this.$scope.isComponentInstanceSelected()) { + this.initEditResourceVersion(); + } + }); + + this.$scope.changeResourceVersion = ():void => { + this.$scope.isLoading = true; + this.$scope.$parent.isLoading = true; + + let onSuccess = (component:Models.Components.Component)=> { + this.$scope.isLoading = false; + this.$scope.$parent.isLoading = false; + this.$scope.setComponent(component); + this.$scope.updateSelectedComponent(); + + this.eventListenerService.notifyObservers(Sdc.Utils.Constants.GRAPH_EVENTS.ON_VERSION_CHANGED, this.$scope.currentComponent); + }; + + let onFailed = (error:any)=> { + this.$scope.isLoading = false; + this.$scope.$parent.isLoading = false; + console.log(error); + }; + + let componentUid:string = this.$scope.editResourceVersion.allVersions[this.$scope.editResourceVersion.changeVersion]; + this.$scope.currentComponent.changeComponentInstanceVersion(componentUid).then(onSuccess, onFailed); + }; + } + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/details/details-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/details/details-view.html new file mode 100644 index 0000000000..6ae462760c --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/details/details-view.html @@ -0,0 +1,129 @@ +<perfect-scrollbar include-padding="true" class="w-sdc-designer-sidebar-tab-content details"> + + <div class="w-sdc-designer-sidebar-section"> + <loader data-display="isLoading"></loader> + <expand-collapse expanded-selector=".w-sdc-designer-sidebar-section-content.general" class="w-sdc-designer-sidebar-section-title"> + + General Info + <div class="w-sdc-designer-sidebar-section-title-icon"></div> + </expand-collapse> + + <div class="w-sdc-designer-sidebar-section-content general"> + <div class="i-sdc-designer-sidebar-section-content-item"> + <span class="i-sdc-designer-sidebar-section-content-item-label">Type:</span> + <span class="i-sdc-designer-sidebar-section-content-item-value" data-tests-id="rightTab_componentType" data-ng-bind="selectedComponent.componentType"></span> + </div> + <div class="i-sdc-designer-sidebar-section-content-item" data-ng-if="selectedComponent.isResource()"> + <span class="i-sdc-designer-sidebar-section-content-item-label">Resource Type:</span> + <span class="i-sdc-designer-sidebar-section-content-item-value" data-ng-if="selectedComponent.isResource()" data-ng-bind="selectedComponent.resourceType" + tooltips tooltip-content="​{{selectedComponent.resourceType | resourceTypeName}}" + data-tests-id="rightTab_resourceType"></span> + </div> + <div class="i-sdc-designer-sidebar-section-content-item"> + + <span class="i-sdc-designer-sidebar-section-content-item-label">Version:</span> + <span class="i-sdc-designer-sidebar-section-content-item-value" + data-ng-if="!isComponentInstanceSelected() || selectedComponent.isVl()" data-tests-id="rightTab_version" data-ng-bind="selectedComponent.version"></span> + + <ng-form name="editForm" data-ng-if="isComponentInstanceSelected() && !selectedComponent.isVl()"> + <select data-ng-model="editResourceVersion.changeVersion" name="changeVersion" data-ng-disabled="$parent.isViewOnly" + class="i-sdc-designer-sidebar-section-content-item-value i-sdc-form-select" + data-ng-class="{'minor': (editResourceVersion.changeVersion)%1}" + data-ng-change="changeResourceVersion()"> + <option class="select-instance-version" data-ng-class="{'minor': key%1}" + ng-repeat="(key, value) in editResourceVersion.allVersions">{{key}}</option> + </select></ng-form> + </div> + <div class="i-sdc-designer-sidebar-section-content-item" data-ng-if="selectedComponent.categories && selectedComponent.categories[0]"> + <span class="i-sdc-designer-sidebar-section-content-item-label">Category:</span> + <span class="i-sdc-designer-sidebar-section-content-item-value" data-ng-bind="selectedComponent.categories[0].name" + tooltips tooltip-content="​{{selectedComponent.categories[0].name}}" + data-tests-id="rightTab_category"></span> + </div> + <div class="i-sdc-designer-sidebar-section-content-item" data-ng-if="selectedComponent.categories && selectedComponent.categories[0] && selectedComponent.categories[0].subcategories"> + <span class="i-sdc-designer-sidebar-section-content-item-label">Sub Category:</span> + <span class="i-sdc-designer-sidebar-section-content-item-value" data-ng-bind="selectedComponent.categories[0].subcategories[0].name" + tooltips tooltip-content="​{{selectedComponent.categories[0].subcategories[0].name}}" + data-tests-id="rightTab_subCategory"></span> + </div> + <div class="i-sdc-designer-sidebar-section-content-item"> + <span class="i-sdc-designer-sidebar-section-content-item-label">Creation Date:</span> + <span class="i-sdc-designer-sidebar-section-content-item-value" data-ng-bind="selectedComponent.creationDate | date: 'MM/dd/yyyy'" + data-tests-id="rightTab_creationDate"></span> + </div> + <div class="i-sdc-designer-sidebar-section-content-item"> + <span class="i-sdc-designer-sidebar-section-content-item-label">Author:</span> + <span class="i-sdc-designer-sidebar-section-content-item-value" data-ng-bind="selectedComponent.creatorFullName" + tooltips tooltip-content="​{{selectedComponent.creatorFullName}}" + data-tests-id="rightTab_author"> + </span> + </div> + <div class="i-sdc-designer-sidebar-section-content-item" data-ng-if="selectedComponent.isService()"> + <span class="i-sdc-designer-sidebar-section-content-item-label" translate="GENERAL_LABEL_PROJECT_CODE"></span> + <span class="i-sdc-designer-sidebar-section-content-item-value" + data-tests-id="rightTab_projectCode" data-ng-bind="selectedComponent.projectCode"></span> + </div> + <div class="i-sdc-designer-sidebar-section-content-item" data-ng-if="selectedComponent.isResource()"> + <span class="i-sdc-designer-sidebar-section-content-item-label">Vendor Name:</span> + <span class="i-sdc-designer-sidebar-section-content-item-value" data-ng-bind="selectedComponent.vendorName" + tooltips tooltip-content="​{{selectedComponent.vendorName}}" + data-tests-id="rightTab_vendorName"> + </span> + </div> + <div class="i-sdc-designer-sidebar-section-content-item" data-ng-if="selectedComponent.isResource()"> + <span class="i-sdc-designer-sidebar-section-content-item-label">Vendor Release:</span> + <span class="i-sdc-designer-sidebar-section-content-item-value" data-ng-bind="selectedComponent.vendorRelease" + tooltips tooltip-class="tooltip-custom break-word-tooltip" tooltip-content="​{{selectedComponent.vendorRelease}}" + data-tests-id="rightTab_vendorRelease"> + </span> + </div> + <div class="i-sdc-designer-sidebar-section-content-item"> + <span class="i-sdc-designer-sidebar-section-content-item-label" translate="GENERAL_LABEL_CONTACT_ID"></span> + <span class="i-sdc-designer-sidebar-section-content-item-value" data-ng-bind="selectedComponent.contactId" + data-tests-id="rightTab_userId"></span> + </div> + <div class="i-sdc-designer-sidebar-section-content-item description"> + <span class="i-sdc-designer-sidebar-section-content-item-label">Description: + + <span class="i-sdc-designer-sidebar-section-content-description-item-value" ellipsis="selectedComponent.description" max-chars="55" + data-tests-id="rightTab_description"></span> + </span> + </div> + + </div> + </div> + + <div class="w-sdc-designer-sidebar-section additionalInformation"> + <expand-collapse expanded-selector=".w-sdc-designer-sidebar-section-content.additionalInformation" class="w-sdc-designer-sidebar-section-title"> + Additional Information + <div class="w-sdc-designer-sidebar-section-title-icon"></div> + </expand-collapse> + + <div class="w-sdc-designer-sidebar-section-content additionalInformation"> + <div class="i-sdc-designer-sidebar-section-content-item" data-ng-repeat="additionalInformation in selectedComponent.getAdditionalInformation() track by $index"> + <span class="i-sdc-designer-sidebar-section-content-item-label additional-information" data-ng-bind="additionalInformation.key" tooltips tooltip-content="{{additionalInformation.key}}"></span> + <span class="i-sdc-designer-sidebar-section-content-item-label">:</span> + <span class="i-sdc-designer-sidebar-section-content-item-value additional-information" data-ng-bind="additionalInformation.value" + tooltips tooltip-class="tooltip-custom break-word-tooltip" tooltip-content="{{additionalInformation.value}}"></span> + </div> + </div> + </div> + + + <div class="w-sdc-designer-sidebar-section tags"> + <expand-collapse expanded-selector=".w-sdc-designer-sidebar-section-content.tags" class="w-sdc-designer-sidebar-section-title"> + Tags + <div class="w-sdc-designer-sidebar-section-title-icon"></div> + </expand-collapse> + + <div class="w-sdc-designer-sidebar-section-content tags"> + <div class="i-sdc-designer-sidebar-section-content-item"> + <span class="i-sdc-designer-sidebar-section-content-item-tag" data-ng-repeat="tag in selectedComponent.tags track by $index" data-ng-bind="tag" + data-tests-id="rightTab_tag" tooltips tooltip-content="{{tag}}"></span> + </div> + </div> + </div> + </div> + +</perfect-scrollbar> + diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/details/details.less b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/details/details.less new file mode 100644 index 0000000000..e88e130379 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/details/details.less @@ -0,0 +1,68 @@ +.w-sdc-designer-sidebar-tab-content.details { + + .w-sdc-designer-sidebar-section-title + .w-sdc-designer-sidebar-section-content { + padding: 0 10px 0 18px; + } + + .w-sdc-designer-sidebar-section-title.expanded + .w-sdc-designer-sidebar-section-content { + padding: 10px 10px 10px 18px; + } + + .i-sdc-designer-sidebar-section-content-item-label { + font-weight: bold; + &.additional-information{ + max-width:100px; + display: inline-block; + text-overflow: ellipsis; + overflow: hidden; + vertical-align: bottom; + } + + } + + + + .i-sdc-designer-sidebar-section-content-item-value { + // .hyphenate; + padding-left: 10px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: inline-block; + max-width: 160px; + vertical-align:bottom; + font-weight: normal; + &.additional-information{ + max-width:160px; + display: inline-block; + } + &.i-sdc-form-select { + .b_1; + border: 1px solid @border_color_f; + width: 210px; + max-width: 210px; + padding-left: 4px; + + .select-instance-version { + .b_1; + &.minor { + .h_1; + } + } + } + &.minor { + .h_1; + } + } + .i-sdc-designer-sidebar-section-content-description-item-value{ + max-width: none; + font-weight: normal; + } + + .w-sdc-designer-sidebar-section.tags { + .i-sdc-designer-sidebar-section-content-item { + white-space: normal; + } + } + +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view-model.ts new file mode 100644 index 0000000000..aef25c51ce --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view-model.ts @@ -0,0 +1,228 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../../../../references"/> +module Sdc.ViewModels { + 'use strict'; + + interface IResourcePropertiesAndAttributesViewModelScope extends ICompositionViewModelScope { + properties: Models.PropertiesGroup; + attributes: Models.AttributesGroup; + propertiesMessage: string; + addProperty(): void; + updateProperty(property:Models.PropertyModel): void; + deleteProperty(property:Models.PropertyModel): void; + viewAttribute(attribute:Models.AttributeModel): void; + groupNameByKey(key:string): string; + isPropertyOwner():boolean; + } + + export class ResourcePropertiesViewModel { + + static '$inject' = [ + '$scope', + '$filter', + '$modal', + '$templateCache', + 'ModalsHandler' + ]; + + + constructor(private $scope:IResourcePropertiesAndAttributesViewModelScope, + private $filter:ng.IFilterService, + private $modal:ng.ui.bootstrap.IModalService, + private $templateCache:ng.ITemplateCacheService, + private ModalsHandler: Utils.ModalsHandler) { + + this.initScope(); + } + + private initComponentProperties = ():void => { + let result:Models.PropertiesGroup = {}; + + if(this.$scope.selectedComponent){ + this.$scope.propertiesMessage = undefined; + if(this.$scope.isComponentInstanceSelected()){ + if (this.$scope.currentComponent.selectedInstance.originType==='VF') { + // Temporally fix to hide properties for VF (UI stack when there are many properties) + this.$scope.propertiesMessage = "Note: properties for VF are disabled"; + } else { + result[this.$scope.currentComponent.selectedInstance.uniqueId] = this.$scope.currentComponent.componentInstancesProperties[this.$scope.currentComponent.selectedInstance.uniqueId]; + } + }else if(this.$scope.currentComponent.isService()){ + // Temporally fix to hide properties for service (UI stack when there are many properties) + //result = this.$scope.currentComponent.componentInstancesProperties; + this.$scope.propertiesMessage = "Note: properties for service are disabled"; + }else{ + let key = this.$scope.selectedComponent.uniqueId; + result[key]= Array<Models.PropertyModel>(); + let derived = Array<Models.PropertyModel>(); + _.forEach(this.$scope.selectedComponent.properties, (property:Models.PropertyModel) => { + if(key == property.parentUniqueId){ + result[key].push(property); + }else{ + property.readonly = true; + derived.push(property); + } + }); + if(derived.length){ + result['derived']= derived; + } + } + this.$scope.properties = result; + } + }; + + + private initComponentAttributes = ():void => { + let result:Models.AttributesGroup = {}; + + if(this.$scope.selectedComponent){ + if(this.$scope.isComponentInstanceSelected()){ + result[this.$scope.currentComponent.selectedInstance.uniqueId] = this.$scope.currentComponent.componentInstancesAttributes[this.$scope.currentComponent.selectedInstance.uniqueId]; + }else if(this.$scope.currentComponent.isService()){ + result = this.$scope.currentComponent.componentInstancesAttributes; + } + this.$scope.attributes = result; + } + }; + + private openEditPropertyModal = (property:Models.PropertyModel):void => { + let viewModelsHtmlBasePath:string = '/app/scripts/view-models/'; + + let modalOptions:ng.ui.bootstrap.IModalSettings = { + template: this.$templateCache.get(viewModelsHtmlBasePath + 'forms/property-form/property-form-view.html'), + controller: 'Sdc.ViewModels.PropertyFormViewModel', + size: 'sdc-l', + backdrop: 'static', + keyboard: false, + resolve: { + property: ():Models.PropertyModel => { + return property; + }, + component: ():Models.Components.Component => { + return this.$scope.currentComponent; + }, + filteredProperties: ():Array<Models.PropertyModel> => { + return this.$scope.selectedComponent.properties + } + } + }; + + + let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$modal.open(modalOptions); + modalInstance + .result + .then(():void => { + // this.initComponentProperties(); + }); + }; + + private openAttributeModal = (atrribute:Models.AttributeModel):void => { + let viewModelsHtmlBasePath:string = '/app/scripts/view-models/'; + + let modalOptions:ng.ui.bootstrap.IModalSettings = { + template: this.$templateCache.get(viewModelsHtmlBasePath + 'forms/attribute-form/attribute-form-view.html'), + controller: 'Sdc.ViewModels.AttributeFormViewModel', + size: 'sdc-md', + backdrop: 'static', + keyboard: false, + resolve: { + attribute: ():Models.AttributeModel => { + return atrribute; + }, + component: ():Models.Components.Component => { + return this.$scope.currentComponent; + } + } + }; + this.$modal.open(modalOptions); + }; + + + + + private initScope = ():void => { + this.initComponentProperties(); + this.initComponentAttributes(); + + this.$scope.$watchCollection('currentComponent.componentInstancesProperties', (newData:any):void => { + this.initComponentProperties(); + }); + + this.$scope.$watchCollection('currentComponent.properties', (newData:any):void => { + this.initComponentProperties(); + }); + + this.$scope.$watch('currentComponent.selectedInstance', (newInstance:Models.ComponentsInstances.ComponentInstance):void => { + if (angular.isDefined(newInstance)) { + this.initComponentProperties(); + this.initComponentAttributes(); + } + }); + + this.$scope.$watchCollection('currentComponent.componentInstancesAttributes', (newData:any):void => { + this.initComponentAttributes(); + }); + + this.$scope.isPropertyOwner = ():boolean => { + return this.$scope.currentComponent && this.$scope.currentComponent.isResource() && + !this.$scope.isComponentInstanceSelected(); + }; + + this.$scope.addProperty = ():void => { + let property = new Models.PropertyModel(); + this.openEditPropertyModal(property); + }; + + this.$scope.updateProperty = (property:Models.PropertyModel):void => { + this.openEditPropertyModal(property); + }; + + this.$scope.deleteProperty = (property:Models.PropertyModel):void => { + + let onOk = ():void => { + this.$scope.currentComponent.deleteProperty(property.uniqueId); + }; + + let title:string = this.$filter('translate')("PROPERTY_VIEW_DELETE_MODAL_TITLE"); + let message:string = this.$filter('translate')("PROPERTY_VIEW_DELETE_MODAL_TEXT", "{'name': '" + property.name + "'}"); + this.ModalsHandler.openConfirmationModal(title, message, false).then(onOk); + }; + + this.$scope.viewAttribute = (attribute:Models.AttributeModel):void => { + this.openAttributeModal(attribute); + }; + + this.$scope.groupNameByKey = (key:string):string => { + switch (key){ + case 'derived': + return "Derived"; + + case this.$scope.currentComponent.uniqueId: + return this.$filter("resourceName")(this.$scope.currentComponent.name); + + default: + return this.$filter("resourceName")((_.find(this.$scope.currentComponent.componentInstances, {uniqueId:key})).name); + } + }; + + } + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view.html new file mode 100644 index 0000000000..3022ee6e90 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view.html @@ -0,0 +1,81 @@ +<perfect-scrollbar class="w-sdc-designer-sidebar-tab-content properties"> + <div class="w-sdc-designer-sidebar-section"> + + <!--expand-collapse data-ng-if="isPropertyOwner() && !currentComponent.properties.length" expanded-selector=".w-sdc-composition-sidebar-section-content.{{currentComponent.name}}" + class="w-sdc-composition-sidebar-section-title"> + <span class="w-sdc-composition-sidebar-section-title-text" tooltips tooltip-content="{{currentComponent.name | resourceName}} Properties" + data-ng-bind="(currentComponent.name | resourceName)+ ' Properties'"></span> + <div class="w-sdc-composition-sidebar-section-title-icon"></div> + </expand-collapse--> + + + <!--properties--> + <expand-collapse data-ng-repeat-start="(key, group) in properties" + expanded-selector=".w-sdc-designer-sidebar-section-content.properties.{{$index}}" class="w-sdc-designer-sidebar-section-title"> + <span class="w-sdc-designer-sidebar-section-title-text" data-ng-bind="groupNameByKey(key) + ' Properties'" + tooltips tooltip-content="{{groupNameByKey(key)}} Properties"></span> + <div class="w-sdc-designer-sidebar-section-title-icon"></div> + </expand-collapse> + + <div data-ng-repeat-end="" class="w-sdc-designer-sidebar-section-content properties {{$index}}"> <!--data-ng-show="isShowDetailsSection" --> + <div class="i-sdc-designer-sidebar-section-content-item"> + <div class="i-sdc-designer-sidebar-section-content-item-property-and-attribute" data-tests-id="propertyRow" + data-ng-repeat="property in group | orderBy: 'name' track by $index"> + <div> + <span class="i-sdc-designer-sidebar-section-content-item-property-and-attribute-label" + data-ng-class="{'hand enabled': !$parent.isViewOnly}" + tooltips tooltip-content="{{property.name}}" + data-ng-click="!$parent.isViewOnly && updateProperty(property)">{{property.name}}</span> + </div> + <div> + <span class="i-sdc-designer-sidebar-section-content-item-property-value" data-ng-if="isPropertyOwner()" + tooltips tooltip-content="{{property.defaultValue}}">{{property.defaultValue}}</span> + <span class="i-sdc-designer-sidebar-section-content-item-property-value" data-ng-if="!isPropertyOwner()" + tooltips tooltip-content="{{property.value}}">{{property.value}}</span> + </div> + <button class="i-sdc-designer-sidebar-section-content-item-button delete sprite e-sdc-small-icon-delete" + data-ng-if="!$parent.isViewOnly&&(isPropertyOwner() && !property.readonly)" + data-ng-click="deleteProperty(property)" type="button"></button> + </div> + </div> + + </div> + <div class="w-sdc-designer-sidebar-section-footer"> + <button class="w-sdc-designer-sidebar-section-footer-action tlv-btn blue" data-ng-click="addProperty()" type="button" data-ng-if="!$parent.isViewOnly && isPropertyOwner()"> + Add Property + </button> + </div> + + + <!--attributes--> + <expand-collapse data-ng-repeat-start="(key, group) in attributes" + expanded-selector=".w-sdc-designer-sidebar-section-content.attributes.{{$index}}" class="w-sdc-designer-sidebar-section-title"> + <span class="w-sdc-designer-sidebar-section-title-text" data-ng-bind="groupNameByKey(key) + ' Attributes'" + tooltips tooltip-content="{{groupNameByKey(key)}} Attributes"></span> + <div class="w-sdc-designer-sidebar-section-title-icon"></div> + </expand-collapse> + + <div data-ng-repeat-end="" class="w-sdc-designer-sidebar-section-content attributes {{$index}}"> <!--data-ng-show="isShowDetailsSection" --> + <div class="i-sdc-designer-sidebar-section-content-item"> + <div class="i-sdc-designer-sidebar-section-content-item-property-and-attribute" + data-ng-repeat="attribute in group | orderBy: 'name' track by $index"> + <div> + <span class="i-sdc-designer-sidebar-section-content-item-property-and-attribute-label" + data-ng-class="{'hand enabled': !$parent.isViewOnly}" + tooltips tooltip-content="{{attribute.name}}" + data-ng-click="!$parent.isViewOnly && viewAttribute(attribute)" + data-tests-id="{{attribute.name}}-attr">{{attribute.name}}</span> + </div> + <div> + <span class="i-sdc-designer-sidebar-section-content-item-property-value" data-ng-if="isPropertyOwner()" + tooltips tooltip-content="{{attribute.defaultValue}}">{{attribute.defaultValue}}</span> + <span class="i-sdc-designer-sidebar-section-content-item-property-value" data-ng-if="!isPropertyOwner()" + tooltips tooltip-content="{{attribute.value}}" data-tests-id="value-of-{{attribute.name}}">{{attribute.value}}</span> + </div> + </div> + </div> + + </div> + + </div> +</perfect-scrollbar> diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties.less b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties.less new file mode 100644 index 0000000000..2ad87b9fca --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties.less @@ -0,0 +1,16 @@ +.w-sdc-designer-sidebar-tab-content.properties { + .i-sdc-designer-sidebar-section-content-item-property-and-attribute-label{ + font-weight: bold; + } + .i-sdc-designer-sidebar-section-content-item-button.update{ + right: 17px; + } + .i-sdc-designer-sidebar-section-content-item-button.delete{ + right: 35px; + } + + .w-sdc-designer-sidebar-properties-disabled { + .s_14_m; + padding: 20px 20px; + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/relations/relations-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/relations/relations-view-model.ts new file mode 100644 index 0000000000..119a59d5af --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/relations/relations-view-model.ts @@ -0,0 +1,81 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../../../../references"/>> +module Sdc.ViewModels { + 'use strict'; + + interface IRelationsViewModelScope extends ICompositionViewModelScope { + isLoading: boolean; + $parent: ICompositionViewModelScope; + getRelation(requirement:any): any; + } + + export class RelationsViewModel { + + static '$inject' = [ + '$scope', + '$filter' + ]; + + constructor(private $scope:IRelationsViewModelScope, + private $filter:ng.IFilterService) { + this.initScope(); + } + + + private updateRC = ():void =>{ + if(this.$scope.currentComponent) { + this.$scope.currentComponent.updateRequirementsCapabilities(); + } + }; + + private initScope = ():void => { + + this.$scope.isLoading = this.$scope.$parent.isLoading; + + this.$scope.getRelation = (requirement:any):any => { + + if(this.$scope.isComponentInstanceSelected() && this.$scope.currentComponent.componentInstancesRelations ) { + let relationItem = _.filter(this.$scope.currentComponent.componentInstancesRelations, (relation:any) => { + return relation.fromNode === this.$scope.currentComponent.selectedInstance.uniqueId && + _.some(relation.relationships, {'requirement': requirement.name, + 'requirementOwnerId': requirement.ownerId}); + }); + + if (relationItem && relationItem.length) { + return { + type: requirement.relationship.split('.').pop(), + requirementName: this.$filter('resourceName')(this.$scope.currentComponent.componentInstances[_.map + (this.$scope.currentComponent.componentInstances, "uniqueId").indexOf(relationItem[0].toNode)].name) + }; + } + } + return null; + }; + + if(!this.$scope.isComponentInstanceSelected()) { + this.$scope.$watch('currentComponent.componentInstances + currentComponent.componentInstancesRelations', ():void => { + this.updateRC(); + }); + + } + } + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/relations/relations-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/relations/relations-view.html new file mode 100644 index 0000000000..72eaae27cf --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/relations/relations-view.html @@ -0,0 +1,57 @@ +<perfect-scrollbar class="w-sdc-designer-sidebar-tab-content relations"> + + <div class="w-sdc-designer-sidebar-section w-sdc-designer-sidebar-section-relations"> + <expand-collapse expanded-selector=".w-sdc-designer-sidebar-section-content.capabilities" class="w-sdc-designer-sidebar-section-title"> + Capabilities + <div class="w-sdc-designer-sidebar-section-title-icon"></div> + </expand-collapse> + + <div class="w-sdc-designer-sidebar-section-content capabilities"> + <div class="i-sdc-designer-sidebar-section-content-item"> + <div class="i-sdc-designer-sidebar-section-content-item-relations-group" data-ng-repeat="(key, value) in (isComponentInstanceSelected() ? currentComponent.selectedInstance.capabilities : selectedComponent.capabilities) track by $index"> + <div class="i-sdc-designer-sidebar-section-content-item-relations" data-ng-repeat="capability in value track by $index"> + <div class="i-sdc-designer-sidebar-section-content-item-relations-details"> + <div class="i-sdc-designer-sidebar-section-content-item-relations-details-name">{{capability.name}} + <span ng-if="selectedComponent.isComplex() && capability.ownerName" + class="i-sdc-designer-sidebar-section-content-item-relations-details-ownerName" + tooltips tooltip-class="tooltip-custom break-word-tooltip" + tooltip-content="{{capability.ownerName | resourceName}}"> {{capability.ownerName | resourceName}}</span></div> + <div class="i-sdc-designer-sidebar-section-content-item-relations-details-desc">{{capability.type}}</div> + </div></div> + </div> + </div> + </div> + </div> + + <div class="w-sdc-designer-sidebar-section w-sdc-designer-sidebar-section-relations"> + <expand-collapse expanded-selector=".w-sdc-designer-sidebar-section-content.requirements" class="w-sdc-designer-sidebar-section-title"> + Requirements + <div class="w-sdc-designer-sidebar-section-title-icon"></div> + </expand-collapse> + + <div class="w-sdc-designer-sidebar-section-content requirements"> + <div class="i-sdc-designer-sidebar-section-content-item"> + <div class="i-sdc-designer-sidebar-section-content-item-relations-group" data-ng-repeat="(key, value) in (isComponentInstanceSelected() ? currentComponent.selectedInstance.requirements : selectedComponent.requirements) track by $index"> + <div class="i-sdc-designer-sidebar-section-content-item-relations" data-ng-repeat="requirement in value track by $index"> + <div class="i-sdc-designer-sidebar-section-content-item-relations-details"> + <div class="i-sdc-designer-sidebar-section-content-item-relations-details-name">{{requirement.name}} + <span ng-if="selectedComponent.isComplex() && requirement.ownerName" + class="i-sdc-designer-sidebar-section-content-item-relations-details-ownerName" + tooltips tooltip-class="tooltip-custom break-word-tooltip" + tooltip-content="{{requirement.ownerName | resourceName}}"> {{requirement.ownerName | resourceName}}</span></div> + <div class="i-sdc-designer-sidebar-section-content-item-relations-details-desc">{{requirement.node}} + <div data-ng-if="getRelation(requirement) != null"> + <div class="i-sdc-designer-sidebar-section-content-item-relations-details-indent-box"></div> + <div class="i-sdc-designer-sidebar-section-content-item-relations-details-child"> + <span class="i-sdc-designer-sidebar-section-content-item-relations-details-desc">{{getRelation(requirement).type}} <br/></span> + <span class="i-sdc-designer-sidebar-section-content-item-relations-details-name">{{getRelation(requirement).requirementName}}</span> + </div> + </div> + </div> + </div></div> + </div> + </div> + </div> + </div> + +</perfect-scrollbar> diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/relations/relations.less b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/relations/relations.less new file mode 100644 index 0000000000..212b9785e9 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/relations/relations.less @@ -0,0 +1,116 @@ +.w-sdc-designer-sidebar-tab-content.relations { + + .w-sdc-designer-sidebar-section-content { + padding: 0; + } + + .w-sdc-designer-sidebar-section-title { + &.expanded { + margin-bottom: 0; + } + } + + .i-sdc-designer-sidebar-section-content-item-relations { + border-bottom: 1px solid @color_e; + padding: 10px 10px 10px 18px; + position: relative; + cursor: default; + + .i-sdc-designer-sidebar-section-content-item-button { + top: 15px; + line-height: 10px; + } + + &:hover { + background-color: @color_c; + + .i-sdc-designer-sidebar-section-content-item-button { + display: block; + + } + + } + + } + .w-sdc-designer-sidebar-section-relations:not(:last-child) .i-sdc-designer-sidebar-section-content-item-relations-group:last-child .i-sdc-designer-sidebar-section-content-item-relations:last-child { + border-bottom: none; + } + + + .i-sdc-designer-sidebar-section-content-item-relations.hand { + .hand; + } + + .i-sdc-designer-sidebar-section-content-item-relations-group { + //border-bottom: 1px solid @color_e; + } + + .i-sdc-designer-sidebar-section-content-item-relations-details { + display: inline-block; + margin-left: 5px; + vertical-align: middle; + } + + .i-sdc-designer-sidebar-section-content-item-relations-details-name { + .b_1; + .bold; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + //width: 100%; + text-transform: capitalize; + max-width: 240px; + display: inline-block; + } + + .i-sdc-designer-sidebar-section-content-item-relations-details-ownerName { + .b_13; + font-weight:400; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + &:before{ + .sprite; + .arrow-left; + content: ''; + } + } + + .i-sdc-designer-sidebar-section-content-item-relations-details-desc { + .p_1; + line-height: 14px; + word-wrap: break-word; + white-space: normal; + + .i-sdc-designer-sidebar-section-content-item-relations-details-indent-box{ + border-left: 1px #848586 solid; + height: 40px; + margin-left: 2px; + margin-top: 5px; + border-bottom: 1px #848586 solid; + width: 25px; + float: left; + } + .i-sdc-designer-sidebar-section-content-item-relations-details-child{ + margin-top: 30px; + float: left; + padding-left: 10px; + max-width: 230px; + } + } + + .i-sdc-designer-sidebar-section-content-item-relations-details-desc-label { + font-family: omnes-medium, sans-serif; + } + + .i-sdc-designer-sidebar-section-content-item-relations-view { + position: absolute; + right: 0; + top: 22px; + display: none; + } + +} + + diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/structure/structure-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/structure/structure-view.html new file mode 100644 index 0000000000..2070041990 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/structure/structure-view.html @@ -0,0 +1,13 @@ +<perfect-scrollbar include-padding="true" class="w-sdc-designer-sidebar-tab-content"> + + <div class="w-sdc-designer-sidebar-section"> + <expand-collapse expanded-selector=".w-sdc-designer-sidebar-section-content" class="w-sdc-designer-sidebar-section-title"> + Composition + <div class="w-sdc-designer-sidebar-section-title-icon"></div> + </expand-collapse> + + <div class="w-sdc-designer-sidebar-section-content" ng-show="selectedComponent.isComplex()"> + <structure-tree component="selectedComponent"></structure-tree> + </div> + </div> +</perfect-scrollbar> diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/structure/structure-view.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/structure/structure-view.ts new file mode 100644 index 0000000000..daeab7f2f3 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/structure/structure-view.ts @@ -0,0 +1,34 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../../../../references"/> +module Sdc.ViewModels { + 'use strict'; + + interface IStructureViewModel extends ICompositionViewModelScope {} + + export class StructureViewModel { + static '$inject' = [ + '$scope' + ]; + + constructor(private $scope:IStructureViewModel) { + } + } + } diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts-view-model.ts new file mode 100644 index 0000000000..43511e2deb --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts-view-model.ts @@ -0,0 +1,253 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../../references"/> +module Sdc.ViewModels { + 'use strict'; + import Resource = Sdc.Models.Components.Resource; + import ArtifactModel = Sdc.Models.ArtifactModel; + + interface IDeploymentArtifactsViewModelScope extends IWorkspaceViewModelScope { + tableHeadersList: Array<any>; + reverse: boolean; + sortBy:string; + artifacts: Array<Models.ArtifactModel>; + editForm:ng.IFormController; + isLoading:boolean; + artifactDescriptions:any; + updateInProgress:boolean; + + addOrUpdate(artifact:Models.ArtifactModel): void; + update(artifact:Models.ArtifactModel): void; + delete(artifact:Models.ArtifactModel): void; + sort(sortBy:string): void; + noArtifactsToShow():boolean; + getValidationPattern(validationType:string, parameterType?:string):RegExp; + validateJson(json:string):boolean; + resetValue(parameter:any):void; + viewModeOrCsarComponent():boolean; + isLicenseArtifact(artifact:Models.ArtifactModel): boolean; + + } + + export class DeploymentArtifactsViewModel { + + static '$inject' = [ + '$scope', + '$filter', + '$modal', + '$templateCache', + 'ValidationUtils', + 'ArtifactsUtils', + 'ModalsHandler' + ]; + + constructor(private $scope:IDeploymentArtifactsViewModelScope, + private $filter:ng.IFilterService, + private $modal:ng.ui.bootstrap.IModalService, + private $templateCache:ng.ITemplateCacheService, + private validationUtils:Sdc.Utils.ValidationUtils, + private artifactsUtils:Sdc.Utils.ArtifactsUtils, + private ModalsHandler:Utils.ModalsHandler) { + this.initScope(); + this.$scope.updateSelectedMenuItem(); + } + + private initDescriptions = ():void => { + this.$scope.artifactDescriptions = {}; + _.forEach(this.$scope.component.deploymentArtifacts, (artifact:Models.ArtifactModel):void => { + this.$scope.artifactDescriptions[artifact.artifactLabel] = artifact.description; + }); + }; + + + private setArtifact = (artifact:Models.ArtifactModel):void => { + if (artifact.heatParameters) { + artifact.heatParameters.forEach((parameter:any):void => { + if (!parameter.currentValue && parameter.defaultValue) { + parameter.currentValue = parameter.defaultValue; + } else if ("" === parameter.currentValue) { + parameter.currentValue = null; + } + }); + } + if (!artifact.description || !this.$scope.getValidationPattern('string').test(artifact.description)) { + artifact.description = this.$scope.artifactDescriptions[artifact.artifactLabel]; + } + }; + + private updateAll = ():void => { + let artifacts:Array<Models.ArtifactModel> = []; + _.forEach(this.$scope.component.deploymentArtifacts, (artifact:Models.ArtifactModel):void => { + if (artifact.selected) { + this.setArtifact(artifact); + artifacts.push(artifact); + } + }); + this.$scope.component.updateMultipleArtifacts(artifacts); + }; + + + private initScope = ():void => { + let self = this; + this.$scope.isLoading = false; + this.$scope.updateInProgress = false; + this.initDescriptions(); + this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.deploymentArtifacts); + this.$scope.setValidState(true); + + this.$scope.tableHeadersList = [ + {title: 'Name', property: 'artifactDisplayName'}, + {title: 'Type', property: 'artifactType'}, + {title: 'Deployment timeout', property: 'timeout'} + ]; + + this.$scope.isLicenseArtifact = (artifact:Models.ArtifactModel) :boolean => { + let isLicense:boolean = false; + if(this.$scope.component.isResource() && (<Resource>this.$scope.component).isCsarComponent()) { + + isLicense = this.artifactsUtils.isLicenseType(artifact.artifactType); + } + + return isLicense; + }; + + this.$scope.sort = (sortBy:string):void => { + this.$scope.reverse = (this.$scope.sortBy === sortBy) ? !this.$scope.reverse : false; + this.$scope.sortBy = sortBy; + }; + + this.$scope.getValidationPattern = (validationType:string, parameterType?:string):RegExp => { + return this.validationUtils.getValidationPattern(validationType, parameterType); + }; + + this.$scope.validateJson = (json:string):boolean => { + if(!json){ + return true; + } + return this.validationUtils.validateJson(json); + }; + + this.$scope.viewModeOrCsarComponent = ():boolean => { + return this.$scope.isViewMode() || (this.$scope.component.isResource() && (<Resource>this.$scope.component).isCsarComponent()); + }; + + + this.$scope.addOrUpdate = (artifact:Models.ArtifactModel):void => { + artifact.artifactGroupType = 'DEPLOYMENT'; + let artifactCopy = new Models.ArtifactModel(artifact); + + let success = (response:any):void => { + self.$scope.artifactDescriptions[artifactCopy.artifactLabel] = artifactCopy.description; + self.$scope.artifacts = <ArtifactModel[]>_.values(self.$scope.component.deploymentArtifacts); + }; + + let error = (err:any):void =>{ + console.log(err); + self.$scope.artifacts = <ArtifactModel[]>_.values(self.$scope.component.deploymentArtifacts); + }; + + + this.ModalsHandler.openWizardArtifactModal(artifactCopy, self.$scope.component).then(success, error); + }; + + this.$scope.noArtifactsToShow = ():boolean => { + return !_.some(this.$scope.artifacts, 'esId'); + }; + + + this.$scope.resetValue = (parameter:any):void => { + if (!parameter.currentValue && parameter.defaultValue) { + parameter.currentValue = parameter.defaultValue; + } + else if ('boolean' == parameter.type) { + parameter.currentValue = parameter.currentValue.toUpperCase(); + } + }; + + + this.$scope.$watch('editForm.$valid', ():void => { + if (this.$scope.editForm) { + // this.$scope.setValidState(this.$scope.editForm.$valid); + } + }); + + this.$scope.update = (artifact:Models.ArtifactModel):void => { + if (false == this.$scope.isLoading) { + if (artifact.selected && !this.$scope.isViewMode()) { + this.$scope.isLoading = true; + this.$scope.updateInProgress = true; + let onSuccess = (responseArtifact:Models.ArtifactModel):void => { + this.$scope.artifactDescriptions[responseArtifact.artifactLabel] = responseArtifact.description; + this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.deploymentArtifacts); + this.$scope.isLoading = false; + artifact.selected = !artifact.selected; + this.$scope.updateInProgress = false; + }; + + let onFailed = (error:any):void => { + console.log('Delete artifact returned error:', error); + this.$scope.isLoading = false; + artifact.selected = !artifact.selected; + this.$scope.updateInProgress = false; + }; + + this.setArtifact(artifact); + this.$scope.component.addOrUpdateArtifact(artifact).then(onSuccess, onFailed); + } else { + artifact.selected = !artifact.selected; + + } + } + }; + + this.$scope.delete = (artifact:Models.ArtifactModel):void => { + let onOk = ():void => { + this.$scope.isLoading = true; + let onSuccess = ():void => { + this.$scope.isLoading = false; + this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.deploymentArtifacts); + }; + + let onFailed = (error:any):void => { + this.$scope.isLoading = false; + console.log('Delete artifact returned error:', error); + }; + + this.$scope.component.deleteArtifact(artifact.uniqueId, artifact.artifactLabel).then(onSuccess, onFailed); + }; + + let title:string = self.$filter('translate')("ARTIFACT_VIEW_DELETE_MODAL_TITLE"); + let message:string = self.$filter('translate')("ARTIFACT_VIEW_DELETE_MODAL_TEXT", "{'name': '" + artifact.artifactDisplayName + "'}"); + this.ModalsHandler.openConfirmationModal(title, message, false).then(onOk); + }; + }; + + public save = (callback:Function):void => { + this.updateAll(); + this.$scope.setComponent(this.$scope.component); + callback(true); + }; + + public back = (callback:Function):void => { + this.$scope.setComponent(this.$scope.component); + callback(true); + } + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts-view.html new file mode 100644 index 0000000000..1547618134 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts-view.html @@ -0,0 +1,149 @@ +<div class="workspace-deployment-artifact"> + + <div data-tests-id="add-deployment-artifact-button" ng-if="!isViewMode()" data-ng-class="{'disabled': isDisableMode()}" data-tests-id="add-property-button" class="add-btn" data-ng-click="addOrUpdate({})">Add</div> + + <div class="table-container-flex"> + + <div class="table" data-ng-class="{'view-mode': isViewMode()}"> + <loader data-display="isLoading"></loader> + <div class="head flex-container"> + <div class="table-header head-row hand flex-item" data-ng-repeat="header in tableHeadersList track by $index" data-ng-click="sort(header.property)">{{header.title}} + <span data-ng-if="sortBy === header.property" class="table-header-sort-arrow" data-ng-class="{'down': reverse, 'up':!reverse}"> </span> + </div> + <div class="table-no-text-header head-row flex-item"></div> + </div> + + <form class="body" name="editForm"> + + <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container"> + + <!-- Artifact row --> + <div ng-if="noArtifactsToShow()" data-ng-class="{'disabled': isDisableMode()}" class="no-row-text" translate="DEPLOYMENT_ARTIFACT_NO_ARTIFACTS_TO_DISPLAY"></div> + <div data-ng-repeat-start="artifact in artifacts | orderBy:sortBy:reverse track by $index" + class="flex-container data-row" + data-ng-class="{'selected': artifact.selected || undefined==artifact.selected && updateInProgress}" + data-ng-if="artifact.esId"> + + <div class="table-col-general flex-item" data-ng-click="update(artifact)"> + + <span class="sprite table-arrow" data-tests-id="{{artifact.artifactDisplayName}}" data-ng-class="{'opened': artifact.selected || undefined==artifact.selected && updateInProgress}"></span> + {{artifact.artifactDisplayName}} + </div> + + <div class="table-col-general flex-item" data-tests-id="{{artifact.artifactType}}"> + {{artifact.artifactType}} + </div> + <div class="table-col-general flex-item" data-tests-id="{{artifact.timeout}}"> + {{artifact.timeout? artifact.timeout:''}} + </div> + + <div class="table-btn-col flex-item"> + <button class="table-edit-btn" data-tests-id="edit_{{artifact.artifactDisplayName}}" + data-ng-if="!isViewMode() && !artifact.isHEAT() && !artifact.isThirdParty() && !isLicenseArtifact(artifact)" data-ng-click="addOrUpdate(artifact)"></button> + <button class="table-delete-btn" data-tests-id="delete_{{artifact.artifactDisplayName}}" + data-ng-if="!isViewMode() && !artifact.isHEAT() && !artifact.isThirdParty() && !isLicenseArtifact(artifact)" data-ng-click="delete(artifact)"> </button> + <button class="table-download-btn" download-artifact data-tests-id="download_{{artifact.artifactDisplayName}}" + data-ng-if="artifact.artifactName" component="component" artifact="artifact"></button> + + + </div> + </div> + <div data-ng-repeat-end="" data-ng-if="artifact.selected || undefined==artifact.selected && updateInProgress" class="w-sdc-form item-opened"> + <!-- Artifact panel opened --> + + <!-- Description field --> + <div class="w-sdc-form-item" ng-form="descriptionForm" data-ng-class="{error:(descriptionForm.$dirty && descriptionForm.$invalid)}"> + <label class="i-sdc-env-form-label required">Description</label> + <textarea class="i-sdc-form-textarea {{$index}}" data-ng-class="{'view-mode': isViewMode()}" + data-ng-maxlength="256" + maxlength="256" + data-ng-required="true" + name="description" + data-ng-model="artifact.description" + data-ng-model-options="{ debounce: 200 }" + data-ng-pattern="getValidationPattern('string')" + ng-readonly="isViewMode()" + data-tests-id="description"> + </textarea> + + <div class="input-error" data-ng-show="descriptionForm.$dirty && descriptionForm.$invalid"> + <span ng-show="descriptionForm.$error.required" translate="ADD_ARTIFACT_ERROR_DESCRIPTION_REQUIRED"></span> + <span ng-show="descriptionForm.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '256' }"></span> + <span ng-show="descriptionForm.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span> + </div> + + </div> + + <!-- Parameters in 2 columns --> + <div class="w-sdc-form-columns-wrapper" data-ng-if="artifact.heatParameters"> + <!-- Left column --> + <div class="w-sdc-form-column"> + <div class="i-sdc-form-item" ng-form="parameterForm" + data-ng-repeat="parameter in artifact.heatParameters.slice(0, artifact.heatParameters.length%2+artifact.heatParameters.length/2) track by $index"> + <label class="i-sdc-env-form-label" data-ng-class="{required:parameter.defaultValue}" tooltip-side="top" sdc-smart-tooltip>{{parameter.name +' (' + parameter.type + ')'}}</label> + <span class="parameter-description" tooltips tooltip-side="top" tooltip-content="{{parameter.description}}">?</span> + <input class="i-sdc-form-input" data-ng-class="{error:(parameterForm.currentValue.$invalid),'view-mode': isViewMode() }" + data-ng-model-options="{ debounce: 200 }" + data-ng-model="parameter.currentValue" + type="text" + name="currentValue" + data-ng-pattern="getValidationPattern(parameter.type, 'heat')" + data-ng-required="parameter.defaultValue" + data-ng-change="'json'==parameter.type && parameterForm.currentValue.$setValidity('pattern', validateJson(parameter.currentValue))" + data-ng-blur="!parameterForm.currentValue.$error.pattern && resetValue(parameter)" + /> + + <div class="input-error" data-ng-show="parameterForm.currentValue.$invalid"> + <span ng-show="parameterForm.currentValue.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Value'}"></span> + <span ng-show="parameterForm.currentValue.$error.pattern && parameter.type==='string'" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span> + <span ng-show="parameterForm.currentValue.$error.pattern && !(parameter.type==='string')" translate="VALIDATION_ERROR_TYPE" translate-values="{'type': '{{parameter.type}}'}"></span> + </div> + </div> + </div> + + <!-- Right column --> + <div class="w-sdc-form-column"> + <div class="i-sdc-form-item" ng-form="parameterForm" data-ng-repeat="parameter in artifact.heatParameters.slice(artifact.heatParameters.length%2+artifact.heatParameters.length/2) track by $index"> + <label class="i-sdc-env-form-label" data-ng-class="{required:parameter.defaultValue}" tooltip-side="top" sdc-smart-tooltip>{{parameter.name +' (' + parameter.type + ')'}}</label> + <span class="parameter-description" tooltips tooltip-side="top" tooltip-content="{{parameter.description}}">?</span> + <input class="i-sdc-form-input" data-ng-class="{error:(parameterForm.currentValue.$invalid), 'view-mode': isViewMode()}" + data-ng-model-options="{ debounce: 200 }" + data-ng-model="parameter.currentValue" + type="text" + name="currentValue" + data-ng-pattern="getValidationPattern(parameter.type, 'heat')" + data-ng-required="parameter.defaultValue" + data-ng-change="'json'==parameter.type && parameterForm.currentValue.$setValidity('pattern', validateJson(parameter.currentValue))" + data-ng-blur="!parameterForm.currentValue.$error.pattern && resetValue(parameter)" + /> + + <div class="input-error" data-ng-show="parameterForm.currentValue.$invalid"> + <span ng-show="parameterForm.currentValue.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Value'}"></span> + <span ng-show="parameterForm.currentValue.$error.pattern && parameter.type==='string'" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span> + <span ng-show="parameterForm.currentValue.$error.pattern && !(parameter.type==='string')" translate="VALIDATION_ERROR_TYPE" translate-values="{'type': '{{parameter.type}}'}"></span> + </div> + </div> + </div> + + + </div><!-- Close: Parameters in 2 columns --> + </div><!-- Close: Artifact panel opened --> + + <!-- Add artifacts buttons --> + <button class="add-button" data-ng-repeat="artifact in artifacts track by $index" + type="button" + data-ng-show="!artifact.esId" + data-ng-if="!viewModeOrCsarComponent()" + data-ng-class="{'disabled': isDisableMode() || component.isCsarComponent()}" + data-tests-id="{{artifact.artifactDisplayName}} deployment_artifact" + translate="DEPLOYMENT_ARTIFACT_BUTTON_ADD_HEAT" + translate-values="{'name': '{{artifact.artifactDisplayName}}'}" + data-ng-click="addOrUpdate(artifact)"></button> + + <!-- Top add button --> + <button class="add-button" type="button" data-ng-if="!isViewMode()" data-ng-class="{'disabled': isDisableMode()}" translate="DEPLOYMENT_ARTIFACT_BUTTON_ADD_OTHER" data-ng-click="addOrUpdate({})"></button> + </perfect-scrollbar> + </form> + </div> + </div> +</div> diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts.less b/catalog-ui/app/scripts/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts.less new file mode 100644 index 0000000000..9f90a47d5a --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts.less @@ -0,0 +1,102 @@ +.workspace-deployment-artifact { + width: 93%; + display: inline-block; + .table-container-flex .table .body .data-row + div.item-opened { + align-items: center; + padding: 10px 40px 10px 30px; + } + + .w-sdc-classic-btn { + float: right; + margin-bottom: 10px; + } + + .table { + height:490px; + margin-bottom: 0; + } + + .parameter-description { + .circle(18px, @color_p); + content: '?'; + line-height: 18px; + vertical-align: middle; + margin-left: 5px; + cursor: default; + display: inline-block; + position: absolute; + top: 16px; + } + + .table-container-flex { + + margin-top: 27px; + + .flex-item:nth-child(1) { + flex-grow: 15; + .hand; + span.table-arrow { + margin-right: 7px; + } + } + + .flex-item:nth-child(2) { + flex-grow: 6; + } + + .flex-item:nth-child(3) { + flex-grow: 9; + } + + .flex-item:nth-child(4) { + flex-grow: 3; + padding-top: 10px; + } + } + .w-sdc-form{ + text-align: left; + + .w-sdc-env-params{ + border-top: 1px solid #cdcdcd; + margin: 25px 0 10px 0; + } + + .i-sdc-form-textarea { + border: 1px solid @color_e; + min-height: 60px; + padding: 10px 13px; + width: 100%; + resize: none; + + } + + .w-sdc-form-item { + &.error { + .i-sdc-form-input, + .i-sdc-form-select, + .i-sdc-form-textarea { + border-color: @color_h; + outline: none; + box-sizing: border-box; + } + } + } + + .i-sdc-env-form-label{ + font-family: @font-omnes-medium; + color: @main_color_m; + overflow: hidden; + max-width: 450px; + text-overflow: ellipsis; + display: inline-block; + white-space: nowrap; + margin-top: 14px; + + &.required::before { + color: #f33; + content: '*'; + margin-right: 4px; + } + } + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/deployment/deployment-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/deployment/deployment-view-model.ts new file mode 100644 index 0000000000..f8afc0b758 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/deployment/deployment-view-model.ts @@ -0,0 +1,127 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../../references"/> +module Sdc.ViewModels { + 'use strict'; + + export interface IDeploymentViewModelScope extends IWorkspaceViewModelScope { + + currentComponent: Models.Components.Component; + selectedComponent: Models.Components.Component; + isLoading: boolean; + sharingService:Sdc.Services.SharingService; + sdcMenu:Models.IAppMenu; + version:string; + isViewOnly:boolean; + tabs:Array<Models.Tab>; + + setComponent(component: Models.Components.Component); + isComponentInstanceSelected():boolean; + updateSelectedComponent(): void + openUpdateModal(); + deleteSelectedComponentInstance():void; + onBackgroundClick():void; + setSelectedInstance(componentInstance: Models.ComponentsInstances.ComponentInstance): void; + printScreen():void; + + } + + export class DeploymentViewModel { + + static '$inject' = [ + '$scope', + 'sdcMenu', + 'MenuHandler', + '$modal', + '$templateCache', + '$state', + 'Sdc.Services.SharingService', + '$filter', + 'Sdc.Services.CacheService', + 'ComponentFactory', + 'ChangeLifecycleStateHandler', + 'LeftPaletteLoaderService', + 'ModalsHandler' + ]; + + constructor(private $scope:IDeploymentViewModelScope, + private sdcMenu:Models.IAppMenu, + private MenuHandler: Utils.MenuHandler, + private $modal:ng.ui.bootstrap.IModalService, + private $templateCache:ng.ITemplateCacheService, + private $state:ng.ui.IStateService, + private sharingService:Services.SharingService, + private $filter:ng.IFilterService, + private cacheService:Services.CacheService, + private ComponentFactory: Utils.ComponentFactory, + private ChangeLifecycleStateHandler: Sdc.Utils.ChangeLifecycleStateHandler, + private LeftPaletteLoaderService: Services.Components.LeftPaletteLoaderService, + private ModalsHandler: Sdc.Utils.ModalsHandler) { + + this.$scope.setValidState(true); + this.initScope(); + this.$scope.updateSelectedMenuItem(); + } + + + private initComponent = ():void => { + + this.$scope.currentComponent = this.$scope.component; + this.$scope.selectedComponent = this.$scope.currentComponent; + this.updateUuidMap(); + this.$scope.isViewOnly = this.$scope.isViewMode(); + }; + + + private updateUuidMap = ():void => { + /** + * In case user press F5, the page is refreshed and this.sharingService.currentEntity will be undefined, + * but after loadService or loadResource this.sharingService.currentEntity will be defined. + * Need to update the uuidMap with the new resource or service. + */ + this.sharingService.addUuidValue(this.$scope.currentComponent.uniqueId,this.$scope.currentComponent.uuid); + }; + + private initRightTabs = ()=> { + if(this.$scope.currentComponent.groups){ + + let hierarchyTab = new Models.Tab('/app/scripts/view-models/tabs/hierarchy/hierarchy-view.html', 'Sdc.ViewModels.HierarchyViewModel', 'hierarchy', this.$scope.currentComponent, 'hierarchy'); + this.$scope.tabs = Array<Models.Tab>(); + this.$scope.tabs.push(hierarchyTab) + } + + } + private initScope = ():void => { + + this.$scope.sharingService = this.sharingService; + this.$scope.sdcMenu = this.sdcMenu; + this.$scope.isLoading = false; + + this.$scope.version = this.cacheService.get('version'); + this.initComponent(); + + this.$scope.setComponent = (component: Models.Components.Product):void => { + this.$scope.currentComponent = component; + } + + this.initRightTabs(); + } + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/deployment/deployment-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/deployment/deployment-view.html new file mode 100644 index 0000000000..9e26656f5f --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/deployment/deployment-view.html @@ -0,0 +1,10 @@ +<div class="deployment-view"> + <div class="w-sdc-deployment-canvas" data-ng-class="{sidebaractive: displayDesignerRightSidebar}"> + <!--<module-graph data-tests-id="canvas" current-component="currentComponent" is-view-only="isViewOnly" is-loading="isLoading"></module-graph>--> + <deployment-graph component="currentComponent" is-view-only="isViewOnly"></deployment-graph> + </div> + + <div class="w-sdc-deployment-right-bar"> + <sdc-tabs tabs="tabs" is-view-only="isViewOnly"></sdc-tabs> + </div> +</div> diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/deployment/deployment.less b/catalog-ui/app/scripts/view-models/workspace/tabs/deployment/deployment.less new file mode 100644 index 0000000000..0439ccd0fa --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/deployment/deployment.less @@ -0,0 +1,33 @@ +.deployment-view{ + + display: inline-block; + text-align: left; + align-items: left; + padding: 0; + width: 100%; + height: 100%; + + .w-sdc-deployment-canvas { + .noselect; + .bg_c; + bottom: 0; + width: 100%; + height: 100%; + + .view-mode{ + background-color: #f8f8f8; + border:0; + } + } + + .w-sdc-deployment-right-bar { + + .noselect; + bottom: 0; + position: absolute; + right: 0px; + transition: right 0.2s; + z-index: 10000; + top: @action_nav_height; + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal-view-model.ts new file mode 100644 index 0000000000..c0d6aba915 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal-view-model.ts @@ -0,0 +1,67 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../../../references"/> +module Sdc.ViewModels { + 'use strict'; + + interface IDistributionStatusModalViewModelScope { + distribution:Models.Distribution; + status:string; + getStatusCount(distributionComponent:Array<Models.DistributionComponent>):any; + getUrlName(url:string):string; + modalDitributionStatus:ng.ui.bootstrap.IModalServiceInstance; + footerButtons: Array<any>; + close(): void; + } + + export class DistributionStatusModalViewModel { + + static '$inject' = ['$scope','$modalInstance', 'data']; + + constructor(private $scope:IDistributionStatusModalViewModelScope, + private $modalInstance:ng.ui.bootstrap.IModalServiceInstance, + private data:any + ) { + this.initScope(); + } + + private initScope = ():void => { + this.$scope.distribution = this.data.distribution; + this.$scope.status = this.data.status; + this.$scope.modalDitributionStatus = this.$modalInstance; + + this.$scope.getUrlName = (url:string):string =>{ + let urlName:string = _.last(url.split('/')); + return urlName; + }; + + this.$scope.close = ():void => { + this.$modalInstance.close(); + }; + + this.$scope.footerButtons = [ + {'name': 'Close', 'css': 'blue', 'callback': this.$scope.close } + ]; + + }; + + + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal-view.html new file mode 100644 index 0000000000..b3393e99e0 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal-view.html @@ -0,0 +1,126 @@ +<sdc-modal modal="modalDitributionStatus" type="classic" class="w-sdc-classic-top-line-modal" buttons="footerButtons" header="Distribution by Status" show-close-button="true"> + + <div class="w-sdc-distribution-view"> + <div class="w-sdc-distribution-view-header"> + + </div> + + + <perfect-scrollbar include-padding="true" class="w-sdc-distribution-view-content"> + <div class="w-sdc-distribution-view-content-section w-sdc-distribute-parent-block"> + <ul> + <li class="w-sdc-distribute-parent-block" > + <div class="w-sdc-distribute-row w-sdc-distribute-row-extends extends"> + + <div class="w-sdc-distribute-row-content"> + <div class="w-sdc-distribute-content"> + <div class="title-section item-1"> + <div class="title">Distribution ID</div> + <div data-ng-bind="distribution.distributionID"></div> + </div> + <div class="title-section item-2"> + <div class="title" translate="DISTRIBUTION_VIEW_TITLE_USER_ID"></div> + <div data-ng-bind="distribution.userId"></div> + </div> + <div class="title-section item-3"> + <div class="title">Time[UTC]:</div> + <div + data-ng-bind="distribution.timestamp | stringToDateFilter | date: 'MM/dd/yyyy h:mma':'UTC'"></div> + </div> + <div class="title-section item-4"> + <span class="sprite-new status-icon" data-ng-class="distribution.deployementStatus"></span> + <span class="sprite-new" data-ng-bind="distribution.deployementStatus"></span> + </div> + </div> + <div class="w-sdc-distribute-status-block" data-ng-show="distribution.statusCount"> + <div class="status-item-1">Status: {{status}} <span data-ng-bind="(distribution.distributionComponents | filter:status:true).length" + class="blue-font"></span></div> + + </div> + </div> + </div> + + <ul class="w-sdc-distribute-components-block disable-hover"> + <li data-ng-repeat="(omfComponentID,omfComponentList) in distribution.distributionComponents | orderBy: '-timestamp' | filter:status:true | groupBy:'omfComponentID'" + class="disable-hover"> + <div class="w-sdc-distribute-row omf-component-row w-sdc-distribute-row-extends " + data-ng-class="{'extends': omfComponentListExtends}"> + <div class="w-sdc-distribution-arrow-btn" data-ng-click="omfComponentListExtends=!omfComponentListExtends" + ng-class="{'extends': omfComponentListExtends}" + data-ng-init="omfComponentListExtends=false" + ></div> + <div class="w-sdc-distribute-status-block"> + <div class="status-item-1">{{omfComponentID}} <span class="blue-font">{{omfComponentList.length}}</span> + </div> + </div> + </div> + <div data-ng-show="omfComponentListExtends" + class="w-sdc-distribute-omfComponent-block disable-hover"> + <div class="w-sdc-distribute-row-extends disable-hover"> + <div class="disable-hover"> + <div class="w-sdc-distribute-row omfComponent-table-head"> + <div class="title item-1">Component ID</div> + <div class="title item-2">Artifact Name</div> + <div class="title item-3">URL</div> + <div class="title item-4">Time(UTC)</div> + <div class="title item-5">Status</div> + </div> + + <div class="w-sdc-distribute-row omfComponent-table-row" + data-ng-repeat-start="(url,urlList) in omfComponentList | orderBy: '-timestamp' | groupBy:'url'" + data-ng-class="urlListExtends?'extends row-{{$index}}':'row-{{$index}}'" > + <div class="w-sdc-distribute-cell item-1"> + <div class="w-sdc-distribution-arrow-btn" data-ng-click="urlListExtends=!urlListExtends" + data-ng-class="{'extends': urlListExtends}" + data-ng-init="urlListListExtends=false" + ></div> + {{urlList[0].omfComponentID}} + </div> + <div class="w-sdc-distribute-cell item-2" sdc-smart-tooltip> + {{getUrlName(urlList[0].url)}} + </div> + <div class="w-sdc-distribute-cell item-3 disable-hover"> + <div sdc-smart-tooltip class="distribution-url">{{urlList[0].url}}</div> + <div sdc-smart-tooltip title="Copy url" clipboard text="urlList[0].url" + class="sprite-new link-btn copy-link disable-hover"></div> + </div> + <div class="w-sdc-distribute-cell item-4"><span + data-ng-bind="urlList[0].timestamp | date: 'MM/dd/yyyy h:mma':'UTC'"></span> + </div> + <div class="w-sdc-distribute-cell item-5">{{urlList[0].status}}</div> + </div> + + + <div data-ng-repeat-end data-ng-show="urlListExtends" class="disable-hover"> + <div class="w-sdc-distribute-row extends disable-hover"> + <ul data-ng-show="urlListExtends" + class="w-sdc-distribute-url-block disable-hover"> + <li data-ng-repeat="distributionComponent in urlList | orderBy: '-timestamp'" + class="disable-hover"> + <span + data-ng-bind="distributionComponent.timestamp | date: 'MM/dd/yyyy h:mma':'UTC'" + class="disable-hover"></span> + <span + class="disable-hover">{{distributionComponent.status}}</span> + <span + class="disable-hover reason" data-ng-show="distributionComponent.status == 'NOT_NOTIFIED'">Reason: Component has determined artifact is not needed.</span> + <span + class="disable-hover reason" data-ng-show="distributionComponent.errorReason">Reason: {{distributionComponent.errorReason}}</span> + </li> + </ul> + </div> + </div> + </div> + </div> + </div> + </li> + </ul> + </li> + </ul> + </div> + + </perfect-scrollbar> + </div> + + +</sdc-modal> diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal.less b/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal.less new file mode 100644 index 0000000000..02321b6e2f --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal.less @@ -0,0 +1,33 @@ +.w-sdc-classic-top-line-modal { + + .w-sdc-modal-head { + // border-bottom: none; + } + .w-sdc-distribution-view { + + .w-sdc-distribution-view-content { + height: 500px; + } + + .w-sdc-distribution-view-content-section { + + .w-sdc-distribute-parent-block { + .w-sdc-distribute-components-block { + + .omf-component-row { + .w-sdc-distribute-status-block { + margin-left: 0; + } + + } + div { + padding-left: 0; + } + } + + } + + } + } +} + diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/distribution-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/distribution-view-model.ts new file mode 100644 index 0000000000..219585fc3d --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/distribution-view-model.ts @@ -0,0 +1,135 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../../references"/> + +module Sdc.ViewModels { + 'use strict'; + + interface IDistributionViewModel extends IWorkspaceViewModelScope{ + modalDistribution:ng.ui.bootstrap.IModalServiceInstance; + service: Models.Components.Service; + distributions : Array<Models.Distribution>; + showComponents(distribution:Models.Distribution): void; + markAsDeployed(distribution:Models.Distribution): void; + getStatusCount(distributionComponent:Array<Models.DistributionComponent>):any; + initDistributions():void; + getUrlName(url:string):string; + close(): void; + openDisributionStatusModal:Function; + } + + export class DistributionViewModel{ + + static '$inject' = [ + '$scope', + 'ModalsHandler' + + ]; + + constructor( + private $scope:IDistributionViewModel, + private ModalsHandler: Sdc.Utils.ModalsHandler + ){ + this.initScope(); + this.$scope.setValidState(true); + this.$scope.updateSelectedMenuItem(); + } + + private initScope = (): void => { + this.$scope.service = <Models.Components.Service>this.$scope.component; + + + // Open Distribution status modal + this.$scope.openDisributionStatusModal = (distribution: Models.Distribution,status:string):void => { + this.ModalsHandler.openDistributionStatusModal(distribution,status).then(()=>{ + // OK + }, ()=>{ + // ERROR + }); + }; + + + this.$scope.showComponents = (distribution: Models.Distribution): void => { + let onError = (response) => { + console.info('onError showComponents',response); + }; + let onSuccess = (distributionComponents: Array<Models.DistributionComponent>) => { + distribution.distributionComponents = distributionComponents; + distribution.statusCount = this.$scope.getStatusCount(distribution.distributionComponents); + // distribution.components = this.aggregateDistributionComponent(distributionComponents);; + }; + this.$scope.service.getDistributionsComponent(distribution.distributionID).then(onSuccess, onError); + }; + + this.$scope.getStatusCount = (distributionComponent:Array<Models.DistributionComponent>):any => { + return _.countBy(distributionComponent, 'status') + }; + + this.$scope.getUrlName = (url:string):string =>{ + let urlName:string = _.last(url.split('/')); + return urlName; + }; + + this.$scope.markAsDeployed = (distribution: Models.Distribution): void => { + let onError = (response) => { + console.info('onError markAsDeployed',response); + }; + let onSuccess = (result: any) => { + distribution.deployementStatus = 'Deployed'; + }; + this.$scope.service.markAsDeployed(distribution.distributionID).then(onSuccess, onError); + + }; + + this.$scope.initDistributions = (): void => { + let onError = (response) => { + console.info('onError initDistributions',response); + }; + let onSuccess = (distributions: Array<Models.Distribution>) => { + this.$scope.distributions = distributions; + }; + this.$scope.service.getDistributionsList().then(onSuccess, onError); + }; + + this.$scope.initDistributions(); + + }; + + + private aggregateDistributionComponent = (distributionComponents:Array<Models.DistributionComponent>):any =>{ + let aggregateDistributions:Utils.Dictionary<string,Utils.Dictionary<string,Array<Models.DistributionComponent>>> = new Utils.Dictionary<string,Utils.Dictionary<string,Array<Models.DistributionComponent>>>(); + let tempAggregateDistributions:any= _.groupBy(distributionComponents,'omfComponentID'); + let aa = new Utils.Dictionary<string,Array<Models.DistributionComponent>>(); + + let tempAggregate:any; + _.forEach(tempAggregateDistributions,(distributionComponents:Array<Models.DistributionComponent>,omfComponentID:string)=>{ + + let urls:any = _.groupBy(distributionComponents,'url'); + aggregateDistributions.setValue(omfComponentID,urls); + // aggregateDistributions[omfComponentID] = ; + + }); + console.log(aggregateDistributions); + return aggregateDistributions; + }; + + + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/distribution-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/distribution-view.html new file mode 100644 index 0000000000..1ab0f1e111 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/distribution-view.html @@ -0,0 +1,171 @@ +<div class="w-sdc-distribution-view"> + <div class="w-sdc-distribution-view-header"> + <div class="w-sdc-distribution-view-title">DISTRIBUTION <span data-ng-bind="'[' + distributions.length +']'" + class="blue-font"></span></div> + <div class="header-spacer"></div> + <div class="top-search"> + <input type="text" + class="search-text" + data-tests-id="searchTextbox" + placeholder="Search" + data-ng-model="searchBind" + data-tests-id="main-menu-input-search" + ng-model-options="{ debounce: 500 }"/> + <span class="w-sdc-search-icon magnification"></span> + </div> + <div class="sprite-new refresh-btn" data-tests-id="refreshButton" data-ng-click="initDistributions()" sdc-smart-tooltip="" + title="Refresh"></div> + </div> + + + <perfect-scrollbar include-padding="true" class="w-sdc-distribution-view-content"> + <div class="w-sdc-distribution-view-content-section" data-tests-id="ditributionTable"> + <ul> + <li data-ng-repeat="item in distributions | orderBy: '-timestamp' | filter:searchBind" + data-ng-init="item.dateFormat = ( item.timestamp | stringToDateFilter | date: 'MM/dd/yyyy h:mma':'UTC' )" + class="w-sdc-distribute-parent-block" data-tests-id="record_{{$index}}" data-ng-class="{'extends': item.showDetails}"> + <div class="w-sdc-distribute-row w-sdc-distribute-row-extends" + data-ng-class="{'extends': item.showDetails && item.distributionComponents.length}"> + <div class="w-sdc-distribution-arrow-btn" data-tests-id="ShowRecordButton_{{$index}}" data-ng-click="showComponents(item); item.showDetails=!item.showDetails" + data-ng-class="{'extends': item.showDetails}" + ></div> + <div class="w-sdc-distribute-row-content"> + <div class="w-sdc-distribute-content"> + <div class="title-section item-1"> + <div class="title">Distribution ID</div> + <div data-ng-bind="item.distributionID"></div> + </div> + <div class="title-section item-2"> + <div class="title" translate="DISTRIBUTION_VIEW_TITLE_USER_ID"></div> + <div data-ng-bind="item.userId"></div> + </div> + <div class="title-section item-3"> + <div class="title">Time[UTC]:</div> + <div + data-ng-bind="item.dateFormat"></div> + </div> + <div class="title-section item-4"> + <span class="sprite-new status-icon" data-ng-class="item.deployementStatus"></span> + <span class="sprite-new" data-ng-bind="item.deployementStatus"></span> + </div> + <div> + <div class="sprite-new distribution-bth item-5" + data-ng-class="{'disable':item.deployementStatus==='Deployed'}" + data-ng-click="(item.deployementStatus==='Deployed') || markAsDeployed(item)"></div> + </div> + </div> + <div class="w-sdc-distribute-status-block" data-ng-show="item.statusCount"> + <div class="status-item-1">Total Artifacts:<span data-ng-bind="(item.statusCount.NOT_NOTIFIED || 0) + (item.statusCount.NOTIFIED || 0) " + class="blue-font" data-tests-id="totalArtifacts_{{$index}}"></span></div> + <div class="status-item-2 " ><sapn class="link" data-ng-click="openDisributionStatusModal(item,'NOTIFIED')">Notified:</sapn><span + data-ng-bind="item.statusCount.NOTIFIED || 0" class="blue-font" data-tests-id="notified_{{$index}}"></span></div> + <div class="status-item-3 link" ><sapn class="link" data-ng-click="openDisributionStatusModal(item,'DOWNLOAD_OK')">Downloaded:</sapn><span + data-ng-bind="item.statusCount.DOWNLOAD_OK || 0" class="blue-font" data-tests-id="downloaded_{{$index}}"></span></div> + <div class="status-item-4 link"><sapn class="link" data-ng-click="openDisributionStatusModal(item,'DEPLOY_OK')">Deployed:</sapn><span + data-ng-bind="item.statusCount.DEPLOY_OK || 0" class="blue-font" data-tests-id="deployed_{{$index}}" ></span><span + data-ng-class="{'deployed':(item.statusCount.DEPLOY_OK > 0)}"></span></div> + <div class="status-item-5 link" ><sapn class="link" data-ng-click="openDisributionStatusModal(item,'NOT_NOTIFIED')">Not Notified:</sapn><span + data-ng-bind="item.statusCount.NOT_NOTIFIED || 0" class="blue-font" data-tests-id="NotNotified_{{$index}}"></span></div> + <div class="status-item-6"><sapn class="link" data-ng-click="openDisributionStatusModal(item,'DOWNLOAD_ERROR')" >Errors:</sapn><span + data-ng-bind="item.statusCount.DOWNLOAD_ERROR || 0" class="red-font "></span><span + data-ng-class="{'error':(item.statusCount.DOWNLOAD_ERROR > 0)}" data-tests-id="errors_{{$index}}"></span></div> + </div> + </div> + </div> + + <ul data-ng-show="item.showDetails && item.distributionComponents.length" + class="w-sdc-distribute-components-block disable-hover"> + + <li data-ng-repeat="(omfComponentID,omfComponentList) in item.distributionComponents | orderBy: '-timestamp' | filter:searchBind | groupBy:'omfComponentID' " + class="disable-hover" + data-ng-init="statusCount = getStatusCount(omfComponentList);"> + <div class="w-sdc-distribute-row omf-component-row w-sdc-distribute-row-extends" + data-ng-class="{'extends': omfComponentListExtends}"> + <div class="w-sdc-distribution-arrow-btn" data-ng-click="omfComponentListExtends=!omfComponentListExtends" + ng-class="{'extends': omfComponentListExtends}" + data-ng-init="omfComponentListExtends=false" + ></div> + <div class="w-sdc-distribute-status-block"> + <div class="status-item-1">{{omfComponentID}} <span class="blue-font">{{(statusCount.NOT_NOTIFIED || 0) + (statusCount.NOTIFIED || 0) }}</span> + </div> + <div class="status-item-2">Notified:<span data-ng-bind="statusCount.NOTIFIED || 0" + class="blue-font"></span></div> + <div class="status-item-3">Downloaded:<span + data-ng-bind="statusCount.DOWNLOAD_OK || 0" class="blue-font"></span></div> + <div class="status-item-4">Deployed:<span data-ng-bind="statusCount.DEPLOY_OK || 0" + class="blue-font"></span><span + data-ng-class="{'deployed':(statusCount.DEPLOY_OK > 0)}"></span></div> + <div class="status-item-5">Not Notified:<span + data-ng-bind="statusCount.NOT_NOTIFIED || 0" class="blue-font"></span></div> + <div class="status-item-6">Errors:<span + data-ng-bind="statusCount.DOWNLOAD_ERROR || 0" class="red-font"></span><span + data-ng-class="{'error':(statusCount.DOWNLOAD_ERROR > 0)}"></span></div> + </div> + </div> + <div data-ng-show="omfComponentListExtends" + class="w-sdc-distribute-omfComponent-block disable-hover"> + <div class="w-sdc-distribute-row-extends disable-hover"> + <div class="disable-hover"> + <div class="w-sdc-distribute-row omfComponent-table-head"> + <div class="title item-1">Component ID</div> + <div class="title item-2">Artifact Name</div> + <div class="title item-3">URL</div> + <div class="title item-4">Time(UTC)</div> + <div class="title item-5">Status</div> + </div> + + <div class="w-sdc-distribute-row omfComponent-table-row" + data-ng-repeat-start="(url,urlList) in omfComponentList | orderBy: '-timestamp' | groupBy:'url'" + data-ng-class="urlListExtends?'extends row-{{$index}}':'row-{{$index}}'"> + <div class="w-sdc-distribute-cell item-1"> + <div class="w-sdc-distribution-arrow-btn" data-ng-click="urlListExtends=!urlListExtends" + data-ng-class="{'extends': urlListExtends}" + data-ng-init="urlListListExtends=false" + ></div> + {{urlList[0].omfComponentID}} + </div> + <div class="w-sdc-distribute-cell item-2" sdc-smart-tooltip> + {{getUrlName(urlList[0].url)}} + </div> + <div class="w-sdc-distribute-cell item-3 disable-hover"> + <div sdc-smart-tooltip class="distribution-url">{{urlList[0].url}}</div> + <div sdc-smart-tooltip title="Copy url" clipboard text="urlList[0].url" + class="sprite-new link-btn copy-link disable-hover"></div> + </div> + <div class="w-sdc-distribute-cell item-4"><span + data-ng-bind="urlList[0].timestamp | date: 'MM/dd/yyyy h:mma':'UTC'"></span> + </div> + <div class="w-sdc-distribute-cell item-5">{{urlList[0].status}}</div> + </div> + + + <div data-ng-repeat-end data-ng-show="urlListExtends" class="disable-hover" > + <div class="w-sdc-distribute-row extends disable-hover"> + <ul data-ng-show="urlListExtends" + class="w-sdc-distribute-url-block disable-hover"> + <li data-ng-repeat="distributionComponent in urlList | orderBy: '-timestamp'" + class="disable-hover"> + <span + data-ng-bind="distributionComponent.timestamp | date: 'MM/dd/yyyy h:mma':'UTC'" + class="disable-hover"></span> + <span + class="disable-hover">{{distributionComponent.status}}</span> + <span + class="disable-hover reason" data-ng-show="distributionComponent.status == 'NOT_NOTIFIED'">Reason: Component has determined artifact is not needed.</span> + <span + class="disable-hover reason" data-ng-show="distributionComponent.errorReason">Reason: {{distributionComponent.errorReason}}</span> + </li> + </ul> + </div> + </div> + </div> + </div> + </div> + </li> + </ul> + </li> + </ul> + </div> + + </perfect-scrollbar> +</div> diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/distribution.less b/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/distribution.less new file mode 100644 index 0000000000..8ad8c1793e --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/distribution.less @@ -0,0 +1,361 @@ + +.w-sdc-distribution-view { + text-align: left; + + .g_1; + min-height: 500px; + + .w-sdc-distribution-view-distributed-green-text { + .l_9; + .bold; + } + .w-sdc-distribution-view-distributed-error-red-text { + .h_9; + .bold; + } + + .bg_c; + vertical-align: top; + padding: 30px 10px; + width: 100%; + + .w-sdc-distribution-view-header { + display: flex; + -webkit-justify-content: space-between; + margin: 0 25px 5px 40px; + + .header-spacer { + flex-grow: 5; + } + } + + .top-search { + position: relative; + input { + &.search-text { + height: 26px; + line-height: 26px; + margin: 0 18px 4px 20px; + padding-right: 25px; + } + + } + .magnification { + top: 8px; + right: 25px; + } + } + + .w-sdc-distribution-view-content { + .perfect-scrollbar; + padding: 0 25px 0 0px; + margin-bottom: 25px; + height: 700px; + overflow: hidden; + position: relative; + + } + + .w-sdc-distribution-view-title { + .s_14_r; + + line-height: 30px; + + span { + padding-left: 5px; + } + } + + .blue-font { + .a_14_m; + + } + + .red-font { + .q_14_m; + } + + .w-sdc-distribution-view-block { + div { + display: inline-block; + } + } + + .w-sdc-distribution-view-content-section { + ul { + list-style-type: none; + } + + .distribution-bth { + .hand; + &.disabled { + cursor: none; + } + } + + .copy-link { + padding-right: 19px; + margin-left: 8px; + cursor: pointer; + + } + + .w-sdc-distribute-row-extends { + border-Left: solid 4px transparent; + &.extends { + border-left: solid 4px @main_color_c; + border-bottom: 1px solid @border_color_f; + margin-bottom: 10px; + } + } + .w-sdc-distribute-parent-block { + border: 1px solid @main_color_o;; + width: 100%; + margin-bottom: 6px; + + .status-icon { + vertical-align: middle; + margin-bottom: 4px; + } + + &.extends { + background-color: @tlv_color_t; + } + + :not(.disable-hover):hover { + background-color: @tlv_color_u; + } + + .title-section { + display: inline-block; + margin-right: 10px; + flex-basis: 0; + } + + .title { + .l_12_m; + font-weight: bold; + } + .w-sdc-distribute-content { + display: flex; + align-items: center; + justify-content: space-between; + margin-left: 10px; + } + + .w-sdc-distribution-arrow-btn { + .sprite-new; + .arrow-up-small; + margin: 0 6px; + } + .extends.w-sdc-distribution-arrow-btn { + -webkit-transform: rotate(180deg); + -ms-transform: rotate(180deg); + transform: rotate(180deg); + } + + .w-sdc-distribute-row { + display: flex; + align-items: center; + justify-content: space-between; + + .w-sdc-distribute-row-content { + margin: 15px 31px 10px 0; + width: 100%; + .w-sdc-distribute-status-block { + border-top: solid 1px @main_color_o; + } + .item-1 { + flex-grow: 2; + } + .item-2 { + flex-grow: 1; + } + .item-3 { + flex-grow: 1; + } + .item-4 { + flex-grow: 1; + } + .item-5 { + flex-grow: 1; + } + } + } + + .w-sdc-distribute-status-block { + display: flex; + align-items: center; + justify-content: space-between; + margin: 10px 5px 0 5px; + padding: 5px 5px 0 5px;; + width: 100%; + div { + border-left: 1px solid @main_color_o; + padding: 0 12px; + } + + .link { + .a_14_m; + cursor: pointer; + &:hover{ + text-decoration: underline; + .b_14_m; + } + } + + span { + padding: 2px; + } + + .deployed { + margin-left: 10px; + .sprite-new; + .success-circle-small; + } + + .error { + .q_14_m; + margin-left: 10px; + .sprite-new; + .error-icon; + } + + .status-item-1 { + border-left: 0; + } + + .status-item-6 { + flex-grow: 1; + border-left: none; + text-align: right; + } + } + + .w-sdc-distribute-components-block { + padding: 0; + padding-bottom: 5px; + list-style-type: none; + + li { + margin: 5px 2px; + } + + .omf-component-row { + border: 1px solid @border_color_f; + padding-left: 3px; + background-color: white; + margin: 0 30px; + &.extends { + padding-left: 0; + border-Left: solid 4px @main_color_c; + + } + + .w-sdc-distribute-status-block { + margin: 5px; + padding: 5px; + } + + .blue-font { + .a_16_m; + + } + + &:hover { + background-color: @tlv_color_u; + } + + } + + } + + .w-sdc-distribute-omfComponent-block { + background-color: white; + margin: 0 30px; + padding: 8px 10px; + border: 1px solid @border_color_f; + + .omfComponent-table-head { + margin-bottom: 5px; + background-color: @tlv_color_u; + .title { + padding: 6px 10px; + border-left: 1px solid @border_color_f; + &:first-child { + border: none; + } + } + } + + .omfComponent-table-row { + border-bottom: 1px solid @border_color_f; + &.row-0 { + border-top: 1px solid @border_color_f; + } + .w-sdc-distribute-cell { + padding: 10px; + border-left: 1px solid @border_color_f; + &:last-child { + border-right: 1px solid @border_color_f; + } + &.item-5 { + .m_14_m; + } + } + } + + .distribution-url { + + } + + .w-sdc-distribute-row.extends { + border-Left: solid 4px @main_color_c; + .item-1 { + border: none; + } + + } + + .item-1 { + width: 20%; + } + .item-2 { + width: 20%; + } + + .item-3 { + width: 24%; + display: flex; + } + + .item-4 { + width: 18%; + } + + .item-5 { + width: 18%; + } + } + + .w-sdc-distribute-url-block { + + padding: 10px 15px; + border: none; + border-right: 1px solid @border_color_f; + border-bottom: 1px solid @border_color_f; + width: 100%; + li { + border: none; + span { + padding-right: 30px; + .m_12_r; + } + } + + } + } + + } +} + diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/general/general-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/general/general-view-model.ts new file mode 100644 index 0000000000..f613648596 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/general/general-view-model.ts @@ -0,0 +1,379 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../../references"/> +module Sdc.ViewModels { + + 'use strict'; + import Resource = Sdc.Models.Components.Resource; + import ISubCategory = Sdc.Models.ISubCategory; + import IMainCategory = Sdc.Models.IMainCategory; + import ResourceType = Sdc.Utils.Constants.ResourceType; + + export class Validation { + validationPattern:RegExp; + contactIdValidationPattern:RegExp; + tagValidationPattern:RegExp; + vendorValidationPattern:RegExp; + commentValidationPattern:RegExp; + projectCodeValidationPattern:RegExp; + } + + export interface IGeneralScope extends IWorkspaceViewModelScope { + validation:Validation; + editForm:ng.IFormController; + categories: Array<IMainCategory>; + latestCategoryId: string; + latestVendorName: string; + importedFileExtension:any; + isCreate:boolean; + isShowFileBrowse:boolean; + isShowOnboardingSelectionBrowse:boolean; + importedToscaBrowseFileText:string; + importCsarProgressKey:string; + browseFileLabel:string; + + + onToscaFileChange():void + validateField(field:any):boolean; + validateName(isInit:boolean): void; + calculateUnique(mainCategory:string, subCategory:string):string; // Build unique string from main and sub category + onVendorNameChange(oldVendorName:string): void; + convertCategoryStringToOneArray(category:string, subcategory:string):Array<Models.IMainCategory>; + onCategoryChange():void; + openOnBoardingModal():void; + initCategoreis():void; + } + + export class GeneralViewModel { + + static '$inject' = [ + '$scope', + 'Sdc.Services.CacheService', + 'ValidationPattern', + 'ContactIdValidationPattern', + 'TagValidationPattern', + 'VendorValidationPattern', + 'CommentValidationPattern', + 'ValidationUtils', + 'sdcConfig', + 'ProjectCodeValidationPattern', + '$state', + 'ModalsHandler', + 'EventListenerService', + 'Notification', + 'Sdc.Services.ProgressService', + '$interval', + '$filter', + '$timeout' + ]; + + constructor(private $scope:IGeneralScope, + private cacheService:Services.CacheService, + private ValidationPattern:RegExp, + private ContactIdValidationPattern:RegExp, + private TagValidationPattern:RegExp, + private VendorValidationPattern:RegExp, + private CommentValidationPattern:RegExp, + private ValidationUtils:Sdc.Utils.ValidationUtils, + private sdcConfig:Models.IAppConfigurtaion, + private ProjectCodeValidationPattern:RegExp, + private $state:ng.ui.IStateService, + private ModalsHandler: Sdc.Utils.ModalsHandler, + private EventListenerService:Services.EventListenerService, + private Notification:any, + private progressService:Sdc.Services.ProgressService, + protected $interval:any, + private $filter:ng.IFilterService, + private $timeout:ng.ITimeoutService + ){ + + this.registerToSuccessSaveEvent(); + this.initScopeValidation(); + this.initScopeMethods(); + this.initScope(); + this.$scope.updateSelectedMenuItem(); + } + + private registerToSuccessSaveEvent = ():void => { + // Register to save success to show notification to user. + this.EventListenerService.registerObserverCallback(Utils.Constants.EVENTS.ON_WORKSPACE_SAVE_BUTTON_SUCCESS, this.showSuccessNotificationMessage); + + }; + + private showSuccessNotificationMessage = ():void => { + // In case we import CSAR. Notify user when import VF was finished. + this.Notification.success({ + message: this.$filter('translate')("IMPORT_VF_MESSAGE_CREATE_FINISHED_DESCRIPTION"), + title: this.$filter('translate')("IMPORT_VF_MESSAGE_CREATE_FINISHED_TITLE") + }); + + //set the form Pristine after save to reset the unsaved changes (whit for dom reload) + this.$timeout(()=> { + if(this.$scope.editForm) { + this.$scope.editForm.$setPristine(); + } + }, 500); + + }; + + + + private initScopeValidation = ():void => { + this.$scope.validation = new Validation(); + this.$scope.validation.validationPattern = this.ValidationPattern; + this.$scope.validation.contactIdValidationPattern = this.ContactIdValidationPattern; + this.$scope.validation.tagValidationPattern = this.TagValidationPattern; + this.$scope.validation.vendorValidationPattern = this.VendorValidationPattern; + this.$scope.validation.commentValidationPattern = this.CommentValidationPattern; + this.$scope.validation.projectCodeValidationPattern = this.ProjectCodeValidationPattern; + }; + + private initScope = ():void => { + + // Work around to change the csar version + if (this.cacheService.get(Utils.Constants.CHANGE_COMPONENT_CSAR_VERSION_FLAG)) { + (<Resource>this.$scope.component).csarVersion = this.cacheService.get(Utils.Constants.CHANGE_COMPONENT_CSAR_VERSION_FLAG); + } + + this.$scope.importedToscaBrowseFileText = this.$scope.component.name + " (" + (<Resource>this.$scope.component).csarVersion + ")"; + this.$scope.importCsarProgressKey = "importCsarProgressKey"; + this.$scope.browseFileLabel = this.$scope.component.isResource() && (<Resource>this.$scope.component).resourceType===ResourceType.VF? "Upload file" : "Upload VFC"; + this.$scope.progressService = this.progressService; + + // Workaround to short vendor name to 25 chars + // onboarding send 27 chars, and the validation pattern is 25 chars. + if (this.$scope.component.vendorName){ + this.$scope.component.vendorName = this.$scope.component.vendorName.substr(0, 25); + } + + // Init UIModel + this.$scope.component.tags = _.without(this.$scope.component.tags, this.$scope.component.name); + + // Init categories + this.$scope.initCategoreis(); + + // Init the decision if to show file browse. + this.$scope.isShowFileBrowse = false; + if (this.$scope.component.isResource()){ + let resource:Sdc.Models.Components.Resource = <Sdc.Models.Components.Resource>this.$scope.component; + console.log(resource.name + ": " + resource.csarUUID); + if (resource.importedFile){ // Component has imported file. + this.$scope.isShowFileBrowse = true; + } + if (this.$scope.isEditMode() && resource.resourceType== ResourceType.VF && !resource.csarUUID){ + this.$scope.isShowFileBrowse = true; + } + }; + + // Init the decision if to show onboarding + this.$scope.isShowOnboardingSelectionBrowse = false; + if (this.$scope.component.isResource() && + this.$scope.isEditMode() && + (<Resource>this.$scope.component).resourceType== ResourceType.VF && + (<Resource>this.$scope.component).csarUUID) { + this.$scope.isShowOnboardingSelectionBrowse = true; + } + + //init file extensions based on the file that was imported. + if (this.$scope.component.isResource() && (<Resource>this.$scope.component).importedFile){ + let fileName:string = (<Resource>this.$scope.component).importedFile.filename; + let fileExtension:string = fileName.split(".").pop(); + if (this.sdcConfig.csarFileExtension.indexOf(fileExtension.toLowerCase()) !== -1){ + this.$scope.importedFileExtension = this.sdcConfig.csarFileExtension; + (<Resource>this.$scope.component).importedFile.filetype="csar"; + } else if (this.sdcConfig.toscaFileExtension.indexOf(fileExtension.toLowerCase()) !== -1){ + (<Resource>this.$scope.component).importedFile.filetype="yaml"; + this.$scope.importedFileExtension = this.sdcConfig.toscaFileExtension; + } + }else if(this.$scope.isEditMode()&& (<Resource>this.$scope.component).resourceType === ResourceType.VF){ + this.$scope.importedFileExtension = this.sdcConfig.csarFileExtension; + //(<Resource>this.$scope.component).importedFile.filetype="csar"; + } + + this.$scope.setValidState(true); + + this.$scope.calculateUnique = (mainCategory:string, subCategory:string):string => { + let uniqueId:string = mainCategory; + if (subCategory) { + uniqueId += "_#_" + subCategory; // Set the select category combobox to show the selected category. + } + return uniqueId; + }; + + //TODO remove this after handling contact in UI + if(this.$scope.component.isProduct() && this.$scope.isCreateMode()){ + (<Models.Components.Product>this.$scope.component).contacts = []; + (<Models.Components.Product>this.$scope.component).contacts.push(this.cacheService.get("user").userId); + }else if(this.$scope.isCreateMode()){ + this.$scope.component.contactId = this.cacheService.get("user").userId; + } + + }; + + // Convert category string MainCategory_#_SubCategory to Array with one item (like the server except) + private convertCategoryStringToOneArray = ():Array<Models.IMainCategory> => { + let tmp = this.$scope.component.selectedCategory.split("_#_"); + let mainCategory = tmp[0]; + let subCategory = tmp[1]; + + // Find the selected category and add the relevant sub category. + let selectedMainCategory:IMainCategory = <Models.IMainCategory>_.find(this.$scope.categories, function (item) { + return item["name"] === mainCategory; + + }); + + let mainCategoryClone = angular.copy(selectedMainCategory); + if (subCategory) { + let selectedSubcategory = <Models.ISubCategory>_.find(selectedMainCategory.subcategories, function (item) { + return item["name"] === subCategory; + }); + mainCategoryClone['subcategories'] = [angular.copy(selectedSubcategory)]; + } + let tmpSelected = <Models.IMainCategory> mainCategoryClone; + + let result:Array<Models.IMainCategory> = []; + result.push(tmpSelected); + + return result; + }; + + private updateComponentNameInBreadcrumbs = ():void => { + //update breadcrum after changing name + this.$scope.breadcrumbsModel[1].updateSelectedMenuItemText(this.$scope.component.getComponentSubType() + ': ' + this.$scope.component.name); + this.$scope.updateMenuComponentName(this.$scope.component.name); + }; + + private initScopeMethods = ():void => { + + this.$scope.initCategoreis = ():void => { + if (this.$scope.componentType === Utils.Constants.ComponentType.RESOURCE) { + this.$scope.categories = this.cacheService.get('resourceCategories'); + + } + if (this.$scope.componentType === Utils.Constants.ComponentType.SERVICE) { + this.$scope.categories = this.cacheService.get('serviceCategories'); + } + } + + this.$scope.validateField = (field:any):boolean => { + if (field && field.$dirty && field.$invalid) { + return true; + } + return false; + }; + + this.$scope.openOnBoardingModal=():void => { + let csarUUID = (<Resource>this.$scope.component).csarUUID; + this.ModalsHandler.openOnboadrdingModal('Update', csarUUID).then(()=>{ + // OK + this.$scope.uploadFileChangedInGeneralTab(); + }, ()=>{ + // ERROR + }); + }; + + this.$scope.validateName = (isInit:boolean):void => { + if (isInit === undefined) { + isInit = false; + } + + let name = this.$scope.component.name; + if (!name || name === "") { + if (this.$scope.editForm + && this.$scope.editForm["componentName"] + && this.$scope.editForm["componentName"].$error) { + + // Clear the error name already exists + this.$scope.editForm["componentName"].$setValidity('nameExist', true); + } + + return; + } + //????????????????????????? + let subtype:string = Utils.Constants.ComponentType.RESOURCE == this.$scope.componentType ? this.$scope.component.getComponentSubType() : undefined; + + let onFailed = (response) => { + //console.info('onFaild', response); + //this.$scope.isLoading = false; + }; + + let onSuccess = (validation:Models.IValidate) => { + this.$scope.editForm["componentName"].$setValidity('nameExist', validation.isValid); + if(validation.isValid){ + //update breadcrumb after changing name + this.updateComponentNameInBreadcrumbs(); + } + }; + + if (isInit) { + // When page is init after update + if (this.$scope.component.name !== this.$scope.originComponent.name) { + if (!(this.$scope.componentType===Utils.Constants.ComponentType.RESOURCE && (<Resource>this.$scope.component).csarUUID!==undefined) + ){ + this.$scope.component.validateName(name, subtype).then(onSuccess, onFailed); + } + } + } else { + // Validating on change (has debounce) + if (this.$scope.editForm + && this.$scope.editForm["componentName"] + && this.$scope.editForm["componentName"].$error + && !this.$scope.editForm["componentName"].$error.pattern + && this.$scope.component.name !== this.$scope.originComponent.name + ) { + if (!(this.$scope.componentType===Utils.Constants.ComponentType.RESOURCE && (<Resource>this.$scope.component).csarUUID!==undefined) + ){ + this.$scope.component.validateName(name, subtype).then(onSuccess, onFailed); + } + } else if (this.$scope.component.name === this.$scope.originComponent.name) { + // Clear the error + this.$scope.editForm["componentName"].$setValidity('nameExist', true); + } + } + }; + + this.$scope.$watchCollection('component.name', (newData:any):void => { + this.$scope.validateName(false); + }); + + // Notify the parent if this step valid or not. + this.$scope.$watch("editForm.$valid", (newVal, oldVal) => { + this.$scope.setValidState(newVal); + }); + + this.$scope.$watch("editForm.$dirty", (newVal, oldVal) => { + if (newVal!==oldVal) { + this.$state.current.data.unsavedChanges = newVal && !this.$scope.isCreateMode(); + } + }); + + this.$scope.onCategoryChange = ():void => { + this.$scope.component.categories = this.convertCategoryStringToOneArray(); + this.$scope.component.icon = Utils.Constants.DEFAULT_ICON; + }; + + this.$scope.onVendorNameChange = (oldVendorName:string):void => { + if (this.$scope.component.icon === oldVendorName) { + this.$scope.component.icon = Utils.Constants.DEFAULT_ICON; + } + }; + }; + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/general/general-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/general/general-view.html new file mode 100644 index 0000000000..1c1d4fedad --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/general/general-view.html @@ -0,0 +1,307 @@ +<div include-padding="true" class="sdc-workspace-general-step"> + + <form novalidate class="w-sdc-form" name="editForm"> + + <div class="w-sdc-form-section-container"> + + <!--------------------- IMPORT TOSCA FILE USING BROWSE (ALSO VFC) --------------------> + <div class="i-sdc-form-item" ng-if="isShowFileBrowse"> + <label class="i-sdc-form-label required">{{browseFileLabel}}</label> + <file-upload id="fileUploadElement" + class="i-sdc-form-input" + element-name="fileElement" + element-disabled="{{!isCreateMode()&&!(isEditMode()&&component.resourceType=='VF')}} || {{isViewMode()}}" + form-element="editForm" + file-model="component.importedFile" + on-file-changed-in-directive="uploadFileChangedInGeneralTab" + extensions="{{importedFileExtension}}" + default-text="'Browse to select file'" + data-ng-class="{'error':!(isEditMode()&&component.resourceType=='VF') && (!editForm.fileElement.$valid || !component.importedFile.filename)}"></file-upload> + </div> + + <!--------------------- IMPORT TOSCA FILE USING ONBOARDING --------------------> + <div class="i-sdc-form-item" ng-if="isShowOnboardingSelectionBrowse"> + <label class="i-sdc-form-label required">Select VSP</label> + <div class="i-sdc-form-file-upload i-sdc-form-input"> + <span class="i-sdc-form-file-name" data-tests-id="filename">{{(fileModel && fileModel.filename) || importedToscaBrowseFileText}}</span> + <div class="i-sdc-form-file-upload-x-btn" ng-click="cancel()" data-ng-show="fileModel.filename && fileModel.filename!=='' && elementDisabled!=='true'"></div> + <input type="button" name="fileElement"/> + <div class="file-upload-browse-btn" data-ng-click="openOnBoardingModal()" data-tests-id="browseButton">Browse</div> + </div> + </div> + + <div class="input-error-file-upload" data-ng-show="component.importedFile && (!editForm.fileElement.$valid || !component.importedFile.filename)"> + <!-- editForm.fileElement.$error.required <== Can not use this, because the browse is done from outside for the first time --> + <span ng-show="!(isEditMode()&&component.resourceType=='VF')&&!component.importedFile.filename" translate="NEW_SERVICE_RESOURCE_ERROR_TOSCA_FILE_REQUIRED"></span><!-- Required --> + <span ng-show="editForm.fileElement.$error.maxsize" translate="VALIDATION_ERROR_MAX_FILE_SIZE"></span> + <span ng-show="editForm.fileElement.$error.filetype" translate="NEW_SERVICE_RESOURCE_ERROR_VALID_TOSCA_EXTENSIONS" translate-values="{'extensions': '{{importedFileExtension}}' }"></span> + <span ng-show="editForm.fileElement.$error.emptyFile" translate="VALIDATION_ERROR_EMPTY_FILE"></span> + </div> + <!--------------------- IMPORT TOSCA FILE --------------------> + + <div class="w-sdc-form-columns-wrapper"> + + <div class="w-sdc-form-column"> + + <!--------------------- NAME --------------------> + <div class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.componentName)}"> + <label class="i-sdc-form-label required">Name</label> + <input class="i-sdc-form-input" + data-ng-class="{'view-mode': isViewMode()}" + name="componentName" + data-ng-init="isCreateMode() && validateName(true)" + data-ng-maxlength="{{component.isProduct()?'25':'50'}}" + maxlength="{{component.isProduct()?'25':'50'}}" + data-ng-minlength="{{component.isProduct()?'4':'0'}}" + minlength="{{component.isProduct()?'4':'0'}}" + data-ng-model="component.name" + type="text" + data-required + data-ng-model-options="{ debounce: 500 }" + data-ng-pattern="validation.validationPattern" + data-ng-disabled="component.isAlreadyCertified()" + data-tests-id="name" + autofocus + ng-readonly="isViewMode()" + /> + + <div class="input-error" data-ng-show="validateField(editForm.componentName)"> + <span ng-show="editForm.componentName.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_RESOURCE_NAME_REQUIRED"></span> + <span ng-show="editForm.componentName.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '50' }"></span> + <span ng-show="editForm.componentName.$error.minlength" translate="VALIDATION_ERROR_MIN_LENGTH" translate-values="{'min': '4' }"></span> + <span ng-show="editForm.componentName.$error.nameExist" translate="NEW_SERVICE_RESOURCE_ERROR_NAME_EXISTS"></span> + <span ng-show="editForm.componentName.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span> + </div> + </div> + <!--------------------- NAME --------------------> + + <!--------------------- FULL NAME --------------------> + <div ng-if="component.isProduct()" class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.fullName)}"> + <label class="i-sdc-form-label required">Full Name</label> + <input class="i-sdc-form-input" + name="fullName" + data-ng-class="{'view-mode': isViewMode()}" + data-ng-change="validateName()" + data-ng-maxlength="100" + maxlength="100" + data-ng-minlength="4" + minlength="4" + data-ng-model="component.fullName" + type="text" + data-required + data-ng-model-options="{ debounce: 500 }" + data-ng-pattern="validation.commentValidationPattern" + data-tests-id="fullName" + autofocus + ng-readonly="isViewMode()" + /> + + <div class="input-error" data-ng-show="validateField(editForm.fullName)"> + <span ng-show="editForm.fullName.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_RESOURCE_NAME_REQUIRED"></span> + <span ng-show="editForm.fullName.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '50' }"></span> + <span ng-show="editForm.fullName.$error.minlength" translate="VALIDATION_ERROR_MIN_LENGTH" translate-values="{'min': '4' }"></span> + <span ng-show="editForm.fullName.$error.nameExist" translate="NEW_SERVICE_RESOURCE_ERROR_NAME_EXISTS"></span> + <span ng-show="editForm.fullName.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span> + </div> + </div> + <!--------------------- NAME --------------------> + + <!--------------------- DESCRIPTION --------------------> + <div class="i-sdc-form-item" + data-ng-class="{'error': validateField(editForm.description)}"> + <label class="i-sdc-form-label required">Description</label> + <textarea class="description" + name="description" + data-ng-class="{'view-mode': isViewMode()}" + data-ng-maxlength="1024" + data-required + data-ng-model="component.description" + data-ng-model-options="{ debounce: 500 }" + data-ng-pattern="validation.commentValidationPattern" + maxlength="1024" + data-tests-id="description"></textarea> + <!-- placeholder="Description here..." --> + + <div class="input-error" data-ng-show="validateField(editForm.description)"> + <span ng-show="editForm.description.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_RESOURCE_DESCRIPTION_REQUIRED"></span> + <span ng-show="editForm.description.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '1024' }"></span> + <span ng-show="editForm.description.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span> + </div> + </div> + <!--------------------- DESCRIPTION --------------------> + + <!--------------------- CATEGORIES --------------------> + <div class="i-sdc-form-item" + data-ng-class="{'error': validateField(editForm.category)}" + data-ng-if="!component.isProduct()"> + <loader data-display="!categories && !initCategoreis()" relative="true"></loader> + <label class="i-sdc-form-label required">Category</label> + <select class="i-sdc-form-select" + data-required + name="category" + data-ng-class="{'view-mode': isViewMode()}" + data-ng-change="onCategoryChange()" + data-ng-disabled="component.isAlreadyCertified() || (component.isCsarComponent() && component.selectedCategory && component.selectedCategory!=='')" + data-ng-model="component.selectedCategory" + data-tests-id="selectGeneralCategory" + > + <option value="">Select category</option> + <optgroup ng-if="component.isResource()" data-ng-repeat="mainCategory in categories | orderBy:['name']" label="{{mainCategory.name}}" data-tests-id="{{mainCategory.name}}"> + <option data-ng-repeat="subCategory in mainCategory.subcategories track by $index" + data-ng-selected="component.selectedCategory === calculateUnique(mainCategory.name,subCategory.name)" + data-tests-id="{{subCategory.name}}" + value="{{calculateUnique(mainCategory.name, subCategory.name)}}">{{subCategory.name}} + + </option> + </optgroup> + <option ng-if="component.isService()" data-ng-repeat="mainCategory in categories | orderBy:['name']" + data-ng-selected="component.selectedCategory===mainCategory.name" + value="{{mainCategory.name}}" + data-tests-id="{{mainCategory.name}}">{{mainCategory.name}}</option> + </select> + + <div class="input-error" data-ng-show="validateField(editForm.category)"> + <span ng-show="editForm.category.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_CATEGORY_REQUIRED"></span> + </div> + </div> + <!--------------------- CATEGORIES --------------------> + + <!--------------------- PROJECT CODE --------------------> + <div class="i-sdc-form-item" data-ng-if="!component.isResource()" + data-ng-class="{'error': validateField(editForm.projectCode)}"> + <label class="i-sdc-form-label required" translate="GENERAL_LABEL_PROJECT_CODE"></label> + <input class="i-sdc-form-input" type="text" + data-ng-model="component.projectCode" + data-ng-class="{'view-mode': isViewMode()}" + data-ng-model-options="{ debounce: 500 }" + data-ng-maxlength="128" + data-required + name="projectCode" + data-ng-pattern="validation.projectCodeValidationPattern" + maxlength="50" + data-tests-id="projectCode" + /> + + <div class="input-error" data-ng-show="validateField(editForm.projectCode)"> + <span ng-show="editForm.projectCode.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_PROJECT_CODE_REQUIRED"></span> + <span ng-show="editForm.projectCode.$error.pattern" translate="NEW_SERVICE_RESOURCE_ERROR_PROJECT_CODE_NOT_VALID"></span> + </div> + </div> + + <!--------------------- VENDOR NAME --------------------> + <div ng-if="component.isResource()" class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.vendorName)}"> + <label class="i-sdc-form-label required">Vendor</label> + <input class="i-sdc-form-input" type="text" + data-ng-class="{'view-mode': isViewMode()}" + data-ng-model="component.vendorName" + data-ng-model-options="{ debounce: 500 }" + data-ng-maxlength="25" + data-required + ng-click="oldValue = component.vendorName" + name="vendorName" + data-ng-change="onVendorNameChange(oldValue)" + data-ng-pattern="validation.vendorValidationPattern" + maxlength="25" + data-ng-disabled="component.isAlreadyCertified() || (component.isCsarComponent() && component.vendorName && component.vendorName!=='')" + data-tests-id="vendorName" + /> + + <div class="input-error" data-ng-show="validateField(editForm.vendorName)"> + <span ng-show="editForm.vendorName.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_VENDOR_NAME_REQUIRED"></span> + <span ng-show="editForm.vendorName.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '25' }"></span> + <span ng-show="editForm.vendorName.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span> + </div> + + </div> + + <!--------------------- VENDOR NAME --------------------> + + <!--------------------- VENDOR RELEASE --------------------> + <div ng-if="component.isResource()" + class="i-sdc-form-item" + data-ng-class="{'error': validateField(editForm.vendorRelease)}"> + <label class="i-sdc-form-label required">Vendor Release</label> + <input class="i-sdc-form-input" type="text" + data-ng-class="{'view-mode': isViewMode()}" + data-ng-model="component.vendorRelease" + data-ng-model-options="{ debounce: 500 }" + data-ng-maxlength="25" + data-required + name="vendorRelease" + data-ng-pattern="validation.vendorValidationPattern" + maxlength="25" + data-ng-disabled="component.isCsarComponent() && component.vendorRelease && component.vendorRelease!==''" + data-tests-id="vendorRelease" + /> + + <div class="input-error" data-ng-show="validateField(editForm.vendorRelease)"> + <span ng-show="editForm.vendorRelease.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_VENDOR_RELEASE_REQUIRED"></span> + <span ng-show="editForm.vendorRelease.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '128' }"></span> + <span ng-show="editForm.vendorRelease.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span> + </div> + </div> + <!--------------------- VENDOR RELEASE --------------------> + + + + </div><!-- Close w-sdc-form-column --> + + <div class="w-sdc-form-column"> + + <!--------------------- RESOURCE TAGS --------------------> + <div class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.tags)}"> + <label class="i-sdc-form-label">Tags</label> + + <sdc-tags form-element="editForm" element-name="tags" max-tags="20" class="i-sdc-form-item-tags" + sdc-disabled="isViewMode()" + tags="component.tags" + pattern="validation.tagValidationPattern" + special-tag="component.name"></sdc-tags> + + <div class="input-error" data-ng-show="validateField(editForm.tags)"> + <span ng-show="editForm.tags.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span> + <span ng-show="editForm.tags.$error.nameExist" translate="NEW_SERVICE_RESOURCE_ERROR_TAG_NAME_EXIST"></span> + </div> + </div> + <!--------------------- RESOURCE TAGS --------------------> + + <!--------------------- CONTACT ID --------------------> + <div class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.contactId)}"> + <label class="i-sdc-form-label " data-ng-class="{'required':!component.isProduct()}" translate="GENERAL_LABEL_CONTACT_ID"></label> + <input class="i-sdc-form-input" type="text" data-ng-if="!component.isProduct()" + data-ng-model="component.contactId" + data-ng-class="{'view-mode': isViewMode()}" + data-ng-required="!component.isProduct()" + name="contactId" + data-ng-pattern="validation.contactIdValidationPattern" + data-ng-model-options="{ debounce: 500 }" + data-tests-id="userId" + maxlength="50" + /> + <input class="i-sdc-form-input" type="text" data-ng-if="component.isProduct()" + data-ng-model="component.contacts[0]" + data-ng-class="{'view-mode': isViewMode()}" + data-ng-required="!component.isProduct()" + name="contactId" + data-ng-pattern="validation.contactIdValidationPattern" + data-ng-model-options="{ debounce: 500 }" + data-tests-id="userId" + maxlength="50" + /> + + <div class="input-error" data-ng-show="validateField(editForm.contactId)"> + <span ng-show="editForm.contactId.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_CONTACT_REQUIRED"></span> + <span ng-show="editForm.contactId.$error.pattern" translate="NEW_SERVICE_RESOURCE_ERROR_CONTACT_NOT_VALID"></span> + </div> + </div> + <!--------------------- CONTACT ID --------------------> + + + </div><!-- Close w-sdc-form-column --> + </div> + + </div><!-- Close w-sdc-form-section-container --> + + </form> + +</div> diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/general/general.less b/catalog-ui/app/scripts/view-models/workspace/tabs/general/general.less new file mode 100644 index 0000000000..1861d02e98 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/general/general.less @@ -0,0 +1,64 @@ +.sdc-workspace-general-step { + + .w-sdc-form { + padding: 0; + + .i-sdc-form-file-upload{ + input[type="button"] { + cursor: pointer; + display: block; + filter: alpha(opacity=0); + width: 100px; + height: 30px; + opacity: 0; + position: absolute; + right: 0; + text-align: right; + top: 0; + } + + .file-upload-browse-btn { + .noselect; + .bg_n; + padding: 4px 6px; + cursor: pointer; + z-index: 999; + width: 100px; + height: 28px; + text-align: center; + + &.disabled { + cursor: default; + } + } + } + + .w-sdc-form-section-container { + text-align: center; + } + + .i-sdc-form-item { + &.upload { + margin-top: 0; + width: auto; + padding: 10px; + } + } + + .template-desc { + border: 1px dashed @border_color_f; + height: 130px; + overflow: hidden; + padding: 10px 6px 6px 6px; + margin-top: 10px; + } + + .sdc-tag .tag { + max-width: 225px; + } + + } + +} + + diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/icons/icons-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/icons/icons-view-model.ts new file mode 100644 index 0000000000..a591641d0a --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/icons/icons-view-model.ts @@ -0,0 +1,131 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ +/** + * Created by obarda on 4/4/2016. + */ +/// <reference path="../../../../references"/> +module Sdc.ViewModels { + 'use strict'; + + export interface IIconsScope extends IWorkspaceViewModelScope { + icons : Array<string>; + iconSprite: string; + setComponentIcon(iconSrc:string): void; + } + + export class IconsViewModel { + + static '$inject' = [ + '$scope', + 'Sdc.Services.AvailableIconsService', + 'ComponentFactory', + '$state' + ]; + + constructor(private $scope:IIconsScope, + private availableIconsService:Services.AvailableIconsService, + private ComponentFactory:Sdc.Utils.ComponentFactory, + private $state:ng.ui.IStateService) { + + + this.initScope(); + this.initIcons(); + this.$scope.updateSelectedMenuItem(); + this.$scope.iconSprite = this.$scope.component.iconSprite; + + if (this.$scope.component.isResource()) { + this.initVendor(); + } + } + + private initialIcon:string = this.$scope.component.icon; + private initIcons = ():void => { + + // For subcategories that where created by admin, there is no icons + this.$scope.icons = new Array<string>(); + if (this.$scope.component.categories && this.$scope.component.categories.length > 0) { + + _.forEach(this.$scope.component.categories, (category:Models.IMainCategory):void => { + if (category.icons) { + this.$scope.icons = this.$scope.icons.concat(category.icons); + } + if (category.subcategories) { + _.forEach(category.subcategories, (subcategory:Models.ISubCategory):void => { + if (subcategory.icons) { + this.$scope.icons = this.$scope.icons.concat(subcategory.icons); + } + }); + } + }); + } + + if (this.$scope.component.isResource()) { + let resourceType:string = this.$scope.component.getComponentSubType(); + if (resourceType === 'VL') { + this.$scope.icons = ['vl']; + } + if (resourceType === 'CP') { + this.$scope.icons = ['cp']; + } + } + + if (this.$scope.icons.length === 0) { + this.$scope.icons = this.availableIconsService.getIcons(this.$scope.component.componentType); + } + //we always add the defual icon to the list + this.$scope.icons.push('defaulticon'); + }; + + private initVendor = ():void => { + let vendors:Array<string> = this.availableIconsService.getIcons(this.$scope.component.componentType).slice(5, 19); + let vendorName = this.$scope.component.vendorName.toLowerCase(); + if ('at&t' === vendorName) { + vendorName = 'att'; + } + if ('nokia' === vendorName) { + vendorName = 'nokiasiemens'; + } + + let vendor:string = _.find(vendors, (vendor:string)=> { + return vendor.replace(/[_]/g, '').toLowerCase() === vendorName; + }); + + if (vendor && this.$scope.icons.indexOf(vendor) === -1) { + this.$scope.icons.push(vendor); + } + }; + + private initScope():void { + this.$scope.icons = []; + this.$scope.setValidState(true); + //if(this.$scope.component.icon === Utils.Constants.DEFAULT_ICON){ + // //this.$scope.setValidState(false); + //} + + this.$scope.setComponentIcon = (iconSrc:string):void => { + this.$state.current.data.unsavedChanges = !this.$scope.isViewMode() && (iconSrc != this.initialIcon); + this.$scope.component.icon = iconSrc; + // this.$scope.setValidState(true); + }; + + } + + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/icons/icons-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/icons/icons-view.html new file mode 100644 index 0000000000..aac14e0e84 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/icons/icons-view.html @@ -0,0 +1,26 @@ +<div class="workspace-icons"> + + <form novalidate class="w-sdc-form" name="iconForm"> + <label class="i-sdc-form-label icons-label required">Icons</label> + <div class="selected-icon-container" data-ng-class="{'view-mode': isViewMode()}"> + <div class="i-sdc-form-item-suggested-icon large selected-icon {{iconSprite}} {{component.icon}}" + data-ng-class="{ 'disable': isViewMode() }" + ng-model="component.icon" + tooltips tooltip-content='{{component.icon | translate}}' + > + </div> + </div> + <div data-ng-class="{'view-mode': isViewMode()}" class="icons-text">Select one of the icons below for the asset</div> + <div class="i-sdc-form-item suggested-icons-container" data-ng-class="{'view-mode no-pointer-events' : isViewMode()}"> + <div class ="suggested-icon-wrapper" ng-class="component.icon==='{{iconSrc}}' ? 'selected' : '' " data-ng-repeat="iconSrc in icons track by $index"> + <div class="i-sdc-form-item-suggested-icon large {{iconSprite}} {{iconSrc}}" data-ng-class="component.isAlreadyCertified() || isViewMode() ? 'disable':'hand'" + ng-model="component.icon" + data-tests-id="{{iconSrc}} iconBox" + data-ng-click="!component.isAlreadyCertified() && setComponentIcon(iconSrc)" + tooltips tooltip-content='{{iconSrc | translate}}' + > + </div> + </div> + </div> + </form> +</div> diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/icons/icons.less b/catalog-ui/app/scripts/view-models/workspace/tabs/icons/icons.less new file mode 100644 index 0000000000..65f946f395 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/icons/icons.less @@ -0,0 +1,65 @@ +.workspace-icons { + + width: 89%; + display: inline-block; + text-align: center; + align-items: center; + + .w-sdc-form { + padding-top: 0px; + padding-bottom: 0px; + .selected-icon-container { + text-align: left; + border: 1px solid #cfcfcf; + clear: both; + margin-bottom: 30px; + padding: 2px 0px 5px 5px; + .selected-icon { + margin: 8px 5px 0px 6px; + } + } + + .suggested-icons-container { + text-align: left; + border: 1px solid #cfcfcf; + clear: both; + padding: 2px 0px 5px 5px; + height: 340px; + margin-bottom: 0px; + + .suggested-icon-wrapper { + margin: 8px 5px 0px 6px; + display: inline-block; + + &.selected { + border: 2px solid @main_color_a; + border-radius: 35px; + display: inline-block; + line-height: 0px; + padding: 3px; + } + + } + .suggested-icon { + // margin: 8px 5px 0px 6px; + display: inline-block; + &.disable{ + opacity: 0.4; + } + } + } + + .icons-label { + float: left; + } + + .icons-text { + text-align: left; + line-height: 32px; + padding-left: 10px; + width: 100%; + border: 1px solid #cfcfcf; + border-bottom: none; + } + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/information-artifacts/information-artifacts-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/information-artifacts/information-artifacts-view-model.ts new file mode 100644 index 0000000000..3a048c1879 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/information-artifacts/information-artifacts-view-model.ts @@ -0,0 +1,150 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../../references"/> +module Sdc.ViewModels { + 'use strict'; + import ArtifactModel = Sdc.Models.ArtifactModel; + + export interface IInformationArtifactsScope extends IWorkspaceViewModelScope { + artifacts: Array<Models.ArtifactModel>; + tableHeadersList: Array<any>; + artifactType: string; + isResourceInstance:boolean; + downloadFile:Models.IFileDownload; + isLoading:boolean; + sortBy:string; + reverse:boolean; + + getTitle(): string; + addOrUpdate(artifact:Models.ArtifactModel): void; + delete(artifact:Models.ArtifactModel): void; + download(artifact:Models.ArtifactModel): void; + clickArtifactName(artifact:any):void; + openEditEnvParametersModal(artifactResource:Models.ArtifactModel):void; + sort(sortBy:string): void; + showNoArtifactMessage():boolean; + } + + export class InformationArtifactsViewModel { + + static '$inject' = [ + '$scope', + '$filter', + '$modal', + '$templateCache', + 'Sdc.Services.SharingService', + '$state', + 'sdcConfig', + 'ModalsHandler' + ]; + + constructor(private $scope:IInformationArtifactsScope, + private $filter:ng.IFilterService, + private $modal:ng.ui.bootstrap.IModalService, + private $templateCache:ng.ITemplateCacheService, + private sharingService:Sdc.Services.SharingService, + private $state:any, + private sdcConfig:Models.IAppConfigurtaion, + private ModalsHandler:Utils.ModalsHandler) { + this.initScope(); + this.$scope.updateSelectedMenuItem(); + } + + + private getMappedObjects():any { + return { + normal: this.$scope.component.artifacts + }; + } + + private initScope = ():void => { + let self = this; + this.$scope.isLoading = false; + this.$scope.sortBy = 'artifactDisplayName'; + this.$scope.reverse = false; + this.$scope.setValidState(true); + this.$scope.artifactType = 'normal'; + this.$scope.getTitle = ():string => { + return this.$filter("resourceName")(this.$scope.component.name) + ' Artifacts'; + + }; + + this.$scope.tableHeadersList = [ + {title: 'Name', property: 'artifactDisplayName'}, + {title: 'Type', property: 'artifactType'} + ]; + + this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.artifacts); + this.$scope.sort = (sortBy:string):void => { + this.$scope.reverse = (this.$scope.sortBy === sortBy) ? !this.$scope.reverse : false; + this.$scope.sortBy = sortBy; + }; + + + this.$scope.addOrUpdate = (artifact:Models.ArtifactModel):void => { + artifact.artifactGroupType = 'INFORMATIONAL'; + this.ModalsHandler.openWizardArtifactModal(artifact, this.$scope.component).then(() => { + this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.artifacts); + }); + }; + + this.$scope.showNoArtifactMessage = ():boolean => { + let artifacts:any = []; + artifacts = _.filter(this.$scope.artifacts, (artifact:Models.ArtifactModel)=> { + return artifact.esId; + }); + + if (artifacts.length === 0) { + return true; + } + return false; + }; + + this.$scope.delete = (artifact:Models.ArtifactModel):void => { + + let onOk = ():void => { + this.$scope.isLoading = true; + let onSuccess = ():void => { + this.$scope.isLoading = false; + this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.artifacts); + }; + + let onFailed = (error:any):void => { + console.log('Delete artifact returned error:', error); + this.$scope.isLoading = false; + }; + + this.$scope.component.deleteArtifact(artifact.uniqueId, artifact.artifactLabel).then(onSuccess, onFailed); + }; + + let title:string = this.$filter('translate')("ARTIFACT_VIEW_DELETE_MODAL_TITLE"); + let message:string = this.$filter('translate')("ARTIFACT_VIEW_DELETE_MODAL_TEXT", "{'name': '" + artifact.artifactDisplayName + "'}"); + this.ModalsHandler.openConfirmationModal(title, message, false).then(onOk); + }; + + this.$scope.clickArtifactName = (artifact:any) => { + if (!artifact.esId) { + this.$scope.addOrUpdate(artifact); + } + + }; + } + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/information-artifacts/information-artifacts-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/information-artifacts/information-artifacts-view.html new file mode 100644 index 0000000000..790117b2fd --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/information-artifacts/information-artifacts-view.html @@ -0,0 +1,57 @@ +<div class="workspace-information-artifact"> + <div data-tests-id="add-information-artifact-button" ng-if="!isViewMode()" + data-ng-class="{'disabled': isDisableMode()}" + data-tests-id="addGrey" class="add-btn" data-ng-click="addOrUpdate({})" type="button">Add </div> + <div class="table-container-flex"> + <div class="table" data-ng-class="{'view-mode': isViewMode()}"> + <div class="head flex-container"> + <div class="table-header head-row hand flex-item" ng-repeat="header in tableHeadersList track by $index" data-ng-click="sort(header.property)">{{header.title}} + <span data-ng-show="sortBy === header.property" class="table-header-sort-arrow" data-ng-class="{'down': reverse, 'up':!reverse}"> </span> + </div> + <div class="table-no-text-header head-row flex-item"></div> + </div> + <div class="body"> + <perfect-scrollbar suppress-scroll-x="true" scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container"> + <div data-ng-if="showNoArtifactMessage()" class="no-row-text" data-ng-class="{'disabled': isDisableMode()}"> + There are no information artifacts to display + </div> + <div data-ng-repeat-start="artifact in artifacts| orderBy:sortBy:reverse track by $index" data-tests-id="InformationalArtifactRow" + class="flex-container data-row" + data-ng-class="{'selected': artifact.selected}" + data-ng-if="artifact.esId"> + + <div class="table-col-general flex-item" data-ng-click="artifact.selected = !artifact.selected"> + <span class="sprite table-arrow" data-ng-class="{'opened': artifact.selected}" data-tests-id="{{artifact.artifactDisplayName}}"></span> + {{artifact.artifactDisplayName}} + </div> + + <div class="table-col-general flex-item" data-ng-click="artifact.selected = !artifact.selected" data-tests-id="{{artifact.artifactType}}"> + {{artifact.artifactType}} + </div> + + <div class="table-btn-col flex-item"> + <button class="table-edit-btn" data-tests-id="edit_{{artifact.artifactDisplayName}}" data-ng-if="!isViewMode() && !artifact.isThirdParty()" data-ng-click="addOrUpdate(artifact)" data-ng-class="{'disabled': isDisableMode()}"></button> + <button class="table-delete-btn" data-tests-id="delete_{{artifact.artifactDisplayName}}" data-ng-if="!isViewMode() && !artifact.isThirdParty()" data-ng-click="delete(artifact)" data-ng-class="{'disabled': isDisableMode()}"> </button> + <button class="table-download-btn" download-artifact data-tests-id="download_{{artifact.artifactDisplayName}}" + data-ng-if="artifact.artifactName" component="component" artifact="artifact"></button> + </div> + </div> + <div data-ng-repeat-end="" data-ng-if="artifact.selected" class="item-opened" data-tests-id="{{artifact.artifactDisplayName}}Description" data-ng-bind="artifact.description"></div> + <button class="add-button" data-ng-repeat="artifact in artifacts track by $index" + data-ng-show="!artifact.esId" + data-tests-id="{{artifact.artifactDisplayName}}" + ng-if="!isViewMode()" + data-ng-class="{'disabled': isDisableMode()}" + translate="DEPLOYMENT_ARTIFACT_BUTTON_ADD_HEAT" + translate-values="{'name': '{{artifact.artifactDisplayName}}'}" + data-ng-click="addOrUpdate(artifact)"></button> + <button class="add-button" + ng-if="!isViewMode()" + data-ng-class="{'disabled': isDisableMode()}" + translate="DEPLOYMENT_ARTIFACT_BUTTON_ADD_OTHER" + data-ng-click="addOrUpdate({})"></button> + </perfect-scrollbar> + </div> + </div> + </div> +</div> diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/information-artifacts/information-artifacts.less b/catalog-ui/app/scripts/view-models/workspace/tabs/information-artifacts/information-artifacts.less new file mode 100644 index 0000000000..d3fe14d945 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/information-artifacts/information-artifacts.less @@ -0,0 +1,47 @@ +.workspace-information-artifact { + width: 93%; + display: inline-block; + .w-sdc-classic-btn { + float: right; + margin-bottom: 10px; + } + + .table{ + height: 490px; + margin-bottom: 0; + } + + .table-container-flex { + margin-top: 27px; + + .item-opened{ + word-wrap: break-word; + } + + + .flex-item:nth-child(1) { + flex-grow: 15; + .hand; + span.table-arrow { + margin-right: 7px; + } + } + + .flex-item:nth-child(2) { + flex-grow: 6; + } + + .flex-item:nth-child(3) { + flex-grow: 3; + padding-top: 10px; + } + + .flex-item:nth-child(4) { + flex-grow: 1; + } + + } + +} + + diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/inputs.less b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/inputs.less new file mode 100644 index 0000000000..76a82c69ee --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/inputs.less @@ -0,0 +1,286 @@ +.workspace-inputs { + + .sdc-workspace-container .w-sdc-main-right-container .w-sdc-main-container-body-content { + padding: 25px 8% 0px 8%; + } + + width: 100%; + display: flex; + + .text { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + padding-left: 15px; + } + + .title-text { + color: @main_color_m; + .f-type._13_m; + .bold; + } + + .title-blue-text { + color: @main_color_a; + .f-type._13_m; + + &.property-name-text { + padding-right: 13px; + border-right: 1px solid rgba(120, 136, 148, 0.26); + flex-grow: 1; + } + } + + .instance-name-text { + flex-grow: 1; + } + + ng-include { + width: 45%; + } + + .w-sdc-inputs-search { + padding: 10px 20px 20px 0; + white-space: nowrap; + position: relative; + width: 60%; + height: 64px; + + .inputs-search-icon { + top: 9px; + right: 11px; + } + + .magnification-white { + .sprite-new; + .search-white-icon; + .hand; + } + + .search-icon-container { + width: 35px; + height: 30px; + background-color: @main_color_a; + white-space: nowrap; + float: right; + position: relative; + bottom: 31px; + right: 1px; + border-radius: 0px 4px 4px 0px; + .hand + } + } + + .total-inputs-count { + width: 100%; + font-weight: bold; + text-align: left; + } + + .new-input-button { + margin: 9px 0 0 0; + } + + .w-sdc-inputs-search-input { + border: 1px solid @color_e; + .border-radius(4px); + height: 32px; + margin: 0; + padding: 0px 28px 3px 10px; + vertical-align: 4px; + width: 100%; + outline: none; + font-style: italic; + } + + .w-sdc-classic-btn { + float: right; + margin-bottom: 10px; + } + + .prop-to-input-button { + position: absolute; + top: 50%; + margin-right: -20px; + margin-bottom: -10px; + + } + + .property-row { + border-bottom: 1px solid @border_color_d; + padding-left: 10px; + .property-name-container { + display: flex; + flex-grow: 4; + } + + .type-schema-container { + flex-grow: 1; + border-left: 1px solid @border_color_d; + text-align: left; + line-height: 30px; + text-transform: capitalize; + width: 10px; + } + + } + + .table-container-flex { + + .flex-item { + line-height: 22px; + } + .expand-collapse-table-row { + + &.expanded { + .flex-container { + .expand-collapse-inputs-table-icon { + transform: rotate(180deg); + left: 0px; + } + } + } + + .data-row { + background: @tlv_color_u; + .hand; + align-items: center; + padding: 0 12px; + margin: 1px 0px 1px 0px; + min-height: 40px; + } + + .data-row:hover { + .bg_j; + } + + .input-row { + padding-left: 45px; + background: @tlv_color_t; + border: @main_color_o solid 1px; + align-items: center; + .hand; + margin: 1px 0px 1px 0px; + + &.service-input-row { + background: @tlv_color_u; + &.new-input { + background: @tlv_color_v; + } + } + &.selected { + background-color: @tlv_color_v; + } + .flex-item { + min-height: 60px; + padding: 0 15px; + } + .input-check-box { + padding-right: 10px; + margin-top: 9px; + } + &>.title-text{ + text-align: start; + padding: 4px 0 0 35px; + } + + .expand-collapse-inputs-table-icon{ + margin-top: 15px; + } + + } + + .input-row:hover { + .bg_j; + } + + + } + } + + .table { + height: 640px; + margin-bottom: 0; + clear: both; + + .empty-row { + padding: 3px; + } + + .flex-item { + line-height: 22px; + } + + .table-header { + + line-height: 14px; + background-color: @main_color_a; + color: @main_color_p; + text-align: left; + padding: 7px 5px 7px 10px; + .f-type._14_m; + } + .head { + background-color: #e6e6e6; + } + + .property-row:hover{ + background-color: @func_color_r; + } + + .body { + .scrollbar-container { + .perfect-scrollbar; + max-height: 610px; + } + + .expand-collapse-inputs-table-icon { + .hand; + .sprite-new; + .arrow-up; + transition: .3s all; + position: relative; + left: 8px; + border: none !important; + padding: 0px 10px 0px 10px; + } + + .table-col-text { + margin-left: 14px; + } + } + } + + .inputs-header { + width: 100%; + position: relative; + bottom: 31px; + } + + .inputs-tables-container { + width: 100%; + min-width: 100%; + display: flex; + } + + .inputs-button-container { + width: 8%; + min-width: 8%; + display: flex; + + .right-arrow-btn { + .sprite-new; + .blue-right-arrow-circle; + margin: auto; + cursor: pointer; + } + } + + .table-container-flex { + margin-top: 27px; + width: 46%; + min-width: 46%; + display: inline-block; + float: left; + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/resource-input/resource-inputs-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/resource-input/resource-inputs-view-model.ts new file mode 100644 index 0000000000..2dc1b1d9ff --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/resource-input/resource-inputs-view-model.ts @@ -0,0 +1,145 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../../../references"/> +module Sdc.ViewModels { + 'use strict'; + import Dictionary = Sdc.Utils.Dictionary; + import InputModel = Sdc.Models.InputModel; + + export interface IInputsViewModelScope extends IWorkspaceViewModelScope { + InstanceInputsProperties:Models.InstanceInputsPropertiesMapData; //this is tha map object that hold the selected inputs and the inputs we already used + vfInstancesList: Array<Models.ComponentsInstances.ComponentInstance>; + component:Models.Components.Resource; + + onArrowPressed():void; + getInputPropertiesForInstance(instanceId:string, instance:Models.ComponentsInstances.ComponentInstance): ng.IPromise<boolean> ; + loadInputPropertiesForInstance(instanceId:string, input:Models.InputModel): ng.IPromise<boolean> ; + loadInputInputs(input:Models.InputModel): ng.IPromise<boolean>; + } + + export class ResourceInputsViewModel { + + static '$inject' = [ + '$scope', + '$q' + ]; + + constructor(private $scope:IInputsViewModelScope, private $q: ng.IQService) { + this.initScope(); + } + + private initScope = (): void => { + + this.$scope.InstanceInputsProperties = new Models.InstanceInputsPropertiesMapData(); + this.$scope.vfInstancesList = this.$scope.component.componentInstances; + + // Need to cast all inputs to InputModel for the search to work + let tmpInputs:Array<Models.InputModel> = new Array<Models.InputModel>(); + _.each(this.$scope.component.inputs, (input):void => { + tmpInputs.push(new Models.InputModel(input)); + }); + this.$scope.component.inputs = tmpInputs; + // This function is not supported for resource + //this.$scope.component.getComponentInputs(); + + /* + * When clicking on instance input in the left or right table, this function will load all properties of the selected input + */ + this.$scope.getInputPropertiesForInstance = (instanceId:string, instance:Models.ComponentsInstances.ComponentInstance): ng.IPromise<boolean> => { + let deferred = this.$q.defer(); + instance.properties = this.$scope.component.componentInstancesProperties[instanceId]; + deferred.resolve(true); + return deferred.promise; + }; + + /* + * When clicking on input in the right table, this function will load all inputs of the selected input + */ + this.$scope.loadInputInputs = (input:Models.InputModel): ng.IPromise<boolean> => { + let deferred = this.$q.defer(); + + let onSuccess = () => { deferred.resolve(true); }; + let onError = () => { deferred.resolve(false); }; + + if(!input.inputs) { + this.$scope.component.getResourceInputInputs(input.uniqueId).then(onSuccess, onError); + } else { + deferred.resolve(true); + } + return deferred.promise; + }; + + /* + * When clicking on instance input in the left or right table, this function will load all properties of the selected input + */ + this.$scope.loadInputPropertiesForInstance = (instanceId:string, input:Models.InputModel): ng.IPromise<boolean> => { + let deferred = this.$q.defer(); + + let onSuccess = (properties:Array<Models.PropertyModel>) => { + input.properties = properties; + deferred.resolve(true); + }; + + let onError = () => { + deferred.resolve(false) + }; + + if(!input.properties) { + this.$scope.component.getComponentInstanceInputProperties(instanceId, input.uniqueId).then(onSuccess, onError); + } else { + deferred.resolve(true); + } + return deferred.promise; + }; + + /* + * When pressing the arrow, we create service inputs from the inputs selected + */ + this.$scope.onArrowPressed = ():void => { + let onSuccess = (inputsCreated: Array<Models.InputModel>) => { + + //disabled all the inputs in the left table + _.forEach(this.$scope.InstanceInputsProperties, (properties:Array<Models.PropertyModel>) => { + _.forEach(properties, (property:Models.PropertyModel) => { + property.isAlreadySelected = true; + }); + }); + + // Adding color to the new inputs (right table) + _.forEach(inputsCreated, (input) => { + input.isNew = true; + }); + + // Removing color to the new inputs (right table) + setTimeout(() => { + _.forEach(inputsCreated, (input) => { + input.isNew = false; + }); + this.$scope.$apply(); + }, 3000); + }; + + this.$scope.component.createInputsFormInstances(this.$scope.InstanceInputsProperties).then(onSuccess); + }; + + } + + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/resource-input/resource-inputs-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/resource-input/resource-inputs-view.html new file mode 100644 index 0000000000..7cdf5a2fa4 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/resource-input/resource-inputs-view.html @@ -0,0 +1,136 @@ +<div class="workspace-inputs"> + <div class="table-container-flex"> + <div class="w-sdc-inputs-search pull-left hideme"> + <input type="text" class="w-sdc-inputs-search-input" placeholder="Search"/> + <div class="search-icon-container"> + <span class="w-sdc-search-icon inputs-search-icon magnification-white"></span> + </div> + </div> + <div class="table"> + <div class="table-header">VFC instances inputs</div> + <div class="body"> + <div class="table-loader" ng-class="{'tlv-loader large loader': isLoading}"></div> + <perfect-scrollbar scroll-y-margin-offset="0" class="scrollbar-container"> + + <expand-collapse expanded-selector=".vf-instance-list.{{$index}}" + class="expand-collapse-table-row" + load-data-function="getInputPropertiesForInstance(instance.uniqueId, instance)" + is-close-on-init="true" + data-ng-repeat-start="instance in vfInstancesList track by $index"> + <div class="flex-container data-row"> + <div class="expand-collapse-inputs-table-icon"></div> + <div class="table-col-general flex-item text"> + <span class="title-text">{{instance.name}}</span> + </div> + </div> + + </expand-collapse> + + <div data-ng-repeat-end="" class="vf-instance-list {{$index}}"> + + <div class="empty-row" ng-if="instance.properties.length===0">No properties to display</div> + + <div ng-repeat="property in instance.properties track by $index"> + <div class="property-row flex-container"> + <div class="flex-item text property-name-container"> + <span class="title-blue-text property-name-text">{{property.name}}</span> + <span class="text instance-name-text">{{property.name}}</span> + </div> + <div class="type-schema-container"> + <div class="text"> + <span>{{property.type}}</span> + </div> + </div> + <div class="type-schema-container"> + <div class="text"> + <span>{{property.schema.property.type}} </span> + </div> + </div> + <!--<sdc-checkbox + class="type-schema-container input-check-box" + disabled ="property.isAlreadySelected" + sdc-checklist-model="InstanceInputsProperties[instance.uniqueId]" + sdc-checklist-value="property" + data-ng-click="$event.stopPropagation()"></sdc-checkbox>--> + </div> + </div> + + </div> + + </perfect-scrollbar> + </div> + </div> + </div> + + <div class="inputs-button-container pull-left"> + <!--<div ng-click="onArrowPressed()" class="right-arrow-btn"></div>--> + </div> + + <div class="table-container-flex"> + <div class="w-sdc-inputs-search pull-left"> + <input type="text" class="w-sdc-inputs-search-input" data-ng-model="search.filterTerm" placeholder="Search" data-ng-model-options="{debounce: 200}"/> + <div class="search-icon-container"> + <span class="w-sdc-search-icon inputs-search-icon magnification-white"></span> + </div> + </div> + <div class="table"> + <div class="body"> + <div class="table-header">Resource instance inputs</div> + <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container"> + <expand-collapse expanded-selector=".resource-inputs.{{$index}}" + class="expand-collapse-table-row" + load-data-function="loadInputPropertiesForInstance(resourceInput.uniqueId, resourceInput)" + is-close-on-init="true" + data-ng-repeat-start="resourceInput in component.inputs | filter:search track by $index "> + <div class="input-row service-input-row"> + <div class="title-text">{{resourceInput.name}}</div> + <div class="flex-container" ng-class="resourceInput.isNew ? 'new-input': 'service-input-row'"> + <div class="expand-collapse-inputs-table-icon"></div> + <div class="flex-item"> + <div> + <span class="title-text">Description:</span> + <span>{{resourceInput.description}}</span> + </div> + </div> + <div class="flex-item "> + <div class="text"> + <span class="title-text">VF Instance:</span> + <span>{{resourceInput.name}}</span> + </div> + <div class="text"> + <span class="title-text">Type:</span> + <span>{{resourceInput.type}} </span> + </div> + </div> + </div> + </div> + </expand-collapse> + + <div data-ng-repeat-end="" class="input-inputs-list resource-inputs {{$index}}"> + <div class="empty-row" ng-if="resourceInput.properties.length===0">No properties to display</div> + <div ng-repeat="property in resourceInput.properties track by $index"> + <div class="property-row flex-container"> + <div class="flex-item text property-name-container"> + <span + class="title-blue-text property-name-text">{{property.name}}</span> + <span class="text instance-name-text">{{property.name}}</span> + </div> + <div class="type-schema-container"> + <div class="text"> + <span>{{property.type}}</span> + </div> + </div> + <div class="type-schema-container"> + <div class="text"> + <span>{{property.schema.property.type}} </span> + </div> + </div> + </div> + </div> + </div> + + </perfect-scrollbar> + </div> + </div> + </div> +</div> diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/resource-input/resource-inputs.less b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/resource-input/resource-inputs.less new file mode 100644 index 0000000000..ebb32fbdb2 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/resource-input/resource-inputs.less @@ -0,0 +1,9 @@ +.workspace-inputs { + + .property-row { + .input-check-box { + text-align: center; + } + } + +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/service-input/service-inputs-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/service-input/service-inputs-view-model.ts new file mode 100644 index 0000000000..6c8391720a --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/service-input/service-inputs-view-model.ts @@ -0,0 +1,246 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../../../references"/> +module Sdc.ViewModels { + 'use strict'; + import IAngularEvent = angular.IAngularEvent; + import ComponentInstance = Sdc.Models.ComponentsInstances.ComponentInstance; + + + interface IServiceInputsViewModelScope extends IWorkspaceViewModelScope { + + vfInstancesList: Array<ComponentInstance>; + selectedInputs:Array<Models.InputModel>; + instanceInputsMap:Models.InstancesInputsMapData; //this is tha map object that hold the selected inputs and the inputs we already used + component:Models.Components.Service; + sdcMenu:Models.IAppMenu; + + onArrowPressed():void; + loadComponentInputs(): void; + loadInstanceInputs(instance:ComponentInstance): ng.IPromise<boolean> ; + loadInputPropertiesForInstance(instanceId:string, input:Models.InputModel): ng.IPromise<boolean> ; + loadInputInputs(input:Models.InputModel): ng.IPromise<boolean>; + deleteInput(input:Models.InputModel):void + } + + export class ServiceInputsViewModel { + + static '$inject' = [ + '$scope', + '$q', + 'ModalsHandler' + ]; + + constructor(private $scope:IServiceInputsViewModelScope, + private $q: ng.IQService, + private ModalsHandler: Sdc.Utils.ModalsHandler) { + this.initScope(); + } + + /* + * When loading the screen again, we need to disabled the inputs that already created on the service, + * we do that by comparing the service input name, to the instance name + '_' + the resource instance input name. + */ + private disableEnableSelectedInputs = (instance: ComponentInstance): void => { + + let alreadySelectedInput = new Array<Models.InputModel>(); + _.forEach(instance.inputs, (input:Models.InputModel) => { + let expectedServiceInputName = instance.normalizedName + '_' + input.name; + let inputAlreadyInService: Models.InputModel = _.find(this.$scope.component.inputs, (serviceInput: Models.InputModel) => { + return serviceInput.name === expectedServiceInputName; + }); + if(inputAlreadyInService) { + input.isAlreadySelected = true; + alreadySelectedInput.push(input); + } else { + input.isAlreadySelected = false; + } + }); + this.$scope.instanceInputsMap[instance.uniqueId] = alreadySelectedInput; + }; + + private initScope = (): void => { + + this.$scope.instanceInputsMap = new Models.InstancesInputsMapData(); + this.$scope.isLoading = true; + this.$scope.selectedInputs = new Array<Models.InputModel>(); + + // Why do we need this? we call this later. + //this.$scope.component.getComponentInputs(); + + let onSuccess = (componentInstances:Array<ComponentInstance>) => { + console.log("component instances loaded: ", componentInstances); + this.$scope.vfInstancesList = componentInstances; + this.$scope.isLoading = false; + }; + + //This function will get al component instance for the left table - in future the instances will be filter according to search text + this.$scope.component.getComponentInstancesFilteredByInputsAndProperties().then(onSuccess); + + // This function will get the service inputs for the right table + this.$scope.component.getComponentInputs(); + + + /* + * When clicking on instance in the left table, this function will load all instance inputs + */ + this.$scope.loadInstanceInputs = (instance:ComponentInstance): ng.IPromise<boolean> => { + let deferred = this.$q.defer(); + + let onSuccess = (inputs:Array<Models.InputModel>) => { + instance.inputs = inputs; + this.disableEnableSelectedInputs(instance); + deferred.resolve(true); + }; + + let onError = () => { + deferred.resolve(false); + }; + + if(!instance.inputs) { + this.$scope.component.getComponentInstanceInputs(instance.uniqueId, instance.componentUid).then(onSuccess, onError); + this.disableEnableSelectedInputs(instance); + } else { + deferred.resolve(true); + } + return deferred.promise; + }; + + /* + * When clicking on instance input in the left or right table, this function will load all properties of the selected input + */ + this.$scope.loadInputPropertiesForInstance = (instanceId:string, input:Models.InputModel): ng.IPromise<boolean> => { + let deferred = this.$q.defer(); + + let onSuccess = (properties:Array<Models.PropertyModel>) => { + input.properties = properties; + deferred.resolve(true); + }; + + let onError = () => { + deferred.resolve(false) + }; + + if(!input.properties) { + this.$scope.component.getComponentInstanceInputProperties(instanceId, input.uniqueId).then(onSuccess, onError); + } else { + deferred.resolve(true); + } + return deferred.promise; + }; + + /* + * When clicking on input in the right table, this function will load all inputs of the selected input + */ + this.$scope.loadInputInputs = (input:Models.InputModel): ng.IPromise<boolean> => { + let deferred = this.$q.defer(); + + let onSuccess = () => { deferred.resolve(true); }; + let onError = () => { deferred.resolve(false); }; + + if(!input.inputs) { // Caching, if exists do not get it. + this.$scope.component.getServiceInputInputs(input.uniqueId).then(onSuccess, onError); + } else { + deferred.resolve(true); + } + return deferred.promise; + }; + + /* + * When pressing the arrow, we create service inputs from the inputs selected + */ + this.$scope.onArrowPressed = ():void => { + let onSuccess = (inputsCreated: Array<Models.InputModel>) => { + + //disabled all the inputs in the left table + _.forEach(this.$scope.instanceInputsMap, (inputs:Array<Models.InputModel>, instanceId:string) => { + _.forEach(inputs, (input:Models.InputModel) => { + input.isAlreadySelected = true; + }); + }); + + this.addColorToItems(inputsCreated); + }; + + this.$scope.component.createInputsFormInstances(this.$scope.instanceInputsMap).then(onSuccess); + }; + + this.$scope.deleteInput = (input: Models.InputModel):void => { + + var onDelete = ():void => { + var onSuccess = (deletedInput: Models.InputModel, componentInstanceId:string):void => { + // Remove from component.inputs the deleted input (service inputs) + var remainingServiceInputs:Array<Models.InputModel> = _.filter(this.$scope.component.inputs, (input:Models.InputModel):boolean => { + return input.uniqueId !== deletedInput.uniqueId; + }); + this.$scope.component.inputs = remainingServiceInputs; + + // Find the instance that contains the deleted input, and set disable|enable the deleted input + var deletedInputComponentInstance:ComponentInstance = _.find(this.$scope.vfInstancesList, (instanceWithChildToDelete:ComponentInstance):boolean => { + return instanceWithChildToDelete.uniqueId === componentInstanceId; + }); + this.disableEnableSelectedInputs(deletedInputComponentInstance); + }; + + var onFailed = (error:any) : void => { + console.log("Error deleting input"); + }; + + this.addColorToItems([input]); + + // Get service inputs of input (so after delete we will know the component instance) + this.$scope.loadInputInputs(input).then((result:boolean):void=>{ + if (result && input.inputs.length>0) { + var componentInstanceId:string = input.inputs[0].componentInstanceId; + this.$scope.component.deleteServiceInput(input.uniqueId).then((deletedInput: Models.InputModel):void => { + onSuccess(deletedInput, componentInstanceId); + }, onFailed); + } + }); + }; + + // Get confirmation modal text from menu.json + var state = "deleteInput"; + var title:string = this.$scope.sdcMenu.alertMessages[state].title; + var message:string = this.$scope.sdcMenu.alertMessages[state].message.format([input.name]); + + // Open confirmation modal + this.ModalsHandler.openAlertModal(title, message).then(onDelete); + } + }; + + private addColorToItems = (inputsCreated:Array<Models.InputModel>):void => { + + // Adding color to the new inputs (right table) + _.forEach(inputsCreated, (input) => { + input.isNew = true; + }); + + // Removing color to the new inputs (right table) + setTimeout(() => { + _.forEach(inputsCreated, (input) => { + input.isNew = false; + }); + this.$scope.$apply(); + }, 3000); + }; + + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/service-input/service-inputs-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/service-input/service-inputs-view.html new file mode 100644 index 0000000000..bf15a70322 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/service-input/service-inputs-view.html @@ -0,0 +1,205 @@ +<div class="workspace-inputs"> + <div class="table-container-flex"> + <div class="w-sdc-inputs-search pull-left hideme"> + <input type="text" class="w-sdc-inputs-search-input" placeholder="Search"/> + <div class="search-icon-container"> + <span class="w-sdc-search-icon inputs-search-icon magnification-white"></span> + </div> + </div> + <div class="table"> + <div class="table-header">Resource instance inputs</div> + <div class="body"> + <div class="table-loader" ng-class="{'tlv-loader large loader': isLoading}"></div> + <perfect-scrollbar scroll-y-margin-offset="0" class="scrollbar-container"> + + <expand-collapse expanded-selector=".vf-instance-list.{{$index}}" + class="expand-collapse-table-row" + load-data-function="loadInstanceInputs(instance)" + is-close-on-init="true" + data-ng-repeat-start="instance in vfInstancesList track by $index"> + <div class="flex-container data-row"> + <div class="expand-collapse-inputs-table-icon"></div> + <div class="table-col-general flex-item text" data-tests-id="inputs-vf-instance-{{$index}}"> + <span class="title-text">{{instance.name}}</span> + </div> + </div> + + </expand-collapse> + + <div data-ng-repeat-end="" class="vf-instance-list {{$index}}"> + + <expand-collapse expanded-selector=".input-list.{{$parent.$index}}-{{$index}}" + class="expand-collapse-table-row" + load-data-function="loadInputPropertiesForInstance(instance.uniqueId, input)" + is-close-on-init="true" + data-ng-repeat-start="input in instance.inputs track by $index"> + <div class="input-row" ng-class="{'selected': selectedInput.uniqueId === input.uniqueId}"> + <div class="title-text">{{input.name}}</div> + <div class="flex-container"> + <div class="expand-collapse-inputs-table-icon"></div> + <div class="flex-item"> + + <div> + <span class="title-text">Description:</span> + <span tooltips tooltip-content="{{input.description}}">{{input.description}}</span> + </div> + </div> + <div class="flex-item "> + <div class="text"> + <span class="title-text">VF Instance:</span> + <span tooltips tooltip-content="{{instance.name}}">{{instance.name}}</span> + </div> + <div class="text"> + <span class="title-text">Type:</span> + <span tooltips tooltip-content="{{input.type}}">{{input.type}} </span> + </div> + </div> + <sdc-checkbox + class="input-check-box" + disabled ="input.isAlreadySelected || isViewMode()" + sdc-checklist-model="instanceInputsMap[instance.uniqueId]" + sdc-checklist-value="input" + data-tests-id="inputs-checkbox-{{$index}}" + data-ng-click=" $event.stopPropagation()"></sdc-checkbox> + </div> + </div> + + + </expand-collapse> + + <div data-ng-repeat-end="" class="input-list {{$parent.$index}}-{{$index}}"> + <div class="empty-row" ng-if="input.properties.length===0">No properties to display</div> + + <div ng-repeat="property in input.properties track by $index"> + <div class="property-row flex-container"> + <div class="flex-item text property-name-container"> + <span class="title-blue-text property-name-text" tooltips tooltip-content="{{property.name}}">{{property.name}}</span> + <span class="text instance-name-text" tooltips tooltip-content="{{property.name}}">{{property.name}}</span> + </div> + <div class="type-schema-container"> + <div class="text"> + <span tooltips tooltip-content="{{property.type}}">{{property.type}}</span> + </div> + </div> + <div class="type-schema-container"> + <div class="text"> + <span tooltips tooltip-content="{{property.schema.property.type}}">{{property.schema.property.type}} </span> + </div> + </div> + </div> + </div> + </div> + </div> + </perfect-scrollbar> + </div> + </div> + </div> + + <div class="inputs-button-container pull-left"> + <div ng-click="onArrowPressed()" class="right-arrow-btn" data-tests-id="add-inputs-to-service-button"></div> + </div> + + <div class="table-container-flex"> + <div class="w-sdc-inputs-search pull-left"> + <input type="text" class="w-sdc-inputs-search-input" data-ng-model="search.filterTerm" placeholder="Search" data-ng-model-options="{debounce: 200}"/> + <div class="search-icon-container"> + <span class="w-sdc-search-icon inputs-search-icon magnification-white"></span> + </div> + </div> + <div class="table"> + <div class="body"> + <div class="table-header">Service Inputs</div> + <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container"> + <expand-collapse expanded-selector=".service-inputs.{{$index}}" + class="expand-collapse-table-row" + load-data-function="loadInputInputs(serviceInput)" + is-close-on-init="true" + data-ng-repeat-start="serviceInput in component.inputs | filter:search track by $index "> + <div class="input-row service-input-row " data-tests-id="service-input-{{$index}}" ng-class="serviceInput.isNew ? 'new-input': ''"> + <div class="title-text">{{serviceInput.name}}</div> + <div class="flex-container"> + <div class="expand-collapse-inputs-table-icon"></div> + <div class="flex-item"> + <div> + <span class="title-text">Description:</span> + <span tooltips tooltip-content="{{serviceInput.description}}">{{serviceInput.description}}</span> + </div> + </div> + <div class="flex-item "> + <div class="text"> + <span class="title-text">VF Instance:</span> + <span tooltips tooltip-content="{{serviceInput.name}}">{{serviceInput.name}}</span> + </div> + <div class="text"> + <span class="title-text">Type:</span> + <span tooltips tooltip-content="{{serviceInput.type}}">{{serviceInput.type}} </span> + </div> + </div> + <div class="delete"> + <span class="sprite-new delete-icon remove-input-icon" + data-ng-class="{'disabled': isViewMode()}" + data-ng-click="deleteInput(serviceInput); $event.stopPropagation();" + data-tests-id="delete-input-{{$index}}"></span> + </div> + </div> + </div> + </expand-collapse> + + <div data-ng-repeat-end="" class="service-inputs {{$index}}"> + <expand-collapse expanded-selector=".input-inputs-list.{{$parent.$index}}-{{$index}}" + class="expand-collapse-table-row" + load-data-function="loadInputPropertiesForInstance(input.componentInstanceId, input)" + is-close-on-init="true" + data-ng-repeat-start="input in serviceInput.inputs track by $index"> + <div class="input-row"> + <div class="title-text">{{input.name}}</div> + <div class="flex-container"> + <div class="expand-collapse-inputs-table-icon"></div> + <div class="flex-item"> + <div> + <span class="title-text">Description:</span> + <span tooltips tooltip-content="{{input.description}}">{{input.description}}</span> + </div> + </div> + <div class="flex-item "> + <div class="text"> + <span class="title-text">VF Instance:</span> + <span tooltips tooltip-content="{{instance.componentInstanceName}}">{{instance.componentInstanceName}}</span> + </div> + <div class="text"> + <span class="title-text">Type:</span> + <span tooltips tooltip-content="{{input.type}}">{{input.type}} </span> + </div> + </div> + </div> + </div> + </expand-collapse> + + <div data-ng-repeat-end="" class="input-inputs-list {{$parent.$index}}-{{$index}}"> + <div class="empty-row" ng-if="input.properties.length===0">No properties to display</div> + <div ng-repeat="property in input.properties track by $index"> + <div class="property-row flex-container"> + <div class="flex-item text property-name-container"> + <span + class="title-blue-text property-name-text" tooltips tooltips-content="{{property.name}}">{{property.name}}</span> + <span class="text instance-name-text" tooltips tooltips-content="{{property.name}}">{{property.name}}</span> + </div> + <div class="type-schema-container"> + <div class="text"> + <span tooltips tooltips-content="{{property.type}}">{{property.type}}</span> + </div> + </div> + <div class="type-schema-container"> + <div class="text"> + <span tooltips tooltips-content="{{property.schema.property.type}}">{{property.schema.property.type}} </span> + </div> + </div> + </div> + </div> + </div> + </div> + </perfect-scrollbar> + </div> + </div> + </div> +</div> diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/service-input/service-inputs.less b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/service-input/service-inputs.less new file mode 100644 index 0000000000..11e613b56e --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/service-input/service-inputs.less @@ -0,0 +1,54 @@ +.workspace-inputs { + + .service-inputs-view { + + .table-container-flex { + width:100% !important; + } + + .table-loader { + position: relative; + top:215px; + } + + } + + .infinite-scroll { + + overflow-y: scroll; + overflow-x: hidden; + max-height: 400px; + } + + .class_with_css_props_leading_to_a_scroll { + height: 100%; + overflow-y: auto; + } + + .table-container-flex { + .expand-collapse-table-row { + .service-input-row { + padding-left: 15px; + border: none; + border-bottom: rgba(120, 136, 148, 0.26) solid 1px; + + .delete { + width: 50px; + padding: 0; + position: relative; + } + + .remove-input-icon { + position: absolute; + top: 12px; + right: 18px; + } + + .remove-input-icon:hover { + .delete-icon-hover; + } + } + } + } + +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/management-workflow/management-workflow-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/management-workflow/management-workflow-view-model.ts new file mode 100644 index 0000000000..2fab118378 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/management-workflow/management-workflow-view-model.ts @@ -0,0 +1,128 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../../references"/> +module Sdc.ViewModels { + 'use strict'; + + export interface IManagementWorkflowViewModelScope extends IWorkspaceViewModelScope { + vendorModel:VendorModel; + } + + export class VendorModel { + artifacts: Models.ArtifactGroupModel; + serviceID: string; + readonly: boolean; + sessionID: string; + requestID: string; + diagramType: string; + participants:Array<participant>; + + constructor(artifacts: Models.ArtifactGroupModel, serviceID:string, readonly:boolean, sessionID:string, + requestID:string, diagramType:string, participants:Array<participant>){ + this.artifacts = artifacts; + this.serviceID = serviceID; + this.readonly = readonly; + this.sessionID = sessionID; + this.requestID = requestID; + this.diagramType = diagramType; + this.participants = participants; + } + } + + export class ManagementWorkflowViewModel { + + static '$inject' = [ + '$scope', + 'uuid4' + ]; + + constructor(private $scope:IManagementWorkflowViewModelScope, + private uuid4:any) { + + this.initScope(); + this.$scope.updateSelectedMenuItem(); + } + + + private static getParticipants():Array<participant> { + return [ + { + "id": "1", + "name": "Customer"}, + { + "id": "2", + "name": "CCD" + }, + { + "id": "3", + "name": "Infrastructure" + }, + { + "id": "4", + "name": "MSO" + }, + { + "id": "5", + "name": "SDN-C" + }, + { + "id": "6", + "name": "A&AI" + }, + { + "id": "7", + "name": "APP-C" + }, + { + "id": "8", + "name": "Cloud" + }, + { + "id": "9", + "name": "DCAE" + }, + { + "id": "10", + "name": "ALTS" + }, + { + "id": "11", + "name": "VF" + } + ] + } + + + private initScope():void { + this.$scope.vendorModel = new VendorModel( + this.$scope.component.artifacts.filteredByType(Utils.Constants.ArtifactType.THIRD_PARTY_RESERVED_TYPES.WORKFLOW), + this.$scope.component.uniqueId, + this.$scope.isViewMode(), + this.$scope.user.userId, + this.uuid4.generate(), + Utils.Constants.ArtifactType.THIRD_PARTY_RESERVED_TYPES.WORKFLOW, + ManagementWorkflowViewModel.getParticipants() + ); + + this.$scope.thirdParty = true; + this.$scope.setValidState(true); + } + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/management-workflow/management-workflow-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/management-workflow/management-workflow-view.html new file mode 100644 index 0000000000..bd196daec8 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/management-workflow/management-workflow-view.html @@ -0,0 +1,3 @@ +<div class="workspace-management-workflow"> + <punch-out name="'sequence-diagram'" data="vendorModel" user="user" on-event="onVendorEvent"></punch-out> +</div> diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/network-call-flow/network-call-flow-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/network-call-flow/network-call-flow-view-model.ts new file mode 100644 index 0000000000..064f1c5896 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/network-call-flow/network-call-flow-view-model.ts @@ -0,0 +1,80 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../../references"/> +module Sdc.ViewModels { + 'use strict'; + + export interface INetworkCallFlowViewModelScope extends IWorkspaceViewModelScope { + vendorMessageModel:VendorModel; + } + + export class participant { + name:string; + id:string; + + constructor(instance:Models.ComponentsInstances.ComponentInstance){ + this.name = instance.name; + this.id = instance.uniqueId; + } + } + + + export class NetworkCallFlowViewModel { + + static '$inject' = [ + '$scope', + 'uuid4' + ]; + + constructor(private $scope:INetworkCallFlowViewModelScope, + private uuid4:any) { + + this.initScope(); + this.$scope.updateSelectedMenuItem(); + } + + private getVFParticipantsFromInstances(instances:Array<Models.ComponentsInstances.ComponentInstance>):Array<participant> { + let participants = []; + _.forEach(instances,(instance)=> { + if(Utils.Constants.ResourceType.VF == instance.originType){ + participants.push(new participant(instance)); + } + }); + return participants; + } + + + private initScope():void { + this.$scope.vendorMessageModel = new VendorModel( + this.$scope.component.artifacts.filteredByType(Utils.Constants.ArtifactType.THIRD_PARTY_RESERVED_TYPES.NETWORK_CALL_FLOW), + this.$scope.component.uniqueId, + this.$scope.isViewMode(), + this.$scope.user.userId, + this.uuid4.generate(), + Utils.Constants.ArtifactType.THIRD_PARTY_RESERVED_TYPES.NETWORK_CALL_FLOW, + this.getVFParticipantsFromInstances(this.$scope.component.componentInstances) + ); + + this.$scope.thirdParty = true; + this.$scope.setValidState(true); + } + + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/network-call-flow/network-call-flow-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/network-call-flow/network-call-flow-view.html new file mode 100644 index 0000000000..6ce3e8e2b7 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/network-call-flow/network-call-flow-view.html @@ -0,0 +1,3 @@ +<div class="workspace-network-call-flow"> + <punch-out name="'sequence-diagram'" data="vendorMessageModel" user="user" on-event="onVendorEvent"></punch-out> +</div> diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/product-hierarchy/product-hierarchy-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/product-hierarchy/product-hierarchy-view-model.ts new file mode 100644 index 0000000000..faf77a5215 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/product-hierarchy/product-hierarchy-view-model.ts @@ -0,0 +1,134 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ +/** + * Created by obarda on 4/7/2016. + */ +/// <reference path="../../../../references"/> +module Sdc.ViewModels { + 'use strict'; + + export interface IProductHierarchyScope extends IWorkspaceViewModelScope { + + categoriesOptions: Array<Models.IMainCategory>; + product:Models.Components.Product; + isLoading:boolean; + showDropDown:boolean; + + onInputTextClicked():void; + onGroupSelected(category: Models.IMainCategory, subcategory: Models.ISubCategory, group: Models.IGroup):void; + clickOutside():void; + deleteGroup(uniqueId:string):void; + } + + export class ProductHierarchyViewModel { + + static '$inject' = [ + '$scope', + 'Sdc.Services.CacheService', + 'ComponentFactory', + '$state' + ]; + + constructor(private $scope:IProductHierarchyScope, + private cacheService:Sdc.Services.CacheService, + private ComponentFactory: Sdc.Utils.ComponentFactory, + private $state:ng.ui.IStateService) { + + + this.$scope.product = <Models.Components.Product>this.$scope.getComponent(); + this.$scope.setValidState(true); + this.initScope(); + this.$scope.updateSelectedMenuItem(); + } + + private initCategories = () => { + this.$scope.categoriesOptions = angular.copy(this.cacheService.get('productCategories')); + let selectedGroup:Array<Models.IGroup> = []; + _.forEach(this.$scope.product.categories, (category: Models.IMainCategory) => { + _.forEach(category.subcategories, (subcategory:Models.ISubCategory) => { + selectedGroup = selectedGroup.concat(subcategory.groupings); + }); + }); + _.forEach(this.$scope.categoriesOptions, (category: Models.IMainCategory) => { + _.forEach(category.subcategories, (subcategory:Models.ISubCategory) => { + _.forEach(subcategory.groupings, (group:Models.ISubCategory) => { + let componentGroup:Models.IGroup = _.find(selectedGroup, (componentGroupObj) => { + return componentGroupObj.uniqueId == group.uniqueId; + }); + if(componentGroup){ + group.isDisabled = true; + } + }); + }); + }); + }; + + private setFormValidation = ():void => { + //if(!this.$scope.product.categories || this.$scope.product.categories.length === 0){ + // this.$scope.setValidState(false); + //} + //else{ + this.$scope.setValidState(true); + // } + + }; + + private initScope = ():void => { + this.$scope.isLoading= false; + this.$scope.showDropDown =false; + this.initCategories(); + this.setFormValidation(); + + this.$scope.onGroupSelected = (category: Models.IMainCategory, subcategory: Models.ISubCategory, group: Models.IGroup):void => { + this.$scope.product.addGroup(category, subcategory, group); + this.$state.current.data.unsavedChanges = !this.$scope.isViewMode(); + group.isDisabled = true; + this.$scope.showDropDown = false; + this.setFormValidation(); + }; + + this.$scope.onInputTextClicked = ():void => {//just edit the component in place, no pop up nor server update ? + this.$scope.showDropDown = !this.$scope.showDropDown; + }; + + this.$scope.clickOutside = (): any => { + this.$scope.showDropDown = false; + }; + + this.$scope.deleteGroup = (uniqueId:string) : void => { + //delete group from component + this.$scope.product.deleteGroup(uniqueId); + this.$state.current.data.unsavedChanges = !this.$scope.isViewMode(); + this.setFormValidation(); + //enabled group + _.forEach(this.$scope.categoriesOptions, (category: Models.IMainCategory) => { + _.forEach(category.subcategories, (subcategory:Models.ISubCategory) => { + let groupObj:Models.IGroup = _.find (subcategory.groupings, (group) => { + return group.uniqueId === uniqueId; + }); + if(groupObj){ + groupObj.isDisabled = false; + } + }); + }); + } + }; + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/product-hierarchy/product-hierarchy-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/product-hierarchy/product-hierarchy-view.html new file mode 100644 index 0000000000..2335ad7c74 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/product-hierarchy/product-hierarchy-view.html @@ -0,0 +1,40 @@ +<div class="workspace-hierarchy"> + <div class="dropdown-container" clicked-outside="{onClickedOutside: 'clickOutside()', clickedOutsideEnable: 'true'}" > + <input placeholder="Add Group" data-ng-click="onInputTextClicked()" class="dropdown-input-text" data-ng-model="search.filterTerms" data-ng-disabled="isViewMode()" data-ng-class="{'view-mode': isViewMode()}" data-ng-model-options="{debounce: 200}"/> + <div data-ng-class="{'show': showDropDown}" class="dropdown-content" > + <perfect-scrollbar scroll-y-margin-offset="0" include-padding="false" class="scrollbar-container"> + <div ng-repeat="category in categoriesOptions track by $index"> + <div ng-repeat="subcategory in category.subcategories track by $index"> + <div class="dropdown-option" ng-show="!category.filteredGroup || category.filteredGroup.length > 0"> + <div class="category-container"> + <div class="category">{{category.name}}</div> + <div class="subcategory">{{subcategory.name}}</div> + </div> + <div class="groupings-container"> + <div ng-init="group.filterTerms = group.name + ' ' + category.name + ' ' + subcategory.name" + ng-repeat="group in (category.filteredGroup = (subcategory.groupings | filter:search )) track by $index"> + <div class="group" data-ng-disabled="group.isDisabled" data-ng-class="{'disabled-group': group.isDisabled}" ng-click="onGroupSelected(category, subcategory, group)"> + <span >{{group.name}}</span> + </div> + </div> + </div> + </div> + </div> + </div> + </perfect-scrollbar> + </div> + </div> + <div class="hierarchy-groups-container no-border-top" data-ng-class="{'view-mode': isViewMode()}"> + <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container"> + <div ng-if="!product.categories.length || product.categories.length === 0" class="no-groups-text" translate="NEW_PRODUCT_NO_CATEGORIES_TO_DISPLAY"></div> + <div ng-repeat="category in product.categories track by $index"> + <div ng-repeat="subcategory in category.subcategories track by $index"> + <div class="group-tag" ng-repeat="group in subcategory.groupings track by $index" + data-ng-init="tooltip = '<b>' + category.name + '</b><br />' + subcategory.name"> + <sdc-tag sdc-disable="isViewMode()" data-on-delete="deleteGroup(uniqueId)" data-tag-data="{tag: group.name, tooltip: tooltip, id: group.uniqueId }"></sdc-tag> + </div> + </div> + </div> + </perfect-scrollbar> + </div> +</div> diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/product-hierarchy/product-hierarchy.less b/catalog-ui/app/scripts/view-models/workspace/tabs/product-hierarchy/product-hierarchy.less new file mode 100644 index 0000000000..c992558ed2 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/product-hierarchy/product-hierarchy.less @@ -0,0 +1,130 @@ +.workspace-hierarchy { + display: inline-block; + width: 93%; + + .scrollbar-container{ + max-height:400px; + .perfect-scrollbar; + } + + .dropdown-container { + position: relative; + display: inline-block; + width: 100%; + + &:after{ + top: 47%; + right: 1%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + border-color: rgba(0, 0, 0, 0); + border-top-color: black; + border-width: 4px; + margin-left: -4px; + } + + .dropdown-input-text { + width: 100%; + padding: 4px 10px; + } + + .dropdown-content { + .perfect-scrollbar; + border: 1px solid #d8d8d8; + display: none; + position: absolute; + overflow: hidden; + width: 100%; + .bg_c; + max-height: 400px; + z-index: 999999; + + .dropdown-option { + border-bottom: 1px solid #d8d8d8; + display: inline-block; + width: 100%; + } + + .category-container{ + width: 250px; + float: left; + padding-left: 5px; + + .category { + .bold; + padding: 3px 3px 2px 3px; + &:after{ + .sprite; + .arrow-left; + content: ''; + margin-left: 5px; + transform: rotate(180deg); + } + } + .subcategory { + padding-left: 3px; + } + } + + .groupings-container{ + display: inline-block; + width: 424px; + border-left: 1px solid #d8d8d8; + min-height: 55px; + .group{ + padding: 3px 3px 3px 10px; + &:hover{ + .hand; + .bg_n; + } + &.disabled-group { + opacity: 0.5; + &:hover{ + cursor: auto; + .bg_c; + } + } + } + } + + .seperator { + height: 1px; + width: 100%; + .bg_j; + margin: 5px 0px; + } + } + .show { + display: block; + } + } + + .hierarchy-groups-container{ + .b_9; + width: 100%; + border: 1px solid #d8d8d8; + height: 425px; + padding: 15px; + text-align: center; + + .scrollbar-container { + z-index: 0; + } + + .no-group-text{ + text-align: center; + margin-top:25px; + a { + cursor: pointer; + } + } + .group-tag{ + display: inline-block; + float: left; + } + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/properties/properties-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/properties/properties-view-model.ts new file mode 100644 index 0000000000..9b824bfca9 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/properties/properties-view-model.ts @@ -0,0 +1,114 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../../references"/> +module Sdc.ViewModels { + 'use strict'; + + interface IPropertiesViewModelScope extends IWorkspaceViewModelScope { + tableHeadersList: Array<any>; + reverse: boolean; + sortBy:string; + filteredProperties:any; + + addOrUpdateProperty(property?:Models.PropertyModel): void; + delete(property:Models.PropertyModel): void; + sort(sortBy:string): void; + } + + export class PropertiesViewModel { + + static '$inject' = [ + '$scope', + '$filter', + '$modal', + '$templateCache', + 'ModalsHandler' + ]; + + + constructor(private $scope:IPropertiesViewModelScope, + private $filter:ng.IFilterService, + private $modal:ng.ui.bootstrap.IModalService, + private $templateCache:ng.ITemplateCacheService, + private ModalsHandler:Utils.ModalsHandler) { + this.initScope(); + this.$scope.updateSelectedMenuItem(); + } + + + private openEditPropertyModal = (property:Models.PropertyModel):void => { + let viewModelsHtmlBasePath:string = '/app/scripts/view-models/'; + + let modalOptions:ng.ui.bootstrap.IModalSettings = { + template: this.$templateCache.get(viewModelsHtmlBasePath + 'forms/property-form/property-form-view.html'), + controller: 'Sdc.ViewModels.PropertyFormViewModel', + size: 'sdc-l', + backdrop: 'static', + keyboard: false, + resolve: { + property: ():Models.PropertyModel => { + return property; + }, + component: ():Models.Components.Component => { + return <Models.Components.Component> this.$scope.component; + }, + filteredProperties: ():Array<Models.PropertyModel> => { + return this.$scope.filteredProperties.properties; + } + } + }; + this.$modal.open(modalOptions); + }; + + private initScope = ():void => { + + //let self = this; + this.$scope.filteredProperties={properties:[]}; + this.$scope.sortBy = 'name'; + this.$scope.reverse = false; + this.$scope.setValidState(true); + this.$scope.tableHeadersList = [ + {title: 'Name', property: 'name'}, + {title: 'Type', property: 'type'}, + {title: 'Schema', property: 'schema.property.type'}, + {title: 'Description', property: 'description'}, + ]; + this.$scope.sort = (sortBy:string):void => { + this.$scope.reverse = (this.$scope.sortBy === sortBy) ? !this.$scope.reverse : false; + this.$scope.sortBy = sortBy; + }; + + + this.$scope.addOrUpdateProperty = (property?:Models.PropertyModel):void => { + this.openEditPropertyModal(property ? property : new Models.PropertyModel()); + }; + + this.$scope.delete = (property:Models.PropertyModel):void => { + + let onOk = ():void => { + this.$scope.component.deleteProperty(property.uniqueId); + }; + let title:string = this.$filter('translate')("PROPERTY_VIEW_DELETE_MODAL_TITLE"); + let message:string = this.$filter('translate')("PROPERTY_VIEW_DELETE_MODAL_TEXT", "{'name': '" + property.name + "'}"); + this.ModalsHandler.openConfirmationModal(title, message, false).then(onOk); + }; + } + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/properties/properties-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/properties/properties-view.html new file mode 100644 index 0000000000..e9a4c3879d --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/properties/properties-view.html @@ -0,0 +1,62 @@ +<div class="workspace-properties"> + <div id="left-top-bar"> + <span id="properties-count">Total Properties: {{component.properties.length}}</span> + <input id="search-by-name" type="search" placeholder="Search" data-ng-model-options="{debounce: 200}" data-ng-model="filterTerms"/> + <span class="sprite magnification-glass search-button"></span> + </div> + <div class="add-btn" data-tests-id="addGrey" ng-if="!isViewMode()" + data-ng-class="{'disabled': isDisableMode()}" data-ng-click="addOrUpdateProperty()">Add Property</div> + <div class="table-container-flex"> + <div class="table" data-ng-class="{'view-mode': isViewMode()}"> + <div class="head flex-container"> + <div class="table-header head-row hand flex-item" data-ng-repeat="header in tableHeadersList track by $index" data-ng-click="sort(header.property)">{{header.title}} + <span data-ng-if="sortBy === header.property" class="table-header-sort-arrow" data-ng-class="{'down': reverse, 'up':!reverse}"> </span> + </div> + <div class="table-no-text-header head-row flex-item" ng-if="!isViewMode()"><span class="delete-col-header"></span></div> + <!--div class="table-no-text-header head-row flex-item"></div--> + </div> + + <div class="body"> + <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container"> + <div data-ng-if="component.properties.length === 0" class="no-row-text" data-ng-class="{'disabled': isDisableMode()}"> + There are no properties to display <br> + <span ng-if="!isViewMode()"> click <a data-ng-click="addOrUpdateProperty()">here</a> to add one </span> + + </div> + <div data-ng-repeat="property in filteredProperties.properties=(component.properties | orderBy:sortBy:reverse | filter: {filterTerm:filterTerms}) track by $index" + data-tests-id="propertyRow" data-ng-class="{'selected': property.selected}" + class="flex-container data-row"> + + <div class="table-col-general flex-item text"> + <a data-tests-id="{{property.name}}" + tooltips tooltip-content="{{property.name}}" + data-ng-click="addOrUpdateProperty(property); $event.stopPropagation();" + data-ng-class="{'disabled': isViewMode()}">{{property.name}}</a> + + </div> + + <div class="table-col-general flex-item text" + data-tests-id="{{property.type}}" + tooltips tooltip-content="{{property.type}}" + data-ng-bind="property.type"> + </div> + <div class="table-col-general flex-item text" + data-tests-id="{{property.schema.property.type}}" + tooltips tooltip-content="{{property.schema.property.type}}" + data-ng-bind="property.schema.property.type"> + </div> + <div class="table-col-general flex-item text"> + <span tooltips tooltip-content="{{property.description}}" data-tests-id="{{property.description}}" data-ng-bind="property.description"></span> + </div> + <div class="table-btn-col flex-item" ng-if="!isViewMode()"> + <button class="table-delete-btn" data-tests-id="delete_{{property.name}}" data-ng-if="property.parentUniqueId==component.uniqueId" + data-ng-click="delete(property); $event.stopPropagation();" data-ng-class="{'disabled': isViewMode()}"> </button> + </div> + </div> + </perfect-scrollbar> + </div> + + </div> + </div> + +</div> diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/properties/properties.less b/catalog-ui/app/scripts/view-models/workspace/tabs/properties/properties.less new file mode 100644 index 0000000000..3e8d6c3fbd --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/properties/properties.less @@ -0,0 +1,115 @@ +.workspace-properties { + + width: 93%; + display: inline-block; + + #left-top-bar{ + float: left; + width: 155px; + ::-webkit-input-placeholder { + font-style: italic; + } + :-moz-placeholder { + font-style: italic; + } + ::-moz-placeholder { + font-style: italic; + } + :-ms-input-placeholder { + font-style: italic; + } + + #properties-count{ + font-weight: bold; + float: left; + } + + #search-by-name{ + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + width: 245px; + height: 32px; + line-height: 32px; + border: 1px solid @main_color_o; + text-indent: 10px; + } + + .search-button{ + .hand; + cursor: pointer; + float: right; + position: relative; + top: -22px; + right: -80px; + } + } + + .add-btn { + margin: 36px 0 0 0; + } + + .delete-col-header{ + .sprite; + .sprite.e-sdc-small-icon-delete; + } + + .w-sdc-classic-btn { + float: right; + margin-bottom: 10px; + } + + .table{ + height:490px; + margin-bottom: 0; + } + + .data-row{ + .table-delete-btn{ + display: none !important; + } + &:hover{ + .table-delete-btn{ + display: inline-block !important; + } + } + } + + .table-container-flex { + margin-top: 27px; + + .text{ + overflow: hidden; + text-overflow: ellipsis; + display: inline-block; + white-space: nowrap; + } + + .flex-item:nth-child(1) { + flex-grow: 15; + a{ + .hand + } + } + + .flex-item:nth-child(2) { + flex-grow: 6; + } + + .flex-item:nth-child(3) { + flex-grow: 6; + } + + .flex-item:nth-child(4) { + flex-grow: 20; + + } + .flex-item:nth-child(5) { + flex-grow: 3; + padding-top: 10px; + } + + + } + +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view-model.ts new file mode 100644 index 0000000000..97a117e8b7 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view-model.ts @@ -0,0 +1,165 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ +/** + * Created by rcohen on 9/22/2016. + */ +/// <reference path="../../../../references"/> +module Sdc.ViewModels { + 'use strict'; + import tree = d3.layout.tree; + + export class SortTableDefined { + reverse:boolean; + sortByField:string; + } + + interface IReqAndCapabilitiesViewModelScope extends IWorkspaceViewModelScope { + requirementsTableHeadersList: Array<any>; + capabilitiesTableHeadersList: Array<any>; + capabilityPropertiesTableHeadersList: Array<any>; + requirementsSortTableDefined: SortTableDefined; + capabilitiesSortTableDefined: SortTableDefined; + propertiesSortTableDefined: SortTableDefined; + requirements:Array<Models.Requirement>; + capabilities:Array<Models.Capability>; + mode:string; + filteredProperties:Array<Array<Models.PropertyModel>>; + searchText:string; + + sort(sortBy:string, sortByTableDefined:SortTableDefined):void; + updateProperty(property:Models.PropertyModel, indexInFilteredProperties:number):void; + allCapabilitiesSelected(selected:boolean):void; + } + + export class ReqAndCapabilitiesViewModel { + + static '$inject' = [ + '$scope', + '$filter', + '$modal', + '$templateCache', + 'ModalsHandler' + ]; + + + constructor(private $scope:IReqAndCapabilitiesViewModelScope, + private $filter:ng.IFilterService, + private $modal:ng.ui.bootstrap.IModalService, + private $templateCache:ng.ITemplateCacheService) { + this.initScope(); + this.$scope.updateSelectedMenuItem(); + } + + + private openEditPropertyModal = (property:Models.PropertyModel, indexInFilteredProperties:number):void => { + let viewModelsHtmlBasePath:string = '/app/scripts/view-models/'; + //...because there is not be api + _.forEach(this.$scope.filteredProperties[indexInFilteredProperties],(prop:Models.PropertyModel)=>{ + prop.readonly = true; + }); + let modalOptions:ng.ui.bootstrap.IModalSettings = { + template: this.$templateCache.get(viewModelsHtmlBasePath + 'forms/property-form/property-form-view.html'), + controller: 'Sdc.ViewModels.PropertyFormViewModel', + size: 'sdc-l', + backdrop: 'static', + keyboard: false, + resolve: { + property: ():Models.PropertyModel => { + return property; + }, + component: ():Models.Components.Component => { + return <Models.Components.Component> this.$scope.component; + }, + filteredProperties: ():Array<Models.PropertyModel> => { + return this.$scope.filteredProperties[indexInFilteredProperties]; + } + } + }; + this.$modal.open(modalOptions); + }; + + private initScope = ():void => { + + this.$scope.requirementsSortTableDefined = { + reverse: false, + sortByField: 'name' + }; + this.$scope.capabilitiesSortTableDefined = { + reverse: false, + sortByField: 'name' + }; + this.$scope.propertiesSortTableDefined = { + reverse: false, + sortByField: 'name' + }; + + this.$scope.setValidState(true); + this.$scope.requirementsTableHeadersList = [ + {title: 'Name', property: 'name'}, + {title: 'Capability', property: 'capability'}, + {title: 'Node', property: 'node'}, + {title: 'Relationship', property: 'relationship'}, + {title: 'Connected To', property: ''}, + {title: 'Occurrences', property: ''} + ]; + this.$scope.capabilitiesTableHeadersList = [ + {title: 'Name', property: 'name'}, + {title: 'Type', property: 'type'}, + {title: 'Description', property: ''}, + {title: 'Valid Source', property: ''}, + {title: 'Occurrences', property: ''} + ]; + this.$scope.capabilityPropertiesTableHeadersList = [ + {title: 'Name', property: 'name'}, + {title: 'Type', property: 'type'}, + {title: 'Schema', property: 'schema.property.type'}, + {title: 'Description', property: 'description'}, + ]; + this.$scope.filteredProperties=[]; + + this.$scope.mode='requirements'; + this.$scope.requirements=[]; + _.forEach(this.$scope.component.requirements,(req:Array<Models.Requirement>,capName)=>{ + this.$scope.requirements=this.$scope.requirements.concat(req); + }); + + this.$scope.capabilities=[]; + _.forEach(this.$scope.component.capabilities,(cap:Array<Models.Capability>,capName)=>{ + this.$scope.capabilities=this.$scope.capabilities.concat(cap); + }); + + this.$scope.sort = (sortBy:string, sortByTableDefined:SortTableDefined):void => { + sortByTableDefined.reverse = (sortByTableDefined.sortByField === sortBy) ? !sortByTableDefined.reverse : false; + sortByTableDefined.sortByField = sortBy; + }; + + this.$scope.updateProperty = (property:Models.PropertyModel, indexInFilteredProperties:number):void => { + this.openEditPropertyModal(property, indexInFilteredProperties); + }; + + this.$scope.allCapabilitiesSelected = (selected:boolean):void => { + _.forEach(this.$scope.capabilities,(cap:Models.Capability)=>{ + cap.selected = selected; + }); + }; + } + } +} + diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view.html new file mode 100644 index 0000000000..047768689a --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view.html @@ -0,0 +1,144 @@ +<div class="workspace-req-and-cap"> + <div class="tabs"> + <button data-tests-id="req-tab" data-ng-click="mode='requirements';filterTerms='';" class="tlv-btn" data-ng-class="{'selected':mode=='requirements'}">Requirements({{requirements.length||'0'}})</button> + <button data-tests-id="cap-tab" data-ng-click="mode='capabilities';filterTerms='';" class="tlv-btn" data-ng-class="{'selected':mode=='capabilities'}">Capabilities({{capabilities.length||'0'}})</button> + </div> + <div class="expand-collapse-buttons" data-ng-if="mode=='capabilities'"> + <span class="sprite-new expand-all" data-ng-click="allCapabilitiesSelected(true)"></span> + <span class="sprite-new collapse-all" data-ng-click="allCapabilitiesSelected(false)"></span> + </div> + <div class="search"> + <input id="search-box" data-tests-id="search-box" type="search" placeholder="Search" data-ng-model-options="{debounce: 200}" data-ng-model="filterTerms"/> + <div class="search-icon-container"> + <span class="search-icon sprite-new search-white-icon"></span> + </div> + </div> + <div class="table-container-flex requirements-table" data-ng-if="mode=='requirements'"> + <div class="table" data-ng-class="{'view-mode': isViewMode()}"> + <div class="head flex-container"> + <div class="table-header head-row hand flex-item" data-ng-repeat="header in requirementsTableHeadersList track by $index" data-ng-click="sort(header.property, requirementsSortTableDefined)">{{header.title}} + <span data-ng-if="requirementsSortTableDefined.sortByField === header.property" class="table-header-sort-arrow" data-ng-class="{'down': requirementsSortTableDefined.reverse, 'up':!requirementsSortTableDefined.reverse}"> </span> + </div> + </div> + + <div class="body"> + <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container"> + <div data-ng-if="requirements.length === 0" class="no-row-text"> + There are no requirements to display + + </div> + <div data-ng-repeat="req in requirements | orderBy:requirementsSortTableDefined.sortByField:requirementsSortTableDefined.reverse | filter: {filterTerm:filterTerms} track by $index" + class="flex-container data-row" data-tests-id="reqRow"> + + <div class="table-col-general flex-item text"> + <span data-tests-id="{{req.name}}" tooltips tooltip-content="{{req.name}}">{{req.name}}</span> + </div> + <div class="table-col-general flex-item text"> + <span data-tests-id="{{req.capability}}" tooltips tooltip-content="{{req.capability}}">{{req.capability.substring("tosca.capabilities.".length)}}</span> + </div> + <div class="table-col-general flex-item text"> + <span data-tests-id="{{req.node}}" tooltips tooltip-content="{{req.node}}">{{req.node.substring("tosca.nodes.".length)}}</span> + </div> + <div class="table-col-general flex-item text"> + <span data-tests-id="{{req.relationship}}" tooltips tooltip-content="{{req.relationship}}">{{req.relationship.substring("tosca.relationships.".length)}}</span> + </div> + <div class="table-col-general flex-item text" data-tests-id="{{}}" data-ng-bind=""></div> + <div class="table-col-general flex-item text"> + <span data-tests-id="{{req.minOccurrences}},{{req.maxOccurrences}}" tooltips tooltip-content="{{req.minOccurrences}},{{req.maxOccurrences}}">{{req.minOccurrences}},{{req.maxOccurrences}}</span> + </div> + </div> + </perfect-scrollbar> + </div> + + </div> + </div> + <div class="table-container-flex capabilities-table" data-ng-if="mode=='capabilities'"> + <div class="table" data-ng-class="{'view-mode': isViewMode()}"> + <div class="head flex-container"> + <div class="table-header head-row hand flex-item" data-ng-repeat="header in capabilitiesTableHeadersList track by $index" data-ng-click="sort(header.property, capabilitiesSortTableDefined)">{{header.title}} + <span data-ng-if="capabilitiesSortTableDefined.sortByField === header.property" class="table-header-sort-arrow" data-ng-class="{'down': capabilitiesSortTableDefined.reverse, 'up':!capabilitiesSortTableDefined.reverse}"> </span> + </div> + </div> + + <div class="body"> + <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container"> + <div data-ng-if="capabilities.length === 0" class="no-row-text"> + There are no capabilities to display + + </div> + <div data-ng-repeat-start="capability in capabilities | orderBy:capabilitiesSortTableDefined.sortByField:capabilitiesSortTableDefined.reverse | filter: {filterTerm:filterTerms} track by $index" + class="flex-container data-row" data-ng-class="{'selected': capability.selected}" + data-ng-click="capability.selected = !capability.selected" data-tests-id="capabilities-table-row"> + + <div class="table-col-general flex-item text"> + <span class="sprite-new arrow-up-small" data-ng-class="{'opened': capability.selected}"></span> + <span data-tests-id="{{capability.name}}" tooltips tooltip-content="{{capability.name}}">{{capability.name}}</span> + </div> + <div class="table-col-general flex-item text"> + <span data-tests-id="{{capability.type}}" tooltips tooltip-content="{{capability.type}}">{{capability.type.substring("tosca.capabilities.".length)}}</span> + </div> + + <div class="table-col-general flex-item text"> + <span data-tests-id="{{capability.description}}" tooltips tooltip-content="{{capability.description}}">{{capability.description}}</span> + </div> + + <div class="table-col-general flex-item text"> + <span data-tests-id="{{capability.validSourceTypes.join(',')}}" tooltips tooltip-content="{{capability.validSourceTypes.join(',')}}">{{capability.validSourceTypes.join(',')}}</span> + </div> + + <div class="table-col-general flex-item text"> + <span data-tests-id="{{capability.minOccurrences}},{{capability.maxOccurrences}}" tooltips tooltip-content="{{capability.minOccurrences}},{{capability.maxOccurrences}}">{{capability.minOccurrences}},{{capability.maxOccurrences}}</span> + </div> + </div> + <div data-ng-repeat-end="" data-ng-if="capability.selected" class="item-opened"> + <p class="properties-title">Properties</p> + <div class="table-container-flex properties-table"> + <div class="table" data-ng-class="{'view-mode': isViewMode()}"> + <div class="head flex-container"> + <div class="table-header head-row hand flex-item" data-ng-repeat="header in capabilityPropertiesTableHeadersList track by $index" data-ng-click="sort(header.property, propertiesSortTableDefined)">{{header.title}} + <span data-ng-if="propertiesSortTableDefined.sortByField === header.property" class="table-header-sort-arrow" data-ng-class="{'down': propertiesSortTableDefined.reverse, 'up':!propertiesSortTableDefined.reverse}"> </span> + </div> + </div> + + <div class="body"> + <div data-ng-if="capability.properties.length === 0" class="no-row-text"> + There are no properties to display + </div> + <div data-ng-repeat="property in filteredProperties[$parent.$index]=(capability.properties | orderBy:propertiesSortTableDefined.sortByField:propertiesSortTableDefined.reverse) track by $index" + data-tests-id="propertyRow" + class="flex-container data-row"> + + <div class="table-col-general flex-item text"> + <a data-tests-id="{{property.name}}" + tooltips tooltip-content="{{property.name}}" + data-ng-click="updateProperty(property, $parent.$index); $event.stopPropagation();" + data-ng-class="{'disabled': isViewMode()}">{{property.name}}</a> + + </div> + + <div class="table-col-general flex-item text" + data-tests-id="{{property.type}}" + tooltips tooltip-content="{{property.type}}" + data-ng-bind="property.type"> + </div> + <div class="table-col-general flex-item text" + data-tests-id="{{property.schema.property.type}}" + tooltips tooltip-content="{{property.schema.property.type}}" + data-ng-bind="property.schema.property.type"> + </div> + <div class="table-col-general flex-item text"> + <span tooltips tooltip-content="{{property.description}}" data-tests-id="{{property.description}}" data-ng-bind="property.description"></span> + </div> + </div> + </div> + + </div> + </div> + </div> + </perfect-scrollbar> + </div> + + </div> + </div> +</div> + diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities.less b/catalog-ui/app/scripts/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities.less new file mode 100644 index 0000000000..9b52fad411 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities.less @@ -0,0 +1,196 @@ +.workspace-req-and-cap { + + width: 93%; + display: inline-block; + + .tabs{ + float: left; + position: relative; + top: 6px; + button{ + float: left; + width: 233px; + height: 38px; + background-color: @tlv_color_t; + border: 1px solid @main_color_o; + color: black; + &:nth-child(1){ + border-radius: 10px 0 0 0; + } + &:nth-child(2){ + border-radius: 0 10px 0 0; + } + &.selected{ + background-color: @main_color_a; + border: 1px solid @main_color_a; + color: white; + } + } + } + .search{ + margin-bottom: 12px; + float: right; + ::-webkit-input-placeholder { + font-style: italic; + } + :-moz-placeholder { + font-style: italic; + } + ::-moz-placeholder { + font-style: italic; + } + :-ms-input-placeholder { + font-style: italic; + } + #search-box{ + -webkit-border-radius: 2px 0 0 2px; + -moz-border-radius: 2px 0 0 2px; + border-radius: 2px 0 0 2px; + width: 213px; + height: 32px; + line-height: 32px; + border: 1px solid @main_color_o; + text-indent: 10px; + float: left; + } + .search-icon-container{ + background-color: @main_color_a; + height: 32px; + width: 32px; + border-radius: 0 2px 2px 0; + float: left; + .search-icon{ + position: relative; + top: 9px; + } + } + } + .expand-collapse-buttons{ + float: right; + width: 44px; + margin-left: 11px; + margin-top: 10px; + span{ + vertical-align: bottom; + .hand; + } + } + + + + .table{ + height:490px; + margin-bottom: 0; + } + + .arrow-up-small{ + &.opened{ + .arrow-up-small-hover; + } + } + + .item-opened{ + background-color: @tlv_color_t; + } + + .properties-title{ + margin:0; + font-weight: bold; + } + + .table-container-flex { + margin-top: 10px; + + .text{ + overflow: hidden; + text-overflow: ellipsis; + display: inline-block; + white-space: nowrap; + } + + &.requirements-table{ + border-top: 4px solid @main_color_a; + .flex-item:nth-child(1) { + flex-grow: 20; + } + + .flex-item:nth-child(2) { + flex-grow: 20; + } + + .flex-item:nth-child(3) { + flex-grow: 20; + } + + .flex-item:nth-child(4) { + flex-grow: 20; + } + + .flex-item:nth-child(5) { + flex-grow: 20; + } + + .flex-item:nth-child(6) { + flex-grow: 20; + } + } + + &.capabilities-table{ + border-top: 4px solid @main_color_a; + .selected{ + .flex-item:nth-child(1) { + border-left: 4px solid @main_color_a; + padding-right: 11px; + } + } + .flex-item:nth-child(1) { + flex-grow: 10; + } + + .flex-item:nth-child(2) { + flex-grow: 10; + } + + .flex-item:nth-child(3) { + flex-grow: 10; + } + + .flex-item:nth-child(4) { + flex-grow: 10; + } + + .flex-item:nth-child(5) { + flex-grow: 10; + } + + } + + &.properties-table{ + .table{ + height: auto; + } + + .flex-item:nth-child(1) { + flex-grow: 15; + a{ + .hand + } + } + + .flex-item:nth-child(2) { + flex-grow: 6; + } + + .flex-item:nth-child(3) { + flex-grow: 6; + } + + .flex-item:nth-child(4) { + flex-grow: 20; + + } + } + + } + +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts-view-model.ts new file mode 100644 index 0000000000..1e6bc04924 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts-view-model.ts @@ -0,0 +1,87 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ +/// <reference path="../../../../references"/> +module Sdc.ViewModels { + 'use strict'; + import ArtifactModel = Sdc.Models.ArtifactModel; + + export interface IToscaArtifactsScope extends IWorkspaceViewModelScope { + artifacts: Array<Models.ArtifactModel>; + tableHeadersList: Array<any>; + artifactType: string; + downloadFile:Models.IFileDownload; + isLoading:boolean; + sortBy:string; + reverse:boolean; + + getTitle(): string; + download(artifact:Models.ArtifactModel): void; + sort(sortBy:string): void; + showNoArtifactMessage():boolean; + } + + export class ToscaArtifactsViewModel { + + static '$inject' = [ + '$scope', + '$filter' + ]; + + constructor(private $scope:IToscaArtifactsScope, + private $filter:ng.IFilterService) { + this.initScope(); + this.$scope.updateSelectedMenuItem(); + } + + private initScope = ():void => { + let self = this; + this.$scope.isLoading = false; + this.$scope.sortBy = 'artifactDisplayName'; + this.$scope.reverse = false; + this.$scope.setValidState(true); + this.$scope.artifactType = 'normal'; + this.$scope.getTitle = ():string => { + return this.$filter("resourceName")(this.$scope.component.name) + ' Artifacts'; + + }; + + this.$scope.tableHeadersList = [ + {title: 'Name', property: 'artifactDisplayName'}, + {title: 'Type', property: 'artifactType'} + ]; + + this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.toscaArtifacts); + this.$scope.sort = (sortBy:string):void => { + this.$scope.reverse = (this.$scope.sortBy === sortBy) ? !this.$scope.reverse : false; + this.$scope.sortBy = sortBy; + }; + + + + this.$scope.showNoArtifactMessage = ():boolean => { + if (this.$scope.artifacts.length === 0) { + return true; + } + return false; + }; + + } + } +} diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts-view.html new file mode 100644 index 0000000000..947b37db93 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts-view.html @@ -0,0 +1,45 @@ +<div class="workspace-tosca-artifact"> + <div class="table-container-flex"> + <div class="table" data-ng-class="{'view-mode': isViewMode()}"> + <div class="head flex-container"> + <div class="table-header head-row hand flex-item" ng-repeat="header in tableHeadersList track by $index" data-ng-click="sort(header.property)">{{header.title}} + <span data-ng-show="sortBy === header.property" class="table-header-sort-arrow" data-ng-class="{'down': reverse, 'up':!reverse}"> </span> + </div> + <div class="table-no-text-header head-row flex-item"></div> + </div> + <div class="body"> + <perfect-scrollbar suppress-scroll-x="true" scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container"> + <div data-ng-if="showNoArtifactMessage()" class="no-row-text" data-ng-class="{'disabled': isDisableMode()}"> + There are no TOSCA artifacts to display + </div> + <div data-ng-repeat-start="artifact in artifacts| orderBy:sortBy:reverse track by $index" + class="flex-container data-row" + data-ng-class="{'selected': artifact.selected}"> + + <div class="table-col-general flex-item" data-ng-click="artifact.selected = !artifact.selected"> + <span class="sprite table-arrow" data-ng-class="{'opened': artifact.selected}" data-tests-id="{{artifact.artifactDisplayName}}"></span> + {{artifact.artifactDisplayName}} + </div> + + <div class="table-col-general flex-item" data-ng-click="artifact.selected = !artifact.selected" data-tests-id="{{artifact.artifactType}}"> + {{artifact.artifactType}} + </div> + + <div class="table-btn-col flex-item download-icon-container"> + <button class="table-download-btn tosca" download-artifact data-tests-id="download_{{artifact.artifactDisplayName}}" + data-ng-if="artifact.artifactName" component="component" artifact="artifact" show-loader="true"></button> + </div> + </div> + <div data-ng-repeat-end="" data-ng-if="artifact.selected" class="item-opened"> + <div><span class="details-title">Label:</span> {{artifact.artifactLabel}}</div> + <div><span class="details-title">UUID:</span> {{artifact.uniqueId}}</div> + <div><span class="details-title">Description:</span> {{artifact.description}}</div> + + + </div> + + </perfect-scrollbar> + </div> + </div> + </div> +</div> diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts.less b/catalog-ui/app/scripts/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts.less new file mode 100644 index 0000000000..f792bb8c53 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts.less @@ -0,0 +1,74 @@ +.workspace-tosca-artifact { + width: 93%; + display: inline-block; + .w-sdc-classic-btn { + float: right; + margin-bottom: 10px; + } + + .details-title{ + font-weight: bold; + margin-right: 5px; + } + + .table{ + height: 490px; + margin-bottom: 0; + } + + + .table-container-flex { + margin-top: 27px; + + .item-opened{ + word-wrap: break-word; + } + + + .flex-item:nth-child(1) { + flex-grow: 15; + .hand; + span.table-arrow { + margin-right: 7px; + } + } + + .flex-item:nth-child(2) { + flex-grow: 6; + } + + .flex-item:nth-child(3) { + flex-grow: 1; + } + + + + + .table-download-btn{ + &.tosca{ + margin-left: 0; + margin-top: 8px; + } + } + + .download-icon-container{ + position: relative; + + .loader{ + left: 60%; + top: 45px; + border: none; + background-color: transparent; + height: 0px; + width: 63px; + outline: none; + + } + } + + + } + +} + + diff --git a/catalog-ui/app/scripts/view-models/workspace/workspace-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/workspace-view-model.ts new file mode 100644 index 0000000000..a8523f24f5 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/workspace-view-model.ts @@ -0,0 +1,703 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ +/** + * Created by obarda on 3/30/2016. + */ +/// <reference path="../../references"/> +module Sdc.ViewModels { + + 'use strict'; + import Resource = Sdc.Models.Components.Resource; + import ResourceType = Sdc.Utils.Constants.ResourceType; + + export interface IWorkspaceViewModelScope extends ng.IScope { + + isLoading: boolean; + isCreateProgress: boolean; + component: Models.Components.Component; + originComponent: Models.Components.Component; + componentType: string; + importFile: any; + leftBarTabs: Utils.MenuItemGroup; + isNew: boolean; + isFromImport: boolean; + isValidForm: boolean; + mode: Utils.Constants.WorkspaceMode; + breadcrumbsModel: Array<Utils.MenuItemGroup>; + sdcMenu: Models.IAppMenu; + changeLifecycleStateButtons: any; + version: string; + versionsList: Array<any>; + changeVersion: any; + isComposition: boolean; + isDeployment: boolean; + $state: ng.ui.IStateService; + user: Models.IUserProperties; + thirdParty: boolean; + disabledButtons: boolean; + menuComponentTitle: string; + progressService: Sdc.Services.ProgressService; + progressMessage: string; + // leftPanelComponents:Array<Models.Components.Component>; //this is in order to load the left panel once, and not wait long time when moving to composition + + showChangeStateButton(): boolean; + getComponent(): Sdc.Models.Components.Component; + setComponent(component: Sdc.Models.Components.Component): void; + onMenuItemPressed(state: string): ng.IPromise<boolean>; + save(): ng.IPromise<boolean>; + setValidState(isValid: boolean): void; + revert(): void; + changeLifecycleState(state: string): void; + enabledTabs(): void + isDesigner(): boolean; + isViewMode(): boolean; + isEditMode(): boolean; + isCreateMode(): boolean; + isDisableMode(): boolean; + showFullIcons(): boolean; + goToBreadcrumbHome(): void; + onVersionChanged(selectedId: string): void; + getLatestVersion(): void; + getStatus(): string; + showLifecycleIcon(): boolean; + updateSelectedMenuItem(): void; + uploadFileChangedInGeneralTab(): void; + updateMenuComponentName(ComponentName: string): void; + } + + export class WorkspaceViewModel { + + static '$inject' = [ + '$scope', + 'injectComponent', + 'ComponentFactory', + '$state', + 'sdcMenu', + '$q', + 'MenuHandler', + 'Sdc.Services.CacheService', + 'ChangeLifecycleStateHandler', + 'ModalsHandler', + 'LeftPaletteLoaderService', + '$filter', + 'EventListenerService', + 'Sdc.Services.EntityService', + 'Notification', + '$stateParams', + 'Sdc.Services.ProgressService' + ]; + + constructor(private $scope: IWorkspaceViewModelScope, + private injectComponent: Models.Components.Component, + private ComponentFactory: Utils.ComponentFactory, + private $state: ng.ui.IStateService, + private sdcMenu: Models.IAppMenu, + private $q: ng.IQService, + private MenuHandler: Utils.MenuHandler, + private cacheService: Services.CacheService, + private ChangeLifecycleStateHandler: Sdc.Utils.ChangeLifecycleStateHandler, + private ModalsHandler: Sdc.Utils.ModalsHandler, + private LeftPaletteLoaderService: Services.Components.LeftPaletteLoaderService, + private $filter: ng.IFilterService, + private EventListenerService: Services.EventListenerService, + private EntityService: Sdc.Services.EntityService, + private Notification: any, + private $stateParams: any, + private progressService: Sdc.Services.ProgressService) { + + this.initScope(); + this.initAfterScope(); + } + + private role: string; + private components: Array<Models.Components.Component>; + + private initViewMode = (): Utils.Constants.WorkspaceMode => { + let mode = Utils.Constants.WorkspaceMode.VIEW; + + if (!this.$state.params['id']) { //&& !this.$state.params['vspComponent'] + mode = Utils.Constants.WorkspaceMode.CREATE; + } else { + if (this.$scope.component.lifecycleState === Utils.Constants.ComponentState.NOT_CERTIFIED_CHECKOUT && + this.$scope.component.lastUpdaterUserId === this.cacheService.get("user").userId) { + if (this.$scope.component.isProduct() && this.role == Utils.Constants.Role.PRODUCT_MANAGER) { + mode = Utils.Constants.WorkspaceMode.EDIT; + } + if ((this.$scope.component.isService() || this.$scope.component.isResource()) && this.role == Utils.Constants.Role.DESIGNER) { + mode = Utils.Constants.WorkspaceMode.EDIT; + } + } + } + return mode; + }; + + private initChangeLifecycleStateButtons = (): void => { + let state = this.$scope.component.isService() && (Utils.Constants.Role.OPS == this.role || Utils.Constants.Role.GOVERNOR == this.role) ? this.$scope.component.distributionStatus : this.$scope.component.lifecycleState; + this.$scope.changeLifecycleStateButtons = this.sdcMenu.roles[this.role].changeLifecycleStateButtons[state]; + }; + + private isNeedSave = (): boolean => { + if (this.$scope.isEditMode() && //this is a workaround for onboarding - we need to get the artifact in order to avoid saving the vf when moving from their tabs + (this.$state.current.name === Utils.Constants.States.WORKSPACE_MANAGEMENT_WORKFLOW || this.$state.current.name === Utils.Constants.States.WORKSPACE_NETWORK_CALL_FLOW)) { + return true; + } + return this.$scope.isEditMode() && + this.$state.current.data && this.$state.current.data.unsavedChanges; + }; + + private initScope = (): void => { + + this.$scope.component = this.injectComponent; + this.$scope.menuComponentTitle = this.$scope.component.name; + this.$scope.disabledButtons = false; + this.$scope.originComponent = this.ComponentFactory.createComponent(this.$scope.component); + this.$scope.componentType = this.$scope.component.componentType; + this.$scope.version = this.cacheService.get('version'); + this.$scope.user = this.cacheService.get("user"); + this.role = this.$scope.user.role; + this.$scope.mode = this.initViewMode(); + this.$scope.isValidForm = true; + this.initChangeLifecycleStateButtons(); + this.initVersionObject(); + this.$scope.$state = this.$state; + this.$scope.isLoading = false; + this.$scope.isComposition = (this.$state.current.name.indexOf(Utils.Constants.States.WORKSPACE_COMPOSITION) > -1); + this.$scope.isDeployment = (this.$state.current.name.indexOf(Utils.Constants.States.WORKSPACE_DEPLOYMENT) > -1); + this.$scope.progressService = this.progressService; + + this.$scope.getComponent = (): Sdc.Models.Components.Component => { + return this.$scope.component; + }; + + this.$scope.updateMenuComponentName = (ComponentName: string): void => { + this.$scope.menuComponentTitle = ComponentName; + }; + + this.$scope.sdcMenu = this.sdcMenu; + // Will be called from each step after save to update the resource. + this.$scope.setComponent = (component: Sdc.Models.Components.Component): void => { + this.$scope.component = component; + }; + + this.$scope.uploadFileChangedInGeneralTab = (): void => { + // In case user select browse file, and in update mode, need to disable submit for testing and checkin buttons. + if (this.$scope.isEditMode() && this.$scope.component.isResource() && (<Resource>this.$scope.component).resourceType == ResourceType.VF) { + this.$scope.disabledButtons = true; + } + }; + + this.$scope.onMenuItemPressed = (state: string): ng.IPromise<boolean> => { + let deferred = this.$q.defer(); + if (this.isNeedSave()) { + if (this.$scope.isValidForm) { + let onSuccess = (): void => { + this.$state.go(state, { + id: this.$scope.component.uniqueId, + type: this.$scope.component.componentType.toLowerCase(), + components: this.components + }); + deferred.resolve(true); + }; + this.$scope.save().then(onSuccess); + } else { + console.log('form is not valid'); + deferred.reject(false); + } + } else { + this.$state.go(state, { + id: this.$scope.component.uniqueId, + type: this.$scope.component.componentType.toLowerCase(), + components: this.components + }); + deferred.resolve(true); + } + return deferred.promise; + }; + + this.$scope.setValidState = (isValid: boolean): void => { + this.$scope.isValidForm = isValid; + }; + + this.$scope.onVersionChanged = (selectedId: string): void => { + this.$scope.isLoading = true; + if (this.$state.current.data && this.$state.current.data.unsavedChanges) { + this.$scope.changeVersion.selectedVersion = _.find(this.$scope.versionsList, {versionId: this.$scope.component.uniqueId}); + } + this.$state.go(this.$state.current.name, { + id: selectedId, + type: this.$scope.componentType.toLowerCase(), + mode: Utils.Constants.WorkspaceMode.VIEW, + components: this.$state.params['components'] + }); + + }; + + this.$scope.getLatestVersion = (): void => { + this.$scope.onVersionChanged(_.first(this.$scope.versionsList).versionId); + }; + + this.$scope.save = (state?: string): ng.IPromise<boolean> => { + this.EventListenerService.notifyObservers(Utils.Constants.EVENTS.ON_WORKSPACE_SAVE_BUTTON_CLICK); + + this.progressService.initCreateComponentProgress(this.$scope.component.uniqueId); + + let deferred = this.$q.defer(); + let modalInstance: ng.ui.bootstrap.IModalServiceInstance; + + let onFailed = () => { + this.EventListenerService.notifyObservers(Utils.Constants.EVENTS.ON_WORKSPACE_SAVE_BUTTON_ERROR); + this.progressService.deleteProgressValue(this.$scope.component.uniqueId); + modalInstance && modalInstance.close(); // Close the modal in case it is opened. + this.$scope.isCreateProgress = false; + this.$scope.isLoading = false; // stop the progress. + + this.$scope.setValidState(true); // Set the form valid (if sent form is valid, the error from server). + if (!this.$scope.isCreateMode()) { + this.$scope.component = this.$scope.originComponent; // Set the component back to the original. + this.enableMenuItems(); // Enable the menu items (left tabs), so user can press on them. + this.$scope.disabledButtons = false; // Enable "submit for testing" & checking buttons. + } + + deferred.reject(false); + }; + + let onSuccessCreate = (component: Models.Components.Component) => { + + this.EventListenerService.notifyObservers(Utils.Constants.EVENTS.ON_WORKSPACE_SAVE_BUTTON_SUCCESS); + this.progressService.deleteProgressValue(this.$scope.component.uniqueId); + //update components for breadcrumbs + this.components.unshift(component); + this.$state.go(Utils.Constants.States.WORKSPACE_GENERAL, { + id: component.uniqueId, + type: component.componentType.toLowerCase(), + components: this.components + }); + + deferred.resolve(true); + }; + + let onSuccessUpdate = (component: Models.Components.Component) => { + this.$scope.isCreateProgress = false; + this.$scope.disabledButtons = false; + this.EventListenerService.notifyObservers(Utils.Constants.EVENTS.ON_WORKSPACE_SAVE_BUTTON_SUCCESS); + this.progressService.deleteProgressValue(this.$scope.component.uniqueId); + + // Stop the circle loader. + this.$scope.isLoading = false; + + component.tags = _.reject(component.tags, (item)=> { + return item === component.name + }); + + // Update the components + this.$scope.component = component; + this.$scope.originComponent = this.ComponentFactory.createComponent(this.$scope.component); + + //update components for breadcrumbs + this.components.unshift(component); + + // Enable left tags + this.$scope.enabledTabs(); + + + if (this.$state.current.data) { + this.$state.current.data.unsavedChanges = false; + } + + deferred.resolve(true); + }; + + if (this.$scope.isCreateMode()) { + this.$scope.progressMessage = "Creating Asset..."; + // CREATE MODE + this.$scope.isCreateProgress = true; + + // Start creating the component + this.ComponentFactory.createComponentOnServer(this.$scope.component).then(onSuccessCreate, onFailed); + + // In case we import CSAR. Notify user that import VF will take long time (the create is performed in the background). + if (this.$scope.component.isResource() && (<Resource>this.$scope.component).csarUUID) { + this.Notification.info({ + message: this.$filter('translate')("IMPORT_VF_MESSAGE_CREATE_TAKES_LONG_TIME_DESCRIPTION"), + title: this.$filter('translate')("IMPORT_VF_MESSAGE_CREATE_TAKES_LONG_TIME_TITLE") + }); + } + } else { + // UPDATE MODE + this.$scope.isCreateProgress = true; + this.$scope.progressMessage = "Updating Asset..."; + this.disableMenuItems(); + + + // Work around to change the csar version + if (this.cacheService.get(Utils.Constants.CHANGE_COMPONENT_CSAR_VERSION_FLAG)) { + (<Resource>this.$scope.component).csarVersion = this.cacheService.get(Utils.Constants.CHANGE_COMPONENT_CSAR_VERSION_FLAG); + this.cacheService.remove(Utils.Constants.CHANGE_COMPONENT_CSAR_VERSION_FLAG); + } + + this.$scope.component.updateComponent().then(onSuccessUpdate, onFailed); + } + return deferred.promise; + }; + + this.$scope.revert = (): void => { + //in state of import file leave the file in place + if (this.$scope.component.isResource() && (<Resource>this.$scope.component).importedFile) { + let tempFile: Sdc.Directives.FileUploadModel = (<Resource>this.$scope.component).importedFile; + this.$scope.component = this.ComponentFactory.createComponent(this.$scope.originComponent); + (<Resource>this.$scope.component).importedFile = tempFile; + } else { + this.$scope.component = this.ComponentFactory.createComponent(this.$scope.originComponent); + } + + }; + + this.$scope.changeLifecycleState = (state: string): void => { + if (this.isNeedSave() && state !== 'deleteVersion') { + this.$scope.save().then(() => { + changeLifecycleState(state); + }) + } else { + changeLifecycleState(state); + } + }; + + let defaultActionAfterChangeLifecycleState = (): void => { + if (this.$state.current.data && this.$state.current.data.unsavedChanges) { + this.$state.current.data.unsavedChanges = false; + } + this.$state.go('dashboard'); + }; + + let changeLifecycleState = (state: string) => { + if ('monitor' === state) { + this.$state.go('workspace.distribution'); + return; + } + + let data = this.$scope.changeLifecycleStateButtons[state]; + let onSuccess = (component: Models.Components.Component): void => { + //Updating the component from server response + + //the server returns only metaData (small component) except checkout (Full component) ,so we update only the statuses of distribution & lifecycle + this.$scope.component.lifecycleState = component.lifecycleState; + this.$scope.component.distributionStatus = component.distributionStatus; + + switch (data.url) { + case 'lifecycleState/CHECKOUT': + // only checkOut get the full component from server + this.$scope.component = component; + // Work around to change the csar version + if (this.cacheService.get(Utils.Constants.CHANGE_COMPONENT_CSAR_VERSION_FLAG)) { + (<Resource>this.$scope.component).csarVersion = this.cacheService.get(Utils.Constants.CHANGE_COMPONENT_CSAR_VERSION_FLAG); + } + + //when checking out a minor version uuid remains + let bcComponent: Sdc.Models.Components.Component = _.find(this.components, (item) => { + return item.uuid === component.uuid; + }); + if (bcComponent) { + this.components[this.components.indexOf(bcComponent)] = component; + } else { + //when checking out a major(certified) version + this.components.unshift(component); + } + + this.$state.go(this.$state.current.name, { + id: component.uniqueId, + type: component.componentType.toLowerCase(), + components: this.components + }); + this.Notification.success({ + message: this.$filter('translate')("CHECKOUT_SUCCESS_MESSAGE_TEXT"), + title: this.$filter('translate')("CHECKOUT_SUCCESS_MESSAGE_TITLE") + }); + break; + case 'lifecycleState/CHECKIN': + defaultActionAfterChangeLifecycleState(); + this.Notification.success({ + message: this.$filter('translate')("CHECKIN_SUCCESS_MESSAGE_TEXT"), + title: this.$filter('translate')("CHECKIN_SUCCESS_MESSAGE_TITLE") + }); + break; + case 'lifecycleState/UNDOCHECKOUT': + defaultActionAfterChangeLifecycleState(); + this.Notification.success({ + message: this.$filter('translate')("DELETE_SUCCESS_MESSAGE_TEXT"), + title: this.$filter('translate')("DELETE_SUCCESS_MESSAGE_TITLE") + }); + break; + case 'lifecycleState/certificationRequest': + defaultActionAfterChangeLifecycleState(); + this.Notification.success({ + message: this.$filter('translate')("SUBMIT_FOR_TESTING_SUCCESS_MESSAGE_TEXT"), + title: this.$filter('translate')("SUBMIT_FOR_TESTING_SUCCESS_MESSAGE_TITLE") + }); + break; + //Tester Role + case 'lifecycleState/failCertification': + defaultActionAfterChangeLifecycleState(); + this.Notification.success({ + message: this.$filter('translate')("REJECT_SUCCESS_MESSAGE_TEXT"), + title: this.$filter('translate')("REJECT_SUCCESS_MESSAGE_TITLE") + }); + break; + case 'lifecycleState/certify': + defaultActionAfterChangeLifecycleState(); + this.Notification.success({ + message: this.$filter('translate')("ACCEPT_TESTING_SUCCESS_MESSAGE_TEXT"), + title: this.$filter('translate')("ACCEPT_TESTING_SUCCESS_MESSAGE_TITLE") + }); + break; + //DE203504 Bug Fix Start + case 'lifecycleState/startCertification': + this.initChangeLifecycleStateButtons(); + this.Notification.success({ + message: this.$filter('translate')("START_TESTING_SUCCESS_MESSAGE_TEXT"), + title: this.$filter('translate')("START_TESTING_SUCCESS_MESSAGE_TITLE") + }); + break; + case 'lifecycleState/cancelCertification': + this.initChangeLifecycleStateButtons(); + this.Notification.success({ + message: this.$filter('translate')("CANCEL_TESTING_SUCCESS_MESSAGE_TEXT"), + title: this.$filter('translate')("CANCEL_TESTING_SUCCESS_MESSAGE_TITLE") + }); + break; + //Ops Role + case 'distribution/PROD/activate': + this.initChangeLifecycleStateButtons(); + this.Notification.success({ + message: this.$filter('translate')("DISTRIBUTE_SUCCESS_MESSAGE_TEXT"), + title: this.$filter('translate')("DISTRIBUTE_SUCCESS_MESSAGE_TITLE") + }); + break; + //Governor Role + case 'distribution-state/reject': + this.initChangeLifecycleStateButtons(); + this.Notification.success({ + message: this.$filter('translate')("REJECT_SUCCESS_MESSAGE_TEXT"), + title: this.$filter('translate')("REJECT_SUCCESS_MESSAGE_TITLE") + }); + break; + case 'distribution-state/approve': + this.initChangeLifecycleStateButtons(); + this.$state.go('catalog'); + this.Notification.success({ + message: this.$filter('translate')("APPROVE_SUCCESS_MESSAGE_TEXT"), + title: this.$filter('translate')("APPROVE_SUCCESS_MESSAGE_TITLE") + }); + break; + //DE203504 Bug Fix End + + default : + defaultActionAfterChangeLifecycleState(); + + } + if (data.url != 'lifecycleState/CHECKOUT') { + this.$scope.isLoading = false; + } + }; + //this.$scope.isLoading = true; + this.ChangeLifecycleStateHandler.changeLifecycleState(this.$scope.component, data, this.$scope, onSuccess); + }; + + this.$scope.enabledTabs = (): void => { + this.$scope.leftBarTabs.menuItems.forEach((item: Utils.MenuItem) => { + item.isDisabled = false; + }); + }; + + this.$scope.isViewMode = (): boolean => { + return this.$scope.mode === Utils.Constants.WorkspaceMode.VIEW; + }; + + this.$scope.isDesigner = (): boolean => { + return this.role == Utils.Constants.Role.DESIGNER; + }; + + this.$scope.isDisableMode = (): boolean => { + return this.$scope.mode === Utils.Constants.WorkspaceMode.VIEW && this.$scope.component.lifecycleState === Utils.Constants.ComponentState.NOT_CERTIFIED_CHECKIN; + }; + + this.$scope.showFullIcons = (): boolean => { + //we show revert and save icons only in general\icon view + return this.$state.current.name === Utils.Constants.States.WORKSPACE_GENERAL || + this.$state.current.name === Utils.Constants.States.WORKSPACE_ICONS; + }; + + this.$scope.isCreateMode = (): boolean => { + return this.$scope.mode === Utils.Constants.WorkspaceMode.CREATE; + }; + + this.$scope.isEditMode = (): boolean => { + return this.$scope.mode === Utils.Constants.WorkspaceMode.EDIT; + }; + + this.$scope.goToBreadcrumbHome = (): void => { + let bcHome: Sdc.Utils.MenuItemGroup = this.$scope.breadcrumbsModel[0]; + this.$state.go(bcHome.menuItems[bcHome.selectedIndex].state); + }; + + this.$scope.showLifecycleIcon = (): boolean => { + return this.role == Utils.Constants.Role.DESIGNER || + this.role == Utils.Constants.Role.PRODUCT_MANAGER; + }; + + this.$scope.getStatus = (): string => { + if (this.$scope.isCreateMode()) { + return 'IN DESIGN'; + } + + return this.$scope.component.getStatus(this.sdcMenu); + }; + + this.initMenuItems(); + + this.$scope.showChangeStateButton = (): boolean => { + let result: boolean = true; + if (!this.$scope.component.isLatestVersion() && Utils.Constants.Role.OPS != this.role && Utils.Constants.Role.GOVERNOR != this.role) { + result = false; + } + if (this.role === Utils.Constants.Role.PRODUCT_MANAGER && !this.$scope.component.isProduct()) { + result = false; + } + if ((this.role === Utils.Constants.Role.DESIGNER || this.role === Utils.Constants.Role.TESTER) + && this.$scope.component.isProduct()) { + result = false; + } + if (Utils.Constants.ComponentState.NOT_CERTIFIED_CHECKOUT === this.$scope.component.lifecycleState && this.$scope.isViewMode()) { + result = false; + } + if (Utils.Constants.ComponentState.CERTIFIED != this.$scope.component.lifecycleState && + (Utils.Constants.Role.OPS == this.role || Utils.Constants.Role.GOVERNOR == this.role)) { + result = false; + } + return result; + }; + + this.$scope.updateSelectedMenuItem = (): void => { + let selectedItem: Sdc.Utils.MenuItem = _.find(this.$scope.leftBarTabs.menuItems, (item: Sdc.Utils.MenuItem) => { + return item.state === this.$state.current.name; + }); + this.$scope.leftBarTabs.selectedIndex = selectedItem ? this.$scope.leftBarTabs.menuItems.indexOf(selectedItem) : 0; + }; + + this.$scope.$watch('$state.current.name', (newVal: string): void => { + if (newVal) { + this.$scope.isComposition = (newVal.indexOf(Utils.Constants.States.WORKSPACE_COMPOSITION) > -1); + this.$scope.isDeployment = (newVal.indexOf(Utils.Constants.States.WORKSPACE_DEPLOYMENT) > -1); + } + }); + }; + + private initAfterScope = (): void => { + // In case user select csar from the onboarding modal, need to disable checkout and submit for testing. + if (this.$state.params['disableButtons'] === true) { + this.$scope.uploadFileChangedInGeneralTab(); + } + }; + + private initVersionObject = (): void => { + this.$scope.versionsList = (this.$scope.component.getAllVersionsAsSortedArray()).reverse(); + this.$scope.changeVersion = {selectedVersion: _.find(this.$scope.versionsList, {versionId: this.$scope.component.uniqueId})}; + }; + + private getNewComponentBreadcrumbItem = (): Utils.MenuItem => { + let text = ""; + if (this.$scope.component.isResource() && (<Resource>this.$scope.component).isCsarComponent()) { + text = this.$scope.component.getComponentSubType() + ': ' + this.$scope.component.name; + } else { + text = 'Create new ' + this.$state.params['type']; + } + return new Utils.MenuItem(text, null, Utils.Constants.States.WORKSPACE_GENERAL, 'goToState', [this.$state.params]); + }; + + private updateMenuItemByRole = (menuItems: Array<Utils.MenuItem>, role: string) => { + let tempMenuItems: Array<Utils.MenuItem> = new Array<Utils.MenuItem>(); + menuItems.forEach((item: Utils.MenuItem) => { + //remove item if role is disabled + if (!(item.disabledRoles && item.disabledRoles.indexOf(role) > -1)) { + tempMenuItems.push(item); + } + }); + return tempMenuItems; + }; + + private initBreadcrumbs = () => { + this.components = this.cacheService.get('breadcrumbsComponents'); + let breadcrumbsComponentsLvl = this.MenuHandler.generateBreadcrumbsModelFromComponents(this.components, this.$scope.component); + + if (this.$scope.isCreateMode()) { + let createItem = this.getNewComponentBreadcrumbItem(); + if (!breadcrumbsComponentsLvl.menuItems) { + breadcrumbsComponentsLvl.menuItems = []; + } + breadcrumbsComponentsLvl.menuItems.unshift(createItem); + breadcrumbsComponentsLvl.selectedIndex = 0; + } + + this.$scope.breadcrumbsModel = [breadcrumbsComponentsLvl, this.$scope.leftBarTabs]; + }; + + private initMenuItems() { + + let inCreateMode = this.$scope.isCreateMode(); + this.$scope.leftBarTabs = new Utils.MenuItemGroup(); + this.$scope.leftBarTabs.menuItems = this.updateMenuItemByRole(this.sdcMenu.component_workspace_menu_option[this.$scope.component.getComponentSubType()], this.role); + + this.$scope.leftBarTabs.menuItems.forEach((item: Utils.MenuItem) => { + item.params = [item.state]; + item.callback = this.$scope.onMenuItemPressed; + item.isDisabled = (inCreateMode && Utils.Constants.States.WORKSPACE_GENERAL != item.state) || + (Utils.Constants.States.WORKSPACE_DEPLOYMENT === item.state && this.$scope.component.groups.length === 0 && this.$scope.component.isResource()); + }); + + if (this.cacheService.get('breadcrumbsComponents')) { + this.initBreadcrumbs(); + } else { + let onSuccess = (components: Array<Models.Components.Component>) => { + this.cacheService.set('breadcrumbsComponents', components); + this.initBreadcrumbs(); + }; + this.EntityService.getCatalog().then(onSuccess); //getAllComponents() doesnt return components from catalog + } + } + + private disableMenuItems() { + this.$scope.leftBarTabs.menuItems.forEach((item: Utils.MenuItem) => { + item.params = [item.state]; + item.callback = this.$scope.onMenuItemPressed; + item.isDisabled = (Utils.Constants.States.WORKSPACE_GENERAL != item.state); + }); + } + + private enableMenuItems() { + this.$scope.leftBarTabs.menuItems.forEach((item: Utils.MenuItem) => { + item.params = [item.state]; + item.callback = this.$scope.onMenuItemPressed; + item.isDisabled = false; + }); + } + + } +} + + diff --git a/catalog-ui/app/scripts/view-models/workspace/workspace-view.html b/catalog-ui/app/scripts/view-models/workspace/workspace-view.html new file mode 100644 index 0000000000..118f7474be --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/workspace-view.html @@ -0,0 +1,86 @@ +<div class="sdc-workspace-container"> + <loader data-display="isLoading"></loader> + + <!-- HEADER --> +<!-- + <ecomp-header menu-data="menuItems" version="{{version}}"></ecomp-header> +--> + + <div class="w-sdc-main-container"> + + <div class="w-sdc-left-sidebar" data-ng-if="!isComposition"> + <div class="i-sdc-left-sidebar-item"> + <expand-collapse-menu-box menu-items-group="leftBarTabs" menu-title="{{menuComponentTitle}}" parent-scope="this"> </expand-collapse-menu-box> + </div> + </div> + + <div include-padding="true" class="w-sdc-main-right-container" data-ng-class="{'composition':isComposition}"> + + <!--<div class="w-sdc-main-right-container" data-ng-class="{'composition':isComposition}">--> + <loader data-display="isCreateProgress" relative="true"></loader> + + <div class="sdc-workspace-top-bar"> + <div class="version-container"> + + + <span data-ng-if="!isCreateMode() && !component.isLatestVersion()" class="not-latest"></span> + <select class="version-selector" data-ng-if="!isCreateMode()" data-tests-id="versionHeader" data-ng-model="changeVersion.selectedVersion" + ng-options="'V'+version.versionNumber for version in versionsList" data-ng-change="onVersionChanged(changeVersion.selectedVersion.versionId)"> + </select> + </div> + + <div class="lifecycle-state"> + <div data-ng-show="showLifecycleIcon()" class="lifecycle-state-icon" data-ng-class="{'in-design-status-icon': isCreateMode(), '{{sdcMenu.LifeCycleStatuses[component.lifecycleState].icon}}': !isCreateMode()}"></div> + <span class="lifecycle-state-text" data-tests-id="formlifecyclestate">{{getStatus()}}</span> + </div> + + + <div class="progress-container" > + <top-progress class="general-view-top-progress" progress-value="progressService.getProgressValue(component.uniqueId)" progress-message="progressMessage"></top-progress> + </div> + + <div class="sdc-workspace-top-bar-buttons"> + + <span ng-if="!isCreateMode() && !component.isLatestVersion() && !showChangeStateButton()">Switch to the <a ng-click="getLatestVersion()">latest version</a></span> + + <button ng-if="isDesigner() && !isCreateMode()" + data-ng-class="{'disabled' :!isValidForm || isDisableMode() || isViewMode()}" + ng-click="save()" + class="tlv-btn blue" + data-tests-id="create/save" + data-ng-show="showFullIcons()" + sdc-smart-tooltip="">Update</button> + + <button ng-repeat="(key,button) in changeLifecycleStateButtons" + ng-click="changeLifecycleState(key)" + ng-if="showChangeStateButton() && key != 'deleteVersion'" + data-ng-disabled="isCreateMode() || button.disabled || disabledButtons || !isValidForm" + class="change-lifecycle-state-btn tlv-btn" + ng-class="$first ? 'outline green' : 'grey'" + data-tests-id="{{button.text | testsId}}"> + {{button.text}} + </button> + + <button ng-if="!isViewMode() && isCreateMode()" data-ng-disabled="!isValidForm || isDisableMode() || isLoading" ng-click="save()" class="tlv-btn outline green" data-tests-id="create/save">Create</button> + + <span data-ng-if="isDesigner() && !isCreateMode() && component.lifecycleState === 'NOT_CERTIFIED_CHECKOUT'" sdc-smart-tooltip="" + data-ng-class="{'disabled' : !isValidForm || isDisableMode() || isViewMode()}" ng-click="changeLifecycleState('deleteVersion')" + class="sprite-new delete-btn" data-tests-id="delete_version" sdc-smart-tooltip="">Delete</span> + + <span data-ng-if="isDesigner()" data-ng-class="{'disabled' :isDisableMode() || isViewMode()}" ng-click="revert()" class="sprite-new revert-btn" data-tests-id="revert" + data-ng-show="showFullIcons()" sdc-smart-tooltip="">Revert</span> + + <span data-ng-if="isComposition && !component.isProduct()" class="sprite-new print-screen-btn" entity="component" print-graph-screen data-tests-id="printScreen"></span> + <span class="delimiter"></span> + <span class="sprite-new x-btn" data-ng-click="goToBreadcrumbHome()" sdc-smart-tooltip="">Close</span> + + </div> + </div> + + <div data-ng-if="component.creationDate && (!isComposition && !isDeployment)" class="sdc-asset-creation-info"><b>Created:</b> {{component.creationDate | date:'MM/dd/yyyy'}}, {{component.creatorFullName}} | <b>Modifed:</b> {{component.lastUpdateDate | date:'MM/dd/yyyy'}} | <b>UUID:</b> {{component.uuid}}<b> Invariant UUID:</b> {{component.invariantUUID}}</div> + + <div class="w-sdc-main-container-body-content" data-ng-class="{'third-party':thirdParty}" data-ui-view></div> + </div> + </div> + <top-nav search-bind="search.filterTerm" hide-search="true" menu-model="breadcrumbsModel" version="{{version}}"></top-nav> +</div> diff --git a/catalog-ui/app/scripts/view-models/workspace/workspace.less b/catalog-ui/app/scripts/view-models/workspace/workspace.less new file mode 100644 index 0000000000..d8bff1b634 --- /dev/null +++ b/catalog-ui/app/scripts/view-models/workspace/workspace.less @@ -0,0 +1,144 @@ +.sdc-workspace-container { + .bg_p; + + .add-btn { + .f-color.a; + .f-type._12_m; + .hand; + float: right; + margin-bottom: 15px; + + &:before { + .sprite-new; + .plus-icon; + margin-right: 5px; + content: ""; + + } + &:hover { + .f-color.b; + &:before { + .sprite-new; + .plus-icon-hover; + } + } + + } + .w-sdc-left-sidebar { + padding: 3px 3px 0px 0px; + background-color: @main_color_p; + box-shadow: 7px -3px 6px -8px @main_color_n; + z-index: 2; + } + + .sdc-asset-creation-info { + .n_12_r; + float: right; + margin: 8px 20px 0 0; + } + + .w-sdc-main-right-container { + + padding: 0px 0px 0px 0px; + background-color: @main_color_p; + z-index: 1; + + .sdc-workspace-top-bar { + height: @action_nav_height; + padding: 12px 10px 0px 50px; + border-bottom: 1px solid @main_color_o; + display: flex; + justify-content: space-between; + + .version-container { + + } + + .progress-container { + flex-grow: 4; + z-index: 10000000; + + .general-view-top-progress { + width: 30%; + margin: 0 auto; + } + } + + .not-latest { + position: absolute; + left: 24px; + top: 20px; + .sprite-new; + .asdc-warning; + } + + .sdc-workspace-top-bar-buttons { + + > button, > span:not(.delimiter) { + margin-right: 10px; + vertical-align: middle; + .hand; + + &.sprite-new { + text-indent: 100%; + } + &.disabled, &:hover.disabled { + pointer-events: none; + } + } + .delimiter { + height: 32px; + width: 1px; + background-color: #959595; + display: inline-block; + vertical-align: middle; + margin-right: 20px; + } + + } + + .lifecycle-state { + padding: 7px 0 0 10px; + margin: 2px 0 7px 10px; + border-left: 1px solid @main_color_o; + line-height: 15px; + font-family: @font-omnes-medium; + color: @main_color_m; + + .lifecycle-state-icon { + .sprite-new; + } + .lifecycle-state-text { + + font-weight: bold; + text-transform: uppercase; + vertical-align: top; + padding: 3px; + } + } + + .version-selector { + // float:left; + background-color: transparent; + border: none; + margin-top: 6px; + } + } + .w-sdc-main-container-body-content { + height:100%; + + text-align: center; + align-items: center; + padding: 40px 14% 20px 14%; + &.third-party { + text-align: left; + padding: 0; + position: absolute; + top: @action_nav_height; + left: 0; + right: 0; + bottom: 0; + } + } + } +} |