summaryrefslogtreecommitdiffstats
path: root/catalog-ui/src/app/view-models/workspace/tabs/composition
diff options
context:
space:
mode:
authorMichael Lando <ml636r@att.com>2017-06-09 03:19:04 +0300
committerMichael Lando <ml636r@att.com>2017-06-09 03:19:04 +0300
commited64b5edff15e702493df21aa3230b81593e6133 (patch)
treea4cb01fdaccc34930a8db403a3097c0d1e40914b /catalog-ui/src/app/view-models/workspace/tabs/composition
parent280f8015d06af1f41a3ef12e8300801c7a5e0d54 (diff)
[SDC-29] catalog 1707 rebase commit.
Change-Id: I43c3dc5cf44abf5da817649bc738938a3e8388c1 Signed-off-by: Michael Lando <ml636r@att.com>
Diffstat (limited to 'catalog-ui/src/app/view-models/workspace/tabs/composition')
-rw-r--r--catalog-ui/src/app/view-models/workspace/tabs/composition/composition-view-model.ts242
-rw-r--r--catalog-ui/src/app/view-models/workspace/tabs/composition/composition-view.html95
-rw-r--r--catalog-ui/src/app/view-models/workspace/tabs/composition/composition.less873
-rw-r--r--catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view-model.ts301
-rw-r--r--catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view.html67
-rw-r--r--catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/artifacts/artifacts.less172
-rw-r--r--catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/details/details-view-model.ts132
-rw-r--r--catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/details/details-view.html136
-rw-r--r--catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/details/details.less72
-rw-r--r--catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view-model.ts217
-rw-r--r--catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view.html117
-rw-r--r--catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties.less38
-rw-r--r--catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/relations/relations-view-model.ts156
-rw-r--r--catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/relations/relations-view.html61
-rw-r--r--catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/relations/relations.less14
-rw-r--r--catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/structure/structure-view.html13
-rw-r--r--catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/structure/structure-view.ts14
17 files changed, 2720 insertions, 0 deletions
diff --git a/catalog-ui/src/app/view-models/workspace/tabs/composition/composition-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/composition/composition-view-model.ts
new file mode 100644
index 0000000000..e2d95280c8
--- /dev/null
+++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/composition-view-model.ts
@@ -0,0 +1,242 @@
+/*-
+ * ============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=========================================================
+ */
+'use strict';
+import {Component, Product, ComponentInstance, IAppMenu} from "app/models";
+import {SharingService, CacheService, EventListenerService, LeftPaletteLoaderService} from "app/services";
+import {ModalsHandler, GRAPH_EVENTS, ComponentFactory, ChangeLifecycleStateHandler, MenuHandler} from "app/utils";
+import {IWorkspaceViewModelScope} from "../../workspace-view-model";
+import {ComponentServiceNg2} from "app/ng2/services/component-services/component.service";
+import {ComponentGenericResponse} from "app/ng2/services/responses/component-generic-response";
+
+export interface ICompositionViewModelScope extends IWorkspaceViewModelScope {
+
+ currentComponent:Component;
+ selectedComponent:Component;
+ isLoading:boolean;
+ graphApi:any;
+ sharingService:SharingService;
+ sdcMenu:IAppMenu;
+ version:string;
+ isViewOnly:boolean;
+ isLoadingRightPanel:boolean;
+ onComponentInstanceVersionChange(component:Component);
+ isComponentInstanceSelected():boolean;
+ updateSelectedComponent():void
+ openUpdateModal();
+ deleteSelectedComponentInstance():void;
+ onBackgroundClick():void;
+ setSelectedInstance(componentInstance:ComponentInstance):void;
+ printScreen():void;
+
+ cacheComponentsInstancesFullData:Component;
+}
+
+export class CompositionViewModel {
+
+ static '$inject' = [
+ '$scope',
+ '$log',
+ 'sdcMenu',
+ 'MenuHandler',
+ '$uibModal',
+ '$state',
+ 'Sdc.Services.SharingService',
+ '$filter',
+ 'Sdc.Services.CacheService',
+ 'ComponentFactory',
+ 'ChangeLifecycleStateHandler',
+ 'LeftPaletteLoaderService',
+ 'ModalsHandler',
+ 'EventListenerService',
+ 'ComponentServiceNg2'
+ ];
+
+ constructor(private $scope:ICompositionViewModelScope,
+ private $log:ng.ILogService,
+ private sdcMenu:IAppMenu,
+ private MenuHandler:MenuHandler,
+ private $uibModal:ng.ui.bootstrap.IModalService,
+ private $state:ng.ui.IStateService,
+ private sharingService:SharingService,
+ private $filter:ng.IFilterService,
+ private cacheService:CacheService,
+ private ComponentFactory:ComponentFactory,
+ private ChangeLifecycleStateHandler:ChangeLifecycleStateHandler,
+ private LeftPaletteLoaderService:LeftPaletteLoaderService,
+ private ModalsHandler:ModalsHandler,
+ private eventListenerService:EventListenerService,
+ private ComponentServiceNg2: ComponentServiceNg2) {
+
+ this.$scope.setValidState(true);
+ this.initScope();
+ this.initGraphData();
+ this.$scope.updateSelectedMenuItem();
+ this.registerGraphEvents(this.$scope);
+ }
+
+
+ private initGraphData = ():void => {
+ if(!this.$scope.component.componentInstances || !this.$scope.component.componentInstancesRelations ) {
+ this.$scope.isLoading = true;
+ this.ComponentServiceNg2.getComponentInstancesAndRelation(this.$scope.component).subscribe((response:ComponentGenericResponse) => {
+ this.$scope.component.componentInstances = response.componentInstances;
+ this.$scope.component.componentInstancesRelations = response.componentInstancesRelations;
+ this.$scope.isLoading = false;
+ this.initComponent();
+ this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_COMPOSITION_GRAPH_DATA_LOADED);
+ });
+ } else {
+ this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_COMPOSITION_GRAPH_DATA_LOADED);
+ }
+ this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_COMPOSITION_GRAPH_DATA_LOADED);
+ };
+
+
+ private cacheComponentsInstancesFullData:Array<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(GRAPH_EVENTS.ON_NODE_SELECTED, scope.setSelectedInstance);
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED, scope.onBackgroundClick);
+
+ };
+
+ private openUpdateComponentInstanceNameModal = ():void => {
+ this.ModalsHandler.openUpdateComponentInstanceNameModal(this.$scope.currentComponent).then(()=> {
+ this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_COMPONENT_INSTANCE_NAME_CHANGED, this.$scope.currentComponent.selectedInstance);
+
+ });
+ };
+
+ private removeSelectedComponentInstance = ():void => {
+ this.eventListenerService.notifyObservers(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<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:Component) => {
+ this.$scope.isLoadingRightPanel = false;
+ this.$scope.selectedComponent = component;
+ this.cacheComponentsInstancesFullData.push(component);
+ };
+ let onError = (component: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: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.onComponentInstanceVersionChange = (component:Product):void => {
+ this.$scope.currentComponent = component;
+ this.$scope.setComponent(this.$scope.currentComponent);
+ this.$scope.updateSelectedComponent();
+ }
+
+ }
+}
diff --git a/catalog-ui/src/app/view-models/workspace/tabs/composition/composition-view.html b/catalog-ui/src/app/view-models/workspace/tabs/composition/composition-view.html
new file mode 100644
index 0000000000..761ae53909
--- /dev/null
+++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/composition-view.html
@@ -0,0 +1,95 @@
+<div class="workspace-composition">
+ <loader data-display="isLoading"></loader>
+ <div class="w-sdc-designer-canvas" data-ng-class="{sidebaractive: displayDesignerRightSidebar}">
+ <palette current-component="currentComponent"
+ is-view-only="isViewOnly"
+ is-loading="isLoading"></palette>
+
+ <composition-graph component="currentComponent" data-tests-id="canvas"
+ is-view-only="isViewOnly"></composition-graph>
+ </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="&#8203;{{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"
+ data-tests-id="information-tab">
+ <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/src/app/view-models/workspace/tabs/composition/composition.less b/catalog-ui/src/app/view-models/workspace/tabs/composition/composition.less
new file mode 100644
index 0000000000..501805be3f
--- /dev/null
+++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/composition.less
@@ -0,0 +1,873 @@
+
+.composition{
+ .sdc-workspace-container{
+ .w-sdc-main-container{
+ .w-sdc-main-right-container{
+ left:0;
+ //overflow-y: scroll;
+ .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: @footer_height;
+ 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: 60px;
+ .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: 0;
+ width: 100%;
+ padding-bottom: 10px;
+
+ }
+
+ .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;
+ }
+
+ &.update-env {
+ background-color: transparent;
+ border: 0;
+ right: 15px;
+ 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/src/app/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view-model.ts
new file mode 100644
index 0000000000..0ac5fd0799
--- /dev/null
+++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view-model.ts
@@ -0,0 +1,301 @@
+'use strict';
+import {
+ ArtifactModel,
+ Service,
+ IAppConfigurtaion,
+ Resource,
+ Component,
+ ComponentInstance,
+ ArtifactGroupModel,
+ IFileDownload
+} from "app/models";
+import {ICompositionViewModelScope} from "../../composition-view-model";
+import {ArtifactsUtils, ModalsHandler, ArtifactGroupType} from "app/utils";
+import {GRAPH_EVENTS} from "app/utils/constants";
+import {EventListenerService} from "app/services/event-listener-service";
+
+export interface IArtifactsViewModelScope extends ICompositionViewModelScope {
+ artifacts:Array<ArtifactModel>;
+ artifactType:string;
+ downloadFile:IFileDownload;
+ isLoading:boolean;
+
+ getTitle():string;
+ addOrUpdate(artifact:ArtifactModel):void;
+ delete(artifact:ArtifactModel):void;
+ download(artifact:ArtifactModel):void;
+ openEditEnvParametersModal(artifact:ArtifactModel):void;
+ getEnvArtifact(heatArtifact:ArtifactModel):any;
+ getEnvArtifactName(artifact:ArtifactModel):string;
+ isLicenseArtifact(artifact:ArtifactModel):boolean;
+ isVFiArtifact(artifact:ArtifactModel):boolean;
+}
+
+export class ResourceArtifactsViewModel {
+
+ static '$inject' = [
+ '$scope',
+ '$filter',
+ '$state',
+ 'sdcConfig',
+ 'ArtifactsUtils',
+ 'ModalsHandler',
+ '$q',
+ 'EventListenerService'
+ ];
+
+ constructor(private $scope:IArtifactsViewModelScope,
+ private $filter:ng.IFilterService,
+ private $state:any,
+ private sdcConfig:IAppConfigurtaion,
+ private artifactsUtils:ArtifactsUtils,
+ private ModalsHandler:ModalsHandler,
+ private $q:ng.IQService,
+ private eventListenerService: EventListenerService) {
+
+ this.initScope();
+ }
+
+
+ private initArtifactArr = (artifactType:string):void => {
+ let artifacts:Array<ArtifactModel> = [];
+
+ if (this.$scope.selectedComponent) {
+ if ('interface' == artifactType) {
+ let interfaces = this.$scope.currentComponent.interfaces;
+ if (interfaces && interfaces.standard && interfaces.standard.operations) {
+
+ angular.forEach(interfaces.standard.operations, (operation:any, interfaceName:string):void => {
+ let item:ArtifactModel = <ArtifactModel>{};
+ if (operation.implementation) {
+ item = <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:ArtifactGroupModel;
+ switch (artifactType) {
+ case "api":
+ artifactsObj = (<Service>this.$scope.currentComponent).serviceApiArtifacts;
+ break;
+ case "deployment":
+ if (!this.$scope.isComponentInstanceSelected()) {
+ artifactsObj = this.$scope.currentComponent.deploymentArtifacts;
+ } else {
+ artifactsObj = this.$scope.currentComponent.selectedInstance.deploymentArtifacts;
+ }
+ break;
+ default:
+ //artifactsObj = this.$scope.selectedComponent.artifacts;
+ if (!this.$scope.isComponentInstanceSelected()) {
+ artifactsObj = this.$scope.currentComponent.artifacts;
+ } else {
+ artifactsObj = this.$scope.currentComponent.selectedInstance.artifacts;
+ }
+ break;
+ }
+ _.forEach(artifactsObj, (artifact:ArtifactModel, key) => {
+ artifacts.push(artifact);
+ });
+ }
+ }
+ this.$scope.artifacts = artifacts;
+ };
+
+
+ private convertToArtifactUrl = (artifactType:string):string => {
+
+ switch (artifactType) {
+ case 'deployment':
+ return 'DEPLOYMENT';
+ case 'api':
+ return 'SERVICE_API';
+ default:
+ return 'INFORMATIONAL';
+ }
+
+ }
+
+ private loadComponentArtifactIfNeeded = (forceLoad?: boolean) => {
+
+ let onGetComponentArtifactsSuccess = (artifacts:ArtifactGroupModel)=> {
+ switch (this.$scope.artifactType) {
+ case 'deployment':
+ this.$scope.currentComponent.deploymentArtifacts = artifacts;
+ break;
+ case 'api':
+ (<Service>this.$scope.currentComponent).serviceApiArtifacts = artifacts;
+ break;
+ default:
+ this.$scope.currentComponent.artifacts = artifacts;
+ break;
+ }
+ this.$scope.isLoading = false;
+ this.initArtifactArr(this.$scope.artifactType);
+ }
+
+ let onError = ()=> {
+ this.$scope.isLoading = false;
+ };
+
+ switch (this.$scope.artifactType) {
+ case 'deployment':
+ if(forceLoad || !this.$scope.currentComponent.deploymentArtifacts) {
+ this.$scope.component.getArtifactByGroupType(this.convertToArtifactUrl(this.$scope.artifactType)).then(onGetComponentArtifactsSuccess, onError);
+ } else {
+ this.initArtifactArr(this.$scope.artifactType);
+ }
+
+ break;
+ case 'api':
+ if(!(<Service>this.$scope.currentComponent).serviceApiArtifacts) {
+ this.$scope.component.getArtifactByGroupType(this.convertToArtifactUrl(this.$scope.artifactType)).then(onGetComponentArtifactsSuccess, onError);
+ } else {
+ this.initArtifactArr(this.$scope.artifactType);
+ }
+ break;
+ default:
+ if(!this.$scope.currentComponent.artifacts) {
+ this.$scope.component.getArtifactByGroupType(this.convertToArtifactUrl(this.$scope.artifactType)).then(onGetComponentArtifactsSuccess, onError);
+ } else {
+ this.initArtifactArr(this.$scope.artifactType);
+ }
+ break;
+ }
+ }
+ private loadArtifacts = (forceLoad?: boolean):void => {
+
+ let onGetInstanceArtifactsSuccess = (artifacts:ArtifactGroupModel)=> {
+ switch (this.$scope.artifactType) {
+ case 'deployment':
+ this.$scope.currentComponent.selectedInstance.deploymentArtifacts = artifacts;
+ break;
+ default:
+ this.$scope.currentComponent.selectedInstance.artifacts = artifacts;
+ break;
+ }
+ this.loadComponentArtifactIfNeeded();
+ };
+
+ let onError = ()=> {
+ this.$scope.isLoading = false;
+ };
+
+ this.$scope.isLoading = true;
+ if (this.$scope.isComponentInstanceSelected()) {
+ this.$scope.component.getComponentInstanceArtifactsByGroupType(this.$scope.component.selectedInstance.uniqueId, this.convertToArtifactUrl(this.$scope.artifactType)).then(onGetInstanceArtifactsSuccess, onError);
+ } else {
+ this.loadComponentArtifactIfNeeded(forceLoad);
+ }
+ }
+
+ private updateArtifactsIfNeeded = ():void => {
+ if (this.$scope.artifactType === "deployment") {
+ this.loadArtifacts(true);
+ } else {
+ this.initArtifactArr(this.$scope.artifactType);
+ }
+ };
+
+ private openEditArtifactModal = (artifact:ArtifactModel):void => {
+ this.ModalsHandler.openArtifactModal(artifact, this.$scope.currentComponent).then(():void => {
+ this.updateArtifactsIfNeeded();
+ });
+ };
+
+ private initScope = ():void => {
+
+ this.$scope.isLoading = false;
+ this.$scope.artifactType = this.artifactsUtils.getArtifactTypeByState(this.$state.current.name);
+ this.loadArtifacts();
+ this.$scope.getTitle = ():string => {
+ return this.artifactsUtils.getTitle(this.$scope.artifactType, this.$scope.currentComponent);
+ };
+
+ this.$scope.isVFiArtifact = (artifact:ArtifactModel):boolean=> {
+ if (artifact.artifactGroupType === ArtifactGroupType.INFORMATION) {//fix DE256847
+ return this.$scope.currentComponent.artifacts && (!this.$scope.currentComponent.artifacts[artifact.artifactLabel] || !this.$scope.currentComponent.artifacts[artifact.artifactLabel].artifactName);
+ }
+ return this.$scope.currentComponent.deploymentArtifacts && (!this.$scope.currentComponent.deploymentArtifacts[artifact.artifactLabel]);//fix DE251314
+ };
+
+ this.$scope.addOrUpdate = (artifact:ArtifactModel):void => {
+ this.artifactsUtils.setArtifactType(artifact, this.$scope.artifactType);
+ let artifactCopy = new ArtifactModel(artifact);
+ this.openEditArtifactModal(artifactCopy);
+ };
+
+
+ this.$scope.delete = (artifact:ArtifactModel):void => {
+
+ let onOk = ():void => {
+ this.$scope.isLoading = true;
+ this.artifactsUtils.removeArtifact(artifact, this.$scope.artifacts);
+
+ let success = (responseArtifact: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:ArtifactModel):any=> {
+ return _.find(this.$scope.artifacts, (item:ArtifactModel)=> {
+ return item.generatedFromId === heatArtifact.uniqueId;
+ });
+ };
+
+ this.$scope.getEnvArtifactName = (artifact:ArtifactModel):string => {
+ let envArtifact = this.$scope.getEnvArtifact(artifact);
+ if (envArtifact) {
+ return envArtifact.artifactDisplayName;
+ }
+ };
+
+ this.$scope.isLicenseArtifact = (artifact: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:ArtifactModel):void => {
+ this.ModalsHandler.openEditEnvParametersModal(artifact, this.$scope.currentComponent).then(()=> {
+ this.updateArtifactsIfNeeded();
+ }, ()=> {
+ // ERROR
+ });
+ };
+
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_NODE_SELECTED, this.loadArtifacts);
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED, this.loadArtifacts);
+
+ this.$scope.$on('$destroy', () => {
+
+ this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_NODE_SELECTED, this.loadArtifacts);
+ this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED, this.loadArtifacts);
+ });
+ }
+}
diff --git a/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view.html b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view.html
new file mode 100644
index 0000000000..b0d81b3437
--- /dev/null
+++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view.html
@@ -0,0 +1,67 @@
+<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">
+ <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"
+ data-tests-id="artifact-item-{{artifact.artifactDisplayName}}">
+ <span data-ng-if="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-{{artifact.artifactDisplayName}}"
+ data-ng-bind="artifact.artifactName" tooltips tooltip-content="{{artifact.artifactName}}"
+ 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': (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="artifact.heatParameters.length">
+ <span data-ng-bind="getEnvArtifactName(artifact)"data-tests-id="heat_env_{{artifact.artifactDisplayName}}"></span>
+ <button class="i-sdc-designer-sidebar-section-content-item-button update-env sprite e-sdc-small-icon-pencil" data-tests-id="edit_{{artifact.artifactDisplayName}}"
+ data-ng-if="!isViewMode()" data-ng-click="addOrUpdate(getEnvArtifact(artifact))"></button>
+ <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="isComponentInstanceSelected()"
+ data-tests-id="download_env_{{artifact.artifactDisplayName}}"></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_{{artifact.artifactDisplayName}}" data-ng-click="delete(artifact)" type="button"></button>
+ <button ng-if="!isViewMode() && artifact.isHEAT() && isComponentInstanceSelected() && 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"
+ data-tests-id="edit-parameters-of-{{artifact.artifactDisplayName}}"></button>
+ <!--need to remove this button -->
+ <button ng-if="!isViewMode() && artifact.isHEAT() && !isComponentInstanceSelected() && artifact.heatParameters.length"
+ class="i-sdc-designer-sidebar-section-content-item-button attach sprite e-sdc-small-icon-pad"
+ data-ng-click="openEditEnvParametersModal(artifact)" type="button"
+ data-tests-id="edit-parameters-of-{{artifact.artifactDisplayName}}"></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="currentComponent" data-tests-id="download-{{artifact.artifactDisplayName}}" instance="isComponentInstanceSelected()"></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_{{artifact.artifactDisplayName}}"
+ show-loader="artifact.isHEAT()"
+ download-icon-class="i-sdc-designer-sidebar-section-content-item-button download sprite e-sdc-small-download hand"></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/src/app/view-models/workspace/tabs/composition/tabs/artifacts/artifacts.less b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/artifacts/artifacts.less
new file mode 100644
index 0000000000..7c8b8315d9
--- /dev/null
+++ b/catalog-ui/src/app/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/src/app/view-models/workspace/tabs/composition/tabs/details/details-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/details/details-view-model.ts
new file mode 100644
index 0000000000..a81bb9176e
--- /dev/null
+++ b/catalog-ui/src/app/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=========================================================
+ */
+
+'use strict';
+import {Component} from "app/models";
+import {GRAPH_EVENTS} from "app/utils";
+import {LeftPaletteLoaderService, EventListenerService} from "app/services";
+import {ICompositionViewModelScope} from "../../composition-view-model";
+import {LeftPaletteComponent} from "../../../../../../models/components/displayComponent";
+
+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:LeftPaletteLoaderService,
+ private eventListenerService: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));
+
+ if (parseFloat(highestVersion) % 1) { //if highest is minor, make sure it is the latest checked in -
+ let latestVersionComponent:LeftPaletteComponent = _.find(this.LeftPaletteLoaderService.getLeftPanelComponentsForDisplay(this.$scope.currentComponent.componentType), (component:LeftPaletteComponent) => { //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:Component) => {
+ if (this.$scope.isComponentInstanceSelected()) {
+ this.initEditResourceVersion();
+ }
+ });
+
+ this.$scope.changeResourceVersion = ():void => {
+ this.$scope.isLoading = true;
+ this.$scope.$parent.isLoading = true;
+
+ let onSuccess = (component:Component)=> {
+ this.$scope.isLoading = false;
+ this.$scope.$parent.isLoading = false;
+ this.$scope.onComponentInstanceVersionChange(component);
+
+ this.eventListenerService.notifyObservers(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/src/app/view-models/workspace/tabs/composition/tabs/details/details-view.html b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/details/details-view.html
new file mode 100644
index 0000000000..70dc58075a
--- /dev/null
+++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/details/details-view.html
@@ -0,0 +1,136 @@
+<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="&#8203;{{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-tests-id="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="&#8203;{{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="&#8203;{{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="&#8203;{{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="&#8203;{{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="&#8203;{{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_contactId"></span>
+ </div>
+ <div class="i-sdc-designer-sidebar-section-content-item" data-ng-if="isViewMode() && currentComponent.isService() && selectedComponent.isResource()">
+ <span class="i-sdc-designer-sidebar-section-content-item-label">Resource Customization UUID:</span><br>
+ <span class="customization-uuid selectable" data-ng-bind="currentComponent.selectedInstance.customizationUUID"
+ data-tests-id="rightTab_customizationModuleUUID"></span><br>
+ </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-if="selectedComponent.tags.indexOf(selectedComponent.name)===-1" data-ng-bind="selectedComponent.name"
+ data-tests-id="rightTab_tag" tooltips tooltip-content="{{selectedComponent.name}}"></span>
+ <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/src/app/view-models/workspace/tabs/composition/tabs/details/details.less b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/details/details.less
new file mode 100644
index 0000000000..841ab3aa49
--- /dev/null
+++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/details/details.less
@@ -0,0 +1,72 @@
+.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;
+ }
+
+ .customization-uuid{
+ .f-type._12_m;
+ }
+
+ .w-sdc-designer-sidebar-section.tags {
+ .i-sdc-designer-sidebar-section-content-item {
+ white-space: normal;
+ }
+ }
+
+}
diff --git a/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view-model.ts
new file mode 100644
index 0000000000..84769d7a62
--- /dev/null
+++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view-model.ts
@@ -0,0 +1,217 @@
+'use strict';
+import {
+ AttributeModel,
+ AttributesGroup,
+ Component,
+ ComponentInstance,
+ PropertyModel,
+ PropertiesGroup
+} from "app/models";
+import {ICompositionViewModelScope} from "../../composition-view-model";
+import {ModalsHandler} from "app/utils";
+import {ComponentServiceNg2} from "app/ng2/services/component-services/component.service";
+import {ComponentGenericResponse} from "app/ng2/services/responses/component-generic-response";
+
+interface IResourcePropertiesAndAttributesViewModelScope extends ICompositionViewModelScope {
+ properties:PropertiesGroup;
+ attributes:AttributesGroup;
+ propertiesMessage:string;
+ groupPropertiesByInstance:boolean;
+ showGroupsOfInstanceProperties:Array<boolean>;
+ addProperty():void;
+ updateProperty(property:PropertyModel):void;
+ deleteProperty(property:PropertyModel):void;
+ viewAttribute(attribute:AttributeModel):void;
+ groupNameByKey(key:string):string;
+ isPropertyOwner():boolean;
+ getComponentInstanceNameFromInstanceByKey(key:string):string;
+}
+
+export class ResourcePropertiesViewModel {
+
+ static '$inject' = [
+ '$scope',
+ '$filter',
+ '$uibModal',
+ 'ModalsHandler',
+ 'ComponentServiceNg2'
+
+ ];
+
+
+ constructor(private $scope:IResourcePropertiesAndAttributesViewModelScope,
+ private $filter:ng.IFilterService,
+ private $uibModal:ng.ui.bootstrap.IModalService,
+ private ModalsHandler:ModalsHandler,
+ private ComponentServiceNg2:ComponentServiceNg2) {
+
+ this.getComponentInstancesPropertiesAndAttributes();
+ }
+
+ private initComponentProperties = ():void => {
+ let result:PropertiesGroup = {};
+
+ if (this.$scope.selectedComponent) {
+ this.$scope.propertiesMessage = undefined;
+ this.$scope.groupPropertiesByInstance = false;
+ if (this.$scope.isComponentInstanceSelected()) {
+ if (this.$scope.currentComponent.selectedInstance.originType === 'VF') {
+ this.$scope.groupPropertiesByInstance = true;
+ }
+ 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<PropertyModel>();
+ let derived = Array<PropertyModel>();
+ _.forEach(this.$scope.selectedComponent.properties, (property: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: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;
+ }
+ };
+
+ /**
+ * This function is checking if the component is the value owner of the current property
+ * in order to notify the edit property modal which fields to disable
+ */
+ private isPropertyValueOwner = ():boolean => {
+ return this.$scope.currentComponent.isService() || !!this.$scope.currentComponent.selectedInstance;
+ };
+
+ /**
+ * The function opens the edit property modal.
+ * It checks if the property is from the VF or from one of it's resource instances and sends the needed property list.
+ * For create property reasons an empty array is transferd
+ *
+ * @param property the wanted property to edit/create
+ */
+ private openEditPropertyModal = (property:PropertyModel):void => {
+ this.ModalsHandler.openEditPropertyModal(property,
+ this.$scope.component,
+ (this.$scope.isPropertyOwner() ?
+ this.$scope.properties[property.parentUniqueId] :
+ this.$scope.properties[property.resourceInstanceUniqueId]) || [],
+ this.isPropertyValueOwner()).then(() => {
+ });
+ };
+
+ private openAttributeModal = (atrribute:AttributeModel):void => {
+
+ let modalOptions:ng.ui.bootstrap.IModalSettings = {
+ template: 'app/view-models/forms/attribute-form/attribute-form-view.html',
+ controller: 'Sdc.ViewModels.AttributeFormViewModel',
+ size: 'sdc-md',
+ backdrop: 'static',
+ keyboard: false,
+ resolve: {
+ attribute: ():AttributeModel => {
+ return atrribute;
+ },
+ component: ():Component => {
+ return this.$scope.currentComponent;
+ }
+ }
+ };
+ this.$uibModal.open(modalOptions);
+ };
+
+ private getComponentInstancesPropertiesAndAttributes = () => {
+
+ this.ComponentServiceNg2.getComponentInstanceAttributesAndProperties(this.$scope.currentComponent).subscribe((genericResponse:ComponentGenericResponse) => {
+ this.$scope.currentComponent.componentInstancesAttributes = genericResponse.componentInstancesAttributes;
+ this.$scope.currentComponent.componentInstancesProperties = genericResponse.componentInstancesProperties;
+ this.initScope();
+ });
+ };
+
+ private initScope = ():void => {
+
+
+ this.initComponentProperties();
+ this.initComponentAttributes();
+
+ this.$scope.$watchCollection('currentComponent.properties', (newData:any):void => {
+ this.initComponentProperties();
+ });
+
+ this.$scope.$watch('currentComponent.selectedInstance', (newInstance:ComponentInstance):void => {
+ if (angular.isDefined(newInstance)) {
+ this.initComponentProperties();
+ this.initComponentAttributes();
+
+ }
+ });
+
+ this.$scope.isPropertyOwner = ():boolean => {
+ return this.$scope.currentComponent && this.$scope.currentComponent.isResource() && !this.$scope.isComponentInstanceSelected();
+ };
+
+ this.$scope.updateProperty = (property:PropertyModel):void => {
+ this.openEditPropertyModal(property);
+ };
+
+ this.$scope.deleteProperty = (property: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: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);
+ }
+ };
+
+ this.$scope.getComponentInstanceNameFromInstanceByKey = (key:string):string => {
+ let instanceName:string = "";
+ if (key !== undefined && this.$scope.selectedComponent.uniqueId == this.$scope.currentComponent.selectedInstance.componentUid) {
+ instanceName = this.$filter("resourceName")((_.find(this.$scope.selectedComponent.componentInstances, {uniqueId: key})).name);
+ }
+ return instanceName;
+ };
+
+ }
+}
diff --git a/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view.html b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view.html
new file mode 100644
index 0000000000..6df8b6a4d6
--- /dev/null
+++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view.html
@@ -0,0 +1,117 @@
+<perfect-scrollbar class="w-sdc-designer-sidebar-tab-content properties" id="main-scroll">
+
+ <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}}&nbsp;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}}">
+ <div class="first-level">
+ <div class="expand-collapse-title-icon"></div>
+ <span class="w-sdc-designer-sidebar-section-title-text" data-ng-bind="groupNameByKey(key) + ' Properties'"
+ tooltips tooltip-content="{{groupNameByKey(key)}}&nbsp;Properties"
+ data-tests-id="vfi-properties"></span>
+ </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" data-ng-if="!groupPropertiesByInstance">
+ <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)"
+ data-tests-id="{{property.name}}">{{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}}"
+ data-tests-id="value_{{property.name}}">{{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 class="i-sdc-designer-sidebar-section-content-item" data-ng-if="groupPropertiesByInstance">
+ <div data-ng-repeat-start="(instancesIds , InstanceProperties) in (group | groupBy:'path')"
+ class="vfci-properties-group"
+ data-ng-click="showGroupsOfInstanceProperties[$index]=!showGroupsOfInstanceProperties[$index]"
+ data-ng-class="{'expanded':showGroupsOfInstanceProperties[$index]}">
+ <div class="second-level">
+ <div class="expand-collapse-title-icon"></div>
+ <span class="w-sdc-designer-sidebar-section-title-text" data-ng-bind="getComponentInstanceNameFromInstanceByKey(InstanceProperties[0].path[1]) + ' Properties'"
+ tooltips tooltip-content="{{getComponentInstanceNameFromInstanceByKey(InstanceProperties[0].path[1])}}&nbsp;Properties"
+ data-tests-id="vfci-properties"></span>
+ </div>
+ </div>
+ <div data-ng-repeat-end="" class="w-sdc-designer-sidebar-section-content instance-properties {{$index}}" data-ng-if="showGroupsOfInstanceProperties[$index]">
+ <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="instanceProperty in InstanceProperties | orderBy: 'name'">
+ <div>
+ <span class="i-sdc-designer-sidebar-section-content-item-property-and-attribute-label"
+ data-ng-class="{'hand enabled': !$parent.isViewOnly}"
+ tooltips tooltip-content="{{instanceProperty.name}}"
+ data-tests-id="vfci-property">{{instanceProperty.name}}</span>
+ </div>
+ <div>
+ <span class="i-sdc-designer-sidebar-section-content-item-property-value"
+ tooltips tooltip-content="{{instanceProperty.value === undefined ? instanceProperty.defaultValue : instanceProperty.value}}">
+ {{instanceProperty.value === undefined ? instanceProperty.defaultValue : instanceProperty.value}}</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <!--<div class="w-sdc-designer-sidebar-section-footer" data-ng-if="(!$parent.isViewOnly && isPropertyOwner()) || showAddPropertyButton">-->
+ <!--<button class="w-sdc-designer-sidebar-section-footer-action tlv-btn blue" data-tests-id="addGrey" data-ng-click="addProperty()" type="button">-->
+ <!--Add Property-->
+ <!--</button>-->
+ <!--</div>-->
+ </div>
+
+
+ <!--attributes-->
+ <expand-collapse data-ng-repeat-start="(key, group) in attributes"
+ expanded-selector=".w-sdc-designer-sidebar-section-content.attributes.{{$index}}">
+ <div class="first-level">
+ <div class="expand-collapse-title-icon"></div>
+ <span class="w-sdc-designer-sidebar-section-title-text" data-ng-bind="groupNameByKey(key) + ' Attributes'"
+ tooltips tooltip-content="{{groupNameByKey(key)}}&nbsp;Attributes"></span>
+ </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/src/app/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties.less b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties.less
new file mode 100644
index 0000000000..41a90bff9d
--- /dev/null
+++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties.less
@@ -0,0 +1,38 @@
+.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;
+ }
+
+ .vfci-properties-group{
+ background-color: @func_color_r;
+ }
+
+ .expand-collapse-title-icon{
+ .hand;
+ .sprite-new;
+ .expand-collapse-plus-icon;
+ vertical-align: middle;
+ margin: 0 6px;
+ }
+
+ .expanded {
+ .expand-collapse-title-icon {
+ .expand-collapse-minus-icon;
+ }
+ }
+
+ .w-sdc-designer-sidebar-section-title-text{
+ vertical-align: middle;
+ }
+}
diff --git a/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/relations/relations-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/relations/relations-view-model.ts
new file mode 100644
index 0000000000..325f250ebe
--- /dev/null
+++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/relations/relations-view-model.ts
@@ -0,0 +1,156 @@
+'use strict';
+import {ICompositionViewModelScope} from "../../composition-view-model";
+import {CapabilitiesGroup, Requirement, RequirementsGroup} from "app/models";
+import {ComponentServiceNg2} from "app/ng2/services/component-services/component.service";
+import {ComponentGenericResponse} from "app/ng2/services/responses/component-generic-response";
+import {GRAPH_EVENTS} from "app/utils";
+import {EventListenerService} from "app/services";
+import {ComponentInstance, Capability} from "app/models";
+
+interface IRelationsViewModelScope extends ICompositionViewModelScope {
+ isLoading:boolean;
+ $parent:ICompositionViewModelScope;
+ getRelation(requirement:any):any;
+ capabilities:Array<Capability>;
+ requirements:Array<Requirement>;
+
+ //for complex components
+ capabilitiesInstancesMap:InstanceCapabilitiesMap;
+ requirementsInstancesMap:InstanceRequirementsMap;
+}
+export class InstanceCapabilitiesMap {
+ [key:string]:Array<Capability>;
+}
+
+export class InstanceRequirementsMap {
+ [key:string]:Array<Requirement>;
+}
+
+export class RelationsViewModel {
+
+ static '$inject' = [
+ '$scope',
+ '$filter',
+ 'ComponentServiceNg2',
+ 'EventListenerService'
+ ];
+
+ constructor(private $scope:IRelationsViewModelScope,
+ private $filter:ng.IFilterService,
+ private ComponentServiceNg2:ComponentServiceNg2,
+ private eventListenerService:EventListenerService) {
+ this.initScope();
+ }
+
+ private loadComplexComponentData = () => {
+ this.$scope.isLoading = true;
+ this.ComponentServiceNg2.getCapabilitiesAndRequirements(this.$scope.currentComponent.componentType, this.$scope.currentComponent.uniqueId).subscribe((response:ComponentGenericResponse) => {
+ this.$scope.currentComponent.capabilities = response.capabilities;
+ this.$scope.currentComponent.requirements = response.requirements;
+ this.setScopeCapabilitiesRequirements(this.$scope.currentComponent.capabilities, this.$scope.currentComponent.requirements);
+ this.initInstancesMap();
+ this.$scope.isLoading = false;
+ });
+ }
+
+
+ private extractValuesFromMap = (map:CapabilitiesGroup | RequirementsGroup):Array<any> => {
+ let values = [];
+ _.forEach(map, (capabilitiesOrRequirements:Array<Capability> | Array<Requirement>, key) => {
+ values = values.concat(capabilitiesOrRequirements)
+ }
+ );
+ return values;
+ }
+
+ private setScopeCapabilitiesRequirements = (capabilities:CapabilitiesGroup, requirements:RequirementsGroup) => {
+ this.$scope.capabilities = this.extractValuesFromMap(capabilities);
+ this.$scope.requirements = this.extractValuesFromMap(requirements);
+ }
+
+
+ private initInstancesMap = ():void => {
+
+ this.$scope.capabilitiesInstancesMap = new InstanceCapabilitiesMap();
+ _.forEach(this.$scope.capabilities, (capability:Capability) => {
+ if (this.$scope.capabilitiesInstancesMap[capability.ownerName]) {
+ this.$scope.capabilitiesInstancesMap[capability.ownerName] = this.$scope.capabilitiesInstancesMap[capability.ownerName].concat(capability);
+ } else {
+ this.$scope.capabilitiesInstancesMap[capability.ownerName] = new Array<Capability>(capability);
+ }
+ });
+
+ this.$scope.requirementsInstancesMap = new InstanceRequirementsMap();
+ _.forEach(this.$scope.requirements, (requirement:Requirement) => {
+ if (this.$scope.requirementsInstancesMap[requirement.ownerName]) {
+ this.$scope.requirementsInstancesMap[requirement.ownerName] = this.$scope.requirementsInstancesMap[requirement.ownerName].concat(requirement);
+ } else {
+ this.$scope.requirementsInstancesMap[requirement.ownerName] = new Array<Requirement>(requirement);
+ }
+ });
+ }
+
+ private initRequirementsAndCapabilities = (needUpdate?: boolean) => {
+
+ // if instance selected, we take the requirement and capabilities of the instance - always exist because we load them with the graph
+ if (this.$scope.isComponentInstanceSelected()) {
+ this.$scope.isLoading = false;
+ this.setScopeCapabilitiesRequirements(this.$scope.currentComponent.selectedInstance.capabilities, this.$scope.currentComponent.selectedInstance.requirements);
+ if (this.$scope.currentComponent.selectedInstance.originType === 'VF') {
+ this.initInstancesMap();
+ }
+ } else {
+ // if instance not selected, we take the requirement and capabilities of the VF/SERVICE, if not exist we call api
+ if (needUpdate || !this.$scope.currentComponent.capabilities || !this.$scope.currentComponent.requirements) {
+ this.loadComplexComponentData();
+
+ } else {
+ this.$scope.isLoading = false;
+ this.setScopeCapabilitiesRequirements(this.$scope.currentComponent.capabilities, this.$scope.currentComponent.requirements);
+ this.initInstancesMap();
+ }
+ }
+ }
+
+ private updateRequirementCapabilities = () => {
+ if (!this.$scope.isComponentInstanceSelected()) {
+ this.loadComplexComponentData();
+ }
+ }
+
+ private initEvents = ():void => {
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_NODE_SELECTED, this.initRequirementsAndCapabilities);
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED, this.updateRequirementCapabilities);
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_CREATE_COMPONENT_INSTANCE, this.updateRequirementCapabilities);
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE, this.updateRequirementCapabilities);
+ }
+
+ private initScope = ():void => {
+
+ this.$scope.requirements = [];
+ this.$scope.capabilities = [];
+
+ this.initEvents();
+ this.initRequirementsAndCapabilities();
+
+ this.$scope.isCurrentDisplayComponentIsComplex = ():boolean => {
+ if (this.$scope.isComponentInstanceSelected()) {
+ if (this.$scope.currentComponent.selectedInstance.originType === 'VF') {
+ return true;
+ }
+ return false;
+ } else {
+ return this.$scope.currentComponent.isComplex();
+ }
+ }
+
+ this.$scope.$on('$destroy', () => {
+
+ this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_NODE_SELECTED, this.initRequirementsAndCapabilities);
+ this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED, this.updateRequirementCapabilities);
+ this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_CREATE_COMPONENT_INSTANCE, this.updateRequirementCapabilities);
+ this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE, this.updateRequirementCapabilities);
+ });
+
+ }
+}
diff --git a/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/relations/relations-view.html b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/relations/relations-view.html
new file mode 100644
index 0000000000..5ecb12cd6f
--- /dev/null
+++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/relations/relations-view.html
@@ -0,0 +1,61 @@
+<perfect-scrollbar class="w-sdc-designer-sidebar-tab-content sdc-general-tab relations">
+ <div ng-if="!isCurrentDisplayComponentIsComplex()">
+ <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">
+ <capabilities-list capabilities="capabilities"></capabilities-list>
+ </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">
+ <requirements-list component='currentComponent' requirements="requirements"></requirements-list>
+ </div>
+ </div>
+ </div>
+
+ <div ng-if="isCurrentDisplayComponentIsComplex()">
+ <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>
+ <div class="w-sdc-designer-sidebar-section-content capabilities">
+ <expand-collapse expanded-selector=".capabilities-component-instances.{{$index}}" is-close-on-init="true" class="general-tab-expand-collapse"
+ data-ng-repeat-start="(key, instanceCapabilities) in capabilitiesInstancesMap track by $index">
+ <div class="expand-collapse-title second-level">
+ <div class="expand-collapse-title-icon"></div>
+ <span class="expand-collapse-title-text" data-ng-bind="key"></span>
+ </div>
+ </expand-collapse>
+
+ <div data-ng-repeat-end="" class="capabilities-component-instances {{$index}}">
+ <capabilities-list capabilities="instanceCapabilities"></capabilities-list>
+ </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>
+ <div class="w-sdc-designer-sidebar-section-content requirements">
+ <expand-collapse expanded-selector=".requirements-component-instances.{{$index}}" is-close-on-init="true" class="general-tab-expand-collapse"
+ data-ng-repeat-start="(key, instanceRequirements) in requirementsInstancesMap track by $index">
+ <div class="expand-collapse-title second-level">
+ <div class="expand-collapse-title-icon"></div>
+ <span class="expand-collapse-title-text" data-ng-bind="key"></span>
+ </div>
+ </expand-collapse>
+
+ <div data-ng-repeat-end="" class="requirements-component-instances {{$index}}">
+ <requirements-list component='currentComponent' requirements="instanceRequirements"></requirements-list>
+ </div>
+ </div>
+ </div>
+</perfect-scrollbar>
diff --git a/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/relations/relations.less b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/relations/relations.less
new file mode 100644
index 0000000000..c3b224d5a6
--- /dev/null
+++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/relations/relations.less
@@ -0,0 +1,14 @@
+.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;
+ }
+ }
+}
+
+
diff --git a/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/structure/structure-view.html b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/structure/structure-view.html
new file mode 100644
index 0000000000..2070041990
--- /dev/null
+++ b/catalog-ui/src/app/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/src/app/view-models/workspace/tabs/composition/tabs/structure/structure-view.ts b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/structure/structure-view.ts
new file mode 100644
index 0000000000..1c28a46d37
--- /dev/null
+++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/structure/structure-view.ts
@@ -0,0 +1,14 @@
+'use strict';
+import {ICompositionViewModelScope} from "../../composition-view-model";
+
+interface IStructureViewModel extends ICompositionViewModelScope {
+}
+
+export class StructureViewModel {
+ static '$inject' = [
+ '$scope'
+ ];
+
+ constructor(private $scope:IStructureViewModel) {
+ }
+}