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 2020/03/16 12:31:03 UTC

[incubator-streampipes] 01/03: Refactor NotificationsModule to Angular 9 module

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 c27b3d1974545d58cbbd826d3eca3bf607074176
Author: Dominik Riemer <ri...@fzi.de>
AuthorDate: Mon Mar 16 11:06:18 2020 +0100

    Refactor NotificationsModule to Angular 9 module
---
 .../org/apache/streampipes/model/Notification.java |   9 ++
 .../model/base/InvocableStreamPipesEntity.java     |  12 ++
 .../manager/setup/CouchDbInstallationStep.java     |  31 ++++-
 .../apache/streampipes/rest/api/INotification.java |   2 +-
 .../apache/streampipes/rest/impl/Notification.java |   7 +-
 .../rest/notifications/NotificationListener.java   |   2 +-
 .../storage/api/INotificationStorage.java          |   2 +-
 .../couchdb/impl/NotificationStorageImpl.java      |  26 +++-
 .../apache/streampipes/vocabulary/StreamPipes.java |   1 +
 .../wrapper/context/RuntimeContext.java            |   2 +
 .../context/SpEventProcessorRuntimeContext.java    |   9 +-
 .../wrapper/context/SpEventSinkRuntimeContext.java |   6 +-
 .../wrapper/context/SpRuntimeContext.java          |  11 +-
 .../runtime/EventProcessorRuntimeParams.java       |   3 +-
 .../params/runtime/EventSinkRuntimeParams.java     |   3 +-
 ui/deployment/state.config.mst                     |   3 +-
 ui/deployment/toolbar.controller.mst               |   2 +-
 ui/src/app/core/working.state.config.ts            |   2 +-
 ui/src/app/dashboard/services/dashboard.service.ts |   1 -
 ui/src/app/editor/editor.controller.ts             |   5 +-
 .../components/notification-item.component.html    |  28 ++++
 .../components/notification-item.component.scss    |  55 ++++++++
 .../components/notification-item.component.ts      |  39 ++++++
 .../app/notifications/model/notifications.model.ts |  32 +++++
 .../app/notifications/notifications.component.html |  67 ++++++++++
 .../app/notifications/notifications.component.scss | 118 +++++++++++++++++
 .../app/notifications/notifications.component.ts   | 144 +++++++++++++++++++++
 .../app/notifications/notifications.controller.ts  |  65 ----------
 ui/src/app/notifications/notifications.html        |  72 -----------
 ui/src/app/notifications/notifications.module.ts   |  54 +++++++-
 .../notifications/service/notifications.service.ts |  55 ++++++++
 .../app/notifications/utils/notifications.utils.ts |  30 +++++
 ui/src/assets/templates/streampipes.html           |  10 +-
 ui/src/index.html                                  |   2 +-
 ui/src/scss/sp/main.scss                           |   2 +-
 35 files changed, 737 insertions(+), 175 deletions(-)

diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/Notification.java b/streampipes-model/src/main/java/org/apache/streampipes/model/Notification.java
index e050ee1..c161c0d 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/Notification.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/Notification.java
@@ -30,6 +30,7 @@ public class Notification {
   private String title;
   private Date createdAt;
   private String targetedAt;
+  private String correspondingPipelineId;
 
   private String message;
 
@@ -100,4 +101,12 @@ public class Notification {
   public void setRead(Boolean read) {
     this.read = read;
   }
+
+  public String getCorrespondingPipelineId() {
+    return correspondingPipelineId;
+  }
+
+  public void setCorrespondingPipelineId(String correspondingPipelineId) {
+    this.correspondingPipelineId = correspondingPipelineId;
+  }
 }
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/base/InvocableStreamPipesEntity.java b/streampipes-model/src/main/java/org/apache/streampipes/model/base/InvocableStreamPipesEntity.java
index e289eb9..6736149 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/base/InvocableStreamPipesEntity.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/base/InvocableStreamPipesEntity.java
@@ -62,6 +62,9 @@ public abstract class InvocableStreamPipesEntity extends NamedStreamPipesEntity
   @RdfProperty(StreamPipes.CORRESPONDING_PIPELINE)
   private String correspondingPipeline;
 
+  @RdfProperty(StreamPipes.CORRESPONDING_USER)
+  private String correspondingUser;
+
   private List<SpDataStream> streamRequirements;
 
   private boolean configured;
@@ -76,6 +79,7 @@ public abstract class InvocableStreamPipesEntity extends NamedStreamPipesEntity
     this.correspondingPipeline = other.getCorrespondingPipeline();
     this.inputStreams = new Cloner().streams(other.getInputStreams());
     this.configured = other.isConfigured();
+    this.correspondingUser = other.getCorrespondingUser();
     if (other.getStreamRequirements() != null) {
       this.streamRequirements = new Cloner().streams(other.getStreamRequirements());
     }
@@ -161,6 +165,14 @@ public abstract class InvocableStreamPipesEntity extends NamedStreamPipesEntity
     this.statusInfoSettings = statusInfoSettings;
   }
 
+  public String getCorrespondingUser() {
+    return correspondingUser;
+  }
+
+  public void setCorrespondingUser(String correspondingUser) {
+    this.correspondingUser = correspondingUser;
+  }
+
   //public Logger getLogger(Class clazz, PeConfig peConfig) {
   public Logger getLogger(Class clazz) {
     //	return LoggerFactory.getPeLogger(clazz, getCorrespondingPipeline(), getUri(), peConfig);
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/CouchDbInstallationStep.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/CouchDbInstallationStep.java
index e816651..18855dd 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/CouchDbInstallationStep.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/CouchDbInstallationStep.java
@@ -35,6 +35,10 @@ public class CouchDbInstallationStep implements InstallationStep {
             Collections.singletonList("8099/api/v1/admin@streampipes.org/master/sources/");
     private static final String initRdfEndpointHost = "http://localhost:";
 
+    private static final String PREPARING_NOTIFICATIONS_TEXT = "Preparing database " +
+            "'notifications'...";
+    private static final String PREPARING_USERS_TEXT = "Preparing database 'users'...";
+
     public CouchDbInstallationStep() {
 
     }
@@ -83,6 +87,7 @@ public class CouchDbInstallationStep implements InstallationStep {
         List<Message> result = new ArrayList<>();
         result.add(addUserView());
         result.add(addConnectionView());
+        result.add(addNotificationView());
         return result;
     }
 
@@ -95,6 +100,26 @@ public class CouchDbInstallationStep implements InstallationStep {
         return Notifications.success("Discovering pipeline element endpoints...");
     }
 
+    private Message addNotificationView() {
+        try {
+            DesignDocument userDocument = prepareDocument("_design/notificationtypes");
+            Map<String, MapReduce> views = new HashMap<>();
+
+            MapReduce notificationTypeFunction = new MapReduce();
+            notificationTypeFunction.setMap("function (doc) { var vizName = doc.title.replace(/\\s/g, '-'); var indexName = doc.correspondingPipelineId + '-' + vizName; emit(indexName, doc);}");
+
+            views.put("notificationtypes", notificationTypeFunction);
+
+            userDocument.setViews(views);
+            Response resp = Utils.getCouchDbNotificationClient().design().synchronizeWithDb(userDocument);
+
+            if (resp.getError() != null) return Notifications.error(PREPARING_NOTIFICATIONS_TEXT);
+            else return Notifications.success(PREPARING_NOTIFICATIONS_TEXT);
+        } catch (Exception e) {
+            return Notifications.error(PREPARING_NOTIFICATIONS_TEXT);
+        }
+    }
+
     private Message addUserView() {
         try {
             DesignDocument userDocument = prepareDocument("_design/users");
@@ -112,10 +137,10 @@ public class CouchDbInstallationStep implements InstallationStep {
             userDocument.setViews(views);
             Response resp = Utils.getCouchDbUserClient().design().synchronizeWithDb(userDocument);
 
-            if (resp.getError() != null) return Notifications.error("Preparing database 'users'...");
-            else return Notifications.success("Preparing database 'users'...");
+            if (resp.getError() != null) return Notifications.error(PREPARING_USERS_TEXT);
+            else return Notifications.success(PREPARING_USERS_TEXT);
         } catch (Exception e) {
-            return Notifications.error("Preparing database 'users'...");
+            return Notifications.error(PREPARING_USERS_TEXT);
         }
     }
 
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/api/INotification.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/api/INotification.java
index 419dd31..75612fe 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/api/INotification.java
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/api/INotification.java
@@ -22,7 +22,7 @@ import javax.ws.rs.core.Response;
 
 public interface INotification {
 
-	Response getNotifications();
+	Response getNotifications(String notificationTypeId, Integer offset, Integer count);
 
 	Response getUnreadNotifications();
 
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/Notification.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/Notification.java
index 527d363..b653ba7 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/Notification.java
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/Notification.java
@@ -28,6 +28,7 @@ import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 
@@ -38,9 +39,11 @@ public class Notification extends AbstractRestInterface implements INotification
     @Produces(MediaType.APPLICATION_JSON)
     @GsonWithIds
     @Override
-    public Response getNotifications() {
+    public Response getNotifications(@QueryParam("notificationType") String notificationTypeId,
+                                     @QueryParam("offset") Integer offset,
+                                     @QueryParam("count") Integer count) {
         return ok(getNotificationStorage()
-                .getAllNotifications());
+                .getAllNotifications(notificationTypeId, offset, count));
     }
 
     @GET
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/notifications/NotificationListener.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/notifications/NotificationListener.java
index 7d0c9bd..4e59eee 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/notifications/NotificationListener.java
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/notifications/NotificationListener.java
@@ -25,7 +25,7 @@ import javax.servlet.ServletContextListener;
 
 public class NotificationListener implements ServletContextListener {
 
-  private static final String internalNotificationTopic = "org.apache.streampipes.notifications";
+  private static final String internalNotificationTopic = "org.apache.streampipes.notifications.*";
 
 
   @Override
diff --git a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/INotificationStorage.java b/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/INotificationStorage.java
index 3a0c2d7..40edf9f 100644
--- a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/INotificationStorage.java
+++ b/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/INotificationStorage.java
@@ -26,7 +26,7 @@ public interface INotificationStorage {
 
   Notification getNotification(String notificationId);
 
-  List<Notification> getAllNotifications();
+  List<Notification> getAllNotifications(String notificationTypeId, Integer offset, Integer count);
 
   List<Notification> getUnreadNotifications();
 
diff --git a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/NotificationStorageImpl.java b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/NotificationStorageImpl.java
index c9bcb18..dfc8e11 100644
--- a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/NotificationStorageImpl.java
+++ b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/NotificationStorageImpl.java
@@ -18,12 +18,14 @@
 
 package org.apache.streampipes.storage.couchdb.impl;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
 import org.apache.streampipes.model.Notification;
 import org.apache.streampipes.storage.api.INotificationStorage;
 import org.apache.streampipes.storage.couchdb.dao.AbstractDao;
 import org.apache.streampipes.storage.couchdb.utils.Utils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.util.List;
 import java.util.stream.Collectors;
@@ -43,8 +45,24 @@ public class NotificationStorageImpl extends AbstractDao<Notification> implement
   }
 
   @Override
-  public List<Notification> getAllNotifications() {
-    return findAll();
+  public List<Notification> getAllNotifications(String notificationTypeId,
+                                                Integer offset,
+                                                Integer count) {
+    Gson gson = couchDbClientSupplier.get().getGson();
+    List<JsonObject> notifications =
+            couchDbClientSupplier
+                    .get()
+                    .view("notificationtypes/notificationtypes")
+                    .key(notificationTypeId)
+                    .includeDocs(true)
+                    .skip(offset)
+                    .limit(count)
+                    .query(JsonObject.class);
+
+    return notifications
+            .stream()
+            .map(notification -> gson.fromJson(notification, Notification.class))
+            .collect(Collectors.toList());
   }
 
   @Override
diff --git a/streampipes-vocabulary/src/main/java/org/apache/streampipes/vocabulary/StreamPipes.java b/streampipes-vocabulary/src/main/java/org/apache/streampipes/vocabulary/StreamPipes.java
index 3fca213..06e8f36 100644
--- a/streampipes-vocabulary/src/main/java/org/apache/streampipes/vocabulary/StreamPipes.java
+++ b/streampipes-vocabulary/src/main/java/org/apache/streampipes/vocabulary/StreamPipes.java
@@ -378,4 +378,5 @@ public class StreamPipes {
   public static final String SELECTED_COLOR = NS + "hasSelectedColor";
   public static final String HAS_WIDGET_ICON_NAME = NS + "hasWidgetIconName";
   public static final String HAS_WIDGET_DESCRIPTION = NS + "hasWidgetDescription";
+  public static final String CORRESPONDING_USER = NS + "hasCorrespondingUser";
 }
diff --git a/streampipes-wrapper/src/main/java/org/apache/streampipes/wrapper/context/RuntimeContext.java b/streampipes-wrapper/src/main/java/org/apache/streampipes/wrapper/context/RuntimeContext.java
index 725f3b8..83c2580 100644
--- a/streampipes-wrapper/src/main/java/org/apache/streampipes/wrapper/context/RuntimeContext.java
+++ b/streampipes-wrapper/src/main/java/org/apache/streampipes/wrapper/context/RuntimeContext.java
@@ -30,4 +30,6 @@ public interface RuntimeContext {
   List<SchemaInfo> getInputSchemaInfo();
 
   List<SourceInfo> getInputSourceInfo();
+
+  String getCorrespondingUser();
 }
diff --git a/streampipes-wrapper/src/main/java/org/apache/streampipes/wrapper/context/SpEventProcessorRuntimeContext.java b/streampipes-wrapper/src/main/java/org/apache/streampipes/wrapper/context/SpEventProcessorRuntimeContext.java
index bf5349c..a8159fe 100644
--- a/streampipes-wrapper/src/main/java/org/apache/streampipes/wrapper/context/SpEventProcessorRuntimeContext.java
+++ b/streampipes-wrapper/src/main/java/org/apache/streampipes/wrapper/context/SpEventProcessorRuntimeContext.java
@@ -29,9 +29,12 @@ public class SpEventProcessorRuntimeContext extends SpRuntimeContext implements
   private SchemaInfo outputSchemaInfo;
   private SourceInfo outputSourceInfo;
 
-  public SpEventProcessorRuntimeContext(List<SourceInfo> inputSourceInfo, List<SchemaInfo>
-          inputSchemaInfo, SourceInfo outputSourceInfo, SchemaInfo outputSchemaInfo) {
-    super(inputSourceInfo, inputSchemaInfo);
+  public SpEventProcessorRuntimeContext(List<SourceInfo> inputSourceInfo,
+                                        List<SchemaInfo> inputSchemaInfo,
+                                        SourceInfo outputSourceInfo,
+                                        SchemaInfo outputSchemaInfo,
+                                        String correspondingUser) {
+    super(inputSourceInfo, inputSchemaInfo, correspondingUser);
     this.outputSchemaInfo = outputSchemaInfo;
     this.outputSourceInfo = outputSourceInfo;
   }
diff --git a/streampipes-wrapper/src/main/java/org/apache/streampipes/wrapper/context/SpEventSinkRuntimeContext.java b/streampipes-wrapper/src/main/java/org/apache/streampipes/wrapper/context/SpEventSinkRuntimeContext.java
index 4899f51..19c79aa 100644
--- a/streampipes-wrapper/src/main/java/org/apache/streampipes/wrapper/context/SpEventSinkRuntimeContext.java
+++ b/streampipes-wrapper/src/main/java/org/apache/streampipes/wrapper/context/SpEventSinkRuntimeContext.java
@@ -26,7 +26,9 @@ import java.util.List;
 public class SpEventSinkRuntimeContext extends SpRuntimeContext implements
         EventSinkRuntimeContext, Serializable {
 
-  public SpEventSinkRuntimeContext(List<SourceInfo> sourceInfo, List<SchemaInfo> inputSchemaInfo) {
-    super(sourceInfo, inputSchemaInfo);
+  public SpEventSinkRuntimeContext(List<SourceInfo> sourceInfo,
+                                   List<SchemaInfo> inputSchemaInfo,
+                                   String correspondingUser) {
+    super(sourceInfo, inputSchemaInfo, correspondingUser);
   }
 }
diff --git a/streampipes-wrapper/src/main/java/org/apache/streampipes/wrapper/context/SpRuntimeContext.java b/streampipes-wrapper/src/main/java/org/apache/streampipes/wrapper/context/SpRuntimeContext.java
index b31ce09..40b9f34 100644
--- a/streampipes-wrapper/src/main/java/org/apache/streampipes/wrapper/context/SpRuntimeContext.java
+++ b/streampipes-wrapper/src/main/java/org/apache/streampipes/wrapper/context/SpRuntimeContext.java
@@ -27,10 +27,14 @@ public class SpRuntimeContext implements RuntimeContext {
 
   private List<SchemaInfo> inputSchemaInfo;
   private List<SourceInfo> sourceInfo;
+  private String correspondingUser;
 
-  public SpRuntimeContext(List<SourceInfo> sourceInfo, List<SchemaInfo> inputSchemaInfo) {
+  public SpRuntimeContext(List<SourceInfo> sourceInfo,
+                          List<SchemaInfo> inputSchemaInfo,
+                          String correspondingUser) {
     this.inputSchemaInfo = inputSchemaInfo;
     this.sourceInfo = sourceInfo;
+    this.correspondingUser = correspondingUser;
   }
 
   public SpRuntimeContext() {
@@ -52,4 +56,9 @@ public class SpRuntimeContext implements RuntimeContext {
   public List<SourceInfo> getInputSourceInfo() {
     return sourceInfo;
   }
+
+  @Override
+  public String getCorrespondingUser() {
+    return correspondingUser;
+  }
 }
diff --git a/streampipes-wrapper/src/main/java/org/apache/streampipes/wrapper/params/runtime/EventProcessorRuntimeParams.java b/streampipes-wrapper/src/main/java/org/apache/streampipes/wrapper/params/runtime/EventProcessorRuntimeParams.java
index 7ceda55..3160e9c 100644
--- a/streampipes-wrapper/src/main/java/org/apache/streampipes/wrapper/params/runtime/EventProcessorRuntimeParams.java
+++ b/streampipes-wrapper/src/main/java/org/apache/streampipes/wrapper/params/runtime/EventProcessorRuntimeParams.java
@@ -34,7 +34,8 @@ public class EventProcessorRuntimeParams<B extends EventProcessorBindingParams>
   protected EventProcessorRuntimeContext makeRuntimeContext() {
     return new SpEventProcessorRuntimeContext(getSourceInfo(),
             getSchemaInfo(), bindingParams.getOutputStreamParams()
-                    .getSourceInfo(), bindingParams.getOutputStreamParams().getSchemaInfo());
+                    .getSourceInfo(), bindingParams.getOutputStreamParams().getSchemaInfo(),
+            bindingParams.getGraph().getCorrespondingUser());
   }
 
 }
diff --git a/streampipes-wrapper/src/main/java/org/apache/streampipes/wrapper/params/runtime/EventSinkRuntimeParams.java b/streampipes-wrapper/src/main/java/org/apache/streampipes/wrapper/params/runtime/EventSinkRuntimeParams.java
index f54a0c0..0286991 100644
--- a/streampipes-wrapper/src/main/java/org/apache/streampipes/wrapper/params/runtime/EventSinkRuntimeParams.java
+++ b/streampipes-wrapper/src/main/java/org/apache/streampipes/wrapper/params/runtime/EventSinkRuntimeParams.java
@@ -32,7 +32,8 @@ public class EventSinkRuntimeParams<B extends EventSinkBindingParams> extends
 
   @Override
   protected EventSinkRuntimeContext makeRuntimeContext() {
-    return new SpEventSinkRuntimeContext(getSourceInfo(), getSchemaInfo());
+    return new SpEventSinkRuntimeContext(getSourceInfo(), getSchemaInfo(),
+            bindingParams.getGraph().getCorrespondingUser());
   }
 
 }
diff --git a/ui/deployment/state.config.mst b/ui/deployment/state.config.mst
index 58a32bd..330e598 100644
--- a/ui/deployment/state.config.mst
+++ b/ui/deployment/state.config.mst
@@ -26,6 +26,7 @@ import { HomeComponent } from '../home/home.component';
 import { InfoComponent } from '../info/info.component';
 import { AppContainerComponent } from '../app-container/app-container.component';
 import { StandaloneDashboardComponent } from "../dashboard/components/standalone/standalone-dashboard.component";
+import { NotificationsComponent } from "../notifications/notifications.component";
 
 export default function stateConfig($stateProvider, $urlRouterProvider) {
 
@@ -140,7 +141,7 @@ export default function stateConfig($stateProvider, $urlRouterProvider) {
             url: '/notifications',
             views: {
                 'spMain@streampipes': {
-                    template: require('../notifications/notifications.html')
+                    component: NotificationsComponent
                 }
             }
         })
diff --git a/ui/deployment/toolbar.controller.mst b/ui/deployment/toolbar.controller.mst
index 4df4a27..ac5d9fe 100644
--- a/ui/deployment/toolbar.controller.mst
+++ b/ui/deployment/toolbar.controller.mst
@@ -202,7 +202,7 @@ export class ToolbarController {
         var passcode = 'admin';
         var websocketProtocol = this.$location.protocol() === "http" ? "ws" : "wss";
         var brokerUrl = websocketProtocol + '://' + this.$location.host() + ':' + this.$location.port() + '/streampipes/ws';
-        var inputTopic = '/topic/org.apache.streampipes.notifications';
+        var inputTopic = '/topic/org.apache.streampipes.notifications.' + this.AuthStatusService.email;
 
         let stompClient = new Client({
             brokerURL: brokerUrl,
diff --git a/ui/src/app/core/working.state.config.ts b/ui/src/app/core/working.state.config.ts
index 786b1e4..3c2ed5a 100644
--- a/ui/src/app/core/working.state.config.ts
+++ b/ui/src/app/core/working.state.config.ts
@@ -122,7 +122,7 @@ export default function stateConfig($stateProvider, $urlRouterProvider) {
       url: '/notifications',
       views: {
         'spMain@streampipes': {
-          templateUrl: '../notifications/notifications.html',
+          templateUrl: '../notifications/notifications.component.html',
           controller: 'NotificationsCtrl',
         },
       },
diff --git a/ui/src/app/dashboard/services/dashboard.service.ts b/ui/src/app/dashboard/services/dashboard.service.ts
index 4cf5fca..a4f1d63 100644
--- a/ui/src/app/dashboard/services/dashboard.service.ts
+++ b/ui/src/app/dashboard/services/dashboard.service.ts
@@ -25,7 +25,6 @@ import {Dashboard} from "../models/dashboard.model";
 import {TsonLdSerializerService} from "../../platform-services/tsonld-serializer.service";
 import {DashboardWidget} from "../../core-model/dashboard/DashboardWidget";
 import {VisualizablePipeline} from "../../core-model/dashboard/VisualizablePipeline";
-import {RestApi} from "../../services/rest-api.service";
 
 @Injectable()
 export class DashboardService {
diff --git a/ui/src/app/editor/editor.controller.ts b/ui/src/app/editor/editor.controller.ts
index f319c18..320efa0 100644
--- a/ui/src/app/editor/editor.controller.ts
+++ b/ui/src/app/editor/editor.controller.ts
@@ -17,6 +17,7 @@
  */
 
 import * as angular from 'angular';
+import {AuthStatusService} from "../services/auth-status.service";
 
 export class EditorCtrl {
 
@@ -26,7 +27,7 @@ export class EditorCtrl {
     $window: any;
     JsplumbBridge: any;
     EditorDialogManager: any;
-    AuthStatusService: any;
+    AuthStatusService: AuthStatusService;
     currentElements: any;
     allElements: any;
     currentModifiedPipelineId: any;
@@ -228,6 +229,7 @@ export class EditorCtrl {
                 let sepas = msg.data;
                 angular.forEach(sepas, sepa => {
                     sepa.type = 'sepa';
+                    sepa.correspondingUser = this.AuthStatusService.email;
                 });
                 this.allElements["sepa"] = sepas;
                 this.checkForTutorial();
@@ -240,6 +242,7 @@ export class EditorCtrl {
                 let actions = msg.data;
                 angular.forEach(actions, action => {
                     action.type = 'action';
+                    action.correspondingUser = this.AuthStatusService.email;
                 });
                 this.allElements["action"] = actions;
                 this.checkForTutorial();
diff --git a/ui/src/app/notifications/components/notification-item.component.html b/ui/src/app/notifications/components/notification-item.component.html
new file mode 100644
index 0000000..7a31381
--- /dev/null
+++ b/ui/src/app/notifications/components/notification-item.component.html
@@ -0,0 +1,28 @@
+<!--
+  ~   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="notification-item">
+    <div class="i">
+        <div class="head">
+            <span class="time">{{notification.createdAt}}</span>
+        </div>
+        <div class="notification-box sp-accent-bg">
+            <div [innerHTML]="sanitizedMessage"></div>
+        </div>
+    </div>
+
+</div>
\ No newline at end of file
diff --git a/ui/src/app/notifications/components/notification-item.component.scss b/ui/src/app/notifications/components/notification-item.component.scss
new file mode 100644
index 0000000..bbb3b92
--- /dev/null
+++ b/ui/src/app/notifications/components/notification-item.component.scss
@@ -0,0 +1,55 @@
+/*
+ *   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 '../../../scss/_variables';
+
+.head {
+    font-size: 13px;
+}
+.name {
+    font-weight: 600;
+    position: relative;
+}
+
+.time {
+    color: $sp-color-accent;
+}
+.notification-box {
+    margin-top: 20px;
+    color: #fff;
+    font-size: 15px;
+    border-radius: 5px;
+    padding: 20px;
+    line-height: 25px;
+    max-width: 400px;
+    word-wrap: break-word;
+    position: relative;
+}
+.notification-box:before {
+    content: '';
+    position: absolute;
+    width: 0;
+    height: 0;
+    top: -12px;
+    border-bottom: 12px solid #1b1464;
+    border-left: 10px solid transparent;
+    border-right: 10px solid transparent;
+}
+
+.notification-item {
+    margin-top: 30px;
+}
\ No newline at end of file
diff --git a/ui/src/app/notifications/components/notification-item.component.ts b/ui/src/app/notifications/components/notification-item.component.ts
new file mode 100644
index 0000000..15d239d
--- /dev/null
+++ b/ui/src/app/notifications/components/notification-item.component.ts
@@ -0,0 +1,39 @@
+/*
+ *   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, Input, OnInit} from "@angular/core";
+import {NotificationItem} from "../model/notifications.model";
+import {DomSanitizer} from "@angular/platform-browser";
+
+@Component({
+    selector: 'notification-item',
+    templateUrl: './notification-item.component.html',
+    styleUrls: ['./notification-item.component.scss']
+})
+export class NotificationItemComponent implements OnInit {
+
+    @Input() notification: NotificationItem;
+    sanitizedMessage;
+
+    constructor(private domSanitizer: DomSanitizer) {
+
+    }
+
+    ngOnInit(): void {
+        this.sanitizedMessage = this.domSanitizer.bypassSecurityTrustHtml(this.notification.message);
+    }
+}
\ No newline at end of file
diff --git a/ui/src/app/notifications/model/notifications.model.ts b/ui/src/app/notifications/model/notifications.model.ts
new file mode 100644
index 0000000..7d1ce57
--- /dev/null
+++ b/ui/src/app/notifications/model/notifications.model.ts
@@ -0,0 +1,32 @@
+/*
+ *   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.
+ */
+
+export interface NotificationItem {
+    title: string;
+    createdAt: Date;
+    targetedAt: string;
+    correspondingPipelineId: string;
+    message: string;
+    read: boolean;
+}
+
+export interface ExistingNotification {
+    pipelineName: string;
+    notificationTitle: string;
+    pipelineId: string;
+    notificationId: string;
+}
\ No newline at end of file
diff --git a/ui/src/app/notifications/notifications.component.html b/ui/src/app/notifications/notifications.component.html
new file mode 100644
index 0000000..1180323
--- /dev/null
+++ b/ui/src/app/notifications/notifications.component.html
@@ -0,0 +1,67 @@
+<!--
+  ~ 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 fxLayout="column" class="page-container page-container-max-height">
+    <div fxLayout="row" style="padding:0px;background-color:#f6f6f6;">
+        <div fxFlex="100" style="line-height:24px;border-bottom:1px solid #ccc">
+            <div fxFlex="100" fxLayout="row">
+                <div fxFlex fxLayoutAlign="start center">
+                    <mat-tab-group>
+                        <mat-tab label="My Notifications"></mat-tab>
+                    </mat-tab-group>
+                </div>
+                <div fxFlex fxLayoutAlign="end center" class="mr-20">
+
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <div class="fixed-height page-container-padding-inner" fxLayout="row" fxFlex="100">
+        <div fxFlex="30" class="notifications-overview scrolling-auto">
+            <mat-list>
+                <mat-list-item *ngFor="let existingNotification of existingNotifications"
+                               (click)="selectNotification(existingNotification)" class="list-item">
+                    <div mat-list-avatar
+                         class="notification-avatar sp-accent-bg">{{elementIconText.getElementIconText(existingNotification.pipelineName)}}
+                    </div>
+                    <h4 mat-line>{{existingNotification.pipelineName}}</h4>
+                    <p mat-line>{{existingNotification.notificationTitle}} </p>
+                    <div class="new-notification-info-panel">
+                        <div class="new-notification-info"
+                             *ngIf="currentlySelectedNotificationId != existingNotification.notificationId &&
+                             newNotificationInfo[existingNotification.notificationId]"></div>
+                    </div>
+                </mat-list-item>
+            </mat-list>
+        </div>
+        <div fxFlex="70" class="notifications-details" *ngIf="pipelinesWithNotificationsPresent">
+            <div class="notification-details-wrapper">
+                <div class="notification-header" fxLayout="column" fxLayoutAlign="center start">
+                    <div class="notification-header-pipeline-name">{{currentlySelectedNotification.pipelineName}}</div>
+                    <div class="notification-header-notification-name">{{currentlySelectedNotification.notificationTitle}}</div>
+                    <hr class="header-divider"/>
+                </div>
+                <div #notificationPane class="notification-pane" (scroll)="onScroll($event)">
+                    <notification-item [notification]="notification" *ngFor="let notification of notifications"></notification-item>
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/ui/src/app/notifications/notifications.component.scss b/ui/src/app/notifications/notifications.component.scss
new file mode 100644
index 0000000..7287acd
--- /dev/null
+++ b/ui/src/app/notifications/notifications.component.scss
@@ -0,0 +1,118 @@
+/*
+ *   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 '../../scss/_variables';
+
+.fixed-height {
+    flex-direction: row;
+    box-sizing: border-box;
+    display: flex;
+    flex: 1 1 100%;
+    max-height: 100%;
+    overflow-y: auto;
+}
+
+.page-container-max-height {
+    max-height: calc(100% - 20px);
+    //overflow: hidden;
+}
+
+.mr-20 {
+    margin-right:20px;
+}
+
+.notifications-overview {
+    border-right: 5px solid $sp-color-accent;
+}
+
+.notifications-details {
+    padding-top: 20px;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+    flex: 1;
+}
+
+.notification-details-wrapper {
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+}
+
+.notification-header {
+    min-height: 75px;
+    padding: 10px 10px 10px 20px;
+    color: $sp-color-accent;
+}
+
+.header-divider {
+    display:flex;
+    align-self: center;
+    margin: 10px 20px;
+    width: 100%;
+    border-top: 2px solid $sp-color-accent
+}
+
+.notification-header-pipeline-name {
+    font-size: 14pt;
+}
+
+.notification-header-notification-name {
+    font-weight: 600;
+}
+
+.notification-avatar {
+    align-items: center;
+    justify-content: center;
+    display: flex;
+    color: white;
+}
+
+.list-item:hover {
+    background: #E0E0E0;
+    cursor: pointer;
+}
+
+.list-item {
+    border-bottom: 1px solid #BDBDBD;
+}
+
+.notification-pane {
+    display: flex;
+    align-items: flex-end;
+    flex-direction: column;
+    overflow-y: auto;
+    padding: 0px 30px 30px 0px;
+    overflow-x: hidden;
+}
+
+.notification-item {
+    margin-bottom: 30px;
+}
+
+.scrolling-auto {
+    overflow: auto;
+    width:100%;
+}
+
+.new-notification-info {
+    background: $sp-color-primary;
+    width: 10px;
+    height: 10px;
+    border-radius: 50%;
+}
+
diff --git a/ui/src/app/notifications/notifications.component.ts b/ui/src/app/notifications/notifications.component.ts
new file mode 100644
index 0000000..7fa96a4
--- /dev/null
+++ b/ui/src/app/notifications/notifications.component.ts
@@ -0,0 +1,144 @@
+/*
+ * 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, ElementRef, OnInit, ViewChild} from "@angular/core";
+import {RestApi} from "../services/rest-api.service";
+import {NotificationItem, ExistingNotification} from "./model/notifications.model";
+import {ElementIconText} from "../services/get-element-icon-text.service";
+import {NotificationsService} from "./service/notifications.service";
+import {Message} from "@stomp/stompjs";
+import {Subscription} from "rxjs";
+import {RxStompService} from "@stomp/ng2-stompjs";
+import {AuthStatusService} from "../services/auth-status.service";
+import {NotificationUtils} from "./utils/notifications.utils";
+
+@Component({
+    selector: 'notifications',
+    templateUrl: './notifications.component.html',
+    styleUrls: ['./notifications.component.scss']
+})
+export class NotificationsComponent implements OnInit {
+
+    static readonly NOTIFICATIONS_APP_ID = "org.apache.streampipes.sinks.internal.jvm.notification";
+    static readonly NOTIFICATION_TOPIC_PREFIX = "org.apache.streampipes.notifications.";
+    static readonly NOTIFICATION_TITLE_KEY = "title";
+
+    @ViewChild('notificationPane') private notificationContainer: ElementRef;
+
+    notifications: Array<NotificationItem> = [];
+    unreadNotifications: any;
+    existingNotifications: Array<ExistingNotification> = [];
+    currentlySelectedNotification: ExistingNotification;
+    currentlySelectedNotificationId: string;
+
+    pipelinesWithNotificationsPresent: boolean = false;
+    currentOffset: number = 0;
+    previousScrollHeight: number;
+
+    subscription: Subscription;
+    notificationTopic: string;
+
+    newNotificationInfo: Array<boolean> = [];
+
+    constructor(private RestApi: RestApi,
+                private AuthStatusService: AuthStatusService,
+                public elementIconText: ElementIconText,
+                private notificationService: NotificationsService,
+                private rxStompService: RxStompService) {
+        this.notifications = [];
+        this.unreadNotifications = [];
+        this.notificationTopic = NotificationsComponent.NOTIFICATION_TOPIC_PREFIX + AuthStatusService.email;
+    }
+
+    ngOnInit() {
+        this.getPipelinesWithNotifications();
+        this.subscription = this.rxStompService.watch("/topic/" +this.notificationTopic).subscribe((message: Message) => {
+            let notification: NotificationItem = JSON.parse(message.body) as NotificationItem;
+            let notificationId = NotificationUtils.makeNotificationId(notification.correspondingPipelineId, notification.title);
+            if (this.currentlySelectedNotificationId === notificationId) {
+                this.notifications.push(notification);
+            } else {
+                this.newNotificationInfo[notificationId] = true;
+            }
+        });
+    }
+
+    getPipelinesWithNotifications() {
+        this.RestApi.getOwnPipelines().then(pipelines => {
+            this.filterForNotifications(pipelines.data);
+            if (this.existingNotifications.length > 0) {
+                this.pipelinesWithNotificationsPresent = true;
+                this.selectNotification(this.existingNotifications[0]);
+            }
+        });
+    }
+
+    filterForNotifications(pipelines: any) {
+        pipelines.forEach(pipeline => {
+           let notificationActions = pipeline.actions.filter(sink => sink.appId === NotificationsComponent.NOTIFICATIONS_APP_ID);
+             notificationActions.forEach(notificationAction => {
+                let notificationName = notificationAction
+                    .staticProperties
+                    .filter(sp => sp.properties.internalName === NotificationsComponent.NOTIFICATION_TITLE_KEY)
+                    .map(sp => sp.properties.value)[0];
+                let pipelineName = pipeline.name;
+                this.existingNotifications.push({notificationTitle: notificationName,
+                    pipelineName: pipelineName, pipelineId: pipeline._id, notificationId: NotificationUtils.makeNotificationId(pipeline._id, notificationName)});
+             });
+        });
+    }
+
+    getNotifications(notification: ExistingNotification, offset: number, count: number, scrollToBottom: boolean) {
+        this.notificationService.getNotifications(notification, offset, count).subscribe(notifications => {
+            this.notifications.unshift(...notifications);
+            if (scrollToBottom) {
+                setTimeout(() => {
+                    this.notificationContainer.nativeElement.scrollTop = this.notificationContainer.nativeElement.scrollHeight;
+                });
+            } else {
+                setTimeout(() => {
+                    this.notificationContainer.nativeElement.scrollTop = this.notificationContainer.nativeElement.scrollHeight - this.previousScrollHeight;
+                })
+            }
+        })
+    };
+
+    changeNotificationStatus(notificationId) {
+        this.RestApi.updateNotification(notificationId)
+            .then(success => {
+                //this.getNotifications();
+                //this.$rootScope.updateUnreadNotifications();
+            });
+    };
+
+    selectNotification(notification: ExistingNotification) {
+        this.notifications = [];
+        this.currentOffset = 0;
+        this.currentlySelectedNotification = notification;
+        this.currentlySelectedNotificationId = NotificationUtils.makeNotificationIdFromNotification(notification);
+        this.getNotifications(notification, 0, 10, true);
+    }
+
+    onScroll(event: any) {
+        if (this.notificationContainer.nativeElement.scrollTop === 0) {
+            this.currentOffset += 10;
+            this.previousScrollHeight = this.notificationContainer.nativeElement.scrollHeight;
+            this.getNotifications(this.currentlySelectedNotification, this.notifications.length, 10, false);
+        }
+    }
+};
diff --git a/ui/src/app/notifications/notifications.controller.ts b/ui/src/app/notifications/notifications.controller.ts
deleted file mode 100644
index 10ce263..0000000
--- a/ui/src/app/notifications/notifications.controller.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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 * as angular from 'angular';
-
-export class NotificationsCtrl {
-
-    RestApi: any;
-    $rootScope: any;
-    notifications: any;
-    unreadNotifications: any;
-
-    constructor(RestApi, $rootScope) {
-        this.RestApi = RestApi;
-        this.$rootScope = $rootScope;
-        this.notifications = [{}];
-        this.unreadNotifications = [];
-    }
-
-    $onInit() {
-        this.getNotifications();
-    }
-
-    getNotifications() {
-        this.unreadNotifications = [];
-        this.RestApi.getNotifications()
-            .then(notifications => {
-                this.notifications = notifications.data;
-                this.getUnreadNotifications();
-            });
-    };
-
-    getUnreadNotifications() {
-        angular.forEach(this.notifications, (value) => {
-            if (!value.read) {
-                this.unreadNotifications.push(value);
-            }
-        });
-    }
-
-    changeNotificationStatus(notificationId) {
-        this.RestApi.updateNotification(notificationId)
-            .then(success => {
-                this.getNotifications();
-                this.$rootScope.updateUnreadNotifications();
-            });
-    };
-};
-
-NotificationsCtrl.$inject = ['RestApi', '$rootScope'];
diff --git a/ui/src/app/notifications/notifications.html b/ui/src/app/notifications/notifications.html
deleted file mode 100644
index c843127..0000000
--- a/ui/src/app/notifications/notifications.html
+++ /dev/null
@@ -1,72 +0,0 @@
-<!--
-  ~ 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 flex class="page-container page-container-padding" ng-controller="NotificationsCtrl as ctrl">
-    <div flex="100" layout="row" style="padding:0px;background-color:#f6f6f6">
-    </div>
-    <div flex layout="column" layout-fill>
-        <md-content>
-            <section>
-                <md-subheader class="md-primary">Unread Messages</md-subheader>
-                <md-list layout-padding>
-                    <md-list-item class="md-3-line" ng-repeat="notification in ctrl.unreadNotifications">
-                        <sp-button sp-button-flat class="md-icon-button" ng-click="changeNotificationStatus(notification._id)">
-                            <i class="material-icons" style="color: rgb(63,81,181);">markunread</i>
-                            <md-tooltip md-direction="top">
-                                Mark as unread
-                            </md-tooltip>
-                        </sp-button>
-                        <div class="md-list-item-text" style="margin-bottom:10px;margin-top:10px;">
-                            <h3><b>{{notification.title}}</b></h3>
-                            <div layout="row" layout-align="start center" style="margin-bottom:5px;">
-                                <div layout="row" layout-align="start center"><i class="material-icons" style="color: rgb(63,81,181);">access_time</i>
-                                    &nbsp;{{notification.createdAt | date:'dd.MM.yyyy HH:mm'}}&nbsp;
-                                    <i class="material-icons" style="color: rgb(63,81,181);">person</i>&nbsp;{{notification.targetedUser}}
-                                </div>
-                            </div>
-                            <div class="speech-bubble sp-blue-100-border" ng-bind-html="notification.message">
-                            </div>
-                            <md-divider></md-divider>
-                        </div>
-                    </md-list-item>
-                </md-list>
-            </section>
-            <section>
-                <md-subheader class="md-primary">All Messages</md-subheader>
-                <md-list layout-padding>
-                    <md-list-item class="md-3-line" ng-repeat="notification in ctrl.notifications">
-
-                        <div class="md-list-item-text" style="margin-bottom:10px;margin-top:10px;">
-                            <h3><b>{{notification.title}}</b></h3>
-                            <div layout="row" layout-align="start center" style="margin-bottom:5px;">
-                                <div layout="row" layout-align="start center"><i class="material-icons" style="color: rgb(63,81,181);">access_time</i>
-                                    &nbsp;{{notification.createdAt | date:'dd.MM.yyyy HH:mm'}}&nbsp;
-                                    <i class="material-icons" style="color: rgb(63,81,181);">person</i>&nbsp;{{notification.targetedUser}}
-                                </div>
-                            </div>
-                            <div class="speech-bubble" ng-bind-html="notification.message">
-                            </div>
-                            <md-divider></md-divider>
-                        </div>
-                    </md-list-item>
-                </md-list>
-            </section>
-        </md-content>
-        </md-whiteframe>
-    </div>
-</div>
\ No newline at end of file
diff --git a/ui/src/app/notifications/notifications.module.ts b/ui/src/app/notifications/notifications.module.ts
index a44b8b9..f6175f7 100644
--- a/ui/src/app/notifications/notifications.module.ts
+++ b/ui/src/app/notifications/notifications.module.ts
@@ -16,11 +16,55 @@
  *
  */
 
-import * as angular from 'angular';
+import {NgModule} from "@angular/core";
+import {CommonModule} from "@angular/common";
+import {MatTabsModule} from "@angular/material/tabs";
+import {FlexLayoutModule} from "@angular/flex-layout";
+import {CustomMaterialModule} from "../CustomMaterial/custom-material.module";
+import {FormsModule} from "@angular/forms";
+import {NotificationsComponent} from "./notifications.component";
+import {NotificationItemComponent} from "./components/notification-item.component";
+import {NotificationsService} from "./service/notifications.service";
+import {InjectableRxStompConfig, RxStompService, rxStompServiceFactory} from "@stomp/ng2-stompjs";
 
-import {NotificationsCtrl} from './notifications.controller';
+@NgModule({
+	imports: [
+		CommonModule,
+		MatTabsModule,
+		FlexLayoutModule,
+		CommonModule,
+		FlexLayoutModule,
+		CustomMaterialModule,
+		FormsModule
+	],
+	declarations: [
+		NotificationsComponent,
+		NotificationItemComponent
+	],
+	providers: [
+		NotificationsService,
+		{
+			provide: 'RestApi',
+			useFactory: ($injector: any) => $injector.get('RestApi'),
+			deps: ['$injector'],
+		},
+		{
+			provide: RxStompService,
+			useFactory: rxStompServiceFactory,
+			deps: [InjectableRxStompConfig]
+		},
+	],
+	exports: [
+		NotificationsComponent
+	],
+	entryComponents: [
+		NotificationsComponent,
+	]
+})
+export class NotificationModule {
 
-export default angular.module('sp.notifications', [])
-	.controller('NotificationsCtrl', NotificationsCtrl)
-	.name;
+	constructor() {
+	}
+
+}
 
diff --git a/ui/src/app/notifications/service/notifications.service.ts b/ui/src/app/notifications/service/notifications.service.ts
new file mode 100644
index 0000000..e2d3721
--- /dev/null
+++ b/ui/src/app/notifications/service/notifications.service.ts
@@ -0,0 +1,55 @@
+/*
+ *   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 {HttpClient} from "@angular/common/http";
+import {AuthStatusService} from "../../services/auth-status.service";
+import {Observable} from "rxjs";
+import {ExistingNotification, NotificationItem} from "../model/notifications.model";
+import {Injectable} from "@angular/core";
+import {NotificationUtils} from "../utils/notifications.utils";
+
+@Injectable()
+export class NotificationsService {
+
+    constructor(private http: HttpClient,
+                private authStatusService: AuthStatusService) {
+    }
+
+    getNotifications(existingNotification: ExistingNotification, offset: number, limit: number): Observable<NotificationItem[]> {
+        return this.http
+            .get(this.notificationUrl
+                + "?"
+                + "notificationType=" + NotificationUtils.makeNotificationIdFromNotification(existingNotification)
+                + "&"
+                + "offset=" + offset
+                + "&"
+                + "count=" + limit)
+            .map(data => {
+                return data as NotificationItem[];
+            });
+    }
+
+
+
+    private get baseUrl() {
+        return '/streampipes-backend';
+    }
+
+    private get notificationUrl() {
+        return this.baseUrl + '/api/v2/users/' + this.authStatusService.email + '/notifications'
+    }
+}
\ No newline at end of file
diff --git a/ui/src/app/notifications/utils/notifications.utils.ts b/ui/src/app/notifications/utils/notifications.utils.ts
new file mode 100644
index 0000000..c2ee380
--- /dev/null
+++ b/ui/src/app/notifications/utils/notifications.utils.ts
@@ -0,0 +1,30 @@
+/*
+ *   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 {ExistingNotification} from "../model/notifications.model";
+
+export class NotificationUtils {
+
+    static makeNotificationIdFromNotification(existingNotification: ExistingNotification): string {
+       return this.makeNotificationId(existingNotification.pipelineId, existingNotification.notificationTitle);
+    }
+
+    static makeNotificationId(pipelineId: string, notificationTitle: string) {
+        let vizName = notificationTitle.replace(/\\s/g, '-');
+        return pipelineId + '-' + vizName;
+    }
+}
\ No newline at end of file
diff --git a/ui/src/assets/templates/streampipes.html b/ui/src/assets/templates/streampipes.html
index e76499c..df14e4f 100644
--- a/ui/src/assets/templates/streampipes.html
+++ b/ui/src/assets/templates/streampipes.html
@@ -16,14 +16,12 @@
   ~
   -->
 
-<div ng-controller="ToolbarController as toolbarCtrl">
+<div flex ng-controller="ToolbarController as toolbarCtrl" class="display: flex;min-height: 100vh; max-height: 100vh;">
     <div ui-view="spNavbar"></div>
-    <div layout="row">
-        <div md-whiteframe="6" style="width:40px;height:calc(100vh - 50px);color:white;" class="sp-darkblue"
+    <div style="display: flex;flex-direction: row; height: calc(100vh - 50px);">
+        <div md-whiteframe="6" style="width:40px;color:white;" class="sp-darkblue"
              ui-view="spIconBar"></div>
-        <div flex="100">
             <md-content class="sp-fade-only-enter" id="content" ui-view="spMain"
-                        style="background:#fff !important; height:calc(100vh - 50px);min-height:calc(100vh - 50px)"></md-content>
-        </div>
+                        style="background:#fff !important;flex-grow: 1;"></md-content>
     </div>
 </div>
\ No newline at end of file
diff --git a/ui/src/index.html b/ui/src/index.html
index eafbd4d..8f5eb1e 100644
--- a/ui/src/index.html
+++ b/ui/src/index.html
@@ -106,7 +106,7 @@
 </app-root>
 
 <div layout="column" style="height:100%;" role="main" tabindex="-1" flex>
-    <div class="sp-fade" ui-view="container" flex layout="column" style="min-height: 100vh">
+    <div class="sp-fade" ui-view="container" flex layout="column" style="min-height: 100vh; max-height: 100vh;">
         <div class="app-loading">
             <div class="animated-logo">
                 <img src="/assets/img/favicon/favicon-128.png" class="logo-splash"/>
diff --git a/ui/src/scss/sp/main.scss b/ui/src/scss/sp/main.scss
index 39376a0..e626924 100644
--- a/ui/src/scss/sp/main.scss
+++ b/ui/src/scss/sp/main.scss
@@ -363,7 +363,7 @@ md-content {
   margin: 10px;
   border: 1px solid #cccccc;
   min-height: calc(100% - 20px);
-  height: 100%;
+  //height: 100%;
 }
 
 .page-container-no-border {