From 2f0536c2aacaf97f49bc5d833091e9a153d05e55 Mon Sep 17 00:00:00 2001 From: Phi Dang Date: Tue, 18 Jan 2022 11:27:39 +0100 Subject: [PATCH] Live-Modeling: Enable Deployments at Modeling Time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kálmán Képes Co-authored-by: Björn Müller Co-authored-by: Lukas Harzenetter Signed-off-by: Phi Dang Signed-off-by: Lukas Harzenetter --- .gitignore | 1 + Dockerfile | 2 + NOTICE | 1 + .../src/main/resources/winery.yml | 2 + .../src/app/canvas/canvas.component.css | 10 +- .../src/app/canvas/canvas.component.html | 9 +- .../src/app/canvas/canvas.component.ts | 70 +- .../buildplan-outputs.component.css | 57 ++ .../buildplan-outputs.component.html | 39 + .../buildplan-outputs.component.ts | 48 + .../buildplan-parameters.component.css | 57 ++ .../buildplan-parameters.component.html | 39 + .../buildplan-parameters.component.ts | 54 ++ .../csar-info/csar-info.component.css | 56 ++ .../csar-info/csar-info.component.html | 57 ++ .../csar-info/csar-info.component.ts | 47 + .../live-modeling-sidebar.component.css | 216 +++++ .../live-modeling-sidebar.component.html | 200 ++++ .../live-modeling-sidebar.component.ts | 329 +++++++ .../live-modeling-sidebar.module.ts | 87 ++ .../app/live-modeling/logs/logs.component.css | 54 ++ .../live-modeling/logs/logs.component.html | 31 + .../app/live-modeling/logs/logs.component.ts | 93 ++ .../confirm-modal/confirm-modal.component.css | 21 + .../confirm-modal.component.html | 51 + .../confirm-modal/confirm-modal.component.ts | 58 ++ .../disable-modal/disable-modal.component.css | 0 .../disable-modal.component.html | 29 + .../disable-modal/disable-modal.component.ts | 60 ++ .../enable-modal/enable-modal.component.css | 76 ++ .../enable-modal/enable-modal.component.html | 64 ++ .../enable-modal/enable-modal.component.ts | 102 ++ .../input-parameters-modal.component.css | 73 ++ .../input-parameters-modal.component.html | 39 + .../input-parameters-modal.component.ts | 67 ++ .../reconfigure-modal.component.css | 74 ++ .../reconfigure-modal.component.html | 74 ++ .../reconfigure-modal.component.ts | 63 ++ .../settings-modal.component.css | 0 .../settings-modal.component.html | 31 + .../settings-modal.component.ts | 76 ++ .../node-template/node-template.component.css | 106 ++ .../node-template.component.html | 64 ++ .../node-template/node-template.component.ts | 185 ++++ .../progressbar/progressbar.component.css | 24 + .../progressbar/progressbar.component.html | 17 + .../progressbar/progressbar.component.ts | 91 ++ .../container/adaptation-payload.model.ts | 20 + .../app/models/container/csar-upload.model.ts | 18 + .../src/app/models/container/csar.model.ts | 25 + .../models/container/input-parameter.model.ts | 20 + .../models/container/link-reference.model.ts | 17 + .../node-template-instance-resources.model.ts | 20 + .../container/node-template-instance.model.ts | 23 + .../node-template-resources.model.ts | 19 + .../models/container/node-template.model.ts | 20 + .../container/output-parameter.model.ts | 19 + .../plan-instance-resources.model.ts | 19 + .../models/container/plan-instance.model.ts | 27 + .../models/container/plan-log-entry.model.ts | 21 + .../models/container/plan-resources.model.ts | 20 + .../src/app/models/container/plan.model.ts | 23 + .../container/resource-support.model.ts | 18 + ...rvice-template-instance-resources.model.ts | 19 + .../container/service-template-instance.ts | 20 + .../container/service-template.model.ts | 18 + .../src/app/models/customErrors.ts | 108 +++ .../topologymodeler/src/app/models/enums.ts | 83 ++ .../src/app/models/liveModelingLog.ts | 22 + .../src/app/models/liveModelingSettings.ts | 25 + .../models/topologyModelerConfiguration.ts | 4 +- .../src/app/models/topologyTemplateUtil.ts | 112 ++- .../src/app/models/ttopology-template.ts | 13 +- .../src/app/navbar/navbar.component.css | 9 + .../src/app/navbar/navbar.component.html | 129 +-- .../src/app/navbar/navbar.component.ts | 90 +- .../src/app/navbar/navbar.module.ts | 45 + .../src/app/node/node.component.css | 48 +- .../src/app/node/node.component.html | 516 +++++----- .../src/app/node/node.component.ts | 78 +- .../src/app/overlay/overlay.component.css | 51 + .../src/app/overlay/overlay.component.html | 8 + .../src/app/overlay/overlay.component.ts | 42 + .../src/app/palette/palette.component.html | 8 +- .../src/app/palette/palette.component.scss | 23 +- .../src/app/palette/palette.component.ts | 12 +- .../kv-properties/kv-properties.component.css | 38 + .../kv-properties/kv-properties.component.ts | 127 ++- .../app/properties/properties.component.html | 22 +- .../app/properties/properties.component.ts | 9 +- .../xml-properties.component.css | 21 + .../xml-properties.component.ts | 5 + .../redux/actions/live-modeling.actions.ts | 148 +++ .../redux/actions/topologyRenderer.actions.ts | 5 + .../src/app/redux/actions/winery.actions.ts | 85 +- .../redux/reducers/live-modeling.reducer.ts | 140 +++ .../reducers/topologyRenderer.reducer.ts | 11 + .../src/app/redux/reducers/winery.reducer.ts | 78 +- .../src/app/redux/store/winery.store.ts | 12 +- .../src/app/services/backend.service.ts | 51 +- .../src/app/services/container.service.ts | 529 ++++++++++ .../src/app/services/live-modeling.service.ts | 910 ++++++++++++++++++ .../src/app/services/logging.service.ts | 61 ++ .../src/app/services/overlay.service.ts | 37 + .../services/property-validator.service.ts | 49 + .../src/app/services/topology.service.ts | 86 ++ .../node-details/node-details-sidebar.ts | 3 +- .../nodeDetailsSidebar.component.html | 56 +- .../nodeDetailsSidebar.component.ts | 45 +- .../src/app/sidebars/sidebar.css | 5 + .../topology-renderer.component.css | 10 +- .../topology-renderer.module.ts | 4 +- .../src/app/winery.component.css | 18 + .../src/app/winery.component.html | 44 +- .../src/app/winery.component.ts | 33 +- .../topologymodeler/src/app/winery.module.ts | 33 +- .../src/assets/styles/styles.scss | 12 +- .../app/topologymodeler/src/index.html | 12 +- .../configuration.component.html | 8 + .../propertiesDefinition.component.ts | 7 +- .../propertiesDefinitionsResourceApiData.ts | 1 + .../WineryRepositoryConfiguration.service.ts | 2 + .../wineryRepository.feature.direct.ts | 2 +- org.eclipse.winery.frontends/package.json | 8 +- .../kvproperties/PropertyDefinitionKV.java | 9 + .../ServiceTemplateResource.java | 33 + .../backend/filebased/GitBasedRepository.java | 1 + package-lock.json | 3 + 128 files changed, 7061 insertions(+), 528 deletions(-) create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/buildplan-outputs/buildplan-outputs.component.css create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/buildplan-outputs/buildplan-outputs.component.html create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/buildplan-outputs/buildplan-outputs.component.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/buildplan-parameters/buildplan-parameters.component.css create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/buildplan-parameters/buildplan-parameters.component.html create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/buildplan-parameters/buildplan-parameters.component.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/csar-info/csar-info.component.css create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/csar-info/csar-info.component.html create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/csar-info/csar-info.component.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/live-modeling-sidebar.component.css create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/live-modeling-sidebar.component.html create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/live-modeling-sidebar.component.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/live-modeling-sidebar.module.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/logs/logs.component.css create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/logs/logs.component.html create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/logs/logs.component.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/confirm-modal/confirm-modal.component.css create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/confirm-modal/confirm-modal.component.html create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/confirm-modal/confirm-modal.component.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/disable-modal/disable-modal.component.css create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/disable-modal/disable-modal.component.html create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/disable-modal/disable-modal.component.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/enable-modal/enable-modal.component.css create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/enable-modal/enable-modal.component.html create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/enable-modal/enable-modal.component.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/input-parameters-modal/input-parameters-modal.component.css create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/input-parameters-modal/input-parameters-modal.component.html create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/input-parameters-modal/input-parameters-modal.component.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/reconfigure-modal/reconfigure-modal.component.css create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/reconfigure-modal/reconfigure-modal.component.html create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/reconfigure-modal/reconfigure-modal.component.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/settings-modal/settings-modal.component.css create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/settings-modal/settings-modal.component.html create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/settings-modal/settings-modal.component.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/node-template/node-template.component.css create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/node-template/node-template.component.html create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/node-template/node-template.component.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/progressbar/progressbar.component.css create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/progressbar/progressbar.component.html create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/progressbar/progressbar.component.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/adaptation-payload.model.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/csar-upload.model.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/csar.model.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/input-parameter.model.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/link-reference.model.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/node-template-instance-resources.model.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/node-template-instance.model.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/node-template-resources.model.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/node-template.model.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/output-parameter.model.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/plan-instance-resources.model.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/plan-instance.model.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/plan-log-entry.model.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/plan-resources.model.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/plan.model.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/resource-support.model.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/service-template-instance-resources.model.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/service-template-instance.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/service-template.model.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/models/customErrors.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/models/liveModelingLog.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/models/liveModelingSettings.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/navbar/navbar.module.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/overlay/overlay.component.css create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/overlay/overlay.component.html create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/overlay/overlay.component.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/properties/kv-properties/kv-properties.component.css create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/properties/xml-properties/xml-properties.component.css create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/actions/live-modeling.actions.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/reducers/live-modeling.reducer.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/services/container.service.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/services/live-modeling.service.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/services/logging.service.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/services/overlay.service.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/services/property-validator.service.ts create mode 100644 org.eclipse.winery.frontends/app/topologymodeler/src/app/services/topology.service.ts create mode 100644 package-lock.json diff --git a/.gitignore b/.gitignore index 85251e5790..8025717d17 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ winery-debug.log **/gen/** **/dist.tgz org.eclipse.winery.repository.ui/src/assets/build-code** +.DS_Store # generated by CLi when executing with -cb copy.bara.sky copy.bara.sky diff --git a/Dockerfile b/Dockerfile index f3a7fc3527..eacc229d3a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -41,6 +41,8 @@ ENV WINERY_FEATURE_SPLITTING false ENV WINERY_FEATURE_TEST_REFINEMENT false ENV WINERY_FEATURE_EDMM_MODELING false ENV WINERY_FEATURE_UPDATE_TEMPLATES false +ENV WINERY_FEATURE_LIVE_MODELING false +ENV WINERY_FEATURE_PROPERTY_CHECK false ENV DOCKERIZE_VERSION v0.6.1 ENV CHE_URL "che.localhost" ENV CHE_URL_PROTOCOL "http" diff --git a/NOTICE b/NOTICE index 143e8ad331..32f96d695c 100644 --- a/NOTICE +++ b/NOTICE @@ -83,6 +83,7 @@ Copyright (c) 2019-2020 Tobias Mathony Copyright (c) 2019 Yannik Dietrich Copyright (c) 2019 Yuye Tong Copyright (c) 2020 Leonardo Frioli +Copyright (c) 2020 Phi Dang Copyright (c) 2021 Alexandros Fouskas Copyright (c) 2021 Andrej Nisin Copyright (c) 2021 Marvin Bechtold diff --git a/org.eclipse.winery.common/src/main/resources/winery.yml b/org.eclipse.winery.common/src/main/resources/winery.yml index 9e537014dd..53f798ddd9 100644 --- a/org.eclipse.winery.common/src/main/resources/winery.yml +++ b/org.eclipse.winery.common/src/main/resources/winery.yml @@ -15,6 +15,8 @@ ui: placement: false radon: false patternDetection: true + liveModeling: true + propertyCheck: true endpoints: container: http://localhost:1337 workflowmodeler: http://localhost:9527 diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/canvas/canvas.component.css b/org.eclipse.winery.frontends/app/topologymodeler/src/app/canvas/canvas.component.css index cb465f1f09..680fd5ae79 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/canvas/canvas.component.css +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/canvas/canvas.component.css @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2017-2018 Contributors to the Eclipse Foundation + * Copyright (c) 2017-2021 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -27,6 +27,10 @@ cursor: crosshair; } +.cursor-move { + cursor: move; +} + .selection-active { display: block !important; } @@ -47,6 +51,10 @@ right: 2em; } +#container { + overflow: hidden; +} + #grid { position: absolute; } diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/canvas/canvas.component.html b/org.eclipse.winery.frontends/app/topologymodeler/src/app/canvas/canvas.component.html index 3866731499..2f68b8451c 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/canvas/canvas.component.html +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/canvas/canvas.component.html @@ -11,16 +11,16 @@ * * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 --> -
+ [style.width]="this.gridTemplate.gridDimension" + [style.height]="this.gridTemplate.gridDimension"> +
this.handleHideDependsOnRelations(hideDependsOnRelations))); this.gridTemplate = new GridTemplate(100, false, false, 30); + this.subscriptions.push(this.ngRedux.select(state => state.wineryState.currentPaletteOpenedState) + .subscribe(currentPaletteOpened => this.setPaletteState(currentPaletteOpened))); + this.subscriptions.push(this.ngRedux.select(state => state.liveModelingState.state) + .subscribe(state => this.liveModelingState = state)); this.hotkeysService.add(new Hotkey('mod+a', (event: KeyboardEvent): boolean => { event.stopPropagation(); this.allNodeTemplates.forEach((node) => this.enhanceDragSelection(node.id)); @@ -280,7 +288,7 @@ export class CanvasComponent implements OnInit, OnDestroy, OnChanges, AfterViewI * Upon detecting a long mouse down the navbar and the palette fade out for maximum dragging space. * Resets the values. */ - @HostListener('mouseup') + @HostListener('mouseup', ['$event']) onMouseUp() { this.longPressing = false; } @@ -291,12 +299,10 @@ export class CanvasComponent implements OnInit, OnDestroy, OnChanges, AfterViewI * Sets the values upon detecting a long mouse down press. */ @HostListener('mousedown', ['$event']) - onMouseDown(event: MouseEvent) { + onMouseDown() { // don't do right/middle clicks - if (event.button === 0) { - this.longPressing = false; - setTimeout(() => this.longPressing = true, 250); - } + this.longPressing = false; + setTimeout(() => this.longPressing = true, 250); } /** @@ -1531,21 +1537,21 @@ export class CanvasComponent implements OnInit, OnDestroy, OnChanges, AfterViewI * @param $event */ showSelectionRange($event: any) { - this.gridTemplate.crosshair = true; - this.ngRedux.dispatch(this.actions.sendPaletteOpened(false)); - this.hideSidebar(); - this.clearSelectedNodes(); - this.nodeComponentChildren.forEach((node) => node.makeSelectionVisible = false); - this.gridTemplate.pageX = $event.pageX; - this.gridTemplate.pageY = $event.pageY; - this.gridTemplate.initialW = $event.pageX; - this.gridTemplate.initialH = $event.pageY; - this.zone.run(() => { - this.unbindMouseActions.push(this.renderer.listen(this.eref.nativeElement, 'mousemove', (event) => - this.openSelector(event))); - this.unbindMouseActions.push(this.renderer.listen(this.eref.nativeElement, 'mouseup', (event) => - this.selectElements(event))); - }); + this.gridTemplate.crosshair = true; + this.ngRedux.dispatch(this.actions.sendPaletteOpened(false)); + this.hideSidebar(); + this.clearSelectedNodes(); + this.nodeComponentChildren.forEach((node) => node.makeSelectionVisible = false); + this.gridTemplate.pageX = $event.pageX; + this.gridTemplate.pageY = $event.pageY; + this.gridTemplate.initialW = $event.pageX; + this.gridTemplate.initialH = $event.pageY; + this.zone.run(() => { + this.unbindMouseActions.push(this.renderer.listen(this.eref.nativeElement, 'mousemove', (event) => + this.openSelector(event))); + this.unbindMouseActions.push(this.renderer.listen(this.eref.nativeElement, 'mouseup', (event) => + this.selectElements(event))); + }); } /** @@ -1621,8 +1627,7 @@ export class CanvasComponent implements OnInit, OnDestroy, OnChanges, AfterViewI * Hides the Sidebar on the right. */ hideSidebar() { - this.ngRedux.dispatch(this.actions.triggerSidebar( - { sidebarContents: new DetailsSidebarState(false) })); + this.ngRedux.dispatch(this.actions.triggerSidebar(new DetailsSidebarState(false))); } /** @@ -1890,13 +1895,20 @@ export class CanvasComponent implements OnInit, OnDestroy, OnChanges, AfterViewI this.newJsPlumbInstance.select().removeType('marked'); const currentRel = this.allRelationshipTemplates.find((con) => con.id === conn.id); if (currentRel) { + if (this.liveModelingState === LiveModelingStates.DISABLED) { + const sourceNode = this.allNodeTemplates.find(node => node.id === currentRel.sourceElement.ref); + const targetNode = this.allNodeTemplates.find(node => node.id === currentRel.targetElement.ref); + if (sourceNode && targetNode && sourceNode.working && targetNode.working) { + return; + } + } let name = currentRel.name; if (currentRel.name.startsWith(this.backendService.configuration.relationshipPrefix)) { // Workaround to support old topology templates with the real name name = currentRel.type.substring(currentRel.type.indexOf('}') + 1); } + const entityType = this.entityTypes.relationshipTypes.find(type => type.qName === currentRel.type); this.ngRedux.dispatch(this.actions.triggerSidebar({ - sidebarContents: { visible: true, nodeClicked: false, template: { @@ -1905,10 +1917,10 @@ export class CanvasComponent implements OnInit, OnDestroy, OnChanges, AfterViewI type: currentRel.type, properties: currentRel.properties, }, + entityType: entityType, relationshipTemplate: currentRel, source: currentRel.sourceElement.ref, target: currentRel.targetElement.ref - } })); conn.addType('marked'); } @@ -2256,6 +2268,10 @@ export class CanvasComponent implements OnInit, OnDestroy, OnChanges, AfterViewI } } + getNodeEntityType(name: string): EntityType { + return this.entityTypes.unGroupedNodeTypes.find(type => type.name === name); + } + /** * Gets all ID's of the topology template and saves them in an array */ @@ -2375,7 +2391,7 @@ export class CanvasComponent implements OnInit, OnDestroy, OnChanges, AfterViewI // update exposed keys for (const key of ['name', 'minInstances', 'maxInstances', 'properties', 'capabilities', 'requirements', 'deploymentArtifacts', - 'policies', 'otherAttributes']) { + 'policies', 'otherAttributes', 'instanceState', 'valid', 'working']) { nodeTemplate[key] = storeData[key]; } const nodeComponent = this.nodeComponentChildren.find((c) => c.nodeTemplate.id === nodeTemplate.id); diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/buildplan-outputs/buildplan-outputs.component.css b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/buildplan-outputs/buildplan-outputs.component.css new file mode 100644 index 0000000000..1f4237c50a --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/buildplan-outputs/buildplan-outputs.component.css @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +.table-scrollable { + max-height: 200px; + overflow-y: auto; + font-size: x-small; +} + +.table-scrollable th { + padding: 0.25em; +} + +.table-scrollable td { + padding: 0.25em; +} + +.parameters-table { + width: 100%; + table-layout: fixed; + border-collapse: collapse; +} + +.parameters-table, +.parameters-table th, +.parameters-table td { + border: 1px solid #3d3d3d; +} + +.parameters-table th, +.parameters-table td { + width: 50%; + word-break: break-all; +} + +.parameters-table th { + background: #333; +} + +.parameters-table tr:nth-child(odd) { + background: #242424; +} + +.parameters-table tr:nth-child(even) { + background: #292929; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/buildplan-outputs/buildplan-outputs.component.html b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/buildplan-outputs/buildplan-outputs.component.html new file mode 100644 index 0000000000..503d11240d --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/buildplan-outputs/buildplan-outputs.component.html @@ -0,0 +1,39 @@ + + +
+ +
+ + + + + + + + + + + + + +
NameValue
{{parameter.name}}{{parameter.value || '(empty)'}}
+
+
+ +
+ No outputs found +
+
diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/buildplan-outputs/buildplan-outputs.component.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/buildplan-outputs/buildplan-outputs.component.ts new file mode 100644 index 0000000000..7a8e851743 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/buildplan-outputs/buildplan-outputs.component.ts @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ +import { Component, OnDestroy, OnInit, } from '@angular/core'; +import { Subscription } from 'rxjs'; +import { NgRedux } from '@angular-redux/store'; +import { IWineryState } from '../../redux/store/winery.store'; +import { ServiceTemplateInstanceStates } from '../../models/enums'; +import { PlanInstance } from '../../models/container/plan-instance.model'; + +@Component({ + selector: 'winery-live-modeling-sidebar-buildplan-outputs', + templateUrl: './buildplan-outputs.component.html', + styleUrls: ['./buildplan-outputs.component.css'], +}) +export class BuildplanOutputsComponent implements OnInit, OnDestroy { + currentBuildPlanInstance: PlanInstance; + subscriptions: Array = []; + + constructor(private ngRedux: NgRedux) { + } + + ngOnInit() { + // tslint:disable-next-line:no-unused-expression + this.subscriptions.push(this.ngRedux.select((state) => { + return state.liveModelingState.currentBuildPlanInstance; + }) + .subscribe((buildPlanInstance) => { + this.currentBuildPlanInstance = buildPlanInstance; + })); + } + + ngOnDestroy() { + this.subscriptions.forEach((subscription) => { + subscription.unsubscribe(); + }); + } +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/buildplan-parameters/buildplan-parameters.component.css b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/buildplan-parameters/buildplan-parameters.component.css new file mode 100644 index 0000000000..1f4237c50a --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/buildplan-parameters/buildplan-parameters.component.css @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +.table-scrollable { + max-height: 200px; + overflow-y: auto; + font-size: x-small; +} + +.table-scrollable th { + padding: 0.25em; +} + +.table-scrollable td { + padding: 0.25em; +} + +.parameters-table { + width: 100%; + table-layout: fixed; + border-collapse: collapse; +} + +.parameters-table, +.parameters-table th, +.parameters-table td { + border: 1px solid #3d3d3d; +} + +.parameters-table th, +.parameters-table td { + width: 50%; + word-break: break-all; +} + +.parameters-table th { + background: #333; +} + +.parameters-table tr:nth-child(odd) { + background: #242424; +} + +.parameters-table tr:nth-child(even) { + background: #292929; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/buildplan-parameters/buildplan-parameters.component.html b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/buildplan-parameters/buildplan-parameters.component.html new file mode 100644 index 0000000000..2e794b876e --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/buildplan-parameters/buildplan-parameters.component.html @@ -0,0 +1,39 @@ + + +
+ +
+ + + + + + + + + + + + + +
NameValue
{{parameter.name}}{{parameter.value || '(empty)'}}
+
+
+ +
+ No build plan parameters found +
+
diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/buildplan-parameters/buildplan-parameters.component.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/buildplan-parameters/buildplan-parameters.component.ts new file mode 100644 index 0000000000..7666796f45 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/buildplan-parameters/buildplan-parameters.component.ts @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ +import { + AfterViewInit, Component, ElementRef, OnDestroy, OnInit, QueryList, ViewChild, ViewChildren +} from '@angular/core'; +import { LiveModelingLog } from '../../models/liveModelingLog'; +import { Subscription } from 'rxjs'; +import { LoggingService } from '../../services/logging.service'; +import { LiveModelingLogTypes } from '../../models/enums'; +import { InputParameter } from '../../models/container/input-parameter.model'; +import { NgRedux } from '@angular-redux/store'; +import { IWineryState } from '../../redux/store/winery.store'; + +@Component({ + selector: 'winery-live-modeling-sidebar-buildplan-parameters', + templateUrl: './buildplan-parameters.component.html', + styleUrls: ['./buildplan-parameters.component.css'], +}) +export class BuildplanParametersComponent implements OnInit, OnDestroy { + + buildPlanInputParameters: Array = []; + subscriptions: Array = []; + + constructor(private ngRedux: NgRedux) { + } + + ngOnInit() { + this.subscriptions.push(this.ngRedux.select((state) => { + return state.liveModelingState.currentBuildPlanInstance; + }) + .subscribe((buildPlanInstance) => { + if (buildPlanInstance && buildPlanInstance.hasOwnProperty('inputs')) { + this.buildPlanInputParameters = buildPlanInstance.inputs; + } + })); + } + + ngOnDestroy() { + this.subscriptions.forEach((subscription) => { + subscription.unsubscribe(); + }); + } +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/csar-info/csar-info.component.css b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/csar-info/csar-info.component.css new file mode 100644 index 0000000000..a0a034d141 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/csar-info/csar-info.component.css @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +.icon-container { + min-height: 50px; + min-width: 50px; + max-width: 50px; +} + +.icon-placeholder { + width: 50px; + height: 50px; + background-color: grey; +} + +.text-container { + overflow: hidden; + text-align: left; +} + +.text-placeholder { + font-style: italic; +} + +.csar-title { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + font-weight: bold; + font-size: smaller; +} + +.csar-title:hover { + white-space: normal; + word-break: break-all; +} + +.csar-description { + font-size: x-small; +} + +.csar-version { + font-style: italic; + font-size: x-small; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/csar-info/csar-info.component.html b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/csar-info/csar-info.component.html new file mode 100644 index 0000000000..81a4aca4b7 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/csar-info/csar-info.component.html @@ -0,0 +1,57 @@ + + +
+
+
+ + csar logo + + +
+
+
+
+ +
+
+ + {{currentCsar.name}} + + + No title found + +
+ + {{currentCsar.description}} + + + No description found + +
+ + Version: {{currentCsar.version}} + + + No version found + +
+
+ +
+ No csar loaded +
+
+
diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/csar-info/csar-info.component.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/csar-info/csar-info.component.ts new file mode 100644 index 0000000000..72b3c4c124 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/csar-info/csar-info.component.ts @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ +import { Component, OnDestroy, OnInit, } from '@angular/core'; +import { Subscription } from 'rxjs'; +import { NgRedux } from '@angular-redux/store'; +import { IWineryState } from '../../redux/store/winery.store'; +import { Csar } from '../../models/container/csar.model'; + +@Component({ + selector: 'winery-live-modeling-sidebar-csar-info', + templateUrl: './csar-info.component.html', + styleUrls: ['./csar-info.component.css'], +}) +export class CsarInfoComponent implements OnInit, OnDestroy { + + currentCsar: Csar; + subscriptions: Array = []; + + constructor(private ngRedux: NgRedux) { + } + + ngOnInit() { + this.subscriptions.push(this.ngRedux.select((state) => { + return state.liveModelingState.currentCsar; + }) + .subscribe((csar) => { + this.currentCsar = csar; + })); + } + + ngOnDestroy() { + this.subscriptions.forEach((subscription) => { + subscription.unsubscribe(); + }); + } +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/live-modeling-sidebar.component.css b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/live-modeling-sidebar.component.css new file mode 100644 index 0000000000..2e78ab93c0 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/live-modeling-sidebar.component.css @@ -0,0 +1,216 @@ +/******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +.live-modeling-sidebar-root { + width: 60%; + position: fixed; + bottom: 0; + z-index: 55; + display: flex; + flex-direction: column; + height: 33vh; + max-height: 30%; + left: 50%; + transform: translate(-50%, 0%); + margin: 0 auto; +} + +#toggleButton { + height: 35px; + box-shadow: initial; + background-color: #4a4a4f; + border: 1px solid #4a4a4f; + min-width: 300px; + color: white; + border-radius: 0 1px 1px 0; + font-size: small; +} + +#toggleButton:hover { + background-color: #737378; +} + +.live-modeling-sidebar-content { + height: 25%; + position: relative; + flex-grow: 1; + min-width: 300px; + background-color: #353537; + box-sizing: border-box; + flex-direction: row; +} + +.live-modeling-sidebar-page { + width: 100%; + height: 100%; + text-align: center; + color: white; + font-size: small; +} + +.live-modeling-sidebar-page.start-page { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.live-modeling-sidebar-page.live-page { + display: flex; + flex-direction: row; +} + +.live-modeling-sidebar-info-panel { + display: flex; + flex-direction: column; + padding: 0.5em; +} + +.warning-changes { + text-align: left; + padding: 0.5em; + background-color: #ffbf00; +} + +.state-information { + border-radius: 0.25em; + font-weight: bold; + font-size: smaller; + color: white; +} + +.btn-live-modeling { + flex: 1 1 0px; + background-color: #353537; + border: 1px solid #007bff; + color: #007bff; + letter-spacing: 0.05em; + padding: 0.75em; + border-radius: 0.25em; + font-weight: bold; + font-size: x-small; +} + +.btn-live-modeling:hover { + cursor: pointer; + color: white; + background-color: #007bff; +} + +.btn-live-modeling:active { + cursor: pointer; + color: white; + background-color: #233c4f; +} + +.btn-live-modeling-deploy { + border: 1px solid #28a745; + color: #28a745; +} + +.btn-live-modeling-deploy:hover { + background-color: #28a745; +} + +.btn-live-modeling-deploy:active { + background-color: #1e7e34; +} + +.btn-live-modeling-terminate { + border: 1px solid #dc3545; + color: #dc3545; +} + +.btn-live-modeling-terminate:hover { + background-color: #dc3545; +} + +.btn-live-modeling-terminate:active { + background-color: #bd2130; +} + +.btn-live-modeling:disabled, +.btn-live-modeling[disabled] { + border: 1px solid #404144; + background-color: #404144; + color: #7a8387; + cursor: not-allowed; +} + +.btn-footer-live-modeling { + border: none; + border-top: 1px solid #4a4a4f; + color: white; +} + +accordion { + border-top: 1px solid rgba(0, 0, 0, 0.125); +} + +accordion-group { + font-size: x-small; + text-align: start; +} + +::ng-deep .card.customClass { + background-color: transparent; + border-radius: 0; + border: none; +} + +::ng-deep .card.customClass .card-header { + background-color: transparent; + border: none; + border-bottom: 1px solid rgba(0, 0, 0, 0.125); +} + +::ng-deep .card.customClass .card-header:hover { + background-color: #495057; + background-clip: border-box; +} + +::ng-deep .card.customClass .card-body { + border-bottom: 1px solid rgba(0, 0, 0, 0.125); +} + +.dropdown-toggle { + width: 100%; +} + +.dropdown-menu { + width: 100%; + text-align: right; +} + +td { + cursor: text; +} + +.dropdown-item { + cursor: pointer; +} + +.disable-icon { + pointer-events: none; + color: grey; +} + +.wrapper { + display: flex; + align-items: center; +} + +.wrapper span { + flex: 1; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/live-modeling-sidebar.component.html b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/live-modeling-sidebar.component.html new file mode 100644 index 0000000000..f6e1d0a8b5 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/live-modeling-sidebar.component.html @@ -0,0 +1,200 @@ + + +
+ +
+
+

+ Live modeling is currently disabled. +

+ +

+ (Unsaved changes detected) +

+
+
+
+ + + Warning + +
+ Your model is out-of-sync with the deployed instance. + Save your changes and reconfigurate to update the instance. +
+
+
+
+ +
+ + +
+
+ +
+
+ + {{serviceTemplateInstanceState}} ({{serviceTemplateInstanceId}}) + + + {{serviceTemplateInstanceState || 'NOT AVAILABLE'}} + +
+ +
+ + + + + + + +
+ +
+
+ +
+ +
+
+
+
+ + + + + + Node Template Instance + + + + + + + + Input Parameters + + + + + + + + Outputs + + + + +
+
+
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/live-modeling-sidebar.component.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/live-modeling-sidebar.component.ts new file mode 100644 index 0000000000..9af02a580b --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/live-modeling-sidebar.component.ts @@ -0,0 +1,329 @@ +/******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { BsModalRef, BsModalService } from 'ngx-bootstrap'; +import { Subscription } from 'rxjs'; +import { NgRedux } from '@angular-redux/store'; +import { IWineryState } from '../redux/store/winery.store'; +import { LiveModelingStates, ReconfigureOptions, ServiceTemplateInstanceStates } from '../models/enums'; +import { LiveModelingActions } from '../redux/actions/live-modeling.actions'; +import { LiveModelingService } from '../services/live-modeling.service'; +import { WineryActions } from '../redux/actions/winery.actions'; +import { state, style, trigger } from '@angular/animations'; +import { ResizeEvent } from 'angular-resizable-element'; +import { EnableModalComponent } from './modals/enable-modal/enable-modal.component'; +import { SettingsModalComponent } from './modals/settings-modal/settings-modal.component'; +import { DisableModalComponent } from './modals/disable-modal/disable-modal.component'; +import { ConfirmModalComponent } from './modals/confirm-modal/confirm-modal.component'; +import { ReconfigureModalComponent } from './modals/reconfigure-modal/reconfigure-modal.component'; +import { BackendService } from '../services/backend.service'; +import { LiveModelingSettings } from '../models/liveModelingSettings'; + +@Component({ + selector: 'winery-live-modeling-sidebar', + templateUrl: './live-modeling-sidebar.component.html', + styleUrls: ['./live-modeling-sidebar.component.css'], + animations: [ + trigger('sidebarContentState', [ + state('shrunk', style({ + display: 'none' + })), + state('extended', style({ + display: 'block' + })) + ]), + trigger('sidebarButtonState', [ + state('top', style({ + transform: 'rotate(0deg)', + })), + state('right', style({ + transform: 'rotate(0deg) translate(-50%,650%)', + })), + ]) + ] +}) +export class LiveModelingSidebarComponent implements OnInit, OnDestroy { + @Input() top: number; + + sidebarWidth: number; + sidebarContentState = 'extended'; + sidebarButtonState = 'right'; + + liveModelingState: LiveModelingStates; + readonly LiveModelingStates = LiveModelingStates; + serviceTemplateInstanceId: string; + serviceTemplateInstanceState: ServiceTemplateInstanceStates; + currentCsarId: string; + + subscriptions: Array = []; + + modalRef: BsModalRef; + + unsavedChanges: boolean; + deploymentChanges: boolean; + + showLogs = false; + private settings: LiveModelingSettings; + + constructor(private ngRedux: NgRedux, + private wineryActions: WineryActions, + private liveModelingActions: LiveModelingActions, + private liveModelingService: LiveModelingService, + private backendService: BackendService, + private modalService: BsModalService) { + } + + ngOnInit() { + this.subscriptions.push(this.ngRedux.select((wineryState) => { + return wineryState.liveModelingState.state; + }) + .subscribe((liveModelingState) => { + this.liveModelingState = liveModelingState; + })); + this.subscriptions.push(this.ngRedux.select((wineryState) => { + return wineryState.liveModelingState.currentServiceTemplateInstanceId; + }) + .subscribe((serviceTemplateInstanceId) => { + this.serviceTemplateInstanceId = serviceTemplateInstanceId; + })); + this.subscriptions.push(this.ngRedux.select((wineryState) => { + return wineryState.liveModelingState.currentServiceTemplateInstanceState; + }) + .subscribe((serviceTemplateInstanceState) => { + this.serviceTemplateInstanceState = serviceTemplateInstanceState; + })); + this.subscriptions.push(this.ngRedux.select((wineryState) => { + return wineryState.wineryState.liveModelingSidebarOpenedState; + }) + .subscribe((sidebarOpened) => { + this.updateSidebarState(sidebarOpened); + })); + this.subscriptions.push(this.ngRedux.select((wineryState) => { + return wineryState.wineryState.unsavedChanges; + }) + .subscribe((unsavedChanges) => { + this.unsavedChanges = unsavedChanges; + })); + this.subscriptions.push(this.ngRedux.select((wineryState) => { + return wineryState.liveModelingState.deploymentChanges; + }) + .subscribe((deploymentChanges) => { + this.deploymentChanges = deploymentChanges; + })); + this.subscriptions.push(this.ngRedux.select((wineryState) => { + return wineryState.liveModelingState.currentCsarId; + }) + .subscribe((csarId) => { + this.currentCsarId = csarId; + })); + + this.subscriptions.push(this.ngRedux.select((newState) => { + return newState.liveModelingState.settings; + }).subscribe((settings) => { + this.settings = settings; + + if (!this.settings) { + // update the Winery Endpoint to the one from the configuration + this.ngRedux.dispatch(this.liveModelingActions.setSettings( + { + ...settings, + wineryEndpoint: this.backendService.configuration.repositoryURL + } + )); + } + })); + } + + handleEnable() { + this.openModal(EnableModalComponent); + } + + handleSettings() { + this.openModal(SettingsModalComponent); + } + + handleDisable() { + this.openModal(DisableModalComponent); + } + + async handleDeploy() { + const resp = await this.openConfirmModal( + 'Deploy new Instance', + 'Are you sure you want to deploy a new instance?', + true, + true + ); + if (resp.confirmed) { + this.liveModelingService.deploy(resp.startInstance); + } + } + + isDeployEnabled() { + return (this.liveModelingState === LiveModelingStates.TERMINATED || this.liveModelingState === LiveModelingStates.ERROR) && !this.unsavedChanges; + } + + async handleRedeploy() { + const resp = await this.openConfirmModal( + 'Redeploy new Instance', + 'Are you sure you want to redeploy a new instance?', + true, + true + ); + if (resp.confirmed) { + this.liveModelingService.redeploy(resp.startInstance); + } + } + + isRedeployEnabled() { + return (this.liveModelingState === LiveModelingStates.TERMINATED || this.liveModelingState === LiveModelingStates.ERROR) && !this.unsavedChanges; + } + + async handleTerminate() { + const resp = await this.openConfirmModal('Terminate Instance', 'Are you sure you want to terminate the instance?'); + if (resp.confirmed) { + this.liveModelingService.terminate(); + } + } + + isTerminateEnabled() { + return this.liveModelingState === LiveModelingStates.ENABLED; + } + + handleRefresh() { + this.liveModelingService.update(); + } + + isRefreshEnabled() { + return this.liveModelingState === LiveModelingStates.ENABLED; + } + + async handleReconfiguration() { + const modalRef = this.modalService.show(ReconfigureModalComponent, { backdrop: 'static' }); + await new Promise((resolve) => { + const subscription = this.modalService.onHidden.subscribe((_) => { + subscription.unsubscribe(); + resolve(); + }); + }); + + if (modalRef.content.selectedOption !== ReconfigureOptions.NONE) { + switch (modalRef.content.selectedOption) { + case ReconfigureOptions.REDEPLOY: { + this.liveModelingService.redeploy(modalRef.content.startInstance); + return; + } + case ReconfigureOptions.TRANSFORM: { + this.liveModelingService.transform(); + return; + } + } + } + } + + isReconfigurationEnabled() { + return this.liveModelingState === LiveModelingStates.ENABLED && !this.unsavedChanges && this.deploymentChanges; + } + + toggleLogs() { + this.showLogs = !this.showLogs; + } + + getBackgroundForState(serviceTemplateInstanceState: ServiceTemplateInstanceStates) { + switch (serviceTemplateInstanceState) { + case ServiceTemplateInstanceStates.DELETED: + case ServiceTemplateInstanceStates.ERROR: + return '#dc3545'; + case ServiceTemplateInstanceStates.DELETING: + case ServiceTemplateInstanceStates.MIGRATING: + case ServiceTemplateInstanceStates.CREATING: + return '#007bff'; + case ServiceTemplateInstanceStates.MIGRATED: + case ServiceTemplateInstanceStates.CREATED: + return '#28a745'; + case ServiceTemplateInstanceStates.INITIAL: + case ServiceTemplateInstanceStates.NOT_AVAILABLE: + default: + return '#6c757d'; + } + } + + updateSidebarState(sidebarOpened: boolean) { + if (sidebarOpened) { + this.sidebarButtonState = 'top'; + this.sidebarContentState = 'extended'; + } else { + this.sidebarButtonState = 'right'; + this.sidebarContentState = 'shrunk'; + } + } + + toggleSidebarState() { + if (this.sidebarContentState === 'shrunk') { + this.ngRedux.dispatch(this.wineryActions.sendLiveModelingSidebarOpened(true)); + } else { + this.ngRedux.dispatch(this.wineryActions.sendLiveModelingSidebarOpened(false)); + } + } + + validateResize(event: ResizeEvent) { + const SIDEBAR_MIN_WIDTH = 300; + return event.rectangle.width >= SIDEBAR_MIN_WIDTH; + } + + onResizeEnd(event: ResizeEvent): void { + this.sidebarWidth = event.rectangle.width; + } + + getResizeEdges() { + if (this.sidebarButtonState === 'right') { + return { bottom: false, right: false, top: false, left: false }; + } else { + return { bottom: false, right: false, top: false, left: true }; + } + } + + openModal(modal: any, options?: any) { + const defaultConfig = { backdrop: 'static' }; + this.modalRef = this.modalService.show(modal, { ...defaultConfig, ...options }); + } + + async openConfirmModal(title: string, content: string, showWarning = false, showStartOption = false): Promise { + const initialState = { + title: title, + content: content, + showWarning: showWarning, + showStartOption: showStartOption + }; + const modalRef = this.modalService.show(ConfirmModalComponent, { initialState, backdrop: 'static' }); + await new Promise((resolve) => { + const subscription = this.modalService.onHidden.subscribe((_) => { + subscription.unsubscribe(); + resolve(); + }); + }); + + return { 'confirmed': modalRef.content.confirmed, 'startInstance': modalRef.content.startInstance }; + } + + dismissModal() { + this.modalRef.hide(); + } + + ngOnDestroy() { + this.subscriptions.forEach((subscription) => { + subscription.unsubscribe(); + }); + } +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/live-modeling-sidebar.module.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/live-modeling-sidebar.module.ts new file mode 100644 index 0000000000..48c42dc9c3 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/live-modeling-sidebar.module.ts @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { HttpClientModule } from '@angular/common/http'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { BsDropdownModule, ProgressbarModule, TooltipModule, TypeaheadModule } from 'ngx-bootstrap'; +import { ToastrModule } from 'ngx-toastr'; +import { NgReduxModule } from '@angular-redux/store'; +import { RouterModule } from '@angular/router'; +import { WineryModalModule } from '../../../../tosca-management/src/app/wineryModalModule/winery.modal.module'; +import { LiveModelingSidebarComponent } from './live-modeling-sidebar.component'; +import { LogsComponent } from './logs/logs.component'; +import { ResizableModule } from 'angular-resizable-element'; +import { AccordionModule } from 'ngx-bootstrap/accordion'; +import { BuildplanParametersComponent } from './buildplan-parameters/buildplan-parameters.component'; +import { BuildplanOutputsComponent } from './buildplan-outputs/buildplan-outputs.component'; +import { NodeTemplateComponent } from './node-template/node-template.component'; +import { EnableModalComponent } from './modals/enable-modal/enable-modal.component'; +import { DisableModalComponent } from './modals/disable-modal/disable-modal.component'; +import { ConfirmModalComponent } from './modals/confirm-modal/confirm-modal.component'; +import { SettingsModalComponent } from './modals/settings-modal/settings-modal.component'; +import { CsarInfoComponent } from './csar-info/csar-info.component'; +import { InputParametersModalComponent } from './modals/input-parameters-modal/input-parameters-modal.component'; +import { ReconfigureModalComponent } from './modals/reconfigure-modal/reconfigure-modal.component'; +import { ProgressbarComponent } from './progressbar/progressbar.component'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + HttpClientModule, + BrowserAnimationsModule, + BsDropdownModule.forRoot(), + ToastrModule.forRoot(), + NgReduxModule, + RouterModule, + WineryModalModule, + TypeaheadModule.forRoot(), + TooltipModule.forRoot(), + ResizableModule, + AccordionModule.forRoot(), + ProgressbarModule.forRoot(), + ], + declarations: [ + LiveModelingSidebarComponent, + LogsComponent, + CsarInfoComponent, + BuildplanParametersComponent, + BuildplanOutputsComponent, + NodeTemplateComponent, + EnableModalComponent, + DisableModalComponent, + ConfirmModalComponent, + SettingsModalComponent, + InputParametersModalComponent, + ReconfigureModalComponent, + ProgressbarComponent + ], + exports: [ + LiveModelingSidebarComponent + ], + entryComponents: [ + EnableModalComponent, + DisableModalComponent, + ConfirmModalComponent, + SettingsModalComponent, + InputParametersModalComponent, + ReconfigureModalComponent + ] +}) +export class LiveModelingSidebarModule { + +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/logs/logs.component.css b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/logs/logs.component.css new file mode 100644 index 0000000000..0cbca33e01 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/logs/logs.component.css @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +.scroll-container { + flex: 1 0 0; + position: fixed; + overflow-y: auto; + max-height: 70%; + min-height: 60%; +} + +.log-item { + width: 100%; + font-size: x-small; + text-align: left; + vertical-align: top; + display: flex; +} + +.log-item > span { + vertical-align: top; + padding: 2px; + cursor: text; +} + +.log-timestamp { + font-weight: bold; + white-space: nowrap; +} + +.badge-xsmall { + font-size: xx-small; + color: white; + border-radius: 5px; + padding: 1px 2px; +} + +.badge-small { + font-size: small; + color: white; + border-radius: 10px; + padding: 2px 4px; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/logs/logs.component.html b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/logs/logs.component.html new file mode 100644 index 0000000000..532d2a09bc --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/logs/logs.component.html @@ -0,0 +1,31 @@ + + +
+ +
+
+ {{log.timestamp}} + + + {{log.type | uppercase }} + + {{log.message}} + +
+
+
diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/logs/logs.component.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/logs/logs.component.ts new file mode 100644 index 0000000000..c26b4bbb7a --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/logs/logs.component.ts @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ +import { + AfterViewInit, Component, ElementRef, OnDestroy, OnInit, QueryList, ViewChild, ViewChildren +} from '@angular/core'; +import { LiveModelingLog } from '../../models/liveModelingLog'; +import { Subscription } from 'rxjs'; +import { LoggingService } from '../../services/logging.service'; +import { LiveModelingLogTypes } from '../../models/enums'; + +@Component({ + selector: 'winery-live-modeling-sidebar-logs', + templateUrl: './logs.component.html', + styleUrls: ['./logs.component.css'], +}) +export class LogsComponent implements OnInit, OnDestroy, AfterViewInit { + + readonly SCROLL_THRESHOLD = 50; + isNearBottom = true; + logs: Array = []; + + subscriptions: Array = []; + + @ViewChild('scrollContainer') private scrollContainer: ElementRef; + @ViewChildren('logItems') private logItems: QueryList; + + constructor(private loggingService: LoggingService) { + } + + ngOnInit() { + this.subscriptions.push(this.loggingService.logStream.subscribe((logs) => { + this.logs = logs; + })); + } + + ngAfterViewInit(): void { + this.subscriptions.push(this.logItems.changes.subscribe((_) => { + this.scrollToBottom(); + })); + } + + getBadgeBackgroundForLog(type: LiveModelingLogTypes) { + switch (type) { + case LiveModelingLogTypes.INFO: + return '#007bff'; + case LiveModelingLogTypes.SUCCESS: + return '#28a745'; + case LiveModelingLogTypes.WARNING: + return '#ffc107'; + case LiveModelingLogTypes.DANGER: + return '#dc3545'; + case LiveModelingLogTypes.CONTAINER: + return '#6c757d'; + } + } + + scrolled() { + const position = this.scrollContainer.nativeElement.scrollTop + this.scrollContainer.nativeElement.offsetHeight; + const height = this.scrollContainer.nativeElement.scrollHeight; + this.isNearBottom = position > height - this.SCROLL_THRESHOLD; + } + + clearLogs() { + this.loggingService.clearLogs(); + } + + ngOnDestroy() { + this.subscriptions.forEach((subscription) => { + subscription.unsubscribe(); + }); + } + + private scrollToBottom(): void { + if (this.isNearBottom) { + this.scrollContainer.nativeElement.scroll({ + top: this.scrollContainer.nativeElement.scrollHeight, + left: 0, + behavior: 'smooth' + }); + } + } +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/confirm-modal/confirm-modal.component.css b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/confirm-modal/confirm-modal.component.css new file mode 100644 index 0000000000..128a1eacd6 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/confirm-modal/confirm-modal.component.css @@ -0,0 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +.warning-container { + width: 100%; + padding: 0.5em; + background-color: #f8f4c3; + color: #a5893c; + margin-bottom: 10px; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/confirm-modal/confirm-modal.component.html b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/confirm-modal/confirm-modal.component.html new file mode 100644 index 0000000000..77f2d4b209 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/confirm-modal/confirm-modal.component.html @@ -0,0 +1,51 @@ + + + + + diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/confirm-modal/confirm-modal.component.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/confirm-modal/confirm-modal.component.ts new file mode 100644 index 0000000000..20a58f9237 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/confirm-modal/confirm-modal.component.ts @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +import { Component, OnInit } from '@angular/core'; +import { BsModalRef } from 'ngx-bootstrap'; +import { PropertyValidatorService } from '../../../services/property-validator.service'; + +@Component({ + selector: 'winery-live-modeling-confirm-modal', + templateUrl: './confirm-modal.component.html', + styleUrls: ['./confirm-modal.component.css'] +}) +export class ConfirmModalComponent implements OnInit { + title: string; + content: string; + showWarning = false; + showStartOption = false; + + startInstance = true; + confirmed = false; + + constructor(private bsModalRef: BsModalRef, + private propertyValidatorService: PropertyValidatorService + ) { + } + + ngOnInit(): void { + } + + cancel() { + this.confirmed = false; + this.dismissModal(); + } + + confirm() { + this.confirmed = true; + this.dismissModal(); + } + + isTopologyInvalid(): boolean { + return this.propertyValidatorService.isTopologyInvalid(); + } + + dismissModal() { + this.bsModalRef.hide(); + } +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/disable-modal/disable-modal.component.css b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/disable-modal/disable-modal.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/disable-modal/disable-modal.component.html b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/disable-modal/disable-modal.component.html new file mode 100644 index 0000000000..b39874fa6a --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/disable-modal/disable-modal.component.html @@ -0,0 +1,29 @@ + + + + + diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/disable-modal/disable-modal.component.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/disable-modal/disable-modal.component.ts new file mode 100644 index 0000000000..0e93dd0563 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/disable-modal/disable-modal.component.ts @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { BsModalRef } from 'ngx-bootstrap'; +import { NgRedux } from '@angular-redux/store'; +import { IWineryState } from '../../../redux/store/winery.store'; +import { Subscription } from 'rxjs'; +import { LiveModelingService } from '../../../services/live-modeling.service'; + +@Component({ + selector: 'winery-live-modeling-disable-modal', + templateUrl: './disable-modal.component.html', + styleUrls: ['./disable-modal.component.css'] +}) +export class DisableModalComponent implements OnInit, OnDestroy { + subscriptions: Array = []; + currentServiceTemplateInstanceId: string; + + constructor(private bsModalRef: BsModalRef, + private ngRedux: NgRedux, + private liveModelingService: LiveModelingService, + ) { + } + + ngOnInit(): void { + this.subscriptions.push(this.ngRedux.select((state) => { + return state.liveModelingState.currentServiceTemplateInstanceId; + }) + .subscribe((instanceId) => { + this.currentServiceTemplateInstanceId = instanceId; + })); + } + + disable() { + this.liveModelingService.disable(); + this.dismissModal(); + } + + dismissModal() { + this.bsModalRef.hide(); + } + + ngOnDestroy() { + this.subscriptions.forEach((subscription) => { + subscription.unsubscribe(); + }); + } +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/enable-modal/enable-modal.component.css b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/enable-modal/enable-modal.component.css new file mode 100644 index 0000000000..afdb0f59be --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/enable-modal/enable-modal.component.css @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +.wrapper { + display: flex; +} + +.wrapper .dropdown { + flex: 1; +} + +.wrapper .dropdown-toggle { + width: 100%; +} + +.wrapper .dropdown-menu { + width: 100%; + text-align: right; +} + +.dropdown-item { + cursor: pointer; +} + +.input-wrapper { + position: relative; +} + +.input-wrapper-check::after { + font-family: 'FontAwesome'; + content: '\f00c'; + position: absolute; + color: green; + top: 7px; + right: 10px; +} + +.input-wrapper-times::after { + font-family: 'FontAwesome'; + content: '\f00d'; + position: absolute; + color: red; + top: 7px; + right: 10px; +} + +.is-valid { + border: 1px solid green; +} + +.has-error { + border: 1px solid red; +} + +.error-text { + color: red; +} + +.warning-container { + width: 100%; + padding: 0.5em; + background-color: #f8f4c3; + color: #a5893c; + margin-bottom: 10px; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/enable-modal/enable-modal.component.html b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/enable-modal/enable-modal.component.html new file mode 100644 index 0000000000..58db03b23c --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/enable-modal/enable-modal.component.html @@ -0,0 +1,64 @@ + + + + + diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/enable-modal/enable-modal.component.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/enable-modal/enable-modal.component.ts new file mode 100644 index 0000000000..6c42f5dc54 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/enable-modal/enable-modal.component.ts @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +import { Component } from '@angular/core'; +import { BsModalRef, BsModalService } from 'ngx-bootstrap'; +import { LiveModelingService } from '../../../services/live-modeling.service'; +import { BackendService } from '../../../services/backend.service'; +import { HttpClient } from '@angular/common/http'; +import { map } from 'rxjs/operators'; +import { SettingsModalComponent } from '../settings-modal/settings-modal.component'; +import { PropertyValidatorService } from '../../../services/property-validator.service'; + +@Component({ + selector: 'winery-live-modeling-enable-modal', + templateUrl: './enable-modal.component.html', + styleUrls: ['./enable-modal.component.css'] +}) +export class EnableModalComponent { + + containerUrl: string; + currentCsarId: string; + + testingContainerUrl = false; + isContainerUrlInvalid: boolean; + + startInstance = true; + + constructor(private bsModalRef: BsModalRef, + private liveModelingService: LiveModelingService, + private backendService: BackendService, + private http: HttpClient, + private modalService: BsModalService, + private propertyValidatorService: PropertyValidatorService + ) { + this.currentCsarId = this.normalizeCsarId(this.backendService.configuration.id); + this.containerUrl = 'http://' + window.location.hostname + ':1337'; + } + + normalizeCsarId(csarId: string) { + const csarEnding = '.csar'; + return csarId.endsWith(csarEnding) ? csarId : csarId + csarEnding; + } + + async enableLiveModeling() { + this.resetErrorsAndAnimations(); + this.testingContainerUrl = true; + try { + const isContainerUrlValid = await this.checkContainerUrl(); + if (isContainerUrlValid) { + await this.liveModelingService.init(this.startInstance, this.containerUrl); + this.dismissModal(); + } else { + this.isContainerUrlInvalid = true; + } + } catch (e) { + this.isContainerUrlInvalid = true; + } finally { + this.testingContainerUrl = false; + } + } + + checkContainerUrl(): Promise { + return this.http.get(this.containerUrl, { observe: 'response' }).pipe( + map((resp) => { + return resp.ok; + }), + ).toPromise(); + } + + resetErrorsAndAnimations() { + this.testingContainerUrl = null; + this.isContainerUrlInvalid = null; + } + + handleSettings() { + this.openModal(SettingsModalComponent); + } + + openModal(modal: any, options?: any) { + const defaultConfig = { backdrop: 'static' }; + this.modalService.show(modal, { ...defaultConfig, ...options }); + } + + isTopologyInvalid(): boolean { + return this.propertyValidatorService.isTopologyInvalid(); + } + + dismissModal() { + this.bsModalRef.hide(); + } +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/input-parameters-modal/input-parameters-modal.component.css b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/input-parameters-modal/input-parameters-modal.component.css new file mode 100644 index 0000000000..8d21488738 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/input-parameters-modal/input-parameters-modal.component.css @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +.wrapper { + display: flex; +} + +.wrapper .dropdown { + flex: 1; +} + +.wrapper .dropdown-toggle { + width: 100%; +} + +.wrapper .dropdown-menu { + width: 100%; + text-align: right; +} + +.dropdown-item { + cursor: pointer; +} + +.input-wrapper { + position: relative; +} + +.input-wrapper-check::after { + font-family: 'FontAwesome'; + content: '\f00c'; + position: absolute; + color: green; + top: 7px; + right: 10px; +} + +.input-wrapper-times::after { + font-family: 'FontAwesome'; + content: '\f00d'; + position: absolute; + color: red; + top: 7px; + right: 10px; +} + +.is-valid { + border: 1px solid green; +} + +.has-error { + border: 1px solid red; +} + +.error-text { + color: red; +} + +.modal-body.input-parameter { + max-height: 500px; + overflow-y: auto; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/input-parameters-modal/input-parameters-modal.component.html b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/input-parameters-modal/input-parameters-modal.component.html new file mode 100644 index 0000000000..71ab40b3de --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/input-parameters-modal/input-parameters-modal.component.html @@ -0,0 +1,39 @@ + + + + + diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/input-parameters-modal/input-parameters-modal.component.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/input-parameters-modal/input-parameters-modal.component.ts new file mode 100644 index 0000000000..415bfeac35 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/input-parameters-modal/input-parameters-modal.component.ts @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +import { Component, OnInit } from '@angular/core'; +import { BsModalRef } from 'ngx-bootstrap'; +import { InputParameter } from '../../../models/container/input-parameter.model'; + +@Component({ + selector: 'winery-live-modeling-input-parameters-modal', + templateUrl: './input-parameters-modal.component.html', + styleUrls: ['./input-parameters-modal.component.css'] +}) +export class InputParametersModalComponent implements OnInit { + inputParameters: InputParameter[]; + cancelled = true; + + constructor(private bsModalRef: BsModalRef, + ) { + } + + ngOnInit(): void { + } + + disableButton(): boolean { + for (let i = 0; i < this.inputParameters.length; i++) { + if (this.isUndefined(this.inputParameters[i].value)) { + return true; + } + } + return false; + } + + isUndefined(someString: string): boolean { + if (someString === null || + typeof someString === 'undefined' || + someString === '') { + return true; + } else { + return false; + } + } + + confirm() { + this.cancelled = false; + this.dismissModal(); + } + + cancel() { + this.cancelled = true; + this.dismissModal(); + } + + dismissModal() { + this.bsModalRef.hide(); + } +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/reconfigure-modal/reconfigure-modal.component.css b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/reconfigure-modal/reconfigure-modal.component.css new file mode 100644 index 0000000000..e984164c34 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/reconfigure-modal/reconfigure-modal.component.css @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +.reconfigure-icon-container { + position: relative; + padding: 1em; + display: flex; + flex-direction: column; + border: 2px solid #ddd; + cursor: pointer; + flex: 1 1 0px; +} + +.reconfigure-icon-container:hover { + border: 2px solid limegreen; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); +} + +.check-icon { + position: absolute; + top: 5px; + right: 8px; + font-size: x-large; + color: limegreen; + z-index: 10; +} + +.reconfigure-icon { + flex-grow: 1; + display: flex; + justify-content: center; + align-items: center; + font-size: 3em; +} + +.title { + margin: 10px 0; + font-size: large; + font-weight: bold; +} + +.desc { + margin-bottom: 0; +} + +.select-icon-container { + border: 2px solid limegreen; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); +} + +.disabled-icon-container { + color: #ddd; + pointer-events: none; + cursor: not-allowed; +} + +.warning-container { + width: 100%; + padding: 0.5em; + background-color: #f8f4c3; + color: #a5893c; + margin-bottom: 10px; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/reconfigure-modal/reconfigure-modal.component.html b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/reconfigure-modal/reconfigure-modal.component.html new file mode 100644 index 0000000000..006a62f7a9 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/reconfigure-modal/reconfigure-modal.component.html @@ -0,0 +1,74 @@ + + + + + diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/reconfigure-modal/reconfigure-modal.component.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/reconfigure-modal/reconfigure-modal.component.ts new file mode 100644 index 0000000000..df5704208f --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/reconfigure-modal/reconfigure-modal.component.ts @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +import { Component, OnInit } from '@angular/core'; +import { BsModalRef } from 'ngx-bootstrap'; +import { ReconfigureOptions } from '../../../models/enums'; +import { PropertyValidatorService } from '../../../services/property-validator.service'; + +@Component({ + selector: 'winery-live-modeling-reconfigure-modal', + templateUrl: './reconfigure-modal.component.html', + styleUrls: ['./reconfigure-modal.component.css'] +}) +export class ReconfigureModalComponent implements OnInit { + + selectedOption = ReconfigureOptions.NONE; + startInstance = true; + ReconfigureOptions = ReconfigureOptions; + + constructor(private bsModalRef: BsModalRef, + private propertyValidatorService: PropertyValidatorService + ) { + } + + ngOnInit(): void { + } + + isOptionSelected(redeployOption: ReconfigureOptions) { + return this.selectedOption === redeployOption; + } + + toggleOption(redeployOption: ReconfigureOptions) { + this.selectedOption === redeployOption ? this.selectedOption = ReconfigureOptions.NONE : this.selectedOption = redeployOption; + } + + cancel() { + this.selectedOption = ReconfigureOptions.NONE; + this.dismissModal(); + } + + confirm() { + this.dismissModal(); + } + + isTopologyInvalid(): boolean { + return this.propertyValidatorService.isTopologyInvalid(); + } + + dismissModal() { + this.bsModalRef.hide(); + } +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/settings-modal/settings-modal.component.css b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/settings-modal/settings-modal.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/settings-modal/settings-modal.component.html b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/settings-modal/settings-modal.component.html new file mode 100644 index 0000000000..8366161aa2 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/settings-modal/settings-modal.component.html @@ -0,0 +1,31 @@ + + + + + diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/settings-modal/settings-modal.component.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/settings-modal/settings-modal.component.ts new file mode 100644 index 0000000000..2342e9090c --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/modals/settings-modal/settings-modal.component.ts @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { BsModalRef } from 'ngx-bootstrap'; +import { NgRedux } from '@angular-redux/store'; +import { IWineryState } from '../../../redux/store/winery.store'; +import { LiveModelingActions } from '../../../redux/actions/live-modeling.actions'; +import { Subscription } from 'rxjs'; +import { LiveModelingSettings } from '../../../models/liveModelingSettings'; + +@Component({ + selector: 'winery-live-modeling-settings-modal', + templateUrl: './settings-modal.component.html', + styleUrls: ['./settings-modal.component.css'] +}) +export class SettingsModalComponent implements OnInit, OnDestroy { + + objectKeys = Object.keys; + settings: LiveModelingSettings; + subscriptions: Array = []; + + constructor(private bsModalRef: BsModalRef, + private ngRedux: NgRedux, + private liveModelingActions: LiveModelingActions, + ) { + } + + ngOnInit(): void { + this.subscriptions.push(this.ngRedux.select((state) => { + return state.liveModelingState.settings; + }) + .subscribe((settings) => { + this.settings = settings; + })); + } + + setSettings() { + this.ngRedux.dispatch(this.liveModelingActions.setSettings(this.settings)); + this.dismissModal(); + } + + getLabel(settingsKey: string) { + switch (settingsKey) { + case 'timeout': + return 'Timeout (ms)'; + case 'interval': + return 'Interval (ms)'; + case 'wineryEndpoint': + return 'Winery Endpoint'; + default: + return ''; + } + } + + dismissModal() { + this.bsModalRef.hide(); + } + + ngOnDestroy() { + this.subscriptions.forEach((subscription) => { + subscription.unsubscribe(); + }); + } +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/node-template/node-template.component.css b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/node-template/node-template.component.css new file mode 100644 index 0000000000..ade8ccaf14 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/node-template/node-template.component.css @@ -0,0 +1,106 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +.table-scrollable { + max-height: 200px; + overflow-y: auto; + font-size: x-small; +} + +.table-scrollable th { + padding: 0.25em; +} + +.table-scrollable td { + padding: 0.25em; +} + +.parameters-table { + width: 100%; + table-layout: fixed; + border-collapse: collapse; +} + +.parameters-table, +.parameters-table th, +.parameters-table td { + border: 1px solid #3d3d3d; +} + +.parameters-table th, +.parameters-table td { + width: 50%; + word-break: break-all; +} + +.parameters-table th { + background: #333; +} + +.parameters-table tr:nth-child(odd) { + background: #242424; +} + +.parameters-table tr:nth-child(even) { + background: #292929; +} + +.btn-live-modeling { + flex: 1 1 0px; + background-color: #353537; + border: 1px solid #007bff; + color: #007bff; + letter-spacing: 0.05em; + padding: 0.75em; + border-radius: 0.25em; + font-weight: bold; + font-size: x-small; +} + +.btn-live-modeling:hover { + cursor: pointer; + color: white; + background-color: #007bff; +} + +.btn-live-modeling:active { + cursor: pointer; + color: white; +} + +.btn-live-modeling-start { + border: 1px solid #28a745; + color: #28a745; +} + +.btn-live-modeling-start:hover { + background-color: #28a745; +} + +.btn-live-modeling-start:active { + background-color: #1e7e34; +} + +.btn-live-modeling-stop { + border: 1px solid #dc3545; + color: #dc3545; +} + +.btn-live-modeling-stop:hover { + background-color: #dc3545; +} + +.btn-live-modeling-stop:active { + background-color: #bd2130; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/node-template/node-template.component.html b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/node-template/node-template.component.html new file mode 100644 index 0000000000..ed5af15ddc --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/node-template/node-template.component.html @@ -0,0 +1,64 @@ + + +
+ +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + +
NameValue
{{key}}{{nodeTemplateInstanceData[key] || '(empty)'}}
+
+
+
+ + + + + + + +
+ +
+ No node template selected or found +
+
+
diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/node-template/node-template.component.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/node-template/node-template.component.ts new file mode 100644 index 0000000000..8d954170a0 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/node-template/node-template.component.ts @@ -0,0 +1,185 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ +import { Component, OnDestroy, OnInit, } from '@angular/core'; +import { of, Subject, Subscription } from 'rxjs'; +import { NgRedux } from '@angular-redux/store'; +import { IWineryState } from '../../redux/store/winery.store'; +import { NodeTemplateInstance } from '../../models/container/node-template-instance.model'; +import { LiveModelingService } from '../../services/live-modeling.service'; +import { distinctUntilChanged, switchMap, tap } from 'rxjs/operators'; +import { AdaptationAction, LiveModelingStates, NodeTemplateInstanceStates } from '../../models/enums'; +import { ConfirmModalComponent } from '../modals/confirm-modal/confirm-modal.component'; +import { BsModalService } from 'ngx-bootstrap'; +import { WineryActions } from '../../redux/actions/winery.actions'; + +@Component({ + selector: 'winery-live-modeling-sidebar-node-template', + templateUrl: './node-template.component.html', + styleUrls: ['./node-template.component.css'], +}) +export class NodeTemplateComponent implements OnInit, OnDestroy { + + subscriptions: Array = []; + fetchingData = false; + + selectedNodeId: string; + selectedNodeState: string; + nodeTemplateInstanceData: NodeTemplateInstance; + + liveModelingState: LiveModelingStates; + deploymentChanges: boolean; + + nodeSubject = new Subject(); + + objectKeys = Object.keys; + NodeTemplateInstanceStates = NodeTemplateInstanceStates; + + constructor(private ngRedux: NgRedux, + private liveModelingService: LiveModelingService, + private modalService: BsModalService, + private wineryActions: WineryActions) { + } + + ngOnInit() { + this.subscriptions.push(this.ngRedux.select((state) => { + return state.wineryState.sidebarContents; + }) + .subscribe((sidebarContents) => { + if (sidebarContents.nodeClicked && sidebarContents.template.id) { + this.nodeSubject.next(sidebarContents.template.id); + } else { + this.nodeSubject.next(null); + } + })); + + this.subscriptions.push(this.ngRedux.select((state) => { + return state.liveModelingState.state; + }) + .subscribe((state) => { + this.liveModelingState = state; + })); + + this.subscriptions.push(this.ngRedux.select((state) => { + return state.liveModelingState.deploymentChanges; + }) + .subscribe((deploymentChanges) => { + this.deploymentChanges = deploymentChanges; + })); + + this.subscriptions.push(this.nodeSubject.pipe( + distinctUntilChanged(), + tap((nodeId) => { + this.selectedNodeId = nodeId; + this.selectedNodeState = NodeTemplateInstanceStates.NOT_AVAILABLE; + }), + tap((_) => { + this.fetchingData = true; + }), + switchMap((nodeId) => { + return nodeId ? this.liveModelingService.fetchNodeTemplateInstanceData(nodeId) : of(null); + }) + ).subscribe((resp) => { + this.updateNodeInstanceData(resp); + this.fetchingData = false; + })); + + } + + enableControlButtons(): boolean { + return this.selectedNodeId && + this.checkUpdatingData() && + this.checkLiveModelingState() && + this.checkNodeState(); + } + + checkUpdatingData(): boolean { + return !this.fetchingData && !this.deploymentChanges; + } + + checkLiveModelingState(): boolean { + return this.liveModelingState === LiveModelingStates.ENABLED; + } + + checkNodeState(): boolean { + return this.selectedNodeState === NodeTemplateInstanceStates.NOT_AVAILABLE || + this.selectedNodeState === NodeTemplateInstanceStates.STARTED || + this.selectedNodeState === NodeTemplateInstanceStates.STOPPED || + this.selectedNodeState === NodeTemplateInstanceStates.DELETED; + } + + async handleStartNode() { + const resp = await this.openConfirmModal( + 'Start Node Instance', + `Are you sure you want to start this node instance ${this.selectedNodeId}? + This might affect other node instances' state too.`); + if (resp) { + await this.liveModelingService.adapt(this.selectedNodeId, AdaptationAction.START_NODE); + this.unselectNodeTemplate(); + } + } + + async handleStopNode() { + const resp = await this.openConfirmModal( + 'Stop Node Instance', + `Are you sure you want to stop the node instance ${this.selectedNodeId}? + This might affect other node instances' state too.`); + if (resp) { + await this.liveModelingService.adapt(this.selectedNodeId, AdaptationAction.STOP_NODE); + this.unselectNodeTemplate(); + } + } + + async openConfirmModal(title: string, content: string, showWarning = false): Promise { + const initialState = { title, content, showWarning }; + const modalRef = this.modalService.show(ConfirmModalComponent, { initialState, backdrop: 'static' }); + await new Promise((resolve) => { + const subscription = this.modalService.onHidden.subscribe((_) => { + subscription.unsubscribe(); + resolve(); + }); + }); + + return modalRef.content.confirmed; + } + + unselectNodeTemplate() { + this.ngRedux.dispatch(this.wineryActions.triggerSidebar({ + sidebarVisible: false, + nodeClicked: false, + id: '', + nameTextFieldValue: '', + type: '', + properties: '', + source: '', + target: '' + })); + this.ngRedux.dispatch(this.wineryActions.sendPaletteOpened(false)); + } + + ngOnDestroy() { + this.subscriptions.forEach((subscription) => { + subscription.unsubscribe(); + }); + } + + private updateNodeInstanceData(nodeTemplateInstanceData: NodeTemplateInstance): void { + if (nodeTemplateInstanceData) { + this.nodeTemplateInstanceData = nodeTemplateInstanceData; + this.selectedNodeState = nodeTemplateInstanceData.state; + } else { + this.nodeTemplateInstanceData = null; + this.selectedNodeState = NodeTemplateInstanceStates.NOT_AVAILABLE; + } + } +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/progressbar/progressbar.component.css b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/progressbar/progressbar.component.css new file mode 100644 index 0000000000..1eb18c68b8 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/progressbar/progressbar.component.css @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +.progressbar-container { + height: 12px; +} + +progressbar { + height: 100%; + border-radius: 0.25em; + font-size: smaller; + font-weight: bold; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/progressbar/progressbar.component.html b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/progressbar/progressbar.component.html new file mode 100644 index 0000000000..92b34a067a --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/progressbar/progressbar.component.html @@ -0,0 +1,17 @@ + + +
+ {{content}} +
diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/progressbar/progressbar.component.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/progressbar/progressbar.component.ts new file mode 100644 index 0000000000..8920dffc46 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/live-modeling/progressbar/progressbar.component.ts @@ -0,0 +1,91 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ +import { Component, OnDestroy, OnInit, } from '@angular/core'; +import { NgRedux } from '@angular-redux/store'; +import { IWineryState } from '../../redux/store/winery.store'; +import { Subscription } from 'rxjs'; +import { LiveModelingStates } from '../../models/enums'; +import { ProgressbarConfig } from 'ngx-bootstrap'; + +export function getProgressbarConfig(): ProgressbarConfig { + return Object.assign(new ProgressbarConfig(), { animate: true, striped: true, max: 100 }); +} + +@Component({ + selector: 'winery-live-modeling-sidebar-progressbar', + providers: [{ provide: ProgressbarConfig, useFactory: getProgressbarConfig }], + templateUrl: './progressbar.component.html', + styleUrls: ['./progressbar.component.css'], +}) +export class ProgressbarComponent implements OnInit, OnDestroy { + + showProgressbar: boolean; + subscriptions: Array = []; + content = ''; + + constructor(private ngRedux: NgRedux) { + } + + ngOnInit() { + this.subscriptions.push(this.ngRedux.select((state) => { + return state.liveModelingState.state; + }) + .subscribe((state) => { + this.toggleProgressbar(state); + this.updateProgressbarContent(state); + })); + } + + ngOnDestroy() { + this.subscriptions.forEach((subscription) => { + subscription.unsubscribe(); + }); + } + + private toggleProgressbar(liveModelingState: LiveModelingStates): void { + switch (liveModelingState) { + case LiveModelingStates.TERMINATED: + case LiveModelingStates.DISABLED: + case LiveModelingStates.ENABLED: + case LiveModelingStates.ERROR: + this.showProgressbar = false; + break; + default: + this.showProgressbar = true; + } + } + + private updateProgressbarContent(liveModelingState: LiveModelingStates): void { + switch (liveModelingState) { + case LiveModelingStates.INIT: + this.content = 'UPLOADING CSAR'; + break; + case LiveModelingStates.DEPLOY: + this.content = 'DEPLOYING INSTANCE'; + break; + case LiveModelingStates.UPDATE: + this.content = 'UPDATING INSTANCE'; + break; + case LiveModelingStates.RECONFIGURATE: + this.content = 'RECONFIGURING INSTANCE'; + break; + case LiveModelingStates.TERMINATE: + this.content = 'TERMINATING INSTANCE'; + break; + default: + this.content = ''; + break; + } + } +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/adaptation-payload.model.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/adaptation-payload.model.ts new file mode 100644 index 0000000000..3e49640017 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/adaptation-payload.model.ts @@ -0,0 +1,20 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +export interface AdaptationPayload { + source_node_templates: string[]; + source_relationship_templates: string[]; + target_node_templates: string[]; + target_relationship_templates: string[]; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/csar-upload.model.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/csar-upload.model.ts new file mode 100644 index 0000000000..0f0369e421 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/csar-upload.model.ts @@ -0,0 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +export class CsarUpload { + constructor(public url: string, public name: string, public enrich: string) { + } +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/csar.model.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/csar.model.ts new file mode 100644 index 0000000000..fb6edbe162 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/csar.model.ts @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ +import { ResourceSupport } from './resource-support.model'; + +export class Csar extends ResourceSupport { + authors: Array; + description: string; + display_name: string; + icon_url: string; + id: string; + image_url: string; + name: string; + version: string; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/input-parameter.model.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/input-parameter.model.ts new file mode 100644 index 0000000000..06f24c3794 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/input-parameter.model.ts @@ -0,0 +1,20 @@ +/******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +export class InputParameter { + name: string; + type: string; + required: string; + value: string; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/link-reference.model.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/link-reference.model.ts new file mode 100644 index 0000000000..e02f0b3dc1 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/link-reference.model.ts @@ -0,0 +1,17 @@ +/******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +export class LinkReference { + href: string; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/node-template-instance-resources.model.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/node-template-instance-resources.model.ts new file mode 100644 index 0000000000..329b7788d0 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/node-template-instance-resources.model.ts @@ -0,0 +1,20 @@ +/******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +import { ResourceSupport } from './resource-support.model'; +import { NodeTemplateInstance } from './node-template-instance.model'; + +export class NodeTemplateInstanceResources extends ResourceSupport { + node_template_instances: Array; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/node-template-instance.model.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/node-template-instance.model.ts new file mode 100644 index 0000000000..a7adcae1b9 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/node-template-instance.model.ts @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +import { ResourceSupport } from './resource-support.model'; + +export class NodeTemplateInstance extends ResourceSupport { + id: number; + node_template_id: string; + state: string; + service_template_instance_id: number; + csar_id: string; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/node-template-resources.model.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/node-template-resources.model.ts new file mode 100644 index 0000000000..5044b6408a --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/node-template-resources.model.ts @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ +import { ResourceSupport } from './resource-support.model'; +import { NodeTemplate } from './node-template.model'; + +export class NodeTemplateResources extends ResourceSupport { + node_templates: Array; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/node-template.model.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/node-template.model.ts new file mode 100644 index 0000000000..6cbb16920e --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/node-template.model.ts @@ -0,0 +1,20 @@ +/******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +import { ResourceSupport } from './resource-support.model'; + +export class NodeTemplate extends ResourceSupport { + id: string; + name: string; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/output-parameter.model.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/output-parameter.model.ts new file mode 100644 index 0000000000..29fab81ff1 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/output-parameter.model.ts @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +export class OutputParameter { + name: string; + value: string; + type: string; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/plan-instance-resources.model.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/plan-instance-resources.model.ts new file mode 100644 index 0000000000..2cace51be0 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/plan-instance-resources.model.ts @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ +import { PlanInstance } from './plan-instance.model'; +import { ResourceSupport } from './resource-support.model'; + +export class PlanInstanceResources extends ResourceSupport { + plan_instances: Array; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/plan-instance.model.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/plan-instance.model.ts new file mode 100644 index 0000000000..e10445649d --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/plan-instance.model.ts @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ +import { ResourceSupport } from './resource-support.model'; +import { InputParameter } from './input-parameter.model'; +import { OutputParameter } from './output-parameter.model'; +import { PlanLogEntry } from './plan-log-entry.model'; + +export class PlanInstance extends ResourceSupport { + correlation_id: string; + service_template_instance_id: number; + state: string; + type: string; + inputs: Array; + outputs: Array; + logs: Array; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/plan-log-entry.model.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/plan-log-entry.model.ts new file mode 100644 index 0000000000..7385ff9ff7 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/plan-log-entry.model.ts @@ -0,0 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +export class PlanLogEntry { + start_timestamp: string; + end_timestamp: string; + status: string; + type: string; + message: string; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/plan-resources.model.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/plan-resources.model.ts new file mode 100644 index 0000000000..0ec5afa511 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/plan-resources.model.ts @@ -0,0 +1,20 @@ +/******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +import { ResourceSupport } from './resource-support.model'; +import { Plan } from './plan.model'; + +export class PlanResources extends ResourceSupport { + plans: Array; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/plan.model.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/plan.model.ts new file mode 100644 index 0000000000..30259e943f --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/plan.model.ts @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ +import { ResourceSupport } from './resource-support.model'; +import { InputParameter } from './input-parameter.model'; +import { OutputParameter } from './output-parameter.model'; + +export class Plan extends ResourceSupport { + id: string; + plan_type: string; + input_parameters: Array; + output_parameters: Array; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/resource-support.model.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/resource-support.model.ts new file mode 100644 index 0000000000..9a3598e296 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/resource-support.model.ts @@ -0,0 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ +import { LinkReference } from './link-reference.model'; + +export class ResourceSupport { + _links: Map; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/service-template-instance-resources.model.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/service-template-instance-resources.model.ts new file mode 100644 index 0000000000..ac5fb4a09c --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/service-template-instance-resources.model.ts @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ +import { ResourceSupport } from './resource-support.model'; +import { ServiceTemplateInstance } from './service-template-instance'; + +export class ServiceTemplateInstanceResources extends ResourceSupport { + service_template_instances: Array; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/service-template-instance.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/service-template-instance.ts new file mode 100644 index 0000000000..def96d1b5b --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/service-template-instance.ts @@ -0,0 +1,20 @@ +/******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ +import { ResourceSupport } from './resource-support.model'; + +export class ServiceTemplateInstance extends ResourceSupport { + id: number; + service_template_id: string; + state: string; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/service-template.model.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/service-template.model.ts new file mode 100644 index 0000000000..f99753f232 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/container/service-template.model.ts @@ -0,0 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ +import { ResourceSupport } from './resource-support.model'; + +export class ServiceTemplate extends ResourceSupport { + name: string; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/customErrors.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/customErrors.ts new file mode 100644 index 0000000000..0f92420182 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/customErrors.ts @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +export abstract class LiveModelingError extends Error { + protected constructor() { + super(); + Object.setPrototypeOf(this, LiveModelingError.prototype); + } +} + +export class CreateLiveModelingTemplateError extends LiveModelingError { + constructor() { + super(); + Object.setPrototypeOf(this, CreateLiveModelingTemplateError.prototype); + this.message = 'There was an error while creating the temporary service template'; + } +} + +export class UploadCsarError extends LiveModelingError { + constructor() { + super(); + Object.setPrototypeOf(this, UploadCsarError.prototype); + this.message = 'There was an error while uploading the csar to the container'; + } +} + +export class RetrieveInputParametersError extends LiveModelingError { + constructor() { + super(); + Object.setPrototypeOf(this, RetrieveInputParametersError.prototype); + this.message = 'There was an error while retrieving the input plan parameters'; + } +} + +export class DeployInstanceError extends LiveModelingError { + constructor() { + super(); + Object.setPrototypeOf(this, DeployInstanceError.prototype); + this.message = 'There was an error while deploying the service template instance'; + } +} + +export class TransformInstanceError extends LiveModelingError { + constructor() { + super(); + Object.setPrototypeOf(this, TransformInstanceError.prototype); + this.message = 'There was an error while transforming the service template instance'; + } +} + +export class AdaptInstanceError extends LiveModelingError { + constructor() { + super(); + Object.setPrototypeOf(this, AdaptInstanceError.prototype); + this.message = 'There was an error while adapting the service template instance'; + } +} + +export class TerminateInstanceError extends LiveModelingError { + constructor() { + super(); + Object.setPrototypeOf(this, TerminateInstanceError.prototype); + this.message = 'There was an error while terminating the service template instance'; + } +} + +export class ServiceTemplateInstanceError extends LiveModelingError { + constructor() { + super(); + Object.setPrototypeOf(this, ServiceTemplateInstanceError.prototype); + this.message = 'The service template instance encountered an error'; + } +} + +export class NodeTemplateInstanceError extends LiveModelingError { + constructor() { + super(); + Object.setPrototypeOf(this, NodeTemplateInstanceError.prototype); + this.message = 'The node template instance encountered an error'; + } +} + +export class TimeoutError extends LiveModelingError { + constructor() { + super(); + Object.setPrototypeOf(this, TimeoutError.prototype); + this.message = 'The operation has timed out'; + } +} + +export class UnauthorizedActionError extends LiveModelingError { + constructor() { + super(); + Object.setPrototypeOf(this, UnauthorizedActionError.prototype); + this.message = 'You are currently not allowed to perform this action'; + } +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/enums.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/enums.ts index 75ca257d5e..4c95a343b2 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/enums.ts +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/enums.ts @@ -57,3 +57,86 @@ export enum PropertyDefinitionType { XML = 'XML', YAML = 'YAML', } + +export enum PropertyKVType { + XSD_STRING = 'xsd:string', + XSD_FLOAT = 'xsd:float', + XSD_DECIMAL = 'xsd:decimal', + XSD_ANYURI = 'xsd:anyURI', + XSD_QNAME = 'xsd:QName', +} + +export enum LiveModelingStates { + DISABLED = 'DISABLED', + INIT = 'INIT', + DEPLOY = 'DEPLOY', + TERMINATE = 'TERMINATE', + TERMINATED = 'TERMINATED', + ENABLED = 'ENABLED', + RECONFIGURATE = 'RECONFIGURATE', + UPDATE = 'UPDATE', + ERROR = 'ERROR' +} + +export enum PlanTypes { + BuildPlan = 'http://docs.oasis-open.org/tosca/ns/2011/12/PlanTypes/BuildPlan', + TerminationPlan = 'http://docs.oasis-open.org/tosca/ns/2011/12/PlanTypes/TerminationPlan', + ManagementPlan = 'http://docs.oasis-open.org/tosca/ns/2011/12/PlanTypes/ManagementPlan', + TransformationPlan = 'http://opentosca.org/plantypes/TransformationPlan' +} + +export enum ServiceTemplateInstanceStates { + INITIAL = 'INITIAL', + CREATING = 'CREATING', + CREATED = 'CREATED', + DELETING = 'DELETING', + DELETED = 'DELETED', + ERROR = 'ERROR', + MIGRATING = 'MIGRATING', + MIGRATED = 'MIGRATED', + NOT_AVAILABLE = 'NOT_AVAILABLE', +} + +export enum NodeTemplateInstanceStates { + INITIAL = 'INITIAL', + CREATING = 'CREATING', + CREATED = 'CREATED', + CONFIGURING = 'CONFIGURING', + CONFIGURED = 'CONFIGURED', + STARTING = 'STARTING', + STARTED = 'STARTED', + STOPPING = 'STOPPING', + STOPPED = 'STOPPED', + DELETING = 'DELETING', + DELETED = 'DELETED', + ERROR = 'ERROR', + MIGRATED = 'MIGRATED', + NOT_AVAILABLE = 'NOT_AVAILABLE', +} + +export enum LiveModelingLogTypes { + SUCCESS = 'success', + INFO = 'info', + WARNING = 'warning', + DANGER = 'danger', + CONTAINER = 'container' +} + +export enum LiveModelingButtons { + START, + TERMINATE, + REFRESH, + SWITCH, + RECONFIGURATE, +} + +export enum ReconfigureOptions { + NONE, + REDEPLOY, + TRANSFORM, +} + +export enum AdaptationAction { + START_NODE, + STOP_NODE +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/liveModelingLog.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/liveModelingLog.ts new file mode 100644 index 0000000000..384951af00 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/liveModelingLog.ts @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ +import { LiveModelingLogTypes } from './enums'; + +export class LiveModelingLog { + public timestamp: any; + + constructor(public message: string, public type: LiveModelingLogTypes, public value?: number) { + this.timestamp = new Date().toLocaleTimeString(); + } +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/liveModelingSettings.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/liveModelingSettings.ts new file mode 100644 index 0000000000..e42adf6927 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/liveModelingSettings.ts @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +export class LiveModelingSettings { + + constructor(public timeout: number, + public interval: number, + public wineryEndpoint: string) { + } + + public static initial() { + return new LiveModelingSettings(600000, 2000, 'http://localhost:8080/winery'); + } +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/topologyModelerConfiguration.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/topologyModelerConfiguration.ts index f98fca82ea..e69d797b24 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/topologyModelerConfiguration.ts +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/topologyModelerConfiguration.ts @@ -19,6 +19,7 @@ export class TopologyModelerConfiguration { public readonly definitionsElement: QName; public readonly idPrefix; public readonly relationshipPrefix; + public readonly parentUrl: string; public readonly parentElementUrl: string; public readonly elementUrl: string; @@ -45,7 +46,8 @@ export class TopologyModelerConfiguration { } this.relationshipPrefix = this.idPrefix + 'con'; - this.parentElementUrl = this.repositoryURL + '/' + this.parentPath + '/' + this.parentUrl = this.repositoryURL + '/' + this.parentPath + '/'; + this.parentElementUrl = this.parentUrl + encodeURIComponent(encodeURIComponent(this.ns)) + '/' + this.id + '/'; this.elementUrl = this.parentElementUrl + this.elementPath; } diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/topologyTemplateUtil.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/topologyTemplateUtil.ts index 593ea9704e..9b51fbbbe6 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/topologyTemplateUtil.ts +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/topologyTemplateUtil.ts @@ -25,6 +25,8 @@ import { RequirementModel } from './requirementModel'; import { InheritanceUtils } from './InheritanceUtils'; import { QName } from '../../../../shared/src/app/model/qName'; import { TPolicy } from './policiesModalData'; +import * as _ from 'lodash'; +import { TNode } from '@angular/core/src/render3/interfaces/node'; export abstract class TopologyTemplateUtil { @@ -172,6 +174,9 @@ export abstract class TopologyTemplateUtil { node.deploymentArtifacts ? node.deploymentArtifacts : [], node.policies ? node.policies : [], node.artifacts ? node.artifacts : [], + node.instanceState, + node.valid, + node.working, state ); } @@ -183,7 +188,7 @@ export abstract class TopologyTemplateUtil { relationship.name, relationship.id, relationship.type, - relationship.properties, + relationship.properties ? relationship.properties : {}, relationship.documentation, relationship.any, relationship.otherAttributes, @@ -305,4 +310,109 @@ export abstract class TopologyTemplateUtil { relationship => ngRedux.dispatch(wineryActions.saveRelationship(relationship)) ); } + + static cloneTopologyTemplate(topologyTemplate: TTopologyTemplate): TTopologyTemplate { + return _.cloneDeep(topologyTemplate); + } + + static objectsEquals(o1: Object, o2: Object): boolean { + if (o1 === null && o2 === null) { + return true; + } + + return typeof o1 === 'object' && Object.keys(o1).length > 0 + ? Object.keys(o1).length === Object.keys(o2).length + && Object.keys(o1).every((p) => { + return this.objectsEquals(o1[p], o2[p]); + }) + : JSON.stringify(o1) === JSON.stringify(o2); + } + + static hasTopologyTemplateChanged(currentTopology: TTopologyTemplate, lastSavedTopology: TTopologyTemplate): boolean { + + if (lastSavedTopology == null) { + return true; + } + + if (currentTopology.nodeTemplates.length !== lastSavedTopology.nodeTemplates.length || + currentTopology.relationshipTemplates.length !== lastSavedTopology.relationshipTemplates.length) { + return true; + } + for (const currentNodeTemplate of currentTopology.nodeTemplates) { + const lastSavedNodeTemplate = this.findLastSaveNodeTemplate(lastSavedTopology, currentNodeTemplate); + if (!lastSavedNodeTemplate) { + return true; + } + + if (this.nodeTemplatesDifferent(currentNodeTemplate, lastSavedNodeTemplate)) { + return true; + } + } + for (const currentRelationshipTemplate of currentTopology.relationshipTemplates) { + const lastSavedRelationshipTemplate = this.findLastSavedRelationshipTemplate(lastSavedTopology, currentRelationshipTemplate); + if (!lastSavedRelationshipTemplate) { + return true; + } + if (this.relationshipTemplatesDifferent(currentRelationshipTemplate, lastSavedRelationshipTemplate)) { + return true; + } + } + return false; + } + + static findLastSaveNodeTemplate(lastSavedTopology: TTopologyTemplate, currentNodeTemplate: TNodeTemplate): TNodeTemplate { + return lastSavedTopology.nodeTemplates.find((nodeTemplate) => { + return nodeTemplate.id === currentNodeTemplate.id; + }); + } + + static findLastSavedRelationshipTemplate(lastSavedTopology: TTopologyTemplate, currentRelationshipTemplate: TRelationshipTemplate): TRelationshipTemplate { + return lastSavedTopology.relationshipTemplates.find((relationshipTemplate) => { + return relationshipTemplate.sourceElement.ref === currentRelationshipTemplate.sourceElement.ref && + relationshipTemplate.targetElement.ref === currentRelationshipTemplate.targetElement.ref && + relationshipTemplate.type === currentRelationshipTemplate.type; + } + ); + } + + static relationshipTemplatesDifferent(currentRelationshipTemplate: TRelationshipTemplate, lastSavedRelationshipTemplate: TRelationshipTemplate): boolean { + if (currentRelationshipTemplate.name !== lastSavedRelationshipTemplate.name) { + return true; + } + if (!this.objectsEquals(currentRelationshipTemplate.properties, lastSavedRelationshipTemplate.properties)) { + return true; + } + if (!this.objectsEquals(currentRelationshipTemplate.policies, lastSavedRelationshipTemplate.policies)) { + return true; + } + return false; + } + + static nodeTemplatesDifferent(currentNodeTemplate: TNodeTemplate, lastSavedNodeTemplate: TNodeTemplate): boolean { + if (!this.objectsEquals(currentNodeTemplate.properties, lastSavedNodeTemplate.properties)) { + return true; + } + if (currentNodeTemplate.name !== lastSavedNodeTemplate.name) { + return true; + } + if (currentNodeTemplate.minInstances !== lastSavedNodeTemplate.minInstances) { + return true; + } + if (currentNodeTemplate.maxInstances !== lastSavedNodeTemplate.maxInstances) { + return true; + } + if (!this.objectsEquals(currentNodeTemplate.capabilities, lastSavedNodeTemplate.capabilities)) { + return true; + } + if (!this.objectsEquals(currentNodeTemplate.requirements, lastSavedNodeTemplate.requirements)) { + return true; + } + if (!this.objectsEquals(currentNodeTemplate.deploymentArtifacts, lastSavedNodeTemplate.deploymentArtifacts)) { + return true; + } + if (!this.objectsEquals(currentNodeTemplate.policies, lastSavedNodeTemplate.policies)) { + return true; + } + return false; + } } diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/ttopology-template.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/ttopology-template.ts index d9d00b02f1..2b72ab3e61 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/ttopology-template.ts +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/models/ttopology-template.ts @@ -17,6 +17,7 @@ import { TPolicy } from './policiesModalData'; import { Interface } from '../../../../tosca-management/src/app/model/interfaces'; import { PropertiesDefinition } from '../../../../tosca-management/src/app/instance/sharedComponents/propertiesDefinition/propertiesDefinitionsResourceApiData'; import { Constraint } from '../../../../tosca-management/src/app/model/constraint'; +import { NodeTemplateInstanceStates } from './enums'; export class AbstractTEntity { constructor(public documentation?: any, @@ -89,7 +90,10 @@ export class TNodeTemplate extends AbstractTEntity { public deploymentArtifacts?: any[], public policies?: Array, public artifacts?: Array, - public _state?: DifferenceStates) { + public instanceState?: NodeTemplateInstanceStates, + public valid?: boolean, + public working?: boolean, + private _state?: DifferenceStates) { super(documentation, any, otherAttributes); } @@ -104,7 +108,7 @@ export class TNodeTemplate extends AbstractTEntity { generateNewNodeTemplateWithUpdatedAttribute(updatedAttribute: string, updatedValue: any): TNodeTemplate { const nodeTemplate = new TNodeTemplate(this.properties, this.id, this.type, this.name, this.minInstances, this.maxInstances, this.visuals, this.documentation, this.any, this.otherAttributes, this.x, this.y, this.capabilities, - this.requirements, this.deploymentArtifacts, this.policies, this.artifacts); + this.requirements, this.deploymentArtifacts, this.policies, this.artifacts, this.instanceState, this.valid, this.working, this._state); if (updatedAttribute === 'coordinates') { nodeTemplate.x = updatedValue.x; nodeTemplate.y = updatedValue.y; @@ -181,8 +185,11 @@ export class TNodeTemplate extends AbstractTEntity { this.visuals.color = VersionUtils.getElementColorByDiffState(value); } - public deleteStateAndVisuals() { + public deleteAppendix() { delete this._state; + delete this.instanceState; + delete this.valid; + delete this.working; delete this.visuals; } } diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/navbar/navbar.component.css b/org.eclipse.winery.frontends/app/topologymodeler/src/app/navbar/navbar.component.css index 5d703a81a2..f35544c0f6 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/navbar/navbar.component.css +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/navbar/navbar.component.css @@ -64,3 +64,12 @@ .button-group { margin-left: 5px; } + +.disable-icon { + color: grey; +} + +::ng-deep .tooltip-check-properties .tooltip-inner { + white-space: nowrap; + max-width: none; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/navbar/navbar.component.html b/org.eclipse.winery.frontends/app/topologymodeler/src/app/navbar/navbar.component.html index c9ed1fc844..9a6111b8e0 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/navbar/navbar.component.html +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/navbar/navbar.component.html @@ -17,13 +17,14 @@

winery

-
+
-->
-
+
-
- -
-
- -
@@ -206,31 +209,6 @@

winery

-
- -
-
- -
-
- -
-
- -
-
- - -
@@ -424,7 +377,7 @@

winery

-
Only show selected Mapping Types:
-
+

Note: It's only possible to edit PRM Mappings here. Detector and Refinement Structure must be defined separately

+ + + + + + diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/navbar/navbar.component.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/navbar/navbar.component.ts index 1e86a37048..d4b23f16c8 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/navbar/navbar.component.ts +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/navbar/navbar.component.ts @@ -12,7 +12,7 @@ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 ********************************************************************************/ -import { Component, ElementRef, Input, OnDestroy, ViewChild } from '@angular/core'; +import { Component, ElementRef, Input, OnDestroy, TemplateRef, ViewChild } from '@angular/core'; import { animate, style, transition, trigger } from '@angular/animations'; import { ToastrService } from 'ngx-toastr'; import { NgRedux } from '@angular-redux/store'; @@ -24,9 +24,16 @@ import { Hotkey, HotkeysService } from 'angular2-hotkeys'; import { TopologyRendererState } from '../redux/reducers/topologyRenderer.reducer'; import { WineryActions } from '../redux/actions/winery.actions'; import { StatefulAnnotationsService } from '../services/statefulAnnotations.service'; -import { FeatureEnum } from '../../../../tosca-management/src/app/wineryFeatureToggleModule/wineryRepository.feature.direct'; -import { WineryRepositoryConfigurationService } from '../../../../tosca-management/src/app/wineryFeatureToggleModule/WineryRepositoryConfiguration.service'; +import { + FeatureEnum +} from '../../../../tosca-management/src/app/wineryFeatureToggleModule/wineryRepository.feature.direct'; +import { + WineryRepositoryConfigurationService +} from '../../../../tosca-management/src/app/wineryFeatureToggleModule/WineryRepositoryConfiguration.service'; import { TTopologyTemplate } from '../models/ttopology-template'; +import { OverlayService } from '../services/overlay.service'; +import { BsModalRef } from 'ngx-bootstrap'; +import { TopologyService } from '../services/topology.service'; import { VersionSliderService } from '../version-slider/version-slider.service'; import { CheService } from '../services/che.service'; import { TopologyModelerConfiguration } from '../models/topologyModelerConfiguration'; @@ -67,23 +74,44 @@ export class NavbarComponent implements OnDestroy { placingOngoing: boolean; showVersionSliderButton: boolean; configEnum = FeatureEnum; + unsavedChanges: boolean; + modalRef: BsModalRef; + @ViewChild('exportCsarButton') private exportCsarButtonRef: ElementRef; + @ViewChild('confirmModal') + private confirmModalRef: TemplateRef; constructor(private alert: ToastrService, private ngRedux: NgRedux, private actions: TopologyRendererActions, private wineryActions: WineryActions, - private backendService: BackendService, + public backendService: BackendService, private statefulService: StatefulAnnotationsService, private hotkeysService: HotkeysService, - private versionSliderService: VersionSliderService, + private overlayService: OverlayService, + private topologyService: TopologyService, public configurationService: WineryRepositoryConfigurationService, + private versionSliderService: VersionSliderService, private che: CheService) { - this.subscriptions.push(ngRedux.select(state => state.topologyRendererState) - .subscribe(newButtonsState => this.setButtonsState(newButtonsState))); - this.subscriptions.push(ngRedux.select(currentState => currentState.wineryState.currentJsonTopology) - .subscribe(topologyTemplate => this.currentTopologyTemplate = topologyTemplate)); + this.subscriptions.push(ngRedux.select((state) => { + return state.topologyRendererState; + }) + .subscribe((newButtonsState) => { + this.setButtonsState(newButtonsState); + })); + this.subscriptions.push(ngRedux.select((currentState) => { + return currentState.wineryState.currentJsonTopology; + }) + .subscribe((topologyTemplate) => { + this.currentTopologyTemplate = topologyTemplate; + })); + this.subscriptions.push(ngRedux.select((currentState) => { + return currentState.wineryState.unsavedChanges; + }) + .subscribe((unsavedChanges) => { + this.unsavedChanges = unsavedChanges; + })); this.hotkeysService.add(new Hotkey('mod+s', (event: KeyboardEvent): boolean => { event.stopPropagation(); this.saveTopologyTemplateToRepository(); @@ -169,6 +197,7 @@ export class NavbarComponent implements OnDestroy { } case 'properties': { this.ngRedux.dispatch(this.actions.toggleProperties()); + this.toggleCheckNodePropertiesIfNecessary(); break; } case 'types': { @@ -303,25 +332,52 @@ export class NavbarComponent implements OnDestroy { this.ngRedux.dispatch(this.actions.showOnlyMappingsOfSelectedType(event.target.value)); } + toggleCheckNodeProperties() { + if (this.navbarButtonsState.buttonsState.propertiesButton) { + this.ngRedux.dispatch(this.actions.toggleCheckNodeProperties()); + } else if (!this.navbarButtonsState.buttonsState.propertiesButton && !this.navbarButtonsState.buttonsState.checkNodePropertiesButton) { + this.ngRedux.dispatch(this.actions.toggleProperties()); + this.ngRedux.dispatch(this.actions.toggleCheckNodeProperties()); + } + } + + toggleCheckNodePropertiesIfNecessary() { + if (!this.navbarButtonsState.buttonsState.propertiesButton && this.navbarButtonsState.buttonsState.checkNodePropertiesButton) { + this.ngRedux.dispatch(this.actions.toggleCheckNodeProperties()); + } + } + /** * Calls the BackendService's saveTopologyTemplate method and displays a success message if successful. */ saveTopologyTemplateToRepository() { - // The topology gets saved here. + this.overlayService.showOverlay('Saving topology template. This may take a while.'); this.backendService.saveTopologyTemplate(this.currentTopologyTemplate) - .subscribe(res => { - res.ok === true ? this.alert.success('

Saved the topology!
' + 'Response Status: ' - + res.statusText + ' ' + res.status + '

') - : this.alert.info('

Something went wrong!
' + 'Response Status: ' - + res.statusText + ' ' + res.status + '

'); - }, err => this.alert.error(err.error)); + .subscribe((res) => { + if (res.ok) { + this.alert.success('

Saved the topology!
' + 'Response Status: ' + + res.statusText + ' ' + res.status + '

'); + } else { + this.alert.info('

Something went wrong!
' + 'Response Status: ' + + res.statusText + ' ' + res.status + '

'); + } + }, (err) => { + this.alert.error(err.error); + }) + .add(() => { + this.topologyService.checkForSaveChanges(); + this.topologyService.checkForDeployChanges(); + this.overlayService.hideOverlay(); + }); } /** * Angular lifecycle event. */ ngOnDestroy() { - this.subscriptions.forEach(subscription => subscription.unsubscribe()); + this.subscriptions.forEach((subscription) => { + subscription.unsubscribe(); + }); } openManagementUi() { diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/navbar/navbar.module.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/navbar/navbar.module.ts new file mode 100644 index 0000000000..3179cd29f7 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/navbar/navbar.module.ts @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2021 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ +import { ModuleWithProviders, NgModule } from '@angular/core'; +import { NavbarComponent } from './navbar.component'; +import { CommonModule } from '@angular/common'; +import { WineryFeatureToggleModule } from '../../../../tosca-management/src/app/wineryFeatureToggleModule/winery-feature-toggle.module'; +import { BsDropdownModule, TooltipModule } from 'ngx-bootstrap'; + +@NgModule({ + imports: [ + CommonModule, + WineryFeatureToggleModule, + TooltipModule, + BsDropdownModule + + ], + declarations: [ + NavbarComponent + ], + exports: [ + NavbarComponent, + ], +}) +export class NavbarModule { + + static forRoot(): ModuleWithProviders { + return { + ngModule: NavbarModule, + providers: [ + ] + }; + } + +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/node/node.component.css b/org.eclipse.winery.frontends/app/topologymodeler/src/app/node/node.component.css index 543a5129d1..7e48c612e7 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/node/node.component.css +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/node/node.component.css @@ -13,11 +13,12 @@ ********************************************************************************/ div.node { + padding: 3px; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; - z-index: 6; + z-index: 60; font-size: small; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); background: #ffffff; @@ -106,6 +107,10 @@ div.unselected { margin: 0; } +div.working { + pointer-events: none; +} + .node-template-header { margin: 0; display: flex; @@ -212,3 +217,44 @@ button.btn.btn-sm.btn-outline-secondary:hover, button.btn.btn-sm.btn-outline-sec color: red; font-size: 20px; } + +.live-modeling-column { + align-self: start; + padding: 3px; +} + +.live-modeling-column .node-instance-status { + cursor: pointer; + margin: 0 auto; + background-color: black; + border: 1px solid black; + height: 10px; + border-radius: 50%; + -moz-border-radius: 50%; + -webkit-border-radius: 50%; + width: 10px; +} + +.has-error { + border: 1px solid red; + border-radius: 0.25rem; +} + +div.overlay { + height: 100%; + width: 100%; + background-color: rgba(0, 0, 0, 0.3); + z-index: 10; + position: absolute; + top: 0; + left: 0; + border-radius: 1em; + pointer-events: none; +} + +div.overlay .overlay-icon { + position: absolute; + right: 0; + top: 0; + transform: translate(50%, -50%); +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/node/node.component.html b/org.eclipse.winery.frontends/app/topologymodeler/src/app/node/node.component.html index 69d7de48bb..264bb75507 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/node/node.component.html +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/node/node.component.html @@ -12,308 +12,330 @@ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 --> -
-
- {{ nodeTemplate.state }} -
+
-
- Policy icon -
- -
- - -
- -
-
- - - - - - - - - - -
{{nodeTemplate.maxInstances}} -

{{nodeTemplate.minInstances}} -
+
+ {{ nodeTemplate.state }}
-
- Node Template icon placeholder - Node Template icon placeholder + +
+ Policy icon
-
- - - - - - - - - -
-
- - -
-
{{nodeTemplate.id}}
- {{nodeTemplate.id}} -
-
{{nodeTemplate.name}}
- {{nodeTemplate.name}} -
-
({{nodeTypeLocalName}})
- {{ (nodeTypeLocalName)}} -
+ +
+ +
-
- -
-
- Pattern icon placeholder - Pattern icon +
+ + + + + + + + + + +
{{nodeTemplate.maxInstances}} +

{{nodeTemplate.minInstances}} +
+
+
+ Node Template icon placeholder + Node Template icon placeholder +
+
+ + + + + + + + + +
+
+ + +
+
{{nodeTemplate.id}}
+ {{nodeTemplate.id}} +
+
{{nodeTemplate.name}}
+ {{nodeTemplate.name}} +
+
({{nodeTypeLocalName}})
+ {{ (nodeTypeLocalName)}} +
+
+
+
+
- + +
+
+ Pattern icon placeholder + Pattern icon +
+
+
-
-
-
- +
+
+
+ +
-
- - - - + + + -
- Properties - -
- - -
+ *ngIf="navbarButtonsState.buttonsState.propertiesButton" + [class.has-error]="!nodeTemplate.valid && !propertiesgroup?.isOpen"> +
+ Properties + +
+ + +
- - -
- {{this.configurationService.isYaml() ? 'Artifacts' : 'Deployment Artifacts'}} - +
+ {{this.configurationService.isYaml() ? 'Artifacts' : 'Deployment Artifacts'}} + -
-
+
+ - -
+ [yamlArtifacts]="nodeTemplate.artifacts"> + - - -
- Requirements - +
+ Requirements + -
-
+
+ - -
+ + - - -
- Capabilities - +
+ Capabilities + -
-
+
+ - -
+ + - - -
- Groups - - -
- - -
+ + +
+ Groups + + +
+ + +
- - -
- Participants - - -
- - -
+ + +
+ Participants + + +
+ + +
- - -
- Deployment Technologies - - -
- - -
+ + +
+ Deployment Technologies + + +
+ + +
- - -
- Policies - - -
- +
+ Policies + + +
+ - - + + - -
+ [policies]="nodeTemplate.policies"> + + - - -
- Target Locations - +
+ Target Locations + - -
- +
+ - -
-
+ + + +
+
+
+ +
+
diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/node/node.component.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/node/node.component.ts index 12ecff93c2..c9cb7e880b 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/node/node.component.ts +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/node/node.component.ts @@ -21,7 +21,7 @@ import { NgRedux } from '@angular-redux/store'; import { IWineryState } from '../redux/store/winery.store'; import { WineryActions } from '../redux/actions/winery.actions'; import { EntityType, OTParticipant, TGroupDefinition, TNodeTemplate } from '../models/ttopology-template'; -import { PropertyDefinitionType, urlElement } from '../models/enums'; +import { LiveModelingStates, NodeTemplateInstanceStates, PropertyDefinitionType, urlElement } from '../models/enums'; import { BackendService } from '../services/backend.service'; import { GroupedNodeTypeModel } from '../models/groupedNodeTypeModel'; import { EntityTypesModel } from '../models/entityTypesModel'; @@ -32,9 +32,14 @@ import { Visuals } from '../models/visuals'; import { VersionElement } from '../models/versionElement'; import { VersionsComponent } from './versions/versions.component'; import { WineryVersion } from '../../../../tosca-management/src/app/model/wineryVersion'; -import { FeatureEnum } from '../../../../tosca-management/src/app/wineryFeatureToggleModule/wineryRepository.feature.direct'; -import { WineryRepositoryConfigurationService } from '../../../../tosca-management/src/app/wineryFeatureToggleModule/WineryRepositoryConfiguration.service'; +import { + FeatureEnum +} from '../../../../tosca-management/src/app/wineryFeatureToggleModule/wineryRepository.feature.direct'; +import { PropertiesComponent } from '../properties/properties.component'; import { Subscription } from 'rxjs'; +import { + WineryRepositoryConfigurationService +} from '../../../../tosca-management/src/app/wineryFeatureToggleModule/WineryRepositoryConfiguration.service'; import { InheritanceUtils } from '../models/InheritanceUtils'; import { QName } from '../../../../shared/src/app/model/qName'; import { DetailsSidebarState } from '../sidebars/node-details/node-details-sidebar'; @@ -72,7 +77,7 @@ export class NodeComponent implements OnInit, AfterViewInit, OnDestroy, DoCheck connectorEndpointVisible = false; startTime; endTime; - longpress = false; + longPress = false; makeSelectionVisible = false; setFlash = false; setMaxFlash = false; @@ -90,6 +95,7 @@ export class NodeComponent implements OnInit, AfterViewInit, OnDestroy, DoCheck @Input() readonly: boolean; @Input() entityTypes: EntityTypesModel; + @Input() nodeEntityType: EntityType; @Input() dragSource: string; @Input() navbarButtonsState: TopologyRendererState; @Input() nodeTemplate: TNodeTemplate; @@ -110,6 +116,7 @@ export class NodeComponent implements OnInit, AfterViewInit, OnDestroy, DoCheck @Output() showYamlPolicyManagementModal: EventEmitter; @ViewChild('versionModal') versionModal: VersionsComponent; + @ViewChild('nodeProperties') nodePropertiesComponent: PropertiesComponent; previousPosition: any; currentPosition: any; nodeRef: ComponentRef; @@ -124,8 +131,13 @@ export class NodeComponent implements OnInit, AfterViewInit, OnDestroy, DoCheck newerVersions: WineryVersion[]; newerVersionExist: boolean; newVersionElement: VersionElement; - private policyChangeSubscription: Subscription; - private artifactsChangedSubscription: Subscription; + + liveModelingEnabled = false; + subscriptions: Subscription[] = []; + NodeTemplateInstanceStates = NodeTemplateInstanceStates; + + private readonly policyChangeSubscription: Subscription; + private readonly artifactsChangedSubscription: Subscription; constructor(private zone: NgZone, private $ngRedux: NgRedux, @@ -271,6 +283,13 @@ export class NodeComponent implements OnInit, AfterViewInit, OnDestroy, DoCheck } this.addNewVersions(new QName(this.nodeTemplate.type)); + + this.subscriptions.push(this.$ngRedux.select((wineryState) => { + return wineryState.liveModelingState.state; + }) + .subscribe((liveModelingState) => { + this.liveModelingEnabled = liveModelingState !== LiveModelingStates.DISABLED; + })); } /** @@ -449,7 +468,7 @@ export class NodeComponent implements OnInit, AfterViewInit, OnDestroy, DoCheck */ closeConnectorEndpoints($event): void { $event.stopPropagation(); - if (!this.longpress && !$event.ctrlKey) { + if (!this.longPress && !$event.ctrlKey) { this.closedEndpoint.emit(this.nodeTemplate.id); this.repaint(new Event('repaint')); } @@ -472,20 +491,24 @@ export class NodeComponent implements OnInit, AfterViewInit, OnDestroy, DoCheck openSidebar($event): void { $event.stopPropagation(); // close sidebar when longpressing a node template - if (this.longpress) { + if (this.longPress) { this.sendPaletteStatus.emit('close Sidebar'); this.$ngRedux.dispatch(this.actions.triggerSidebar( - { sidebarContents: new DetailsSidebarState(false, true) })); + new DetailsSidebarState(false, true, this.nodeEntityType))); } else { this.$ngRedux.dispatch(this.actions.triggerSidebar({ - sidebarContents: { visible: true, nodeClicked: true, + nodeData: { + entityTypes: this.entityTypes, + nodeTemplate: this.nodeTemplate, + propertyDefinitionType: this.propertyDefinitionType, + entityType: this.nodeEntityType + }, template: this.nodeTemplate, // special handling for instance restrictions due to infinity minInstances: this.nodeTemplate.minInstances, maxInstances: this.nodeTemplate.maxInstances, - } })); } } @@ -525,6 +548,11 @@ export class NodeComponent implements OnInit, AfterViewInit, OnDestroy, DoCheck if (this.artifactsChangedSubscription) { this.artifactsChangedSubscription.unsubscribe(); } + + this.subscriptions.forEach((subscription: Subscription) => { + subscription.unsubscribe(); + }); + this.subscriptions = []; } public openVersionModal() { @@ -535,15 +563,39 @@ export class NodeComponent implements OnInit, AfterViewInit, OnDestroy, DoCheck this.showYamlPolicyManagementModal.emit(); } + getInstanceStateColor() { + switch (this.nodeTemplate.instanceState) { + case NodeTemplateInstanceStates.STARTED: + case NodeTemplateInstanceStates.CREATED: + return '#0EFF09'; + case NodeTemplateInstanceStates.STOPPED: + case NodeTemplateInstanceStates.DELETED: + return '#B8B8B6'; + case NodeTemplateInstanceStates.CONFIGURING: + case NodeTemplateInstanceStates.CREATING: + case NodeTemplateInstanceStates.DELETING: + case NodeTemplateInstanceStates.STARTING: + case NodeTemplateInstanceStates.STOPPING: + return '#F9CF00'; + case NodeTemplateInstanceStates.CONFIGURED: + case NodeTemplateInstanceStates.MIGRATED: + return '#007bff'; + case NodeTemplateInstanceStates.ERROR: + return '#ff090d'; + default: + return 'black'; + } + } + /** * Checks if it was a click or a drag operation on the node. * $event */ private testTimeDifference(): void { if ((this.endTime - this.startTime) < 200) { - this.longpress = false; + this.longPress = false; } else if (this.endTime - this.startTime >= 200) { - this.longpress = true; + this.longPress = true; } } diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/overlay/overlay.component.css b/org.eclipse.winery.frontends/app/topologymodeler/src/app/overlay/overlay.component.css new file mode 100644 index 0000000000..90678fb8a7 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/overlay/overlay.component.css @@ -0,0 +1,51 @@ +.overlay { + position: fixed; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + z-index: 9000; + background-color: #00000080; +} + +.overlay-content { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: white; +} + +.overlay-content p { + font-style: italic; +} + +@keyframes blink { + 0% { + opacity: 0.2; + } + + 20% { + opacity: 1; + } + + 100% { + opacity: 0.2; + } +} + +.overlay-content p span { + animation-name: blink; + animation-duration: 1.4s; + animation-iteration-count: infinite; + animation-fill-mode: both; +} + +.overlay-content p span:nth-child(2) { + animation-delay: 0.2s; +} + +.overlay-content p span:nth-child(3) { + animation-delay: 0.4s; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/overlay/overlay.component.html b/org.eclipse.winery.frontends/app/topologymodeler/src/app/overlay/overlay.component.html new file mode 100644 index 0000000000..62c625dfbd --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/overlay/overlay.component.html @@ -0,0 +1,8 @@ +
+
+
+ +
+

{{_content}}...

+
+
diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/overlay/overlay.component.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/overlay/overlay.component.ts new file mode 100644 index 0000000000..5c45e6440a --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/overlay/overlay.component.ts @@ -0,0 +1,42 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { NgRedux } from '@angular-redux/store'; +import { IWineryState } from '../redux/store/winery.store'; +import { Observable, Subscription } from 'rxjs'; +import { OverlayService } from '../services/overlay.service'; + +@Component({ + selector: 'winery-overlay', + templateUrl: './overlay.component.html', + styleUrls: ['./overlay.component.css'] +}) +export class OverlayComponent implements OnInit, OnDestroy { + + _content: string; + visible: boolean; + subscriptions: Array = []; + + constructor( + private overlayService: OverlayService, + private ngRedux: NgRedux) { + + // this.subscriptions.push( + // this.ngRedux.select(state => state.wineryState.overlayState) + // .subscribe(overlayState => { + // this.content = overlayState.content.replace(/\.$/, ''); + // this.visible = overlayState.visible; + // })); + } + + set content(content: string) { + this._content = content.replace(/\.$/, ''); + } + + ngOnInit() { + this.overlayService.initOverlayService(this); + } + + ngOnDestroy(): void { + // this.subscriptions.forEach(subscription => subscription.unsubscribe()); + } + +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/palette/palette.component.html b/org.eclipse.winery.frontends/app/topologymodeler/src/app/palette/palette.component.html index d94d6844c3..19d7e8f044 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/palette/palette.component.html +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/palette/palette.component.html @@ -12,10 +12,14 @@ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 --> -
+
diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/palette/palette.component.scss b/org.eclipse.winery.frontends/app/topologymodeler/src/app/palette/palette.component.scss index 2f903ff32a..e75b318d12 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/palette/palette.component.scss +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/palette/palette.component.scss @@ -13,24 +13,23 @@ ********************************************************************************/ #palette_root { - border-radius: 1px; + min-height: 35px; box-shadow: initial; /*background-color: #424242;*/ background-color: #dfdfdf; border: 1px solid #c0c0c0; color: black; min-width: 300px; - border-top-left-radius: 0; - border-bottom-left-radius: 0; font-size: small; + border-radius: 0 1px 1px 0; } #paletteBody { - z-index: 3; + z-index: 5; } .paletteBodyOpenZindex { - z-index: 55 !important; + z-index: 6; } #palette_root:hover { @@ -38,9 +37,9 @@ } .palette-items-wrapper { - height: 100vh; background: #eee; - overflow: scroll; + overflow: auto; + flex-grow: 1; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; @@ -51,6 +50,10 @@ padding-bottom: 100px; } +.palette-items-wrapper::-webkit-scrollbar { + display: none; +} + .accordion-root { } @@ -58,6 +61,7 @@ accordion-group { font-size: x-small; max-width: 300px; text-overflow: ellipsis; + overflow: auto; white-space: nowrap !important; text-align: start !important; } @@ -106,8 +110,11 @@ li div { .bootstrap-vertical-nav { position: fixed; - float: left; + width: 300px; height: 100vh; + overflow: auto; + display: flex; + flex-direction: column; } .btn-light { diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/palette/palette.component.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/palette/palette.component.ts index eb0ea3296b..614b1775eb 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/palette/palette.component.ts +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/palette/palette.component.ts @@ -41,27 +41,19 @@ import { InheritanceUtils } from '../models/InheritanceUtils'; state('shrunk', style({ display: 'none', opacity: '0', - height: '*', })), state('extended', style({ display: 'block', opacity: '1', - height: '*', })), transition('shrunk => extended', animate('500ms ease-out')), - transition('extended => shrunk', animate('500ms ease-out')) + transition('extended => shrunk', animate('50ms ease-out')) ]), trigger('paletteButtonState', [ state('left', style({ - display: 'block', - opacity: '1', - height: '*', transform: 'rotate(-90deg) translateY(-135px) translateX(-135px)' })), state('top', style({ - display: 'block', - opacity: '1', - height: '*', transform: 'rotate(0deg) translateY(0px) translateX(0px)' })), transition('left => top', animate('50ms ease-in')), @@ -76,6 +68,7 @@ import { InheritanceUtils } from '../models/InheritanceUtils'; export class PaletteComponent implements OnInit, OnDestroy { @Input() entityTypes: EntityTypesModel; + @Input() top: number; paletteRootState = 'extended'; paletteButtonRootState = 'left'; @@ -120,7 +113,6 @@ export class PaletteComponent implements OnInit, OnDestroy { public toggleRootState(): void { if (this.paletteRootState === 'shrunk') { this.ngRedux.dispatch(this.actions.sendPaletteOpened(true)); - } else { this.ngRedux.dispatch(this.actions.sendPaletteOpened(false)); } diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/properties/kv-properties/kv-properties.component.css b/org.eclipse.winery.frontends/app/topologymodeler/src/app/properties/kv-properties/kv-properties.component.css new file mode 100644 index 0000000000..3dae94204c --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/properties/kv-properties/kv-properties.component.css @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +.kv-table { + table-layout: fixed; + width: 100%; +} + +.kv-table td { + padding: 2px; +} + +.kv-table .col-key { + width: 40%; +} + +.kv-table .col-value { + width: 60%; +} + +.has-error { + border-color: #ff4a5b; +} + +:host > .tooltip { + font-size: x-small; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/properties/kv-properties/kv-properties.component.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/properties/kv-properties/kv-properties.component.ts index 27e909bbb2..cf03fb3315 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/properties/kv-properties/kv-properties.component.ts +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/properties/kv-properties/kv-properties.component.ts @@ -11,25 +11,42 @@ * * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 *******************************************************************************/ -import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; import { Subject, Subscription } from 'rxjs'; +import { NgRedux } from '@angular-redux/store'; +import { IWineryState } from '../../redux/store/winery.store'; +import { WineryActions } from '../../redux/actions/winery.actions'; +import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; +import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; import { KeyValueItem } from '../../../../../tosca-management/src/app/model/keyValueItem'; -import { debounceTime } from 'rxjs/operators'; import { Utils } from '../../../../../tosca-management/src/app/wineryUtils/utils'; +import { EntityType } from '../../models/ttopology-template'; @Component({ selector: 'winery-kv-properties', templateUrl: './kv-properties.component.html', + styleUrls: ['./kv-properties.component.css'] }) export class KvPropertiesComponent implements OnInit, OnDestroy { @Input() readonly: boolean; @Input() nodeProperties: object; + @Input() nodeId: string; + @Input() entityType: EntityType; @Output() propertyEdited: EventEmitter = new EventEmitter(); + invalidNodeProperties: any = {}; + kvPatternMap: any; + kvDescriptionMap: any; + checkEnabled: boolean; + + propertiesSubject: Subject = new Subject(); debouncer: Subject = new Subject(); subscriptions: Array = []; + constructor(private ngRedux: NgRedux, + private actions: WineryActions) { + } + ngOnInit(): void { this.subscriptions.push(this.debouncer.pipe(debounceTime(50)) .subscribe(target => { @@ -38,6 +55,33 @@ export class KvPropertiesComponent implements OnInit, OnDestroy { value: target.value, }); })); + + if (this.nodeProperties) { + this.initKVDescriptionMap(); + this.initKVPatternMap(); + } + + this.subscriptions.push(this.ngRedux.select((state) => { + return state.topologyRendererState.buttonsState.checkNodePropertiesButton; + }) + .subscribe((checked) => { + this.checkEnabled = checked; + if (this.checkEnabled) { + this.checkAllProperties(); + } else { + this.invalidNodeProperties = {}; + this.ngRedux.dispatch(this.actions.setNodePropertyValidity(this.nodeId, true)); + } + })); + + this.subscriptions.push(this.propertiesSubject.pipe( + distinctUntilChanged(), + ).subscribe((property) => { + if (this.checkEnabled) { + this.checkProperty(property.key, property.value); + } + this.propertyEdited.emit({ key: property.key, value: property.value }); + })); } ngOnDestroy(): void { @@ -51,4 +95,83 @@ export class KvPropertiesComponent implements OnInit, OnDestroy { keyup(target: any): void { this.debouncer.next(target); } + + initKVDescriptionMap() { + this.kvDescriptionMap = {}; + try { + if (this.entityType) { + const propertyDefinitionKVList = + this.entityType.full.serviceTemplateOrNodeTypeOrNodeTypeImplementation[0].propertyDefinitionKVList; + if (propertyDefinitionKVList) { + propertyDefinitionKVList.forEach((prop) => { + this.kvDescriptionMap[prop.key] = prop['description']; + }); + } + } + } catch (e) { + console.error(e); + } + } + + initKVPatternMap() { + this.kvPatternMap = {}; + try { + if (this.entityType) { + const propertyDefinitionKVList = + this.entityType.full.serviceTemplateOrNodeTypeOrNodeTypeImplementation[0].propertyDefinitionKVList; + if (propertyDefinitionKVList) { + propertyDefinitionKVList.forEach((prop) => { + this.kvPatternMap[prop.key] = prop['pattern']; + }); + } + } + } catch (e) { + console.error(e); + } + } + + hasError(key: string): boolean { + return !!this.invalidNodeProperties[key]; + } + + checkForErrors() { + if (Object.keys(this.invalidNodeProperties).length > 0) { + this.ngRedux.dispatch(this.actions.setNodePropertyValidity(this.nodeId, false)); + } else { + this.ngRedux.dispatch(this.actions.setNodePropertyValidity(this.nodeId, true)); + } + } + + checkAllProperties() { + Object.keys(this.nodeProperties).forEach((key) => { + this.checkProperty(key, this.nodeProperties[key]); + }); + this.checkForErrors(); + } + + checkProperty(key: string, value: string) { + try { + delete this.invalidNodeProperties[key]; + if (value && this.kvPatternMap[key]) { + if (!(this.isInputOrPropertyValue(value))) { + this.checkAndSetPattern(key, value); + } + } + } catch (e) { + console.log(e); + } finally { + this.checkForErrors(); + } + } + + checkAndSetPattern(key: string, value: string): void { + const pattern = this.kvPatternMap[key]; + if (!new RegExp(pattern).test(value)) { + this.invalidNodeProperties[key] = pattern; + } + } + + isInputOrPropertyValue(value: string): boolean { + return value.startsWith('get_input:') || value.startsWith('get_property:'); + } } diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/properties/properties.component.html b/org.eclipse.winery.frontends/app/topologymodeler/src/app/properties/properties.component.html index d6efabd7d5..3f6be68419 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/properties/properties.component.html +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/properties/properties.component.html @@ -16,22 +16,24 @@
+ [readonly]="readonly" + [nodeProperties]="templateProperties" + [nodeId]="this.nodeId" + [entityType]="this.entityType" + (propertyEdited)="kvPropertyEdit($event)"> + [readonly]="readonly" + [properties]="templateProperties" + [templateType]="templateType" + (propertyEdited)="yamlPropertyEdit($event)"> + [readonly]="readonly" + [propertiesValue]="templateProperties" + (propertyEdited)="xmlPropertyEdit($event)">
diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/properties/properties.component.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/properties/properties.component.ts index f56f68245b..605ac61d61 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/properties/properties.component.ts +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/properties/properties.component.ts @@ -13,14 +13,14 @@ *******************************************************************************/ import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core'; -import { Subscription } from 'rxjs'; +import { Subject, Subscription } from 'rxjs'; import { NgRedux } from '@angular-redux/store'; import { IWineryState } from '../redux/store/winery.store'; import { WineryActions } from '../redux/actions/winery.actions'; import { JsPlumbService } from '../services/jsPlumb.service'; import { PropertyDefinitionType } from '../models/enums'; import { KeyValueItem } from '../../../../tosca-management/src/app/model/keyValueItem'; -import { TNodeTemplate, TRelationshipTemplate } from '../models/ttopology-template'; +import { EntityType, TNodeTemplate, TRelationshipTemplate } from '../models/ttopology-template'; @Component({ selector: 'winery-properties', @@ -32,11 +32,16 @@ export class PropertiesComponent implements OnInit, OnChanges, OnDestroy { @Input() readonly: boolean; @Input() templateId: string; @Input() isNode: boolean; + @Input() entityType: EntityType; + @Input() nodeId: string; propertyDefinitionType: PropertyDefinitionType; templateProperties: any = {}; templateType: string; + key: string; + nodeProperties: any; + dispatchSubject: Subject = new Subject(); private subscriptions: Array = []; // flag to allow skipping an update when this instance is the instigator of said update // this way we avoid recreating the input form during the editing process diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/properties/xml-properties/xml-properties.component.css b/org.eclipse.winery.frontends/app/topologymodeler/src/app/properties/xml-properties/xml-properties.component.css new file mode 100644 index 0000000000..d8ba3d7d41 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/properties/xml-properties/xml-properties.component.css @@ -0,0 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +textarea { + pointer-events: auto; + resize: none; + margin-left: 5px; + width: 100%; + overflow: hidden; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/properties/xml-properties/xml-properties.component.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/properties/xml-properties/xml-properties.component.ts index 98d3ffd501..9a3c6cad4b 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/properties/xml-properties/xml-properties.component.ts +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/properties/xml-properties/xml-properties.component.ts @@ -19,7 +19,9 @@ import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; @Component({ selector: 'winery-xml-properties', templateUrl: './xml-properties.component.html', + styleUrls: ['./xml-properties.component.css'] }) + export class XmlPropertiesComponent implements OnInit, OnDestroy { @Input() readonly: boolean; @Input() propertiesValue: any; @@ -29,6 +31,9 @@ export class XmlPropertiesComponent implements OnInit, OnDestroy { subscriptions: Array = []; + constructor() { + } + ngOnInit(): void { this.subscriptions.push(this.properties.pipe(debounceTime(300), distinctUntilChanged()) .subscribe(value => { diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/actions/live-modeling.actions.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/actions/live-modeling.actions.ts new file mode 100644 index 0000000000..1263de138c --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/actions/live-modeling.actions.ts @@ -0,0 +1,148 @@ +/******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +import { Injectable } from '@angular/core'; +import { Action } from 'redux'; +import { LiveModelingStates, ServiceTemplateInstanceStates } from '../../models/enums'; +import { Csar } from '../../models/container/csar.model'; +import { PlanInstance } from '../../models/container/plan-instance.model'; +import { TTopologyTemplate } from '../../models/ttopology-template'; +import { LiveModelingSettings } from '../../models/liveModelingSettings'; + +export interface SetStateAction extends Action { + state: LiveModelingStates; +} + +export interface SetContainerUrlAction extends Action { + containerUrl: string; +} + +export interface SetCurrentCsarIdAction extends Action { + csarId: string; +} + +export interface SetCurrentCsarAction extends Action { + csar: Csar; +} + +export interface SetCurrentServiceTemplateInstanceIdAction extends Action { + serviceTemplateInstanceId: string; +} + +export interface SetCurrentServiceTemplateInstanceStateAction extends Action { + serviceTemplateInstanceState: ServiceTemplateInstanceStates; +} + +export interface SetSettingsAction extends Action { + settings: any; +} + +export interface SetDeploymentChangesAction extends Action { + deploymentChanges: boolean; +} + +export interface SetCurrentBuildPlanInstance extends Action { + buildPlanInstance: PlanInstance; +} + +export interface SetDeployedJsonTopology extends Action { + deployedJsonTopology: TTopologyTemplate; +} + +/** + * Actions for live modeling + */ +@Injectable() +export class LiveModelingActions { + static SET_STATE = 'SET_STATE'; + static SET_CONTAINER_URL = 'SET_CONTAINER_URL'; + static SET_CURRENT_CSAR_ID = 'SET_CURRENT_CSAR_ID'; + static SET_CURRENT_CSAR = 'SET_CURRENT_CSAR'; + static SET_CURRENT_SERVICE_TEMPLATE_INSTANCE_ID = 'SET_CURRENT_SERVICE_TEMPLATE_INSTANCE_ID'; + static SET_CURRENT_SERVICE_TEMPLATE_INSTANCE_STATE = 'SET_CURRENT_SERVICE_TEMPLATE_INSTANCE_STATE'; + static SET_SETTINGS = 'SET_SETTING'; + static SET_DEPLOYMENT_CHANGES = 'SET_DEPLOYMENT_CHANGES'; + static SET_CURRENT_BUILD_PLAN_INSTANCE = 'SET_CURRENT_BUILD_PLAN_INSTANCE'; + static SET_DEPLOYED_JSON_TOPOLOGY = 'SET_DEPLOYED_JSON_TOPOLOGY'; + + setState(state: LiveModelingStates): SetStateAction { + return { + type: LiveModelingActions.SET_STATE, + state: state + }; + } + + setContainerUrl(containerUrl: string): SetContainerUrlAction { + return { + type: LiveModelingActions.SET_CONTAINER_URL, + containerUrl: containerUrl + }; + } + + setCurrentCsar(csar: Csar): SetCurrentCsarAction { + return { + type: LiveModelingActions.SET_CURRENT_CSAR, + csar: csar + }; + } + + setCurrentCsarId(csarId: string): SetCurrentCsarIdAction { + return { + type: LiveModelingActions.SET_CURRENT_CSAR_ID, + csarId: csarId + }; + } + + setCurrentServiceTemplateInstanceId(serviceTemplateInstanceId: string): SetCurrentServiceTemplateInstanceIdAction { + return { + type: LiveModelingActions.SET_CURRENT_SERVICE_TEMPLATE_INSTANCE_ID, + serviceTemplateInstanceId: serviceTemplateInstanceId + }; + } + + setCurrentServiceTemplateInstanceState(serviceTemplateInstanceState: ServiceTemplateInstanceStates): SetCurrentServiceTemplateInstanceStateAction { + return { + type: LiveModelingActions.SET_CURRENT_SERVICE_TEMPLATE_INSTANCE_STATE, + serviceTemplateInstanceState: serviceTemplateInstanceState + }; + } + + setSettings(settings: LiveModelingSettings): SetSettingsAction { + return { + type: LiveModelingActions.SET_SETTINGS, + settings: settings + }; + } + + setDeploymentChanges(deploymentChanges: boolean): SetDeploymentChangesAction { + return { + type: LiveModelingActions.SET_DEPLOYMENT_CHANGES, + deploymentChanges: deploymentChanges + }; + } + + setCurrentBuildPlanInstance(buildPlanInstance: PlanInstance): SetCurrentBuildPlanInstance { + return { + type: LiveModelingActions.SET_CURRENT_BUILD_PLAN_INSTANCE, + buildPlanInstance: buildPlanInstance + }; + } + + setDeployedJsonTopology(topologyTemplate: TTopologyTemplate): SetDeployedJsonTopology { + return { + type: LiveModelingActions.SET_DEPLOYED_JSON_TOPOLOGY, + deployedJsonTopology: topologyTemplate + }; + } +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/actions/topologyRenderer.actions.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/actions/topologyRenderer.actions.ts index 09bea86284..56f3415951 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/actions/topologyRenderer.actions.ts +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/actions/topologyRenderer.actions.ts @@ -59,6 +59,7 @@ export class TopologyRendererActions { static DETERMINE_FREEZABLE_COMPONENTS = 'DETERMINE_FREEZABLE_COMPONENTS'; static CLEAN_FREEZABLE_COMPONENTS = 'CLEAN_FREEZABLE_COMPONENTS'; static PLACE_COMPONENTS = 'PLACE_COMPONENTS'; + static TOGGLE_CHECK_NODE_PROPERTIES = 'TOGGLE_CHECK_NODE_PROPERTIES'; static MANAGE_YAML_POLICIES = 'MANAGE_YAML_POLICIES'; static TOGGLE_VERSION_SLIDER = 'TOGGLE_VERSION_SLIDER'; static SHOW_MANAGE_YAML_GROUPS = 'SHOW_MANAGE_YAML_GROUPS'; @@ -197,6 +198,10 @@ export class TopologyRendererActions { return { type: TopologyRendererActions.PLACE_COMPONENTS }; } + toggleCheckNodeProperties(): Action { + return { type: TopologyRendererActions.TOGGLE_CHECK_NODE_PROPERTIES }; + } + manageYamlPolicies(): Action { return { type: TopologyRendererActions.MANAGE_YAML_POLICIES }; } diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/actions/winery.actions.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/actions/winery.actions.ts index 7d09e2bb73..96ec13352f 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/actions/winery.actions.ts +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/actions/winery.actions.ts @@ -15,13 +15,14 @@ import { Action, ActionCreator } from 'redux'; import { Injectable } from '@angular/core'; import { - OTParticipant, TArtifact, TGroupDefinition, TNodeTemplate, TRelationshipTemplate + OTParticipant, TArtifact, TGroupDefinition, TNodeTemplate, TRelationshipTemplate, TTopologyTemplate } from '../../models/ttopology-template'; import { TDeploymentArtifact } from '../../models/artifactsModalData'; import { TPolicy } from '../../models/policiesModalData'; import { Visuals } from '../../models/visuals'; import { DetailsSidebarState } from '../../sidebars/node-details/node-details-sidebar'; import { EntityTypesModel } from '../../models/entityTypesModel'; +import { NodeTemplateInstanceStates } from '../../models/enums'; export interface SendPaletteOpenedAction extends Action { paletteOpened: boolean; @@ -225,6 +226,39 @@ export interface SetNodeVisuals extends Action { visuals: Visuals[]; } +export interface SendLiveModelingSidebarOpenedAction extends Action { + sidebarOpened: boolean; +} + +export interface SetLastSavedJsonTopologyAction extends Action { + lastSavedJsonTopology: TTopologyTemplate; +} + +export interface SetUnsavedChangesAction extends Action { + unsavedChanges: boolean; +} + +export interface SetNodePropertyValidityAction extends Action { + nodeValidity: { + nodeId: string; + valid: boolean; + }; +} + +export interface SetNodeInstanceStateAction extends Action { + nodeInstanceState: { + nodeId: string; + state: NodeTemplateInstanceStates + }; +} + +export interface SetNodeWorkingAction extends Action { + nodeWorking: { + nodeId: string; + working: boolean; + }; +} + /** * Winery Actions */ @@ -262,9 +296,15 @@ export class WineryActions { static DELETE_POLICY = 'DELETE_POLICY'; static SEND_CURRENT_NODE_ID = 'SEND_CURRENT_NODE_ID'; static SET_NODE_VISUALS = 'SET_NODE_VISUALS'; + static SEND_LIVE_MODELING_SIDEBAR_OPENED = 'SEND_LIVE_MODELING_SIDEBAR_OPENED'; static UPDATE_GROUP_DEFINITIONS = 'UPDATE_GROUP_DEFINITIONS'; static UPDATE_PARTICIPANTS = 'UPDATE_PARTICIPANTS'; static ASSIGN_PARTICIPANT = 'ASSIGN_PARTICIPANT'; + static SET_LAST_SAVED_JSON_TOPOLOGY = 'SET_LAST_SAVED_JSON_TOPOLOGY'; + static SET_UNSAVED_CHANGES = 'SET_UNSAVED_CHANGES'; + static SET_NODE_VALIDITY = 'SET_NODE_VALIDITY'; + static SET_NODE_INSTANCE_STATE = 'SET_NODE_INSTANCE_STATE'; + static SET_NODE_WORKING = 'SET_NODE_WORKING'; static ASSIGN_DEPLOYMENT_TECHNOLOGY = 'ASSIGN_DEPLOYMENT_TECHNOLOGY'; addEntityTypes: ActionCreator = ((entityTypes) => ({ @@ -283,9 +323,9 @@ export class WineryActions { hideNavBarAndPalette: hideNavBarAndPalette })); triggerSidebar: ActionCreator = - ((newSidebarData) => ({ + ((newSidebarData: DetailsSidebarState) => ({ type: WineryActions.TRIGGER_SIDEBAR, - sidebarContents: newSidebarData.sidebarContents + sidebarContents: newSidebarData })); changeNodeName: ActionCreator = ((nodeNames) => ({ @@ -444,4 +484,43 @@ export class WineryActions { type: WineryActions.SET_NODE_VISUALS, visuals: visuals })); + sendLiveModelingSidebarOpened: ActionCreator = + ((sidebarOpened) => ({ + type: WineryActions.SEND_LIVE_MODELING_SIDEBAR_OPENED, + sidebarOpened: sidebarOpened + })); + setLastSavedJsonTopology: ActionCreator = + ((lastSavedJsonTopology: TTopologyTemplate) => ({ + type: WineryActions.SET_LAST_SAVED_JSON_TOPOLOGY, + lastSavedJsonTopology: lastSavedJsonTopology + })); + setUnsavedChanges: ActionCreator = + ((unsavedChanges: boolean) => ({ + type: WineryActions.SET_UNSAVED_CHANGES, + unsavedChanges: unsavedChanges + })); + setNodePropertyValidity: ActionCreator = + ((nodeId: string, valid: boolean) => ({ + type: WineryActions.SET_NODE_VALIDITY, + nodeValidity: { + nodeId: nodeId, + valid: valid + } + })); + setNodeInstanceState: ActionCreator = + ((nodeId: string, state: NodeTemplateInstanceStates) => ({ + type: WineryActions.SET_NODE_INSTANCE_STATE, + nodeInstanceState: { + nodeId: nodeId, + state: state + } + })); + setNodeWorking: ActionCreator = + ((nodeId: string, working: boolean) => ({ + type: WineryActions.SET_NODE_WORKING, + nodeWorking: { + nodeId: nodeId, + working: working + } + })); } diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/reducers/live-modeling.reducer.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/reducers/live-modeling.reducer.ts new file mode 100644 index 0000000000..e106bc5e3a --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/reducers/live-modeling.reducer.ts @@ -0,0 +1,140 @@ +/******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ +import { LiveModelingStates, ServiceTemplateInstanceStates } from '../../models/enums'; +import { Action } from 'redux'; +import { + LiveModelingActions, SetCurrentBuildPlanInstance, SetContainerUrlAction, SetCurrentCsarAction, + SetCurrentCsarIdAction, + SetCurrentServiceTemplateInstanceIdAction, SetCurrentServiceTemplateInstanceStateAction, SetDeploymentChangesAction, SetSettingsAction, SetStateAction, + SetDeployedJsonTopology +} from '../actions/live-modeling.actions'; +import { InputParameter } from '../../models/container/input-parameter.model'; +import { Csar } from '../../models/container/csar.model'; +import { PlanInstance } from '../../models/container/plan-instance.model'; +import { TTopologyTemplate } from '../../models/ttopology-template'; +import { TopologyTemplateUtil } from '../../models/topologyTemplateUtil'; +import { LiveModelingSettings } from '../../models/liveModelingSettings'; + +export interface LiveModelingState { + state: LiveModelingStates; + containerUrl: string; + currentCsarId: string; + currentCsar: Csar; + currentServiceTemplateInstanceId: string; + currentServiceTemplateInstanceState: ServiceTemplateInstanceStates; + settings: LiveModelingSettings; + deploymentChanges: boolean; + currentBuildPlanInstance: PlanInstance; + deployedJsonTopology: TTopologyTemplate; +} + +export const INITIAL_LIVE_MODELING_STATE: LiveModelingState = { + state: LiveModelingStates.DISABLED, + containerUrl: null, + currentCsarId: null, + currentCsar: null, + currentServiceTemplateInstanceId: null, + currentServiceTemplateInstanceState: ServiceTemplateInstanceStates.INITIAL, + settings: LiveModelingSettings.initial(), + deploymentChanges: false, + currentBuildPlanInstance: null, + deployedJsonTopology: null +}; + +export const LiveModelingReducer = + function (lastState: LiveModelingState = INITIAL_LIVE_MODELING_STATE, action: Action): LiveModelingState { + switch (action.type) { + case LiveModelingActions.SET_STATE: + const state = (action).state; + return { + ...lastState, + state: state + }; + case LiveModelingActions.SET_CONTAINER_URL: { + const containerUrl = (action).containerUrl; + + return { + ...lastState, + containerUrl: containerUrl + }; + } + case LiveModelingActions.SET_CURRENT_CSAR_ID: { + const csarId = (action).csarId; + + return { + ...lastState, + currentCsarId: csarId + }; + } + case LiveModelingActions.SET_CURRENT_CSAR: { + const csar = (action).csar; + + return { + ...lastState, + currentCsar: csar + }; + } + case LiveModelingActions.SET_CURRENT_SERVICE_TEMPLATE_INSTANCE_ID: { + const serviceTemplateInstanceId = (action).serviceTemplateInstanceId; + + return { + ...lastState, + currentServiceTemplateInstanceId: serviceTemplateInstanceId + }; + } + case LiveModelingActions.SET_CURRENT_SERVICE_TEMPLATE_INSTANCE_STATE: { + const serviceTemplateInstanceState = (action).serviceTemplateInstanceState; + + return { + ...lastState, + currentServiceTemplateInstanceState: serviceTemplateInstanceState + }; + } + case LiveModelingActions.SET_SETTINGS: { + const settings = (action).settings; + + return { + ...lastState, + settings: settings + }; + } + case LiveModelingActions.SET_DEPLOYMENT_CHANGES: { + const deploymentChanges = (action).deploymentChanges; + + return { + ...lastState, + deploymentChanges: deploymentChanges + }; + } + case LiveModelingActions.SET_CURRENT_BUILD_PLAN_INSTANCE: { + const buildPlanInstance = (action).buildPlanInstance; + + return { + ...lastState, + currentBuildPlanInstance: buildPlanInstance + }; + } + case LiveModelingActions.SET_DEPLOYED_JSON_TOPOLOGY: { + const topologyTemplate = (action).deployedJsonTopology; + + return { + ...lastState, + deployedJsonTopology: TopologyTemplateUtil.cloneTopologyTemplate(topologyTemplate) + }; + } + default: { + return lastState; + } + } + }; diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/reducers/topologyRenderer.reducer.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/reducers/topologyRenderer.reducer.ts index a4fa73fa16..eb48c2a44a 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/reducers/topologyRenderer.reducer.ts +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/reducers/topologyRenderer.reducer.ts @@ -65,6 +65,7 @@ export interface TopologyRendererState { yamlGroupsButton?: boolean; manageParticipantsButton?: boolean; assignParticipantsButton?: boolean; + checkNodePropertiesButton?: boolean; assignDeploymentTechnologyButton?: boolean; hideDependsOnRelations?: boolean; detectPatternsButton?: boolean; @@ -113,6 +114,7 @@ export const INITIAL_TOPOLOGY_RENDERER_STATE: TopologyRendererState = { manageParticipantsButton: false, assignDeploymentTechnologyButton: false, detectPatternsButton: false, + checkNodePropertiesButton: false, }, activeResearchPlugin: undefined, }; @@ -485,6 +487,15 @@ export const TopologyRendererReducer = } else { delete lastState.mappingType; } + break; + case TopologyRendererActions.TOGGLE_CHECK_NODE_PROPERTIES: + return { + ...lastState, + buttonsState: { + ...lastState.buttonsState, + checkNodePropertiesButton: !lastState.buttonsState.checkNodePropertiesButton + } + }; } return lastState; }; diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/reducers/winery.reducer.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/reducers/winery.reducer.ts index 86e05f50b0..031ee0952b 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/reducers/winery.reducer.ts +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/reducers/winery.reducer.ts @@ -21,11 +21,14 @@ import { SetCapabilityAction, SetDeploymentArtifactAction, SetNodeVisuals, SetPolicyAction, SetPropertyAction, SetRequirementAction, SetTargetLocation, SetYamlArtifactAction, SidebarChangeNodeName, SidebarMaxInstanceChanges, SidebarMinInstanceChanges, SidebarStateAction, UpdateGroupDefinitionAction, UpdateNodeCoordinatesAction, - UpdateParticipantsAction, UpdateRelationshipNameAction, WineryActions + UpdateParticipantsAction, UpdateRelationshipNameAction, WineryActions, SendLiveModelingSidebarOpenedAction, + SetLastSavedJsonTopologyAction, SetNodeInstanceStateAction, SetNodePropertyValidityAction, SetNodeWorkingAction, + SetUnsavedChangesAction } from '../actions/winery.actions'; import { TArtifact, TNodeTemplate, TRelationshipTemplate, TTopologyTemplate } from '../../models/ttopology-template'; import { TDeploymentArtifact } from '../../models/artifactsModalData'; import { Visuals } from '../../models/visuals'; +import { TopologyTemplateUtil } from '../../models/topologyTemplateUtil'; import { DetailsSidebarState } from '../../sidebars/node-details/node-details-sidebar'; import { EntityTypesModel } from '../../models/entityTypesModel'; @@ -37,6 +40,9 @@ export interface WineryState { currentNodeData: any; nodeVisuals: Visuals[]; entityTypes?: EntityTypesModel; + liveModelingSidebarOpenedState: boolean; + lastSavedJsonTopology: TTopologyTemplate; + unsavedChanges: boolean; } export const INITIAL_WINERY_STATE: WineryState = { @@ -62,7 +68,10 @@ export const INITIAL_WINERY_STATE: WineryState = { id: '', focus: false }, - nodeVisuals: null + nodeVisuals: null, + liveModelingSidebarOpenedState: false, + lastSavedJsonTopology: null, + unsavedChanges: false, }; /** @@ -76,6 +85,7 @@ export const WineryReducer = ...lastState, entityTypes: (action).types, }; + case WineryActions.SEND_PALETTE_OPENED: case WineryActions.SEND_PALETTE_OPENED: const paletteOpened: boolean = (action).paletteOpened; @@ -91,7 +101,7 @@ export const WineryReducer = hideNavBarAndPaletteState: hideNavBarAndPalette }; case WineryActions.TRIGGER_SIDEBAR: - const newSidebarData: DetailsSidebarState = (action).sidebarContents; + const newSidebarData: any = (action).sidebarContents; return { ...lastState, @@ -612,7 +622,69 @@ export const WineryReducer = ...lastState, nodeVisuals: visuals }; + case WineryActions.SEND_LIVE_MODELING_SIDEBAR_OPENED: + const sidebarOpened: boolean = (action).sidebarOpened; + + return { + ...lastState, + liveModelingSidebarOpenedState: sidebarOpened + }; + case WineryActions.SET_LAST_SAVED_JSON_TOPOLOGY: + const lastSavedJsonTopology: TTopologyTemplate = (action).lastSavedJsonTopology; + + return { + ...lastState, + lastSavedJsonTopology: TopologyTemplateUtil.cloneTopologyTemplate(lastSavedJsonTopology) + }; + case WineryActions.SET_UNSAVED_CHANGES: + const unsavedChanges: boolean = (action).unsavedChanges; + + return { + ...lastState, + unsavedChanges: unsavedChanges + }; + case WineryActions.SET_NODE_VALIDITY: + const nodeValidity = (action).nodeValidity; + + return { + ...lastState, + currentJsonTopology: { + ...lastState.currentJsonTopology, + nodeTemplates: lastState.currentJsonTopology.nodeTemplates + .map(nodeTemplate => nodeTemplate.id === nodeValidity.nodeId ? + nodeTemplate.generateNewNodeTemplateWithUpdatedAttribute('valid', nodeValidity.valid) + : nodeTemplate + ) + } + }; + case WineryActions.SET_NODE_INSTANCE_STATE: + const nodeInstanceState = (action).nodeInstanceState; + return { + ...lastState, + currentJsonTopology: { + ...lastState.currentJsonTopology, + nodeTemplates: lastState.currentJsonTopology.nodeTemplates + .map(nodeTemplate => nodeTemplate.id === nodeInstanceState.nodeId ? + nodeTemplate.generateNewNodeTemplateWithUpdatedAttribute('instanceState', nodeInstanceState.state) + : nodeTemplate + ) + } + }; + case WineryActions.SET_NODE_WORKING: + const nodeWorking = (action).nodeWorking; + + return { + ...lastState, + currentJsonTopology: { + ...lastState.currentJsonTopology, + nodeTemplates: lastState.currentJsonTopology.nodeTemplates + .map(nodeTemplate => nodeTemplate.id === nodeWorking.nodeId ? + nodeTemplate.generateNewNodeTemplateWithUpdatedAttribute('working', nodeWorking.working) + : nodeTemplate + ) + } + }; default: return lastState; } diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/store/winery.store.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/store/winery.store.ts index 0809cb92c5..e18ffb9e3b 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/store/winery.store.ts +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/redux/store/winery.store.ts @@ -14,7 +14,10 @@ import { combineReducers, Reducer } from 'redux'; import { INITIAL_WINERY_STATE, WineryReducer, WineryState } from '../reducers/winery.reducer'; -import { INITIAL_TOPOLOGY_RENDERER_STATE, TopologyRendererReducer, TopologyRendererState } from '../reducers/topologyRenderer.reducer'; +import { + INITIAL_TOPOLOGY_RENDERER_STATE, TopologyRendererReducer, TopologyRendererState +} from '../reducers/topologyRenderer.reducer'; +import { INITIAL_LIVE_MODELING_STATE, LiveModelingReducer, LiveModelingState } from '../reducers/live-modeling.reducer'; /** * The topology modeler has one store for all data. @@ -22,14 +25,17 @@ import { INITIAL_TOPOLOGY_RENDERER_STATE, TopologyRendererReducer, TopologyRende export interface IWineryState { topologyRendererState: TopologyRendererState; wineryState: WineryState; + liveModelingState: LiveModelingState; } export const INITIAL_IWINERY_STATE: IWineryState = { topologyRendererState: INITIAL_TOPOLOGY_RENDERER_STATE, - wineryState: INITIAL_WINERY_STATE + wineryState: INITIAL_WINERY_STATE, + liveModelingState: INITIAL_LIVE_MODELING_STATE }; export const rootReducer: Reducer = combineReducers({ topologyRendererState: TopologyRendererReducer, - wineryState: WineryReducer + wineryState: WineryReducer, + liveModelingState: LiveModelingReducer }); diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/services/backend.service.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/services/backend.service.ts index 5c85f3174f..804bfe55cf 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/services/backend.service.ts +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/services/backend.service.ts @@ -31,12 +31,15 @@ import { Threat, ThreatAssessmentApiData } from '../models/threatModelingModalDa import { Visuals } from '../models/visuals'; import { VersionElement } from '../models/versionElement'; import { WineryRepositoryConfigurationService } from '../../../../tosca-management/src/app/wineryFeatureToggleModule/WineryRepositoryConfiguration.service'; -import { takeLast } from 'rxjs/operators'; import { TPolicy } from '../models/policiesModalData'; import { EntityTypesModel } from '../models/entityTypesModel'; import { ToscaUtils } from '../models/toscaUtils'; import { TopologyTemplateUtil } from '../models/topologyTemplateUtil'; import { SubMenuItems } from '../../../../tosca-management/src/app/model/subMenuItem'; +import { takeLast, tap } from 'rxjs/operators'; +import { NgRedux } from '@angular-redux/store'; +import { IWineryState } from '../redux/store/winery.store'; +import { WineryActions } from '../redux/actions/winery.actions'; import { DeploymentTechnology } from '../models/deployment-technology'; /** @@ -68,7 +71,9 @@ export class BackendService { constructor(private http: HttpClient, private alert: ToastrService, private errorHandler: ErrorHandlerService, - private configurationService: WineryRepositoryConfigurationService) { + private configurationService: WineryRepositoryConfigurationService, + private ngRedux: NgRedux, + private wineryActions: WineryActions) { } /** @@ -210,6 +215,25 @@ export class BackendService { return this.http.put(url, TopologyTemplateUtil.prepareSave(topologyTemplate), { headers: headers, responseType: 'text', observe: 'response' } + ).pipe( + tap(resp => { + if (resp.ok) { + this.ngRedux.dispatch(this.wineryActions.setLastSavedJsonTopology(topologyTemplate)); + } + }) + ); + } + } + + /** + * Creates new service template version for live-modeling. + */ + createLiveModelingServiceTemplate(): Observable { + if (this.configuration) { + const headers = new HttpHeaders().set('Content-Type', 'application/json'); + return this.http.post(this.configuration.parentElementUrl + 'createlivemodelingversion', + null, + { headers: headers } ); } } @@ -381,6 +405,23 @@ export class BackendService { } } + /** + * This method retrieves a Topology Template from the backend. + */ + requestTopologyTemplate(serviceTemplateId?: string): Observable { + if (this.configuration) { + if (serviceTemplateId) { + const url = this.configuration.parentUrl + + encodeURIComponent(encodeURIComponent(this.configuration.ns)) + '/' + + serviceTemplateId + '/topologytemplate'; + return this.http.get(url); + } else { + return this.http.get(this.configuration.elementUrl); + } + } + return null; + } + get model(): Observable { return this._model; } @@ -751,12 +792,6 @@ export class BackendService { } } - private requestTopologyTemplate(): Observable { - if (this.configuration) { - return this.http.get(this.configuration.elementUrl); - } - } - private requestNodeVisuals(): Observable { if (this.configuration) { return this.http.get(this.configuration.repositoryURL + '/nodetypes/allvisualappearancedata'); diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/services/container.service.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/services/container.service.ts new file mode 100644 index 0000000000..872bf673fe --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/services/container.service.ts @@ -0,0 +1,529 @@ +/******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +import { Injectable } from '@angular/core'; +import { CsarUpload } from '../models/container/csar-upload.model'; +import { of } from 'rxjs'; +import { Observable } from 'rxjs/Rx'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { catchError, concatMap, filter, map, tap , retry} from 'rxjs/operators'; +import { NodeTemplateInstanceStates, PlanTypes, ServiceTemplateInstanceStates } from '../models/enums'; +import { Csar } from '../models/container/csar.model'; +import { ServiceTemplate } from '../models/container/service-template.model'; +import { PlanResources } from '../models/container/plan-resources.model'; +import { PlanInstanceResources } from '../models/container/plan-instance-resources.model'; +import { ServiceTemplateInstance } from '../models/container/service-template-instance'; +import { ServiceTemplateInstanceResources } from '../models/container/service-template-instance-resources.model'; +import { Plan } from '../models/container/plan.model'; +import { NodeTemplateResources } from '../models/container/node-template-resources.model'; +import { NodeTemplateInstanceResources } from '../models/container/node-template-instance-resources.model'; +import { NodeTemplateInstance } from '../models/container/node-template-instance.model'; +import { NgRedux } from '@angular-redux/store'; +import { IWineryState } from '../redux/store/winery.store'; +import { PlanInstance } from '../models/container/plan-instance.model'; +import { PlanLogEntry } from '../models/container/plan-log-entry.model'; +import { InputParameter } from '../models/container/input-parameter.model'; +import { OutputParameter } from '../models/container/output-parameter.model'; +import { NodeTemplate } from '../models/container/node-template.model'; +import { AdaptationPayload } from '../models/container/adaptation-payload.model'; + +@Injectable() +export class ContainerService { + private containerUrl: string; + + private readonly headerAcceptJSON = { + headers: new HttpHeaders({ + 'Accept': 'application/json' + }) + }; + private readonly headerContentJSON = { + headers: new HttpHeaders({ + 'Content-Type': 'application/json' + }) + }; + private readonly headerContentTextPlain = { + headers: new HttpHeaders({ + 'Content-Type': 'text/plain' + }) + }; + + private readonly baseInstallationPayload = [ + { 'name': 'instanceDataAPIUrl', 'type': 'String', 'required': 'true' }, + { 'name': 'csarEntrypoint', 'type': 'String', 'required': 'true' }, + { 'name': 'CorrelationID', 'type': 'String', 'required': 'true' }, + { 'name': 'containerApiAddress', 'type': 'String', 'required': 'true' } + ]; + private readonly baseManagementPayload = [ + { 'name': 'instanceDataAPIUrl', 'type': 'String', 'required': 'true' }, + { 'name': 'OpenTOSCAContainerAPIServiceInstanceURL', 'type': 'String', 'required': 'true' }, + { 'name': 'CorrelationID', 'type': 'String', 'required': 'true' }, + { 'name': 'containerApiAddress', 'type': 'String', 'required': 'true' } + ]; + private readonly baseTransformationPayload = [ + { 'name': 'CorrelationID', 'type': 'String', 'required': 'true' }, + { 'name': 'instanceDataAPIUrl', 'type': 'String', 'required': 'true' }, + { 'name': 'planCallbackAddress_invoker', 'type': 'String', 'required': 'true' }, + { 'name': 'csarEntrypoint', 'type': 'String', 'required': 'true' }, + { 'name': 'OpenTOSCAContainerAPIServiceInstanceURL', 'type': 'String', 'required': 'true' }, + { 'name': 'containerApiAddress', 'type': 'String', 'required': 'true' } + ]; + private readonly hiddenInputParameters = [ + 'CorrelationID', + 'csarID', + 'serviceTemplateID', + 'containerApiAddress', + 'instanceDataAPIUrl', + 'planCallbackAddress_invoker', + 'csarEntrypoint', + 'OpenTOSCAContainerAPIServiceInstanceID', + 'OpenTOSCAContainerAPIServiceInstanceURL' + ]; + + constructor( + private ngRedux: NgRedux, + private http: HttpClient, + ) { + this.ngRedux.select((state) => { + return state.liveModelingState.containerUrl; + }) + .subscribe((containerUrl) => { + this.containerUrl = containerUrl; + }); + } + + public getCsar(csarId: string): Observable { + const csarUrl = this.combineURLs(this.combineURLs(this.containerUrl, 'csars'), csarId); + return this.http.get(csarUrl, this.headerAcceptJSON).pipe(map((resp) => { + return resp; + })); + } + + public isApplicationInstalled(csarId: string): Observable { + const csarUrl = this.combineURLs(this.combineURLs(this.containerUrl, 'csars'), csarId); + return this.http.get(csarUrl, { observe: 'response' }).pipe( + map((resp) => { + return resp.ok; + }), + catchError(() => of(false)) + ); + } + + public installApplication(uploadPayload: CsarUpload): Observable { + return this.http.post(this.combineURLs(this.containerUrl, 'csars'), uploadPayload, this.headerContentJSON); + } + + public deleteApplication(csarId: string): Observable { + const url = this.combineURLs(this.combineURLs(this.containerUrl, 'csars'), csarId); + return this.http.delete(url); + } + + public getRequiredBuildPlanInputParameters(csarId: string): Observable> { + return this.getAllBuildPlanInputParameters(csarId).pipe( + map((resp) => { + return resp.filter((input) => { + return this.hiddenInputParameters.indexOf(input.name) === -1; + }); + }) + ); + } + + public deployServiceTemplateInstance(csarId: string, buildPlanInputParameters: InputParameter[]): Observable { + const payload = [...buildPlanInputParameters, ...this.baseInstallationPayload]; + + return this.getBuildPlan(csarId).pipe( + concatMap((resp) => { + return this.http.post(resp._links['instances'].href, payload, { + headers: new HttpHeaders({ + 'Content-Type': 'application/json' + }), + responseType: 'text' + }); + }) + ); + } + + public initializeServiceTemplateInstance(csarId: string, correlationId: string): Observable { + return this.getServiceTemplate(csarId).pipe( + concatMap((resp) => { + return this.http.post(resp._links['instances'].href, { 'correlation_id': correlationId }, { + headers: new HttpHeaders({ + 'Content-Type': 'application/json' + }), + responseType: 'text' + }); + }), + map((resp) => { + return resp.replace(/"|%22/g, ''); + }), + concatMap((resp) => { + return this.http.get(resp); + }), + map((resp) => { + return resp.id.toString(); + }) + ); + } + + public getServiceTemplateInstanceIdAfterDeployment(csarId: string, correlationId: string): Observable { + return this.getBuildPlanInstance(csarId, correlationId).pipe(retry(10), + map((resp) => { + return resp ? resp.service_template_instance_id.toString() : ''; + }), + ); + } + + public getServiceTemplateInstanceState(csarId: string, serviceTemplateInstanceId: string): Observable { + return this.getServiceTemplateInstance(csarId, serviceTemplateInstanceId).pipe( + map((resp) => { + return ServiceTemplateInstanceStates[resp.state]; + }), + ); + } + + public getServiceTemplateInstanceBuildPlanInstance(csarId: string, serviceTemplateInstanceId: string): Observable { + return this.getServiceTemplateInstance(csarId, serviceTemplateInstanceId).pipe( + concatMap((resp) => { + return this.http.get(resp._links['build_plan_instance'].href, this.headerAcceptJSON); + }), + map((resp) => { + resp.inputs = resp.inputs.filter((input) => { + return this.hiddenInputParameters.indexOf(input.name) === -1; + }); + return resp; + }) + ); + } + + public getBuildPlanOutputParameters(csarId: string): Observable> { + return this.getBuildPlan(csarId).pipe( + map((resp) => { + return resp.output_parameters; + }) + ); + } + + public getBuildPlanLogs(csarId: string, correlationId: string): Observable> { + return this.getBuildPlanInstance(csarId, correlationId).pipe(retry(10), + map((resp) => { + return resp.logs; + }) + ); + } + + public getNodeTemplates(csarId: string): Observable> { + return this.getServiceTemplate(csarId).pipe( + concatMap((resp) => { + return this.http.get(resp._links['nodetemplates'].href, this.headerAcceptJSON); + }), + map((resp) => { + return resp.node_templates; + }) + ); + } + + public getNodeTemplateInstance(csarId: string, serviceTemplateInstanceId: string, nodeTemplateId: string): Observable { + return this.getNodeTemplates(csarId).pipe( + // TODO: temporary until bug in container fixed (see https://github.com/OpenTOSCA/container/issues/133) + concatMap((resp) => { + return this.http.get( + resp.find((template) => { + return template.id.toString() === nodeTemplateId; + })._links['self'].href + '/instances', this.headerAcceptJSON); + } + ), + map((resp) => { + return resp.node_template_instances.filter((n) => { + return n.service_template_instance_id.toString() === serviceTemplateInstanceId; + }); + }), + concatMap((resp) => { + return this.http.get( + resp[resp.length - 1]._links['self'].href, this.headerAcceptJSON); + } + ) + ); + } + + public getNodeTemplateInstanceState(csarId: string, serviceTemplateInstanceId: string, nodeTemplateId: string): Observable { + return this.getNodeTemplateInstance(csarId, serviceTemplateInstanceId, nodeTemplateId).pipe( + map((resp) => { + return NodeTemplateInstanceStates[resp.state]; + }), + catchError(() => of(NodeTemplateInstanceStates.NOT_AVAILABLE)) + ); + } + + public generateTransformationPlan(sourceCsarId: string, targetCsarId: string): Observable { + const transformPayload = { + 'source_csar_name': sourceCsarId, + 'target_csar_name': targetCsarId + }; + + try { + const endpoint = this.combineURLs(this.containerUrl, 'csars/transform'); + return this.http.post(endpoint, transformPayload, this.headerContentJSON).pipe(map((resp) => { + return resp.id.toString(); + })); + } catch (error) { + console.log(error); + } + } + + public executeTransformationPlan( + serviceTemplateInstanceId: string, + planId: string, + sourceCsarId: string, + targetCsarId: string, + inputParameters: InputParameter[] + ): Observable { + const payload = [...inputParameters, ...this.baseTransformationPayload]; + try { + return this.getManagementPlan(sourceCsarId, serviceTemplateInstanceId, planId).pipe( + concatMap((resp) => { + return this.http.post(resp._links['instances'].href, + payload, + { + headers: new HttpHeaders({ + 'Content-Type': 'application/json' + }), + responseType: 'text' + }); + }) + ); + } catch (error) { + console.log(error); + } + } + + public getServiceTemplateInstanceIdAfterTransformation( + csarId: string, + serviceTemplateInstanceId: string, + correlationId: string, + planId: string): Observable { + return this.getManagementPlans(csarId, serviceTemplateInstanceId).pipe( + concatMap((resp) => { + return this.http.get( + resp.find((plan) => { + return plan.id === planId && plan.plan_type === PlanTypes.TransformationPlan; + })._links['instances'].href, this.headerAcceptJSON); + } + ), + map((resp) => { + return resp.plan_instances.find( + (plan) => { + return plan.correlation_id.toString() === correlationId; + }).outputs.find((output) => { + return output.name === 'instanceId'; + }).value; + }), + catchError(() => of('')) + ); + } + + public getTransformationPlanLogs( + csarId: string, + serviceTemplateInstanceId: string, + planId: string, + correlationId: string, + ): Observable> { + return this.getManagementPlans(csarId, serviceTemplateInstanceId).pipe( + concatMap((resp) => { + return this.http.get( + resp.find((plan) => { + return plan.id === planId && plan.plan_type === PlanTypes.TransformationPlan; + })._links['instances'].href, this.headerAcceptJSON); + } + ), + map((resp) => { + return resp.plan_instances.find((plan) => { + return plan.correlation_id.toString() === correlationId; + }).logs; + }) + ); + } + + public generateAdaptationPlan(csarId: string, payload: AdaptationPayload): Observable { + return this.getCsar(csarId).pipe( + concatMap((resp) => { + return this.http.post(this.combineURLs(resp._links['servicetemplate'].href, 'transform'), payload, this.headerContentJSON); + }), + map((resp) => { + return { + ...resp, + input_parameters: resp.input_parameters.filter((input) => { + return this.hiddenInputParameters.indexOf(input.name) === -1; + }) + }; + }) + ); + } + + public getManagementPlanInputParameters(csarId: string, serviceTemplateInstanceId: string, planId: string): Observable { + return this.getManagementPlan(csarId, serviceTemplateInstanceId, planId).pipe( + map((resp) => { + return resp.input_parameters.filter((input) => { + return this.hiddenInputParameters.indexOf(input.name) === -1; + }); + }) + ); + } + + public executeManagementPlan( + csarId: string, + serviceTemplateInstanceId: string, + planId: string, + inputParameters: InputParameter[] + ): Observable { + const payload = [...inputParameters, ...this.baseManagementPayload]; + + return this.getManagementPlan(csarId, serviceTemplateInstanceId, planId).pipe( + concatMap((resp) => { + return this.http.post(resp._links['instances'].href, + payload, + { + headers: new HttpHeaders({ + 'Content-Type': 'application/json' + }), + responseType: 'text' + }); + }) + ); + } + + public updateNodeTemplateInstanceState( + csarId: string, + serviceTemplateInstanceId: string, + nodeTemplateId: string, + state: NodeTemplateInstanceStates + ): Observable { + return this.getNodeTemplateInstance(csarId, serviceTemplateInstanceId, nodeTemplateId).pipe( + concatMap((resp) => { + return this.http.put(resp._links['state'].href, state.toString(), this.headerContentTextPlain); + }) + ); + } + + public executeTerminationPlan(csarId: string, serviceTemplateInstanceId: string): Observable { + return this.getTerminationPlan(csarId, serviceTemplateInstanceId).pipe( + concatMap((resp) => { + return this.http.post(resp._links['instances'].href, this.baseManagementPayload, { + headers: new HttpHeaders({ + 'Content-Type': 'application/json' + }), + responseType: 'text' + }); + }) + ); + } + + private getServiceTemplate(csarId: string): Observable { + return this.getCsar(csarId).pipe( + concatMap((resp) => { + return this.http.get(resp._links['servicetemplate'].href, this.headerAcceptJSON); + }) + ); + } + + private getServiceTemplateInstance(csarId: string, serviceTemplateInstanceId: string): Observable { + return this.getServiceTemplate(csarId).pipe( + concatMap((resp) => { + return this.http.get(resp._links['instances'].href, this.headerAcceptJSON); + }), + concatMap((resp) => { + return this.http.get( + resp.service_template_instances.find((instance) => { + return instance.id.toString() === serviceTemplateInstanceId; + })._links['self'].href, this.headerAcceptJSON); + } + ) + ); + } + + private getAllBuildPlanInputParameters(csarId: string): Observable> { + return this.getBuildPlan(csarId).pipe( + map((resp) => { + return resp.input_parameters; + }) + ); + } + + private getBuildPlan(csarId: string): Observable { + const result = this.getServiceTemplate(csarId).pipe( + concatMap((resp) => { + return this.http.get(resp._links['buildplans'].href, this.headerAcceptJSON); + }), + map((resp) => { + return resp.plans.find((plan) => { + return plan.plan_type === PlanTypes.BuildPlan; + }); + }) + ); + return result; + } + + private getBuildPlanInstance(csarId: string, correlationId: string): Observable { + return this.getBuildPlan(csarId).pipe( + concatMap((resp) => { + return this.http.get(resp._links['instances'].href, this.headerAcceptJSON); + }), + map((resp) => { + return resp.plan_instances.find((planInstance) => { + return planInstance.correlation_id.toString() === correlationId; + }); + }) + ); + } + + private getManagementPlans(csarId: string, serviceTemplateInstanceId: string): Observable> { + return this.getServiceTemplateInstance(csarId, serviceTemplateInstanceId).pipe( + concatMap((resp) => { + return this.http.get(resp._links['managementplans'].href, this.headerAcceptJSON); + }), + map((resp) => { + return resp.plans; + }) + ); + } + + private getManagementPlan(csarId: string, serviceTemplateInstanceId: string, planId: string): Observable { + return this.getManagementPlans(csarId, serviceTemplateInstanceId).pipe( + map((resp) => { + return resp.find((plan) => { + return plan.id.toString() === planId; + }); + }) + ); + } + + private getTerminationPlan(csarId: string, serviceTemplateInstanceId: string): Observable { + return this.getManagementPlans(csarId, serviceTemplateInstanceId).pipe( + map((resp) => { + return resp.find((plan) => { + return plan.plan_type === PlanTypes.TerminationPlan; + }); + }) + ); + } + + private combineURLs(baseURL: string, relativeURL: string) { + return relativeURL + ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '') + : baseURL; + } + + private stripCsarSuffix(csarId: string) { + const csarEnding = '.csar'; + return csarId.endsWith(csarEnding) ? csarId.slice(0, -csarEnding.length) : csarId; + } +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/services/live-modeling.service.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/services/live-modeling.service.ts new file mode 100644 index 0000000000..6c9450647b --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/services/live-modeling.service.ts @@ -0,0 +1,910 @@ +/******************************************************************************* + * Copyright (c) 2019-2021 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +import { Injectable } from '@angular/core'; +import { NgRedux } from '@angular-redux/store'; +import { IWineryState } from '../redux/store/winery.store'; +import { + AdaptationAction, LiveModelingStates, NodeTemplateInstanceStates, ServiceTemplateInstanceStates +} from '../models/enums'; +import { BackendService } from './backend.service'; +import { CsarUpload } from '../models/container/csar-upload.model'; +import { ContainerService } from './container.service'; +import { ErrorHandlerService } from './error-handler.service'; +import { TRelationshipTemplate, TTopologyTemplate } from '../models/ttopology-template'; +import { LiveModelingActions } from '../redux/actions/live-modeling.actions'; +import { WineryActions } from '../redux/actions/winery.actions'; +import { BsModalService } from 'ngx-bootstrap'; +import { OverlayService } from './overlay.service'; +import { LoggingService } from './logging.service'; +import { catchError, concatMap, distinctUntilChanged, first, switchMap, takeWhile, tap, timeout } from 'rxjs/operators'; +import { forkJoin, Observable, of, Subscription } from 'rxjs'; +import 'rxjs/add/observable/timer'; +import { Csar } from '../models/container/csar.model'; +import { PlanInstance } from '../models/container/plan-instance.model'; +import { InputParameter } from '../models/container/input-parameter.model'; +import { + InputParametersModalComponent +} from '../live-modeling/modals/input-parameters-modal/input-parameters-modal.component'; +import { ToastrService } from 'ngx-toastr'; +import { NodeTemplateInstance } from '../models/container/node-template-instance.model'; +import { + AdaptInstanceError, CreateLiveModelingTemplateError, DeployInstanceError, LiveModelingError, + NodeTemplateInstanceError, RetrieveInputParametersError, ServiceTemplateInstanceError, TerminateInstanceError, + TimeoutError, TransformInstanceError, UploadCsarError +} from '../models/customErrors'; +import { AdaptationPayload } from '../models/container/adaptation-payload.model'; +import * as _ from 'lodash'; +import { LiveModelingSettings } from '../models/liveModelingSettings'; + +@Injectable() +export class LiveModelingService { + + private currentCsarId: string; + private currentServiceTemplateInstanceId: string; + private currentTopologyTemplate: TTopologyTemplate; + private lastSavedTopologyTemplate: TTopologyTemplate; + private deployedTopologyTemplate: TTopologyTemplate; + + private settings: LiveModelingSettings; + private state: LiveModelingStates; + + constructor( + private ngRedux: NgRedux, + private liveModelingActions: LiveModelingActions, + private wineryActions: WineryActions, + private containerService: ContainerService, + private backendService: BackendService, + private errorHandler: ErrorHandlerService, + private modalService: BsModalService, + private overlayService: OverlayService, + private loggingService: LoggingService, + private toastrService: ToastrService) { + + this.ngRedux.select((state) => { + return state.liveModelingState.currentCsarId; + }) + .subscribe((csarId) => { + this.currentCsarId = csarId; + }); + this.ngRedux.select((state) => { + return state.liveModelingState.currentServiceTemplateInstanceId; + }) + .subscribe((serviceTemplateInstanceId) => { + this.currentServiceTemplateInstanceId = serviceTemplateInstanceId; + }); + this.ngRedux.select((state) => { + return state.wineryState.currentJsonTopology; + }) + .subscribe((topologyTemplate) => { + this.currentTopologyTemplate = topologyTemplate; + }); + this.ngRedux.select((state) => { + return state.wineryState.lastSavedJsonTopology; + }) + .subscribe((topologyTemplate) => { + this.lastSavedTopologyTemplate = topologyTemplate; + }); + this.ngRedux.select((state) => { + return state.liveModelingState.deployedJsonTopology; + }) + .subscribe((topologyTemplate) => { + this.deployedTopologyTemplate = topologyTemplate; + }); + this.ngRedux.select((state) => { + return state.liveModelingState.state; + }) + .subscribe((state) => { + this.state = state; + }); + this.ngRedux.select((state) => { + return state.liveModelingState.settings; + }) + .subscribe((settings) => { + this.settings = settings; + }); + } + + public async init(startInstance: boolean, containerUrl?: string): Promise { + try { + if (this.liveModelingIsActive()) { + this.toastrService.error('Unauthorized action'); + return; + } + this.ngRedux.dispatch(this.liveModelingActions.setState(LiveModelingStates.INIT)); + this.clearData(); + if (containerUrl) { + this.ngRedux.dispatch(this.liveModelingActions.setContainerUrl(containerUrl)); + } + this.setAllNodeTemplateWorkingState(true); + const csarId = await this.createLiveModelingServiceTemplate(); + await this.installCsarIfNeeded(csarId); + this.ngRedux.dispatch(this.liveModelingActions.setCurrentCsarId(csarId)); + this.setAllNodeTemplateWorkingState(false); + await this.deploy(startInstance); + } catch (error) { + this.handleError(error); + } + } + + public liveModelingIsActive(): boolean { + return this.state !== LiveModelingStates.DISABLED && + this.state !== LiveModelingStates.TERMINATED && + this.state !== LiveModelingStates.ERROR && + this.state !== LiveModelingStates.RECONFIGURATE; + } + + public propertyStateForDeploy(): boolean { + return this.state !== LiveModelingStates.INIT && + this.state !== LiveModelingStates.TERMINATED && + this.state !== LiveModelingStates.ERROR; + } + + public async deploy(startInstance: boolean): Promise { + try { + if (this.propertyStateForDeploy()) { + this.toastrService.error('Unauthorized action'); + return; + } + this.ngRedux.dispatch(this.liveModelingActions.setState(LiveModelingStates.DEPLOY)); + const csarId = this.currentCsarId; + this.setAllNodeTemplateWorkingState(true); + this.setAllNodeTemplateInstanceState(NodeTemplateInstanceStates.NOT_AVAILABLE); + let newInstanceId; + if (startInstance) { + const buildPlanInputParameters = await this.retrieveBuildPlanParametersAndShowModalIfNeeded(csarId); + newInstanceId = await this.deployServiceTemplateInstance(csarId, buildPlanInputParameters); + } else { + newInstanceId = await this.initializeServiceTemplateInstance(csarId); + } + this.ngRedux.dispatch(this.liveModelingActions.setCurrentServiceTemplateInstanceId(newInstanceId)); + await this.setDeployedTopologyTemplate(csarId); + this.setAllNodeTemplateWorkingState(false); + await this.update(); + } catch (error) { + this.handleError(error); + } + } + + public isProperStateForUpdate(): boolean { + return this.state !== LiveModelingStates.DEPLOY && + this.state !== LiveModelingStates.ENABLED && + this.state !== LiveModelingStates.RECONFIGURATE; + } + + public async update(): Promise { + try { + if (this.isProperStateForUpdate()) { + this.toastrService.error('Unauthorized action'); + return; + } + this.ngRedux.dispatch(this.liveModelingActions.setState(LiveModelingStates.UPDATE)); + this.setAllNodeTemplateWorkingState(true); + await this.updateLiveModelingData(this.currentCsarId, this.currentServiceTemplateInstanceId); + this.setAllNodeTemplateWorkingState(false); + this.ngRedux.dispatch(this.liveModelingActions.setState(LiveModelingStates.ENABLED)); + } catch (error) { + this.handleError(error); + } + } + + public async redeploy(startInstance: boolean): Promise { + try { + if (this.properStateForRedeploy()) { + this.toastrService.error('Unauthorized action'); + return; + } + this.ngRedux.dispatch(this.liveModelingActions.setState(LiveModelingStates.RECONFIGURATE)); + const oldCsarId = this.currentCsarId; + const oldInstanceId = this.currentServiceTemplateInstanceId; + this.setAllNodeTemplateWorkingState(true); + if (oldInstanceId) { + this.terminateServiceTemplateInstanceInBackground(oldCsarId, oldInstanceId).add(() => { + this.deleteApplicationInBackground(oldCsarId); + }); + } + this.setAllNodeTemplateWorkingState(false); + await this.init(startInstance); + } catch (error) { + this.handleError(error); + } + } + + public properStateForRedeploy(): boolean { + return this.state !== LiveModelingStates.ENABLED && + this.state !== LiveModelingStates.ERROR && + this.state !== LiveModelingStates.TERMINATED; + } + + public async transform(): Promise { + try { + if (this.state !== LiveModelingStates.ENABLED) { + this.toastrService.error('Unauthorized action'); + return; + } + this.ngRedux.dispatch(this.liveModelingActions.setState(LiveModelingStates.RECONFIGURATE)); + this.setAllNodeTemplateWorkingState(true); + const sourceCsarId = this.currentCsarId; + const oldInstanceId = this.currentServiceTemplateInstanceId; + const targetCsarId = await this.createLiveModelingServiceTemplate(); + await this.installCsarIfNeeded(targetCsarId); + const transformationPlanId = await this.containerService.generateTransformationPlan(sourceCsarId, targetCsarId).toPromise(); + const parameterPayload = await this.retrieveTransformPlanParametersAndShowModalIfNeeded( + sourceCsarId, oldInstanceId, transformationPlanId); + this.setAllNodeTemplateInstanceState(NodeTemplateInstanceStates.NOT_AVAILABLE); + const newInstanceId = await this.transformServiceTemplateInstance( + sourceCsarId, targetCsarId, oldInstanceId, parameterPayload, transformationPlanId); + this.ngRedux.dispatch(this.liveModelingActions.setCurrentCsarId(targetCsarId)); + this.ngRedux.dispatch(this.liveModelingActions.setCurrentServiceTemplateInstanceId(newInstanceId)); + await this.setDeployedTopologyTemplate(targetCsarId); + this.setAllNodeTemplateWorkingState(false); + await this.update(); + } catch (error) { + this.handleError(error); + } + } + + public async adapt(nodeTemplateId: string, adaptationAction: AdaptationAction) { + try { + if (this.state !== LiveModelingStates.ENABLED) { + this.toastrService.error('Unauthorized action'); + return; + } + this.ngRedux.dispatch(this.liveModelingActions.setState(LiveModelingStates.RECONFIGURATE)); + const csarId = this.currentCsarId; + const serviceTemplateInstanceId = this.currentServiceTemplateInstanceId; + const topologyTemplate = _.cloneDeep(this.currentTopologyTemplate); + const adaptationPayload = this.generateAdaptationPayload(topologyTemplate, nodeTemplateId, adaptationAction); + const workingNodeIds = _.union(adaptationPayload.source_node_templates, adaptationPayload.target_node_templates); + + for (const nodeId of workingNodeIds) { + this.ngRedux.dispatch(this.wineryActions.setNodeWorking(nodeId, true)); + } + + // MOCK: + // await this.adaptServiceTemplateInstanceMock(csarId, serviceTemplateInstanceId, topologyTemplate, adaptationPayload); + await this.adaptServiceTemplateInstance(csarId, serviceTemplateInstanceId, adaptationPayload); + + await this.waitUntilNodeTemplateInstanceIsInState( + csarId, + serviceTemplateInstanceId, + nodeTemplateId, + adaptationAction === AdaptationAction.START_NODE ? NodeTemplateInstanceStates.STARTED : NodeTemplateInstanceStates.DELETED + ); + + for (const nodeId of workingNodeIds) { + this.ngRedux.dispatch(this.wineryActions.setNodeWorking(nodeId, false)); + } + await this.update(); + } catch (error) { + this.handleError(error); + } + } + + public async terminate(): Promise { + try { + if (this.state !== LiveModelingStates.ENABLED) { + this.toastrService.error('Unauthorized action'); + return; + } + this.ngRedux.dispatch(this.liveModelingActions.setState(LiveModelingStates.TERMINATE)); + this.setAllNodeTemplateWorkingState(true); + await this.terminateServiceTemplateInstance(this.currentCsarId, this.currentServiceTemplateInstanceId); + this.setAllNodeTemplateWorkingState(false); + this.ngRedux.dispatch(this.liveModelingActions.setState(LiveModelingStates.TERMINATED)); + } catch (error) { + this.handleError(error); + } + } + + public disable(): Promise { + try { + if (this.properStateForDisable()) { + this.toastrService.error('Unauthorized action'); + return; + } + const csarId = this.currentCsarId; + const instanceId = this.currentServiceTemplateInstanceId; + if (this.state === LiveModelingStates.ENABLED && instanceId) { + this.terminateServiceTemplateInstanceInBackground(csarId, instanceId).add(() => { + this.deleteApplicationInBackground(csarId); + }); + } else { + this.deleteApplicationInBackground(csarId); + } + this.resetLiveModelingData(); + this.ngRedux.dispatch(this.liveModelingActions.setState(LiveModelingStates.DISABLED)); + } catch (error) { + this.handleError(error); + } + } + + public properStateForDisable(): boolean { + return this.state !== LiveModelingStates.ENABLED && + this.state !== LiveModelingStates.TERMINATED && + this.state !== LiveModelingStates.ERROR; + } + + // Action methods + + public fetchNodeTemplateInstanceData(nodeTemplateId: string): Observable { + try { + if (this.state === LiveModelingStates.DISABLED) { + return of(null); + } + return this.containerService.getNodeTemplateInstance(this.currentCsarId, this.currentServiceTemplateInstanceId, nodeTemplateId).pipe( + tap((resp) => { + this.ngRedux.dispatch(this.wineryActions.setNodeInstanceState(nodeTemplateId, NodeTemplateInstanceStates[resp.state])); + }), + catchError((error) => { + this.loggingService.logWarning('Unable to fetch node template instance' + error.message); + this.ngRedux.dispatch(this.wineryActions.setNodeInstanceState(nodeTemplateId, NodeTemplateInstanceStates.NOT_AVAILABLE)); + return of(null); + }) + ); + } catch (error) { + return of(null); + } + } + + private clearData(): void { + this.loggingService.clearLogs(); + } + + private async createLiveModelingServiceTemplate(): Promise { + try { + this.overlayService.showOverlay('Creating temporary live modeling template'); + const resp = await this.backendService.createLiveModelingServiceTemplate().toPromise(); + const csarId = resp.localname; + const csarEnding = '.csar'; + const normalizedCsarId = csarId.endsWith(csarEnding) ? csarId : csarId + csarEnding; + this.overlayService.hideOverlay(); + return normalizedCsarId; + } catch (error) { + console.log(error); + throw new CreateLiveModelingTemplateError(); + } + } + + private async installCsarIfNeeded(csarId: string): Promise { + try { + const appInstalled = await this.containerService.isApplicationInstalled(csarId).toPromise(); + if (!appInstalled) { + this.loggingService.logWarning('App not found, installing now...'); + const uploadPayload = new CsarUpload(this.getCsarResourceUrl(csarId), csarId, 'false'); + await this.containerService.installApplication(uploadPayload).toPromise(); + } else { + this.loggingService.logInfo('App found. Skipping installation'); + } + } catch (error) { + console.log(error); + throw new UploadCsarError(); + } + } + + private getCsarResourceUrl(csarId: string): string { + const csarQueryString = '?csar'; + const csarEnding = '.csar'; + const csarIdWithoutCsarSuffix = csarId.endsWith(csarEnding) ? csarId.slice(0, -csarEnding.length) : csarId; + return this.settings.wineryEndpoint + '/' + + this.backendService.configuration.parentPath + '/' + + encodeURIComponent(encodeURIComponent(this.backendService.configuration.ns)) + '/' + + csarIdWithoutCsarSuffix + csarQueryString; + } + + private async retrieveBuildPlanParametersAndShowModalIfNeeded(csarId: string): Promise { + try { + const requiredBuildPlanInputParameters = await this.containerService.getRequiredBuildPlanInputParameters(csarId).toPromise(); + let buildPlanInputParameters = []; + if (requiredBuildPlanInputParameters.length > 0) { + buildPlanInputParameters = await this.requestInputParameters(requiredBuildPlanInputParameters); + } + return buildPlanInputParameters; + } catch (error) { + console.log(error); + throw new RetrieveInputParametersError(); + } + } + + private async deployServiceTemplateInstance(csarId: string, buildPlanInputParameters: InputParameter[]): Promise { + let correlationId; + try { + this.setAllNodeTemplateInstanceState(NodeTemplateInstanceStates.INITIAL); + + this.loggingService.logInfo('Deploying service template instance...'); + + correlationId = await this.containerService.deployServiceTemplateInstance(csarId, buildPlanInputParameters).toPromise(); + + this.loggingService.logInfo('Executing build plan with correlation id ' + correlationId); + + const newInstanceId = await this.waitForServiceTemplateInstanceIdAfterDeployment(csarId, correlationId).toPromise(); + + this.loggingService.logInfo('Waiting for deployment of service template instance with id ' + newInstanceId); + + await this.waitUntilServiceTemplateInstanceIsInState(csarId, newInstanceId, ServiceTemplateInstanceStates.CREATED); + + this.loggingService.logSuccess('Successfully deployed service template instance with Id ' + newInstanceId); + return newInstanceId; + } catch (error) { + console.log(error); + throw new DeployInstanceError(); + } finally { + try { + const buildPlanLogs = await this.containerService.getBuildPlanLogs(csarId, correlationId).toPromise(); + for (const log of buildPlanLogs) { + this.loggingService.logContainer(log.message); + } + } catch (e) { + console.log(e); + } + } + } + + private waitForServiceTemplateInstanceIdAfterDeployment(csarId: string, correlationId: string): Observable { + return Observable.timer(0, this.settings.interval).pipe( + concatMap(() => this.containerService.getServiceTemplateInstanceIdAfterDeployment(csarId, correlationId)), + distinctUntilChanged(), + first((resp) => { + return resp !== ''; + }), + timeout(this.settings.timeout) + ); + } + + private async initializeServiceTemplateInstance(csarId: string) { + const correlationId = Date.now().toString(); + return await this.containerService.initializeServiceTemplateInstance(csarId, correlationId).toPromise(); + } + + private waitUntilServiceTemplateInstanceIsInState( + csarId: string, + serviceTemplateInstanceId: string, + desiredInstanceState: ServiceTemplateInstanceStates + ): Promise { + return new Promise((resolve, reject) => { + Observable.timer(0, this.settings.interval).pipe( + concatMap(() => this.containerService.getServiceTemplateInstanceState(csarId, serviceTemplateInstanceId)), + distinctUntilChanged(), + timeout(this.settings.timeout), + // TODO: fix + // takeWhile(state => state !== desiredInstanceState && state !== ServiceTemplateInstanceStates.ERROR, true), + takeWhile((state) => { + return state !== desiredInstanceState && state !== ServiceTemplateInstanceStates.ERROR; + }), + ).subscribe((state) => { + this.ngRedux.dispatch(this.liveModelingActions.setCurrentServiceTemplateInstanceState(state)); + if (state === ServiceTemplateInstanceStates.ERROR) { + reject(new ServiceTemplateInstanceError()); + } + }, () => { + reject(new TimeoutError()); + }, () => { + resolve(); + }); + }); + } + + private updateLiveModelingData(csarId: string, serviceTemplateInstanceId: string): Promise { + return forkJoin([ + this.updateCsar(csarId) + .pipe(tap((resp) => { + this.ngRedux.dispatch(this.liveModelingActions.setCurrentCsar(resp)); + })), + this.updateBuildPlanInstance(csarId, serviceTemplateInstanceId) + .pipe(tap((resp) => { + this.ngRedux.dispatch(this.liveModelingActions.setCurrentBuildPlanInstance(resp)); + })), + this.updateCurrentServiceTemplateInstanceState(csarId, serviceTemplateInstanceId) + .pipe(tap((resp) => { + this.ngRedux.dispatch(this.liveModelingActions.setCurrentServiceTemplateInstanceState(resp)); + })), + this.updateNodeTemplateData(csarId, serviceTemplateInstanceId) + ]).toPromise(); + } + + private updateCsar(csarId: string): Observable { + this.loggingService.logInfo('Fetching csar information'); + return this.containerService.getCsar(csarId).pipe( + catchError((error) => { + this.loggingService.logWarning(`Unable to fetch csar information\n${error.message}`); + return of(null); + }) + ); + } + + private updateBuildPlanInstance(csarId: string, serviceTemplateInstanceId: string): Observable { + this.loggingService.logInfo('Fetching service template instance build plan instance'); + return this.containerService.getServiceTemplateInstanceBuildPlanInstance(csarId, serviceTemplateInstanceId).pipe( + catchError((error) => { + this.loggingService.logWarning(`Unable to fetch build plan instance\n${error.message}`); + return of(null); + }) + ); + } + + private updateNodeTemplateData(csarId: string, serviceTemplateInstanceId: string): Observable { + this.loggingService.logInfo('Fetching node template data'); + return this.containerService.getNodeTemplates(csarId).pipe( + switchMap((nodeTemplates) => { + const observables: Observable[] = []; + for (const nodeTemplate of nodeTemplates) { + observables.push(this.containerService.getNodeTemplateInstance( + csarId, serviceTemplateInstanceId, nodeTemplate.id).pipe( + tap((resp) => { + this.ngRedux.dispatch(this.wineryActions.setNodeInstanceState(resp.node_template_id, NodeTemplateInstanceStates[resp.state])); + }), + catchError((error) => { + this.loggingService.logWarning(`Unable to fetch data for node ${nodeTemplate.id}\n${error.message}`); + return of(null); + }) + )); + } + return forkJoin(observables); + }), + catchError((error) => { + this.loggingService.logWarning(`Unable to fetch node templates\n${error.message}`); + return of(null); + }) + ); + } + + private updateCurrentServiceTemplateInstanceState(csarId: string, serviceTemplateInstanceId: string): Observable { + this.loggingService.logInfo('Fetching service template instance state'); + return this.containerService.getServiceTemplateInstanceState(csarId, serviceTemplateInstanceId).pipe( + catchError((error) => { + this.loggingService.logWarning(`Unable to fetch service template instance state\n${error.message}`); + return of(ServiceTemplateInstanceStates.NOT_AVAILABLE); + }) + ); + } + + private async retrieveTransformPlanParametersAndShowModalIfNeeded( + csarId: string, serviceTemplateInstanceId: string, transformationPlanId: string): Promise { + try { + const inputParameters = await this.containerService.getManagementPlanInputParameters( + csarId, + serviceTemplateInstanceId, + transformationPlanId + ).toPromise(); + + let parameterPayload = []; + if (inputParameters.length > 0) { + parameterPayload = await this.requestInputParameters(inputParameters); + } + + return parameterPayload; + } catch (error) { + this.loggingService.logWarning(`Error while retrieving Transformation Plan parameters!\n${error.message}`); + throw new RetrieveInputParametersError(); + } + } + + private async transformServiceTemplateInstance( + sourceCsarId: string, + targetCsarId: string, + serviceTemplateInstanceId: string, + transformationPayload: InputParameter[], + planId: string): Promise { + let correlationId; + let newInstanceId; + try { + this.setAllNodeTemplateInstanceState(NodeTemplateInstanceStates.INITIAL); + + this.loggingService.logInfo('Transforming service template instance...'); + correlationId = await this.containerService.executeTransformationPlan( + serviceTemplateInstanceId, planId, sourceCsarId, targetCsarId, transformationPayload).toPromise(); + + this.loggingService.logInfo('Executing transformation plan with correlation id ' + correlationId); + + await this.waitUntilServiceTemplateInstanceIsInState(sourceCsarId, serviceTemplateInstanceId, ServiceTemplateInstanceStates.MIGRATED); + + newInstanceId = await this.waitForServiceTemplateInstanceIdAfterMigration( + sourceCsarId, serviceTemplateInstanceId, planId, correlationId).toPromise(); + this.loggingService.logInfo('Waiting for transformation of service template instance with id ' + newInstanceId); + + await this.waitUntilServiceTemplateInstanceIsInState(targetCsarId, newInstanceId, ServiceTemplateInstanceStates.CREATED); + + this.loggingService.logSuccess('Successfully transformed service template instance from ${prevServiceTemplateInstanceId} to ${newInstanceId}'); + return newInstanceId; + } catch (error) { + this.loggingService.logWarning(`Unable to transform the service templates\n${error.message}`); + throw new TransformInstanceError(); + } finally { + try { + const transformationPlanLogs = await this.containerService.getTransformationPlanLogs( + targetCsarId, newInstanceId, planId, correlationId).toPromise(); + for (const log of transformationPlanLogs) { + this.loggingService.logContainer(log.message); + } + } catch (e) { + console.log(e); + } + } + } + + private waitForServiceTemplateInstanceIdAfterMigration( + csarId: string, + serviceTemplateInstanceId: string, + planId: string, + correlationId: string + ): Observable { + return Observable.timer(0, this.settings.interval).pipe( + concatMap(() => this.containerService.getServiceTemplateInstanceIdAfterTransformation(csarId, serviceTemplateInstanceId, correlationId, planId)), + distinctUntilChanged(), + first((resp) => { + return resp !== ''; + }), + timeout(this.settings.timeout) + ); + } + + private async adaptServiceTemplateInstance( + csarId: string, + serviceTemplateInstanceId: string, + adaptationPayload: AdaptationPayload): Promise { + try { + const adaptationPlan = await this.containerService.generateAdaptationPlan(csarId, adaptationPayload).toPromise(); + let parameterPayload = []; + if (adaptationPlan.input_parameters.length > 0) { + parameterPayload = await this.requestInputParameters(adaptationPlan.input_parameters); + } + return await this.containerService.executeManagementPlan(csarId, serviceTemplateInstanceId, adaptationPlan.id, parameterPayload).toPromise(); + } catch (error) { + console.log(error); + throw new AdaptInstanceError(); + } + } + + private adaptServiceTemplateInstanceMock( + csarId: string, + serviceTemplateInstanceId: string, + topologyTemplate: TTopologyTemplate, + adaptationPayload: AdaptationPayload) { + const startingNodes = adaptationPayload.target_node_templates; + const stoppingNodes = topologyTemplate.nodeTemplates.filter((n) => { + return !startingNodes.includes(n.id); + }).map((n) => { + return n.id; + }); + + const observables = []; + startingNodes.forEach((nodeId) => { + observables.push( + this.containerService.updateNodeTemplateInstanceState(csarId, serviceTemplateInstanceId, nodeId, NodeTemplateInstanceStates.STARTED)); + }); + stoppingNodes.forEach((nodeId) => { + observables.push( + this.containerService.updateNodeTemplateInstanceState(csarId, serviceTemplateInstanceId, nodeId, NodeTemplateInstanceStates.DELETED)); + }); + return Observable.forkJoin(observables).toPromise(); + } + + private waitUntilNodeTemplateInstanceIsInState( + csarId: string, + serviceTemplateInstanceId: string, + nodeTemplateId: string, + desiredInstanceState: NodeTemplateInstanceStates + ): Promise { + return new Promise((resolve, reject) => { + Observable.timer(0, this.settings.interval).pipe( + concatMap(() => this.containerService.getNodeTemplateInstanceState(csarId, serviceTemplateInstanceId, nodeTemplateId)), + distinctUntilChanged(), + timeout(this.settings.timeout), + // TODO: fix + // takeWhile(state => state !== desiredInstanceState && state !== NodeTemplateInstanceStates.ERROR, true), + takeWhile((state) => { + return state !== desiredInstanceState && state !== NodeTemplateInstanceStates.ERROR; + }), + ).subscribe((state) => { + if (state === NodeTemplateInstanceStates.ERROR) { + reject(new NodeTemplateInstanceError()); + } + }, () => { + reject(new TimeoutError()); + }, () => { + resolve(); + }); + }); + } + + private async terminateServiceTemplateInstance(csarId: string, serviceTemplateInstanceId: string): Promise { + try { + + this.loggingService.logInfo('Terminating service template instance ${serviceTemplateInstanceId}'); + + await this.containerService.executeTerminationPlan(csarId, serviceTemplateInstanceId).toPromise(); + + this.loggingService.logInfo('Waiting for deletion of service template instance with instance id ${serviceTemplateInstanceId}'); + + await this.waitUntilServiceTemplateInstanceIsInState(csarId, serviceTemplateInstanceId, ServiceTemplateInstanceStates.DELETED); + + this.setAllNodeTemplateInstanceState(NodeTemplateInstanceStates.NOT_AVAILABLE); + this.ngRedux.dispatch(this.liveModelingActions.setCurrentServiceTemplateInstanceId(null)); + this.ngRedux.dispatch(this.liveModelingActions.setCurrentBuildPlanInstance(null)); + + this.loggingService.logSuccess('Instance has been successfully deleted'); + } catch (error) { + throw new TerminateInstanceError(); + } + } + + private terminateServiceTemplateInstanceInBackground(csarId: string, serviceTemplateInstanceId: string): Subscription { + return this.containerService.executeTerminationPlan(csarId, serviceTemplateInstanceId).subscribe(() => { + this.toastrService.info('Instance successfully terminated'); + }, (error) => { + this.toastrService.error(`There was an error while terminating the service template instance\n${error.message}`); + }); + } + + private deleteApplicationInBackground(csarId: string): Subscription { + return this.containerService.deleteApplication(csarId).subscribe(() => { + this.toastrService.info('Application successfully deleted'); + }, (error) => { + this.toastrService.error(`There was an error while deleting the application\n${error.message}`); + }); + } + + private resetLiveModelingData(): void { + this.ngRedux.dispatch(this.liveModelingActions.setCurrentCsar(null)); + this.ngRedux.dispatch(this.liveModelingActions.setCurrentCsarId(null)); + this.ngRedux.dispatch(this.liveModelingActions.setCurrentServiceTemplateInstanceId(null)); + this.ngRedux.dispatch(this.liveModelingActions.setCurrentServiceTemplateInstanceState(null)); + this.ngRedux.dispatch(this.liveModelingActions.setCurrentBuildPlanInstance(null)); + this.setAllNodeTemplateWorkingState(false); + this.setAllNodeTemplateInstanceState(null); + } + + // Helper methods + + private handleError(error: Error): void { + this.updateLiveModelingData(this.currentCsarId, this.currentServiceTemplateInstanceId); + + if (error instanceof LiveModelingError) { + this.toastrService.error(error.message); + this.loggingService.logError(error.message); + } else { + const errorMessage = 'There was an unexpected error during operation'; + this.toastrService.error(errorMessage); + this.loggingService.logError(errorMessage); + } + + this.ngRedux.dispatch(this.liveModelingActions.setState(LiveModelingStates.ERROR)); + this.ngRedux.dispatch(this.liveModelingActions.setCurrentServiceTemplateInstanceState(ServiceTemplateInstanceStates.ERROR)); + this.overlayService.hideOverlay(); + this.setAllNodeTemplateWorkingState(false); + } + + private setAllNodeTemplateWorkingState(working: boolean): void { + for (const nodeTemplate of this.lastSavedTopologyTemplate.nodeTemplates) { + this.ngRedux.dispatch(this.wineryActions.setNodeWorking(nodeTemplate.id, working)); + } + } + + private setAllNodeTemplateInstanceState(state: NodeTemplateInstanceStates): void { + for (const nodeTemplate of this.lastSavedTopologyTemplate.nodeTemplates) { + this.ngRedux.dispatch(this.wineryActions.setNodeInstanceState(nodeTemplate.id, state)); + } + } + + private async requestInputParameters(inputParameters: InputParameter[]): Promise { + const initialState = { + inputParameters: inputParameters + }; + const modalRef = this.modalService.show(InputParametersModalComponent, { initialState, backdrop: 'static' }); + await new Promise((resolve) => { + this.modalService.onHidden.subscribe(() => { + resolve(); + }); + }); + + if (modalRef.content.cancelled) { + return null; + } + + return modalRef.content.inputParameters; + } + + private generateAdaptationPayload(topologyTemplate: TTopologyTemplate, nodeTemplateId: string, adaptationAction: AdaptationAction): AdaptationPayload { + // Calculate source node templates + const sourceNodeTemplates = []; + for (const nodeTemplate of topologyTemplate.nodeTemplates) { + if (nodeTemplate.instanceState === NodeTemplateInstanceStates.STARTED) { + sourceNodeTemplates.push(nodeTemplate.id); + } + } + + // Calculate source relationship templates + const sourceRelationshipTemplates = []; + for (const relationshipTemplate of topologyTemplate.relationshipTemplates) { + if (sourceNodeTemplates.findIndex((n) => { + return n === relationshipTemplate.sourceElement.ref; + }) > -1 && + sourceNodeTemplates.findIndex((n) => { + return n === relationshipTemplate.targetElement.ref; + }) > -1 + ) { + sourceRelationshipTemplates.push(relationshipTemplate.id); + } + } + + // Recursively calculate all node templates that are dependent + const tempNodeTemplates = []; + tempNodeTemplates.push(nodeTemplateId); + const dependentNodeTemplatesSet = new Set(); + while (tempNodeTemplates.length > 0) { + const nodeId = tempNodeTemplates.shift(); + dependentNodeTemplatesSet.add(nodeId); + let tempRelationships: TRelationshipTemplate[]; + if (adaptationAction === AdaptationAction.START_NODE) { + tempRelationships = topologyTemplate.relationshipTemplates.filter((rel) => { + return rel.sourceElement.ref === nodeId; + }); + } else { + tempRelationships = topologyTemplate.relationshipTemplates.filter((rel) => { + return rel.targetElement.ref === nodeId; + }); + } + for (const tempRel of tempRelationships) { + if (adaptationAction === AdaptationAction.START_NODE) { + tempNodeTemplates.push(tempRel.targetElement.ref); + } else { + tempNodeTemplates.push(tempRel.sourceElement.ref); + } + } + } + + // Holds all node templates that need to be started/stopped + let targetNodeTemplates; + const dependentNodeTemplates = Array.from(dependentNodeTemplatesSet); + if (adaptationAction === AdaptationAction.START_NODE) { + targetNodeTemplates = _.union(sourceNodeTemplates, dependentNodeTemplates); + } else { + targetNodeTemplates = sourceNodeTemplates.filter((n) => { + return !dependentNodeTemplates.includes(n); + }); + } + + // Calculate source relationship templates + const targetRelationshipTemplates = []; + for (const relationshipTemplate of topologyTemplate.relationshipTemplates) { + if (targetNodeTemplates.findIndex((n) => { + return n === relationshipTemplate.sourceElement.ref; + }) > -1 && + targetNodeTemplates.findIndex((n) => { + return n === relationshipTemplate.targetElement.ref; + }) > -1 + ) { + targetRelationshipTemplates.push(relationshipTemplate.id); + } + } + + return { + source_node_templates: sourceNodeTemplates, + source_relationship_templates: sourceRelationshipTemplates, + target_node_templates: targetNodeTemplates, + target_relationship_templates: targetRelationshipTemplates + }; + } + + private async setDeployedTopologyTemplate(csarId: string) { + const topologyTemplate = await this.getTopologyTemplate(csarId).toPromise(); + this.ngRedux.dispatch(this.liveModelingActions.setDeployedJsonTopology(topologyTemplate)); + } + + private getTopologyTemplate(csarId: string): Observable { + const csarEnding = '.csar'; + const csarIdWithoutCsarSuffix = csarId.endsWith(csarEnding) ? csarId.slice(0, -csarEnding.length) : csarId; + return this.backendService.requestTopologyTemplate(csarIdWithoutCsarSuffix); + } +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/services/logging.service.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/services/logging.service.ts new file mode 100644 index 0000000000..a5ef612902 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/services/logging.service.ts @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ +import { Injectable } from '@angular/core'; +import { ReplaySubject } from 'rxjs'; +import { LiveModelingLog } from '../models/liveModelingLog'; +import { LiveModelingLogTypes } from '../models/enums'; + +@Injectable() +export class LoggingService { + private logsSubject = new ReplaySubject(); + private logs: LiveModelingLog[]; + + constructor() { + this.logs = new Array(); + } + + get logStream() { + return this.logsSubject.asObservable(); + } + + public clearLogs() { + this.logs = new Array(); + this.logsSubject.next(this.logs); + } + + public logInfo(message: string) { + this.logs = [...this.logs, new LiveModelingLog(message, LiveModelingLogTypes.INFO)]; + this.logsSubject.next(this.logs); + } + + public logWarning(message: string) { + this.logs = [...this.logs, new LiveModelingLog(message, LiveModelingLogTypes.WARNING)]; + this.logsSubject.next(this.logs); + } + + public logError(message: string) { + this.logs = [...this.logs, new LiveModelingLog(message, LiveModelingLogTypes.DANGER)]; + this.logsSubject.next(this.logs); + } + + public logSuccess(message: string) { + this.logs = [...this.logs, new LiveModelingLog(message, LiveModelingLogTypes.SUCCESS)]; + this.logsSubject.next(this.logs); + } + + public logContainer(message: string) { + this.logs = [...this.logs, new LiveModelingLog(message, LiveModelingLogTypes.CONTAINER)]; + this.logsSubject.next(this.logs); + } +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/services/overlay.service.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/services/overlay.service.ts new file mode 100644 index 0000000000..c460571183 --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/services/overlay.service.ts @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +import { Injectable } from '@angular/core'; +import { OverlayComponent } from '../overlay/overlay.component'; + +@Injectable() +export class OverlayService { + private overlay: OverlayComponent; + + constructor() { + } + + public initOverlayService(overlay: OverlayComponent) { + this.overlay = overlay; + } + + public showOverlay(content: string) { + this.overlay.content = content; + this.overlay.visible = true; + } + + public hideOverlay() { + this.overlay.visible = false; + } +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/services/property-validator.service.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/services/property-validator.service.ts new file mode 100644 index 0000000000..0732e12dad --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/services/property-validator.service.ts @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ +import { NgRedux } from '@angular-redux/store'; +import { IWineryState } from '../redux/store/winery.store'; +import { BsModalService } from 'ngx-bootstrap'; +import { Injectable } from '@angular/core'; + +@Injectable() +export class PropertyValidatorService { + private validationEnabled: boolean; + private nodeTemplates: any[]; + + constructor(private ngRedux: NgRedux, + private modalService: BsModalService) { + this.ngRedux.select((state) => { + return state.topologyRendererState.buttonsState.checkNodePropertiesButton; + }) + .subscribe((checked) => { + this.validationEnabled = checked; + }); + this.ngRedux.select((state) => { + return state.wineryState.currentJsonTopology.nodeTemplates; + }) + .subscribe((nodeTemplates) => { + this.nodeTemplates = nodeTemplates; + }); + } + + public isTopologyInvalid(): boolean { + if (this.validationEnabled) { + return this.nodeTemplates.some((node) => { + return !node.valid; + }); + } else { + return false; + } + } +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/services/topology.service.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/services/topology.service.ts new file mode 100644 index 0000000000..b7f7aa95fd --- /dev/null +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/services/topology.service.ts @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +import { Injectable } from '@angular/core'; +import { NgRedux } from '@angular-redux/store'; +import { IWineryState } from '../redux/store/winery.store'; +import { TopologyTemplateUtil } from '../models/topologyTemplateUtil'; +import { TTopologyTemplate } from '../models/ttopology-template'; +import { WineryActions } from '../redux/actions/winery.actions'; +import { LiveModelingStates } from '../models/enums'; +import { LiveModelingActions } from '../redux/actions/live-modeling.actions'; +import { BackendService } from './backend.service'; + +@Injectable() +export class TopologyService { + private currentJsonTopologyTemplate: TTopologyTemplate; + private lastSavedJsonTopologyTemplate: TTopologyTemplate; + private deployedJsonTopologyTemplate: TTopologyTemplate; + private enabled = false; + private liveModelingState: LiveModelingStates; + + constructor(private ngRedux: NgRedux, + private wineryActions: WineryActions, + private liveModelingActions: LiveModelingActions, + private backendService: BackendService) { + this.ngRedux.select((state) => { + return state.wineryState.currentJsonTopology; + }) + .subscribe((topologyTemplate) => { + this.currentJsonTopologyTemplate = topologyTemplate; + this.checkForSaveChanges(); + }); + this.ngRedux.select((state) => { + return state.wineryState.lastSavedJsonTopology; + }) + .subscribe((topologyTemplate) => { + this.lastSavedJsonTopologyTemplate = topologyTemplate; + }); + this.ngRedux.select((state) => { + return state.liveModelingState.deployedJsonTopology; + }) + .subscribe((topologyTemplate) => { + this.deployedJsonTopologyTemplate = topologyTemplate; + this.checkForDeployChanges(); + }); + this.ngRedux.select((state) => { + return state.liveModelingState.state; + }) + .subscribe((state) => { + this.liveModelingState = state; + }); + } + + public enableCheck() { + this.enabled = true; + } + + public checkForSaveChanges() { + if (!this.enabled) { + return; + } + const changed = TopologyTemplateUtil.hasTopologyTemplateChanged(this.currentJsonTopologyTemplate, this.lastSavedJsonTopologyTemplate); + this.ngRedux.dispatch(this.wineryActions.setUnsavedChanges(changed)); + } + + public checkForDeployChanges() { + if (this.liveModelingState === LiveModelingStates.DISABLED || this.liveModelingState == null) { + return; + } + this.backendService.requestTopologyTemplate().subscribe((resp) => { + const changed = TopologyTemplateUtil.hasTopologyTemplateChanged(resp, this.deployedJsonTopologyTemplate); + this.ngRedux.dispatch(this.liveModelingActions.setDeploymentChanges(changed)); + }); + } +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/sidebars/node-details/node-details-sidebar.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/sidebars/node-details/node-details-sidebar.ts index 680aec3b82..f5a8deba09 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/sidebars/node-details/node-details-sidebar.ts +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/sidebars/node-details/node-details-sidebar.ts @@ -11,12 +11,13 @@ * * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 *******************************************************************************/ -import { TRelationshipTemplate } from '../../models/ttopology-template'; +import { EntityType, TRelationshipTemplate } from '../../models/ttopology-template'; export class DetailsSidebarState { constructor( public visible: boolean, public nodeClicked?: boolean, + public entityType?: EntityType, public template?: SidebarEntityTemplate, public relationshipTemplate?: TRelationshipTemplate, public minInstances?: number, diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/sidebars/node-details/nodeDetailsSidebar.component.html b/org.eclipse.winery.frontends/app/topologymodeler/src/app/sidebars/node-details/nodeDetailsSidebar.component.html index ddf4c6f1ff..8664dde245 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/sidebars/node-details/nodeDetailsSidebar.component.html +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/sidebars/node-details/nodeDetailsSidebar.component.html @@ -13,56 +13,55 @@ -->
diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/sidebars/node-details/nodeDetailsSidebar.component.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/sidebars/node-details/nodeDetailsSidebar.component.ts index 1f43cee01d..4fe30adc7c 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/sidebars/node-details/nodeDetailsSidebar.component.ts +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/sidebars/node-details/nodeDetailsSidebar.component.ts @@ -24,14 +24,30 @@ import { BackendService } from '../../services/backend.service'; import { PolicyService } from '../../services/policy.service'; import { DetailsSidebarState } from './node-details-sidebar'; import { QName } from '../../../../../shared/src/app/model/qName'; +import { animate, state, style, transition, trigger } from '@angular/animations'; /** * This is the right sidebar, where attributes of nodes and relationships get displayed. */ @Component({ selector: 'winery-node-details-sidebar', - templateUrl: './nodeDetailsSidebar.component.html', + templateUrl: 'nodeDetailsSidebar.component.html', styleUrls: ['../sidebar.css'], + animations: [ + trigger('sidebarAnimationStatus', [ + state('in', style({ transform: 'translateX(0)' })), + transition('void => *', [ + style({ transform: 'translateX(-100%)' }), + animate('100ms cubic-bezier(0.86, 0, 0.07, 1)') + ]), + transition('* => void', [ + animate('200ms cubic-bezier(0.86, 0, 0.07, 1)', style({ + opacity: 0, + transform: 'translateX(-100%)' + })) + ]) + ]) + ] }) export class NodeDetailsSidebarComponent implements OnInit, OnDestroy { // ngRedux sidebarSubscription @@ -39,6 +55,7 @@ export class NodeDetailsSidebarComponent implements OnInit, OnDestroy { sidebarState: DetailsSidebarState; maxInputEnabled = true; + @Input() top: number; @Input() readonly: boolean; @Output() sidebarDeleteButtonClicked: EventEmitter = new EventEmitter(); @@ -46,6 +63,7 @@ export class NodeDetailsSidebarComponent implements OnInit, OnDestroy { public nodeMinInstancesKeyUp: Subject = new Subject(); public nodeMaxInstancesKeyUp: Subject = new Subject(); subscription: Subscription; + sidebarAnimationStatus: any; constructor(private $ngRedux: NgRedux, private actions: WineryActions, @@ -64,8 +82,7 @@ export class NodeDetailsSidebarComponent implements OnInit, OnDestroy { * Closes the sidebar. */ closeSidebar() { - this.$ngRedux.dispatch(this.actions.triggerSidebar( - { sidebarContents: new DetailsSidebarState(false) })); + this.$ngRedux.dispatch(this.actions.triggerSidebar(new DetailsSidebarState(false))); } /** @@ -92,7 +109,7 @@ export class NodeDetailsSidebarComponent implements OnInit, OnDestroy { // apply changes to the node name field with a debounceTime of 300ms this.subscription = this.nodeNameKeyUp.pipe( debounceTime(300), - distinctUntilChanged(), ) + distinctUntilChanged()) .subscribe(data => { if (this.sidebarState.nodeClicked) { this.$ngRedux.dispatch(this.actions.changeNodeName({ @@ -114,7 +131,6 @@ export class NodeDetailsSidebarComponent implements OnInit, OnDestroy { } // refresh this.$ngRedux.dispatch(this.actions.triggerSidebar({ - sidebarContents: { visible: true, nodeClicked: this.sidebarState.nodeClicked, template: { @@ -128,14 +144,13 @@ export class NodeDetailsSidebarComponent implements OnInit, OnDestroy { maxInstances: Number(this.sidebarState.maxInstances), source: this.sidebarState.source, target: this.sidebarState.target - } - })); + })); }); // minInstances - const nodeMinInstancesKeyUpObservable = this.nodeMinInstancesKeyUp.pipe( + this.nodeMinInstancesKeyUp.pipe( debounceTime(300), - distinctUntilChanged(), ) + distinctUntilChanged()) .subscribe(data => { if (this.sidebarState.nodeClicked) { this.$ngRedux.dispatch(this.actions.changeMinInstances({ @@ -147,7 +162,6 @@ export class NodeDetailsSidebarComponent implements OnInit, OnDestroy { } // refresh this.$ngRedux.dispatch(this.actions.triggerSidebar({ - sidebarContents: { visible: true, nodeClicked: this.sidebarState.nodeClicked, template: this.sidebarState.template, @@ -156,13 +170,12 @@ export class NodeDetailsSidebarComponent implements OnInit, OnDestroy { maxInstances: this.sidebarState.maxInstances, source: this.sidebarState.source, target: this.sidebarState.target - } })); }); // maxInstances - const nodeMaxInstancesKeyUpObservable = this.nodeMaxInstancesKeyUp.pipe( + this.nodeMaxInstancesKeyUp.pipe( debounceTime(300), - distinctUntilChanged(), ) + distinctUntilChanged()) .subscribe(data => { if (this.sidebarState.nodeClicked) { this.$ngRedux.dispatch(this.actions.changeMaxInstances({ @@ -174,7 +187,6 @@ export class NodeDetailsSidebarComponent implements OnInit, OnDestroy { } // refresh this.$ngRedux.dispatch(this.actions.triggerSidebar({ - sidebarContents: { visible: true, nodeClicked: this.sidebarState.nodeClicked, template: this.sidebarState.template, @@ -183,7 +195,6 @@ export class NodeDetailsSidebarComponent implements OnInit, OnDestroy { maxInstances: Number(data), source: this.sidebarState.source, target: this.sidebarState.target - } })); }); } @@ -216,7 +227,6 @@ export class NodeDetailsSidebarComponent implements OnInit, OnDestroy { } // refresh this.$ngRedux.dispatch(this.actions.triggerSidebar({ - sidebarContents: { visible: true, nodeClicked: this.sidebarState.nodeClicked, template: this.sidebarState.template, @@ -225,7 +235,6 @@ export class NodeDetailsSidebarComponent implements OnInit, OnDestroy { maxInstances: this.sidebarState.maxInstances, source: this.sidebarState.source, target: this.sidebarState.target - } })); } @@ -276,7 +285,6 @@ export class NodeDetailsSidebarComponent implements OnInit, OnDestroy { } // refresh this.$ngRedux.dispatch(this.actions.triggerSidebar({ - sidebarContents: { visible: true, nodeClicked: this.sidebarState.nodeClicked, template: this.sidebarState.template, @@ -285,7 +293,6 @@ export class NodeDetailsSidebarComponent implements OnInit, OnDestroy { maxInstances: this.sidebarState.maxInstances, source: this.sidebarState.source, target: this.sidebarState.target - } })); } diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/sidebars/sidebar.css b/org.eclipse.winery.frontends/app/topologymodeler/src/app/sidebars/sidebar.css index 9967e503e6..17336dc56c 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/sidebars/sidebar.css +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/sidebars/sidebar.css @@ -37,3 +37,8 @@ input::-webkit-inner-spin-button { a { overflow-y: auto; } + +a:hover { + background-color: #303030; + color: #03a9f4; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/topology-renderer/topology-renderer.component.css b/org.eclipse.winery.frontends/app/topologymodeler/src/app/topology-renderer/topology-renderer.component.css index 3e60216a18..87bbdde2f6 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/topology-renderer/topology-renderer.component.css +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/topology-renderer/topology-renderer.component.css @@ -12,14 +12,8 @@ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 ********************************************************************************/ -winery-navbar { - position: fixed; - top: 0px; - width: 100%; - z-index: 700; -} - winery-canvas { + position: absolute; + height: 100%; width: 100%; - padding-top: 47px; } diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/topology-renderer/topology-renderer.module.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/topology-renderer/topology-renderer.module.ts index f74d8df7d6..872e7138ec 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/topology-renderer/topology-renderer.module.ts +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/topology-renderer/topology-renderer.module.ts @@ -19,7 +19,6 @@ import { HttpClientModule } from '@angular/common/http'; import { RouterModule } from '@angular/router'; import { BsDropdownModule } from 'ngx-bootstrap/dropdown'; import { AccordionModule } from 'ngx-bootstrap/accordion'; -import { NavbarComponent } from '../navbar/navbar.component'; import { NodeComponent } from '../node/node.component'; import { CanvasComponent } from '../canvas/canvas.component'; import { LayoutDirective } from '../layout/layout.directive'; @@ -48,6 +47,7 @@ import { Ng2TableModule } from 'ng2-table'; import { GroupsComponent } from '../node/groups/groups.component'; import { AssignParticipantsComponent } from '../participants/assign-participants.component'; import { AssignDeploymentTechnologyComponent } from '../edmm/assign-deployment-technology.component'; +import { NavbarModule } from '../navbar/navbar.module'; @NgModule({ imports: [ @@ -68,9 +68,9 @@ import { AssignDeploymentTechnologyComponent } from '../edmm/assign-deployment-t WineryFeatureToggleModule, WineryTableModule, Ng2TableModule, + NavbarModule ], declarations: [ - NavbarComponent, NodeComponent, CanvasComponent, LayoutDirective, diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/winery.component.css b/org.eclipse.winery.frontends/app/topologymodeler/src/app/winery.component.css index 8b8e88cbc2..c96b0c65c0 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/winery.component.css +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/winery.component.css @@ -22,3 +22,21 @@ left: 40px; width: 450px; } + +winery-navbar { + position: fixed; + top: 0; + z-index: 700; + width: 100vw; +} + +winery-topology-renderer { + position: absolute; + width: 100%; + height: 100%; +} + +.sidebar { + z-index: 400; + width: 300px; +} diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/winery.component.html b/org.eclipse.winery.frontends/app/topologymodeler/src/app/winery.component.html index 8f0efc59ff..ce984779ee 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/winery.component.html +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/winery.component.html @@ -38,16 +38,25 @@ - - + + + + + + +
@@ -55,6 +64,21 @@ style="width:300px;float:left;margin-top:50px;" [entityTypes]="this.entityTypes"> + +
+ + +
+ +
diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/winery.component.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/winery.component.ts index 4fa8b3adaf..0419e368fa 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/winery.component.ts +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/winery.component.ts @@ -29,10 +29,17 @@ import { ToastrService } from 'ngx-toastr'; import { ResearchPlugin, TopologyRendererState } from './redux/reducers/topologyRenderer.reducer'; import { VersionElement } from './models/versionElement'; import { TopologyRendererActions } from './redux/actions/topologyRenderer.actions'; -import { WineryRepositoryConfigurationService } from '../../../tosca-management/src/app/wineryFeatureToggleModule/WineryRepositoryConfiguration.service'; +import { + WineryRepositoryConfigurationService +} from '../../../tosca-management/src/app/wineryFeatureToggleModule/WineryRepositoryConfiguration.service'; import { WineryActions } from './redux/actions/winery.actions'; import { DetailsSidebarState } from './sidebars/node-details/node-details-sidebar'; import { SubMenuItems } from '../../../tosca-management/src/app/model/subMenuItem'; +import { ResizedEvent } from 'angular-resize-event'; +import { + FeatureEnum +} from '../../../tosca-management/src/app/wineryFeatureToggleModule/wineryRepository.feature.direct'; +import { TopologyService } from './services/topology.service'; /** * This is the root component of the topology modeler. @@ -69,6 +76,9 @@ export class WineryComponent implements OnInit, AfterViewInit { showVersionSlider: boolean; + navbarHeight = 0; + configEnum = FeatureEnum; + public loaded: ILoaded; private loadedRelationshipVisuals = 0; @@ -76,11 +86,13 @@ export class WineryComponent implements OnInit, AfterViewInit { private appReadyEvent: AppReadyEventService, public backendService: BackendService, private ngRedux: NgRedux, + private wineryActions: WineryActions, private actions: TopologyRendererActions, private uiActions: WineryActions, private alert: ToastrService, private activatedRoute: ActivatedRoute, - private configurationService: WineryRepositoryConfigurationService) { + private configurationService: WineryRepositoryConfigurationService, + private topologyService: TopologyService) { this.subscriptions.push(this.ngRedux.select(state => state.wineryState.hideNavBarAndPaletteState) .subscribe(hideNavBar => this.hideNavBarState = hideNavBar)); this.subscriptions.push(this.ngRedux.select(state => state.topologyRendererState) @@ -157,8 +169,7 @@ export class WineryComponent implements OnInit, AfterViewInit { notifyClose(key: string): void { // FIXME this currently basically only supports the node-details sidebar // because none of the other sidebars are based off ng-sidebar - this.ngRedux.dispatch(this.uiActions.triggerSidebar( - { sidebarContents: new DetailsSidebarState(false) })); + this.ngRedux.dispatch(this.uiActions.triggerSidebar( new DetailsSidebarState(false))); } initTopologyTemplateForRendering(nodeTemplateArray: Array, relationshipTemplateArray: Array) { @@ -198,12 +209,26 @@ export class WineryComponent implements OnInit, AfterViewInit { onReduxReady() { this.loaded.generatedReduxState = true; + this.ngRedux.dispatch(this.wineryActions.setLastSavedJsonTopology(this.ngRedux.getState().wineryState.currentJsonTopology)); + this.topologyService.enableCheck(); + window.addEventListener('beforeunload', ((e) => { + if (this.ngRedux.getState().wineryState.unsavedChanges) { + e.preventDefault(); + e.returnValue = ''; + } else { + delete e['returnValue']; + } + })); } sidebarDeleteButtonClicked($event) { this.sidebarDeleteButtonClickEvent = $event; } + onNavbarResized(event: ResizedEvent) { + this.navbarHeight = event.newHeight; + } + private configure(params: TopologyModelerConfiguration) { this.backendService.configure(params); this.templateParameter = params; diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/app/winery.module.ts b/org.eclipse.winery.frontends/app/topologymodeler/src/app/winery.module.ts index c7f1af256c..6371925d2a 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/app/winery.module.ts +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/app/winery.module.ts @@ -21,7 +21,6 @@ import { BsDropdownModule } from 'ngx-bootstrap/dropdown'; import { AccordionModule } from 'ngx-bootstrap/accordion'; import { JsPlumbService } from './services/jsPlumb.service'; import { WineryComponent } from './winery.component'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { ToastrModule } from 'ngx-toastr'; import { PaletteComponent } from './palette/palette.component'; import { TopologyRendererModule } from './topology-renderer/topology-renderer.module'; @@ -49,8 +48,14 @@ import { EnricherComponent } from './enricher/enricher.component'; import { WineryFeatureToggleModule } from '../../../tosca-management/src/app/wineryFeatureToggleModule/winery-feature-toggle.module'; import { PlaceComponentsService } from './services/placement.service'; import { MultiParticipantsComponent } from './multi-participants/multi-participants.component'; +import { LiveModelingService } from './services/live-modeling.service'; +import { ContainerService } from './services/container.service'; +import { ModalModule, TooltipModule } from 'ngx-bootstrap'; import { ReqCapRelationshipService } from './services/req-cap-relationship.service'; import { WineryTableModule } from '../../../tosca-management/src/app/wineryTableModule/wineryTable.module'; +import { LiveModelingActions } from './redux/actions/live-modeling.actions'; +import { AngularResizedEventModule } from 'angular-resize-event'; +import { OverlayComponent } from './overlay/overlay.component'; import { EdmmTransformationCheckComponent } from './edmmTransformationCheck/edmmTransformationCheck.component'; import { EdmmReplacementRulesComponent } from './edmmTransformationCheck/edmm-replacement-rules/edmm-replacement-rules.component'; import { ManageTopologyService } from './services/manage-topology.service'; @@ -72,6 +77,13 @@ import { ManageParticipantsComponent } from './participants/manage-participants. import { ResearchPluginsComponent } from './sidebars/research-plugins/research-plugins.component'; import { InstanceModelComponent } from './sidebars/instanceModel/instanceModel.component'; import { WineryNamespaceSelectorService } from '../../../tosca-management/src/app/wineryNamespaceSelector/wineryNamespaceSelector.service'; +import { PropertyValidatorService } from './services/property-validator.service'; +import { OverlayService } from './services/overlay.service'; +import { TopologyService } from './services/topology.service'; +import { LoggingService } from './services/logging.service'; +import { LiveModelingSidebarModule } from './live-modeling/live-modeling-sidebar.module'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { NavbarModule } from './navbar/navbar.module'; @NgModule({ declarations: [ @@ -82,6 +94,7 @@ import { WineryNamespaceSelectorService } from '../../../tosca-management/src/ap RefinementSidebarComponent, ProblemDetectionComponent, EnricherComponent, + OverlayComponent, MultiParticipantsComponent, EdmmTransformationCheckComponent, VersionSliderComponent, @@ -124,13 +137,19 @@ import { WineryNamespaceSelectorService } from '../../../tosca-management/src/ap WineryDuplicateValidatorModule, CollapseModule, WineryTableModule, - NgxSliderModule + NgxSliderModule, + TooltipModule.forRoot(), + AngularResizedEventModule, + ModalModule.forRoot(), + LiveModelingSidebarModule, + NavbarModule ], providers: [ // { provide: ToastOptions, useClass: WineryCustomOption }, JsPlumbService, WineryActions, TopologyRendererActions, + LiveModelingActions, LoadedService, AppReadyEventService, BackendService, @@ -147,7 +166,15 @@ import { WineryNamespaceSelectorService } from '../../../tosca-management/src/ap PolicyService, VersionSliderService, MultiParticipantsService, - WineryNamespaceSelectorService + WineryNamespaceSelectorService, + PolicyService, + ContainerService, + PropertyValidatorService, + LiveModelingService, + ReqCapRelationshipService, + OverlayService, + TopologyService, + LoggingService ], bootstrap: [WineryComponent] }) diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/assets/styles/styles.scss b/org.eclipse.winery.frontends/app/topologymodeler/src/assets/styles/styles.scss index 0b8e4d5801..d2abbcbac0 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/assets/styles/styles.scss +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/assets/styles/styles.scss @@ -17,12 +17,18 @@ body { overflow-x: hidden; - overflow-y: scroll; + overflow-y: auto; /*background-color: #303030;*/ + background-size: 5px 5px; + background-image: + linear-gradient(to right, rgba(238, 238, 238, 0.5) 1px, transparent 1px), + linear-gradient(to bottom, rgba(238, 238, 238, 0.5) 1px, transparent 1px); } -::-webkit-scrollbar { - display: none; +#pre-bootstrap-container { + height: 100vh; + width: 100vw; + background-color: white; } .modal { diff --git a/org.eclipse.winery.frontends/app/topologymodeler/src/index.html b/org.eclipse.winery.frontends/app/topologymodeler/src/index.html index 370398ead6..8c736fac37 100644 --- a/org.eclipse.winery.frontends/app/topologymodeler/src/index.html +++ b/org.eclipse.winery.frontends/app/topologymodeler/src/index.html @@ -25,9 +25,15 @@ - - - +
diff --git a/org.eclipse.winery.frontends/app/tosca-management/src/app/instance/admin/configuration/configuration.component.html b/org.eclipse.winery.frontends/app/tosca-management/src/app/instance/admin/configuration/configuration.component.html index fb60b6e7ca..5d04940f3f 100644 --- a/org.eclipse.winery.frontends/app/tosca-management/src/app/instance/admin/configuration/configuration.component.html +++ b/org.eclipse.winery.frontends/app/tosca-management/src/app/instance/admin/configuration/configuration.component.html @@ -87,6 +87,14 @@

Configure the visibility of the following features:

[checked]="config.features.instanceModelRefinement">
+ + +
+ + +
diff --git a/org.eclipse.winery.frontends/app/tosca-management/src/app/instance/sharedComponents/propertiesDefinition/propertiesDefinition.component.ts b/org.eclipse.winery.frontends/app/tosca-management/src/app/instance/sharedComponents/propertiesDefinition/propertiesDefinition.component.ts index bf79b23308..2b4062ad18 100644 --- a/org.eclipse.winery.frontends/app/tosca-management/src/app/instance/sharedComponents/propertiesDefinition/propertiesDefinition.component.ts +++ b/org.eclipse.winery.frontends/app/tosca-management/src/app/instance/sharedComponents/propertiesDefinition/propertiesDefinition.component.ts @@ -146,13 +146,18 @@ export class PropertiesDefinitionComponent implements OnInit { 'Description', 3 ), + new DynamicTextData( + 'pattern', + 'Pattern', + 4 + ), new DynamicConstraintsData( 'constraints', 'Constraints', valid_constraint_keys, list_constraint_keys, range_constraint_keys, - 4, + 5, ) ]; if (!this.configurationService.configuration.features.yaml) { diff --git a/org.eclipse.winery.frontends/app/tosca-management/src/app/instance/sharedComponents/propertiesDefinition/propertiesDefinitionsResourceApiData.ts b/org.eclipse.winery.frontends/app/tosca-management/src/app/instance/sharedComponents/propertiesDefinition/propertiesDefinitionsResourceApiData.ts index d2314a0a52..25e560fdf6 100644 --- a/org.eclipse.winery.frontends/app/tosca-management/src/app/instance/sharedComponents/propertiesDefinition/propertiesDefinitionsResourceApiData.ts +++ b/org.eclipse.winery.frontends/app/tosca-management/src/app/instance/sharedComponents/propertiesDefinition/propertiesDefinitionsResourceApiData.ts @@ -29,6 +29,7 @@ export class PropertiesDefinitionKVElement { defaultValue: string; description: string; constraints: Constraint[] = []; + pattern: string = null; } export class PropertiesDefinition { diff --git a/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryFeatureToggleModule/WineryRepositoryConfiguration.service.ts b/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryFeatureToggleModule/WineryRepositoryConfiguration.service.ts index 9827984c2e..ae69e4d7ad 100644 --- a/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryFeatureToggleModule/WineryRepositoryConfiguration.service.ts +++ b/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryFeatureToggleModule/WineryRepositoryConfiguration.service.ts @@ -35,6 +35,8 @@ export interface WineryConfiguration { placement: boolean; edmmModeling: boolean; updateTemplates: boolean; + liveModeling: boolean; + propertyCheck: boolean; yaml: boolean; patternDetection: boolean; }; diff --git a/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryFeatureToggleModule/wineryRepository.feature.direct.ts b/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryFeatureToggleModule/wineryRepository.feature.direct.ts index 71b6aa774d..cc1756001a 100644 --- a/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryFeatureToggleModule/wineryRepository.feature.direct.ts +++ b/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryFeatureToggleModule/wineryRepository.feature.direct.ts @@ -21,7 +21,7 @@ export enum FeatureEnum { ProblemDetection = 'problemDetection', Radon = 'radon', Splitting = 'splitting', MultiParticipant = 'multiParticipant', TestRefinement = 'testRefinement', TopologyFragmentRefinementModel = 'topologyFragmentRefinementModel', Placement = 'placement', updateTemplates = 'updateTemplates', Yaml = 'yaml', - PatternDetection = 'patternDetection' + PatternDetection = 'patternDetection', LiveModeling = 'liveModeling', PropertyCheck = 'propertyCheck' } @Directive({ diff --git a/org.eclipse.winery.frontends/package.json b/org.eclipse.winery.frontends/package.json index 87b18c7693..57f5944bce 100644 --- a/org.eclipse.winery.frontends/package.json +++ b/org.eclipse.winery.frontends/package.json @@ -5,8 +5,8 @@ "scripts": { "ng": "ng", "start": "ng serve", - "start-tosca-management": "ng serve tosca-management", - "start-topologymodeler": "ng serve topologymodeler", + "start-tosca-management": "ng serve tosca-management --host 0.0.0.0", + "start-topologymodeler": "ng serve topologymodeler --host 0.0.0.0", "start-workflowmodeler": "ng serve workflowmodeler", "build-tosca-management": "ng build tosca-management", "build-topologymodeler": "ng build topologymodeler", @@ -37,6 +37,8 @@ "@angular/platform-browser-dynamic": "7.2.7", "@angular/router": "7.2.7", "angular-in-memory-web-api": "0.6.1", + "angular-resizable-element": "3.2.6", + "angular-resize-event": "1.1.1", "angular2-hotkeys": "2.1.3", "angular2-markdown": "1.6.0", "angular2-uuid": "1.1.1", @@ -46,7 +48,9 @@ "css-element-queries": "0.4.0", "elkjs": "0.4.1", "font-awesome": "4.7.0", + "is-promise": "4.0.0", "jsplumb": "2.8.0", + "lodash": "4.17.15", "ng-diff-match-patch": "3.0.1", "ng-sidebar": "9.4.2", "ng2-file-upload": "1.3.0", diff --git a/org.eclipse.winery.model/org.eclipse.winery.model.tosca.canonical/src/main/java/org/eclipse/winery/model/tosca/extensions/kvproperties/PropertyDefinitionKV.java b/org.eclipse.winery.model/org.eclipse.winery.model.tosca.canonical/src/main/java/org/eclipse/winery/model/tosca/extensions/kvproperties/PropertyDefinitionKV.java index bc59d98ffe..5e1afff562 100644 --- a/org.eclipse.winery.model/org.eclipse.winery.model.tosca.canonical/src/main/java/org/eclipse/winery/model/tosca/extensions/kvproperties/PropertyDefinitionKV.java +++ b/org.eclipse.winery.model/org.eclipse.winery.model.tosca.canonical/src/main/java/org/eclipse/winery/model/tosca/extensions/kvproperties/PropertyDefinitionKV.java @@ -33,6 +33,7 @@ public class PropertyDefinitionKV implements Serializable { @JsonProperty("constraint") private List constraintList; + private String pattern; @Deprecated // used for XML deserialization of API request content public PropertyDefinitionKV() { @@ -83,6 +84,14 @@ public void setType(String type) { this.type = type; } + public String getPattern() { + return this.pattern; + } + + public void setPattern(String pattern) { + this.pattern = pattern; + } + public Boolean isRequired() { return required; } diff --git a/org.eclipse.winery.repository.rest/src/main/java/org/eclipse/winery/repository/rest/resources/servicetemplates/ServiceTemplateResource.java b/org.eclipse.winery.repository.rest/src/main/java/org/eclipse/winery/repository/rest/resources/servicetemplates/ServiceTemplateResource.java index f4218b7c10..e1135020a2 100644 --- a/org.eclipse.winery.repository.rest/src/main/java/org/eclipse/winery/repository/rest/resources/servicetemplates/ServiceTemplateResource.java +++ b/org.eclipse.winery.repository.rest/src/main/java/org/eclipse/winery/repository/rest/resources/servicetemplates/ServiceTemplateResource.java @@ -77,6 +77,7 @@ import org.eclipse.winery.model.tosca.extensions.kvproperties.PropertyDefinitionKV; import org.eclipse.winery.model.tosca.extensions.kvproperties.WinerysPropertiesDefinition; import org.eclipse.winery.model.tosca.utils.ModelUtilities; +import org.eclipse.winery.model.version.VersionSupport; import org.eclipse.winery.repository.backend.BackendUtils; import org.eclipse.winery.repository.backend.IRepository; import org.eclipse.winery.repository.backend.NamespaceManager; @@ -808,6 +809,38 @@ public Response createNewStatefulVersion() { return response.getResponse(); } + @POST() + @Path("createlivemodelingversion") + @Produces( {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) + public Response createLiveModelingVersion() { + LOGGER.debug("Creating live modeling version of Service Template {}...", this.getId().getQName()); + ServiceTemplateId id = (ServiceTemplateId) this.getId(); + WineryVersion version = VersionUtils.getVersion(id.getQName().toString()); + + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss"); + WineryVersion newVersion = new WineryVersion( + version.toString() + "-live-" + dateFormat.format(new Date()), + 1, + 0 + ); + + String newComponentVersionId = VersionSupport.getNewComponentVersionId(id, "live-" + dateFormat.format(new Date())); + + ServiceTemplateId newId = new ServiceTemplateId(id.getNamespace().getDecoded(), + newComponentVersionId, + false); + ResourceResult response = RestUtils.duplicate(id, newId); + + if (response.getStatus() == Status.CREATED) { + response.setUri(null); + response.setMessage(new QNameApiData(newId)); + } + + LOGGER.debug("Created Service Template {}", newId.getQName()); + + return response.getResponse(); + } + @Path("threatmodeling") @Produces(MediaType.APPLICATION_JSON) @GET diff --git a/org.eclipse.winery.repository/src/main/java/org/eclipse/winery/repository/backend/filebased/GitBasedRepository.java b/org.eclipse.winery.repository/src/main/java/org/eclipse/winery/repository/backend/filebased/GitBasedRepository.java index 9b1b40e23b..b9b0761279 100644 --- a/org.eclipse.winery.repository/src/main/java/org/eclipse/winery/repository/backend/filebased/GitBasedRepository.java +++ b/org.eclipse.winery.repository/src/main/java/org/eclipse/winery/repository/backend/filebased/GitBasedRepository.java @@ -451,6 +451,7 @@ public String getRepositoryUrl() { } private Git cloneRepository(String repoUrl, String branch) throws GitAPIException { + LOGGER.info("Cloning repository \"{}\" using branch \"{}\"", repoUrl, branch); return Git.cloneRepository() .setURI(repoUrl) .setDirectory(this.repository.getRepositoryRoot().toFile()) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000..48e341a095 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3 @@ +{ + "lockfileVersion": 1 +}