You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@streampipes.apache.org by ri...@apache.org on 2021/10/13 07:56:45 UTC

[incubator-streampipes] branch dev updated (17e03d0 -> d3a8840)

This is an automated email from the ASF dual-hosted git repository.

riemer pushed a change to branch dev
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git.


    from 17e03d0  Merge branch 'dev' of github.com:apache/incubator-streampipes into dev
     new e1f215d  [STREAMPIPES-444] Allow modification of asset dashboards
     new dc2ede5  [STREAMPIPES-444] Links can be placed on asset dashboards
     new 0424e27  [STREAMPIPES-444] Improve link behaviour in asset dashboard
     new d3a8840  Merge branch 'dev' of github.com:apache/incubator-streampipes into dev

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../assetdashboard/AssetDashboardConfig.java       |   7 +-
 .../client/assetdashboard/CanvasAttributes.java    |  28 ++++
 .../model/client/assetdashboard/CanvasElement.java |   7 +-
 .../streampipes/rest/impl/AssetDashboard.java      |  17 ++
 .../storage/api/IAssetDashboardStorage.java        |   2 +
 .../couchdb/impl/AssetDashboardStorageImpl.java    |   5 +
 .../app-asset-monitoring.component.html            |   4 +-
 .../app-asset-monitoring.component.ts              |  11 +-
 .../app-asset-monitoring.module.ts                 |   4 +-
 .../create-asset/create-asset.component.html       |  36 +++--
 .../create-asset/create-asset.component.ts         | 176 +++++++++++++++------
 .../components/view-asset/view-asset.component.css |   7 +-
 .../view-asset/view-asset.component.html           |   1 +
 .../components/view-asset/view-asset.component.ts  |  28 ++++
 .../add-link-dialog.component.html}                |  24 +--
 .../add-link/add-link-dialog.component.scss}       |   5 +-
 .../dialog/add-link/add-link-dialog.component.ts   |  58 +++++++
 .../save-dashboard-dialog.component.html           |   2 +-
 .../save-dashboard-dialog.component.ts             |  37 ++++-
 .../app-asset-monitoring/model/image-info.model.ts |   4 +-
 .../model/selected-visualization-data.model.ts     |   9 +-
 .../app-asset-monitoring/services/rest.service.ts  |   4 +
 .../app-asset-monitoring/services/shape.service.ts |  91 ++++++++---
 23 files changed, 450 insertions(+), 117 deletions(-)
 copy ui/src/app/app-asset-monitoring/dialog/{save-dashboard/save-dashboard-dialog.component.html => add-link/add-link-dialog.component.html} (70%)
 copy ui/src/app/{editor/dialog/pipeline-element-discovery/pipeline-element-discovery.component.scss => app-asset-monitoring/dialog/add-link/add-link-dialog.component.scss} (94%)
 create mode 100644 ui/src/app/app-asset-monitoring/dialog/add-link/add-link-dialog.component.ts

[incubator-streampipes] 04/04: Merge branch 'dev' of github.com:apache/incubator-streampipes into dev

Posted by ri...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

riemer pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git

commit d3a884032f572f3afb81f0b417478b956dfef0e6
Merge: 0424e27 17e03d0
Author: Dominik Riemer <ri...@fzi.de>
AuthorDate: Wed Oct 13 09:56:30 2021 +0200

    Merge branch 'dev' of github.com:apache/incubator-streampipes into dev

 .../container/worker/management/AdapterWorkerManagement.java  | 11 ++++++++++-
 .../manager/execution/http/HttpRequestBuilder.java            |  2 +-
 2 files changed, 11 insertions(+), 2 deletions(-)

[incubator-streampipes] 02/04: [STREAMPIPES-444] Links can be placed on asset dashboards

Posted by ri...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

riemer pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git

commit dc2ede561d9ec74bfbbe23304654b0c58121022f
Author: Dominik Riemer <ri...@fzi.de>
AuthorDate: Wed Oct 13 09:38:50 2021 +0200

    [STREAMPIPES-444] Links can be placed on asset dashboards
---
 .../client/assetdashboard/CanvasAttributes.java    | 28 +++++++++++
 .../app-asset-monitoring.module.ts                 |  4 +-
 .../create-asset/create-asset.component.html       | 36 +++++++++-----
 .../create-asset/create-asset.component.ts         | 30 ++++++++++--
 .../components/view-asset/view-asset.component.ts  | 23 +++++++++
 .../dialog/add-link/add-link-dialog.component.html | 49 +++++++++++++++++++
 .../add-link/add-link-dialog.component.scss}       | 16 ++-----
 .../dialog/add-link/add-link-dialog.component.ts   | 56 ++++++++++++++++++++++
 .../model/selected-visualization-data.model.ts     |  8 +++-
 .../app-asset-monitoring/services/shape.service.ts | 39 +++++++++++++--
 10 files changed, 256 insertions(+), 33 deletions(-)

diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/assetdashboard/CanvasAttributes.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/assetdashboard/CanvasAttributes.java
index 596d5e2..62149be 100644
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/assetdashboard/CanvasAttributes.java
+++ b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/assetdashboard/CanvasAttributes.java
@@ -38,6 +38,10 @@ public class CanvasAttributes {
   private String topic;
   private String name;
 
+  private String hyperlink;
+  private boolean newWindow;
+  private String fontStyle;
+
   public CanvasAttributes() {
   }
 
@@ -168,4 +172,28 @@ public class CanvasAttributes {
   public void setFontSize(String fontSize) {
     this.fontSize = fontSize;
   }
+
+  public String getHyperlink() {
+    return hyperlink;
+  }
+
+  public void setHyperlink(String hyperlink) {
+    this.hyperlink = hyperlink;
+  }
+
+  public boolean isNewWindow() {
+    return newWindow;
+  }
+
+  public void setNewWindow(boolean newWindow) {
+    this.newWindow = newWindow;
+  }
+
+  public String getFontStyle() {
+    return fontStyle;
+  }
+
+  public void setFontStyle(String fontStyle) {
+    this.fontStyle = fontStyle;
+  }
 }
diff --git a/ui/src/app/app-asset-monitoring/app-asset-monitoring.module.ts b/ui/src/app/app-asset-monitoring/app-asset-monitoring.module.ts
index a1260a8..8c4f020 100644
--- a/ui/src/app/app-asset-monitoring/app-asset-monitoring.module.ts
+++ b/ui/src/app/app-asset-monitoring/app-asset-monitoring.module.ts
@@ -40,6 +40,7 @@ import {SaveDashboardDialogComponent} from "./dialog/save-dashboard/save-dashboa
 import {AssetDashboardOverviewComponent} from "./components/dashboard-overview/dashboard-overview.component";
 import {InjectableRxStompConfig, RxStompService, rxStompServiceFactory} from "@stomp/ng2-stompjs";
 import {streamPipesStompConfig} from "../dashboard/services/websocket.config";
+import { AddLinkDialogComponent } from './dialog/add-link/add-link-dialog.component';
 
 @NgModule({
     imports: [
@@ -56,6 +57,7 @@ import {streamPipesStompConfig} from "../dashboard/services/websocket.config";
         AppAssetMonitoringComponent,
         CreateAssetComponent,
         ViewAssetComponent,
+        AddLinkDialogComponent,
         AddPipelineDialogComponent,
         SaveDashboardDialogComponent,
         AssetDashboardOverviewComponent
@@ -85,4 +87,4 @@ import {streamPipesStompConfig} from "../dashboard/services/websocket.config";
     ]
 })
 export class AppAssetMonitoringModule {
-}
\ No newline at end of file
+}
diff --git a/ui/src/app/app-asset-monitoring/components/create-asset/create-asset.component.html b/ui/src/app/app-asset-monitoring/components/create-asset/create-asset.component.html
index 8e81443..1e46811 100644
--- a/ui/src/app/app-asset-monitoring/components/create-asset/create-asset.component.html
+++ b/ui/src/app/app-asset-monitoring/components/create-asset/create-asset.component.html
@@ -16,19 +16,32 @@
   ~
   -->
 
-<div fxLayout="column" fxFlex="100" >
+<div fxLayout="column" fxFlex="100">
     <input type="file" (change)="handleFileInput($event)" accept="image/*" #file style="display:none;">
     <div class="sp-tab-bg sp-blue-bg page-container-nav">
         <div fxLayout="row" fxFlex="100" fxLayoutAlign="start center">
-            <button color="accent" mat-button mat-icon-button (click)="prepareDashboard()" type="submit" [disabled]="!backgroundImagePresent || !measurementPresent">
-                <i class="material-icons">save</i>
-            </button>
-            <button color="accent" mat-button mat-icon-button (click)="file.click()" type="submit" [disabled]="backgroundImagePresent">
-                <i class="material-icons">file_upload</i>
-            </button>
-            <button color="accent" mat-button mat-icon-button (click)="openAddPipelineDialog()" >
-                <i class="material-icons">add</i>
-            </button>
+            <div>
+                <button color="accent" class="mat-basic" mat-button (click)="prepareDashboard()" type="submit"
+                        [disabled]="!backgroundImagePresent || !measurementPresent">
+                    <mat-icon>save</mat-icon>&nbsp;Save
+                </button>
+            </div>
+            <div>
+                <button color="accent" mat-button (click)="file.click()" type="submit"
+                        [disabled]="backgroundImagePresent">
+                    <i class="material-icons">file_upload</i>&nbsp;Add image
+                </button>
+            </div>
+            <div>
+                <button color="accent" mat-button (click)="openAddPipelineDialog()">
+                    <i class="material-icons">add</i>&nbsp;Add value
+                </button>
+            </div>
+            <div>
+                <button color="accent" mat-button (click)="openAddLinkDialog()">
+                    <span fxLayoutAlign="start center"><i class="material-icons">link</i>&nbsp;Add link</span>
+                </button>
+            </div>
             <div fxFlex="100" fxLayout="row" fxLayoutAlign="end center">
                 <button color="accent" mat-button mat-icon-button (click)="clearCanvas()" matTooltip="Clear">
                     <i class="material-icons">
@@ -39,7 +52,8 @@
         </div>
     </div>
     <div id="outerAssemblyArea" class="asset-configuration-board-panel">
-        <div id="asset-configuration-board-canvas" class="asset-configuration-board-canvas" fxLayoutAlign="center center">
+        <div id="asset-configuration-board-canvas" class="asset-configuration-board-canvas"
+             fxLayoutAlign="center center">
 
         </div>
     </div>
diff --git a/ui/src/app/app-asset-monitoring/components/create-asset/create-asset.component.ts b/ui/src/app/app-asset-monitoring/components/create-asset/create-asset.component.ts
index b4dd597..a3e00f4 100644
--- a/ui/src/app/app-asset-monitoring/components/create-asset/create-asset.component.ts
+++ b/ui/src/app/app-asset-monitoring/components/create-asset/create-asset.component.ts
@@ -27,6 +27,7 @@ import { PanelType } from '../../../core-ui/dialog/base-dialog/base-dialog.model
 import { DialogService } from '../../../core-ui/dialog/base-dialog/base-dialog.service';
 import { DashboardConfiguration } from '../../model/dashboard-configuration.model';
 import { RestService } from '../../services/rest.service';
+import { AddLinkDialogComponent } from '../../dialog/add-link/add-link-dialog.component';
 
 interface Window {
     Image: any;
@@ -214,7 +215,8 @@ export class CreateAssetComponent implements AfterViewInit {
 
         dialogRef.afterClosed().subscribe(result => {
             if (result) {
-                this.addNewVisulizationItem(result);
+                const visGroup = this.shapeService.makeNewMeasurementShape(result);
+                this.addNewVisulizationItem(visGroup);
                 this.measurementPresent = true;
                 this.mainLayer.draw();
             }
@@ -222,6 +224,25 @@ export class CreateAssetComponent implements AfterViewInit {
         });
     }
 
+    openAddLinkDialog() {
+        this.keyboardListenerActive = false;
+        const dialogRef = this.dialogService.open(AddLinkDialogComponent, {
+            panelType: PanelType.SLIDE_IN_PANEL,
+            title: 'Add link',
+            width: '50vw',
+            data: {
+            }
+        });
+
+        dialogRef.afterClosed().subscribe(result => {
+            if (result) {
+                console.log(result);
+                this.addNewVisulizationItem(result);
+            }
+            this.keyboardListenerActive = true;
+        });
+    }
+
     @HostListener('document:keydown', ['$event'])
     handleKeyboardEvent(event: KeyboardEvent) {
         if (this.keyboardListenerActive) {
@@ -252,8 +273,7 @@ export class CreateAssetComponent implements AfterViewInit {
         }
     }
 
-    addNewVisulizationItem(visualizationConfig) {
-        const visGroup = this.shapeService.makeNewMeasurementShape(visualizationConfig);
+    addNewVisulizationItem(visGroup) {
         const id = this.makeId();
         visGroup.id(id);
         this.mainLayer.add(visGroup);
@@ -264,8 +284,8 @@ export class CreateAssetComponent implements AfterViewInit {
         const tr = this.getNewTransformer(id);
         this.mainLayer.add(tr);
         tr.attachTo(visGroup);
-        //this.mainLayer.draw();
-        //this.currentlySelectedShape = visGroup;
+        this.mainLayer.draw();
+        this.currentlySelectedShape = visGroup;
     }
 
     getNewTransformer(id: string): Konva.Transformer {
diff --git a/ui/src/app/app-asset-monitoring/components/view-asset/view-asset.component.ts b/ui/src/app/app-asset-monitoring/components/view-asset/view-asset.component.ts
index 674deff..d1c08e6 100644
--- a/ui/src/app/app-asset-monitoring/components/view-asset/view-asset.component.ts
+++ b/ui/src/app/app-asset-monitoring/components/view-asset/view-asset.component.ts
@@ -56,11 +56,34 @@ export class ViewAssetComponent {
         this.backgroundImageLayer = new Konva.Layer();
         this.showImage();
         this.mainCanvasStage.add(this.backgroundImageLayer);
+        const labels = this.mainCanvasStage.find('Label');
+        labels.each(label => {
+           label.on('mouseenter', () => this.onMouseEnter(label));
+            label.on('mouseleave', () => this.onMouseLeave(label));
+           label.on('click', () => this.onLinkClicked(label));
+        });
+
         this.backgroundImageLayer.moveToBottom();
         this.mainCanvasStage.draw();
         this.updateMeasurements();
     }
 
+    onMouseEnter(label) {
+        label.children[0].attrs.fontStyle = 'bold';
+        this.mainCanvasStage.draw();
+    }
+
+    onMouseLeave(label) {
+        label.children[0].attrs.fontStyle = 'normal';
+        this.mainCanvasStage.draw();
+    }
+
+    onLinkClicked(label) {
+        const href = label.children[0].attrs.hyperlink;
+        const newWindow = label.children[0].attrs.newWindow;
+        newWindow ? (window as any).open(href) : (window as any).location.href = href;
+    }
+
     updateMeasurements() {
         const dynamicShapes = this.mainCanvasStage.find('.dynamic-text');
         dynamicShapes.forEach(ds => {
diff --git a/ui/src/app/app-asset-monitoring/dialog/add-link/add-link-dialog.component.html b/ui/src/app/app-asset-monitoring/dialog/add-link/add-link-dialog.component.html
new file mode 100644
index 0000000..3a2b81f
--- /dev/null
+++ b/ui/src/app/app-asset-monitoring/dialog/add-link/add-link-dialog.component.html
@@ -0,0 +1,49 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+
+<div class="sp-dialog-container">
+    <div class="sp-dialog-content p-15">
+        <div fxFlex="100" fxLayout="column">
+            <h4>New link</h4>
+            <div class="dialog-margin-bottom" fxLayout="column">
+                <mat-form-field class="dialog-margin-bottom" color="accent">
+                    <mat-label>Label</mat-label>
+                    <input matInput placeholder="Label" [(ngModel)]="linkLabel">
+                    <mat-hint>The label that should be displayed as link</mat-hint>
+                </mat-form-field>
+                <mat-form-field color="accent">
+                    <mat-label>Target</mat-label>
+                    <input matInput placeholder="Label" [(ngModel)]="linkHref">
+                    <mat-hint>The target URL of the link</mat-hint>
+                </mat-form-field>
+                <mat-checkbox [(ngModel)]="newWindow">Open in new window</mat-checkbox>
+            </div>
+
+        </div>
+    </div>
+    <mat-divider></mat-divider>
+    <div class="sp-dialog-actions">
+        <button mat-button mat-raised-button class="mat-basic mr-10" (click)="cancel()">
+            Cancel
+        </button>
+        <button mat-button mat-raised-button color="accent" (click)="add()">
+            Add
+        </button>
+    </div>
+</div>
+
diff --git a/ui/src/app/app-asset-monitoring/model/selected-visualization-data.model.ts b/ui/src/app/app-asset-monitoring/dialog/add-link/add-link-dialog.component.scss
similarity index 73%
copy from ui/src/app/app-asset-monitoring/model/selected-visualization-data.model.ts
copy to ui/src/app/app-asset-monitoring/dialog/add-link/add-link-dialog.component.scss
index 2bcbe28..974d8b1 100644
--- a/ui/src/app/app-asset-monitoring/model/selected-visualization-data.model.ts
+++ b/ui/src/app/app-asset-monitoring/dialog/add-link/add-link-dialog.component.scss
@@ -16,14 +16,8 @@
  *
  */
 
-export interface SelectedVisualizationData {
-    labelBackgroundColor: string;
-    labelTextColor: string;
-    measurementBackgroundColor: string;
-    measurementTextColor: string;
-    visualizationId: string;
-    measurement: string;
-    label: string;
-    brokerUrl: string;
-    topic: string;
-}
\ No newline at end of file
+@import '../../../../scss/sp/sp-dialog.scss';
+
+.mr-10 {
+  margin-right: 10px;
+}
diff --git a/ui/src/app/app-asset-monitoring/dialog/add-link/add-link-dialog.component.ts b/ui/src/app/app-asset-monitoring/dialog/add-link/add-link-dialog.component.ts
new file mode 100644
index 0000000..3e45a79
--- /dev/null
+++ b/ui/src/app/app-asset-monitoring/dialog/add-link/add-link-dialog.component.ts
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { Component, OnInit } from '@angular/core';
+import { DialogRef } from '../../../core-ui/dialog/base-dialog/dialog-ref';
+import { ShapeService } from '../../services/shape.service';
+import { HyperlinkConfig } from '../../model/selected-visualization-data.model';
+
+@Component({
+  selector: 'sp-add-link-dialog-component',
+  templateUrl: 'add-link-dialog.component.html',
+  styleUrls: ['./add-link-dialog.component.scss'],
+})
+export class AddLinkDialogComponent implements OnInit {
+
+  linkLabel: string;
+  linkHref: string;
+  newWindow = false;
+
+  constructor(private dialogRef: DialogRef<AddLinkDialogComponent>,
+              private shapeService: ShapeService) {}
+
+  ngOnInit(): void {
+  }
+
+  cancel() {
+    this.dialogRef.close();
+  }
+
+  add() {
+    const hyperlinkConfig: HyperlinkConfig = {
+      linkLabel: this.linkLabel,
+      linkHref: this.linkHref,
+      newWindow: this.newWindow
+    };
+
+    const group = this.shapeService.makeNewHyperlinkGroup(hyperlinkConfig);
+    this.dialogRef.close(group);
+  }
+
+}
diff --git a/ui/src/app/app-asset-monitoring/model/selected-visualization-data.model.ts b/ui/src/app/app-asset-monitoring/model/selected-visualization-data.model.ts
index 2bcbe28..027482c 100644
--- a/ui/src/app/app-asset-monitoring/model/selected-visualization-data.model.ts
+++ b/ui/src/app/app-asset-monitoring/model/selected-visualization-data.model.ts
@@ -26,4 +26,10 @@ export interface SelectedVisualizationData {
     label: string;
     brokerUrl: string;
     topic: string;
-}
\ No newline at end of file
+}
+
+export interface HyperlinkConfig {
+    linkLabel: string;
+    linkHref: string;
+    newWindow: boolean;
+}
diff --git a/ui/src/app/app-asset-monitoring/services/shape.service.ts b/ui/src/app/app-asset-monitoring/services/shape.service.ts
index 6f0c56d..6e4a698 100644
--- a/ui/src/app/app-asset-monitoring/services/shape.service.ts
+++ b/ui/src/app/app-asset-monitoring/services/shape.service.ts
@@ -20,7 +20,10 @@ import {Injectable} from "@angular/core";
 
 
 import Konva from "konva";
-import {SelectedVisualizationData} from "../model/selected-visualization-data.model";
+import {
+    HyperlinkConfig,
+    SelectedVisualizationData
+} from "../model/selected-visualization-data.model";
 
 @Injectable()
 export class ShapeService {
@@ -29,22 +32,50 @@ export class ShapeService {
 
     }
 
+    makeNewHyperlinkGroup(hyperlinkConfig: HyperlinkConfig) {
+        const group = this.makeGroup(true);
+        group.add(this.makeHyperlinkLabel(hyperlinkConfig));
+        return group;
+    }
+
+    makeHyperlinkLabel(hyperlinkConfig: HyperlinkConfig): Konva.Label {
+        const label = new Konva.Label({
+            x: 200,
+            y: 40,
+        });
+        label.add(this.makeHyperlinkText(hyperlinkConfig));
+        return label;
+    }
+
+    makeHyperlinkText(hyperlinkConfig: HyperlinkConfig): Konva.Text {
+        const settings: any = {
+            text: hyperlinkConfig.linkLabel,
+            width: 200,
+            height: 20,
+            hyperlink: hyperlinkConfig.linkHref,
+            newWindow: hyperlinkConfig.newWindow,
+            textDecoration: 'underline',
+            fill: '#1b1464',
+        };
+        return new Konva.Text(settings);
+    }
+
     makeNewMeasurementShape(visualizationConfig: SelectedVisualizationData): Konva.Group {
-        let visualizationGroup = this.makeGroup(true);
+        const visualizationGroup = this.makeGroup(true);
         visualizationGroup.add(this.makeLabelGroup(visualizationConfig));
         visualizationGroup.add(this.makeMeasurementGroup(visualizationConfig));
         return visualizationGroup;
     }
 
     makeLabelGroup(config: SelectedVisualizationData): Konva.Group {
-        let labelGroup = this.makeGroup(false);
+        const labelGroup = this.makeGroup(false);
         labelGroup.add(this.makeRect(config.labelBackgroundColor, 120, 40, 120, 20));
         labelGroup.add(this.makeText(config, config.label, config.labelTextColor, 120, 45, 120, 20, false))
         return labelGroup;
     }
 
     makeMeasurementGroup(config: SelectedVisualizationData): Konva.Group {
-        let measurementGroup = this.makeGroup(false);
+        const measurementGroup = this.makeGroup(false);
         measurementGroup.add(this.makeRect(config.measurementBackgroundColor, 120, 60, 120, 40));
         measurementGroup.add(this.makeText(config, config.measurement, config.measurementTextColor, 120, 65, 120, 40, true))
         return measurementGroup;

[incubator-streampipes] 03/04: [STREAMPIPES-444] Improve link behaviour in asset dashboard

Posted by ri...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

riemer pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git

commit 0424e27843cb9728afa47e8be34f6d78a34cfc79
Author: Dominik Riemer <ri...@fzi.de>
AuthorDate: Wed Oct 13 09:56:20 2021 +0200

    [STREAMPIPES-444] Improve link behaviour in asset dashboard
---
 .../assetdashboard/AssetDashboardConfig.java       |  7 +--
 .../model/client/assetdashboard/CanvasElement.java |  7 +--
 .../dialog/add-link/add-link-dialog.component.html |  5 ++
 .../dialog/add-link/add-link-dialog.component.ts   |  2 +
 .../model/selected-visualization-data.model.ts     |  1 +
 .../app-asset-monitoring/services/shape.service.ts | 60 +++++++++++++---------
 6 files changed, 52 insertions(+), 30 deletions(-)

diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/assetdashboard/AssetDashboardConfig.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/assetdashboard/AssetDashboardConfig.java
index c375028..d8956e5 100644
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/assetdashboard/AssetDashboardConfig.java
+++ b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/assetdashboard/AssetDashboardConfig.java
@@ -20,6 +20,7 @@ package org.apache.streampipes.model.client.assetdashboard;
 import com.google.gson.annotations.SerializedName;
 
 import java.util.List;
+import java.util.Map;
 
 public class AssetDashboardConfig {
 
@@ -29,7 +30,7 @@ public class AssetDashboardConfig {
   private String dashboardName;
   private String dashboardDescription;
   private ImageInfo imageInfo;
-  private CanvasAttributes attrs;
+  private Map<String, Object> attrs;
   private String className;
   private List<CanvasElement> children;
 
@@ -52,11 +53,11 @@ public class AssetDashboardConfig {
     this.dashboardDescription = dashboardDescription;
   }
 
-  public CanvasAttributes getAttrs() {
+  public Map<String, Object> getAttrs() {
     return attrs;
   }
 
-  public void setAttrs(CanvasAttributes attrs) {
+  public void setAttrs(Map<String, Object> attrs) {
     this.attrs = attrs;
   }
 
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/assetdashboard/CanvasElement.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/assetdashboard/CanvasElement.java
index 1c5b595..a0cefd2 100644
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/assetdashboard/CanvasElement.java
+++ b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/assetdashboard/CanvasElement.java
@@ -18,21 +18,22 @@
 package org.apache.streampipes.model.client.assetdashboard;
 
 import java.util.List;
+import java.util.Map;
 
 public class CanvasElement {
 
-  private CanvasAttributes attrs;
+  private Map<String, Object> attrs;
   private String className;
   private List<CanvasElement> children;
 
   public CanvasElement() {
   }
 
-  public CanvasAttributes getAttrs() {
+  public Map<String, Object> getAttrs() {
     return attrs;
   }
 
-  public void setAttrs(CanvasAttributes attrs) {
+  public void setAttrs(Map<String, Object> attrs) {
     this.attrs = attrs;
   }
 
diff --git a/ui/src/app/app-asset-monitoring/dialog/add-link/add-link-dialog.component.html b/ui/src/app/app-asset-monitoring/dialog/add-link/add-link-dialog.component.html
index 3a2b81f..0bf9e0a 100644
--- a/ui/src/app/app-asset-monitoring/dialog/add-link/add-link-dialog.component.html
+++ b/ui/src/app/app-asset-monitoring/dialog/add-link/add-link-dialog.component.html
@@ -31,6 +31,11 @@
                     <input matInput placeholder="Label" [(ngModel)]="linkHref">
                     <mat-hint>The target URL of the link</mat-hint>
                 </mat-form-field>
+                <mat-form-field color="accent">
+                    <mat-label>Font Size</mat-label>
+                    <input matInput placeholder="Label" [(ngModel)]="labelFontSize">
+                    <mat-hint>Font Size</mat-hint>
+                </mat-form-field>
                 <mat-checkbox [(ngModel)]="newWindow">Open in new window</mat-checkbox>
             </div>
 
diff --git a/ui/src/app/app-asset-monitoring/dialog/add-link/add-link-dialog.component.ts b/ui/src/app/app-asset-monitoring/dialog/add-link/add-link-dialog.component.ts
index 3e45a79..89b5c9a 100644
--- a/ui/src/app/app-asset-monitoring/dialog/add-link/add-link-dialog.component.ts
+++ b/ui/src/app/app-asset-monitoring/dialog/add-link/add-link-dialog.component.ts
@@ -30,6 +30,7 @@ export class AddLinkDialogComponent implements OnInit {
 
   linkLabel: string;
   linkHref: string;
+  labelFontSize = 12;
   newWindow = false;
 
   constructor(private dialogRef: DialogRef<AddLinkDialogComponent>,
@@ -46,6 +47,7 @@ export class AddLinkDialogComponent implements OnInit {
     const hyperlinkConfig: HyperlinkConfig = {
       linkLabel: this.linkLabel,
       linkHref: this.linkHref,
+      labelFontSize: this.labelFontSize,
       newWindow: this.newWindow
     };
 
diff --git a/ui/src/app/app-asset-monitoring/model/selected-visualization-data.model.ts b/ui/src/app/app-asset-monitoring/model/selected-visualization-data.model.ts
index 027482c..b79cb68 100644
--- a/ui/src/app/app-asset-monitoring/model/selected-visualization-data.model.ts
+++ b/ui/src/app/app-asset-monitoring/model/selected-visualization-data.model.ts
@@ -31,5 +31,6 @@ export interface SelectedVisualizationData {
 export interface HyperlinkConfig {
     linkLabel: string;
     linkHref: string;
+    labelFontSize: number;
     newWindow: boolean;
 }
diff --git a/ui/src/app/app-asset-monitoring/services/shape.service.ts b/ui/src/app/app-asset-monitoring/services/shape.service.ts
index 6e4a698..3e61250 100644
--- a/ui/src/app/app-asset-monitoring/services/shape.service.ts
+++ b/ui/src/app/app-asset-monitoring/services/shape.service.ts
@@ -16,14 +16,14 @@
  *
  */
 
-import {Injectable} from "@angular/core";
+import { Injectable } from '@angular/core';
 
 
-import Konva from "konva";
+import Konva from 'konva';
 import {
     HyperlinkConfig,
     SelectedVisualizationData
-} from "../model/selected-visualization-data.model";
+} from '../model/selected-visualization-data.model';
 
 @Injectable()
 export class ShapeService {
@@ -50,12 +50,13 @@ export class ShapeService {
     makeHyperlinkText(hyperlinkConfig: HyperlinkConfig): Konva.Text {
         const settings: any = {
             text: hyperlinkConfig.linkLabel,
-            width: 200,
-            height: 20,
+            width: 'auto',
+            height: 'auto',
             hyperlink: hyperlinkConfig.linkHref,
             newWindow: hyperlinkConfig.newWindow,
             textDecoration: 'underline',
-            fill: '#1b1464',
+            fill: '#62e497',
+            fontSize: hyperlinkConfig.labelFontSize
         };
         return new Konva.Text(settings);
     }
@@ -69,15 +70,15 @@ export class ShapeService {
 
     makeLabelGroup(config: SelectedVisualizationData): Konva.Group {
         const labelGroup = this.makeGroup(false);
-        labelGroup.add(this.makeRect(config.labelBackgroundColor, 120, 40, 120, 20));
-        labelGroup.add(this.makeText(config, config.label, config.labelTextColor, 120, 45, 120, 20, false))
+        labelGroup.add(this.makeRect(config.labelBackgroundColor, 120, 40, 140, 20));
+        labelGroup.add(this.makeText(config, config.label, config.labelTextColor, 120, 45, 140, 20, false));
         return labelGroup;
     }
 
     makeMeasurementGroup(config: SelectedVisualizationData): Konva.Group {
         const measurementGroup = this.makeGroup(false);
-        measurementGroup.add(this.makeRect(config.measurementBackgroundColor, 120, 60, 120, 40));
-        measurementGroup.add(this.makeText(config, config.measurement, config.measurementTextColor, 120, 65, 120, 40, true))
+        measurementGroup.add(this.makeRect(config.measurementBackgroundColor, 120, 60, 140, 40));
+        measurementGroup.add(this.makeText(config, config.measurement, config.measurementTextColor, 120, 65, 140, 40, true));
         return measurementGroup;
     }
 
@@ -85,34 +86,45 @@ export class ShapeService {
         return new Konva.Group({
             x: 120,
             y: 40,
-            draggable: draggable
+            draggable
         });
     }
 
-    makeRect(fillColor: string, x: number, y: number, width: number, height: number): Konva.Rect {
+    makeRect(fillColor: string,
+             x: number,
+             y: number,
+             width: number,
+             height: number): Konva.Rect {
         return new Konva.Rect({
-            x: x,
-            y: y,
+            x,
+            y,
             fill: fillColor,
-            width: width,
-            height: height,
+            width,
+            height,
         });
     }
 
-    makeText(config: SelectedVisualizationData, text: string, textColor: string, x: number, y: number, width: number, height: number, dynamicContent: boolean): Konva.Text {
-        let textSettings: any = {
-            text: text,
-            x: x,
-            y: y,
-            width: width,
-            height: height,
+    makeText(config: SelectedVisualizationData,
+             text: string,
+             textColor: string,
+             x: number,
+             y: number,
+             width: number | string,
+             height: number | string,
+             dynamicContent: boolean): Konva.Text {
+        const textSettings: any = {
+            text,
+            x,
+            y,
+            width,
+            height,
             fill: textColor,
             align: 'center',
             fontSize: '15'
         };
 
         if (dynamicContent) {
-            textSettings.name = "dynamic-text";
+            textSettings.name = 'dynamic-text';
             textSettings.brokerUrl = config.brokerUrl;
             textSettings.topic = config.topic;
             textSettings.fontSize = '30';

[incubator-streampipes] 01/04: [STREAMPIPES-444] Allow modification of asset dashboards

Posted by ri...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

riemer pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git

commit e1f215deb93d6243fd139c5ff62ccb8b4960c98e
Author: Dominik Riemer <ri...@fzi.de>
AuthorDate: Tue Oct 12 22:07:57 2021 +0200

    [STREAMPIPES-444] Allow modification of asset dashboards
---
 .../streampipes/rest/impl/AssetDashboard.java      |  17 +++
 .../storage/api/IAssetDashboardStorage.java        |   2 +
 .../couchdb/impl/AssetDashboardStorageImpl.java    |   5 +
 .../app-asset-monitoring.component.html            |   4 +-
 .../app-asset-monitoring.component.ts              |  11 +-
 .../create-asset/create-asset.component.ts         | 154 +++++++++++++++------
 .../components/view-asset/view-asset.component.css |   7 +-
 .../view-asset/view-asset.component.html           |   1 +
 .../components/view-asset/view-asset.component.ts  |   5 +
 .../save-dashboard-dialog.component.html           |   2 +-
 .../save-dashboard-dialog.component.ts             |  37 ++++-
 .../app-asset-monitoring/model/image-info.model.ts |   4 +-
 .../app-asset-monitoring/services/rest.service.ts  |   4 +
 13 files changed, 192 insertions(+), 61 deletions(-)

diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/AssetDashboard.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/AssetDashboard.java
index 6114d20..bc00199 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/AssetDashboard.java
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/AssetDashboard.java
@@ -20,6 +20,8 @@ package org.apache.streampipes.rest.impl;
 import org.apache.commons.io.FileUtils;
 import org.apache.streampipes.model.client.assetdashboard.AssetDashboardConfig;
 import org.apache.streampipes.rest.core.base.impl.AbstractRestResource;
+import org.apache.streampipes.storage.api.IAssetDashboardStorage;
+import org.apache.streampipes.storage.management.StorageDispatcher;
 import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
 import org.glassfish.jersey.media.multipart.FormDataParam;
 
@@ -46,6 +48,17 @@ public class AssetDashboard extends AbstractRestResource {
     return ok(getNoSqlStorage().getAssetDashboardStorage().getAssetDashboard(dashboardId));
   }
 
+  @PUT
+  @Produces(MediaType.APPLICATION_JSON)
+  @Path("/{dashboardId}")
+  public Response updateAssetDashboard(@PathParam("dashboardId") String dashboardId,
+                                       AssetDashboardConfig dashboardConfig) {
+    AssetDashboardConfig dashboard = getAssetDashboardStorage().getAssetDashboard(dashboardId);
+    dashboardConfig.setRev(dashboard.getRev());
+    getNoSqlStorage().getAssetDashboardStorage().updateAssetDashboard(dashboardConfig);
+    return ok();
+  }
+
   @GET
   @Produces(MediaType.APPLICATION_JSON)
   public Response getAllDashboards() {
@@ -112,4 +125,8 @@ public class AssetDashboard extends AbstractRestResource {
   private String getTargetFile(String filename) {
     return getTargetDirectory() + File.separator + filename;
   }
+
+  private IAssetDashboardStorage getAssetDashboardStorage() {
+    return StorageDispatcher.INSTANCE.getNoSqlStore().getAssetDashboardStorage();
+  }
 }
diff --git a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IAssetDashboardStorage.java b/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IAssetDashboardStorage.java
index e1759bb..69d6627 100644
--- a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IAssetDashboardStorage.java
+++ b/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IAssetDashboardStorage.java
@@ -29,5 +29,7 @@ public interface IAssetDashboardStorage {
 
   void storeAssetDashboard(AssetDashboardConfig assetDashboardConfig);
 
+  void updateAssetDashboard(AssetDashboardConfig assetDashboardConfig);
+
   void deleteAssetDashboard(String dashboardId);
 }
diff --git a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/AssetDashboardStorageImpl.java b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/AssetDashboardStorageImpl.java
index 9d11b76..665259c 100644
--- a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/AssetDashboardStorageImpl.java
+++ b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/AssetDashboardStorageImpl.java
@@ -46,6 +46,11 @@ public class AssetDashboardStorageImpl extends AbstractDao<AssetDashboardConfig>
   }
 
   @Override
+  public void updateAssetDashboard(AssetDashboardConfig assetDashboardConfig) {
+    update(assetDashboardConfig);
+  }
+
+  @Override
   public void deleteAssetDashboard(String dashboardId) {
     delete(dashboardId);
   }
diff --git a/ui/src/app/app-asset-monitoring/app-asset-monitoring.component.html b/ui/src/app/app-asset-monitoring/app-asset-monitoring.component.html
index f3d5d2c..a51fd85 100644
--- a/ui/src/app/app-asset-monitoring/app-asset-monitoring.component.html
+++ b/ui/src/app/app-asset-monitoring/app-asset-monitoring.component.html
@@ -30,7 +30,7 @@
 
     <div fxLayout="column" fxFlex="100">
         <asset-dashboard-overview *ngIf="selectedIndex == 0 && !dashboardSelected" fxFlex="100" (selectedDashboard)="openDashboard($event)"></asset-dashboard-overview>
-        <view-asset fxFlex="100" *ngIf="selectedIndex == 0 && dashboardSelected" [dashboardConfig]="selectedDashboard" (dashboardClosed)="closeDashboard($event)"></view-asset>
-        <create-asset *ngIf="selectedIndex == 1" fxFlex="100" (dashboardClosed)="closeDashboard($event)"></create-asset>
+        <view-asset fxFlex="100" *ngIf="selectedIndex == 0 && dashboardSelected" [dashboardConfig]="selectedDashboard" (dashboardClosed)="closeDashboard($event)" (editDashboardEmitter)="editDashboard($event)"></view-asset>
+        <create-asset *ngIf="selectedIndex == 1" fxFlex="100" (dashboardClosed)="closeDashboard($event)" [dashboardConfig]="selectedDashboard"></create-asset>
     </div>
 </div>
diff --git a/ui/src/app/app-asset-monitoring/app-asset-monitoring.component.ts b/ui/src/app/app-asset-monitoring/app-asset-monitoring.component.ts
index e9bef92..e692078 100644
--- a/ui/src/app/app-asset-monitoring/app-asset-monitoring.component.ts
+++ b/ui/src/app/app-asset-monitoring/app-asset-monitoring.component.ts
@@ -16,8 +16,8 @@
  *
  */
 
-import {Component, EventEmitter, Output} from '@angular/core';
-import {DashboardConfiguration} from "./model/dashboard-configuration.model";
+import { Component, EventEmitter, Output } from '@angular/core';
+import { DashboardConfiguration } from "./model/dashboard-configuration.model";
 
 @Component({
     selector: 'app-asset-monitoring',
@@ -54,4 +54,9 @@ export class AppAssetMonitoringComponent {
         this.selectedIndex = 0;
     }
 
-}
\ No newline at end of file
+    editDashboard(dashboardConfig: DashboardConfiguration) {
+        this.selectedDashboard = dashboardConfig;
+        this.selectedIndex = 1;
+    }
+
+}
diff --git a/ui/src/app/app-asset-monitoring/components/create-asset/create-asset.component.ts b/ui/src/app/app-asset-monitoring/components/create-asset/create-asset.component.ts
index 5da1236..b4dd597 100644
--- a/ui/src/app/app-asset-monitoring/components/create-asset/create-asset.component.ts
+++ b/ui/src/app/app-asset-monitoring/components/create-asset/create-asset.component.ts
@@ -16,7 +16,7 @@
  *
  */
 
-import { Component, EventEmitter, HostListener, Output } from '@angular/core';
+import { AfterViewInit, Component, EventEmitter, HostListener, Input, Output } from '@angular/core';
 import Konva from 'konva';
 import { AddPipelineDialogComponent } from '../../dialog/add-pipeline/add-pipeline-dialog.component';
 import { MatDialog } from '@angular/material/dialog';
@@ -25,6 +25,8 @@ import { SelectedVisualizationData } from '../../model/selected-visualization-da
 import { SaveDashboardDialogComponent } from '../../dialog/save-dashboard/save-dashboard-dialog.component';
 import { PanelType } from '../../../core-ui/dialog/base-dialog/base-dialog.model';
 import { DialogService } from '../../../core-ui/dialog/base-dialog/base-dialog.service';
+import { DashboardConfiguration } from '../../model/dashboard-configuration.model';
+import { RestService } from '../../services/rest.service';
 
 interface Window {
     Image: any;
@@ -37,7 +39,7 @@ declare const window: Window;
     templateUrl: './create-asset.component.html',
     styleUrls: ['./create-asset.component.css']
 })
-export class CreateAssetComponent {
+export class CreateAssetComponent implements AfterViewInit {
 
     fileName: any;
     selectedUploadFile: File;
@@ -53,33 +55,72 @@ export class CreateAssetComponent {
 
     backgroundImagePresent = false;
     measurementPresent = false;
+    editMode = false;
+
+    @Input()
+    dashboardConfig: DashboardConfiguration;
 
     @Output() dashboardClosed = new EventEmitter<boolean>();
 
+    keyboardListenerActive = true;
+
     constructor(public dialog: MatDialog,
                 public shapeService: ShapeService,
-                private dialogService: DialogService) {
+                private dialogService: DialogService,
+                private restService: RestService) {
     }
 
     ngAfterViewInit() {
         const width = 1400;
         const height = 900;
-        this.mainCanvasStage = new Konva.Stage({
-            container: 'asset-configuration-board-canvas',
-            width,
-            height
-        });
+        if (!this.dashboardConfig) {
+            this.mainCanvasStage = new Konva.Stage({
+                container: 'asset-configuration-board-canvas',
+                width,
+                height
+            });
+            this.initLayers();
+        } else {
+            this.editMode = true;
+            this.makeEditable();
+            this.mainCanvasStage = Konva.Node.create(this.dashboardConfig, 'asset-configuration-board-canvas');
+            this.mainCanvasStage.draw();
+
+            this.backgroundImageLayer = this.mainCanvasStage.children[0];
+            this.mainLayer = this.mainCanvasStage.children[1];
+            const groups = this.mainLayer.getChildren().find('Group');
+            groups.forEach(g => {
+               const id = this.makeId();
+               g.id(id);
+               g.setDraggable(true);
+               this.addTransformerToShape(id, g);
+            });
+            this.makeClickable();
+            this.showImage();
+            this.backgroundImageLayer.moveToBottom();
+            this.mainCanvasStage.draw();
+            this.measurementPresent = true;
+        }
 
         const container = this.mainCanvasStage.container();
         container.focus();
+    }
 
-        this.initLayers();
+    makeEditable() {
+        this.dashboardConfig.imageInfo.draggable = true;
+        this.dashboardConfig.imageInfo.id = this.IMAGE_ID;
     }
 
     initLayers() {
         this.mainLayer = new Konva.Layer();
         this.backgroundImageLayer = new Konva.Layer();
 
+        this.makeClickable();
+        this.mainCanvasStage.add(this.backgroundImageLayer);
+        this.mainCanvasStage.add(this.mainLayer);
+    }
+
+    makeClickable() {
         this.backgroundImageLayer.on('click', evt => {
             this.currentlySelectedShape = evt.target;
         });
@@ -90,8 +131,6 @@ export class CreateAssetComponent {
             }
             this.currentlySelectedShape = parentElement;
         });
-        this.mainCanvasStage.add(this.backgroundImageLayer);
-        this.mainCanvasStage.add(this.mainLayer);
     }
 
     handleFileInput(event: any) {
@@ -103,7 +142,7 @@ export class CreateAssetComponent {
         image.onload = () => {
             const desiredWidth = Math.min(this.mainCanvasStage.width(), image.width);
             const aspectRatio = image.width / image.height;
-            const desiredHeight = desiredWidth * aspectRatio;
+            const desiredHeight = image.width > image.height ? desiredWidth / aspectRatio : desiredWidth * aspectRatio;
             const imageCanvas = new Konva.Image({
                 image,
                 width: desiredWidth,
@@ -114,13 +153,7 @@ export class CreateAssetComponent {
                 id: this.IMAGE_ID
             });
 
-            this.backgroundImageLayer.add(imageCanvas);
-            this.backgroundImageLayer.draw();
-
-            const tr = this.getNewTransformer(this.IMAGE_ID);
-            this.backgroundImageLayer.add(tr);
-            tr.attachTo(imageCanvas);
-            this.backgroundImageLayer.draw();
+            this.addImageTransformer(imageCanvas);
             this.currentlySelectedShape = imageCanvas;
         };
 
@@ -129,10 +162,21 @@ export class CreateAssetComponent {
 
         reader.readAsDataURL(this.selectedUploadFile);
         event.target.value = null;
-        this.backgroundImagePresent = true;
 
     }
 
+    addImageTransformer(imageCanvas: any) {
+        this.backgroundImageLayer.add(imageCanvas);
+        this.backgroundImageLayer.draw();
+
+        const tr = this.getNewTransformer(this.IMAGE_ID);
+        this.backgroundImageLayer.add(tr);
+        tr.attachTo(imageCanvas);
+        this.backgroundImageLayer.moveToBottom();
+        this.backgroundImageLayer.draw();
+        this.backgroundImagePresent = true;
+    }
+
     prepareDashboard() {
         const dialogRef = this.dialogService.open(SaveDashboardDialogComponent, {
             panelType: PanelType.SLIDE_IN_PANEL,
@@ -140,11 +184,12 @@ export class CreateAssetComponent {
             width: '50vw',
             data: {
                 dashboardCanvas: this.mainCanvasStage as any,
-                file: this.selectedUploadFile
+                file: this.selectedUploadFile,
+                editMode: this.editMode,
+                dashboardConfig: this.dashboardConfig
             }
         });
         dialogRef.afterClosed().subscribe(closed => {
-            console.log('close');
             this.dashboardClosed.emit(true);
         });
     }
@@ -158,6 +203,7 @@ export class CreateAssetComponent {
     }
 
     openAddPipelineDialog(): void {
+        this.keyboardListenerActive = false;
         const dialogRef = this.dialogService.open(AddPipelineDialogComponent, {
             panelType: PanelType.SLIDE_IN_PANEL,
             title: 'Add visualization',
@@ -167,41 +213,43 @@ export class CreateAssetComponent {
         });
 
         dialogRef.afterClosed().subscribe(result => {
-            console.log(result);
             if (result) {
                 this.addNewVisulizationItem(result);
                 this.measurementPresent = true;
                 this.mainLayer.draw();
             }
+            this.keyboardListenerActive = true;
         });
     }
 
     @HostListener('document:keydown', ['$event'])
     handleKeyboardEvent(event: KeyboardEvent) {
-        const delta = 4;
-        if (event.code === 'Delete') {
-            const id = this.currentlySelectedShape.id();
-            this.mainCanvasStage.findOne('#' + id + '-transformer').destroy();
-            this.currentlySelectedShape.destroy();
-            if (id === this.IMAGE_ID) {
-                this.backgroundImagePresent = false;
-            } else {
-                const remainingElementIds = this.mainLayer.find('Group');
-                if (remainingElementIds.length === 0) {
-                    this.measurementPresent = false;
+        if (this.keyboardListenerActive) {
+            const delta = 4;
+            if (event.code === 'Delete') {
+                const id = this.currentlySelectedShape.id();
+                this.mainCanvasStage.findOne('#' + id + '-transformer').destroy();
+                this.currentlySelectedShape.destroy();
+                if (id === this.IMAGE_ID) {
+                    this.backgroundImagePresent = false;
+                } else {
+                    const remainingElementIds = this.mainLayer.find('Group');
+                    if (remainingElementIds.length === 0) {
+                        this.measurementPresent = false;
+                    }
                 }
+            } else if (event.code === 'ArrowLeft') {
+                this.currentlySelectedShape.x(this.currentlySelectedShape.x() - delta);
+            } else if (event.code === 'ArrowRight') {
+                this.currentlySelectedShape.x(this.currentlySelectedShape.x() + delta);
+            } else if (event.code === 'ArrowDown') {
+                this.currentlySelectedShape.y(this.currentlySelectedShape.y() + delta);
+            } else if (event.code === 'ArrowUp') {
+                this.currentlySelectedShape.y(this.currentlySelectedShape.y() - delta);
             }
-        } else if (event.code === 'ArrowLeft') {
-            this.currentlySelectedShape.x(this.currentlySelectedShape.x() - delta);
-        } else if (event.code === 'ArrowRight') {
-            this.currentlySelectedShape.x(this.currentlySelectedShape.x() + delta);
-        } else if (event.code === 'ArrowDown') {
-            this.currentlySelectedShape.y(this.currentlySelectedShape.y() + delta);
-        } else if (event.code === 'ArrowUp') {
-            this.currentlySelectedShape.y(this.currentlySelectedShape.y() - delta);
+            this.backgroundImageLayer.draw();
+            this.mainLayer.draw();
         }
-        this.backgroundImageLayer.draw();
-        this.mainLayer.draw();
     }
 
     addNewVisulizationItem(visualizationConfig) {
@@ -209,11 +257,15 @@ export class CreateAssetComponent {
         const id = this.makeId();
         visGroup.id(id);
         this.mainLayer.add(visGroup);
+        this.addTransformerToShape(id, visGroup);
+    }
+
+    addTransformerToShape(id: string, visGroup: any) {
         const tr = this.getNewTransformer(id);
         this.mainLayer.add(tr);
         tr.attachTo(visGroup);
-        this.mainLayer.draw();
-        this.currentlySelectedShape = visGroup;
+        //this.mainLayer.draw();
+        //this.currentlySelectedShape = visGroup;
     }
 
     getNewTransformer(id: string): Konva.Transformer {
@@ -239,4 +291,14 @@ export class CreateAssetComponent {
         return text;
     }
 
+    showImage() {
+        const image = new window.Image();
+        image.src = this.restService.getImageUrl(this.dashboardConfig.imageInfo.imageName);
+        this.dashboardConfig.imageInfo.image = image;
+        image.onload = () => {
+            const imageCanvas = new Konva.Image(this.dashboardConfig.imageInfo);
+            this.addImageTransformer(imageCanvas);
+        };
+    }
+
 }
diff --git a/ui/src/app/app-asset-monitoring/components/view-asset/view-asset.component.css b/ui/src/app/app-asset-monitoring/components/view-asset/view-asset.component.css
index 4447b41..0fe1fc6 100644
--- a/ui/src/app/app-asset-monitoring/components/view-asset/view-asset.component.css
+++ b/ui/src/app/app-asset-monitoring/components/view-asset/view-asset.component.css
@@ -17,9 +17,12 @@
  */
 
 .asset-view-canvas {
-    width:1200px;
+    width:1400px;
     height:900px;
     border: 2px solid rgb(27, 20, 100);
     margin: 20px auto;
+}
 
-}
\ No newline at end of file
+.mr-10 {
+    margin-right: 10px;
+}
diff --git a/ui/src/app/app-asset-monitoring/components/view-asset/view-asset.component.html b/ui/src/app/app-asset-monitoring/components/view-asset/view-asset.component.html
index 35cd662..df343f2 100644
--- a/ui/src/app/app-asset-monitoring/components/view-asset/view-asset.component.html
+++ b/ui/src/app/app-asset-monitoring/components/view-asset/view-asset.component.html
@@ -22,6 +22,7 @@
             <h1>{{dashboardConfig.dashboardName}}</h1>
         </div>
         <div fxFlex="100" fxLayout="row" fxLayoutAlign="end center">
+            <button mat-button mat-raised-button color="accent" (click)="editDashboard()" class="mr-10">Edit dashboard</button>
             <button mat-button mat-raised-button class="mat-basic" (click)="closeDashboard()">Close dashboard</button>
         </div>
     </div>
diff --git a/ui/src/app/app-asset-monitoring/components/view-asset/view-asset.component.ts b/ui/src/app/app-asset-monitoring/components/view-asset/view-asset.component.ts
index bbd0c80..674deff 100644
--- a/ui/src/app/app-asset-monitoring/components/view-asset/view-asset.component.ts
+++ b/ui/src/app/app-asset-monitoring/components/view-asset/view-asset.component.ts
@@ -38,6 +38,7 @@ export class ViewAssetComponent {
 
     @Input() dashboardConfig: DashboardConfiguration;
     @Output() dashboardClosed = new EventEmitter<boolean>();
+    @Output() editDashboardEmitter = new EventEmitter<DashboardConfiguration>();
 
     mainCanvasStage: any;
     mainLayer: any;
@@ -86,4 +87,8 @@ export class ViewAssetComponent {
         this.dashboardClosed.emit(true);
     }
 
+    editDashboard() {
+        this.editDashboardEmitter.emit(this.dashboardConfig);
+    }
+
 }
diff --git a/ui/src/app/app-asset-monitoring/dialog/save-dashboard/save-dashboard-dialog.component.html b/ui/src/app/app-asset-monitoring/dialog/save-dashboard/save-dashboard-dialog.component.html
index 11a4194..9f743fd 100644
--- a/ui/src/app/app-asset-monitoring/dialog/save-dashboard/save-dashboard-dialog.component.html
+++ b/ui/src/app/app-asset-monitoring/dialog/save-dashboard/save-dashboard-dialog.component.html
@@ -41,7 +41,7 @@
             Cancel
         </button>
         <button mat-button mat-raised-button color="accent" (click)="save()">
-            Next
+            Save
         </button>
     </div>
 </div>
diff --git a/ui/src/app/app-asset-monitoring/dialog/save-dashboard/save-dashboard-dialog.component.ts b/ui/src/app/app-asset-monitoring/dialog/save-dashboard/save-dashboard-dialog.component.ts
index 413a344..8f2a078 100644
--- a/ui/src/app/app-asset-monitoring/dialog/save-dashboard/save-dashboard-dialog.component.ts
+++ b/ui/src/app/app-asset-monitoring/dialog/save-dashboard/save-dashboard-dialog.component.ts
@@ -16,7 +16,7 @@
  *
  */
 
-import { Component, Input } from '@angular/core';
+import { Component, Input, OnInit } from '@angular/core';
 import { RestService } from '../../services/rest.service';
 import { DashboardConfiguration } from '../../model/dashboard-configuration.model';
 import { ImageInfo } from '../../model/image-info.model';
@@ -29,7 +29,7 @@ import Stage = Konva.Stage;
     templateUrl: 'save-dashboard-dialog.component.html',
     styleUrls: ['./save-dashboard-dialog.component.scss'],
 })
-export class SaveDashboardDialogComponent {
+export class SaveDashboardDialogComponent implements OnInit {
 
     dashboardName: string;
     dashboardDescription: string;
@@ -40,19 +40,34 @@ export class SaveDashboardDialogComponent {
     @Input()
     file: File;
 
+    @Input()
+    editMode: boolean;
+
+    @Input()
+    dashboardConfig: DashboardConfiguration;
+
     constructor(
         private dialogRef: DialogRef<SaveDashboardDialogComponent>,
         private restService: RestService) {
     }
 
+    ngOnInit() {
+        if (this.editMode) {
+            this.dashboardName = this.dashboardConfig.dashboardName;
+            this.dashboardDescription = this.dashboardConfig.dashboardDescription;
+        }
+    }
+
     cancel() {
         this.dialogRef.close();
     }
 
     save() {
         // save image
-        this.restService.storeImage(this.file).subscribe(response => {
-        });
+        if (this.file) {
+            this.restService.storeImage(this.file).subscribe(response => {
+            });
+        }
 
         // save dashboard
         const imageInfo = this.makeImageInfo();
@@ -60,9 +75,19 @@ export class SaveDashboardDialogComponent {
         dashboardConfig.dashboardName = this.dashboardName;
         dashboardConfig.dashboardDescription = this.dashboardDescription;
         dashboardConfig.imageInfo = imageInfo;
-        dashboardConfig.imageInfo.imageName = this.file.name;
+        dashboardConfig.imageInfo.draggable = false;
+        if (this.file) {
+            dashboardConfig.imageInfo.imageName = this.file.name;
+        }
+
+        if (this.editMode) {
+            dashboardConfig.dashboardId = this.dashboardConfig.dashboardId;
+        }
 
-        this.restService.storeDashboard(dashboardConfig).subscribe(response => {
+        const observable = this.editMode ?
+            this.restService.updateDashboard(dashboardConfig) :
+            this.restService.storeDashboard(dashboardConfig);
+        observable.subscribe(response => {
             this.dialogRef.close();
         });
     }
diff --git a/ui/src/app/app-asset-monitoring/model/image-info.model.ts b/ui/src/app/app-asset-monitoring/model/image-info.model.ts
index a1e5e46..ca011eb 100644
--- a/ui/src/app/app-asset-monitoring/model/image-info.model.ts
+++ b/ui/src/app/app-asset-monitoring/model/image-info.model.ts
@@ -26,4 +26,6 @@ export interface ImageInfo {
     scaleY: number;
     rotation: number;
     image: any;
-}
\ No newline at end of file
+    draggable: boolean;
+    id: string;
+}
diff --git a/ui/src/app/app-asset-monitoring/services/rest.service.ts b/ui/src/app/app-asset-monitoring/services/rest.service.ts
index 2797cc5..e21427c 100644
--- a/ui/src/app/app-asset-monitoring/services/rest.service.ts
+++ b/ui/src/app/app-asset-monitoring/services/rest.service.ts
@@ -57,6 +57,10 @@ export class RestService {
     return this.http.post(this.url, dashboardConfig);
   }
 
+  updateDashboard(dashboardConfig: DashboardConfiguration) {
+    return this.http.put(this.url + '/' + dashboardConfig.dashboardId, dashboardConfig);
+  }
+
   getDashboards(): Observable<DashboardConfiguration[]> {
     return this.http.get(this.url).pipe(map(response => {
       return response as DashboardConfiguration[];