aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--catalog-ui/src/app/app.ts8
-rw-r--r--catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.directive.ts1
-rw-r--r--catalog-ui/src/app/models.ts2
-rw-r--r--catalog-ui/src/app/models/inputs.ts11
-rw-r--r--catalog-ui/src/app/models/service-instance-properties-and-interfaces.ts35
-rw-r--r--catalog-ui/src/app/modules/directive-module.ts7
-rw-r--r--catalog-ui/src/app/modules/view-model-module.ts6
-rw-r--r--catalog-ui/src/app/ng2/app.module.ts4
-rw-r--r--catalog-ui/src/app/ng2/components/logic/properties-table/dynamic-property/dynamic-property.component.html8
-rw-r--r--catalog-ui/src/app/ng2/components/logic/properties-table/property-table.module.ts2
-rw-r--r--catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.html26
-rw-r--r--catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.less67
-rw-r--r--catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.ts228
-rw-r--r--catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.module.ts39
-rw-r--r--catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.html98
-rw-r--r--catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.less143
-rw-r--r--catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.ts294
-rw-r--r--catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.module.ts28
-rw-r--r--catalog-ui/src/app/ng2/services/component-services/component.service.ts7
-rw-r--r--catalog-ui/src/app/ng2/services/component-services/service.service.ts24
-rw-r--r--catalog-ui/src/app/ng2/services/responses/component-generic-response.ts16
-rw-r--r--catalog-ui/src/app/utils/common-utils.ts6
-rw-r--r--catalog-ui/src/app/utils/constants.ts2
-rw-r--r--catalog-ui/src/app/view-models/workspace/tabs/composition/composition-view.html8
-rw-r--r--catalog-ui/src/app/view-models/workspace/tabs/composition/composition.less7
-rw-r--r--catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/service-consumption/service-consumption-view-model.ts79
-rw-r--r--catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/service-consumption/service-consumption-view.html22
27 files changed, 1157 insertions, 21 deletions
diff --git a/catalog-ui/src/app/app.ts b/catalog-ui/src/app/app.ts
index ebcfdd2785..a147f8cab4 100644
--- a/catalog-ui/src/app/app.ts
+++ b/catalog-ui/src/app/app.ts
@@ -530,6 +530,14 @@ ng1appModule.config([
}
);
$stateProvider.state(
+ 'workspace.composition.consumption', {
+ url: 'consumption',
+ parent: 'workspace.composition',
+ templateUrl: './view-models/workspace/tabs/composition/tabs/service-consumption/service-consumption-view.html',
+ controller: viewModelsModuleName + '.ServiceConsumptionViewModel'
+ }
+ );
+ $stateProvider.state(
'workspace.composition.dependencies', {
url: 'dependencies',
parent: 'workspace.composition',
diff --git a/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.directive.ts b/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.directive.ts
index 502188b3c2..e6c2fb395b 100644
--- a/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.directive.ts
+++ b/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.directive.ts
@@ -87,6 +87,7 @@ export interface ICompositionGraphScope extends ng.IScope {
zones: Array<Zone>;
zoneMinimizeToggle(zoneType: ZoneInstanceType): void;
zoneInstanceTagged(taggedInstance: ZoneInstance): void;
+ zoneBackgroundClicked() :void;
zoneInstanceModeChanged(newMode: ZoneInstanceMode, instance: ZoneInstance, zoneId: ZoneInstanceType);
unsetActiveZoneInstance(): void;
clickOutsideZoneInstance(): void;
diff --git a/catalog-ui/src/app/models.ts b/catalog-ui/src/app/models.ts
index 5c79155260..ad1da27c62 100644
--- a/catalog-ui/src/app/models.ts
+++ b/catalog-ui/src/app/models.ts
@@ -114,7 +114,7 @@ export * from './models/wizard-step';
export * from './models/radio-button';
export * from './models/filter-properties-assignment-data';
export * from './models/properties-inputs/input-be-model';
-export * from './models/service-instance-properties';
+export * from './models/service-instance-properties-and-interfaces';
export * from './models/relationship-types';
export * from './models/tosca-presentation';
export * from './models/node-types';
diff --git a/catalog-ui/src/app/models/inputs.ts b/catalog-ui/src/app/models/inputs.ts
index cbed226324..e5b2274835 100644
--- a/catalog-ui/src/app/models/inputs.ts
+++ b/catalog-ui/src/app/models/inputs.ts
@@ -26,6 +26,17 @@ import {PropertyModel} from "./properties";
import {InputPropertyBase} from "./input-property-base";
import {SchemaPropertyGroupModel} from "./aschema-property";
+export class InputsGroup {
+ constructor(inputsObj?:InputsGroup) {
+ _.forEach(inputsObj, (inputs:Array<InputModel>, instance) => {
+ this[instance] = [];
+ _.forEach(inputs, (input:InputModel):void => {
+ this[instance].push(new InputModel(input));
+ });
+ });
+ }
+}
+
export interface IInputModel extends InputPropertyBase {
//server data
definition:boolean;
diff --git a/catalog-ui/src/app/models/service-instance-properties-and-interfaces.ts b/catalog-ui/src/app/models/service-instance-properties-and-interfaces.ts
new file mode 100644
index 0000000000..168b0af215
--- /dev/null
+++ b/catalog-ui/src/app/models/service-instance-properties-and-interfaces.ts
@@ -0,0 +1,35 @@
+/*!
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.
+ */
+
+import {PropertyModel, InputModel, InterfaceModel} from 'app/models';
+
+export class ServiceInstanceObject {
+ id: string;
+ name: string;
+ properties: Array<PropertyModel> = [];
+ inputs: Array<InputModel> = [];
+ interfaces: Array<InterfaceModel> = [];
+
+ constructor(input?:any) {
+ if(input) {
+ this.id = input.id;
+ this.name = input.name;
+ this.properties = input.properties;
+ this.inputs = input.inputs;
+ this.interfaces = _.map(input.interfaces, interf => new InterfaceModel(interf));
+ }
+ }
+}
diff --git a/catalog-ui/src/app/modules/directive-module.ts b/catalog-ui/src/app/modules/directive-module.ts
index c541d58208..96bee45c59 100644
--- a/catalog-ui/src/app/modules/directive-module.ts
+++ b/catalog-ui/src/app/modules/directive-module.ts
@@ -183,6 +183,7 @@ import { SearchWithAutoCompleteComponent } from "../ng2/components/ui/search-wit
import { PalettePopupPanelComponent } from "../ng2/components/ui/palette-popup-panel/palette-popup-panel.component";
import { ServicePathComponent } from '../ng2/components/logic/service-path/service-path.component';
import { ServicePathSelectorComponent } from '../ng2/components/logic/service-path-selector/service-path-selector.component';
+import { ServiceConsumptionComponent } from '../ng2/components/logic/service-consumption/service-consumption.component';
import { ServiceDependenciesComponent } from '../ng2/components/logic/service-dependencies/service-dependencies.component';
import { MultilineEllipsisComponent } from "../ng2/shared/multiline-ellipsis/multiline-ellipsis.component";
import { InterfaceOperationComponent } from '../ng2/pages/interface-operation/interface-operation.page.component';
@@ -246,6 +247,12 @@ directiveModule.directive('ng2ServicePathSelector', downgradeComponent({
outputs: []
}) as angular.IDirectiveFactory);
+directiveModule.directive('ng2ServiceConsumption', downgradeComponent({
+ component: ServiceConsumptionComponent,
+ inputs: ['parentService', 'selectedService', 'selectedServiceInstanceId', 'instancesMappedList','parentServiceInputs', 'readonly'],
+ outputs: []
+}) as angular.IDirectiveFactory);
+
directiveModule.directive('ng2ServiceDependencies', downgradeComponent({
component: ServiceDependenciesComponent,
inputs: ['compositeService', 'currentServiceInstance', 'selectedInstanceProperties', 'selectedInstanceSiblings', 'selectedInstanceConstraints', 'readonly'],
diff --git a/catalog-ui/src/app/modules/view-model-module.ts b/catalog-ui/src/app/modules/view-model-module.ts
index a390ab4fb4..8c8f239c4e 100644
--- a/catalog-ui/src/app/modules/view-model-module.ts
+++ b/catalog-ui/src/app/modules/view-model-module.ts
@@ -24,6 +24,7 @@ import {WorkspaceViewModel} from "../view-models/workspace/workspace-view-model"
import {CompositionViewModel} from "../view-models/workspace/tabs/composition/composition-view-model";
import {DetailsViewModel} from "../view-models/workspace/tabs/composition/tabs/details/details-view-model";
import {ResourceArtifactsViewModel} from "../view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view-model";
+import {ServiceConsumptionViewModel} from "../view-models/workspace/tabs/composition/tabs/service-consumption/service-consumption-view-model";
import {ServiceDependenciesViewModel} from "../view-models/workspace/tabs/composition/tabs/service-dependencies/service-dependencies-view-model";
import {PropertyFormBaseView} from "../view-models/forms/property-forms/base-property-form/property-form-base-model";
import {PropertyFormViewModel} from "../view-models/forms/property-forms/component-property-form/property-form-view-model";
@@ -83,6 +84,7 @@ viewModelModule
.controller(moduleName + '.DetailsViewModel', DetailsViewModel)
.controller(moduleName + '.ResourceArtifactsViewModel', ResourceArtifactsViewModel)
+ .controller(moduleName + '.ServiceConsumptionViewModel', ServiceConsumptionViewModel)
.controller(moduleName + '.ServiceDependenciesViewModel', ServiceDependenciesViewModel)
.controller(moduleName + '.PropertyFormBaseView', PropertyFormBaseView)
.controller(moduleName + '.PropertyFormViewModel', PropertyFormViewModel)
@@ -140,5 +142,5 @@ viewModelModule
// //TABS
.controller(moduleName + '.HierarchyViewModel', HierarchyViewModel);
- // NG2
- //.controller(moduleName + '.NG2Example', downgradeComponent({component: NG2Example2Component}) );
+// NG2
+//.controller(moduleName + '.NG2Example', downgradeComponent({component: NG2Example2Component}) );
diff --git a/catalog-ui/src/app/ng2/app.module.ts b/catalog-ui/src/app/ng2/app.module.ts
index 0ff9378949..22c6624348 100644
--- a/catalog-ui/src/app/ng2/app.module.ts
+++ b/catalog-ui/src/app/ng2/app.module.ts
@@ -61,6 +61,8 @@ import { ServicePathCreatorModule } from './pages/service-path-creator/service-p
import { ServicePathsListModule } from './pages/service-paths-list/service-paths-list.module';
import { ServicePathModule } from 'app/ng2/components/logic/service-path/service-path.module';
import { ServicePathSelectorModule } from 'app/ng2/components/logic/service-path-selector/service-path-selector.module';
+import { ServiceConsumptionModule } from 'app/ng2/components/logic/service-consumption/service-consumption.module';
+import { ServiceConsumptionCreatorModule } from './pages/service-consumption-editor/service-consumption-editor.module';
import {ServiceDependenciesModule} from 'app/ng2/components/logic/service-dependencies/service-dependencies.module';
import {ServiceDependenciesEditorModule} from './pages/service-dependencies-editor/service-dependencies-editor.module';
import { CompositionPanelModule } from 'app/ng2/pages/composition/panel/panel.module';
@@ -116,6 +118,8 @@ export function configServiceFactory(config: ConfigService) {
ServicePathsListModule,
ServicePathModule,
ServicePathSelectorModule,
+ ServiceConsumptionModule,
+ ServiceConsumptionCreatorModule,
ServiceDependenciesModule,
ServiceDependenciesEditorModule,
RequirementsEditorModule,
diff --git a/catalog-ui/src/app/ng2/components/logic/properties-table/dynamic-property/dynamic-property.component.html b/catalog-ui/src/app/ng2/components/logic/properties-table/dynamic-property/dynamic-property.component.html
index d4e7b02930..3f87b070e8 100644
--- a/catalog-ui/src/app/ng2/components/logic/properties-table/dynamic-property/dynamic-property.component.html
+++ b/catalog-ui/src/app/ng2/components/logic/properties-table/dynamic-property/dynamic-property.component.html
@@ -12,8 +12,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
-->
-
-
+
+
<div *ngIf="!property.hidden" class="dynamic-property-row nested-level-{{nestedLevel}}" [@fadeIn]
[ngClass]="{'selected': selectedPropertyId && selectedPropertyId === property.propertiesName, 'readonly': property.isDisabled ||property.isDeclared}"
[class.with-top-border]="property.isChildOfListOrMap"
@@ -24,7 +24,9 @@
<checkbox *ngIf="hasDeclareOption" [(checked)]="property.isSelected" [disabled]="property.isDisabled ||property.isDeclared || readonly" (checkedChange)="checkProperty.emit(property.propertiesName)" ></checkbox>
<div class="inner-cell-div" tooltip="{{property.name}}"><span>{{property.name}}</span></div>
</div>
- <div class="table-cell" *ngIf="!canBeDeclared && !property.isChildOfListOrMap">{{property.name}}</div> <!-- simple children of complex type within map or list -->
+ <div class="table-cell" *ngIf="!canBeDeclared && !property.isChildOfListOrMap">
+ <div class="inner-cell-div" tooltip="{{property.name}}"><span>{{property.name}}</span></div>
+ </div> <!-- simple children of complex type within map or list -->
<div class="table-cell map-entry" *ngIf="property.isChildOfListOrMap && propType == derivedPropertyTypes.MAP"><!-- map left cell -->
<!--<input [value]="property.mapKey" [placeholder]="property.name" (input)="mapKeyChanged.emit($event.target.value)" [readonly]="readonly" type="text" [ngClass]="{'disabled':readonly, 'error':property.mapKeyError}" required/>-->
<dynamic-element #mapKeyInput
diff --git a/catalog-ui/src/app/ng2/components/logic/properties-table/property-table.module.ts b/catalog-ui/src/app/ng2/components/logic/properties-table/property-table.module.ts
index 91baaf1bc1..cb8c9a694b 100644
--- a/catalog-ui/src/app/ng2/components/logic/properties-table/property-table.module.ts
+++ b/catalog-ui/src/app/ng2/components/logic/properties-table/property-table.module.ts
@@ -24,7 +24,7 @@ import {MultilineEllipsisModule} from "../../../shared/multiline-ellipsis/multil
DynamicPropertyComponent,
PropertiesTableComponent
],
- exports: [PropertiesTableComponent],
+ exports: [PropertiesTableComponent, DynamicPropertyComponent],
providers: [FilterChildPropertiesPipe, PropertiesService]
})
export class PropertyTableModule {
diff --git a/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.html b/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.html
new file mode 100644
index 0000000000..9c52afbb6b
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.html
@@ -0,0 +1,26 @@
+<div class='service-consumption'>
+ <loader [display]="isLoading" [size]="'medium'" [relative]="true"></loader>
+ <div class="no-operations-text" data-tests-id="no-operations" *ngIf="!isLoading && !interfacesList.length">{{'CONSUMPTION_NO_OPERATIONS_TO_SHOW' | translate}}</div>
+
+ <div class="interface-operations-group"
+ *ngFor="let interface of interfacesList; let interfaceIndex = index"
+ (click)="expandCollapseInterfaceGroup(interface)">
+ <div class="interface-title first-level hand" [attr.data-tests-id]="interface.displayName" [ngClass]="{'expanded': interface.isExpanded}">
+ <span class="sprite-new expand-collapse-plus-icon expand-collapse-icon"></span>
+ <div class="title-text" tooltips tooltip="{{interface.displayName}}">{{interface.displayName}}</div>
+ </div>
+ <div class="i-sdc-designer-sidebar-section-content-item operations-group" *ngIf="interface.isExpanded">
+ <div class="operation-data"
+ [ngClass]="{'hand': !readonly}"
+ *ngFor="let serviceOperation of interface.operationsList; let opIndex = index"
+ (click)="onSelectOperation($event, interface, opIndex)">
+ <div class="operation-name"
+ [attr.data-tests-id]="interface.displayName + '.' + serviceOperation.operation.name"
+ [ngClass]="{'readonly': readonly}"
+ tooltips tooltip="{{serviceOperation.operation.name}}">
+ {{serviceOperation.operation.name}}
+ </div>
+ </div>
+ </div>
+ </div>
+</div> \ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.less b/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.less
new file mode 100644
index 0000000000..5830c06972
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.less
@@ -0,0 +1,67 @@
+@import './../../../../../assets/styles/variables.less';
+@import './../../../../../assets/styles/variables-old.less';
+@import './../../../../../assets/styles/mixins_old.less';
+@import './../../../../../assets/styles/sprite.less';
+
+.service-consumption {
+ min-height: 100px;
+ .no-operations-text {
+ text-align: center;
+ padding: 30px;
+ opacity: 0.7;
+ font-size: 14px;
+ }
+ .interface-operations-group {
+ &:first-of-type {
+ margin-top: 1px;
+ }
+ .interface-title {
+ display: flex;
+ .title-text {
+ font-size: 14px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ max-width: 90%;
+ padding-left: 4px;
+ }
+ &.expanded {
+ background-color: @tlv_color_v;
+ border-left: 4px solid @main_color_a;
+ box-shadow: 0 0px 3px -1px rgba(0, 0, 0, 0.3);
+ margin-bottom: 2px;
+ padding-left: 4px !important;
+ .expand-collapse-icon {
+ .expand-collapse-minus-icon;
+ }
+ }
+ .expand-collapse-icon {
+ margin: 0 6px;
+ display: inline-block;
+ margin-top: 4px;
+ }
+ }
+ .operation-data {
+ border-bottom: 1px solid #c8cdd1;
+ padding: 5px 10px 5px 18px;
+ min-height: 61px;
+ display: flex;
+ align-items: center;
+ }
+
+ .operation-name {
+ .s_1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ max-width: 220px;
+ display: inline-block;
+ &:not(.readonly):hover {
+ .a_7;
+ }
+ &.readonly {
+ opacity: 0.5;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.ts b/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.ts
new file mode 100644
index 0000000000..b0f6dfb649
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.ts
@@ -0,0 +1,228 @@
+/*!
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.
+ */
+
+import {Component, Input, ComponentRef} from '@angular/core';
+import {ComponentServiceNg2} from 'app/ng2/services/component-services/component.service';
+import {ComponentInstanceServiceNg2} from 'app/ng2/services/component-instance-services/component-instance.service';
+import {ServiceServiceNg2} from "app/ng2/services/component-services/service.service";
+import {ModalService} from 'app/ng2/services/modal.service';
+import {ModalComponent} from 'app/ng2/components/ui/modal/modal.component';
+import {
+ ModalModel,
+ ButtonModel,
+ OperationModel,
+ Service,
+ ServiceInstanceObject,
+ PropertyFEModel,
+ PropertyBEModel,
+ InputBEModel,
+ InterfaceModel
+} from 'app/models';
+import {ServiceConsumptionCreatorComponent} from 'app/ng2/pages/service-consumption-editor/service-consumption-editor.component';
+
+
+export class ConsumptionInput extends PropertyFEModel{
+ inputId: string;
+ type: string;
+ source: string;
+ value: any;
+
+ constructor(input?: any) {
+ super(input);
+ if (input) {
+ this.inputId = input.inputId;
+ this.type = input.type;
+ this.source = input.source;
+ this.value = input.value || "";
+ }
+ }
+}
+
+export class ConsumptionInputDetails extends ConsumptionInput {
+ name: string;
+ expanded: boolean;
+ assignValueLabel: string;
+ associatedProps: Array<string>;
+ associatedInterfaces: Array<any>;
+ origVal: string;
+ isValid: boolean;
+
+ constructor(input: any) {
+ super(input);
+ if (input) {
+ this.name = input.name;
+ this.expanded = input.expanded;
+ this.assignValueLabel = input.assignValueLabel;
+ this.associatedProps = input.associatedProps;
+ this.associatedInterfaces = input.associatedInterfaces;
+ this.origVal = input.value || "";
+ this.isValid = input.isValid;
+ }
+ }
+
+ public updateValidity(isValid: boolean) {
+ this.isValid = isValid;
+ }
+}
+
+export class ServiceOperation {
+ operation: OperationModel;
+ consumptionInputs: Array<ConsumptionInputDetails>;
+
+ constructor(input?: any) {
+ if (input) {
+ this.operation = new OperationModel(input.operation || {});
+ this.consumptionInputs = input.consumptionInputs || [];
+ }
+ }
+}
+
+export class InterfaceWithServiceOperation {
+ interfaceId: string;
+ displayName: string;
+ operationsList: Array<ServiceOperation>;
+ isExpanded: boolean;
+
+ constructor(input?: InterfaceModel) {
+ if (input) {
+ this.interfaceId = input.uniqueId;
+ this.displayName = input.displayType();
+ this.operationsList = _.map(input.operations, operation => new ServiceOperation({operation: operation}));
+ this.isExpanded = true;
+ }
+ }
+}
+
+
+
+@Component({
+ selector: 'service-consumption',
+ templateUrl: './service-consumption.component.html',
+ styleUrls: ['service-consumption.component.less'],
+ providers: [ModalService]
+})
+
+export class ServiceConsumptionComponent {
+
+ modalInstance: ComponentRef<ModalComponent>;
+ isLoading: boolean = false;
+ interfacesList: Array<InterfaceWithServiceOperation>;
+ operationsGroup: Array<ServiceOperation>;
+ @Input() parentServiceInputs: Array<InputBEModel> = [];
+ @Input() parentService: Service;
+ @Input() selectedService: Service;
+ @Input() selectedServiceInstanceId: string;
+ @Input() instancesMappedList: Array<ServiceInstanceObject>;
+ @Input() readonly: boolean;
+
+ selectedInstanceSiblings: Array<ServiceInstanceObject>;
+ selectedInstancePropertiesList: Array<PropertyBEModel> = [];
+
+ constructor(private ModalServiceNg2: ModalService, private serviceServiceNg2: ServiceServiceNg2, private componentServiceNg2: ComponentServiceNg2, private componentInstanceServiceNg2:ComponentInstanceServiceNg2) {}
+
+ ngOnInit() {
+ this.updateSelectedInstancePropertiesAndSiblings();
+ }
+
+ ngOnChanges(changes) {
+ if(changes.selectedServiceInstanceId && changes.selectedServiceInstanceId.currentValue !== changes.selectedServiceInstanceId.previousValue) {
+ this.selectedServiceInstanceId = changes.selectedServiceInstanceId.currentValue;
+ if(changes.selectedService && changes.selectedService.currentValue !== changes.selectedService.previousValue) {
+ this.selectedService = changes.selectedService.currentValue;
+ }
+ this.updateSelectedInstancePropertiesAndSiblings();
+ }
+ if(changes.instancesMappedList && !_.isEqual(changes.instancesMappedList.currentValue, changes.instancesMappedList.previousValue)) {
+ this.updateSelectedInstancePropertiesAndSiblings();
+ }
+ }
+
+ updateSelectedInstancePropertiesAndSiblings() {
+ this.interfacesList = [];
+ let selectedInstanceMetadata: ServiceInstanceObject = _.find(this.instancesMappedList, coInstance => coInstance.id === this.selectedServiceInstanceId);
+ if (selectedInstanceMetadata) {
+ _.forEach(selectedInstanceMetadata.interfaces, (interfaceData:InterfaceModel) => {
+ this.interfacesList.push(new InterfaceWithServiceOperation(interfaceData));
+ });
+ }
+ this.interfacesList.sort((interf1:InterfaceWithServiceOperation, interf2:InterfaceWithServiceOperation) => interf1.displayName.localeCompare(interf2.displayName));
+
+ this.selectedInstancePropertiesList = selectedInstanceMetadata && selectedInstanceMetadata.properties;
+ this.selectedInstanceSiblings = _.filter(this.instancesMappedList, coInstance => coInstance.id !== this.selectedServiceInstanceId);
+ }
+
+ expandCollapseInterfaceGroup(currInterface) {
+ currInterface.isExpanded = !currInterface.isExpanded;
+ }
+
+ onSelectOperation(event, currInterface:InterfaceWithServiceOperation, opIndex: number) {
+ event.stopPropagation();
+ if(!this.readonly) {
+ this.operationsGroup = currInterface.operationsList;
+ let cancelButton: ButtonModel = new ButtonModel('Cancel', 'outline white', this.ModalServiceNg2.closeCurrentModal);
+ let saveButton: ButtonModel = new ButtonModel('Save', 'blue', this.createOrUpdateOperationInput, this.getDisabled);
+ let modalModel: ModalModel = new ModalModel('l', 'Modify Operation Consumption', '', [saveButton, cancelButton], 'standard');
+ this.modalInstance = this.ModalServiceNg2.createCustomModal(modalModel);
+ this.ModalServiceNg2.addDynamicContentToModal(
+ this.modalInstance,
+ ServiceConsumptionCreatorComponent,
+ {
+ interfaceId: currInterface.interfaceId,
+ serviceOperationIndex: opIndex,
+ serviceOperations: this.operationsGroup,
+ parentService: this.parentService,
+ selectedService: this.selectedService,
+ parentServiceInputs: this.parentServiceInputs,
+ selectedServiceProperties: this.selectedInstancePropertiesList,
+ selectedServiceInstanceId: this.selectedServiceInstanceId,
+ selectedInstanceSiblings: this.selectedInstanceSiblings
+ }
+ );
+ this.modalInstance.instance.open();
+ }
+ }
+
+ createOrUpdateOperationInput = ():void => {
+ this.isLoading = true;
+ let consumptionInputsList:Array<{[id: string]: Array<ConsumptionInput>}> = _.map(this.operationsGroup, (serviceOp) => {
+ let consumptionInputsArr: Array<any> = [];
+ if(serviceOp.consumptionInputs) {
+ consumptionInputsArr = _.map(serviceOp.consumptionInputs, (input: ConsumptionInputDetails) => {
+ return {
+ inputId: input.inputId,
+ type: input.isSimpleType ? input.type : 'json',
+ source: input.source,
+ value: input.value
+ };
+ });
+ }
+ return {
+ [serviceOp.operation.uniqueId]: consumptionInputsArr
+ };
+ });
+ this.serviceServiceNg2.createOrUpdateServiceConsumptionInputs(this.parentService,this.selectedServiceInstanceId, consumptionInputsList).subscribe(() => {
+ this.isLoading = false;
+ }, err=> {
+ this.isLoading = false;
+ });
+ this.ModalServiceNg2.closeCurrentModal();
+ };
+
+ getDisabled = ():boolean => {
+ return !this.modalInstance.instance.dynamicContent.instance.checkFormValidForSubmit();
+ };
+
+} \ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.module.ts b/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.module.ts
new file mode 100644
index 0000000000..8593bef3eb
--- /dev/null
+++ b/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.module.ts
@@ -0,0 +1,39 @@
+/*!
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.
+ */
+
+import { NgModule } from "@angular/core";
+import {CommonModule} from "@angular/common";
+import {ServiceConsumptionComponent} from "./service-consumption.component";
+import {UiElementsModule} from "app/ng2/components/ui/ui-elements.module";
+import {TranslateModule} from 'app/ng2/shared/translator/translate.module';
+
+@NgModule({
+ declarations: [
+ ServiceConsumptionComponent
+ ],
+ imports: [
+ CommonModule,
+ UiElementsModule,
+ TranslateModule
+ ],
+ exports: [],
+ entryComponents: [
+ ServiceConsumptionComponent
+ ],
+ providers: []
+})
+export class ServiceConsumptionModule {
+} \ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.html b/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.html
new file mode 100644
index 0000000000..5d42fa7694
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.html
@@ -0,0 +1,98 @@
+
+<div class="service-consumption-editor">
+ <form class="w-sdc-form">
+
+ <div class="sdc-modal-top-bar">
+ <div class="operation-name">{{serviceOperation.operation.name}}</div>
+
+ <div class="sdc-modal-top-bar-buttons">
+ <span (click)="onChangePage(currentIndex - 1)" [ngClass]="{'disabled' : currentIndex === 0 || !checkFormValidForNavigation()}" class="sprite-new left-arrow" data-tests-id="get-prev" tooltip="Previous"></span>
+ <span (click)="onChangePage(currentIndex + 1)" [ngClass]="{'disabled' : currentIndex === serviceOperationsList.length - 1 || !checkFormValidForNavigation()}" class="sprite-new right-arrow" data-tests-id="get-next" tooltip="Next"></span>
+ </div>
+ </div>
+ <div class="expand-collapse-all" *ngIf="serviceOperation.consumptionInputs.length">
+ <div class="expand-all" (click)="onExpandAll()" [ngClass]="{'disabled': isAllInputExpanded()}"> {{'CONSUMPTION_EXPAND_ALL' | translate}}</div>
+ <div class="separator-line"></div>
+ <div class="collapse-all" (click)="onCollapseAll()" [ngClass]="{'disabled': isAllInputCollapsed()}"> {{'CONSUMPTION_COLLAPSE_ALL' | translate}}</div>
+ </div>
+
+
+ <loader [display]="isLoading" [size]="'large'" [relative]="true"></loader>
+
+ <div class="i-sdc-form-content-operation-inputs-content" [ngClass]="{'no-inputs': !serviceOperation.consumptionInputs.length}">
+ <div class="no-inputs-text" *ngIf="!serviceOperation.consumptionInputs.length">{{'CONSUMPTION_NO_INPUTS_TO_SHOW' | translate}}</div>
+ <div class="i-sdc-form-content-operation-input-box" *ngFor="let consumptionInput of serviceOperation.consumptionInputs">
+ <div class="i-sdc-form-content-operation-input-box-title" (click)="onExpandCollapse(consumptionInput)">
+ <div class="expand-collapse-icon" [ngClass]="{'expanded': consumptionInput.expanded}"></div>
+ <div class="operation-input-name">{{consumptionInput.name}}</div>
+ <div class="operation-input-type">
+ <span class="type-text"> | {{ 'CONSUMPTION_TYPE' | translate}}:</span>
+ <span class="type-val"> {{consumptionInput.type}} </span>
+ </div>
+ </div>
+
+ <div class="operation-input-section-row with-top-border" *ngIf="consumptionInput.expanded">
+ <div class="i-sdc-form-item operation-input-section-col">
+ <label class="i-sdc-form-label" [ngClass]="{'required': consumptionInput.required}" >{{ 'CONSUMPTION_SOURCE' | translate}}</label>
+ <ui-element-dropdown
+ class="i-sdc-form-select"
+ data-tests-id="sourceType"
+ [values]="sourceTypes"
+ [(value)]="consumptionInput.source"
+ (change)="onSourceChanged(consumptionInput)">
+ </ui-element-dropdown>
+ </div>
+
+ <div class="operation-input-section-col assigned-value">
+ <label class="i-sdc-form-label" *ngIf="consumptionInput.source !== SOURCE_TYPES.STATIC || consumptionInput.isSimpleType">
+ {{consumptionInput.assignValueLabel}}
+ </label>
+ <dynamic-element
+ class="dynamic-input-field"
+ *ngIf="consumptionInput.isSimpleType && (consumptionInput.source === SOURCE_TYPES.STATIC || consumptionInput.source === '')"
+ data-tests-id="inputValue"
+ [(value)]="consumptionInput.value"
+ (elementChanged)="onChange($event.value, $event.isValid, consumptionInput)"
+ [type]="consumptionInput.type">
+ </dynamic-element>
+ <select
+ class="i-sdc-form-select"
+ *ngIf="consumptionInput.source !== SOURCE_TYPES.STATIC"
+ [attr.data-tests-id]="inputValue"
+ (change)="onChange(value, true, consumptionInput)"
+ [(ngModel)]="consumptionInput.value"
+ [ngModelOptions]="{standalone: true}">
+ <option
+ *ngFor="let propName of consumptionInput.associatedProps"
+ [ngValue]="propName">
+ {{propName}}
+ </option>
+ <optgroup
+ *ngFor="let interfaceOperation of consumptionInput.associatedInterfaces"
+ label="{{interfaceOperation.label}}">
+ <option
+ *ngFor="let output of interfaceOperation.outputs"
+ [ngValue]="interfaceOperation.name + '.' + output.name">
+ {{output.name}}
+ </option>
+ </optgroup>
+ </select>
+ </div>
+ </div>
+ <div class="operation-input-complex-type-section" *ngIf="consumptionInput.expanded && consumptionInput.source === SOURCE_TYPES.STATIC && !consumptionInput.isSimpleType">
+ <div class="separator"></div>
+ <label class="static-values-title-for-complex-type">{{ 'CONSUMPTION_STATIC_VALUES' | translate}}</label>
+ <div class="dynamic-property" *ngFor="let property of inputFePropertiesMap[consumptionInput.name]">
+ <dynamic-property
+ [property]="property"
+ [readonly]="false"
+ [expandedChildId]="property.expandedChildPropertyId"
+ (propertyChanged)="onComplexPropertyChanged(property, consumptionInput)"
+ (expandChild)="property.updateExpandedChildPropertyId($event)">
+ </dynamic-property>
+ </div>
+ </div>
+ </div>
+ </div>
+ </form>
+</div>
diff --git a/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.less b/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.less
new file mode 100644
index 0000000000..83481c1d74
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.less
@@ -0,0 +1,143 @@
+@import './../../../../assets/styles/variables.less';
+@import './../../../../assets/styles/variables-old.less';
+@import './../../../../assets/styles/sprite.less';
+@import './../../../../assets/styles/mixins_old.less';
+
+.service-consumption-editor {
+ .sdc-modal-top-bar {
+ display: flex;
+ justify-content: space-between;
+ .operation-name {
+ text-transform: capitalize;
+ font-family: @font-opensans-bold;
+ font-size: 18px;
+ }
+ }
+ .expand-collapse-all {
+ display: flex;
+ .expand-all, .collapse-all {
+ color: @main_color_a;
+ border-bottom: solid 1px @main_color_a;
+ font-size: 12px;
+ font-family: @font-opensans-regular;
+ cursor: pointer;
+ width: max-content;
+ line-height: 24px;
+ }
+ .separator-line {
+ border-left: 1px solid @main_color_o;
+ margin: 0 7px;
+ }
+ }
+
+ .i-sdc-form-content-operation-inputs-content {
+ margin-top: 6px;
+ padding-top: 10px;
+ height: 390px;
+ overflow: auto;
+ &.no-inputs {
+ border: 1px solid @main_color_o;
+ }
+ .no-inputs-text {
+ text-align: center;
+ padding: 30px;
+ opacity: 0.7;
+ }
+ .i-sdc-form-content-operation-input-box {
+ border: solid 1px @main_color_o;
+ &:not(:last-of-type) {
+ margin-bottom: 17px;
+ }
+
+ .i-sdc-form-content-operation-input-box-title {
+ .hand;
+ display: flex;
+ align-items: center;
+ padding-left: 10px;
+ height: 38px;
+ font-size: 14px;
+ .expand-collapse-icon {
+ .sprite-new;
+ .expand-collapse-plus-icon;
+ vertical-align: middle;
+ &.expanded {
+ .expand-collapse-minus-icon;
+ }
+ }
+ .operation-input-name {
+ margin-left: 10px;
+ margin-right: 7px;
+ font-family: @font-opensans-bold
+ }
+ .operation-input-type {
+ .type-text {
+ font-family: @font-opensans-bold;
+ }
+ .type-val {
+ font-family: @font-opensans-regular;
+ }
+ }
+ }
+
+ .operation-input-section-row {
+ &.with-top-border, .separator {
+ border-top: 1px solid @main_color_o;
+ }
+ background-color: @tlv_color_t;
+ display: flex;
+ padding: 19px 20px 12px 19px;
+ .operation-input-section-col {
+ flex: 45%;
+ &:not(:last-of-type) {
+ margin-right: 38px;
+ }
+ .i-sdc-form-label {
+ font-size: 12px;
+ font-family: @font-opensans-bold;
+ }
+
+ &.assigned-value {
+ display: flex;
+ flex-direction: column;
+ .i-sdc-form-label {
+ margin-bottom: 0;
+ }
+ }
+
+ /deep/ ui-element-dropdown select, select,
+ /deep/ dynamic-element input {
+ height: 30px;
+ }
+
+ select {
+ margin-top: 2px;
+ border: solid 1px @main_color_o;
+ }
+ }
+ }
+
+ //for complex types
+ /deep/ .operation-input-complex-type-section {
+ background-color: @tlv_color_t;
+ padding: 0 20px 12px 19px;
+ .separator {
+ border-top: 1px solid @main_color_o;
+ padding-bottom: 19px;
+ }
+ .static-values-title-for-complex-type {
+ font-size: 14px;
+ font-family: @font-opensans-bold;
+ margin-bottom: 10px;
+ }
+ .flat-children-container {
+ border: solid 1px @main_color_o;
+ }
+ .dynamic-property {
+ .dynamic-property-row {
+ background-color: @tlv_color_t;
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.ts b/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.ts
new file mode 100644
index 0000000000..25f412671f
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.ts
@@ -0,0 +1,294 @@
+/*!
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.
+ */
+
+import * as _ from "lodash";
+import { Component } from '@angular/core';
+import {ServiceServiceNg2} from "app/ng2/services/component-services/service.service";
+import {Service, ServiceInstanceObject, InstanceFePropertiesMap, InstanceBePropertiesMap, PropertyBEModel, InputBEModel, OperationModel, InterfaceModel} from 'app/models';
+import {ConsumptionInput, ConsumptionInputDetails, ServiceOperation} from 'app/ng2/components/logic/service-consumption/service-consumption.component';
+import {PropertiesUtils} from "app/ng2/pages/properties-assignment/services/properties.utils";
+import { PROPERTY_DATA } from 'app/utils';
+
+
+@Component({
+ selector: 'service-consumption-editor',
+ templateUrl: './service-consumption-editor.component.html',
+ styleUrls:['./service-consumption-editor.component.less'],
+ providers: []
+})
+
+export class ServiceConsumptionCreatorComponent {
+
+ input: {
+ interfaceId: string;
+ serviceOperationIndex: number,
+ serviceOperations: Array<ServiceOperation>,
+ parentService: Service,
+ selectedService: Service,
+ parentServiceInputs: Array<InputBEModel>,
+ selectedServiceProperties: Array<PropertyBEModel>,
+ selectedServiceInstanceId: String,
+ selectedInstanceSiblings: Array<ServiceInstanceObject>
+ };
+ sourceTypes: Array<any> = [];
+ serviceOperationsList: Array<ServiceOperation>;
+ serviceOperation: ServiceOperation;
+ currentIndex: number;
+ isLoading: boolean = false;
+ parentService: Service;
+ selectedService: Service;
+ selectedServiceInstanceId: String;
+ parentServiceInputs: Array<InputBEModel>;
+ selectedServiceProperties: Array<PropertyBEModel>;
+ changedData: Array<ConsumptionInputDetails> = [];
+ inputFePropertiesMap: any = [];
+
+ SOURCE_TYPES = {
+ STATIC: 'Static',
+ SELF: 'Self',
+ SERVICE_PROPERTY_LABEL: 'Service Property',
+ SERVICE_INPUT_LABEL: 'Service Input'
+ };
+
+ constructor(private serviceServiceNg2: ServiceServiceNg2, private propertiesUtils:PropertiesUtils) {}
+
+ ngOnInit() {
+ this.serviceOperationsList = this.input.serviceOperations;
+ this.currentIndex = this.input.serviceOperationIndex;
+ this.serviceOperation = this.serviceOperationsList[this.currentIndex];
+ this.parentService = this.input.parentService;
+ this.selectedService = this.input.selectedService;
+ this.selectedServiceInstanceId = this.input.selectedServiceInstanceId;
+ this.parentServiceInputs = this.input.parentServiceInputs || [];
+ this.selectedServiceProperties = this.input.selectedServiceProperties || [];
+ this.initSourceTypes();
+ this.initConsumptionInputs();
+ }
+
+ initSourceTypes() {
+ this.sourceTypes = [
+ { label: this.SOURCE_TYPES.STATIC, value: this.SOURCE_TYPES.STATIC, options: [], interfaces: []},
+ { label: this.SOURCE_TYPES.SELF + ' (' + this.selectedService.name + ')',
+ value: this.selectedServiceInstanceId,
+ options: this.selectedServiceProperties,
+ interfaces: this.selectedService.interfaces
+ },
+ { label: this.parentService.name,
+ value: this.parentService.uniqueId,
+ options: this.parentServiceInputs,
+ interfaces: []
+ }
+ ];
+ _.forEach(this.input.selectedInstanceSiblings, sib =>
+ this.sourceTypes.push({
+ label: sib.name,
+ value: sib.id,
+ options: _.unionBy(sib.inputs, sib.properties, 'uniqueId'),
+ interfaces: sib.interfaces
+ })
+ );
+ }
+
+ onExpandCollapse(consumptionInput: ConsumptionInputDetails) {
+ consumptionInput.expanded = !consumptionInput.expanded;
+ }
+
+ onExpandAll() {
+ _.forEach(this.serviceOperation.consumptionInputs, coInput => {
+ coInput.expanded = true;
+ })
+ }
+ onCollapseAll() {
+ _.forEach(this.serviceOperation.consumptionInputs, coInput => {
+ coInput.expanded = false;
+ })
+ }
+
+ isAllInputExpanded() {
+ return _.every(this.serviceOperation.consumptionInputs, coInput => coInput.expanded === true);
+ }
+ isAllInputCollapsed() {
+ return _.every(this.serviceOperation.consumptionInputs, coInput => coInput.expanded === false);
+ }
+
+ onChangePage(newIndex) {
+ if (newIndex >= 0 && newIndex < this.serviceOperationsList.length) {
+ this.currentIndex = newIndex;
+ this.serviceOperation = this.serviceOperationsList[newIndex];
+ if(!this.serviceOperation.consumptionInputs || this.serviceOperation.consumptionInputs.length === 0) {
+ this.initConsumptionInputs();
+ }
+ this.getComplexPropertiesForCurrentInputsOfOperation(this.serviceOperation.consumptionInputs);
+ }
+ }
+
+ private initConsumptionInputs() {
+ this.isLoading = true;
+ this.serviceServiceNg2.getServiceConsumptionInputs(this.parentService, this.selectedServiceInstanceId, this.input.interfaceId, this.serviceOperation.operation).subscribe((result: Array<ConsumptionInput>) => {
+ this.isLoading = false;
+ this.serviceOperation.consumptionInputs = this.analyzeCurrentConsumptionInputs(result);
+ this.getComplexPropertiesForCurrentInputsOfOperation(this.serviceOperation.consumptionInputs);
+ }, err=> {
+ this.isLoading = false;
+ });
+ }
+
+ private analyzeCurrentConsumptionInputs(result: Array<any>): Array<ConsumptionInputDetails> {
+ let inputsResult: Array<ConsumptionInputDetails> = [];
+ let currentOp = this.serviceOperation.operation;
+ if(currentOp) {
+ inputsResult = _.map(result, input => {
+ let sourceVal = input.source || this.SOURCE_TYPES.STATIC;
+ let consumptionInputDetails: ConsumptionInputDetails = _.cloneDeep(input);
+ consumptionInputDetails.source = sourceVal;
+ consumptionInputDetails.isValid = true;
+ consumptionInputDetails.expanded = false;
+ let filteredListsObj = this.getFilteredProps(sourceVal, input.type);
+ consumptionInputDetails.assignValueLabel = this.getAssignValueLabel(sourceVal);
+ consumptionInputDetails.associatedProps = filteredListsObj.associatedPropsList;
+ consumptionInputDetails.associatedInterfaces = filteredListsObj.associatedInterfacesList;
+ return new ConsumptionInputDetails(consumptionInputDetails);
+ });
+ }
+ return inputsResult;
+ }
+
+ private onSourceChanged(consumptionInput: ConsumptionInputDetails): void {
+ consumptionInput.assignValueLabel = this.getAssignValueLabel(consumptionInput.source);
+ let filteredListsObj = this.getFilteredProps(consumptionInput.source, consumptionInput.type);
+ consumptionInput.associatedProps = filteredListsObj.associatedPropsList;
+ consumptionInput.associatedInterfaces = filteredListsObj.associatedInterfacesList;
+ if(consumptionInput.source === this.SOURCE_TYPES.STATIC) {
+ if(PROPERTY_DATA.SIMPLE_TYPES.indexOf(consumptionInput.type) !== -1) {
+ consumptionInput.value = consumptionInput.defaultValue || "";
+ }
+ else {
+ consumptionInput.value = null;
+ Object.assign(this.inputFePropertiesMap, this.processPropertiesOfComplexTypeInput(consumptionInput));
+ }
+ }
+ }
+
+ private getFilteredProps(sourceVal, inputType) {
+ let currentSourceObj = this.sourceTypes.find(s => s.value === sourceVal);
+ let associatedInterfacesList = [], associatedPropsList = [];
+ if(currentSourceObj) {
+ if (currentSourceObj.interfaces) {
+ associatedInterfacesList = this.getFilteredInterfaceOutputs(currentSourceObj, inputType);
+ }
+ associatedPropsList = currentSourceObj.options.reduce((result, prop) => {
+ if (prop.type === inputType) {
+ result.push(prop.name);
+ }
+ return result;
+ }, []);
+ }
+ return {
+ associatedPropsList: associatedPropsList,
+ associatedInterfacesList: associatedInterfacesList
+ }
+ }
+
+ private getFilteredInterfaceOutputs(currentSourceObj, inputType) {
+ let currentServiceOperationId = this.serviceOperation.operation.uniqueId;
+ let filteredInterfacesList = [];
+ Object.keys(currentSourceObj.interfaces).map(interfId => {
+ let interfaceObj: InterfaceModel = new InterfaceModel(currentSourceObj.interfaces[interfId]);
+ Object.keys(interfaceObj.operations).map(opId => {
+ if(currentServiceOperationId !== opId) {
+ let operationObj: OperationModel = interfaceObj.operations[opId];
+ let filteredOutputsList = _.filter(operationObj.outputs.listToscaDataDefinition, output => output.type === inputType);
+ if (filteredOutputsList.length) {
+ filteredInterfacesList.push({
+ name: `${interfaceObj.type}.${operationObj.name}`,
+ label: `${interfaceObj.displayType()}.${operationObj.name}`,
+ outputs: filteredOutputsList
+ });
+ }
+ }
+ });
+ });
+ return filteredInterfacesList;
+ }
+
+ getAssignValueLabel(selectedSource: string): string {
+ if(selectedSource === this.SOURCE_TYPES.STATIC || selectedSource === "") {
+ return this.SOURCE_TYPES.STATIC;
+ }
+ else {
+ if(selectedSource === this.parentService.uniqueId) { //parent is the source
+ return this.SOURCE_TYPES.SERVICE_INPUT_LABEL;
+ }
+ return this.SOURCE_TYPES.SERVICE_PROPERTY_LABEL;
+ }
+ }
+
+
+ private isValidInputsValues(): boolean {
+ return this.changedData.length > 0 && this.changedData.every((changedItem) => changedItem.isValid);
+ }
+
+ private isMandatoryFieldsValid(): boolean {
+ const invalid: Array<ConsumptionInputDetails> = this.serviceOperation.consumptionInputs.filter(item =>
+ item.required && (item.value === null || typeof item.value === 'undefined' || item.value === ''));
+ if (invalid.length > 0) {
+ return false;
+ }
+ return true;
+ }
+
+ checkFormValidForSubmit(): boolean {
+ return this.isValidInputsValues() && this.isMandatoryFieldsValid();
+ }
+
+ checkFormValidForNavigation(): boolean {
+ return this.isMandatoryFieldsValid() && (this.changedData.length === 0 || this.isValidInputsValues());
+ }
+
+ onChange(value: any, isValid: boolean, consumptionInput: ConsumptionInputDetails) {
+ consumptionInput.updateValidity(isValid);
+ const dataChangedIndex = this.changedData.findIndex((changedItem) => changedItem.inputId === consumptionInput.inputId);
+ if (value !== consumptionInput.origVal) {
+ if (dataChangedIndex === -1) {
+ this.changedData.push(consumptionInput);
+ }
+ } else {
+ if (dataChangedIndex !== -1) {
+ this.changedData.splice(dataChangedIndex, 1);
+ }
+ }
+ }
+
+ private getComplexPropertiesForCurrentInputsOfOperation(opInputs: Array<ConsumptionInput>) {
+ _.forEach(opInputs, input => {
+ if(PROPERTY_DATA.SIMPLE_TYPES.indexOf(input.type) === -1 && input.source === this.SOURCE_TYPES.STATIC) {
+ Object.assign(this.inputFePropertiesMap, this.processPropertiesOfComplexTypeInput(input));
+ }
+ });
+ }
+
+ private processPropertiesOfComplexTypeInput(input: ConsumptionInput): InstanceFePropertiesMap {
+ let inputBePropertiesMap: InstanceBePropertiesMap = new InstanceBePropertiesMap();
+ inputBePropertiesMap[input.name] = [input];
+ let originTypeIsVF = false;
+ return this.propertiesUtils.convertPropertiesMapToFEAndCreateChildren(inputBePropertiesMap, originTypeIsVF); //create flattened children and init values
+ }
+
+ onComplexPropertyChanged(property, consumptionInput) {
+ consumptionInput.value = JSON.stringify(property.valueObj);
+ this.onChange(property.valueObj, property.valueObjIsValid , consumptionInput);
+ }
+} \ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.module.ts b/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.module.ts
new file mode 100644
index 0000000000..e37cd76716
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.module.ts
@@ -0,0 +1,28 @@
+import { NgModule } from "@angular/core";
+import {CommonModule} from "@angular/common";
+import {ServiceConsumptionCreatorComponent} from "./service-consumption-editor.component";
+import {FormsModule} from "@angular/forms";
+import {FormElementsModule} from "app/ng2/components/ui/form-components/form-elements.module";
+import {UiElementsModule} from "app/ng2/components/ui/ui-elements.module";
+import {PropertyTableModule} from 'app/ng2/components/logic/properties-table/property-table.module';
+import {TranslateModule} from 'app/ng2/shared/translator/translate.module';
+
+@NgModule({
+ declarations: [
+ ServiceConsumptionCreatorComponent
+ ],
+ imports: [CommonModule,
+ FormsModule,
+ FormElementsModule,
+ UiElementsModule,
+ PropertyTableModule,
+ TranslateModule
+ ],
+ exports: [],
+ entryComponents: [
+ ServiceConsumptionCreatorComponent
+ ],
+ providers: []
+})
+export class ServiceConsumptionCreatorModule {
+} \ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/services/component-services/component.service.ts b/catalog-ui/src/app/ng2/services/component-services/component.service.ts
index 46dfe01992..a25fb75a41 100644
--- a/catalog-ui/src/app/ng2/services/component-services/component.service.ts
+++ b/catalog-ui/src/app/ng2/services/component-services/component.service.ts
@@ -25,10 +25,9 @@ import 'rxjs/add/operator/map';
import 'rxjs/add/operator/toPromise';
import {Response, URLSearchParams} from '@angular/http';
import { Component, ComponentInstance, InputBEModel, InstancePropertiesAPIMap, FilterPropertiesAssignmentData,
- PropertyBEModel, OperationModel, BEOperationModel, Capability, Requirement
-} from "app/models";
-import {downgradeInjectable} from '@angular/upgrade/static';
+ PropertyBEModel, OperationModel, BEOperationModel, Capability, Requirement} from "app/models";
import {COMPONENT_FIELDS, CommonUtils, SERVICE_FIELDS} from "app/utils";
+import {downgradeInjectable} from '@angular/upgrade/static';
import {ComponentGenericResponse} from "../responses/component-generic-response";
import {InstanceBePropertiesMap} from "../../../models/properties-inputs/property-fe-map";
import {API_QUERY_PARAMS} from "app/utils";
@@ -282,7 +281,7 @@ export class ComponentServiceNg2 {
})
}
- restoreComponent(componentType:string, componentId:string){
+ restoreComponent(componentType:string, componentId:string){
return this.http.post(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/restore', {})
}
diff --git a/catalog-ui/src/app/ng2/services/component-services/service.service.ts b/catalog-ui/src/app/ng2/services/component-services/service.service.ts
index 15e624b81f..406ac77ec4 100644
--- a/catalog-ui/src/app/ng2/services/component-services/service.service.ts
+++ b/catalog-ui/src/app/ng2/services/component-services/service.service.ts
@@ -23,8 +23,7 @@ import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/toPromise';
import { Response, URLSearchParams } from '@angular/http';
-import {Service} from "app/models";
-import { downgradeInjectable } from '@angular/upgrade/static';
+import {Service, OperationModel} from "app/models";
import { HttpService } from '../http.service';
import {SdcConfigToken, ISdcConfig} from "../../config/sdc-config.config";
@@ -37,6 +36,7 @@ import {COMPONENT_FIELDS, SERVICE_FIELDS} from "app/utils/constants";
import {ComponentServiceNg2} from "./component.service";
import {ServiceGenericResponse} from "app/ng2/services/responses/service-generic-response";
import {ServicePathMapItem} from "app/models/graph/nodes-and-links-map";
+import {ConsumptionInput} from 'app/ng2/components/logic/service-consumption/service-consumption.component';
@Injectable()
@@ -110,6 +110,26 @@ export class ServiceServiceNg2 extends ComponentServiceNg2 {
});
}
+ getServiceConsumptionData(service: Service):Observable<ComponentGenericResponse> {
+ return this.getComponentDataByFieldsName(service.componentType, service.uniqueId, [
+ COMPONENT_FIELDS.COMPONENT_INSTANCES_INTERFACES,
+ COMPONENT_FIELDS.COMPONENT_INSTANCES_PROPERTIES,
+ COMPONENT_FIELDS.COMPONENT_INSTANCES_INPUTS,
+ COMPONENT_FIELDS.COMPONENT_INPUTS
+ ]);
+ }
+
+ getServiceConsumptionInputs(service: Service, serviceInstanceId: String, interfaceId: string, operation: OperationModel): Observable<any> {
+ return this.http.get(this.baseUrl + service.getTypeUrl() + service.uniqueId + '/consumption/' + serviceInstanceId + '/interfaces/' + interfaceId + '/operations/' + operation.uniqueId + '/inputs')
+ .map(res => {
+ return res.json();
+ });
+ }
+
+ createOrUpdateServiceConsumptionInputs(service: Service, serviceInstanceId: String, consumptionInputsList: Array<{[id: string]: Array<ConsumptionInput>}>): Observable<any> {
+ return this.http.post(this.baseUrl + service.getTypeUrl() + service.uniqueId + '/consumption/' + serviceInstanceId, consumptionInputsList);
+ }
+
checkComponentInstanceVersionChange(service: Service, newVersionId: string):Observable<Array<string>> {
let instanceId = service.selectedInstance.uniqueId;
let queries = {componentInstanceId: instanceId, newComponentInstanceId: newVersionId};
diff --git a/catalog-ui/src/app/ng2/services/responses/component-generic-response.ts b/catalog-ui/src/app/ng2/services/responses/component-generic-response.ts
index 37ccf381c9..d297ea0874 100644
--- a/catalog-ui/src/app/ng2/services/responses/component-generic-response.ts
+++ b/catalog-ui/src/app/ng2/services/responses/component-generic-response.ts
@@ -22,11 +22,10 @@
* Created by ob0695 on 4/18/2017.
*/
-import { ArtifactGroupModel, PropertyModel, PropertiesGroup, AttributeModel, AttributesGroup, ComponentInstance, OperationModel,
- InputBEModel, Module, ComponentMetadata, RelationshipModel, RequirementsGroup, CapabilitiesGroup,InputFEModel} from "app/models";
+import { ArtifactGroupModel, PropertyModel, PropertiesGroup, InputsGroup, AttributeModel, AttributesGroup, ComponentInstance, OperationModel,
+ InputBEModel, Module, ComponentMetadata, RelationshipModel, RequirementsGroup, CapabilitiesGroup, InterfaceModel} from "app/models";
import {CommonUtils} from "app/utils";
import {Serializable} from "../utils/serializable";
-import {PropertyBEModel} from "../../../models/properties-inputs/property-be-model";
import { PolicyInstance } from "app/models/graph/zones/policy-instance";
import { GroupInstance } from "../../../models/graph/zones/group-instance";
@@ -37,9 +36,11 @@ export class ComponentGenericResponse implements Serializable<ComponentGenericR
public artifacts:ArtifactGroupModel;
public toscaArtifacts:ArtifactGroupModel;
public componentInstancesProperties:PropertiesGroup;
+ public componentInstancesInputs:InputsGroup;
public componentInstancesAttributes:AttributesGroup;
public componentInstancesRelations:Array<RelationshipModel>;
public componentInstances:Array<ComponentInstance>;
+ public componentInstancesInterfaces:Map<string,Array<InterfaceModel>>;
public inputs:Array<InputBEModel>;
public capabilities:CapabilitiesGroup;
public requirements:RequirementsGroup;
@@ -59,6 +60,9 @@ export class ComponentGenericResponse implements Serializable<ComponentGenericR
if(response.componentInstancesProperties) {
this.componentInstancesProperties = new PropertiesGroup(response.componentInstancesProperties);
}
+ if(response.componentInstancesInputs) {
+ this.componentInstancesInputs = response.componentInstancesInputs;
+ }
if(response.componentInstancesAttributes) {
this.componentInstancesAttributes = new AttributesGroup(response.componentInstancesAttributes);
}
@@ -96,6 +100,12 @@ export class ComponentGenericResponse implements Serializable<ComponentGenericR
this.interfaces = CommonUtils.initInterfaces(response.interfaces);
this.interfaceOperations = CommonUtils.initInterfaceOperations(response.interfaces);
}
+ if(response.componentInstancesInterfaces) {
+ this.componentInstancesInterfaces = new Map();
+ for (let resourceId in response.componentInstancesInterfaces) {
+ this.componentInstancesInterfaces[resourceId] = CommonUtils.initInterfaces(response.componentInstancesInterfaces[resourceId]);
+ }
+ }
if(response.metadata) {
this.metadata = new ComponentMetadata().deserialize(response.metadata);
}
diff --git a/catalog-ui/src/app/utils/common-utils.ts b/catalog-ui/src/app/utils/common-utils.ts
index c5259f0d30..99f7c49a29 100644
--- a/catalog-ui/src/app/utils/common-utils.ts
+++ b/catalog-ui/src/app/utils/common-utils.ts
@@ -19,9 +19,9 @@
*/
import * as _ from "lodash";
-import { ComponentInstanceFactory } from "./component-instance-factory";
-import { Module, AttributeModel, ResourceInstance, PropertyModel, InputFEModel, InterfaceModel, OperationModel } from "../models";
-import { InputBEModel, PropertyBEModel, RelationshipModel } from "app/models";
+import {Module, AttributeModel, ResourceInstance, PropertyModel, InterfaceModel, OperationModel} from "../models";
+import {ComponentInstanceFactory} from "./component-instance-factory";
+import {InputBEModel, PropertyBEModel, RelationshipModel} from "app/models";
import { PolicyInstance } from "app/models/graph/zones/policy-instance";
import { GroupInstance } from "../models/graph/zones/group-instance";
diff --git a/catalog-ui/src/app/utils/constants.ts b/catalog-ui/src/app/utils/constants.ts
index 104b5dcb14..d96bf6f616 100644
--- a/catalog-ui/src/app/utils/constants.ts
+++ b/catalog-ui/src/app/utils/constants.ts
@@ -336,6 +336,7 @@ export class DEPENDENCY_EVENTS {
export class COMPONENT_FIELDS {
static COMPONENT_INSTANCES_PROPERTIES = "componentInstancesProperties";
+ static COMPONENT_INSTANCES_INPUTS = "componentInstancesInputs";
static COMPONENT_INSTANCES_ATTRIBUTES = "componentInstancesAttributes";
static COMPONENT_ATTRIBUTES = "attributes";
static COMPONENT_INSTANCES = "componentInstances";
@@ -351,6 +352,7 @@ export class COMPONENT_FIELDS {
static COMPONENT_POLICIES = "policies";
static COMPONENT_GROUPS = "groups";
static COMPONENT_INTERFACE_OPERATIONS = "interfaces";
+ static COMPONENT_INSTANCES_INTERFACES = "componentInstancesInterfaces";
static COMPONENT_NON_EXCLUDED_GROUPS = "nonExcludedGroups";
static COMPONENT_NON_EXCLUDED_POLICIES = "nonExcludedPolicies";
}
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
index 5559e534b9..c2d6007edc 100644
--- 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
@@ -116,6 +116,14 @@
<div class="i-sdc-designer-sidebar-tab-icon sprite-new api"></div>
</button>
<button class="i-sdc-designer-sidebar-tab" data-ui-sref-active="active"
+ data-ui-sref="workspace.composition.consumption"
+ tooltips tooltip-class="tooltip-custom tab-tooltip" tooltip-content="Operation Consumption"
+ data-tests-id="service-consumption-tab"
+ data-ng-if="(isComponentInstanceSelected() && currentComponent.selectedInstance.isServiceProxy())"
+ data-ng-class="{'disabled': disabledTabs}">
+ <div class="i-sdc-designer-sidebar-tab-icon sprite-new import-icon"></div>
+ </button>
+ <button class="i-sdc-designer-sidebar-tab" data-ui-sref-active="active"
data-ui-sref="workspace.composition.dependencies"
tooltips tooltip-class="tooltip-custom tab-tooltip " tooltip-content="Service Dependencies"
data-tests-id="service-dependency-tab"
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
index b9bb66cde7..f37a492572 100644
--- a/catalog-ui/src/app/view-models/workspace/tabs/composition/composition.less
+++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/composition.less
@@ -216,6 +216,9 @@
.i-sdc-designer-sidebar-tab-icon {
margin-top: 5px ;
+ &.import-icon {
+ transform: rotate(270deg);
+ }
// opacity: .4;
}
@@ -828,7 +831,7 @@
.g_1;
line-height: 18px;
}
-
+
.service-path-buttons {
margin-top: 12px;
position: absolute;
@@ -886,7 +889,7 @@
border-radius: 2px;
border:solid 1px #fff;
}
- }
+ }
}
.zoom-icons {
diff --git a/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/service-consumption/service-consumption-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/service-consumption/service-consumption-view-model.ts
new file mode 100644
index 0000000000..20e99b0d39
--- /dev/null
+++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/service-consumption/service-consumption-view-model.ts
@@ -0,0 +1,79 @@
+/*!
+* Copyright © 2016-2018 European Support Limited
+*
+* 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.
+*/
+
+
+import {ICompositionViewModelScope} from "../../composition-view-model";
+import {Service, PropertiesGroup, InputsGroup, ServiceInstanceObject, InterfaceModel, InputBEModel} from 'app/models';
+import {ComponentGenericResponse} from "app/ng2/services/responses/component-generic-response";
+import {ServiceServiceNg2} from "app/ng2/services/component-services/service.service";
+
+interface IServiceConsumptionViewModelScope extends ICompositionViewModelScope {
+ service: Service;
+ instancesMappedList: Array<ServiceInstanceObject>;
+ componentInstancesProperties: PropertiesGroup;
+ componentInstancesInputs: InputsGroup;
+ componentInstancesInterfaces: Map<string, Array<InterfaceModel>>;
+ componentInputs: Array<InputBEModel>;
+}
+
+
+export class ServiceConsumptionViewModel {
+
+ static '$inject' = [
+ '$scope',
+ 'ServiceServiceNg2'
+ ];
+
+ constructor(private $scope:IServiceConsumptionViewModelScope, private ServiceServiceNg2:ServiceServiceNg2) {
+ this.$scope.service = <Service>this.$scope.currentComponent;
+ this.initInstances();
+ this.initScope();
+ }
+
+ private initInstances = ():void => {
+ this.ServiceServiceNg2.getServiceConsumptionData(this.$scope.service).subscribe((genericResponse:ComponentGenericResponse) => {
+ this.$scope.componentInstancesProperties = genericResponse.componentInstancesProperties;
+ this.$scope.componentInstancesInputs = genericResponse.componentInstancesInputs;
+ this.$scope.componentInstancesInterfaces = genericResponse.componentInstancesInterfaces;
+ this.$scope.componentInputs = genericResponse.inputs;
+ this.updateInstanceAttributes();
+ });
+ }
+
+ private updateInstanceAttributes = ():void => {
+ if (this.$scope.isComponentInstanceSelected() && this.$scope.componentInstancesProperties) {
+ this.$scope.instancesMappedList = this.$scope.service.componentInstances.map(coInstance => new ServiceInstanceObject({
+ id: coInstance.uniqueId,
+ name: coInstance.name,
+ properties: this.$scope.componentInstancesProperties[coInstance.uniqueId] || [],
+ inputs: this.$scope.componentInstancesInputs[coInstance.uniqueId] || [],
+ interfaces: this.$scope.componentInstancesInterfaces[coInstance.uniqueId] || []
+ }));
+ }
+ }
+
+ private initScope = ():void => {
+ this.$scope.$watch('currentComponent.selectedInstance', ():void => {
+ this.updateInstanceAttributes();
+ });
+
+ this.$scope.registerCreateInstanceEvent(() => {
+ this.initInstances();
+ });
+
+ this.$scope.$on('$destroy', this.$scope.unregisterCreateInstanceEvent);
+ }
+}
diff --git a/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/service-consumption/service-consumption-view.html b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/service-consumption/service-consumption-view.html
new file mode 100644
index 0000000000..835ded33f6
--- /dev/null
+++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/service-consumption/service-consumption-view.html
@@ -0,0 +1,22 @@
+<perfect-scrollbar class="w-sdc-designer-sidebar-tab-content service-consumption">
+ <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" tooltips tooltip-content="Operation Consumption">Operation Consumption</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">
+ <ng2-service-consumption
+ [parent-service]="service"
+ [selected-service]="selectedComponent"
+ [selected-service-instance-id]="currentComponent.selectedInstance.uniqueId"
+ [instances-mapped-list]="instancesMappedList"
+ [parent-service-inputs]="componentInputs"
+ [readonly]="isViewMode() || !isDesigner()">
+ </ng2-service-consumption>
+ </div>
+ </div>
+ </div>
+</perfect-scrollbar> \ No newline at end of file