diff options
31 files changed, 1555 insertions, 49 deletions
diff --git a/catalog-ui/configurations/menu.js b/catalog-ui/configurations/menu.js index e433ebc857..d93b3ac45c 100644 --- a/catalog-ui/configurations/menu.js +++ b/catalog-ui/configurations/menu.js @@ -465,8 +465,13 @@ const SDC_MENU_CONFIG = { {"text": "Composition", "action": "onMenuItemPressed", "state": "workspace.composition.details"}, {"text": "Operation", "action":"onMenuItemPressed", "state": "workspace.interface_operation"}, {"text": "Activity Log", "action": "onMenuItemPressed", "state": "workspace.activity_log"}, - {"text": "Deployment", "action": "onMenuItemPressed", "state": "workspace.deployment"}, - {"text": "Properties Assignment", "action": "onMenuItemPressed", "state": "workspace.properties_assignment"} + {"text": "Deployment", "action": "onMenuItemPressed", "state": "workspace.deployment"}, + { + "text": "Properties Assignment", + "action": "onMenuItemPressed", + "state": "workspace.properties_assignment" + }, + {"text": "Req. & Capabilities", "action": "onMenuItemPressed", "state": "workspace.reqAndCapEditable"} ], "PNF": [ {"text": "General", "action": "onMenuItemPressed", "state": "workspace.general"}, @@ -475,7 +480,12 @@ const SDC_MENU_CONFIG = { {"text": "Composition", "action": "onMenuItemPressed", "state": "workspace.composition.details"}, {"text": "Operation", "action": "onMenuItemPressed", "state": "workspace.interface_operation"}, {"text": "Activity Log", "action": "onMenuItemPressed", "state": "workspace.activity_log"}, - {"text": "Properties Assignment", "action": "onMenuItemPressed", "state": "workspace.properties_assignment"} + { + "text": "Properties Assignment", + "action": "onMenuItemPressed", + "state": "workspace.properties_assignment" + }, + {"text": "Req. & Capabilities", "action": "onMenuItemPressed", "state": "workspace.reqAndCapEditable"} ], "CR": [ {"text": "General", "action": "onMenuItemPressed", "state": "workspace.general"}, @@ -495,8 +505,13 @@ const SDC_MENU_CONFIG = { {"text": "Management Workflow", "action": "onMenuItemPressed", "state": "workspace.management_workflow"}, {"text": "Network Call Flow ", "action": "onMenuItemPressed", "state": "workspace.network_call_flow"}, {"text": "Distribution","action": "onMenuItemPressed","state": "workspace.distribution","disabledRoles": ["ADMIN", "TESTER", "GOVERNOR", "DESIGNER"]}, - {"text": "Deployment", "action": "onMenuItemPressed", "state": "workspace.deployment"}, - {"text": "Properties Assignment", "action": "onMenuItemPressed", "state": "workspace.properties_assignment"} + {"text": "Deployment", "action": "onMenuItemPressed", "state": "workspace.deployment"}, + { + "text": "Properties Assignment", + "action": "onMenuItemPressed", + "state": "workspace.properties_assignment" + }, + {"text": "Req. & Capabilities", "action": "onMenuItemPressed", "state": "workspace.reqAndCapEditable"} ] } diff --git a/catalog-ui/src/app/app.ts b/catalog-ui/src/app/app.ts index 8fa7f1eb50..ebcfdd2785 100644 --- a/catalog-ui/src/app/app.ts +++ b/catalog-ui/src/app/app.ts @@ -388,6 +388,18 @@ ng1appModule.config([ } ); + $stateProvider.state( + States.WORKSPACE_REQUIREMENTS_AND_CAPABILITIES_EDITABLE, { + url: 'req_and_capabilities_editable', + parent: 'workspace', + controller: viewModelsModuleName + '.ReqAndCapabilitiesViewModel', + templateUrl: './view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-editable-view.html', + data: { + bodyClass: 'attributes' + } + } + ); + $stateProvider.state( States.WORKSPACE_MANAGEMENT_WORKFLOW, { diff --git a/catalog-ui/src/app/directives/ellipsis/ellipsis-directive.html b/catalog-ui/src/app/directives/ellipsis/ellipsis-directive.html index 6ddbd6169a..d59c44d67f 100644 --- a/catalog-ui/src/app/directives/ellipsis/ellipsis-directive.html +++ b/catalog-ui/src/app/directives/ellipsis/ellipsis-directive.html @@ -18,7 +18,8 @@ {{actualText}} <span class="ellipsis-directive-more-less" - data-ng-click="collapsed = !collapsed; toggleText()" - data-ng-hide="ellipsis.length <= maxChars"> - {{collapsed ? "More" : "Less"}} + data-ng-click="onMoreLessClick($event)" + data-ng-hide="ellipsis.length <= maxChars" + data-tests-id="ellipsis-more-less"> + {{actualText ? (collapsed ? "More" : "Less") : ""}} </span> diff --git a/catalog-ui/src/app/directives/ellipsis/ellipsis-directive.ts b/catalog-ui/src/app/directives/ellipsis/ellipsis-directive.ts index 60baf3e183..21e074a8b9 100644 --- a/catalog-ui/src/app/directives/ellipsis/ellipsis-directive.ts +++ b/catalog-ui/src/app/directives/ellipsis/ellipsis-directive.ts @@ -23,6 +23,7 @@ export interface IEllipsisScope extends ng.IScope { ellipsis:string; maxChars:number; toggleText():void; + onMoreLessClick(event): void; collapsed:boolean; actualText:string; @@ -50,6 +51,12 @@ export class EllipsisDirective implements ng.IDirective { scope.collapsed = true; + scope.onMoreLessClick = (event): void => { + event.stopPropagation(); + scope.collapsed = !scope.collapsed; + scope.toggleText(); + }; + scope.toggleText = ():void => { if (scope.ellipsis && scope.collapsed) { scope.actualText = scope.ellipsis.substr(0, scope.maxChars); diff --git a/catalog-ui/src/app/models.ts b/catalog-ui/src/app/models.ts index 3a31cafab5..5c79155260 100644 --- a/catalog-ui/src/app/models.ts +++ b/catalog-ui/src/app/models.ts @@ -112,7 +112,11 @@ export * from './models/modal'; export * from './models/button'; 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/filter-properties-assignment-data'; +export * from './models/properties-inputs/input-be-model'; +export * from './models/service-instance-properties'; +export * from './models/relationship-types'; +export * from './models/tosca-presentation'; +export * from './models/node-types'; +export * from './models/capability-types'; diff --git a/catalog-ui/src/app/models/capability-types.ts b/catalog-ui/src/app/models/capability-types.ts new file mode 100644 index 0000000000..fc01f540a7 --- /dev/null +++ b/catalog-ui/src/app/models/capability-types.ts @@ -0,0 +1,41 @@ +/*! + * 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 {ToscaPresentationData} from "./tosca-presentation"; + +export class CapabilityTypesMap { + capabilityTypesMap: CapabilityTypesMapData; + + constructor(capabilityTypesMap: CapabilityTypesMapData) { + this.capabilityTypesMap = capabilityTypesMap; + } +} + +export class CapabilityTypesMapData { + [capabilityTypeId: string]: CapabilityTypeModel; +} + +export class CapabilityTypeModel { + derivedFrom: string; + toscaPresentation: ToscaPresentationData; + + constructor(capabilityType?: CapabilityTypeModel) { + if (capabilityType) { + this.derivedFrom = capabilityType.derivedFrom; + this.toscaPresentation = capabilityType.toscaPresentation; + } + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/models/capability.ts b/catalog-ui/src/app/models/capability.ts index 74994b155b..caef2e87dd 100644 --- a/catalog-ui/src/app/models/capability.ts +++ b/catalog-ui/src/app/models/capability.ts @@ -57,7 +57,7 @@ export class Capability implements RequirementCapabilityModel{ uniqueId:string; capabilitySources:Array<String>; leftOccurrences:string; - minOccurrences:string; + minOccurrences: number; maxOccurrences:string; description:string; validSourceTypes:Array<string>; diff --git a/catalog-ui/src/app/models/component-metadata.ts b/catalog-ui/src/app/models/component-metadata.ts index 9f5e22cce4..0f0a30d529 100644 --- a/catalog-ui/src/app/models/component-metadata.ts +++ b/catalog-ui/src/app/models/component-metadata.ts @@ -49,6 +49,7 @@ export class ComponentMetadata { public systemName:string; public archived:boolean; public vspArchived: boolean; + public toscaResourceName: string; //Resource only public resourceType: string; @@ -118,6 +119,7 @@ export class ComponentMetadata { this.archived = response.archived; this.instantiationType = response.instantiationType; this.vspArchived = response.vspArchived; + this.toscaResourceName = response.toscaResourceName; return this; } diff --git a/catalog-ui/src/app/models/node-types.ts b/catalog-ui/src/app/models/node-types.ts new file mode 100644 index 0000000000..54d2ef567d --- /dev/null +++ b/catalog-ui/src/app/models/node-types.ts @@ -0,0 +1,45 @@ +/*! + * 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 {ComponentMetadata} from "./component-metadata"; + +export class NodeTypesMap { + nodeTypesMap: NodeTypesMapData; + + constructor(nodeTypesMap: NodeTypesMapData) { + this.nodeTypesMap = nodeTypesMap; + } +} + +export class NodeTypesMapData { + [nodeTypeId: string]: NodeTypeModel; +} + +export class NodeTypeModel { + componentMetadataDefinition: ComponentMetadataDefModel; + + constructor(nodeType?: NodeTypeModel) { + this.componentMetadataDefinition = nodeType.componentMetadataDefinition; + } +} + +export class ComponentMetadataDefModel { + componentMetadataDataDefinition: ComponentMetadata; + + constructor(componentMetadataDef?: ComponentMetadataDefModel) { + this.componentMetadataDataDefinition = componentMetadataDef.componentMetadataDataDefinition; + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/models/relationship-types.ts b/catalog-ui/src/app/models/relationship-types.ts new file mode 100644 index 0000000000..8ae827bff4 --- /dev/null +++ b/catalog-ui/src/app/models/relationship-types.ts @@ -0,0 +1,41 @@ +/*! + * 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 {ToscaPresentationData} from "./tosca-presentation"; + +export class RelationshipTypesMap { + relationshipTypesMap: RelationshipTypesMapData; + + constructor(relationshipTypesMap: RelationshipTypesMapData) { + this.relationshipTypesMap = relationshipTypesMap; + } +} + +export class RelationshipTypesMapData { + [relationshipTypeId: string]: RelationshipTypeModel; +} + +export class RelationshipTypeModel { + derivedFrom: string; + toscaPresentation: ToscaPresentationData; + + constructor(relationshipType?: RelationshipTypeModel) { + if (relationshipType) { + this.derivedFrom = relationshipType.derivedFrom; + this.toscaPresentation = relationshipType.toscaPresentation; + } + } +} diff --git a/catalog-ui/src/app/models/requirement.ts b/catalog-ui/src/app/models/requirement.ts index 65428b398e..3cc0cf22e4 100644 --- a/catalog-ui/src/app/models/requirement.ts +++ b/catalog-ui/src/app/models/requirement.ts @@ -51,7 +51,7 @@ export class Requirement implements RequirementCapabilityModel{ uniqueId:string; relationship:string; leftOccurrences:string; - minOccurrences:string; + minOccurrences: number; maxOccurrences:string; //custom filterTerm:string; diff --git a/catalog-ui/src/app/models/tosca-presentation.ts b/catalog-ui/src/app/models/tosca-presentation.ts new file mode 100644 index 0000000000..3fdddde448 --- /dev/null +++ b/catalog-ui/src/app/models/tosca-presentation.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. + */ + +export class ToscaPresentationData { + creationTime: number; + description: string; + type: string; + validTargetTypes: Array<string>; + modificationTime: number; + uniqueId: string; + + constructor(toscaPresentation?: ToscaPresentationData) { + if (toscaPresentation) { + this.creationTime = toscaPresentation.creationTime; + this.description = toscaPresentation.description; + this.type = toscaPresentation.type; + this.validTargetTypes = toscaPresentation.validTargetTypes; + this.modificationTime = toscaPresentation.modificationTime; + this.uniqueId = toscaPresentation.uniqueId; + } + } +} diff --git a/catalog-ui/src/app/modules/service-module.ts b/catalog-ui/src/app/modules/service-module.ts index f4350a39d4..376a036540 100644 --- a/catalog-ui/src/app/modules/service-module.ts +++ b/catalog-ui/src/app/modules/service-module.ts @@ -61,6 +61,7 @@ import {DynamicComponentService} from "app/ng2/services/dynamic-component.servic import {AutomatedUpgradeService} from "../ng2/pages/automated-upgrade/automated-upgrade.service"; import {ArchiveService as ArchiveServiceNg2} from "app/ng2/services/archive.service"; import {ComponentFactory} from "app/utils/component-factory"; +import {ToscaTypesServiceNg2} from "app/ng2/services/tosca-types.service"; let moduleName:string = 'Sdc.Services'; let serviceModule:ng.IModule = angular.module(moduleName, []); @@ -115,3 +116,4 @@ serviceModule.factory('EventBusService', downgradeInjectable(EventBusService)); serviceModule.factory('DynamicComponentService', downgradeInjectable(DynamicComponentService)); serviceModule.factory('ArchiveServiceNg2', downgradeInjectable(ArchiveServiceNg2)); serviceModule.factory('AutomatedUpgradeService', downgradeInjectable(AutomatedUpgradeService)); +serviceModule.factory('ToscaTypesServiceNg2', downgradeInjectable(ToscaTypesServiceNg2)); diff --git a/catalog-ui/src/app/ng2/app.module.ts b/catalog-ui/src/app/ng2/app.module.ts index 1ae2df2d82..0ff9378949 100644 --- a/catalog-ui/src/app/ng2/app.module.ts +++ b/catalog-ui/src/app/ng2/app.module.ts @@ -43,6 +43,7 @@ import { ComponentServiceFactoryNg2 } from "./services/component-services/compon import { ServiceServiceNg2 } from "./services/component-services/service.service"; import { ComponentInstanceServiceNg2 } from "./services/component-instance-services/component-instance.service"; import { WorkflowServiceNg2 } from './services/workflow.service'; +import {ToscaTypesServiceNg2} from "./services/tosca-types.service"; import { ModalService } from "./services/modal.service"; import { UiElementsModule } from "./components/ui/ui-elements.module"; import { ConnectionWizardModule } from "./pages/connection-wizard/connection-wizard.module"; @@ -73,6 +74,8 @@ import {GroupsService} from "./services/groups.service"; import {PoliciesService} from "./services/policies.service"; import {AutomatedUpgradeService} from "./pages/automated-upgrade/automated-upgrade.service"; import {AutomatedUpgradeModule} from "./pages/automated-upgrade/automated-upgrade.module"; +import {RequirementsEditorModule} from "./pages/req-and-capabilities-editor/requirements-editor/requirements-editor.module" +import {CapabilitiesEditorModule} from "./pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.module" export const upgradeAdapter = new UpgradeAdapter(forwardRef(() => AppModule)); @@ -114,12 +117,14 @@ export function configServiceFactory(config: ConfigService) { ServicePathModule, ServicePathSelectorModule, ServiceDependenciesModule, - ServiceDependenciesEditorModule + ServiceDependenciesEditorModule, + RequirementsEditorModule, + CapabilitiesEditorModule ], exports: [], entryComponents: [ // *** sdc-ui components to be used as downgraded: - // SdcUiComponents.ButtonComponent + SdcUiComponents.SvgIconComponent ], providers: [ WindowRef, @@ -143,6 +148,7 @@ export function configServiceFactory(config: ConfigService) { ServiceServiceNg2, AutomatedUpgradeService, WorkflowServiceNg2, + ToscaTypesServiceNg2, HttpService, UserService, PoliciesService, diff --git a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.html b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.html new file mode 100644 index 0000000000..c0bfc8a1a5 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.html @@ -0,0 +1,92 @@ +<div class="capability-editor"> + <form class="w-sdc-form"> + <div class="i-sdc-form-content-capability-content"> + <div class="content-row"> + <div class="i-sdc-form-item"> + <sdc-input + label="{{ 'CAP_NAME' | translate}}" + required="true" + class="i-sdc-form-input" + testId="capName" + [disabled]="isReadonly" + [(value)]="capabilityData.name" + (valueChange)="validityChanged()"> + </sdc-input> + </div> + </div> + + <div class="group-with-border"> + <div class="content-row i-sdc-form-item"> + <sdc-dropdown + label="{{ 'CAP_TYPE' | translate}}" + required="true" + class="i-sdc-form-select" + testId="capType" + [disabled]="isReadonly" + [options]="capabilityTypesMappedList" + selectedOption="{{capabilityData.type}}" + (changed)="onSelectCapabilityType($event)"> + </sdc-dropdown> + </div> + <div class="content-row i-sdc-form-item"> + <label class="i-sdc-form-label"> {{ 'CAP_DESCRIPTION' | translate}} </label> + <textarea + rows="3" + class="i-sdc-form-input description" + data-tests-id="capDesc" + disabled + value="{{capabilityData.description}}"> + </textarea> + </div> + <div class="content-row i-sdc-form-item"> + <label class="i-sdc-form-label valid-source-label"> {{ 'CAP_VALID_SOURCE' | translate}} </label> + <textarea + rows="2" + class="i-sdc-form-input" + data-tests-id="capValidSrc" + disabled + value="{{capabilityData.validSourceTypes}}"> + </textarea> + </div> + </div> + + <label class="i-sdc-form-label occurrences-label"> {{ 'REQ_CAP_OCCURRENCES' | translate}} </label> + <div class="content-row occurrences-section"> + <div class="min-occurrences-value"> + <sdc-input + label="{{ 'REQ_CAP_OCCURRENCES_MIN' | translate}}" + class="i-sdc-form-input" + testId="capOccurrencesMin" + [disabled]="isReadonly" + [(value)]="capabilityData.minOccurrences" + (valueChange)="validityChanged()" + type="number"> + </sdc-input> + </div> + <div class="sdc-input"> + <label class="sdc-input__label"> {{ 'REQ_CAP_OCCURRENCES_MAX' | translate}} </label> + <div class="max-occurrences-value"> + <sdc-checkbox + class="checkbox-label unbounded-value" + testId="capOccurrencesMaxUnbounded" + label="{{translatedUnboundTxt.toLowerCase()}}" + (checkedChange)="onUnboundedChanged()" + [checked]="isUnboundedChecked" + [disabled]="isReadonly"> + </sdc-checkbox> + + <sdc-input + *ngIf="!isUnboundedChecked" + class="i-sdc-form-input" + testId="capOccurrencesMax" + [disabled]="isReadonly" + [(value)]="capabilityData.maxOccurrences" + (valueChange)="validityChanged()" + type="number"> + </sdc-input> + </div> + </div> + </div> + </div> + </form> +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.less b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.less new file mode 100644 index 0000000000..915628117e --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.less @@ -0,0 +1,38 @@ +@import '../../../../../assets/styles/variables.less'; + +.capability-editor { + .i-sdc-form-content-capability-content { + padding: 10px 25px; + .group-with-border { + margin: 25px 0; + padding: 15px 0; + border-top: 1px solid @tlv_color_u; + border-bottom: 1px solid @tlv_color_u; + .content-row:not(:last-of-type) { + padding-bottom: 13px; + } + } + + .occurrences-label { + font-family: @font-opensans-bold; + margin-bottom: 19px; + } + .occurrences-section, /deep/ .max-occurrences-value { + display: flex; + .min-occurrences-value { + padding-right: 30px; + } + .unbounded-value { + padding-top: 7px; + padding-right: 20px; + .sdc-checkbox__label { + text-transform: capitalize; + } + } + } + textarea { + min-height: unset; + height: unset; + } + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.ts b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.ts new file mode 100644 index 0000000000..82e2e464cc --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.ts @@ -0,0 +1,73 @@ +import {Component} from '@angular/core'; +import {ServiceServiceNg2} from "app/ng2/services/component-services/service.service"; +import {Capability, CapabilityTypeModel} from 'app/models'; +import {DropdownValue} from "app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component"; +import {TranslateService} from 'app/ng2/shared/translator/translate.service'; + +@Component({ + selector: 'capabilities-editor', + templateUrl: './capabilities-editor.component.html', + styleUrls: ['./capabilities-editor.component.less'], + providers: [ServiceServiceNg2] +}) + +export class CapabilitiesEditorComponent { + input: { + capability: Capability, + capabilityTypesList: Array<CapabilityTypeModel>, + isReadonly: boolean; + validityChangedCallback: Function; + }; + capabilityData: Capability; + capabilityTypesMappedList: Array<DropdownValue>; + isUnboundedChecked: boolean; + isReadonly: boolean; + translatedUnboundTxt: string; + + constructor(private translateService: TranslateService) { + } + + ngOnInit() { + this.capabilityData = new Capability(this.input.capability); + this.translatedUnboundTxt = ''; + this.capabilityData.minOccurrences = this.capabilityData.minOccurrences || 0; + this.translateService.languageChangedObservable.subscribe(lang => { + this.translatedUnboundTxt = this.translateService.translate('REQ_CAP_OCCURRENCES_UNBOUNDED'); + this.capabilityData.maxOccurrences = this.capabilityData.maxOccurrences || this.translatedUnboundTxt; + this.isUnboundedChecked = this.capabilityData.maxOccurrences === this.translatedUnboundTxt; + }); + this.capabilityTypesMappedList = _.map(this.input.capabilityTypesList, capType => new DropdownValue(capType.toscaPresentation.type, capType.toscaPresentation.type)); + this.isReadonly = this.input.isReadonly; + this.validityChanged(); + } + + onUnboundedChanged() { + this.isUnboundedChecked = !this.isUnboundedChecked; + this.capabilityData.maxOccurrences = this.isUnboundedChecked ? this.translatedUnboundTxt : null; + this.validityChanged(); + } + + checkFormValidForSubmit() { + return this.capabilityData.name && this.capabilityData.name.length && + this.capabilityData.type && this.capabilityData.type.length && !_.isEqual(this.capabilityData.minOccurrences, "") && this.capabilityData.minOccurrences >= 0 && + ( + this.isUnboundedChecked || + (this.capabilityData.maxOccurrences && (this.capabilityData.minOccurrences <= parseInt(this.capabilityData.maxOccurrences))) + ); + } + + onSelectCapabilityType(selectedCapType: DropdownValue) { + this.capabilityData.type = selectedCapType && selectedCapType.value; + if (selectedCapType && selectedCapType.value) { + let selectedCapabilityTypeObj: CapabilityTypeModel = this.input.capabilityTypesList.find(capType => capType.toscaPresentation.type === selectedCapType.value); + this.capabilityData.description = selectedCapabilityTypeObj.toscaPresentation.description; + this.capabilityData.validSourceTypes = selectedCapabilityTypeObj.toscaPresentation.validTargetTypes; + } + this.validityChanged(); + } + + validityChanged = () => { + let validState = this.checkFormValidForSubmit(); + this.input.validityChangedCallback(validState); + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.module.ts b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.module.ts new file mode 100644 index 0000000000..1e767a5690 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.module.ts @@ -0,0 +1,28 @@ +import {NgModule} from "@angular/core"; +import {CommonModule} from "@angular/common"; +import {CapabilitiesEditorComponent} from "./capabilities-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 {TranslateModule} from 'app/ng2/shared/translator/translate.module'; +import {SdcUiComponentsModule} from "sdc-ui/lib/angular/index"; + +@NgModule({ + declarations: [ + CapabilitiesEditorComponent + ], + imports: [CommonModule, + FormsModule, + FormElementsModule, + UiElementsModule, + TranslateModule, + SdcUiComponentsModule + ], + exports: [], + entryComponents: [ + CapabilitiesEditorComponent + ], + providers: [] +}) +export class CapabilitiesEditorModule { +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.html b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.html new file mode 100644 index 0000000000..0fe326efb9 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.html @@ -0,0 +1,88 @@ +<div class="requirement-editor"> + <form class="w-sdc-form"> + <div class="i-sdc-form-content-requirement-content"> + <div class="content-row"> + <div class="i-sdc-form-item"> + <sdc-input + label="{{ 'REQ_NAME' | translate}}" + required="true" + testId="reqName" + [disabled]="isReadonly" + [(value)]="requirementData.name" + (valueChange)="validityChanged()"> + </sdc-input> + </div> + </div> + + <div class="group-with-border"> + <div class="content-row i-sdc-form-item"> + <sdc-dropdown + label="{{ 'REQ_RELATED_CAPABILITY' | translate}}" + testId="reqRelatedCapability" + required="true" + [disabled]="isReadonly" + [options]="capabilityTypesMappedList" + selectedOption="{{requirementData.capability}}" + (changed)="onCapabilityChanged($event)"> + </sdc-dropdown> + </div> + <div class="content-row i-sdc-form-item"> + <sdc-dropdown + label="{{ 'REQ_NODE' | translate}}" + testId="reqNode" + [disabled]="isReadonly" + [options]="nodeTypesMappedList" + selectedOption="{{requirementData.node}}" + (changed)="onNodeChanged($event)"> + </sdc-dropdown> + </div> + <div class="content-row i-sdc-form-item"> + <sdc-dropdown + label="{{ 'REQ_RELATIONSHIP' | translate}}" + testId="reqRelationship" + [disabled]="isReadonly" + [options]="relationshipTypesMappedList" + selectedOption="{{requirementData.relationship}}" + (changed)="onRelationshipChanged($event)"> + </sdc-dropdown> + </div> + </div> + + <label class="i-sdc-form-label occurrences-label"> {{ 'REQ_CAP_OCCURRENCES' | translate}} </label> + <div class="content-row occurrences-section"> + <div class="min-occurrences-value"> + <sdc-input + label="{{ 'REQ_CAP_OCCURRENCES_MIN' | translate}}" + testId="reqOccurrencesMin" + [disabled]="isReadonly" + [(value)]="requirementData.minOccurrences" + (valueChange)="validityChanged()" + type="number"> + </sdc-input> + </div> + <div class="sdc-input"> + <label class="sdc-input__label"> {{ 'REQ_CAP_OCCURRENCES_MAX' | translate}} </label> + <div class="max-occurrences-value"> + <sdc-checkbox + class="checkbox-label unbounded-value" + testId="reqOccurrencesMaxUnbounded" + label="{{translatedUnboundTxt.toLowerCase()}}" + (checkedChange)="onUnboundedChanged()" + [checked]="isUnboundedChecked" + [disabled]="isReadonly"> + </sdc-checkbox> + <sdc-input + *ngIf="!isUnboundedChecked" + testId="reqOccurrencesMax" + [disabled]="isReadonly" + [(value)]="requirementData.maxOccurrences" + (valueChange)="validityChanged()" + type="number"> + </sdc-input> + </div> + + </div> + </div> + </div> + </form> +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.less b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.less new file mode 100644 index 0000000000..f0ae3bf3ac --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.less @@ -0,0 +1,35 @@ +@import '../../../../../assets/styles/variables.less'; + +.requirement-editor { + .i-sdc-form-content-requirement-content { + padding: 10px 25px; + + .group-with-border { + margin: 25px 0; + padding: 15px 0; + border-top: 1px solid @tlv_color_u; + border-bottom: 1px solid @tlv_color_u; + .content-row:not(:last-of-type) { + padding-bottom: 13px; + } + } + + .occurrences-label { + font-family: @font-opensans-bold; + margin-bottom: 19px; + } + .occurrences-section, /deep/ .max-occurrences-value { + display: flex; + .min-occurrences-value { + padding-right: 30px; + } + .unbounded-value { + padding-top: 7px; + padding-right: 20px; + .sdc-checkbox__label { + text-transform: capitalize; + } + } + } + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.ts b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.ts new file mode 100644 index 0000000000..464b581997 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.ts @@ -0,0 +1,88 @@ +import {Component} from '@angular/core'; +import {ServiceServiceNg2} from "app/ng2/services/component-services/service.service"; +import {Requirement, RelationshipTypeModel, NodeTypeModel, CapabilityTypeModel} from 'app/models'; +import {TranslateService} from 'app/ng2/shared/translator/translate.service'; +import {DropdownValue} from "app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component"; + +@Component({ + selector: 'requirements-editor', + templateUrl: 'requirements-editor.component.html', + styleUrls: ['requirements-editor.component.less'], + providers: [ServiceServiceNg2, TranslateService] +}) + +export class RequirementsEditorComponent { + + input: { + requirement: Requirement, + relationshipTypesList: Array<RelationshipTypeModel>; + nodeTypesList: Array<NodeTypeModel>; + capabilityTypesList: Array<CapabilityTypeModel>; + isReadonly: boolean; + validityChangedCallback: Function; + }; + requirementData: Requirement; + capabilityTypesMappedList: Array<DropdownValue>; + relationshipTypesMappedList: Array<DropdownValue>; + nodeTypesMappedList: Array<DropdownValue>; + isUnboundedChecked: boolean; + isReadonly: boolean; + translatedUnboundTxt: string; + + constructor(private translateService: TranslateService) { + } + + ngOnInit() { + this.requirementData = new Requirement(this.input.requirement); + this.requirementData.minOccurrences = this.requirementData.minOccurrences || 0; + this.translatedUnboundTxt = ''; + this.capabilityTypesMappedList = _.map(this.input.capabilityTypesList, capType => new DropdownValue(capType.toscaPresentation.type, capType.toscaPresentation.type)); + this.relationshipTypesMappedList = _.map(this.input.relationshipTypesList, rType => new DropdownValue(rType.toscaPresentation.type, rType.toscaPresentation.type)); + this.nodeTypesMappedList = _.map(this.input.nodeTypesList, nodeType => { + return new DropdownValue( + nodeType.componentMetadataDefinition.componentMetadataDataDefinition.toscaResourceName, + nodeType.componentMetadataDefinition.componentMetadataDataDefinition.toscaResourceName) + }); + this.translateService.languageChangedObservable.subscribe(lang => { + this.translatedUnboundTxt = this.translateService.translate('REQ_CAP_OCCURRENCES_UNBOUNDED'); + this.requirementData.maxOccurrences = this.requirementData.maxOccurrences || this.translatedUnboundTxt; + this.isUnboundedChecked = this.requirementData.maxOccurrences === this.translatedUnboundTxt; + }); + this.isReadonly = this.input.isReadonly; + this.validityChanged(); + } + + onUnboundedChanged() { + this.isUnboundedChecked = !this.isUnboundedChecked; + this.requirementData.maxOccurrences = this.isUnboundedChecked ? this.translatedUnboundTxt : null; + this.validityChanged(); + } + + onCapabilityChanged(selectedCapability: DropdownValue) { + this.requirementData.capability = selectedCapability && selectedCapability.value; + this.validityChanged(); + } + + onNodeChanged(selectedNode: DropdownValue) { + this.requirementData.node = selectedNode && selectedNode.value; + } + + onRelationshipChanged(selectedRelationship: DropdownValue) { + this.requirementData.relationship = selectedRelationship && selectedRelationship.value; + } + + checkFormValidForSubmit() { + return this.requirementData.name && this.requirementData.name.length && + this.requirementData.capability && this.requirementData.capability.length && !_.isEqual(this.requirementData.minOccurrences, "") && this.requirementData.minOccurrences >= 0 && + ( + this.isUnboundedChecked || + (this.requirementData.maxOccurrences && (this.requirementData.minOccurrences <= parseInt(this.requirementData.maxOccurrences))) + ); + } + + validityChanged = () => { + let validState = this.checkFormValidForSubmit(); + this.input.validityChangedCallback(validState); + } + +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.module.ts b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.module.ts new file mode 100644 index 0000000000..1be8be51af --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.module.ts @@ -0,0 +1,26 @@ +import {NgModule} from "@angular/core"; +import {CommonModule} from "@angular/common"; +import {RequirementsEditorComponent} from "./requirements-editor.component"; +import {FormsModule} from "@angular/forms"; +import {FormElementsModule} from "../../../components/ui/form-components/form-elements.module"; +import {TranslateModule} from 'app/ng2/shared/translator/translate.module'; +import {SdcUiComponentsModule} from "sdc-ui/lib/angular/index"; + +@NgModule({ + declarations: [ + RequirementsEditorComponent + ], + imports: [CommonModule, + FormsModule, + FormElementsModule, + TranslateModule, + SdcUiComponentsModule + ], + exports: [], + entryComponents: [ + RequirementsEditorComponent + ], + providers: [] +}) +export class RequirementsEditorModule { +}
\ 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 41bfc4e088..46dfe01992 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,7 +25,8 @@ 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} from "app/models"; + PropertyBEModel, OperationModel, BEOperationModel, Capability, Requirement +} from "app/models"; import {downgradeInjectable} from '@angular/upgrade/static'; import {COMPONENT_FIELDS, CommonUtils, SERVICE_FIELDS} from "app/utils"; import {ComponentGenericResponse} from "../responses/component-generic-response"; @@ -207,6 +208,68 @@ export class ComponentServiceNg2 { return this.getComponentDataByFieldsName(componentType, componentId, [COMPONENT_FIELDS.COMPONENT_REQUIREMENTS, COMPONENT_FIELDS.COMPONENT_CAPABILITIES]); } + createCapability(component: Component, capabilityData: Capability): Observable<Array<Capability>> { + let capBEObj = { + 'capabilities': { + [capabilityData.type]: [capabilityData] + } + }; + return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/capabilities', capBEObj) + .map((res: Response) => { + return res.json(); + }); + } + + updateCapability(component: Component, capabilityData: Capability): Observable<Array<Capability>> { + let capBEObj = { + 'capabilities': { + [capabilityData.type]: [capabilityData] + } + }; + return this.http.put(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/capabilities', capBEObj) + .map((res: Response) => { + return res.json(); + }); + } + + deleteCapability(component: Component, capId: string): Observable<Capability> { + return this.http.delete(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/capabilities/' + capId) + .map((res: Response) => { + return res.json(); + }); + } + + createRequirement(component: Component, requirementData: Requirement): Observable<any> { + let reqBEObj = { + 'requirements': { + [requirementData.capability]: [requirementData] + } + }; + return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/requirements', reqBEObj) + .map((res: Response) => { + return res.json(); + }); + } + + updateRequirement(component: Component, requirementData: Requirement): Observable<any> { + let reqBEObj = { + 'requirements': { + [requirementData.capability]: [requirementData] + } + }; + return this.http.put(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/requirements', reqBEObj) + .map((res: Response) => { + return res.json(); + }); + } + + deleteRequirement(component: Component, reqId: string): Observable<Requirement> { + return this.http.delete(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/requirements/' + reqId) + .map((res: Response) => { + return res.json(); + }); + } + getDeploymentGraphData(component:Component):Observable<ComponentGenericResponse> { return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INSTANCES_RELATION, COMPONENT_FIELDS.COMPONENT_INSTANCES, COMPONENT_FIELDS.COMPONENT_GROUPS]); } diff --git a/catalog-ui/src/app/ng2/services/tosca-types.service.ts b/catalog-ui/src/app/ng2/services/tosca-types.service.ts new file mode 100644 index 0000000000..66826c0fef --- /dev/null +++ b/catalog-ui/src/app/ng2/services/tosca-types.service.ts @@ -0,0 +1,55 @@ +/*! + * 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 {Injectable, Inject} from '@angular/core'; +import {Observable} from 'rxjs/Observable'; +import {HttpService} from './http.service'; +import {SdcConfigToken, ISdcConfig} from "../config/sdc-config.config"; +import {CapabilityTypesMap, NodeTypesMap, RelationshipTypesMap} from "app/models"; +import {Response} from '@angular/http'; + +declare var angular: angular.IAngularStatic; + +@Injectable() +export class ToscaTypesServiceNg2 { + + protected baseUrl; + + constructor(protected http: HttpService, @Inject(SdcConfigToken) sdcConfig: ISdcConfig) { + this.baseUrl = sdcConfig.api.root + sdcConfig.api.component_api_root; + } + + fetchRelationshipTypes(): Observable<RelationshipTypesMap> { + return this.http.get(this.baseUrl + 'relationshipTypes') + .map((res: Response) => { + return res.json(); + }); + } + + fetchNodeTypes(): Observable<NodeTypesMap> { + return this.http.get(this.baseUrl + 'nodeTypes') + .map((res: Response) => { + return res.json(); + }); + } + + fetchCapabilityTypes(): Observable<CapabilityTypesMap> { + return this.http.get(this.baseUrl + 'capabilityTypes') + .map((res: Response) => { + return res.json(); + }); + } +} diff --git a/catalog-ui/src/app/utils/constants.ts b/catalog-ui/src/app/utils/constants.ts index 379f2f17c5..104b5dcb14 100644 --- a/catalog-ui/src/app/utils/constants.ts +++ b/catalog-ui/src/app/utils/constants.ts @@ -264,6 +264,7 @@ export class States { public static WORKSPACE_DISTRIBUTION = 'workspace.distribution'; public static WORKSPACE_PROPERTIES_ASSIGNMENT = 'workspace.properties_assignment'; public static WORKSPACE_REQUIREMENTS_AND_CAPABILITIES = 'workspace.reqAndCap'; + public static WORKSPACE_REQUIREMENTS_AND_CAPABILITIES_EDITABLE = 'workspace.reqAndCapEditable'; public static WORKSPACE_PLUGINS = 'workspace.plugins'; public static WORKSPACE_NG2 = 'workspace.ng2'; } diff --git a/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-editable-view.html b/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-editable-view.html new file mode 100644 index 0000000000..14bc49e28b --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-editable-view.html @@ -0,0 +1,197 @@ +<!-- + ~ 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. +--> + +<div class="workspace-req-and-cap-editable"> + <loader data-display="isLoading"></loader> + + <div class="tabs-header"> + <div class="req-and-cap-tabs"> + <div data-tests-id="req-tab" data-ng-click="onSwitchTab()" class="tab" + data-ng-class="{'selected':mode=='requirements'}">Requirements + </div> + <div data-tests-id="cap-tab" data-ng-click="onSwitchTab()" class="tab" + data-ng-class="{'selected':mode=='capabilities'}">Capabilities + </div> + </div> + <div class="buttons-in-right" data-ng-if="!isListEmpty()"> + <div class="search"> + <input id="search-box" data-ng-if="filter.show" data-tests-id="search-box" placeholder="Search" + data-ng-model-options="{debounce: 200}" data-ng-model="filter.txt" data-ng-change="onFilter()"/> + <div class="search-icon-container" data-tests-id="search-icon"> + <svg-icon + class="hand" + [name]="'search-o'" + [mode]="'primary'" + [size]="'small'" + [clickable]="'true'" + data-ng-click="onSearchIconClick()"> + </svg-icon> + </div> + </div> + <div class="add-button-icon-and-label" data-ng-if="isEditable" data-ng-click="onAddBtnClicked()" + data-ng-class="{'disabled': isReadonly()}" data-tests-id="add-button"> + <svg-icon + name="plus" + mode="primary" + size="small" + clickable="true" + [disabled]="isReadonly()" + labelPlacement="top"> + </svg-icon> + <span class="icon-label-txt">{{mode === 'requirements' ? 'Add Requirement' : 'Add Capability'}}</span> + </div> + </div> + </div> + + <div class="empty-list-container" data-ng-if="isListEmpty() && !isLoading" data-tests-id="empty-list-container"> + <div class="empty-list-add-btn add-button-icon-and-label" data-ng-class="{'disabled': isReadonly()}" + data-ng-click="onAddBtnClicked()" data-tests-id="empty-list-add-btn"> + <svg-icon + name="plus-circle" + mode="primary" + size="x_large" + clickable="true" + [disabled]="isReadonly()"> + </svg-icon> + <div class="icon-label-txt">{{mode === 'requirements' ? 'Add Requirement' : 'Add Capability'}}</div> + </div> + </div> + + <div class="table-container-flex requirements-table" data-ng-if="mode=='requirements' && !isListEmpty()"> + <div class="table" data-ng-class="{'view-mode': isViewMode()}" data-tests-id="requirement-table"> + <div class="head flex-container"> + <div data-ng-repeat="header in editableRequirementsTableHeadersList track by $index" + data-ng-click="sort(header.property, requirementsSortTableDefined)" + class="table-header head-row hand flex-item {{header.property}}" + data-tests-id="table-header-{{header.property}}"> + {{header.title}} + <span data-ng-if="requirementsSortTableDefined.sortByField === header.property" + class="table-header-sort-arrow" data-tests-id="table-header-sort-arrow" + data-ng-class="{'down': requirementsSortTableDefined.reverse, 'up':!requirementsSortTableDefined.reverse}"> </span> + </div> + </div> + + <div class="body"> + <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="editable-table-data"> + <div data-ng-if="filteredRequirementsList.length === 0" class="no-row-text" + data-tests-id="no-rows-in-table"> + There are no requirements to display + + </div> + <div data-ng-repeat="req in filteredRequirementsList | orderBy:requirementsSortTableDefined.sortByField:requirementsSortTableDefined.reverse track by $index" + data-tests-id="reqRow"> + <div class="flex-container data-row" data-ng-class="{'editable-row': req.isCreatedManually}" + data-ng-click="req.isCreatedManually && onEditRequirement(req)"> + <div class="table-col-general flex-item text ellipsis-text" tooltips + tooltip-content="{{req.name}}"> + <span data-tests-id="{{req.name}}">{{req.name}}</span> + </div> + <div class="table-col-general flex-item text ellipsis-text" tooltips + tooltip-content="{{req.capability}}"> + <span data-tests-id="{{req.capability}}">{{req.capability && cutToscaTypePrefix(req.capability, 'capabilities.')}}</span> + </div> + <div class="table-col-general flex-item text ellipsis-text" tooltips + tooltip-content="{{req.node}}"> + <span data-tests-id="{{req.node}}">{{req.node && cutToscaTypePrefix(req.node, "nodes.")}}</span> + </div> + <div class="table-col-general flex-item text ellipsis-text" tooltips + tooltip-content="{{req.relationship}}"> + <span data-tests-id="{{req.relationship}}">{{req.relationship && cutToscaTypePrefix(req.relationship, "relationships.")}}</span> + </div> + <div class="table-col-general flex-item text ellipsis-text occurrences-col" tooltips + tooltip-content="{{req.minOccurrences}} - {{req.maxOccurrences}}"> + <span data-tests-id="{{req.minOccurrences}} - {{req.maxOccurrences}}">{{req.minOccurrences}} - {{req.maxOccurrences}}</span> + </div> + <div class="table-col-general flex-item text other-col" data-tests-id="delete-req" + data-ng-class="{'disabled': isReadonly()}"> + <svg-icon name="trash-o" class="trash-icon" size="small" + data-ng-if="req.isCreatedManually && !isReadonly()" + data-ng-click="onDeleteReq($event, req)"></svg-icon> + </div> + </div> + </div> + </perfect-scrollbar> + </div> + + </div> + </div> + <div class="table-container-flex capabilities-table" data-ng-if="mode=='capabilities' && !isListEmpty()" + data-tests-id="capabilities-table"> + <div class="table" data-ng-class="{'view-mode': isViewMode()}"> + <div class="head flex-container"> + <div data-ng-repeat="header in editableCapabilitiesTableHeadersList track by $index" + data-ng-click="sort(header.property, capabilitiesSortTableDefined)" + class="table-header head-row hand flex-item {{header.property}}" + data-tests-id="header-{{header.property}}"> + {{header.title}} + <span data-ng-if="capabilitiesSortTableDefined.sortByField === header.property" + class="table-header-sort-arrow" data-tests-id=="table-header-sort-arrow" + data-ng-class="{'down': capabilitiesSortTableDefined.reverse, 'up':!capabilitiesSortTableDefined.reverse}"> </span> + </div> + </div> + + <div class="body"> + <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="editable-table-data"> + <div data-ng-if="filteredCapabilitiesList.length === 0" class="no-row-text" + data-tests-id="no-rows-in-table"> + There are no capabilities to display + + </div> + <div data-ng-repeat="capability in filteredCapabilitiesList | orderBy:capabilitiesSortTableDefined.sortByField:capabilitiesSortTableDefined.reverse track by $index" + class="flex-container data-row" + data-ng-class="{'selected': capability.selected, 'editable-row': capability.isCreatedManually}" + data-ng-click="capability.isCreatedManually && onEditCapability(capability)" + data-tests-id="capabilities-table-row"> + + <div class="table-col-general flex-item text ellipsis-text" tooltips + tooltip-content="{{capability.name}}"> + <span data-tests-id="{{capability.name}}">{{capability.name}}</span> + </div> + <div class="table-col-general flex-item text ellipsis-text" tooltips + tooltip-content="{{capability.type}}"> + <span data-tests-id="{{capability.type}}">{{capability.type && cutToscaTypePrefix(capability.type, 'capabilities.')}}</span> + </div> + + <div class="table-col-general flex-item text description-col"> + <div data-tests-id="{{capability.description}}" class="multiline-ellipsis" + ellipsis="capability.description" max-chars="60">{{capability.description}} + </div> + </div> + + <div class="table-col-general flex-item text ellipsis-text" tooltips + tooltip-content="{{capability.validSourceTypes.join(',')}}"> + <span data-tests-id="{{capability.validSourceTypes.join(',')}}">{{capability.validSourceTypes.join(',')}}</span> + </div> + + <div class="table-col-general flex-item text ellipsis-text occurrences-col" tooltips + tooltip-content="{{capability.minOccurrences}} - {{capability.maxOccurrences}}"> + <span data-tests-id="{{capability.minOccurrences}} - {{capability.maxOccurrences}}">{{capability.minOccurrences}} - {{capability.maxOccurrences}}</span> + </div> + + <div class="table-col-general flex-item text other-col" data-tests-id="delete-cap" + data-ng-class="{'disabled': isReadonly()}"> + <svg-icon name="trash-o" class="trash-icon" size="small" + data-ng-if="capability.isCreatedManually && !isReadonly()" + data-ng-click="onDeleteCap($event, capability)"></svg-icon> + </div> + </div> + </perfect-scrollbar> + </div> + + </div> + </div> +</div> + diff --git a/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view-model.ts index 6eaae44eb2..165578d008 100644 --- a/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view-model.ts +++ b/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view-model.ts @@ -23,33 +23,82 @@ */ 'use strict'; import * as _ from "lodash"; +import {ComponentRef} from '@angular/core'; import {IWorkspaceViewModelScope} from "app/view-models/workspace/workspace-view-model"; -import {ModalsHandler} from "app/utils"; -import {Capability, PropertyModel, Requirement} from "app/models"; -import {ComponentGenericResponse} from "../../../../ng2/services/responses/component-generic-response"; -import {ComponentServiceNg2} from "../../../../ng2/services/component-services/component.service"; +import {ModalsHandler, ResourceType} from "app/utils"; +import {ComponentType} from "app/utils/constants"; +import { + Capability, PropertyModel, Requirement, Resource, + RelationshipTypesMap, NodeTypesMap, CapabilityTypesMap +} from "app/models"; +import {ComponentGenericResponse} from "app/ng2/services/responses/component-generic-response"; +import {ComponentServiceNg2} from "app/ng2/services/component-services/component.service"; +import {ToscaTypesServiceNg2} from "app/ng2/services/tosca-types.service"; +import {ModalComponent} from 'app/ng2/components/ui/modal/modal.component'; +import {ModalService} from 'app/ng2/services/modal.service'; +import {RequirementsEditorComponent} from 'app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component'; +import {CapabilitiesEditorComponent} from 'app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component'; +import {ModalService as ModalServiceSdcUI} from "sdc-ui/lib/angular/modals/modal.service"; +import {IModalConfig} from "sdc-ui/lib/angular/modals/models/modal-config"; +import {ModalButtonComponent} from "sdc-ui/lib/angular/components"; export class SortTableDefined { reverse:boolean; sortByField:string; } +class RequirementUI extends Requirement { + isCreatedManually: boolean; + + constructor(input: Requirement, componentUniqueId: string) { + super(input); + this.isCreatedManually = input.ownerId === componentUniqueId; + } +} +class CapabilityUI extends Capability { + isCreatedManually: boolean; + + constructor(input: Capability, componentUniqueId: string) { + super(input); + this.isCreatedManually = input.ownerId === componentUniqueId; + } +} + interface IReqAndCapabilitiesViewModelScope extends IWorkspaceViewModelScope { requirementsTableHeadersList:Array<any>; + editableRequirementsTableHeadersList: Array<any>; capabilitiesTableHeadersList:Array<any>; + editableCapabilitiesTableHeadersList: Array<any>; capabilityPropertiesTableHeadersList:Array<any>; requirementsSortTableDefined:SortTableDefined; capabilitiesSortTableDefined:SortTableDefined; propertiesSortTableDefined:SortTableDefined; - requirements:Array<Requirement>; - capabilities:Array<Capability>; + requirements: Array<RequirementUI>; + filteredRequirementsList: Array<RequirementUI>; + capabilities: Array<CapabilityUI>; + filteredCapabilitiesList: Array<CapabilityUI>; mode:string; filteredProperties:Array<Array<PropertyModel>>; searchText:string; + isEditable: boolean; + modalInstance: ComponentRef<ModalComponent>; + filter: {txt: string; show: boolean}; sort(sortBy:string, sortByTableDefined:SortTableDefined):void; + sortByIsCreatedManually(arrToSort: Array<RequirementUI|CapabilityUI>): Array<any>; updateProperty(property:PropertyModel, indexInFilteredProperties:number):void; allCapabilitiesSelected(selected:boolean):void; + onAddBtnClicked(): void; + onEditRequirement(req: RequirementUI): void; + onEditCapability(cap: CapabilityUI): void; + onDeleteReq(event, req: RequirementUI): void; + onDeleteCap(event, cap: CapabilityUI): void; + onFilter(): void; + isListEmpty(): boolean; + onSwitchTab(): void; + onSearchIconClick(): void; + cutToscaTypePrefix(valToCut: string, textToStartCut: string): string; + isReadonly(): boolean; } export class ReqAndCapabilitiesViewModel { @@ -58,33 +107,37 @@ export class ReqAndCapabilitiesViewModel { '$scope', '$filter', 'ModalsHandler', - 'ComponentServiceNg2' + 'ComponentServiceNg2', + 'ToscaTypesServiceNg2', + 'ModalServiceNg2', + 'ModalServiceSdcUI' ]; constructor(private $scope:IReqAndCapabilitiesViewModelScope, private $filter:ng.IFilterService, private ModalsHandler:ModalsHandler, - private ComponentServiceNg2: ComponentServiceNg2) { + private ComponentServiceNg2: ComponentServiceNg2, + private ToscaTypesServiceNg2: ToscaTypesServiceNg2, + private ModalServiceNg2: ModalService, + private ModalServiceSdcUI: ModalServiceSdcUI) { this.initCapabilitiesAndRequirements(); + this.fetchCapabilitiesRelatedData(); } private initCapabilitiesAndRequirements = (): void => { - if(!this.$scope.component.capabilities || !this.$scope.component.requirements) { - this.$scope.isLoading = true; - this.ComponentServiceNg2.getCapabilitiesAndRequirements(this.$scope.component.componentType, this.$scope.component.uniqueId).subscribe((response:ComponentGenericResponse) => { - this.$scope.component.capabilities = response.capabilities; - this.$scope.component.requirements = response.requirements; - this.initScope(); - this.$scope.isLoading = false; - }, () => { - this.$scope.isLoading = false; - }); - } else { + this.$scope.isEditable = this.getIsEditableByComponentType(); + this.$scope.isLoading = true; + this.ComponentServiceNg2.getCapabilitiesAndRequirements(this.$scope.component.componentType, this.$scope.component.uniqueId).subscribe((response: ComponentGenericResponse) => { + this.$scope.component.capabilities = response.capabilities; + this.$scope.component.requirements = response.requirements; this.initScope(); - } + this.$scope.isLoading = false; + }, () => { + this.$scope.isLoading = false; + }); } @@ -98,15 +151,18 @@ export class ReqAndCapabilitiesViewModel { }); }; - private initScope = ():void => { - + private initScope = (currentMode = 'requirements'): void => { + this.$scope.isReadonly = (): boolean => { + return this.$scope.isViewMode() || !this.$scope.isDesigner(); + }; + this.$scope.filter = {txt: '', show: false}; this.$scope.requirementsSortTableDefined = { reverse: false, - sortByField: 'name' + sortByField: this.$scope.isEditable ? 'other' : 'name' }; this.$scope.capabilitiesSortTableDefined = { reverse: false, - sortByField: 'name' + sortByField: this.$scope.isEditable ? 'other' : 'name' }; this.$scope.propertiesSortTableDefined = { reverse: false, @@ -129,6 +185,22 @@ export class ReqAndCapabilitiesViewModel { {title: 'Valid Source', property: ''}, {title: 'Occurrences', property: ''} ]; + this.$scope.editableRequirementsTableHeadersList = [ + {title: 'Name', property: 'name'}, + {title: 'Capability', property: 'capability'}, + {title: 'Node', property: 'node'}, + {title: 'Relationship', property: 'relationship'}, + {title: 'Occurrences', property: 'occurrences'}, + {title: '●●●', property: 'other'} + ]; + this.$scope.editableCapabilitiesTableHeadersList = [ + {title: 'Name', property: 'name'}, + {title: 'Type', property: 'type'}, + {title: 'Description', property: 'description'}, + {title: 'Valid Sources', property: 'valid-sources'}, + {title: 'Occurrences', property: 'occurrences'}, + {title: '●●●', property: 'other'} + ]; this.$scope.capabilityPropertiesTableHeadersList = [ {title: 'Name', property: 'name'}, {title: 'Type', property: 'type'}, @@ -137,17 +209,26 @@ export class ReqAndCapabilitiesViewModel { ]; this.$scope.filteredProperties = []; - this.$scope.mode = 'requirements'; + this.$scope.mode = currentMode; this.$scope.requirements = []; _.forEach(this.$scope.component.requirements, (req:Array<Requirement>, capName)=> { - this.$scope.requirements = this.$scope.requirements.concat(req); + let reqUIList: Array<RequirementUI> = _.map(req, reqObj => new RequirementUI(reqObj, this.$scope.component.uniqueId)); + this.$scope.requirements = this.$scope.requirements.concat(reqUIList); }); + this.$scope.filteredRequirementsList = this.$scope.requirements; this.$scope.capabilities = []; _.forEach(this.$scope.component.capabilities, (cap:Array<Capability>, capName)=> { - this.$scope.capabilities = this.$scope.capabilities.concat(cap); + let capUIList: Array<CapabilityUI> = _.map(cap, capObj => new CapabilityUI(capObj, this.$scope.component.uniqueId)); + this.$scope.capabilities = this.$scope.capabilities.concat(capUIList); }); + this.$scope.sortByIsCreatedManually = (arrToSort: Array<RequirementUI|CapabilityUI>): Array<any> => { + return arrToSort.sort((elem1: RequirementUI|CapabilityUI, elem2: RequirementUI|CapabilityUI) => +elem2.isCreatedManually - (+elem1.isCreatedManually)); + }; + this.$scope.filteredCapabilitiesList = this.$scope.sortByIsCreatedManually(this.$scope.capabilities); + this.$scope.filteredRequirementsList = this.$scope.sortByIsCreatedManually(this.$scope.requirements); + this.$scope.sort = (sortBy:string, sortByTableDefined:SortTableDefined):void => { sortByTableDefined.reverse = (sortByTableDefined.sortByField === sortBy) ? !sortByTableDefined.reverse : false; sortByTableDefined.sortByField = sortBy; @@ -162,6 +243,226 @@ export class ReqAndCapabilitiesViewModel { cap.selected = selected; }); }; + this.$scope.onAddBtnClicked = (): void => { + switch (this.$scope.mode) { + case 'requirements': + this.openRequirementsModal(); + break; + case 'capabilities': + this.openCapabilitiesModal(); + break; + } + }; + this.$scope.onEditRequirement = (req: RequirementUI): void => { + this.openRequirementsModal(req); + }; + this.$scope.onEditCapability = (cap: CapabilityUI): void => { + this.openCapabilitiesModal(cap); + }; + this.$scope.onDeleteReq = (event: Event, req: RequirementUI): void => { + event.stopPropagation(); + this.ModalServiceSdcUI.openAlertModal('Delete Requirement', + `Are you sure you want to delete requirement: ${req.name}?`, 'OK', () => this.deleteRequirement(req), 'Cancel'); + }; + this.$scope.onDeleteCap = (event: Event, cap: CapabilityUI): void => { + event.stopPropagation(); + this.ModalServiceSdcUI.openAlertModal('Delete Capability', + `Are you sure you want to delete capability: ${cap.name}?`, 'OK', () => this.deleteCapability(cap), 'Cancel'); + }; + this.$scope.onSearchIconClick = (): void => { + this.$scope.filter.show = !!this.$scope.filter.txt || !this.$scope.filter.show; + }; + this.$scope.onFilter = (): void => { + switch (this.$scope.mode) { + case 'requirements': + this.$scope.filteredRequirementsList = _.filter(this.$scope.requirements, req => req.name.includes(this.$scope.filter.txt)); + break; + case 'capabilities': + this.$scope.filteredCapabilitiesList = _.filter(this.$scope.capabilities, cap => cap.name.includes(this.$scope.filter.txt)); + break; + } + }; + this.$scope.isListEmpty = (): boolean => { + switch (this.$scope.mode) { + case 'requirements': + return this.$scope.requirements.length === 0; + case 'capabilities': + return this.$scope.capabilities.length === 0; + } + }; + this.$scope.onSwitchTab = (): void => { + this.$scope.mode = this.$scope.mode === 'requirements' ? 'capabilities' : 'requirements'; + this.$scope.filter.txt = ''; + this.$scope.filter.show = false; + this.$scope.filteredRequirementsList = this.$scope.requirements; + this.$scope.filteredCapabilitiesList = this.$scope.capabilities; + }; + this.$scope.cutToscaTypePrefix = (valToCut: string, textToStartCut: string): string => { + let index = valToCut.indexOf(textToStartCut); + return index !== -1 ? valToCut.substr(index + textToStartCut.length) : valToCut; + }; + }; + + private getIsEditableByComponentType() { + if (this.$scope.componentType === ComponentType.SERVICE) { + return true; + } + if (this.$scope.component.isResource()) { + let componentAsResource: Resource = <Resource>this.$scope.component; + return componentAsResource.resourceType === ResourceType.VF || + componentAsResource.resourceType === ResourceType.PNF; + } + return false; + }; + + private fetchCapabilitiesRelatedData() { + if (this.$scope.isEditable) { + this.$scope.capabilityTypesList = []; + this.ToscaTypesServiceNg2.fetchCapabilityTypes().subscribe((result: CapabilityTypesMap) => { + _.forEach(result, capabilityType => this.$scope.capabilityTypesList.push(capabilityType)); + }); + this.$scope.nodeTypesList = []; + this.ToscaTypesServiceNg2.fetchNodeTypes().subscribe((result: NodeTypesMap) => { + _.forEach(result, nodeType => this.$scope.nodeTypesList.push(nodeType)); + }); + this.$scope.relationshipTypesList = []; + this.ToscaTypesServiceNg2.fetchRelationshipTypes().subscribe((result: RelationshipTypesMap) => { + _.forEach(result, relshipType => this.$scope.relationshipTypesList.push(relshipType)); + }); + } + } + + private openRequirementsModal(req?: RequirementUI) { + let modalConfig: IModalConfig = { + size: 'md', + title: (req ? 'Update' : 'Add') + ' Requirement', + type: 'custom', + buttons: [ + { + id: 'saveButton', + text: (req ? 'Update' : 'Create'), + size: "'x-small'", + callback: () => this.createOrUpdateRequirement(), + closeModal: true + }, + {text: "Cancel", size: "'x-small'", closeModal: true}] + }; + let modalInputs = { + requirement: req, + relationshipTypesList: this.$scope.relationshipTypesList, + nodeTypesList: this.$scope.nodeTypesList, + capabilityTypesList: this.$scope.capabilityTypesList, + isReadonly: this.$scope.isViewMode() || !this.$scope.isDesigner(), + validityChangedCallback: this.getDisabled + }; + + this.ModalServiceSdcUI.openCustomModal(modalConfig, RequirementsEditorComponent, {input: modalInputs}); + } + + private openCapabilitiesModal(cap?: CapabilityUI) { + let modalConfig: IModalConfig = { + size: 'md', + title: (cap ? 'Update' : 'Add') + ' Capability', + type: 'custom', + buttons: [ + { + id: 'saveButton', + text: (cap ? 'Update' : 'Create'), + size: "'x-small'", + callback: () => this.createOrUpdateCapability(), + closeModal: true + }, + {text: "Cancel", size: "'x-small'", closeModal: true}] + }; + let modalInputs = { + capability: cap, + capabilityTypesList: this.$scope.capabilityTypesList, + isReadonly: this.$scope.isViewMode() || !this.$scope.isDesigner(), + validityChangedCallback: this.getDisabled + }; + + this.ModalServiceSdcUI.openCustomModal(modalConfig, CapabilitiesEditorComponent, {input: modalInputs}); + } + + getDisabled = (shouldEnable: boolean): void => { + let saveButton: ModalButtonComponent = this.ModalServiceSdcUI.getCurrentInstance().getButtonById('saveButton'); + saveButton.disabled = this.$scope.isViewMode() || !this.$scope.isDesigner() || !shouldEnable; + }; + + private createOrUpdateRequirement() { + let requirement = this.ModalServiceSdcUI.getCurrentInstance().innerModalContent.instance.requirementData; + this.$scope.isLoading = true; + if (!requirement.uniqueId) { + this.ComponentServiceNg2.createRequirement(this.$scope.component, requirement).subscribe(result => { + this.$scope.requirements.unshift(new RequirementUI(result[0], this.$scope.component.uniqueId)); + this.$scope.isLoading = false; + }, () => { + this.$scope.isLoading = false; + }); + } + else { + this.ComponentServiceNg2.updateRequirement(this.$scope.component, requirement).subscribe(result => { + let index = this.$scope.requirements.findIndex(req => result[0].uniqueId === req.uniqueId); + this.$scope.requirements[index] = new RequirementUI(result[0], this.$scope.component.uniqueId); + this.$scope.isLoading = false; + this.$scope.$apply(); + }, () => { + this.$scope.isLoading = false; + }); + } + } + + private createOrUpdateCapability() { + let capability = this.ModalServiceSdcUI.getCurrentInstance().innerModalContent.instance.capabilityData; + this.$scope.isLoading = true; + if (!capability.uniqueId) { + this.ComponentServiceNg2.createCapability(this.$scope.component, capability).subscribe(result => { + this.$scope.capabilities.unshift(new CapabilityUI(result[0], this.$scope.component.uniqueId)); + this.$scope.isLoading = false; + }, () => { + this.$scope.isLoading = false; + }); + } + else { + this.ComponentServiceNg2.updateCapability(this.$scope.component, capability).subscribe(result => { + let index = this.$scope.capabilities.findIndex(cap => result[0].uniqueId === cap.uniqueId); + this.$scope.capabilities[index] = new CapabilityUI(result[0], this.$scope.component.uniqueId); + this.$scope.isLoading = false; + this.$scope.$apply(); + }, () => { + this.$scope.isLoading = false; + }); + } + } + + private deleteRequirement(req) { + this.$scope.isLoading = true; + this.ComponentServiceNg2.deleteRequirement(this.$scope.component, req.uniqueId).subscribe(() => { + this.ComponentServiceNg2.getCapabilitiesAndRequirements(this.$scope.componentType, this.$scope.component.uniqueId).subscribe(response => { + this.$scope.component.requirements = response.requirements; + this.initScope('requirements'); + this.$scope.isLoading = false; + }, () => { + this.$scope.isLoading = false; + }); + }, () => { + this.$scope.isLoading = false; + }); + } + + private deleteCapability(cap) { + this.$scope.isLoading = true; + this.ComponentServiceNg2.deleteCapability(this.$scope.component, cap.uniqueId).subscribe(() => { + this.ComponentServiceNg2.getCapabilitiesAndRequirements(this.$scope.componentType, this.$scope.component.uniqueId).subscribe(response => { + this.$scope.component.capabilities = response.capabilities; + this.initScope('capabilities'); + this.$scope.isLoading = false; + }, () => { + this.$scope.isLoading = false; + }); + }, () => { + this.$scope.isLoading = false; + }); } } diff --git a/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities.less b/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities.less index 9b52fad411..fa6623f089 100644 --- a/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities.less +++ b/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities.less @@ -65,13 +65,14 @@ } } } - .expand-collapse-buttons{ + .add-button { + color: @main_color_a; + } + .add-button, .expand-collapse-buttons { float: right; - width: 44px; margin-left: 11px; margin-top: 10px; - span{ - vertical-align: bottom; + &, span { .hand; } } @@ -108,6 +109,28 @@ white-space: nowrap; } + .editable-table-data { + max-height: 430px; + } + + .data-row { + &:not(.editable-row) { + background: @tlv_color_t; + color: @main_color_n; + } + &.editable-row { + cursor: pointer; + } + .sprite-new.delete-icon { + visibility: hidden; + } + &:hover { + .sprite-new.delete-icon { + visibility: visible; + } + } + } + &.requirements-table{ border-top: 4px solid @main_color_a; .flex-item:nth-child(1) { @@ -194,3 +217,165 @@ } } + +.workspace-req-and-cap-editable { + .tabs-header { + display: flex; + justify-content: space-between; + border-bottom: 1px solid @main_color_o; + .req-and-cap-tabs { + display: flex; + .tab { + font-family: @font-opensans-regular; + font-size: 22px; + padding: 5px; + .hand; + &:first-of-type { + margin-right: 35px; + } + &.selected { + color: @main_color_a; + border-bottom: 2px solid @main_color_a; + } + } + } + .buttons-in-right { + display: flex; + .search { + display: flex; + height: min-content; + margin-top: 10px; + padding-right: 11px; + border-right: 1px solid @main_color_o; + #search-box { + border: none; + border-bottom: 1px solid @main_color_o; + text-indent: 10px; + &:focus { + outline: none; + } + } + .search-icon-container { + margin-top: 3px; + padding-top: 4px; + } + + } + .add-button-icon-and-label { + font-size: 14px; + margin-left: 11px; + margin-top: 10px; + padding-top: 5px; + /deep/ svg-icon { + vertical-align: bottom; + } + &:hover { + &:not(.disabled) { + cursor: pointer; + color: @sdcui_color_light-blue; + } + } + } + } + } + .add-button-icon-and-label { + .icon-label-txt { + text-transform: uppercase; + font-family: @font-opensans-medium; + color: @main_color_a; + &:hover { + &:not(.disabled) { + color: @sdcui_color_light-blue; + } + } + } + } + .empty-list-container { + width: 100%; + display: flex; + justify-content: center; + + .empty-list-add-btn { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + border: 1px solid @main_color_o; + margin-top: 50px; + height: 229px; + width: 480px; + &.disabled { + pointer-events: none; + } + &:hover { + &:not(.disabled) { + border: 1px solid @main_color_a; + cursor: pointer; + } + } + .icon-label-txt { + margin-top: 15px; + font-size: 16px; + } + } + } + .table-container-flex .table .head .head-row { + text-align: left; + &.description { + flex: 2; + } + &.other { + flex: 0.25; + text-align: center; + } + &.occurrences { + flex: 0.75; + } + } + .data-row { + .ellipsis-text { + overflow: hidden; + text-overflow: ellipsis; + } + &:not(.editable-row) { + background: @tlv_color_t; + cursor: default; + color: @main_color_n; + } + &.editable-row { + cursor: pointer; + .table-col-general:hover { + color: @main_color_b; + } + } + .description-col { + flex: 2; + } + .occurrences-col { + flex: 0.75; + } + .other-col { + display: flex; + justify-content: center; + align-items: center; + flex: 0.25; + .trash-icon { + visibility: hidden; + } + } + &:hover { + .trash-icon { + visibility: visible; + } + } + .multiline-ellipsis { + line-height: 1.5em; + padding: 1px 0 1px 0; + /deep/ .ellipsis-directive-more-less { + float: none; + margin-left: 5px; + color: @main_color_a; + } + } + } +} diff --git a/catalog-ui/src/app/view-models/workspace/workspace-view-model.ts b/catalog-ui/src/app/view-models/workspace/workspace-view-model.ts index 676a2d38d3..9429022b48 100644 --- a/catalog-ui/src/app/view-models/workspace/workspace-view-model.ts +++ b/catalog-ui/src/app/view-models/workspace/workspace-view-model.ts @@ -23,7 +23,10 @@ */ 'use strict'; import * as _ from "lodash"; -import {IUserProperties, IAppMenu, Resource, Component, Plugin, PluginsConfiguration, PluginDisplayOptions} from "app/models"; +import { + IUserProperties, IAppMenu, Resource, Component, Plugin, PluginsConfiguration, PluginDisplayOptions, + RelationshipTypeModel, NodeTypeModel, CapabilityTypeModel +} from "app/models"; import { WorkspaceMode, ComponentFactory, ChangeLifecycleStateHandler, Role, ComponentState, MenuItemGroup, MenuHandler, MenuItem, ModalsHandler, States, EVENTS, CHANGE_COMPONENT_CSAR_VERSION_FLAG, ResourceType, PREVIOUS_CSAR_COMPONENT @@ -78,6 +81,9 @@ export interface IWorkspaceViewModelScope extends ng.IScope { unsavedChanges:boolean; unsavedChangesCallback:Function; unsavedFile:boolean; + capabilityTypesList: Array<CapabilityTypeModel>; + relationshipTypesList: Array<RelationshipTypeModel>; + nodeTypesList: Array<NodeTypeModel>; startProgress(message:string):void; diff --git a/catalog-ui/src/assets/languages/en_US.json b/catalog-ui/src/assets/languages/en_US.json index 41f543aae8..b81dadffd9 100644 --- a/catalog-ui/src/assets/languages/en_US.json +++ b/catalog-ui/src/assets/languages/en_US.json @@ -549,5 +549,18 @@ "SERVICE_CERTIFICATION_STATUS_TEXT": "Service {{serviceName}} was successfully certified", "SERVICE_AUTOMATED_UPGRADE_WITH_COMPONENTS_TO_UPGRADE": "The following services reference <b>{{vspName}}</b>.<br/> One or more of the services were not yet upgraded with the most recently certified version of <b>{{vspName}}</b>.</br>Select services from the list to upgrade them with <b>{{vspName}} {{vspVersion}}</b>.", "SERVICE_AUTOMATED_UPGRADE_ALL_COMPONENTS_LOCKED": "The listed services reference <b>{{vspName}}</b>.<br/> These services were not upgraded with the most recently certified version of <b>{{vspName}}</b>. Currently they are locked from being upgraded with <b>{{vspName}} {{vspVersion}}</b>", - "SERVICE_AUTOMATED_UPGRADE_ALL_COMPONENTS_UPGRADED": "The listed services each reference <b>{{vspName}}</b> and have already been updated with the most recently certified version of the <b>{{vspName}} {{vspVersion}}</b>" + "SERVICE_AUTOMATED_UPGRADE_ALL_COMPONENTS_UPGRADED": "The listed services each reference <b>{{vspName}}</b> and have already been updated with the most recently certified version of the <b>{{vspName}} {{vspVersion}}</b>", + "=========== REQUIREMENTS AND CAPABILITIES ===========": "", + "REQ_NAME": "Requirement Name", + "REQ_RELATED_CAPABILITY": "Related Capability", + "REQ_NODE": "Node", + "REQ_RELATIONSHIP": "Relationship", + "REQ_CAP_OCCURRENCES": "Occurrences", + "REQ_CAP_OCCURRENCES_UNBOUNDED": "UNBOUNDED", + "REQ_CAP_OCCURRENCES_MIN": "Min", + "REQ_CAP_OCCURRENCES_MAX": "Max", + "CAP_NAME": "Capability Name", + "CAP_TYPE": "Capability Type", + "CAP_DESCRIPTION": "Description", + "CAP_VALID_SOURCE": "Valid Sources" } diff --git a/catalog-ui/src/assets/styles/sprite.less b/catalog-ui/src/assets/styles/sprite.less index 383a830b00..5883094f4c 100644 --- a/catalog-ui/src/assets/styles/sprite.less +++ b/catalog-ui/src/assets/styles/sprite.less @@ -31,6 +31,12 @@ .plus-icon { background-position: -50px -231px; width: 12px; height: 12px;} .plus-icon-hover { background-position: -100px -231px; width: 12px; height: 12px;} +.plus-icon-circle { + background-position: -49px -959px; + width: 20px; + height: 20px; +} + .delete-icon { background-position: -675px -231px; width: 11px; height: 13px;} .delete-icon-hover { background-position: -702px -231px; width: 11px; height: 13px;} |